影子的知识库

影子的知识库

  • 知识库
  • GitHub

›Java系列

JVM系列

  • JVM内存区域
  • 对象创建-布局-访问
  • 内存溢出实战
  • 内存区域回收
  • 四大引用
  • 垃圾回收算法
  • HotSpot回收算法细节

Java系列

  • java注解
  • springboot请求参数绑定
  • springboot请求参数校验框架
  • YAML语法
  • 动态代理
  • classpath和java命令
  • springboot-aop编程
  • springboot统一异常处理
  • springboot数据库和事务
  • springboot拦截器
  • springboot中的web配置
  • docker的简单开发
  • springboot自动配置
  • 数据库的隔离级别
  • springboot监控
  • java类加载
  • java-agent的相关内容
  • 类加载器详解
  • java的SecurityManager
  • maven学习

Node

    JS 基础

    • 语法基础和数据类型
    • 数据类型转换
    • 语句 表达式 运算符
    • 变量与对象
    • 函数
    • 数据处理
    • 常用 API
    • 重点知识

    ES6

    • 块级作用域
    • 字符串和正则表达式
    • 函数
    • 对象
    • Symbol
    • Set和Map
    • 迭代器和生成器
    • 类
    • 数组
    • Promise

    Node 基础

    • 模块系统
    • package.json
    • 内置对象
    • npm脚本的使用
    • Buffer
    • Stream
    • 事件循环机制
    • 示例代码

    stream系列

    • 流的缓冲
    • 可读流
    • 可写流
    • 双工流和转换流
    • 自定义流

后期计划

  • 学习计划
  • 专题研究计划
Edit

本文内容

数据库环境

  • Mysql 8.0.19
  • Root 密码:123456
  • czp 用户密码:123456
  • 表名不区分大小写
  • 数据库:auth
  • URL:localhost:3306

POM 引入

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

application.properties 配置

#通用数据源配置,新版的 mysql 都是 com.mysql.cj.jdbc.Driver 类,也可以不写
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/auth?charset=utf8mb4&useSSL=false
spring.datasource.username=czp
spring.datasource.password=123456
# Hikari 数据源专用配置
spring.datasource.hikari.maximum-pool-size=2
spring.datasource.hikari.minimum-idle=1
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=500
spring.datasource.hikari.login-timeout=500
spring.datasource.hikari.validation-timeout=1000
spring.datasource.hikari.initialization-fail-timeout=1000

这里只是简单的配置,在生产环境中的配置最好单独研究下 hikari

Main 方法测试

@SpringBootApplication
public class JavaDemoApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(JavaDemoApplication.class, args);
        DataSource bean = context.getBean(DataSource.class);
        System.out.println(bean.getClass().getName());
    }
}

输出:
com.zaxxer.hikari.HikariDataSource

spring boot 2 之后,当我们引入了 spring-boot-starter-jdbc 或者 spring-boot-starter-data-jpa,就会包含有 hikari 依赖,只要找到 hikari 依赖,spring boot 就会加载它作为默认的 dataSource 类型。

可以修改 spring.datasource.type = com.zaxxer.hikari.HikariDataSource 这个配置,改成其他的连接池比如说 Druid。

DataSource 是什么

旧版 API DriverManager

Class.forName(“com.mysql.jdbc.Driver”);
String url = “jdbc:mysql://localhost:3306/test?user=root&password=123456″;
Connection con = DriverManager.getConnection(url);
Statement statement = con.createStatement();

Class.forName(“com.mysql.jdbc.Driver”);,主要是为了加载驱动类,然后驱动类里有一段静态初始化的代码就是:将自己注册到 DriverManager 的 driver 列表里

然后我们所有的操作就是:使用 DriverManager 来获取连接

DriverManager 根据 URL 等协议前缀来判断应该由哪个驱动类来获取连接

DriverManager 最大的问题有两个:

  • 获取连接的时候需要传入连接参数,它自己内部不保存 url、password 等信息,这样导致硬编码
  • 不支持连接池,每次获取连接都是创建新连接。这一点是因为:DriverManager 本身是一个类,并不是一个接口规范,它的行为就是不支持连接池,所以只能外挂连接池

