商城要点

发布时间 2023-07-23 18:33:52作者: jinganglang567

创建项目时选spring inital选项 maven选可以产生目录结构的 spring的版本要选低一点,否则很容易出现导入失败的错误。

配置数据库通用

spring.datasource.url=jdbc:mysql://localhost:3306/store?serverTimezone=GMT%2b8
spring.datasource.username=root
spring.datasource.password=123456

在测试文件下测试数据库连接

 @Autowired
    private DataSource dataSource;
 @Test
    void getConnection() throws SQLException {
        System.out.println(dataSource.getConnection());
    }

运行getConnection方法,若成功返回HikariProxyConnection@189194499 wrapping com.mysql.cj.jdbc.ConnectionImpl@2b0e9f30则说明成功连接数据库,其中Hikari是一个连接池,用来管理数据库的连接对象,是springboot默认内部整合的连接池,该连接池号称世界上最快的连接池,底层仍然采用c3p0来管理数据库的连接对象

静态资源无法正常加载

将静态资源(SpringBoot电脑商城项目-V1.0\tools\pages_src\pages*)复制到static目录下重启项目并尝试访问localhost:8080/web/login.html(因为static是默认根目录,所以不是localhost:8080/static/web/login.html)

如果这个过程访问失败,原因是idea对于JS代码的兼容性较差,编写了js代码但是有的时候不能正常去加载,解决办法有以下四种

  1. clear-install:依次点击MavenProject->store->Lifecycle->clean,等待清哩项目完毕后点击同目录下的install重新部署

  2. idea缓存清理:点击File下的Invalidate Caches/Restart…然后在弹出的窗口中选择Invalidate and Restart,此时就会自动清除缓存并重新启动idea

  3. rebuild重新构建:点击工具栏的Build下的Rebuild Project

  4. 重启电脑

实体类为什么要实现序列化

Serializable序列化接口没有任何方法或者字段,只是用于标识可序列化的语义。实现了Serializable接口的类可以被ObjectOutputStream转换为字节流,同时也可以通过ObjectInputStream再将其解析为对象。例如,我们可以将序列化对象写入文件后,再次从文件中读取它并反序列化成对象,也就是说,可以使用表示对象及其数据的类型信息和字节在内存中重新创建对象。

而这一点对于面向对象的编程语言来说是非常重要的,因为无论什么编程语言,其底层涉及IO操作的部分还是由操作系统其帮其完成的,而底层IO操作都是以字节流的方式进行的,所以写操作都涉及将编程语言数据类型转换为字节流,而读操作则又涉及将字节流转化为编程语言类型的特定数据类型。而Java作为一门面向对象的编程语言,对象作为其主要数据的类型载体,为了完成对象数据的读写操作,也就需要一种方式来让JVM知道在进行IO操作时如何将对象数据转换为字节流,以及如何将字节流数据转换为特定的对象,而Serializable接口就承担了这样一个角色。

序列化是指把对象转换为字节序列的过程,我们称之为对象的序列化,就是把内存中的这些对象变成一连串的字节(bytes)描述的过程。

而反序列化则相反,就是把持久化的字节文件数据恢复为对象的过程。

常用场景:

需要把内存中的对象状态数据保存到一个文件或者数据库中的时候,这个场景是比较常见的,例如我们利用mybatis框架编写持久层insert对象数据到数据库中时;
网络通信时需要用套接字在网络中传送对象时,如我们使用RPC协议进行网络通信时;
当我们需要把对象的状态信息通过网络进行传输,或者需要将对象的状态信息持久化,以便将来使用时都需要把对象进行序列化。

那为什么还要继承Serializable。那是存储对象在存储介质中,以便在下次使用的时候,可以很快捷的重建一个副本。

因为Serializable就相当于一个标识接口,这个Serializable接口其实是给jvm看的,告诉jvm,我不对这个类做序列化了,你(jvm)帮我序列化就好了。

