spring boot全面复习第二天

spring boot 学习总结 第二天

10-restful

在观察控制台打印结果时,发现了日志控制的每个方法的运行时间,推测应该是做了拦截器,点开示例代码,用日志实现的运行时间打印,

com.imooc.springboot.mvc.LogInterceptor  : preHandle
c.imooc.springboot.mvc.TimeInterceptor   : 开始时间:1617872090814
c.imooc.springboot.mvc.TimeInterceptor   : 结束时间:1617872090819
com.imooc.springboot.mvc.LogInterceptor  : postHandle
c.imooc.springboot.mvc.TimeInterceptor   : 接口执行时间:5毫秒
com.imooc.springboot.mvc.LogInterceptor  : afterCompletion

1. interceptor拦截器

  • 定义:类似于过滤器,对资源进行拦截;
  • 特点:
    • 在请求到达Controller经过拦截器,响应也经过拦截器
    • 只能拦截Controller的请求,不能拦截JSP的请求;
    • 可以中断用户请求;
  • 好处:
    • 如果有公共代码,可以放入拦截器中减少代码冗余;
  • 应用场景:日志输出、资源处理;
1.1 配置一个拦截器
  • 自定义一个拦截器类,实现HandlerInterceptor类,实现preHandle、postHandle、afterCompletion方法
/**
 * 自定义拦截器,打印请求时间
 *
 * @author 沈晨曦
 */
@Slf4j
@Component
public class TimeInterceptor implements HandlerInterceptor {
    // 定义threadLocal类
    private ThreadLocal<Long> threadLocalStart = new ThreadLocal<>();
    private ThreadLocal<Long> threadLocalEnd = new ThreadLocal<>();


    /**
     * 前置拦截,在请求到达Controller之前进行拦截
     *
     * @param request  请求对象,包含请求地址、头、参数、body等
     * @param response 响应对象,包含输入流、响应body等
     * @param handler  拦截的对象
     * @return true:放行,false终止请求
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取请求开始时间
        long startTime = System.currentTimeMillis();
        // 2. 把此变量放进本地线程中的缓存
        threadLocalStart.set(startTime);
        // 3. 控制台输出日志,
        log.info("开始时间:{}", getFormat(startTime));
        // 4. 放行,请求到达Controller
        return true;
    }

    /**
     * 对毫秒值格式化为年月日 精确到毫秒
     *
     * @param startTime 开始时间
     * @return 返回一个年月日字符串
     */
    private String getFormat(long startTime) {
//        把获取出来的毫秒值转换为当前年月日格式
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS")
                .format(new Date(startTime));
    }


    /**
     * 在preHandle返回TRUE,并且Controller执行完毕,视图未渲染前拦截
     *
     * @param request      请求对象,包含请求地址、头、参数、body等
     * @param response     响应对象,包含输入流、响应body等
     * @param handler      拦截的对象
     * @param modelAndView 视图模型对象
     * @throws Exception 异常
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        long endTime = System.currentTimeMillis();
        threadLocalEnd.set(endTime);
        log.info("结束时间:{}", getFormat(endTime));
    }

    /** 在preHandle返回TRUE,dispatcherServlet进行视图渲染之后,用于清理资源
     * @param request  请求对象,包含请求地址、头、参数、body等
     * @param response 响应对象,包含输入流、响应body等
     * @param handler  拦截的对象
     * @param ex       异常对象
     * @throws Exception 异常
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        long startTime = threadLocalStart.get();
        long endTime = threadLocalEnd.get();
        log.info("接口执行时间:{}毫秒", endTime - startTime);
    }
}
  • 定义一个配置类,实现WebMvcConfigurer接口,重写addInterceptors方法,并使用InterceptorRegistry参数的addInterceptor方法把我们自定义的拦截器对象加入
/** 拦截器配置类
 * @author 沈晨曦
 */
@Configuration
public class WebConfigurer implements WebMvcConfigurer {

    /**
     * 日志输出
     */
    @Autowired
    private LogInterceptor logInterceptor;

