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 等.