JdbcTemplate
JdbcTemplate的设计
JdbcTemplate只是一个基于原生 Jdbc 的简单封装,它提供了 CRUD 的方法,以及直接执行 SQL 的方法。它仅仅只是一个简单的封装。
JdbcTemplate 提供的几类方法:
- execute:用于直接执行 SQL,通常用于执行 DDL 语句
- update:用于对表数据执行的增删改操作,通常同于DML语句
- query:用于查询数据,用于 DQL 语句
- call:一般用于执行存储过程、函数等
使用 JdbcTemplate 一般都是在三层架构下,在 Dao 层使用,该方法通常 Dao 实现类会继承 JdbcDaoSupport ,直接在其中注入 DataSource 即可获得 JdbcTemplate 。
除此之外,JdbcTemplate 还有一种基于 SQL 参数扩展的 NamedParameterJdbcTemplate ,使用它的时候 SQL 的参数将不再使用 ? 作为占位符,而是使用参数引用的方式。
事务
事务的四大特性
- 原子性:一个事务就是一个不可分割的整体,同一个事务中的操作要么全部成功,要么全是失败;原则性强调的是事务 整体
- 一致性:事务执行的后,所有的数据都要保持一致状态。一致性强调的是数据的 完整
- 隔离性:多个数据库操作并发执行时,一个请求的事务操作不能被其它事务操作干扰,多个并发事务执行之间要相互隔离。隔离性强调的是并发隔离
- 持久性:事务提交之后,它对数据的影响是永久的。持久性强调的是操作的结果
事务隔离级别
针对数据库的并发操作,可能会出现一些事务的并发问题。事务并发操作中会出现三种问题:
- 脏读:一个事务读取到另一个事务还未提交的数据
- 幻读:事务使用相同的条件对数据表的两次查询结果不一致,在两次查询中间有其它事务插入并提交了新数据,导致结果不一致
- 不可重复读:事务使用相同条件对同一条数据的两次查询结果不一致,在两次查询中间其它事务对该条数据修改并提交了,导致两次结果不一致
数据库针对上面三种问题,提出了事务隔离级别从而解决:
- read uncommitted:读未提交 —— 不解决任何问题
- read committed:读已提交 —— 解决脏读
- repeatable read:可重复读 —— 解决脏读、不可重复读
- serializable:可串行化 —— 解决脏读、幻读、不可重复读
四种隔离级别,自上而下级别逐级增高,但并发性能逐级降低。MySQL 中默认的事务隔离级别是 repeatable read ,Oracle 、PostgresSQL 的默认事务隔离级别是 read committed 。
SpringFramework 事务
SpringFramework 事务控制模型和核心逻辑
SpringFramework 的事务控制模型是三个顶层接口
PlatformTransactionManager
:平台事务管理器(5.2 之后抽象为更高级别的 TransactionManager,该接口仅仅为标识性接口)TransactionDefinition
:事务定义信息TransactionStatus
:事务状态信息
SpringFramework 对事务的控制,可以理解为事务管理器,可以根据事务的定义,获取 / 控制事务的状态。
流程控制来讲,当需要事务控制时,会先根据被执行的方法,解析(获取)到对应的事务定义信息,再以此从事务管理器中获取 / 创建处事务状态信息,并绑定到线程中。
事务的使用方式
SpringFramework 的事务使用,可以分为编程式与声明式:
- 编程式:借助 TransactionTemplate,将业务逻辑卸载 TransactionCallback 的内部,以此达到事务控制的效果
- 声明式:通过配置事务增强器 / 开启注解声明式事务,并配置通知方法 / 标注 @Transactional 注解,也可以实现事务控制
事务的传播行为
事务传播行为,指的是外层的事务传播到内层之后,内层事务作出的行为(持有的态度)。SpringFramework 中定义了 7 种传播行为
传播行为 | 含义 |
---|---|
REQUIRED:必须的 | Spring 默认事务,如果当前没有事务,则开启一个新的事务;如果当前已经存在事务,则加入到该事物中执行 |
REQUIRES_NEW:新事务 | 如果当前没有事务,则会开启一个新事务;如果当前已经存在事务,则会将该事务挂起,重新开启一个新事务 。当新事务执行完毕之后,再将原来的事务释放 |
SUPPORTS:支持 | 如果当前存在事务,则将该事务挂起;如果当前没有事务,则以无事务的方式执行 |
NOT_SUPPORTED:不支持 | 如果当前存在事务,则将该事物挂起;如果不存在事务,则以无事务方式执行 |
MANDATORY:强制 | 当前方法必须在事务中执行,如果没有事务,则直接抛出异常 |
NEVER:不允许 | 当前方法不允许在事务中执行,如果存在事务则直接抛出异 |
NESTED:嵌套 | 如果当前不存在事务,则创建一个新的事务执行;如果当前存在事务,则会记录一个保存点,并加入到该事务中继续执行 。如果子事务执行过程中出现异常,不会全部回滚,而是回滚到上一个保存点 |
事务监听器
事务监听器是 SpringFramework 基于原有的事件监听机制的扩展,它的核心注解是 :@TransactionEventListener
,它可以在事务执行的特定阶段触发事件监听。支持 4 中特性:
- BEFORE_COMMIT:事务提交之后触发监听
- AFTER_COMMIT:事务提交之后触发监听
- AFTER_ROLLBACK:事务回滚之后触发监听
- AFTER_COMPLETION:事务完成之后触发监听(无论提交还是回滚都会触发)
JTA 分布式事务
SpringFramework 支持基于 JTA 的分布式事务,JTA分布式事务是基于 XA 规范的抽象,而 XA 规范事务两段式提交(2PC)协议的数据层支持方案。JTA 分布式事务具有 3 个重要角色:
- 全局事务控制器:控制一整个分布式会务的事务管理器,它可以控制一个分布式事务中各个节点上的事务提交或回滚
- 资源管理器:可以简单的理解为关系型数据库
- 应用程序:我们自己编写的程序
SpringFramework 使用 JTA 事务时,需要有外部 JTA 事务资源管理器的支持,或者整合第三方 JTA 事务框架 (如 Atomikos),方可形成完整的 JTA 事务控制的模型。
事务失效场景
一般情况下事务失效会有如下一些场景:
- 原始的 SSM 开发中,父子容器一起包扫描,会导致子容器先扫描到 service 并注册到子容器中但不加载事务,之后虽然父容器也扫描到 service 但因为子容器中的 controller 已经注入了没有事务代理的 service ,会导致事务失效
- 我们知道容器是有父子关系的,在整合 SpringWebMvc 之后,会形成父子容器,子容器中只有 controller ,对应的 spring-mvc.xml 配置文件或注解配置类中,是只应该扫描 @Controller 注解的,这样才可以保证子容器仍然能从父容器中获取到 service 、dao 等,而父容器会开启事务控制,所以 service 是被事务增强过的。
- 如果不慎在 spring-mvc.xml 中,扫描 controller 时一并扫描了 service ,会导致父子容器中同时存在 service ,则此时 controller 不会去父容器找,而是直接拿子容器中没有被事务增强过的 service ,导致事务失效。
- 声明式事务必须由父 IOC 容器加载,SpringWebMvc 的子 IOC 容器加载不生效
- 声明式事务控制放在子容器,父容器不会感知到子容器的配置,所以相当于“啥也没干”
- 如果 @Transactional 注解标注在接口上,但是实现类使用 Cglib 代理,事务会失效
- Cglib 直接拿类去增强,不管接口了,自然也就不好使了
- 事务默认捕捉 RuntimeException ,如果抛出 Exception ,默认不捕捉,事务失效
- ervice 方法中自行 try-catch 了异常但没有再抛出 RuntimeException ,会导致事务拦截器无法感知到异常,从而失效
- 同一个类中,一个方法调用了自身另一个带有事务控制的方法,则直接调用时也会导致事务失效
- 使用 Cglib 代理时,类使用了 final 关键字,目标方法使用了 final 关键字