定义异常

1.为什么会有异常:

比如,用户在进行注册时可能会产生用户名被占用的错误,这时需要抛出一个异常

2.怎么处理异常:

异常不能用RuntimeException,太笼统了,开发者没办法第一时间定位到具体的错误类型上,我们可以定义具体的异常类型来继承这个异常.
正常的开发中异常又要分等级,可能是在业务层产生异常,可能是在控制层产生异常,所以可以创建一个业务层异常的基类,起名ServiceException异常,并使其继承RuntimeException异常
后期开发业务层时具体的异常可以再继承业务层的异常ServiceException

/**
 * 因为整个业务的异常只有一种情况下才会产生:只有运行时才会产生,不运行不会产生
 * 所以要求业务层的异常都要继承运行时异常RuntimeException并且重写父类的所有构造方法以便后期能抛出自已定义的异常
 */
public class ServiceException extends RuntimeException{
    //什么也不返回
    public ServiceException() {
        super();
    }

    //返回异常信息(常用)
    public ServiceException(String message) {
        super(message);
    }

    //返回异常信息和异常对象(常用)
    public ServiceException(String message, Throwable cause) {
        super(message, cause);
    }

    public ServiceException(Throwable cause) {
        super(cause);
    }

    protected ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

equals和hashcode

equal和hashCode是Java中用于处理对象相等性的两个方法。

  1. equals()方法: equals方法用于比较两个对象是否相等。每个对象都可以根据自己的逻辑来实现equals方法。默认情况下,equals方法比较的是对象的引用是否相等,即是否指向相同的内存地址。但是,通常我们需要根据对象的内容来判断它们是否相等。因此,我们需要重写equals方法,根据我们自定义的逻辑来实现对象相等性的比较。

  2. hashCode()方法: hashCode方法返回对象的哈希码值(即散列码)。哈希码值是一个整数,用于快速定位对象在哈希表中的位置。在Java中,哈希表(HashMap、HashSet等)使用hashCode方法来确定对象在表中的位置。因此,hashCode方法的实现需要与equals方法保持一致性:如果两个对象相等(根据equals方法的判断),它们的哈希码值应该相等。反之,如果两个对象的哈希码值相等,它们不一定相等。

在重写equals方法时,通常也需要重写hashCode方法,以确保对象在使用哈希表的容器中正常工作。这两个方法在保证正确性和一致性方面是紧密相关的。

使用equals和hashcode

  1. enter+insert
  2. 点击euqals() and hashCode()
  3. 勾选Accept…和Use这两段话,并且选择Template为IntelliJ Default
  4. 一路next到底

spring

ssm框架开发项目的时候需要在实体类上面加@Component然后spring才能自动进行对象的创建维护,而springboot不再需要,因为springboot遵循的原则是约定大于配置,如果字段名称相同那就可以自动完成字段的初始化

ssm框架开发项目的时候需要在mapper接口上加@Mapper用于自动生成相应的接口实现类,在springboot也可以这样,但是后续会有很多mapper接口,每个接口分别加@Mapper太麻烦了,所以在启动类类里面指定当前项目的mapper接口在哪,然后项目启动的时候会自动加载所有的接口


@SpringBootApplication
@MapperScan("com.cy.mapper")
public class StoreApplication {

    public static void main(String[] args) {
        SpringApplication.run(StoreApplication.class, args);
    }

}

mybatis规定需要用占位符来占位,并给占位符起一个变量的名字,且变量的名字需要在占位符#{}内部,且占位符的要与实体类中的属性相同

<!--useGeneratedKeys="true"表示开启某个字段的值递增(大部分都是主键递增)
    keyProperty="uid"表示将表中哪个字段进行递增
    -->
<insert id="insert" useGeneratedKeys="true" keyProperty="uid">
    insert into t_user(
        username,`password`,salt,phone,email,gender,avatar,is_delete,
        created_user,created_time,modified_user,modified_time
    ) values (
    #{username},#{password},#{salt},#{phone},#{email},#{gender},#			{avatar},#{isDelete},#{createdUser},#{createdTime},#{modifiedUser},#	{modifiedTime}
    )
</insert>

resultmap的使用

