Spring 事务管理
1.事务问题
什么是事务:
事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败.
事务特性(4种):
原子性 (atomicity):强调事务的不可分割.
一致性 (consistency):事务的执行的前后数据的完整性保持一致.
隔离性 (isolation):一个事务执行的过程中,不应该受到其他事务的干扰
持久性(durability) :事务一旦结束,数据就持久到数据库
如果不考虑隔离性引发安全性问题:
脏读 :一个事务读到了另一个事务的未提交的数据
不可重复读 :一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致.
脏读:所谓的脏读,其实就是读到了别的事务回滚前的脏数据。比如事务B执行过程中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。
也就是说,当前事务读到的数据是别的事务想要修改成为的但是没有修改成功的数据。
不可重复读:事务A首先读取了一条数据,然后执行逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。
也就是说,当前事务先进行了一次数据读取,然后再次读取到的数据是别的事务修改成功的数据,导致两次读取到的数据不匹配,也就照应了不可重复读的语义。
幻读:事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N+M条数据了,就产生了幻读。
解决读问题: 设置事务隔离级别(5种)
DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
未提交读(read uncommited) :脏读,不可重复读,虚读都有可能发生
已提交读 (read commited):避免脏读。但是不可重复读和虚读有可能发生
可重复读 (repeatable read) :避免脏读和不可重复读.但是虚读有可能发生.
串行化的 (serializable) :避免以上所有读问题.
read uncommited:是最低的事务隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。
read commited:保证一个事物提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。
repeatable read:这种事务隔离级别可以防止脏读,不可重复读。但是可能会出现幻象读。它除了保证一个事务不能被另外一个事务读取未提交的数据之外还避免了以下情况产生(不可重复读)。
serializable:这是花费最高代价但最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读之外,还避免了幻象读(避免三种)。
PROPAGION_XXX :事务的传播行为
- 保证同一个事务中
PROPAGATION_REQUIRED 支持当前事务,如果不存在 就新建一个(默认)
PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务
PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常 - 保证没有在同一个事务中
PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常
PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行
1.导入JAR包
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!--数据库连接 -->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
</dependencies>
2.Dao层
2.1接口层
public interface AccountDao {
/**
* 加钱方法
* @param id
* @param money
*/
void increaseMoney(Integer id , Double money);
/**
* 减钱方法
* @param id
* @param money
*/
void decreaseMoney(Integer id , Double money);
}
2.2实现层
@Component("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void increaseMoney(Integer id, Double money) {
jdbcTemplate.update("update ar_account set money = money + ? where id = ?",money,id);
}
public void decreaseMoney(Integer id, Double money) {
jdbcTemplate.update("update ar_account set money = money - ? where id = ?",money,id);
}
}
3.Service层
3.1 service接口层
public interface AccountService {
//转账的方法
void Transfer(Integer from,Integer to ,Double money);
}
3.2 service实现层
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void Transfer(final Integer from, final Integer to,final Double money) {
accountDao.decreaseMoney(from,money);
int i =1/0;
accountDao.increaseMoney(to,money);
}
}
4.配置xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
<context:component-scan base-package="com.qf"></context:component-scan>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
<property name="user" value="root"></property>
<property name="password" value="59852369"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
5.调用
@RunWith(SpringJUnit4ClassRunner.class)
//指定创建容器时使用哪个配置文件
@ContextConfiguration("classpath:applicationContext.xml")
public class TestJdbc02 {
@Autowired
private AccountService accountService;
@Test
public void test(){
accountService.Transfer(3,2,1000.0);
}
}
2.使用AOP配置事务的方式
2.1 更改xml配置,配置事务,开启AOP的注解模式
<!-- 平台事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2.2配置通知类
@Component
@Aspect
public class MyAdvice {
@Autowired
private DataSourceTransactionManager transactional;
@Pointcut("execution( * com.qf.service.*.*.*(..))")
public void pt1(){
}
@Around("pt1()")
public Object around(ProceedingJoinPoint point) throws Throwable {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = transactional.getTransaction(def);
Object proceed=null;
try {
System.out.println("这是环绕通知之前的部分");
proceed = point.proceed();
System.out.println("这是环绕通知之后的部分");
transactional.commit(status);
}catch (Exception e){
transactional.rollback(status);
System.out.println(e.getMessage());
System.out.println("这是环绕通知异常的部分");
}finally {
System.out.println("这是最终通知");
}
return proceed;
}
}
3.使用TransactionTemplate配置事务的方式
3.1 增加xml配置
<bean id="template" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 传播行为 -->
<!-- REQUIRED:如果有事务,则在事务中执行;如果没有事务,则开启一个新的事物
<!—->l <!—->DEFAULT 使用数据库设置的隔离级别 ( 默认 ) ,由 DBA 默认的设置来决定隔离级别 .
<!—->l <!—->READ_UNCOMMITTED 会出现脏读、不可重复读、幻读 ( 隔离级别最低,并发性能高 )
<!—->l <!—->READ_COMMITTED 会出现不可重复读、幻读问题(锁定正在读取的行)
<!—->l <!—->REPEATABLE_READ 会出幻读(锁定所读取的所有行)
<!—->l <!—->SERIALIZABLE 保证所有的情况不会发生(锁表)
mysql的默认隔离界别 Repeatable Read :可重复读(会出现幻读)
-->
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="transfer" propagation="REQUIRED" />
<!-- SUPPORTS:如果有事务,则在事务中执行;如果没有事务,则不会开启事物 -->
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="select*" propagation="SUPPORTS" read-only="true" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.qf.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
3.2修改service转账方法
//将事务管理类注入到service中
@Autowired
private TransactionTemplate template;
public void Transfer(final Integer from, final Integer to,final Double money) {
template.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.decreaseMoney(from,money);
int i =1/0;
accountDao.increaseMoney(to,money);
}
});
}
4.使用注解配置事务方式
4.1修改xml
需要在Spring的配置中通过一行配置’通知’Spring容器对标注@Transactional注解的Bean进行加工处理!
在默认情况, <tx:annotation-driven /> 中transaction-manager属性会自动使用名为 “transactionManager” 的事务管理器.所以,如果用户将事务管理器的id定义为 transactionManager
, 则可以进一步将①处的配置简化为 <tx:annotation-driven />.
使用以上测试用例即可
<!--开启spring对注解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--①配置注解事务开启 对标注@Transactional注解的Bean进行加工处理,以织入事物管理切面 -->
<tx:annotation-driven transaction-manager="transactionManager" />
4.2修改service代码,配置事务管理
因为注解本身具有一组默认的事务属性,所以往往只要在需要事务的业务类中添加一个@Transactional注解,就完成了业务类事务属性的配置!
@Service
@Transactional
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void Transfer(final Integer from, final Integer to,final Double money) {
accountDao.decreaseMoney(from,money);
int i =1/0;
accountDao.increaseMoney(to,money);
}
}
4.3在何处标注@Transactional注解
@Transactional注解可以直接用于接口定义和接口方法,类定义和类的public方法上.
但Spring建议在业务实现类上使用@Transactional注解,当然也可以添加到业务接口上,但是这样会留下一些容易被忽视的隐患,因为注解不能被继承,所以业务接口中标注的@Transactional注解不会被业务类实现继承.
方法出添加注解会覆盖类定义处的注解,如果有写方法需要使用特殊的事务属性,则可以在类注解的基础上提供方法注解,如下:
@Transactional
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
//不等于默认值!可以覆盖类注解
@Transactional(readOnly = false , isolation = Isolation.READ_COMMITTED)
public void transfer(final Integer from, final Integer to, final Double money) {
accountDao.decreaseMoney(from,money);
// int i = 1/0;
accountDao.increaseMoney(to,money);
}
}