0x00 参考

本笔记直接参考或引自如下链接文章:

https://www.w3cschool.cn/wkspring/

http://c.biancheng.net/spring/

0x01 Spring事务管理

事务就是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。

在企业级应用程序开发中,事务管理是必不可少的技术,用来确保数据的完整性和一致性。

事务的概念可以描述为具有以下四个关键属性说成是ACID

  • 原子性:事务应该当作一个单独单元的操作,这意味着整个序列操作要么是成功,要么是失败的。
  • 一致性:这表示数据库的引用完整性的一致性,表中唯一的主键等。
  • 隔离性:可能同时处理很多有相同的数据集的事务,每个事务应该与其他事务隔离,以防止数据损坏。
  • 持久性:一个事务一旦完成全部操作后,这个事务的结果必须是永久性的,不能因系统故障而从数据库中删除。

Spring的事务管理是基于AOP实现的,而AOP是以方法为单位的。Spring的事务属性分别为传播行为、隔离级别、只读和超时属性,这些属性提供了事务应用的方法和描述策略。

在Java EE开发经常采用的分层模式中,Spring的事务处理位于业务逻辑层,它提供了针对事务的解决方案。

0x02 三个核心类

在Spring解压包的libs目录中,包含一个名称为spring-tx-4.1.6.RELEASE.jar的文件,该文件是 Spring 提供的用于事务管理的 JAR 包,其中包括事务管理的三个核心接口:PlatformTransactionManager、TransactionDefinition和TransactionStatus。

三个核心类之间的关系如图:

PlatformTransactionManager

PlatformTransactionManager接口是Spring提供的平台事务管理器,用于管理事务。该接口中提供了三个事务操作方法,具体如下:

  • TransactionStatus getTransaction(TransactionDefinition definition):用于获取事务状态信息。
  • void commit(TransactionStatus status):用于提交事务。
  • void rollback(TransactionStatus status):用于回滚事务。

在项目中,Spring将xml中配置的事务详细信息封装到对象TransactionDefinition中,然后通过事务管理器的getTransaction()方法获得事务的状态(TransactionStatus),并对事务进行下一步的操作。

TransactionDefinition

TransactionDefinition接口是事务定义(描述)的对象,它提供了事务相关信息获取的方法,其中包括五个操作,具体如下:

  • String getName():获取事务对象名称。
  • int getIsolationLevel():获取事务的隔离级别。
  • int getPropagationBehavior():获取事务的传播行为。
  • int getTimeout():获取事务的超时时间。
  • boolean isReadOnly():获取事务是否只读。

隔离级别:

序号 隔离 & 描述
1 TransactionDefinition.ISOLATION_DEFAULT这是默认的隔离级别。
2 TransactionDefinition.ISOLATION_READ_COMMITTED表明能够阻止误读;可以发生不可重复读和虚读。
3 TransactionDefinition.ISOLATION_READ_UNCOMMITTED表明可以发生误读、不可重复读和虚读。
4 TransactionDefinition.ISOLATION_REPEATABLE_READ表明能够阻止误读和不可重复读;可以发生虚读。
5 TransactionDefinition.ISOLATION_SERIALIZABLE表明能够阻止误读、不可重复读和虚读。

传播类型:

序号 传播 & 描述
1 TransactionDefinition.PROPAGATION_MANDATORY支持当前事务;如果不存在当前事务,则抛出一个异常。
2 TransactionDefinition.PROPAGATION_NESTED如果存在当前事务,则在一个嵌套的事务中执行。
3 TransactionDefinition.PROPAGATION_NEVER不支持当前事务;如果存在当前事务,则抛出一个异常。
4 TransactionDefinition.PROPAGATION_NOT_SUPPORTED不支持当前事务;而总是执行非事务性。
5 TransactionDefinition.PROPAGATION_REQUIRED支持当前事务;如果不存在事务,则创建一个新的事务。
6 TransactionDefinition.PROPAGATION_REQUIRES_NEW创建一个新事务,如果存在一个事务,则把当前事务挂起。
7 TransactionDefinition.PROPAGATION_SUPPORTS支持当前事务;如果不存在,则执行非事务性。
8 TransactionDefinition.TIMEOUT_DEFAULT使用默认超时的底层事务系统,或者如果不支持超时则没有。