   <resultMap id="UserEntityMap" type="com.cy.store.entity.User">
        <!--将表的字段和类的属性名不一致的进行匹配指定,名称一致的也可以指定,但没必要
            但是,在定义映射规则时无论主键名称是否一致都不能省
            column属性:表示表中的字段名称
            property属性:表示类中的属性名称
            -->
        <id column="uid" property="uid"></id>
        <result column="is_delete" property="isDelete"></result>
        <result column="created_user" property="createdUser"></result>
        <result column="created_time" property="createdTime"></result>
        <result column="modified_user" property="modifiedUser"></result>
        <result column="modified_time" property="modifiedTime"></result>
    </resultMap>

@autowired mapper出错

因为测试方法要用到mapper层的接口来访问刚刚写的两个方法,所以要在类里面声明UserMapper对象:即private UserMapper userMapper;且需要加上@Autowired完成值的初始化,但此时会发现提示"Could not autowire.No beans of’UserMapper’type found",报错原因是idea有自动检测的功能,在java中接口是不能够直接创建bean的,所以idea认为这个语法不合理,但本质上在项目启动时mybatis自动创建了接口的动态代理实现类,所以从项目的运行角度讲这不能算是错.解决办法:

在Settings里面搜索inspections,依次找到Spring->Spring Core->Code->Autowiring for Bean Class然后将Severity的Error改为Warning

md5加密算法

 private String getmd5gpassword(String password,String salt){
        for (int i = 0; i <3 ; i++) {
            password= DigestUtils.md5DigestAsHex((salt+password+salt).getBytes()).toUpperCase();
        }
        return password;
    }

泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。

泛型类,泛型接口,泛型方法的使用可以通过继承,实现抽象了所有公共方法,避免了每次都要写相同的代码。

返回给前端的工具类

package com.cy.store.utils;

import java.io.Serializable;

//创建响应前端数据格式类

public class jsonresult<E> implements Serializable {

    //状态码
    private Integer state;
    //描述信息
    private String message;
    //数据类型不确定,用E表示任何的数据类型,一个类里如果声明的有泛型的数据类型,类也要声明为泛型
    private E data;

    public jsonresult(){}

    public jsonresult(Integer state) {
        this.state = state;
    }

    public jsonresult(Integer state, E data) {
        this.state = state;
        this.data = data;
    }

    public jsonresult(Throwable e) {
        this.message = e.getMessage();
    }

    public Integer getState() {
        return state;
    }

    public void setState(Integer state) {
        this.state = state;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public E getData() {
        return data;
    }

    public void setData(E data) {
        this.data = data;
    }
}

统一处理异常优化

  @ExceptionHandler(serviceexception.class)
    public jsonresult<Void> handleexception(Throwable e){
        jsonresult<Void> jsonresult = new jsonresult<>();
        if (e instanceof usernameduplicateexception){
            jsonresult.setState(4000);
            jsonresult.setMessage("用户名被占用");
        } else if (e instanceof insertexception){
            jsonresult.setState(5000);
            jsonresult.setMessage("注册异常");
        }
        return jsonresult;
    }

http://localhost:8080/users/reg/?username=er2re&password=1234567

ajax

