不知道你有没有被spring的事务困扰过,事务传播行为,事务隔离级别,aop一堆的概念袭来,要不要缴械投降?本文就来带你蹂躏这些难懂的概念,各个击破最终理解事物。

Spring提供了一个事务管理接口,PlatformTransactionManager它里面提供了常用的事务操作的方法:


public interface PlatformTransactionManager {

//获取事务状态信息
??? TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
??? //提交事务
??? void commit(TransactionStatus var1) throws TransactionException;
??? //回滚事务
??? void rollback(TransactionStatus var1) throws TransactionException;
}


PlatformTransactionManager接口里面定义了提交,回滚等对事物操作的基本方法。

这里出现了两个接口:

一:TransactionDefinition

它定义了事物的基本属性

public interface TransactionDefinition {

??? //返回事物的传播行为

int getPropagationBehavior();
//返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据。

int getIsolationLevel();
//返回事务必须在多少秒内完成
? int getTimeout();
//事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的。
? boolean isReadOnly();

}

事物的传播行为表示一个方法被另一个方法调用时事物如何传播。

?

我有一篇文章详细的讲解了事物传播行为:

?

事物的隔离级别 我在这篇文章里有详述:

?

getTimeout()返回事物的超时时间,事物的执行会不同程度的锁定表,如果时间过长,其它事物对表的操作就会受到影响,执行超时以后事物就会回滚。

isReadOnly()返回事物是否只读,如果事物只是去读取数据库我们可以把它设置为只读,数据库就可以进行相应的优化,提高执行效率。

?

二:TransactionStatus

此接口提供了事物运行的具体状态

public?interface?TransactionStatus?extends?SavepointManager,?Flushable?{
?????boolean?isNewTransaction();//是否是新的事物
?????boolean?hasSavepoint();//是否有恢复点
?????void?setRollbackOnly();//设置为只回滚
?????boolean?isRollbackOnly();//是否为只回滚
?????boolean?isCompleted();//是否已完成
?}
在回滚或提交操作的时候需要这些事物的状态作为参数。
PlatformTransactionManager只是一个事物管理的接口,我们真正要用的是该接口的实现类,如果你是用Jdbc或mybatis操作数据库需要使用事务管理类:
org.springframework.jdbc.datasource.DataSourceTransactionManager
如果用的是Hibernate需要使用事物管理类:
org.springframework.orm.hibernate5.HibernateTransactionManager
?
在spring中实现事物通常有两种方式:
一:基于?XML?的声明式事务控制(配置方式):
通过转账的例子来说明:


数据库:

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? spacer.gif

第一步:导入依赖

<dependencies>
?????<dependency>
?????????<groupId>org.springframework</groupId>
?????????<artifactId>spring-context</artifactId>
?????????<version>5.0.2.RELEASE</version>
?????</dependency>
?????<dependency>
?????????<groupId>org.springframework</groupId>
?????????<artifactId>spring-test</artifactId>
?????????<version>5.0.2.RELEASE</version>
?????</dependency>
?????????<dependency>
?????<groupId>mysql</groupId>
?????<artifactId>mysql-connector-java</artifactId>
?????<version>5.1.46</version>
?</dependency>
<dependency>
?????<groupId>org.springframework</groupId>
?????<artifactId>spring-jdbc</artifactId>
?????<version>5.0.2.RELEASE</version>
?</dependency>
<dependency>
?????????<groupId>junit</groupId>
?????????<artifactId>junit</artifactId>
?????????<version>4.12</version>
?????</dependency>
?????<dependency>
?????????<groupId>org.aspectj</groupId>
?????????<artifactId>aspectjweaver</artifactId>
?????????<version>1.8.7</version>
?????</dependency>
?????<dependency>
?????????<groupId>org.springframework</groupId>
?????????<artifactId>spring-tx</artifactId>
?????????<version>5.0.2.RELEASE</version>
?????</dependency>
?</dependencies>
?
spring-tx是事物管理的依赖,因为spring的事物管理底层使用的是aop所以需要把aop的依赖aspectjweaver也导入进来。
第二步:创建?spring?的配置文件并导入约束
<?xml?version="1.0"?encoding="UTF-8"?>
?<beans?xmlns="http://amyhdc.sun72.net/schema/beans"
????????xmlns:context="http://amyhdc.sun72.net/schema/context"?xmlns:p="http://amyhdc.sun72.net/schema/p"
????????xmlns:aop="http://amyhdc.sun72.net/schema/aop"?xmlns:tx="http://amyhdc.sun72.net/schema/tx"
????????xmlns:xsi="http://amyhdc.sun72.net/2001/XMLSchema-instance"
????????xsi:schemaLocation="http://amyhdc.sun72.net/schema/beans?http://amyhdc.sun72.net/schema/beans/spring-beans-4.0.xsd
????http://amyhdc.sun72.net/schema/context?http://amyhdc.sun72.net/schema/context/spring-context-4.0.xsd
????http://amyhdc.sun72.net/schema/aop?http://amyhdc.sun72.net/schema/aop/spring-aop-4.0.xsd?http://amyhdc.sun72.net/schema/tx?http://amyhdc.sun72.net/schema/tx/spring-tx-4.0.xsd
????http://amyhdc.sun72.net/schema/util?http://amyhdc.sun72.net/schema/util/spring-util-4.0.xsd">
?</beans>
第三步:创建Account实体类
第四步:编写?Dao?接口和实现类