    /**
     * 时间输出
     */
    @Autowired
    private TimeInterceptor timeInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(logInterceptor);
        registry.addInterceptor(timeInterceptor);
    }
}
  • 效果演示

    • preHandle返回true
    com.example.interceptor.LogInterceptor   : preHandle
    com.example.interceptor.TimeInterceptor  : 开始时间:2021-04-09 10:37:30:954
    com.example.service.impl.StuServiceImpl  : 查询学生列表成功
    com.example.interceptor.TimeInterceptor  : 结束时间:2021-04-09 10:37:31:032
    com.example.interceptor.LogInterceptor   : postHandle
    com.example.interceptor.TimeInterceptor  : 接口执行时间:78毫秒
    com.example.interceptor.LogInterceptor   : afterCompletion
    
    • preHandle返回false
    com.example.interceptor.TimeInterceptor  : preHandle:开始时间:2021-04-09 11:00:20:433
    
1.2 HandlerInterceptor的preHandle、postHandle、afterCompletion方法详解
  • preHandle
    • 调用时间:在请求到达Controller之前进行拦截
    • 参数:
      • request:http请求对象;
      • Response:http响应对象;
      • handle:被拦截的对象
    • 返回值:
      • true:放行
      • false:中断执行,请求不会进入postHandle和afterCompletion
    • 执行顺序:链式Intercepter情况下,Intercepter按照声明顺序一个接一个执行
  • postHandle
    • 调用前提:preHandle返回true;
    • 调用时间:Controller方法处理完之后,DispatcherServlet进行视图渲染之前,在此方法可以对ModelAndView进行处理;
    • 执行顺序:链式Intercepter情况下,Intercepter按照声明的顺序倒序执行;
    • 参数:在preHandle参数下多了一个ModelAndView
      • ModelAndView:视图对象
  • afterCompletion
    • 调用前提:preHandle返回true
    • 调用时间:DispatcherServlet进行视图渲染之后
    • 参数:
      • Exception:异常对象
    • 应用:多用于清理资源
1.3 自定义configuration、Bean
  • Configuration:用来标识该类是一个配置类,
  • @Component:把当前类放入IOC容器中

2. 过滤器(filter)

  • 定义:处于客服端和服务器之间一道过滤网,可以过滤一些不符合要求的请求

  • 应用:

    • 统一字符集
    • session校验、用户权限、token
    • 放行页面资源文件,如登录页面、css等资源文件
  • 特点:

    • 依赖于Servlet容器
    • 一个过滤器实例只在初始化时调用一次
  • 过滤器的使用:

    • 随着web应用的初始化而启动,web应用的销毁而销毁;
    • 启动服务器加载过滤器的实例,调用init()初始化实例;
    • 每一次请求时只调用方法doFilter()进行过滤
    • 停止服务器则调用 destroy()销毁实例;
  • 自定义过滤器

    • 定义一个类,实现@Filter接口,并加上WebFilter注解,指定过滤哪些资源
    package com.example.filter;
    
    import lombok.extern.slf4j.Slf4j;
    
    import javax.servlet.*;
    import javax.servlet.annotation.WebFilter;
    import java.io.IOException;
    
    /**
     * @author 沈晨曦
     * @version 2021/4/9 11:51:17
     */
    
    
    @WebFilter(urlPatterns = "/*")
    @Slf4j
    public class MyFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            log.info("过滤器初始化");
        }
    
        /**
         * @param servletRequest  请求对象
         * @param servletResponse 响应对象
         * @param filterChain     过滤对象
         * @throws IOException      异常
         * @throws ServletException 异常
         */
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    //        字符集统一
            servletRequest.setCharacterEncoding("UTF-8");
            servletResponse.setContentType("text/html;charset=utf-8");
            log.info("过滤器执行");
    //        放行
            filterChain.doFilter(servletRequest, servletResponse);
        }
    
        @Override
        public void destroy() {
            log.info("过滤器销毁");
        }
    }
    
    • 启动类上加入@ServletCompoentScan注解