  • 使用ajax()时需要传递一个方法体作为方法的参数来使用(一对大括号就是一个方法体)
  • ajax接受多个参数时,参数与参数之间使用","分割
  • 每一组参数之间使用":"进行分割
  • 参数的组成部分一个是参数的名称(不能随便定义),另一个是参数的值(必须用字符串来表示)
  • 参数的声明顺序没有要求
$.ajax({
    url: "",
    type: "",
    data: "",
    dataType: "",
    success: function() {
        
    },
    error: function() {
        
    }
});

//serialize这个API会自动检测该表单有什么控件,每个控件检测后还会获取每个控
					// 件的值,拿到这个值后并自动拼接成形如username=Tom&password=123的结构
					data: $("#form-reg").serialize(),
error: function (xhr) { //如果问题不在可控范围内,服务器就不会返回自己定
						//义的json字符串:{state: 4000,message: "用户名已经被占用",data: null}
						//而是返回一个XHR类型的对象,该对象也有一个状态码名字是status
						alert("注册时产生未知的错误!"+xhr.status);
					}

一个功能模块的开发 持久层-业务层-控制层-前端响应

持久层mapper语句, 主要是后端数据库增删改查操作 mapper.java和mapper.xml主要负责

业务层写响应的逻辑 service和impl主要是执行逻辑 比较复杂的逻辑和函数,和上面一层不同

控制层处理异常 设计请求处理请求 controller主要实现获取参数和前端传递参数

sprinboot前后端数据传参,两种,直接传实体类,或者直接传参数

请求处理方法的参数列表设置为非pojo类型:

SpringBoot会直接将请求的参数名和方法的参数名直接进行比较,如果名称相同则自动完成值的依赖注入

请求处理方法的参数列表设置为pojo类型:

SpringBoot会将前端的url地址中的参数名和pojo类的属性名进行比较,如果这两个名称相同,则将值注入到pojo类中对应的属性上

这两种方法都没有使用注解等等花里胡哨的,却能正常使用,原因是springboot是约定大于配置的,省略了大量配置以及注解的编写

session获取参数

   /**
     * 获取session对象中的uid
     * @param session session对象
     * @return 当前登录的用户uid的值
     */
    public final Integer getUidFromSession(HttpSession session) {
        //getAttribute返回的是Object对象,需要转换为字符串再转换为包装类
        return Integer.valueOf(session.getAttribute("uid").toString());
    }

    public final String getUsernameFromSession(HttpSession session) {
        return session.getAttribute("username").toString();
    }

服务器本身自动创建有session对象,已经是一个全局的session对象,所以我们需要想办法获取session对象:如果直接将HttpSession类型的对象作为请求处理方法的参数,这时springboot会自动将全局的session对象注入到请求处理方法的session形参上:

自定义拦截器与在项目注册拦截器

拦截器的作用是将所有的请求统一拦截到拦截器中,可以在拦截器中定义过滤的规则,如果不满足系统设置的过滤规则,该项目统一的处理是重新去打开login.html页面(重定向和转发都可以,推荐使用重定向)
拦截器在springboot中本质是依靠springMVC完成的.springMVC提供了一个HandlerInterceptor接口用于表示定义一个拦截器
1.所以想要使用拦截器就要定义一个类并使其实现HandlerInterceptor接口,在store下建包interceptor,包下建类LoginInterceptor并编写代码:

public class logininterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //通过HttpServletRequest对象来获取session对象
        Object obj = request.getSession().getAttribute("uid");
        if (obj == null) { //说明用户没有登录过系统,则重定向到login.html页面
            //不能用相对路径,因为这里是要告诉前端访问的新页面是在哪个目录下的新
            //页面,但前面的localhost:8080可以省略,因为在同一个项目下
            response.sendRedirect("/web/login.html");
            //结束后续的调用
            return false;
        }
        //放行这个请求
        return true;

    }
}

注册过滤器的技术:借助WebMvcConfigure接口将用户定义的拦截器进行注册.所以想要注册过滤器需要定义一个类使其实现WebMvcConfigure接口并在其内部添加黑名单(在用户登录的状态下才可以访问的页面资源)和白名单(哪些资源可以在不登录的情况下访问:①register.html②login.html③index.html④/users/reg⑤/users/login⑥静态资源):

下拉列表二级关联