TransactionStatus

TransactionStatus接口是事务的状态,它描述了某一时间点上事务的状态信息。其中包含六个操作,具体如下:

名称 说明
void flush() 刷新事务
boolean hasSavepoint() 获取是否存在保存点
boolean isCompleted() 获取事务是否完成
boolean isNewTransaction() 获取是否是新事务
boolean isRollbackOnly() 获取是否回滚
void setRollbackOnly() 设置事务回滚

0x03 编程式事务管理

编程式事务管理方法允许你在对你的源代码编程的帮助下管理事务。这给了你极大地灵活性,但是它很难维护。

Demo

注意,这里需要的jar包和前面《Spring基础篇之JDBC框架》中的Demo是一样的。

StudentMarks.java:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.mi1k7ea;

public class StudentMarks {
private Integer age;
private String name;
private Integer id;
private Integer marks;
private Integer year;
private Integer sid;
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setMarks(Integer marks) {
this.marks = marks;
}
public Integer getMarks() {
return marks;
}
public void setYear(Integer year) {
this.year = year;
}
public Integer getYear() {
return year;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public Integer getSid() {
return sid;
}
}

StudentDAO.java,数据访问对象接口类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.mi1k7ea;

import javax.sql.DataSource;
import java.util.List;

public interface StudentDAO {
/**
* This is the method to be used to initialize
* database resources ie. connection.
*/
public void setDataSource(DataSource ds);
/**
* This is the method to be used to create
* a record in the Student and Marks tables.
*/
public void create(String name, Integer age, Integer marks, Integer year);
/**
* This is the method to be used to list down
* all the records from the Student and Marks tables.
*/
public List<StudentMarks> listStudents();
}

StudentMarksMapper.java,:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.mi1k7ea;

import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

public class StudentMarksMapper implements RowMapper<StudentMarks> {
public StudentMarks mapRow(ResultSet rs, int rowNum) throws SQLException {
StudentMarks studentMarks = new StudentMarks();
studentMarks.setId(rs.getInt("id"));
studentMarks.setName(rs.getString("name"));
studentMarks.setAge(rs.getInt("age"));
studentMarks.setSid(rs.getInt("sid"));
studentMarks.setMarks(rs.getInt("marks"));
studentMarks.setYear(rs.getInt("year"));
return studentMarks;
}
}

StudentJDBCTemplate.java,DAO接口类StudentDAO的实现类:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.mi1k7ea;

import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import javax.sql.DataSource;
import java.util.List;

public class StudentJDBCTemplate implements StudentDAO {
private DataSource dataSource;
private JdbcTemplate jdbcTemplateObject;
private PlatformTransactionManager transactionManager;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
this.jdbcTemplateObject = new JdbcTemplate(dataSource);
}
public void setTransactionManager(
PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void create(String name, Integer age, Integer marks, Integer year){
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
String SQL1 = "insert into Student (name, age) values (?, ?)";
jdbcTemplateObject.update( SQL1, name, age);
// Get the latest student id to be used in Marks table
String SQL2 = "select max(id) from Student";
int sid = jdbcTemplateObject.queryForInt( SQL2 );
String SQL3 = "insert into Marks(sid, marks, year) " +
"values (?, ?, ?)";
jdbcTemplateObject.update( SQL3, sid, marks, year);
System.out.println("Created Name = " + name + ", Age = " + age);
transactionManager.commit(status);
} catch (DataAccessException e) {
System.out.println("Error in creating record, rolling back");
transactionManager.rollback(status);
throw e;
}
return;
}
public List<StudentMarks> listStudents() {
String SQL = "select * from Student, Marks where Student.id=Marks.sid";
List <StudentMarks> studentMarks = jdbcTemplateObject.query(SQL,
new StudentMarksMapper());
return studentMarks;
}
}

