基于JDBC的Spring中数据库事务的相关操作

数据库事务的四大特性(ACID)

  1. 原子性(Atomicity)事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
  2. 一致性(Consistency)事务在完成时,必须是所有的数据都保持一致状态。
  3. 隔离性(Isolation)并发事务执行之间无影响,在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性。
  4. 持久性(Durability)一旦事务完成,数据库的改变必须是持久化的。

并发访问数据库导致的问题

在企业级应用中,多用户访问数据库是常见的场景,这就是所谓的事务的并发。事务并发所可能存在的问题:

  1. 脏读:一个事务读到另一个事务未提交的更新数据。
  2. 不可重复读:一个事务两次读同一行数据,可是这两次读到的数据不一样。
  3. 幻读:一个事务执行两次查询,但第二次查询比第一次查询多出了一些数据行。
  4. 丢失更新:撤消一个事务时,把其它事务已提交的更新的数据覆盖了。

应对并发访问导致问题的举措

上述四种并发访问数据库导致的问题是并发情况下难免会发生的,但是又是不得不解决的问题。在JDBC中,数据库事务有四大隔离级别:

  • SERIALIZABLE(串行化)
    • 不会出现任何并发问题,因为它是对同一数据的访问是串行的,非并发访问的;
    • 性能最差;
  • REPEATABLE READ (可重复读)(MySQL)
    • 防止脏读和不可重复读,不能处理幻读问题;
    • 性能比SERIALIZABLE好
  • READ COMMITTED (读已提交数据)(Oracle)
    • 防止脏读,没有处理不可重复读,也没有处理幻读;
    • 性能比REPEATABLE READ好
  • READ UNCOMMITTED (读未提交数据)
    • 可能出现任何事务并发问题
    • 性能最好

理解:例如当数据库事务的隔离级别是读已提交,那么当前事务对数据进行了更改,只要是该事务还没有提交,那么其他事务对数据的更改是看不见的.

Spring中对事务的处理

Spring事务管理高层抽象主要包括3个接口:

  • PlatformTransactionManager 事务管理器

    我们常常使用的DataSourceTransactionManager就是它的一个间接子类

  • TransactionDefinition 事务定义信息(隔离、传播、超时、只读)

    主要定义了它自己的传播行为和隔离级别

  • TransactionStatus 事务具体运行状态

    主要方法:

    1
    2
    3
    4
    5
    6
    void flush();               //如果适用的话,这个方法用于刷新底层会话中的修改到数据库,例如,所有受影响的Hibernate/JPA会话。
    boolean hasSavepoint(); // 是否有恢复点
    boolean isCompleted(); // 是否已完成
    boolean isNewTransaction(); // 是否是新的事务
    boolean isRollbackOnly(); // 是否为只回滚
    void setRollbackOnly(); // 设置为只回滚

声明式事务管理

声明式事务管理是非侵入式的

xml方式配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
</beans> 
......
<!--配置事务-->
<!--第一步:配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="jdbcDatasource"/>
</bean>

<!--第二步:配置事务增强-->
<tx:advice id="advice" transaction-manager="transactionManager">
<!--做事务操作-->
<tx:attributes>
<!--设置事务操作的方法匹配规则-->
<tx:method name="add*"/>
</tx:attributes>
</tx:advice>

<!--第三步:配置切面-->
<aop:config>
<!--com.lee.JDBCTransaction包下的所有的类中的方法都会和事务增强中设置的匹配规则进行匹配-->
<aop:pointcut id="tx_pt" expression="execution(* com.lee.JDBCTransaction.*.*(..))"/>
<aop:advisor advice-ref="advice" pointcut-ref="tx_pt"/>
</aop:config>
......
</beans>

基于注解的配置方式

基于注解的方式简单好理解,是常用的方式

在xml配置文件中:

1
2
3
4
5
6
7
8
9
10
11
12
<beans>
<!--基于注解配置事务-->
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--配置数据源-->
<property name="dataSource" ref="jdbcDatasource"/>
</bean>

<!--开启对注解事务管理器的支持-->
<tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

然后在业务层中开启事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class UserServiceImpl2 implements UserService {

private UserDao mUserDao;

/**
* 模拟逻辑,向数据库中插入条相同的记录,并在第二条记录上打上"--copy"的标记
*/
@Override
@Transactional // 开启事务
public void addTwoSimpleUser(User user) {
mUserDao.add(user);

user.setUsername(user.getUsername() + "--copy");
int error = 2 / 0;
mUserDao.add(user);
}
}

@Transactional可以标记在方法上,当标记在方法上的时候,代表此方法作为一个事务的原子操作,该方法中的内容是事务中事务体.

@Transactional也可以标记在类上,当标记在类上面的时候,代表此类中的所有方法都开启了事务.

编程式事务管理

编程式事务管理是侵入性的,用的不多

请参考 Spring事务管理详解