Spring学习笔记八
事务介绍
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。
这里我们以取钱的例子来讲解:比如你去ATM机取1000块钱,大体有两个步骤:第一步输入密码和金额,银行卡扣掉1000元钱;第二步从ATM出1000元钱。这两个步骤必须是要么都执行要么都不执行。如果银行卡扣除了1000元但是ATM机出钱失败,你将会损失1000元;如果银行卡扣钱失败但是ATM机出了1000元,那么银行将损失1000元。
如何保证这两个步骤不会出现一个异常了,而另外一个执行成功呢?事务就是用来解决这样的问题的。事务是一系列的动作,他们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的组状态,仿佛什么都没有发生过。在企业级应用程序开发中,事务管理是必不可少的技术,用来确保数据的完整性和一致性。
事务的四个特性(ACID)
- 原子性(Atomicity):事务是一个原子操作,有一系列动作组成。事务的原子性且薄动作要么全部完成,要么完全不起作用。
- 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一个状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
- 隔离性(Isolation):可能有许多事务或同事处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
- 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该收到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
Spring事务管理的核心
首先我们先导入Spring的事务包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
我们打开Spring 的核心事务包,查看类 org.springframework.transaction
上面所示的三个类文件便是Spring的事务管理接口。下面是多这三个接口的简单分析:
PlatformTransactionManager 事务管理器
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,如上图所示,Spring并不直接管理事务,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,也就是将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
我们进入到 PlatformTransactionManager 接口,查看源码:
- TransactionStatus getTransaction(TransactionDefinition definition) ,事务管理器 通过TransactionDefinition,获得“事务状态”,从而管理事务。
- void commit(TransactionStatus status) 根据状态提交
- void rollback(TransactionStatus status) 根据状态回滚
也就是说Spring事务管理的为不同的事务API提供一致的编程模型,具体的事务管理管理机制由各个平台去实现。
TransactionStatus 事务状态
在上面PlatformTransactionManager 接口中,有如下方法:
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
这个方法返回的是TransactionStatus 对象,然后程序根据返回的对象来获取事务状态,然后进行相应的操作。
而 TransactionStatus 这和接口的内容如下:
这个接口描述的是一些处理事务提供简单的事务执行和查询事务状态的方法,在回滚或者提交时需要应用对应的事务状态。
TransactionDefinition 基本事务属性的定义
上面讲的事务管理接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。
事务属性包括了5各方面
一 传播行为:当事务方法被另一个事务方法调用时,必须指定事务应该如何传播
Spring 定义了7个传播行为,这里以A业务和B业务之间的传播事务为说明:
- PROPAGATION_REQUIRED:required,必须。默认值,A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务。
- PROPAGATION_SUPPORTS:supports ,支持。A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行。
- PROPAGATION_MANDATORY:mandatory,强制。A如果有事务,B将使用该事务;如果A没有事务,B将抛异常。
- PROPAGATION_REQUIRES_NEW :requires_new,必须新的。如果A有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务。
- PROPAGATION_NOT_SUPPORTED:not_supported,不支持。如果A有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行。
- PROPAGATION_NEVER :never,从不。如果Ayou事务,B将抛异常;如果A没有事务,B将以非事务执行。
- PROPAGATION_NESTED :nested ,嵌套。A 和B底层采用保存点机制,形成嵌套事务。
二隔离级别:定义了一个事务可能受其他的并发事务影响的程度。
并发事务引起的问题:
在典型的应用程序中,多个事务并发运行,经常操作相同的数据来完成各自的任务。并发虽然是必须的,但可能会导致一下的问题。
- 脏读(Dirty reads)——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
- 不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行了相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。
- 幻读(Phantom read)——幻读与不可重复复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。
注意:不可重复读重点是修改看,而幻读重点是新增或删除。
在Spring 事务管理中,为我们定义了如下的隔离级别:
- ISOLATION_DEFAULT:使用后端数据库默认的隔离级别
- ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
- ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
- ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一样的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
- ISOLATION_SERIALIZABLE:最高的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过俄安全锁定事务相关的数据库表来实现的
上面定义的隔离级别,在Spring 的TransactionDefinition中也分别用常量-1,0,1,2,4,8,表示。
三只读
这是事务的第三特性,是否为只读事务。如果事务只对后端的数据库进行操作,数据库可以利用事务的只读性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用他认为合适的优化措施。
四 事务超时
为了使应用程序很好的运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库的资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。
五 回滚规则
事务的做后一个方面是一组规则,这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)。但是你可以声明事务在遇到特定的检查型时像遇到运行期间异常那样回滚。同样,你还可以声明事务遇到特定的异常不会滚,即使遇到这些异常是运行期异常。
Spring 编程式事务和声明式事务的区别
编程式事务处理:
所谓编程式事务指的是通过编码方式实现事务,允许用户在代码中精确定义事务的边界。即类似于JDBC编程实现事务管理。管理使用 TransactionTemplate 或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring 推荐使用TransactionTemplate 。
声明式事务管理:
管理建立在AOP上面的。其本质对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点是不通过编程的方式管理事务,这样就不需要再业务逻辑代码中掺杂事务管理的代码,只需要在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方法),便可以将事务规则应用到业务逻辑中。
简答地说,编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;而声明式事务由于基于AOP,所以既能起到事务管理的作用,又可以不影响业务带的具体实现。
不用事务实现转账
第一步:创建Java工程并导入相应的Jar包
<dependencies>
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-expression -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
</dependencies>
第二步:编写Dao层
AccountDao接口:
public interface AccountDao{
/**
* 汇款
* @param outer 汇款人
* @param money 汇款金额
*/
public void out(String outer,int money);
/**
* 收款
* @param inner 收款人
* @param money 收款金额
*/
public void in(String inner,int money);
}
AccountDaoImpl 接口实现类
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao{
@Override
public void out(String outer, int money) {
this.getJdbcTemplate().update("update account set money = money - ? where username = ?",money,outer);
}
@Override
public void in(String inner, int money) {
this.getJdbcTemplate().update("update account set money = money + ? where username = ?",money,inner);
}
}
第三步:实现Service层
AccountService接口:
public interface AccountService{
/**
* 转账
* @param outer 汇款人
* @param inner 收款人
* @param money 交易金额
*/
public void transfer(String outer,String inner,int money);
}
AccountServiceImpl 接口实现类
public class AccountServiceImpl implements AccountService{
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao){
this.accountDao = accountDao;
}
@Override
public void transfer(String outer, String inner, int money) {
accountDao.out(outer, money);
accountDao.in(inner, money);
}
}
第四步:Spring 全局配置文件 applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<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/testdb"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<bean id="accountDao" class="com.rzjt.trans.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="accountService" class="com.rzjt.trans.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
</beans>
第五步:测试
public class App
{
public static void main( String[] args )
{
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService account = (AccountService) context.getBean("accountService");
account.transfer("Tom", "Marry", 1000);
}
}
第六步:查看数据表account
上面的结果和我们想的一样,Tom 账户 的 money 减少了1000块。而Mary账户金额增加了1000块。
这时候问题来了,比如在Tom账户money减少了1000块正常。而Marry账户金额增加时发生了异常,实际应用过程中比如断电:
public class AccountServiceImpl implements AccountService{
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao){
this.accountDao = accountDao;
}
@Override
public void transfer(String outer, String inner, int money) {
accountDao.out(outer, money);
int i = 1/0;
accountDao.in(inner, money);
}
}
我们发现,程序执行报错了,但是数据库Tom账户减少了1000块,但是Marry账户的金额没有增加。这在实际应用中肯定是不允许的,那么怎么解决呢?
编程式事务处理实现转账
上面转账的两步操作中间发生了异常,但是第一步依然在数据库中进行了增加操作。实际应用中不会允许这样的情况发生,所以我们这里用事务来进行管理。
Dao层不变,我们在Service层注入TransactionTemplate模板,因为是用模板来管理事务,所以模板需要注入事务管理器 DataSoourceTransactionManager。而事务管理器说到底还是用底层的JDBC在管理,所以我们需要在事务管理中注入DataSource。这几个步骤分别如下:
AccountServiceImpl 接口:
public class AccountServiceImpl implements AccountService{
private AccountDao accountDao;
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate){
this.transactionTemplate = transactionTemplate;
}
public void setAccountDao(AccountDao accountDao){
this.accountDao = accountDao;
}
@Override
public void transfer(String outer, String inner, int money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult(){
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
accountDao.out(outer, money);
//int i = 1/0;
accountDao.in(inner, money);
}
});
}
}
Spring 全局配置文件applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/testdb"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<bean id="accountDao" class="com.rzjt.trans.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="accountService" class="com.rzjt.trans.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<property name="transactionTemplate" ref="transactionTemplate"></property>
</bean>
<!-- 创建模板 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="txManager"></property>
</bean>
<!-- 配置事务管理器 ,管理器需要事务,事务从Connection获得,连接从连接池DataSource获得 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
测试文件保持不变,可以分两次测试,第一次两次操作没有发生异常,然后数据库正常改变了。第二次操作中间发生了加长,发现数据库内容改变。
如果大家有兴趣也可以试试底层的PlarformTransactionManager来进行事务管理
//定义一个某个框架平台的TransactionManager,如JDBC、Hibernate
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 设置数据源
DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定义事务属性
transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 设置传播行为属性
TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 获得事务状态
try {
// 数据库操作
accountDao.out(outer, money);
int i = 1/0;
accountDao.in(inner, money);
dataSourceTransactionManager.commit(status);// 提交
} catch (Exception e) {
dataSourceTransactionManager.rollback(status);// 回滚
}
声明式事务处理时限转账(基于AOP 的xml配置)
Dao层和Service层与我们最先开始的不用事务时限转账保持不变。主要是applicationContext.xml文件发生了变化。
在applicationContext.xml文件中配置aop自动生成代理,进行事务管理:
- 配置管理器
- 配置事务详情
- 配置AOP
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/testdb"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<bean id="accountDao" class="com.rzjt.trans.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="accountService" class="com.rzjt.trans.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<!-- <property name="transactionTemplate" ref="transactionTemplate"></property> -->
</bean>
<!-- 配置事务管理器 ,管理器需要事务,事务从Connection获得,连接从连接池DataSource获得 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2 事务详情(事务通知) , 在aop筛选基础上,比如对ABC三个确定使用什么样的事务。例如:AC读写、B只读 等
<tx:attributes> 用于配置事务详情(属性属性)
<tx:method name=""/> 详情具体配置
propagation 传播行为 , REQUIRED:必须;REQUIRES_NEW:必须是新的
isolation 隔离级别
-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>
<!-- 3 AOP编程,利用切入点表达式从目标类方法中 确定增强的连接器,从而获得切入点 -->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.rzjt.service..*.*(..))"/>
</aop:config>
</beans>
声明式事务处理时限转账(基于AOP的注解配置)
分为两步:
- 在applicationContext.xml 配置事务管理器,将事务管理器交给spring
- 在目标类或目标方法添加注解即可@Transaction
首先在applicationContext.xml 文件中配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/testdb"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<bean id="accountDao" class="com.rzjt.trans.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="accountService" class="com.rzjt.trans.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<!-- <property name="transactionTemplate" ref="transactionTemplate"></property> -->
</bean>
<!-- 配置事务管理器 ,管理器需要事务,事务从Connection获得,连接从连接池DataSource获得 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 将管理器交予spring
* transaction-manager 配置事务管理器
* proxy-target-class
true : 底层强制使用cglib 代理
-->
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
</beans>
其次在目标类或者目标方法上添加注解@Transaction.如果在类上添加,则说明类中的所有方法都添加事务,如果在方法上添加,则只有该方法具有事务。
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT)
public class AccountServiceImpl implements AccountService{...}