SpringFramework — JDBC试题

小龙 875 2022-06-29

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 事务控制的模型。

事务失效场景

一般情况下事务失效会有如下一些场景:

  1. 原始的 SSM 开发中,父子容器一起包扫描,会导致子容器先扫描到 service 并注册到子容器中但不加载事务,之后虽然父容器也扫描到 service 但因为子容器中的 controller 已经注入了没有事务代理的 service ,会导致事务失效
    • 我们知道容器是有父子关系的,在整合 SpringWebMvc 之后,会形成父子容器,子容器中只有 controller ,对应的 spring-mvc.xml 配置文件或注解配置类中,是只应该扫描 @Controller 注解的,这样才可以保证子容器仍然能从父容器中获取到 service 、dao 等,而父容器会开启事务控制,所以 service 是被事务增强过的。
    • 如果不慎在 spring-mvc.xml 中,扫描 controller 时一并扫描了 service ,会导致父子容器中同时存在 service ,则此时 controller 不会去父容器找,而是直接拿子容器中没有被事务增强过的 service ,导致事务失效。
  2. 声明式事务必须由父 IOC 容器加载,SpringWebMvc 的子 IOC 容器加载不生效
    • 声明式事务控制放在子容器,父容器不会感知到子容器的配置,所以相当于“啥也没干”
  3. 如果 @Transactional 注解标注在接口上,但是实现类使用 Cglib 代理,事务会失效
    • Cglib 直接拿类去增强,不管接口了,自然也就不好使了
  4. 事务默认捕捉 RuntimeException ,如果抛出 Exception ,默认不捕捉,事务失效
  5. ervice 方法中自行 try-catch 了异常但没有再抛出 RuntimeException ,会导致事务拦截器无法感知到异常,从而失效
  6. 同一个类中,一个方法调用了自身另一个带有事务控制的方法,则直接调用时也会导致事务失效
  7. 使用 Cglib 代理时,类使用了 final 关键字,目标方法使用了 final 关键字

# AOP # 面试 # 事务