3. 监听器

  • 定义:能够监听服务器的启动和销毁,如:ServletContext、HttpSeesion、ServletRequest的创建和销毁;

  • 场景:监听Servlet上下文来初始化一些数据、监听HTTPSeesion用来获取在线人数等;

  • SpringApplicationRunListener

    • 时机:项目启动的全过程都可以通过此监听

    • 配置:需要在spring.factories

      org.springframework.book.SpringApplicationRunListener=自己写的监听实现类的全类名
      
  • ApplicationListener

    • 时机:项目启动的全过程都可以选择监控
    • 配置:在启动类上注册监听器
  • ApplicationContextInitializer

    • 时机:项目IOC容器初始化时
    • 配置:需要在spring.factories
  • 服务层返回一个对象

@Service
public class UserServiceImpl {

    /**
     * 获取用户信息
     * @return
     */
    public User getUser() {
        // 实际中会根据具体的业务场景,从数据库中查询对应的信息
        return new User(1, "zs", "13");
    }
}

  • 自定义监听器
/**
 * 使用 ApplicationListener 来初始化一些数据到 application 域中的监听器
 */
@Component
public class MyServletContextListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        // 先获取到 application 上下文
        ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
        // 获取对应的 service
        UserService userService = applicationContext.getBean(UserService.class);
        User user = userService.getUser();
        // 获取 application 域对象,将查到的信息放到 application 域中
        ServletContext application = applicationContext.getBean(ServletContext.class);
        application.setAttribute("user", user);
    }
}

  • 配合Controller获取数据
@RestController
@RequestMapping("/listener")
public class TestController {

    @GetMapping("/user")
    public User getUser(HttpServletRequest request) {
        ServletContext application = request.getServletContext();
        return (User) application.getAttribute("user");
    }
}

4. 过滤器、拦截器、监听器区别、应用

三者关系:请求会先到达过滤器,再是拦截器,最后是Controller

  • 过滤器(filter):如果配置了过滤器,请求会率先到达过滤器;
    • 应用:字符集处理、页面放行、登录时的token认证
  • 监听器(listener):监听项目启动和关闭,只在项目启动和关闭时触发
    • 应用:处理项目初始化
  • 拦截器(Interceptor):对Controller的请求进行拦截
    • 应用:日志记录,业务处理、视图处理、异常处理
  • 拦截器和过滤器区别:
    • 拦截器基于Java反射,过滤器基于函数回调
    • 拦截器不依赖Servlet容器,过滤器依赖Servlet容器
    • 拦截器只拦截Controller的请求,过滤器可以拦截所有请求
    • 拦截器可以访问请求上下文,值栈里的对象,过滤器不能
    • 执行顺序:过滤前-拦截前-Controller-拦截后-过滤后

5. restful风格

  • 定义:一种约束条件和原则,满足这些约束条件和原则的应用程序或设计就是RESTful,RESTful风格的web服务是一种ROA(面向资源的架构);
  • 原则:
    • 对网络上所有的资源都有一个资源标识符
    • 对资源的操作不会改变标识符
    • 同一资源有多种表现形式(json,xml)
    • 所有操作都是无状态的(Stateless)
http方法资源操作幂等安全
GETSELECT
POSTINSERT
PUTUPDATE
DELETEDELETE

幂等性:对同一REST接口的多次访问,得到的资源状态是相同的。

安全性:对该REST接口访问,不会使服务器端资源的状态发生改变。

  • 传统url风格
http://localhost:8080/user/query/1 GET 根据用户id查询用户数据

http://localhost:8080/user/save POST 新增用户

http://localhost:8080/user/update POST 修改用户信息

http://localhost:8080/user/delete GET/POST 删除用户信息
  • RESRful
http://localhost:8080/user/1 GET 根据用户id查询用户数据

http://localhost:8080/user POST 新增用户

http://localhost:8080/user PUT 修改用户信息

http://localhost:8080/user/1 DELETE 删除用户信息
5.1 DTO、VO
  • DTO(data transfer Object):数据传输对象,展示层与服务层之间的数据传输对象
  • VO(view object):视图对象,用于展示层,把某个指定页面或组件的所有数据封装起来
  • 场景:分页查询时,前端传过来的分页参数、还有带有字段的查询如根据name或者档案号查询;我们用dto来接收;
  • 在RESTful风格中,我们通常会定义一个Response类或Result类,来返回数据,类中通常有响应状态、响应数据、响应message等;
