
Preface
后端应用当中与DB交互也是必不可少的一部, 在Java中我们将交互部分抽象成了 ORM(Object Relational Mapping), 以下是数据源以及ORM相关…
数据源
配置数据源
更多请查看官方指导:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-configure-a-datasource
MySQL
pom.xml:
1 | <dependency> |
application.yml:
1 | spring: |
H2
注意:使用H2控制台不能使用WebFlux,否则控制台出不来
pom.xml:
1 | <!-- h2 数据源连接驱动 --> |
application.yml:
1 | spring: |
开启H2控制台
1 | spring: |
常用连接池配置
Spring Boot 2 默认使用 HikariCP 作为连接池
如果项目中已包含spring-boot-starter-jdbc或spring-boot-starter-jpa模块,那么连接池将自动激活!
在Spring Boot2中选择数据库链接池实现的判断逻辑:
- 检查HikariCP是否可用,如可用,则启用。使用
spring.datasource.hikari.*可以控制链接池的行为。 - 检查Tomcat的数据库链接池实现是否可用,如可用,则启用。使用
spring.datasource.tomcat.*可以控制链接池的行为。 - 检查Commons DBCP2是否可用,如可用,则启用。使用
spring.datasource.dbcp2.*可以控制链接池的行为。
Spring Boot 2中已经使用Hikari作为默认连接池,如果需要指定其他使用spring.datasource.type
1 | spring: |
HikariCP 连接池常用属性
| 属性 | 描述 | 默认值 |
|---|---|---|
| dataSourceClassName | JDBC 驱动程序提供的 DataSource 类的名称,如果使用了jdbcUrl则不需要此属性 | - |
| jdbcUrl | 数据库连接地址 | - |
| username | 数据库账户,如果使用了jdbcUrl则需要此属性 | - |
| password | 数据库密码,如果使用了jdbcUrl则需要此属性 | - |
| autoCommit | 是否自动提交事务 | true |
| connectionTimeout | 连接超时时间(毫秒),如果在没有连接可用的情况下等待超过此时间,则抛出 SQLException | 30000(30秒) |
| idleTimeout | 空闲超时时间(毫秒),只有在minimumIdle<maximumPoolSize时生效,超时的连接可能被回收,数值 0 表示空闲连接永不从池中删除 | 600000(10分钟) |
| maxLifetime | 连接池中的连接的最长生命周期(毫秒)。数值 0 表示不限制 | 1800000(30分钟) |
| connectionTestQuery | 连接池每分配一条连接前执行的查询语句(如:SELECT 1),以验证该连接是否是有效的。如果你的驱动程序支持 JDBC4,HikariCP 强烈建议我们不要设置此属性 | - |
| minimumIdle | 最小空闲连接数,HikariCP 建议我们不要设置此值,而是充当固定大小的连接池 | 与maximumPoolSize数值相同 |
| maximumPoolSize | 连接池中可同时连接的最大连接数,当池中没有空闲连接可用时,就会阻塞直到超出connectionTimeout设定的数值,推荐的公式:((core_count * 2) + effective_spindle_count) | 10 |
| poolName | 连接池名称,主要用于显示在日志记录和 JMX 管理控制台中 | auto-generated |
application.yml
1 | spring: |
Tomcat连接池常用的属性
| 属性 | 描述 | 默认值 |
|---|---|---|
| defaultAutoCommit | 连接池中创建的连接默认是否自动提交事务 | 驱动的缺省值 |
| defaultReadOnly | 连接池中创建的连接默认是否为只读状态 | - |
| defaultCatalog | 连接池中创建的连接默认的 catalog | - |
| driverClassName | 驱动类的名称 | - |
| username | 数据库账户 | - |
| password | 数据库密码 | - |
| maxActive | 连接池同一时间可分配的最大活跃连接数 | 100 |
| maxIdle | 始终保留在池中的最大连接数,如果启用,将定期检查限制连接,超出此属性设定的值且空闲时间超过minEvictableIdleTimeMillis的连接则释放 | 与maxActive设定的值相同 |
| minIdle | 始终保留在池中的最小连接数,池中的连接数量若低于此值则创建新的连接,如果连接验证失败将缩小至此值 | 与initialSize设定的值相同 |
| initialSize | 连接池启动时创建的初始连接数量 | 10 |
| maxWait | 最大等待时间(毫秒),如果在没有连接可用的情况下等待超过此时间,则抛出异常 | 30000(30秒) |
| testOnBorrow | 当从连接池中取出一个连接时是否进行验证,若验证失败则从池中删除该连接并尝试取出另一个连接 | false |
| testOnConnect | 当一个连接首次被创建时是否进行验证,若验证失败则抛出 SQLException 异常 | false |
| testOnReturn | 当一个连接使用完归还到连接池时是否进行验证 | false |
| testWhileIdle | 对池中空闲的连接是否进行验证,验证失败则回收此连接 | false |
| validationQuery | 在连接池返回连接给调用者前用来对连接进行验证的查询 SQL | null |
| validationQueryTimeout | SQL 查询验证超时时间(秒),小于或等于 0 的数值表示禁用 | -1 |
| timeBetweenEvictionRunsMillis | 在空闲连接回收器线程运行期间休眠时间(毫秒), 该值不应该小于 1 秒,它决定线程多久验证空闲连接或丢弃连接的频率 | 5000(5秒) |
| minEvictableIdleTimeMillis | 连接在池中保持空闲而不被回收的最小时间(毫秒) | 60000(60秒) |
| removeAbandoned | 标记是否删除泄露的连接,如果连接超出removeAbandonedTimeout的限制,且该属性设置为 true,则连接被认为是被泄露并且可以被删除 | false |
| removeAbandonedTimeout | 泄露的连接可以被删除的超时时间(秒),该值应设置为应用程序查询可能执行的最长时间 | 60 |
application.yml:
1 | spring: |
DBCP 连接池常用配置
| 属性 | 描述 | 默认值 |
|---|---|---|
| url | 数据库连接地址 | - |
| username | 数据库账户 | - |
| password | 数据库密码 | - |
| driverClassName | 驱动类的名称 | - |
| defaultAutoCommit | 连接池中创建的连接默认是否自动提交事务 | 驱动的缺省值 |
| defaultReadOnly | 连接池中创建的连接默认是否为只读状态 | 驱动的缺省值 |
| defaultCatalog | 连接池中创建的连接默认的 catalog | - |
| initialSize | 连接池启动时创建的初始连接数量 | 0 |
| maxTotal | 连接池同一时间可分配的最大活跃连接数; 负数表示不限制 | 8 |
| maxIdle | 可以在池中保持空闲的最大连接数,超出此值的空闲连接被释放,负数表示不限制 | 8 |
| minIdle | 可以在池中保持空闲的最小连接数,低于此值将创建空闲连接,若设置为 0,则不创建 | 0 |
| maxWaitMillis | 最大等待时间(毫秒),如果在没有连接可用的情况下等待超过此时间,则抛出异常; -1 表示无限期等待,直到获取到连接为止 | - |
| validationQuery | 在连接池返回连接给调用者前用来对连接进行验证的查询 SQL | - |
| validationQueryTimeout | SQL 查询验证超时时间(秒) | - |
| testOnCreate | 连接在创建之后是否进行验证 | false |
| testOnBorrow | 当从连接池中取出一个连接时是否进行验证,若验证失败则从池中删除该连接并尝试取出另一个连接 | true |
| testOnReturn | 当一个连接使用完归还到连接池时是否进行验证 | false |
| testWhileIdle | 对池中空闲的连接是否进行验证,验证失败则释放此连接 | false |
| timeBetweenEvictionRunsMillis | 在空闲连接回收器线程运行期间休眠时间(毫秒),如果设置为非正数,则不运行此线程 | -1 |
| numTestsPerEvictionRun | 空闲连接回收器线程运行期间检查连接的个数 | 3 |
| minEvictableIdleTimeMillis | 连接在池中保持空闲而不被回收的最小时间(毫秒) | 1800000(30分钟) |
| removeAbandonedOnBorrow | 标记是否删除泄露的连接,如果连接超出removeAbandonedTimeout的限制,且该属性设置为 true,则连接被认为是被泄露并且可以被删除 | false |
| removeAbandonedTimeout | 泄露的连接可以被删除的超时时间(秒),该值应设置为应用程序查询可能执行的最长时间 | 300(5分钟) |
| poolPreparedStatements | 设置该连接池的预处理语句池是否生效 | false |
application.yml
1 | spring: |
Spring Boot Data Jpa 依赖声明:
1 | 通过application.yml: spring.datasource.type=...配置 |
Druid连接池配置
参考:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
JTA分布式事务数据源
Atomikos是一个非常流行的开源事务管理器,并且可以嵌入到Spring Boot应用中。可以使用 spring-boot-starter-jta-atomikos Starter去获取正确的Atomikos库。Spring Boot会自动配置Atomikos,并将合适的 depends-on 应用到Spring Beans上,确保它们以正确的顺序启动和关闭。
默认情况下,Atomikos事务日志将被记录在应用home目录(应用jar文件放置的目录)下的 transaction-logs 文件夹中。可以在 application.properties 文件中通过设置 spring.jta.log-dir 属性来定义该目录,以 spring.jta.atomikos.properties 开头的属性能用来定义Atomikos的 UserTransactionServiceIml 实现,具体参考AtomikosProperties javadoc。
注 为了确保多个事务管理器能够安全地和相应的资源管理器配合,每个Atomikos实例必须设置一个唯一的ID。默认情况下,该ID是Atomikos实例运行的机器上的IP地址。为了确保生产环境中该ID的唯一性,需要为应用的每个实例设置不同的
spring.jta.transaction-manager-id属性值。
依赖:
1 | <dependency> |
application.yml
1 | spring: |
DataSourceJTAIncomeConfig.java:
1 |
|
DataSourceJTAUserConfig.java
1 |
|
表与数据初始化
使用 Hibernate 初始化
可以显式设置 spring.jpa.hibernate.ddl-auto ,标准的Hibernate属性值有 none , validate , update , create , create-drop 。Spring Boot根据数据库是否为内嵌数据库来选择相应的默认值,如果是内嵌型的则默认值为 create-drop ,否则为 none 。通过查看 Connection 类型可以检查是否为内嵌型数据库,hsqldb,h2和derby是内嵌的,其他都不是。当从内存数据库迁移到一个真正的数据库时,需要当心,在新的平台中不能对数据库表和数据是否存在进行臆断,也需要显式设置 ddl-auto ,或使用其他机制初始化数据库。
可以通过启用
org.hibernate.SQL来输出Schema的创建过程。当DEBUG MODE被开启的时候,这个功能就已经被自动开启了。
此外,启动时处于classpath根目录下的 import.sql 文件会被执行(前提是ddl-auto属性被设置为 create 或 create-drop)。这在demos或测试时很有用,但在生产环境中可能不期望这样。这是Hibernate的特性,和Spring没有一点关系。
使用 Spring JDBC 初始化
指定初始化脚本位置:
1 | spring: |
不能与Hibernate的创建表功能一起开启, 否则会报错.
运行时SQL监控
虽然一些开源框架会自带SQL打印, 但都需要各自配置, 但我们可以通过第三方框架比如p6spy以及log4jdbc做到驱动级别拦截.
p6spy
添加依赖:
1 | <dependency> |
应用P6Spy只需要
- 1.替换你的
JDBC Driver为com.p6spy.engine.spy.P6SpyDriver - 2.修改
JDBC Url为jdbc:p6spy:xxxx - 3.配置
spy.properties
修改application.yml文件,替换jdbc driver和url
1 | # 数据源 |
配置spy.properties
1 | module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory |
自定义日志打印 , 这里有两种方式
一、实现MessageFormattingStrategy接口
1 | public class PrettySqlMultiLineFormat implements MessageFormattingStrategy { |
二、在 spy.properties 中指定
1 | # 自定义日志打印 |
附录: spy.properties详细说明
1 | # 指定应用的日志拦截模块,默认为com.p6spy.engine.spy.P6SpyFactory |
log4jdbc
添加依赖:
1 | <dependency> |
MySQL数据源配置换成:
1 | spring: |
Spring 事务监控
某些特殊的场景下, 我们需要在事务开启时, 完成时做一些事情, 比如释放一些资源.
方式一: TransactionSynchronization
Spring事务的核心部分在 TransactionInterceptor#invoke, TransactionInterceptor 继承了 TransactionAspectSupport, TransactionAspectSupport 使用 AbstractPlatformTransactionManager 的实现类操作事务. AbstractPlatformTransactionManager 中的 processCommit 以及 processRollback 中的几个节点会调用到 TransactionSynchronizationUtils 中的一些方法:


这些被trigger的类就是 TransactionSynchronization 的实现类.
TransactionSynchronizationUtils 通过 TransactionSynchronizationManager#getSynchronizations 来获取 TransactionSynchronization 列表, 而 TransactionSynchronizationManager 是通过 ThreadLocal 来管理这些类的:



示例:
1 |
|
那么这个类什么时候注册进 TransactionSynchronizationManager 呢? 答案是每次事务开启的时候, 因为这个是使用 ThreadLocal 保存的, 每次事务过后会被清空掉. 我们可以使用切面, 将打有 @Transactional 注解的方法增强一下:
1 |
|
这样就行了~
方式二: 继承DataSourceTransactionManager
上面的方法有一个缺点, 不能在事务开启时做一些事情, 可以通过继承 DataSourceTransactionManager 来实现:
1 | 4j |
配置类:
1 |
|
方式三: @TransactionalEventListener
使用 @TransactionalEventListener 可以在事务的某些阶段处理 Event
1 | ({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) |
1 | public enum TransactionPhase { |
示例:
1 |
|
- 它的原理也是利用了方式一中的
TransactionSynchronization. - 贴上
@TransactionalEventListener注解的方法会被 Spring 使用TransactionalEventListenerFactory包装成ApplicationListenerMethodTransactionalAdapter ApplicationListenerMethodTransactionalAdapter中进一步将监听方法封装成TransactionSynchronizationEventAdapter, 再通过TransactionSynchronizationManager.registerSynchronization进行注册
判断当前方法是否在事务环境中
通过上面的 TransactionSynchronizationManager 可以发现定义了很多 ThreadLocal:

可以看到变量中有一个 actualTransactionActive 以及 currentTransactionReadOnly, 通过这两个变量可以判断当前是否在事务当中, Spring很多代码中也是通过这个来判断的, 比如 RedisConnectionUtils#isActualNonReadonlyTransactionActive :

对象映射
https://www.baeldung.com/java-performance-mapping-frameworks
ORM 对比
- MyBatis:MyBatis 本是 Apache 的一个开源项目 iBatis,2010 年这个项目由 Apache Software Foundation 迁移到了 Google Code,并且改名为 MyBatis,其着力于 POJO 与 SQL 之间的映射关系,可以进行更为细致的 SQL,使用起来十分灵活、上手简单、容易掌握,所以深受开发者的喜欢,目前市场占有率最高,比较适合互联应用公司的 API 场景; 缺点就是工作量比较大,需要各种配置文件的配置和 SQL 语句。
- Hibernate:Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,使得 Java 程序员可以随心所欲的使用对象编程思维来操纵数据库,并且对象有自己的生命周期,着力点对象与对象之间关系,有自己的 HQL 查询语言,所以数据库移植性很好。Hibernate 是完备的 ORM 框架,是符合 JPA 规范的,有自己的缓存机制,上手来说比较难,比较适合企业级的应用系统开发。
- Spring Data JPA:可以理解为 JPA 规范的再次封装抽象,底层还是使用了 Hibernate 的 JPA 技术实现,引用 JPQL(Java Persistence Query Language)查询语言,属于 Spring 的整个生态体系的一部分。由于 Spring Boot 和 Spring Cloud 在市场上的流行,Spring Data JPA 也逐渐进入大家的视野,他们有机的整体,使用起来比较方便,加快了开发的效率,使开发者不需要关系和配置更多的东西,完全可以沉浸在 Spring 的完整生态标准的实现下,上手简单、开发效率高,又对对象的支持比较好,又有很大的灵活性,市场的认可度越来越高。
MyBatis
对于MyBatis, 现已有很优秀的二次封装框架, 比如 Mapper4, MtBatis-Plus 等.