新版 API DataSource

public interface DataSource  extends CommonDataSource, Wrapper {

  Connection getConnection() throws SQLException;

  Connection getConnection(String username, String password)
    throws SQLException;
}

DataSource 是一个接口定义,它的核心就是:获取连接

而至于连接是如何获取的,是否是新建的,是否被缓存了,这全都取决于具体的 DataSource 实现

DataSource 的好处就是抽象出了数据源最重要的功能,那就是获取连接,让实现者有很高的自由度:

  • 使用者不需要显示传入连接参数,这些信息在 DataSource 初始化的时候就已经保存了
  • 使用者不需要关心连接池的逻辑,这些在 DataSource 的实现者那里已经解决了

总结

DataSource 就是一个获取连接的对象,它的核心接口就是获取连接,我们初始化 DataSource 就把连接所需要的 URL 等信息保存到了 DataSource 内部了。优秀的 DataSource 实现都会实现连接池的功能,这一块儿是保证性能的一个关键点。

在 spring boot 2 之后,我们只要依赖里包含了 hikari(引入了 spring-boot-starter-jdbc 或者 spring-boot-starter-data-jpa 会自动包含),就会默认使用 hikari 实现的数据源:com.zaxxer.hikari.HikariDataSource 创建一个 DataSource 对象到容器中。

建表并且插入数据

resources/schema.sql

CREATE TABLE IF NOT EXISTS operation_log
(
    id            int(11)      NOT NULL AUTO_INCREMENT COMMENT '主键',
    block_height  bigint(20)   NOT NULL COMMENT '区块高度',
    download_link varchar(320) NOT NULL DEFAULT '' COMMENT '下载链接',
    create_time   datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
    update_time   datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录修改时间',
    PRIMARY KEY (id),
    INDEX create_time_key (create_time),
    INDEX update_time_key (update_time)
) ENGINE = InnoDB
  AUTO_INCREMENT = 701 // 表示自增从 701 开始
  DEFAULT CHARSET = utf8mb4;

resources/data.sql

INSERT INTO operation_log(block_height, download_link)
VALUES (100, 'aaa'),
       (100, 'aaa'),
       (100, 'aaa'),
       (100, 'aaa'),
       (100, 'aaa'),
       (100, 'aaa'),
       (100, 'aaa'),
       (100, 'aaa'),
       (100, 'aaa'),
       (100, 'aaa');

在 application.properties 中添加

# spring.datasource.schema=classpath:schema.sql
# spring.datasource.data=classpath:data.sql
spring.datasource.initialization-mode=always

前两行可以不配置,默认就是这两个文件

initialization-mode 有:

  • always,启动就执行初始化的两个 sql 文件
  • never,不执行
  • embedded,h2这种内嵌的数据库才执行