5.3 RESTful风格规范
  1. 使用名词而不是动词

    不要使用:/getAllUsers、/createNewUser、/deleteAllUser

  2. Get方法和查询参数不应该涉及状态改变:

    使用PUT, POST 和DELETE 方法 而不是 GET 方法来改变状态,不要使用GET 进行状态改变:

  3. 使用复数名词

    不要混淆名词单数和复数,为了保持简单,只对所有资源使用复数。

    /cars 而不是 /car

    /users 而不是 /user

    /products 而不是 /product

    /settings 而不是 /setting

  4. 使用子资源表达关系

如果一个资源与另外一个资源有关系,使用子资源:

GET /cars/711/drivers/ 返回 car 711的所有司机

GET /cars/711/drivers/4 返回 car 711的4号司机

  1. 使用Http头声明序列化格式

    在客户端和服务端,双方都要知道通讯的格式,格式在HTTP-Header中指定

    Content-Type 定义请求格式

    Accept 定义系列可接受的响应格式

  2. 版本化API

    使得API版本变得强制性,不要发布无版本的API,使用简单数字,避免小数点如2.5.

    如:/blog/api/v1

  3. 使用Http状态码处理错误

    2XX (请求成功)表示成功处理了请求的状态代码。

    200   (成功)  服务器已成功处理了请求。 通常,这表示服务器提供了请求的网页。 
    201   (已创建)  请求成功并且服务器创建了新的资源。 
    202   (已接受)  服务器已接受请求,但尚未处理。 
    203   (非授权信息)  服务器已成功处理了请求,但返回的信息可能来自另一来源。 
    204   (无内容)  服务器成功处理了请求,但没有返回任何内容。 
    205   (重置内容) 服务器成功处理了请求,但没有返回任何内容。
    206   (部分内容)  服务器成功处理了部分 GET 请求。
    

    3XX (请求被重定向)表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向。

    300   (多种选择)  针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。 
    301   (永久移动)  请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。
    302   (临时移动)  服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
    303   (查看其他位置) 请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。
    304   (未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。 
    305   (使用代理) 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理。 
    307   (临时重定向)  服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
    

    4XX (请求错误)这些状态代码表示请求可能出错,妨碍了服务器的处理。

    400   (错误请求) 服务器不理解请求的语法。 
    401   (未授权) 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。 
    403   (禁止) 服务器拒绝请求。
    404   (未找到) 服务器找不到请求的网页。
    405   (方法禁用) 禁用请求中指定的方法。 
    406   (不接受) 无法使用请求的内容特性响应请求的网页。 
    407   (需要代理授权) 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理。
    408   (请求超时)  服务器等候请求时发生超时。 
    409   (冲突)  服务器在完成请求时发生冲突。 服务器必须在响应中包含有关冲突的信息。 
    410   (已删除)  如果请求的资源已永久删除,服务器就会返回此响应。 
    411   (需要有效长度) 服务器不接受不含有效内容长度标头字段的请求。 
    412   (未满足前提条件) 服务器未满足请求者在请求中设置的其中一个前提条件。 
    413   (请求实体过大) 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。 
    414   (请求的 URI 过长) 请求的 URI(通常为网址)过长,服务器无法处理。 
    415   (不支持的媒体类型) 请求的格式不受请求页面的支持。 
    416   (请求范围不符合要求) 如果页面无法提供请求的范围,则服务器会返回此状态代码。 
    417   (未满足期望值) 服务器未满足"期望"请求标头字段的要求。
    

    5XX(服务器错误)这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。

    500   (服务器内部错误)  服务器遇到错误,无法完成请求。 
    501   (尚未实施) 服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码。 
    502   (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。 
    503   (服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。 
    504   (网关超时)  服务器作为网关或代理,但是没有及时从上游服务器收到请求。 
    505   (HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。
    

6. ThreadLocal类

  • ThreadLocal的作用:
    • 在java8的时候,每个线程内部都有一块特殊的空间,ThreadLocalMap,这个map集合的key值是当前线程本身的id,value是我们存进去的实例值的变量副本; 每个线程内部的ThreadLocalMap由自己维护,别的线程不能获取当前线程的变量副本,这也形成的线程的隔离;
    • ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
  • 应用场景:
    • 可以用来Session管理,存储一些共享值;

12-jpa(Java Persistence API)

  • 定义:用于对象持久化的API
  • 描述:本质是一种ORM规范并非框架,JPA只提供了一些相关的接口。接口并不能直接使用,JPA底层需要JPA实现,JPA是Hibernate功能的一个子集

1. JPA和Hibernate关系

  • JPA是Hibernate的抽象,如同JDBC和JDBC驱动的关系
  • JPA是规范,Hibernate是实现,HIbernate是ORM框架也是JPA实现

2. JPA优势

  • 标准化:
  • 简单易用:
  • 查询能力同JDBC媲美:
  • 支持面向对象:

3. springboot整合JPA

  • pom:需要导入jpa依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
  • 配置属性:
spring:
  jpa:
    hibernate:
      ddl-auto: update # update更新或创建数据表结构,ddl代表数据表的生成策略,没有的时候就会创建
    show-sql: true #控制台显示SQL,
    #逆向工程(由表生成实体类)
    generate-ddl: true
    #指定数据库的类型
    database: mysql
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
  • 实体类model:同Mybatis的pojo一样
//使用JPA注解配置映射关系
@Entity //告诉JPA这是一个实体类(和数据表映射的类)
@Data // 生成get/set方法
@Table(name = "user") //@Table来指定和哪个数据表对应;如果省略默认表名就是user;
public class User {

    @Id //这是一个主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增主键
    private Integer id;

    @Column(name = "name",length = 50) //这是和数据表对应的一个列
    private String name;
    @Column //省略默认列名就是属性名
    private String email;
  • repository:即mapper层,类似于Mybatis plus的方式
//继承JpaRepository来完成对数据库的操作
public interface UserRepository extends JpaRepository<User,Integer> {
    public List<User> findByAge(Integer age);

    Page<User> findByName(String name);
}

14-mybatis

1. spring boot整合Mybatis

  • pom:核心坐标
 		<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
  • 配置属性
spring:
#  数据库连接
  datasource:
    url: jdbc:mysql://192.168.5.128/db1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
# mybatis
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml
  configuration: #sql打印日志输出
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  type-aliases-package: com.example.pojo
  • 编码

    • 启动类:启动类需要加上@MapperScan扫描mapper,或者直接在每一个mapper上添加@mapper
    • pojo
    @Data
    @TableName("stu")// 指定表名
    public class Stu {
        @ApiModelProperty("stu主键ID")
        @TableId(type = IdType.AUTO)
        private Integer id;
        @ApiModelProperty("学生姓名")
        private String name;
        @ApiModelProperty("学生年龄")
        @MyAnno4Valid(message = "年龄不能小于0")
        private Integer age;
    }
    
    • mapper
    public interface UserMapper {
        /** 插入一条数据
        *
        /
        int insert(Stu stu);
    }
    
    • mapper.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.imooc.springboot.mybatis.mapper.UserMapper">
    
    <insert id="insert" parameterType="Stu">
    #  获取主键自增的key
        <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
          SELECT LAST_INSERT_ID()
        </selectKey>
        insert into stu (id, name,age)
        values (#{id,jdbcType=VARCHAR}, #{name,jdbcType=INTEGER}, #{age,jdbcType=TIMESTAMP})
      </insert>
    </mapper>
    
  • mapper.xml注意事项:

    • namespace:名称空间,是对应mapper的全限定类名
    • id:是对应方法的方法名
    • parameterType:参数类型,如果没有在属性里配置起别名则需要写对应实体类的全限定类名
    • resultType:返回值类型
1.1 怎么获取自增的ID
  • Mybatis中,利用<selectKey>标签,使用LAST_INSERT_ID()把结果值封装到指定的字段
    • KeyProperty:对应属性的字段
    • order:执行顺序
  • 在insert标签中,配置两个属性
<insert id="insert" parameterType="_int" useGeneratedKeys="true" keyColumn="id" >
  • mybatis plus中。在实体类上的ID字段加上@TableId(type = IdType.AUTO)即可

2. spring boot 集成Mybatis plus

Mybatis plus 对Mybatis做了增强,mapper、service都有增强,需要继承增强的接口

  • pom
		<dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.1</version>
        </dependency>
        <!--mysql依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
  • yml
spring:
#  数据库连接
  datasource:
    url: jdbc:mysql://192.168.5.128/db1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
# mybatis
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml
  type-aliases-package: com.example.pojo
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  • mapper
public interface StuMapper extends BaseMapper<Stu>
  • service
public class StuServiceImpl extends ServiceImpl<StuMapper, Stu> implements StuService

3. 异常

配置了Mybatis日志输出sql,不生效,只打印一部分,sql语句并未打印

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@326c28b8] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@997887803 wrapping com.mysql.jdbc.JDBC4Connection@5fe28809] will not be managed by Spring

正常打印

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@326c28b8] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@997887803 wrapping com.mysql.jdbc.JDBC4Connection@5fe28809] will not be managed by Spring
==>  Preparing: select id,name,age from stu 
==> Parameters: 
<==    Columns: id, name, age
<==        Row: 1, 韩雪, 18
<==        Row: 3, 一栗小莎子, 24
<==        Row: 4, 史蒂夫, 18
<==      Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@326c28b8]

环境:

  • mapper集成的Mybatis-plus
  • yml属性里配置的Mybatis的log-impl

解决:

  • 在yml里把Mybatis的配置改为Mybatis-plus的

16-transaction

1 事务是什么

  • 定义:Transaction 其实指的一组操作,里面包含许多个单一的逻辑。只要有一个逻辑没有执行成功,那么都算失败。 所有的数据都回归到最初的状态(回滚) 。事物的存在是为了确保逻辑的成功;

2 事务的传播行为

spring传播行为说的是:当多个事务同时存在时,spring如何处理这些事务的行为;

  • PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新的事务;如果当前存在事务,就加入该事务,该设置是最常用的设置;
  • PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务就以非事务执行;
  • PROPAGATION_MANDATORY:支持当前事务。如果当前存在事务就加入不存在抛出异常;
  • PROPAGATION_REQUIRES_NEW:创建新事物;无论当前是否存在事务,都会创建新事务;
  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常
  • PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则按REQUIRED属性;

3 spring的事务隔离

spring有五大隔离级别,默认值为ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库一致;

  • ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。

  • ISOLATION_READ_UNCOMMITTED:读未提交,允许事务在执行过程中,读取其他事务未提交的数据。

  • ISOLATION_READ_COMMITTED:读已提交,允许事务在执行过程中,读取其他事务已经提交的数据。

  • ISOLATION_REPEATABLE_READ:可重复读,在同一个事务内,任意时刻的查询结果都是一致的。

  • ISOLATION_SERIALIZABLE:所有事务逐个依次执行。

**脏读:**表示一个事务能够读取另一个事务中还未提交的数据,比如:某个事务尝试插入记录A,此事务还未提交,然后另一个事务读取到了记录A;

**不可重复读:**是指在一个事务内,多次读同一个数据;

**幻读:**指同一个事务内多次查询返回的结果集不一样,比如同一个事务A第一次插叙拿到时候有n条记录,但是第二次同等条件查询却又n+1条记录;发生幻读的原因是另外一个事务对数据进行了增删改操作,同一个记录的数据被修改,数据行的记录就改变了;

4 @Transaction注解

  • 定义:控制事务回滚、提交
  • 作用域:
    • 注解在类上:作用于该类所有的public方法
    • 注解在接口上:使用基于接口的代理才生效,但是CGLIB动态代理采用继承的方式会导致@Transaction注解失效
    • 注解在方法上:只在该方法上生效,如果类和方法都配置该注解,方法的注解会覆盖类上注解;
  • 由事务由MySQL控制,回滚也是mysql控制
  • MySQL的commit是从执行到MySQL相关的语句开始到结束
  • 相关属性:
    • timeout:设置一个超时时间,默认为-1,不超时
    • value:用来指定不同的事务管理器,满足在一个系统中存在不同的事务管理器;
    • propagation:指定事务传播行为,默认为Required
    • isolation:指定事务隔离级别,默认为DEFAULT,使用数据库的隔离级别
    • readOnly:读写或只读事务,默认读写
    • rollbackFor:(继承自Throwable)导致事务回滚的异常,即指定某种异常事务就回滚,类似于触发器
    • rollbackForClassName:导致事务回滚的异常类名字数组
    • noRollbackFor:不会导致事务回滚的异常类;
    • noRollbackForClassName:不会导致事务回滚的异常类名字数组

5 @Transaction失效场景

  • 非public方法上
  • 同一个类中的方法调用
  • 异常被catch

6. 自定义场景测试

  • 创建两张表:user1、user2
  • pojo、service、impl、mapper都搭建好
  • 代码环境:
    • user1:insert方法
    • user2: insert方法,自己制造了异常的insert方法
  • 事务控制:在两个user的insert方法都加入了@Transation,指定传播行为为Required
场景一:外围方法不开启事务,调用内部方法
  • 测试环境1:

    • 在测试方法里调用两个user的insert方法,方法调用完毕,手动制造异常
    @Test
    	public void requiredTest(){
    		// 调用user1的插入方法
    		User1 user1=new User1();
    		user1.setName("张三");
    		user1Service.insert(user1);
    		// 调用user2的插入方法
    		User2 user2=new User2();
    		user2.setName("李四");
    		user2Service.insert(user2);
    		// 手动制造异常
    		throw new RuntimeException();
    	}
    
    • 结果:user1、user2插入成功
    • 分析:外围方法未开启事务,而两个user的insert方法都有自己的事务,外围方法异常不影响内部独立的事务
  • 测试环境2:

    • 测试方法调用user1的insert方法,调用user2的异常insert方法,测试方法不自制异常
    @Test
       public void requiredTest(){
          // 调用user1的插入方法
          User1 user1=new User1();
          user1.setName("张三");
          user1Service.insert(user1);
          // 调用user2的异常插入方法
          User2 user2=new User2();
          user2.setName("李四");
          user2Service.insertException(user2);
          // 手动制造异常
    //    throw new RuntimeException();
       }
    
    • 结果:user1插入成功,user2插入失败
    • 分析:外围方法没有事务,两个user都独立事务,有一个事务一旦异常则回滚,不影响另一个事务
场景二:外围方法开启事务,调用内部方法
  • 测试环境1:

    • 测试方法开启事务,正常调用两个insert方法,调用完毕,自制异常
    @Test
    @Transactional(propagation = Propagation.REQUIRED)
    public void requiredTest(){
       // 调用user1的插入方法
       User1 user1=new User1();
       user1.setName("张三");
       user1Service.insert(user1);
       // 调用user2的插入方法
       User2 user2=new User2();
       user2.setName("李四");
       user2Service.insert(user2);
       // 手动制造异常
       throw new RuntimeException();
    }
    
    • 结果:两个user都回滚
    • 分析:外围方法开启事务,内部方法加入外围方法事务,一旦外围方法抛出异常,则内部方法跟着回滚
  • 测试环境2

    • 外围方法开启事务,调用内部异常方法
    @Test
       @Transactional(propagation = Propagation.REQUIRED)
       public void requiredTest(){
          // 调用user1的插入方法
          User1 user1=new User1();
          user1.setName("张三");
          user1Service.insert(user1);
          // 调用user2的插入方法
          User2 user2=new User2();
          user2.setName("李四");
          user2Service.insertException(user2);
          // 手动制造异常
    //    throw new RuntimeException();
       }
    
    • 结果:均未成功
    • 分析:外围方法开启事务,内部方法加入外围事务,内部方法抛出异常,外围方法感知到异常跟着回滚
  • 测试环境3:

    • 外围开启事务,调用内部异常方法,内部方法全部不开启事务

    • 结果:均未成功

    • 分析:外围事务开启,内部方法加入外围事务,内部方法异常,外围感知,回滚

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值