Spring中的事务管理
事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用。例如网上购物:购物者的账户减少相应的数额和商品库存减少相应的数目这两个操作必须同时作用或一起不作用。
事务的四个关键属性(ACID)
原子性(atomicity): 事务是一个原子操作, 由一系列动作组成。事务的原子性确保动作要么全部完成要么完全不起作用。
一致性(consistency): 一旦所有事务动作完成, 事务就被提交。 数据和资源就处于一种满足业务规则的一致性状态中。
隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏。
持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响。通常情况下, 事务的结果被写到持久化存储器中(也就是数据库中)。
二、事务的基本属性详解
事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。 TransactionDefinition接口内容如下:
public interface TransactionDefinition { int getPropagationBehavior(); // 返回事务的传播行为 int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据 int getTimeout(); // 返回事务必须在多少秒内完成 boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的 }
1、事务的传播行为
事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:如下图所示
(1)PROPAGATION_REQUIRED
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。如下面的例子:
//事务属性 PROPAGATION_REQUIRED
methodA{
……
methodB();
……
}
//事务属性 PROPAGATION_REQUIRED
methodB{
……
}
spring使用AOP来支持声明式事务,会根据事务属性,自动在方法调用之前决定是否开启一个事务,并在方法执行之后决定事务提交或回滚事务。
当我们单独调用methodB方法:
main{ metodB(); } 相当于 main{ Connection con=null; try{ con = getConnection(); con.setAutoCommit(false); //方法调用 methodB(); //提交事务 con.commit(); } Catch(RuntimeException ex) { //回滚事务 con.rollback(); } finally { //释放资源 closeCon(); } }
而当我们单独调用MethodA时,在MethodA内又会调用MethodB。
执行效果相当于:
main{ Connection con = null; try{ con = getConnection(); methodA(); con.commit(); } catch(RuntimeException ex) { con.rollback(); } finally { closeCon(); } }
(2)PROPAGATION_REQUIRES_NEW
总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。如下面的例子:
//事务属性 PROPAGATION_REQUIRED
methodA(){
doSomeThingA();
methodB();
doSomeThingB();
}
//事务属性 PROPAGATION_REQUIRES_NEW
methodB(){
……
}
此时我们调用A方法:
main(){methodA();
}
相当于
main(){ TransactionManager tm = null; try{ //获得一个JTA事务管理器 tm = getTransactionManager(); tm.begin();//开启一个新的事务 Transaction ts1 = tm.getTransaction(); doSomeThingA(); tm.suspend();//挂起当前事务 try{ tm.begin();//重新开启第二个事务 Transaction ts2 = tm.getTransaction(); methodB(); ts2.commit();//提交第二个事务 } Catch(RunTimeException ex) { ts2.rollback();//回滚第二个事务 } finally { //释放资源 } //methodB执行完后,恢复第一个事务 tm.resume(ts1); doSomeThingB(); ts1.commit();//提交第一个事务 } catch(RunTimeException ex) { ts1.rollback();//回滚第一个事务 } finally { //释放资源 } }
2、隔离级别
事务的第二个维度就是隔离级别(isolation level)。隔离级别定义了一个事务可能受其他并发事务影响的程度。
(1)并发事务引起的问题
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务。并发虽然是必须的,但可能会导致一下的问题。
脏读(Dirty reads)——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。
幻读(Phantom read)——幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。
(2)不可重复读与幻读的区别
不可重复读的重点是修改:
同样的条件, 你读取过的数据, 再次读取出来发现值不一样了。
例如:在事务1中,Mary 读取了自己的工资为1000,操作并没有完成。
con1 = getConnection();
select salary from employee empId ="Mary";
在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务。
con2 = getConnection();
update employee set salary = 2000;
con2.commit();
在事务1中,Mary 再次读取自己的工资时,工资变为了2000。
//con1
select salary from employee empId ="Mary";
在一个事务中前后两次读取的结果并不一致,导致了不可重复读。
幻读的重点在于新增或者删除:
同样的条件, 第1次和第2次读出来的记录数不一样。
例如:目前工资为1000的员工有10人。事务1,读取所有工资为1000的员工。
con1 = getConnection();
Select * from employee where salary =1000;
共读取10条记录。
这时另一个事务向employee表插入了一条员工记录,工资也为1000。
con2 = getConnection();
Insert into employee(empId,salary) values("Lili",1000);
con2.commit();
事务1再次读取所有工资为1000的员工。
//con1
select * from employee where salary =1000;
共读取到了11条记录,这就产生了幻像读。
从总的结果来看, 似乎不可重复读和幻读都表现为两次读取的结果不一致。但如果你从控制的角度来看, 两者的区别就比较大。
对于前者, 只需要锁住满足条件的记录。
对于后者, 要锁住满足条件及其相近的记录。
(3)Spring提供的隔离级别
事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持。
Oracle 支持的 2 种事务隔离级别:READ_COMMITED , SERIALIZABLE。
在MySQL中,实现了四种隔离级别,分别有可能产生问题如下所示:
3、只读属性
事务的第三个特性是它是否为只读事务。如果事务只对后端的数据库进行查询操作,则可以将事务指定为只读。数据库可以利用事务的只读特性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。
4、事务超时
为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。
5、回滚规则
事务最后一个方面是一组规则,这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)。
但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
Spring 既支持编程式事务管理, 也支持声明式的事务管理。
声明式事务管理: 大多数情况下比编程式事务管理更好用。它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理。事务管理作为一种横切关注点, 可以通过 AOP 方法模块化。Spring 通过 Spring AOP 框架支持声明式事务管理。
Spring事务管理的结构如下:
Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
1、JDBC事务
如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
2、Hibernate事务
如果应用程序的持久化是通过Hibernate实现的,那么需要使用HibernateTransactionManager。对于Hibernate4,需要在Spring上下文定义中添加如下的<bean>声明:
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
3、JPA事务
Hibernate多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野。如果你计划使用JPA的话,那你需要使用Spring的JpaTransactionManager来处理事务。你需要在Spring中这样配置JpaTransactionManager:
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
4、Java原生API事务
如果你没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),你就需要使用JtaTransactionManager:
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManagerName" value="java:/TransactionManager" /> </bean>
看以看出所有的事务管理器都是以普通的 Bean 形式声明在 Spring IOC 容器中。
四、Spring中的声明式事务配置(针对JDBC)
1、基于XML配置
为了在 Spring 2.x 中启用声明式事务管理, 可以通过 tx Schema 中定义的 <tx:advice> 元素声明事务通知, 为此必须事先将这个 Schema 定义添加到 <beans> 根元素中去。
声明了事务通知后, 就需要将它与切入点关联起来. 由于事务通知是在 <aop:config> 元素外部声明的, 所以它无法直接与切入点产生关联. 所以必须在 <aop:config> 元素中声明一个增强器通知与切入点关联起来。
由于 Spring AOP 是基于代理的方法, 所以只能增强公共方法. 因此, 只有公有方法才能通过 Spring AOP 进行事务管理。如下例:
<!-- 1. 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 2. 配置事务属性 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- 根据方法名指定事务的属性 --> <tx:method name="purchase" propagation="REQUIRES_NEW"/> <tx:method name="get*" read-only="true"/> <tx:method name="find*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- 3. 配置事务切入点, 以及把事务切入点和事务属性关联起来 --> <aop:config> <aop:pointcut expression="execution(* com.atguigu.spring.tx.xml.service.*.*(..))" id="txPointCut"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config>
2、基于注解
除了在带有切入点, 通知和增强器的 Bean 配置文件中声明事务外, Spring 还允许简单地用 @Transactional 注解来标注事务方法。
为了将方法定义为支持事务处理的, 可以为方法添加 @Transactional 注解。根据 Spring AOP 基于代理机制, 只能标注公有方法。
可以在方法或者类级别上添加 @Transactional 注解。 当把这个注解应用到类上时, 这个类中的所有公共方法都会被定义成支持事务处理的。
在 Bean 配置文件中只需要启用 <tx:annotation-driven> 元素, 并为之指定事务管理器就可以了。
如果事务处理器的名称是 transactionManager, 就可以在<tx:annotation-driven> 元素中省略 transaction-manager 属性. 这个元素会自动检测该名称的事务处理器。如下例:
<!--配置事务管理器:使用事务必须 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--启用事务注解 --> <tx:annotation-driven transaction-manager="transactionManager"/>
此时在DAO上需加上@Transactional注解即可
// @Transactional(propagation=Propagation.REQUIRES_NEW, // isolation=Isolation.READ_COMMITTED, // readOnly=false, // timeout=3) @Transactional public void purchase(String username, String isbn){ //实现方法 }
添加事务注解属性
---使用 propagation 指定事务的传播行为,默认取值为 REQUIRED。
---使用 isolation 指定事务的隔离级别, 最常用的取值为 READ_COMMITTED。
---默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚. 也可以通过对应的属性进行设置. 通常情况下使用默认值即可.
---使用 readOnly 指定事务是否为只读。
---使用 timeout 指定强制回滚之前事务可以占用的时间.
五、基于注解的声明事务示例
1、首先是数据库表
book(isbn, book_name, price)
account(username, balance)
book_stock(isbn, stock)
2、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: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/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scan base-package="com.springinaction.transaction"></context:component-scan> <!-- 导入资源文件 --> <context:property-placeholder location="classpath:db.properties"/> <!-- 配置 C3P0 数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property> </bean> <!-- 配置 Spirng 的 JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置 NamedParameterJdbcTemplate, 该对象可以使用具名参数, 其没有无参数的构造器, 所以必须为其构造器指定参数--> <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"> <constructor-arg ref="dataSource"></constructor-arg> </bean> <!--配置事务管理器:使用事务必须 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--启用事务注解 --> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
3、使用的类
BookShopDao
package com.springinaction.transaction; public interface BookShopDao { // 根据书号获取书的单价 public int findBookPriceByIsbn(String isbn); // 更新书的库存,使书号对应的库存-1 public void updateBookStock(String isbn); // 更新用户的账户余额:account的balance-price public void updateUserAccount(String username, int price); }
BookShopDaoImpl
package com.springinaction.transaction; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository("bookShopDao") public class BookShopDaoImpl implements BookShopDao { @Autowired private JdbcTemplate JdbcTemplate; @Override public int findBookPriceByIsbn(String isbn) { String sql = "SELECT price FROM book WHERE isbn = ?"; return JdbcTemplate.queryForObject(sql, Integer.class, isbn); } @Override public void updateBookStock(String isbn) { //检查书的库存是否足够,若不够,则抛出异常 String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?"; int stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn); if (stock == 0) { throw new BookStockException("库存不足!"); } String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?"; JdbcTemplate.update(sql, isbn); } @Override public void updateUserAccount(String username, int price) { //检查余额是否不足,若不足,则抛出异常 String sql2 = "SELECT balance FROM account WHERE username = ?"; int balance = JdbcTemplate.queryForObject(sql2, Integer.class, username); if (balance < price) { throw new UserAccountException("余额不足!"); } String sql = "UPDATE account SET balance = balance - ? WHERE username = ?"; JdbcTemplate.update(sql, price, username); } }
BookShopService
package com.springinaction.transaction; public interface BookShopService { public void purchase(String username, String isbn); }
BookShopServiceImpl
package com.springinaction.transaction; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service("bookShopService") public class BookShopServiceImpl implements BookShopService { @Autowired private BookShopDao bookShopDao; /** * 1.添加事务注解 * 使用propagation 指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时如何使用事务。 * 默认取值为REQUIRED,即使用调用方法的事务 * REQUIRES_NEW:使用自己的事务,调用的事务方法的事务被挂起。 * * 2.使用isolation 指定事务的隔离级别,最常用的取值为READ_COMMITTED * 3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置。通常情况下,默认值即可。 * 4.使用readOnly 指定事务是否为只读。 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。若真的是一个只读取数据库值得方法,应设置readOnly=true * 5.使用timeOut 指定强制回滚之前事务可以占用的时间。 */ @Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED, noRollbackFor={UserAccountException.class}, readOnly=true, timeout=3) @Override public void purchase(String username, String isbn) { //1.获取书的单价 int price = bookShopDao.findBookPriceByIsbn(isbn); //2.更新书的库存 bookShopDao.updateBookStock(isbn); //3.更新用户余额 bookShopDao.updateUserAccount(username, price); } }
Cashier
package com.springinaction.transaction; import java.util.List; public interface Cashier { public void checkout(String username, List<String>isbns); }
CashierImpl
package com.springinaction.transaction; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service("cashier") public class CashierImpl implements Cashier { @Autowired private BookShopService bookShopService; @Transactional @Override public void checkout(String username, List<String> isbns) { for(String isbn : isbns) { bookShopService.purchase(username, isbn); } } }
BookStockException
package com.springinaction.transaction; public class BookStockException extends RuntimeException { private static final long serialVersionUID = 1L; public BookStockException() { super(); // TODO Auto-generated constructor stub } public BookStockException(String arg0, Throwable arg1, boolean arg2, boolean arg3) { super(arg0, arg1, arg2, arg3); // TODO Auto-generated constructor stub } public BookStockException(String arg0, Throwable arg1) { super(arg0, arg1); // TODO Auto-generated constructor stub } public BookStockException(String arg0) { super(arg0); // TODO Auto-generated constructor stub } public BookStockException(Throwable arg0) { super(arg0); // TODO Auto-generated constructor stub } }
UserAccountException
package com.springinaction.transaction; public class UserAccountException extends RuntimeException { private static final long serialVersionUID = 1L; public UserAccountException() { super(); // TODO Auto-generated constructor stub } public UserAccountException(String arg0, Throwable arg1, boolean arg2, boolean arg3) { super(arg0, arg1, arg2, arg3); // TODO Auto-generated constructor stub } public UserAccountException(String arg0, Throwable arg1) { super(arg0, arg1); // TODO Auto-generated constructor stub } public UserAccountException(String arg0) { super(arg0); // TODO Auto-generated constructor stub } public UserAccountException(Throwable arg0) { super(arg0); // TODO Auto-generated constructor stub } }
测试类
package com.springinaction.transaction; import java.util.Arrays; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTransitionTest { private ApplicationContext ctx = null; private BookShopDao bookShopDao = null; private BookShopService bookShopService = null; private Cashier cashier = null; { ctx = new ClassPathXmlApplicationContext("config/transaction.xml"); bookShopDao = ctx.getBean(BookShopDao.class); bookShopService = ctx.getBean(BookShopService.class); cashier = ctx.getBean(Cashier.class); } @Test public void testBookShopDaoFindPriceByIsbn() { System.out.println(bookShopDao.findBookPriceByIsbn("1001")); } @Test public void testBookShopDaoUpdateBookStock(){ bookShopDao.updateBookStock("1001"); } @Test public void testBookShopDaoUpdateUserAccount(){ bookShopDao.updateUserAccount("AA", 100); } @Test public void testBookShopService(){ bookShopService.purchase("AA", "1001"); } @Test public void testTransactionPropagation(){ cashier.checkout("AA", Arrays.asList("1001", "1002")); } }
六、基于XML的声明事务示例
这里只给出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:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scan base-package="com.kang.spring"></context:component-scan> <!-- 导入资源文件 --> <context:property-placeholder location="classpath:db.properties"/> <!-- 配置 C3P0 数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property> </bean> <!-- 配置 Spirng 的 JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置 bean --> <bean id="bookShopDao" class="com.kang.spring.BookShopDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <bean id="bookShopService" class="com.kang.spring.BookShopServiceImpl"> <property name="bookShopDao" ref="bookShopDao"></property> </bean> <bean id="cashier" class="com.kang.spring.CashierImpl"> <property name="bookShopService" ref="bookShopService"></property> </bean> <!-- 1. 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 2. 配置事务属性 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- 根据方法名指定事务的属性 --> <tx:method name="purchase" propagation="REQUIRES_NEW"/> <tx:method name="get*" read-only="true"/> <tx:method name="find*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- 3. 配置事务切入点, 以及把事务切入点和事务属性关联起来 --> <aop:config> <aop:pointcut expression="execution(* com.kang.*.*(..))" id="txPointCut"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config> </beans>