集成 mybatis

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>
mybatis.type-aliases-package=com.example.javademo.model
mybatis.mapper-locations=classpath:mapper/*.xml

使用 MyBatisCodeHelper-Pro 生成代码

image-20200317182638543

transactionManager PlatformTransactionManager

public interface PlatformTransactionManager extends TransactionManager {

    TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
            throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

事务管理器,用于管理事务什么时候提交,什么时候回滚,对于不同的框架,会有不同的事务管理器的实现

当我们使用 spring-boot-starter-data-jpa 时,springboot 会创建 JpaTransacationManager 作为事务管理器。

当我们使用 mybatis-spring-boot-starter 时,他会创建 DataSourceTransacationManager 作为事务管理器。

Transactional 注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";
  
    @AliasFor("value")
    String transactionManager() default "";

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};

}
  • value 和 transactionManager
    • 两个含义是一样的,都是指定要使用的事务管理器的 bean 名称
  • propagation
    • 事务的传播机制类型,下一节详细说明
    • 默认是 Propagation.REQUIRED
      • 进入此方法时没有事务就新建事务
      • 进入此方法时已经有事务了,这个方法就也加入这个事务
  • isolation
    • 事务的隔离级别,参考 15.数据库的隔离级别
    • 默认是 Isolation.DEFAULT
      • 表示使用数据库默认的事务隔离级别,对于 mysql 是 RR,可重复读。
      • 其它数据库很多是 RC,读已提交
  • timeout
    • 超时时间(秒数)
  • readOnly
    • 是否只读事务
  • rollbackFor
    • 方法发生哪些类型的异常时事务回滚,默认是任何类型的异常都回滚
  • rollbackForClassName
    • 同上,只不过这里的是传入字符串,表示异常的全类名
  • noRollbackFor
    • 方法在发生指定异常时不回滚,默认是所有异常都回滚
  • noRollbackForClassName
    • 同上,只不过这里的是传入字符串,表示异常的全类名

spring 事务传播机制

参考

https://segmentfault.com/a/1190000020386113?utm_source=tag-newest

传播机制的作用

控制事务传播的行为:

  • A 方法调用 B 方法,两个 Transactional 方法是否处于一个事务
  • B 方法失败了,是否都回滚,还是只回滚 B
  • A 方法失败了,是否都回滚,还是只回滚 A

事务传播机制就是配置这些一系列的情况下的行为的

传播机制的类型

  • REQUIRED
    • 沿用当前已经存在的事务,如果当前不存在则新建一个事务
    • 这是最常用的一种
  • SUPPORTS
    • 如果当前存在事务,则加入这个事务
    • 如果当前不存在事务,则以非事务方式运行,这个和不写没区别(有点搞笑)
  • MANDATORY
    • 如果当前存在事务,则加入事务
    • 如果当前无事务,则抛出异常,也即父级方法必须有事务
    • 也就是说这种类型的,必须加入一个现存的事务中
  • REQUIRES_NEW
    • 新建事务,如果当前存在事务,则把当前事务挂起
    • 这个方法会独立提交事务,不受调用者的事务影响,父级异常,它也是正常提交
    • 也就是说这个方法运行在一个独立的事务里
  • NOT_SUPPORTED
    • 以非事务方式运行
    • 如果当前存在事务,则把当前事务挂起
  • NEVER
    • 以非事务方式运行,如果当前存在事务,则抛出异常,即父级方法必须无事务
  • NESTED
    • 如果当前存在事务,它将会成为父级事务的一个子事务,方法结束后并没有提交,只有等父事务结束才提交
    • 如果当前没有事务,则新建事务
    • 如果它异常,父级可以捕获它的异常而不进行回滚,正常提交
    • 但如果父级异常,它必然回滚,这就是和 REQUIRES_NEW 的区别

传播机制不生效的情况

spring 使用 aop 对代理对象进行拦截从而添加事务的特性。我们在一个 service 内部的 A 方法调用 B 方法,这里 A 方法内部对 B 方法的调用,使用的是被代理的对象的 B 方法调用,此时事务管理器是察觉不到 B 方法的调用的,因此 B 方法上的事务注解就失效了

要解决这种内部调用失效的情况,知道原理就很简单:既然是因为调用内部方法时使用的是被代理对象,那么我们可以手工在 A 方法内部调用容器方法获取到代理对象service,通过 service.B() 的方式来进行代理对象的调用

Last updated on 11/8/2020
← springboot统一异常处理springboot拦截器 →
  • 数据库环境
  • POM 引入
  • application.properties 配置
  • Main 方法测试
  • DataSource 是什么
    • 旧版 API DriverManager
    • 新版 API DataSource
    • 总结
  • 建表并且插入数据
    • resources/schema.sql
    • resources/data.sql
  • 集成 mybatis
  • 使用 MyBatisCodeHelper-Pro 生成代码
  • transactionManager PlatformTransactionManager
  • Transactional 注解
  • spring 事务传播机制
    • 参考
    • 传播机制的作用
    • 传播机制的类型
    • 传播机制不生效的情况
影子的知识库
Docs
Getting Started (or other categories)Guides (or other categories)API Reference (or other categories)
Community
User ShowcaseStack OverflowProject ChatTwitter
More
BlogGitHub
Copyright © 2020 Cen ZhiPeng