public interface IAccountDao {
//根据名称查询账户信息
Account findAccountByName(String name);
//更新账户信息
void updateAccount(Account account);

}

?

?

public?class?AccountDaoImpl?implements?IAccountDao?{
?????@Autowired
?????private?JdbcTemplate?jt;
?????public?Account?findAccountByName(String?name)?{
?????????List<Account>?lst?=?jt.query("select?*?from?account?where?name?like??",new?BeanPropertyRowMapper<Account>(Account.class),name);
?????????return?lst.isEmpty()?null:lst.get(0);
?????}
?????public?void?updateAccount(Account?account)?{
?????????jt.update("update?account?set?money?=???where?id?=???",account.getMoney(),account.getId());
?????}
?}
第五步:编写业务层接口和实现类
public?interface?IAccountService?{
?*?转账
?*?@param?sourceName?转出账户名称
?*?@param?targeName?转入账户名称
?*?@param?money?转账金额
?*/
?void?transfer(String?sourceName,String?targeName,Float?money);//增删改
}
?
/**
?*?账户的业务层实现类
?*/
?public?class?AccountServiceImpl?implements?IAccountService?{
?????private?IAccountDao?accountDao;
?????????public?void?transfer(String?sourceName,?String?targeName,?Float?money)?{
?????????//1.根据名称查询两个账户
?????????Account?source?=?accountDao.findAccountByName(sourceName);
?????????Account?target?=?accountDao.findAccountByName(targeName);
????????//2.修改两个账户的金额
?????????source.setMoney(source.getMoney()-money);//转出账户减钱
?????????target.setMoney(target.getMoney()+money);//转入账户加钱
????????//3.更新两个账户
?????????accountDao.updateAccount(source);
?????????int?i=1/0;
?????????accountDao.updateAccount(target);
?????}
?}
第六步:在配置文件中配置业务层和持久层
<context:component-scan?base-package="cn.xh"></context:component-scan>
?????<bean?id="jdbcTemplate"?class="org.springframework.jdbc.core.JdbcTemplate">
?????????<property?name="dataSource"?ref="dataSource"></property>
?????</bean>
?????<!--?配置?service?-->
?????<bean?id="accountService"?class="cn.xh.service.AccountServiceImpl">
?
?????</bean>
?????<!--?配置?dao?-->
?????<bean?id="accountDao"?class="cn.xh.dao.AccountDaoImpl">
?
?????</bean>
?????<!--?配置数据源?-->
?????<bean?id="dataSource"
???????????class="org.springframework.jdbc.datasource.DriverManagerDataSource">
?????????<property?name="driverClassName"?value="com.mysql.jdbc.Driver"></property>
?????????<property?name="url"?value="jdbc:mysql:///spring_demo01"></property>
?????????<property?name="username"?value="root"></property>
?????????<property?name="password"?value="123456"></property>
</bean>


第七步:配置事务

Spring事务是基于aop的关于aop我在这篇文章里有详细的讲解:

?

?

?

配置步骤:

1: 配置事务管理器:

<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<!-- 注入 DataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>

2:配置事务的通知引用事务管理器:

<tx:advice id="txAdvice" transaction-manager="transactionManager">
</tx:advice>

?

3:配置事务的属性:

<!--?配置事务的通知-->
?<tx:advice?id="txAdvice"?transaction-manager="transactionManager">
?????<!--?配置事务的属性
?????????????isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
?????????????propagation:用于指定事务的传播行为。
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
?????????????timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
?????????????rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
?????????????no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
?????-->
?????<tx:attributes>
?????????<tx:method?name="*"?propagation="REQUIRED"?read-only="false"/>
?????????<tx:method?name="find*"?propagation="SUPPORTS"?read-only="true"></tx:method>
?????</tx:attributes>
?</tx:advice>


?

4:配置 AOP 切入点表达式:

????<!--?配置aop-->
?<aop:config>
?????<!--?配置切入点表达式-->
?????<aop:pointcut?id="pt1"?expression="execution(*?cn.xh.service.*.*(..))"></aop:pointcut>
?????<!--建立切入点表达式和事务通知的对应关系?-->
?????<aop:advisor?advice-ref="txAdvice"?pointcut-ref="pt1"></aop:advisor>
?</aop:config>


?

我们看看这个配置:

<!-- 配置切入点表达式-->
??? <aop:pointcut id="pt1" expression="execution(* cn.xh.service.*.*(..))"></aop:pointcut>

说明对cn.xh.service下的所有类的所有方法加上事务。

再来看看这个配置:

<!--?配置事务的通知-->
?<tx:advice?id="txAdvice"?transaction-manager="transactionManager">
<tx:attributes>
?????????<tx:method?name="*"?propagation="REQUIRED"?read-only="false"/>
?????????<tx:method?name="find*"?propagation="SUPPORTS"?read-only="true"></tx:method>
?????</tx:attributes>
?</tx:advice>


这个配置说明以find开头的方法事务的传播方式为SUPPORTS,为只读,其它的方法事务传播方式为REQUIRED,不是只读。

?

好啦,现在可以测试啦:

@RunWith(SpringJUnit4Cla***unner.class)
?@ContextConfiguration("classpath:applicationContext.xml")
?public?class?serviceTest?{
?????@Autowired
?????private?IAccountService?serviceImpl;
?????@Test
?????public?void?testDo(){
?????????serviceImpl.transfer("%张%","%李%",1000f);
?????}
?}
从张三账户往李四账户转1000,因为有除零错误,事务回滚,张三账户还是2000,李四账户还是3000.
?
再来看看事务实现的第二种方式
二:基于注解的配置方式


我们重点看一下事务的注解配置,其它同上。

配置步骤:

第一步:配置事务管理器并注入数据源

<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>

第二步:在业务层使用@Transactional 注解:

@Transactional(readOnly=true,propagation=Propagation.SUPPORTS)
public class AccountServiceImpl implements IAccountService {
????? @Autowired
????? private IAccountDao accountDao;
????? @Override
????? public Account findAccountById(Integer id) {
??????????? ??return accountDao.findAccountById(id);
????? }


????? @Override
????? @Transactional(readOnly=false,propagation=Propagation.REQUIRED)
????? public void transfer(String sourceName, String targeName, Float money) {
????????????? //1.根据名称查询两个账户
???????????? Account source = accountDao.findAccountByName(sourceName);
???????????? Account target = accountDao.findAccountByName(targeName);
???????????? //2.修改两个账户的金额
??????????? source.setMoney(source.getMoney()-money);//转出账户减钱
??????????? target.setMoney(target.getMoney()+money);//转入账户加钱
??????????? //3.更新两个账户
??????????? accountDao.updateAccount(source);
?????????? //int i=1/0;
?????????? accountDao.updateAccount(target);

}
}
该注解的属性和 xml 中的属性含义一致,该注解可以出现在接口上,类上和方法上。
出现接口上,表示该接口的所有实现类都有事务支持。
出现在类上,表示类中所有方法有事务支持
出现在方法上,表示方法有事务支持。
以上三个位置的优先级:方法>类>接口

第三步:在配置文件中开启 spring 对注解事务的支持:
<tx:annotation-driven transaction-manager="transactionManager"/>