MainApp.java:

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
26
package com.mi1k7ea;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
StudentJDBCTemplate studentJDBCTemplate = (StudentJDBCTemplate)context.getBean("studentJDBCTemplate");
System.out.println("------Records creation--------" );
studentJDBCTemplate.create("Zara", 11, 99, 2010);
studentJDBCTemplate.create("Nuha", 20, 97, 2010);
studentJDBCTemplate.create("Ayan", 25, 100, 2011);
System.out.println("------Listing all the records--------" );
List<StudentMarks> studentMarks = studentJDBCTemplate.listStudents();
for (StudentMarks record : studentMarks) {
System.out.print("ID : " + record.getId() );
System.out.print(", Name : " + record.getName() );
System.out.print(", Marks : " + record.getMarks());
System.out.print(", Year : " + record.getYear());
System.out.println(", Age : " + record.getAge());
}
}
}

Beans.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
26
27
28
29
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd ">

<!-- Initialization for data source -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</bean>

<!-- Initialization for TransactionManager -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

<!-- Definition for studentJDBCTemplate bean -->
<bean id="studentJDBCTemplate"
class="com.mi1k7ea.StudentJDBCTemplate">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
</bean>

</beans>

在执行之前,需要在数据库中执行以下SQL语句创建test数据库以及新建相关的表:

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE DATABASE test;
USE test;
CREATE TABLE Student(
ID INT NOT NULL AUTO_INCREMENT,
NAME VARCHAR(20) NOT NULL,
AGE INT NOT NULL,
PRIMARY KEY (ID)
);
CREATE TABLE Marks(
SID INT NOT NULL,
MARKS INT NOT NULL,
YEAR INT NOT NULL
);

创建成功后,运行MainApp成功使用JDBC框架实现数据库的连接使用:

0x04 声明式事务管理

Spring声明式事务管理在底层采用了AOP技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。

下面是与声明式事务相关的步骤:

  • 我们使用标签,它创建一个事务处理的建议,同时,我们定义一个匹配所有方法的切入点,我们希望这些方法是事务型的并且会引用事务型的建议。
  • 如果在事务型配置中包含了一个方法的名称,那么创建的建议在调用方法之前就会在事务中开始进行。
  • 目标方法会在try/catch块中执行。
  • 如果方法正常结束,AOP建议会成功的提交事务,否则它执行回滚操作。

Spring实现声明式事务管理主要有两种方式:

  • 基于XML方式的声明式事务管理。
  • 通过Annotation注解方式的事务管理。

基于XML实现事务管理

基于XML形式实现的事务管理,就是通过XML配置文件来实现的事务管理。下面直接看示例。

注意,这里需要的jar包,在前面编程式事务管理的基础上,还需添加一个c3p0、mchange-commons的jar包。

先在MySQL中创建一个名为spring的数据库,然后在该数据库中创建一个account表,并向表中插入两条数据,其SQL执行语句如下:

1
2
3
4
5
6
7
8
9
CREATE DATABASE spring;
USE spring;
CREATE TABLE account (
id INT (11) PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(20) NOT NULL,
money INT DEFAULT NULL
);
INSERT INTO account VALUES (1,'zhangsan',1000);
INSERT INTO account VALUES (2,'lisi',1000);

接着在项目的src下创建一个名为c3p0-db.properties的配置文件,这里使用C3P0数据源,需要在该文件中添加如下配置:

1
2
3
4
jdbc.driverClass = com.mysql.jdbc.Driver
jdbc.jdbcUrl = jdbc:mysql://localhost:3306/spring?serverTimezone=UTC
jdbc.user = root
jdbc.password =

AccountDao.java,DAO接口类,在com.mi1k7ea.dao包中,并在接口中创建汇款和收款的方法:

1
2
3
4
5
6
7
8
package com.mi1k7ea.dao;

public interface AccountDao {
// 汇款
void out(String outUser, int money);
// 收款
void in(String inUser, int money);
}

AccountDaoImpl.java,DAO层接口实现类,在com.mi1k7ea.dao.impl包中,使用JdbcTemplate.update()方法实现了更新操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.mi1k7ea.dao.impl;

import com.mi1k7ea.dao.AccountDao;
import org.springframework.jdbc.core.JdbcTemplate;

public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// 汇款的实现方法
public void out(String outUser, int money) {
this.jdbcTemplate.update("update account set money =money-? where username =?", money, outUser);
}
// 收款的实现方法
public void in(String inUser, int money) {
this.jdbcTemplate.update("update account set money =money+? where username =?", money, inUser);
}
}

AccountService.java,Service层DAO接口类,在com.mi1k7ea.service包中:

1
2
3
4
5
6
package com.mi1k7ea.service;

public interface AccountService {
// 转账
void transfer(String outUser, String inUser, int money);
}

AccountServiceImpl.java,Service层DAO接口实现类,在com.mi1k7ea.service.impl包中,实现了AccountService接口,,并对转账方法进行了实现,根据参数的不同调用DAO层相应的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.mi1k7ea.service.impl;

import com.mi1k7ea.dao.AccountDao;
import com.mi1k7ea.service.AccountService;

public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(String outUser, String inUser, int money) {
this.accountDao.out(outUser, money);
this.accountDao.in(inUser, money);
}
}

创建Spirng配置文件applicationContext.xml,位于src目录下,其中使用<tx:advice>标记配置事务通知内容:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.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-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!-- 加载properties文件 -->
<context:property-placeholder location="classpath:c3p0-db.properties" />
<!-- 配置数据源,读取properties文件信息 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}" />
<property name="jdbcUrl" value="${jdbc.jdbcUrl}" />
<property name="user" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- 配置jdbc模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置dao -->
<bean id="accountDao" class="com.mi1k7ea.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
<!-- 配置service -->
<bean id="accountService" class="com.mi1k7ea.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<!-- 事务管理器,依赖于数据源 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 编写通知:对事务进行增强(通知),需要编写切入点和具体执行事务的细节 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 给切入点方法添加事务详情,name表示方法名称,*表示任意方法名称,propagation用于设置传播行为,read-only表示隔离级别,是否只读 -->
<tx:method name="find*" propagation="SUPPORTS"
rollback-for="Exception" />
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT"
read-only="false" />
</tx:attributes>
</tx:advice>
<!-- aop编写,让Spring自动对目标生成代理,需要使用AspectJ的表达式 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut expression="execution(* com.mi1k7ea.service.*.*(..))"
id="txPointCut" />
<!-- 切面:将切入点与通知整合 -->
<aop:advisor pointcut-ref="txPointCut" advice-ref="txAdvice" />
</aop:config>
</beans>

MainApp.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.mi1k7ea;

import com.mi1k7ea.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
accountService.transfer("zhangsan", "lisi", 100);
}
}

运行之后,无报错的话,在MySQL中就可以看到zhangsan用户的钱减少了100而lisi的钱多了100:

基于Annotation实现事务管理

使用Annotation的方式非常简单,只需要在项目中做两件事:

  1. 在Spring容器中注册驱动,代码如下:

    1
    <tx:annotation-driven transaction-manager="txManager"/>
  2. 在需要使用事务的业务类或者方法中添加注解@Transactional,并配置@Transactional的参数;

下面还是修改上一小节的示例作为演示。

修改Spring配置文件applicationContext.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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.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-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!-- 加载properties文件 -->
<context:property-placeholder location="classpath:c3p0-db.properties" />
<!-- 配置数据源,读取properties文件信息 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}" />
<property name="jdbcUrl" value="${jdbc.jdbcUrl}" />
<property name="user" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- 配置jdbc模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置dao -->
<bean id="accountDao" class="com.mi1k7ea.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
<!-- 配置service -->
<bean id="accountService" class="com.mi1k7ea.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<!-- 事务管理器,依赖于数据源 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 注册事务管理驱动 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>

需要注意得失,在学习AOP注解方式开发时,需要在配置文件中开启注解处理器,指定扫描哪些包下的注解,这里没有开启注解处理器是因为在XML中手动配置了AccountServiceImpl,而@Transactional注解就配置在该类中,所以会直接生效。

修改AccountServiceImpl.java,在文件中添加@Transactional注解及参数,注意在使用@Transactional注解时参数之间用”,”进行分割:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.mi1k7ea.service.impl;

import com.mi1k7ea.dao.AccountDao;
import com.mi1k7ea.service.AccountService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false)
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(String outUser, String inUser, int money) {
this.accountDao.out(outUser, money);
this.accountDao.in(inUser, money);
}
}

运行MainApp之后,效果和前面的是一样的。