JdbcTemplate简介
为了使JDBC更加易于使用,Spring 在 JDBC API 上定义了一个抽象层,以此建立一个 JDBC 存取框架
作为 Spring JDBC 框架的核心,JDBC 模板的设计目的是为不同类型的 JDBC 操作提供模板方法。每个模板方法都能控制整个过程,并允许覆盖过程中的特定任务。通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取工作量降到最低。
在test Schema下创建一张名为 user 的表,表结构如下
#添加c3p0数据库连接池 jdbc.user=root jdbc.password=000 jdbc.jdbcUrl=jdbc:mysql:///test jdbc.driverClass=com.mysql.jdbc.Driver jdbc.initPoolSize=5 jdbc.maxPoolSize=10
<!-- 导入资源文件 --> <context:property-placeholder location="classpath:db.properties"/> <!-- 配置 c3p0 数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/> <property name="driverClass" value="${jdbc.driverClass}"/> <property name="initialPoolSize" value="${jdbc.initPoolSize}"/> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/> </bean> <!-- 配置 Spring 的 jdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 还可以配置 NamedParameterJdbcTemplate,该对象可以使用具名参数,其没有无参构造器,所以必须为其构造器指定参数 --> <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"> <constructor-arg ref="dataSource"></constructor-arg> </bean>
package com.bupt.springtest.jdbc; public class User { private int uid; private String name; private int age; public int getUid() { return uid; } public void setUid(int uid) { this.uid = uid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User [uid=" + uid + ", name=" + name + ", age=" + age + "]"; } }
package com.bupt.springtest.jdbc; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource; public class JDBCTest { ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml"); JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate"); //在jdbcTemplate中使用具名参数 NamedParameterJdbcTemplate namedParameterJdbcTemplate = ctx.getBean(NamedParameterJdbcTemplate.class); /* * 执行INSERT, UPDATE, DELETE */ public void testUpdate() throws SQLException { String sql = "UPDATE user SET name = ? WHERE uid = ?"; jdbcTemplate.update(sql, "jimmy", 6); } /* * 执行批量更新:批量的INSERT, UPDATE, DELETE * 最后一个参数是 Object[]的 List 类型:因为修改一条记录需要一个Object数组,多条即需要多个Object数组 */ public void testBatchUpdate() { String sql = "INSERT INTO user(uid, name, age) VALUES(?,?,?)"; List<Object[]> batchArgs = new ArrayList<>(); batchArgs.add(new Object[]{7, "A", 28}); batchArgs.add(new Object[]{8, "B", 20}); batchArgs.add(new Object[]{9, "C", 30}); jdbcTemplate.batchUpdate(sql, batchArgs); } /* * 从数据库获取一条记录,实际得到对应的一个对象 * 类跟表对应,对象跟记录对应 * 调用queryForObject(String sql, RowMapper<User> rowMapper, Object... args)方法 * 1. 其中的RowMapper指定如何映射结果集的行,常用实现类为 BeanPropertyRowMapper * 2. 使用SQL中列的别名完成列名和类的属性名完成映射 * 如:若表中定义的name为 user_name,而在User类中定义为name,则sql语句应写为 * "SELECT uid, user_name name, age FROM user WHERE uid = ?" * 3. 不支持级联属性,即表中关联元素不能查询到其他表中的数据 */ public void testQuery4Object() { String sql = "SELECT * FROM user WHERE uid = ?"; RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class); User user = jdbcTemplate.queryForObject(sql, rowMapper, 1); System.out.println(user); } /* * 查询实体类的集合 * 注意调用的不是 queryForList 方法 */ public void testQuery4List() { String sql = "SELECT uid, name, age FROM user WHERE uid > ?"; RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class); List<User> users = jdbcTemplate.query(sql, rowMapper, 5); System.out.println(users); } /* * 获取单个列的值或做统计查询 */ public void testQueryForObject() { String sql = "SELECT count(uid) FROM user"; // String sql = "SELECT name FROM user WHERE uid = ?"; // String name = jdbcTemplate.queryForObject(sql, String.class, 2); long count = jdbcTemplate.queryForObject(sql, Long.class); System.out.println(count); } /* * 在经典的JDBC用法中, SQL参数是用占位符 ? 表示,并且受到位置的限制。定位参数的问题在于,一旦参数的顺序发生变化,就必须改变参数绑定 * 在Spring JDBC 框架中,绑定 SQL 参数的另一种选择是使用具名参数(named parameter),它可以为参数起名字 * 具名参数:SQL按名字(以冒号开头)而不是按位置进行指定。具名参数更易于维护,也提升了可读性。具名参数由框架类在运行时用占位符取代 * 具名参数只在NamedParameterJdbcTemplate中得到支持 * 1. 好处:若有多个参数,则不用去对应位置,直接对应参数名,便于维护 * 2. 缺点:较为麻烦 */ public void testNamedParameterJdbcTemplate() { String sql = "INSERT INTO user VALUES(:id, :nm, :age)"; Map<String, Object> paramMap = new HashMap<>(); paramMap.put("id", 10); paramMap.put("nm", "elle"); paramMap.put("age", 30); namedParameterJdbcTemplate.update(sql, paramMap); } /* * 使用具名参数时,可以使用update(String sql, SqlParameterSource paramSource)方法进行更新操作 * 1. SQL语句中的参数和类的属性一致 * 2. 使用 SqlParameterSource的 BeanPropertySqlParameterSource 实现类作为参数 */ @Test public void testNamedParameterJdbcTemplate1() { String sql = "INSERT INTO user VALUES(:uid, :name, :age)"; User user = new User(); user.setUid(11); user.setName("lock"); user.setAge(28); SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user); namedParameterJdbcTemplate.update(sql, parameterSource); } }
Spring 的事务管理
作为企业级应用程序框架,Spring 在不同的事务管理 API 之上定义了一个抽象层。而应用程序开发人员不必了解底层的事务管理 API,就可以使用 Spring 的事务管理机制。
Spring 既支持编程式事务管理,也支持声明式事务管理。
编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在编程式管理事务时,必须在每个事物操作中包含额外的事务管理代码。
声明式事务管理:大多数情况下比编程式事务管理更好用。它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。事务管理作为一种横切关注点,可以通过 AOP 方法模块化。Spring 通过 Spring AOP 框架支持声明式事务。
Spring 从不同的事务管理 API 中抽象了一整套的事务机制。开发人员不必了解底层事务 API,就可以利用这些事务机制。有了这些事务机制,事务管理代码就能独立于特定的事务技术了。Spring 的核心事务管理抽象是 org.springframework.transaction.PlatformTransactionManager,它为事务管理封装了一组独立于技术的方法。无论使用 Spring 的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
通过代码来说明Spring 对事务的支持,也学习下 JdbcTemplate 正真在实际中如何使用,我们先在test数据库中新建三张表
account表 book表 book_stock表
<context:component-scan base-package="com.bupt.springtest.tx"></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 name="password" value="${jdbc.password}"/> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/> <property name="driverClass" value="${jdbc.driverClass}"/> </bean> <!-- 配置Spring的 jdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean>
package com.bupt.springtest.tx; public interface BookShopDao { //根据书号获取书的单价 public int findBookPriceByIsbn(String isbn); //更新书的库存,使书号对应的库存减1 public void updateBookStock(String isbn); //更新用户的账户余额:使 username 的 balance - price public void updateUserAccount(String username, int price); }
package com.bupt.springtest.tx; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository("bookShopDao") public class BookShopDaoImp 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 sql1 = "SELECT stock FROM book_stock WHERE isbn = ?"; int stock = jdbcTemplate.queryForObject(sql1, Integer.class, isbn); if(stock <= 0) { throw new RuntimeException("库存不足!"); } String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?"; jdbcTemplate.update(sql, isbn); } @Override public void updateUserAccount(String username, int price) { //验证余额是否足够,若不够,则抛出异常 String sql1 = "SELECT balance FROM account WHERE username = ?"; int balance = jdbcTemplate.queryForObject(sql1, Integer.class, username); if(balance < price) { throw new RuntimeException("余额不足!"); }
String sql = "UPDATE account SET balance = balance - ? WHERE username = ?"; jdbcTemplate.update(sql, price, username); } }
package com.bupt.springtest.tx; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
//对单个操作事务的测试 public class SpringTransactionTest { ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml"); BookShopDao bookShopDao = ctx.getBean(BookShopDao.class); @Test public void testBookShopDaoUpdateBookStock() { bookShopDao.updateBookStock("1001"); } public void testBookShopDaoFindPriceByIsbn() { System.out.println(bookShopDao.findBookPriceByIsbn("1001")); } public void testBookShopDaoUpdateUserAccount(){ bookShopDao.updateUserAccount("Tom", 100); } }
上面代码分别测试了单个操作事务,现在我们把上面的操作组成一个完整的事务过程,即买书 —> 更新书库存 —> 更新账户余额
package com.bupt.springtest.tx; public interface BookShopService { //买书事务 public void purchase(String username, String isbn); }
package com.bupt.springtest.tx; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service("bookShopService") public class BookShopServiceImp implements BookShopService { @Autowired private BookShopDao bookShopDao; @Override public void purchase(String username, String isbn) { //获取书的单价 int price = bookShopDao.findBookPriceByIsbn(isbn); //更新书的库存 bookShopDao.updateBookStock(isbn); //更新用户余额 bookShopDao.updateUserAccount(username, price); } }
package com.bupt.springtest.tx; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTransactionTest { ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml"); BookShopService bookShopService = ctx.getBean(BookShopService.class); @Test public void testBookShopService() {
//执行此代码前,先将account表中的balance改为60,即使用户余额不足以购买该书 bookShopService.purchase("Tom", "1001"); } }
执行测试之后我们可以看到,这个操作并不满足事务的要求,因为书的库存更新了但账户里的钱没有变,及违背了原子性。这种情况下,我们就需要 Spring 的事务管理来帮我们解决。
用事务通知声明式地管理事务
Spring 允许简单地使用 @Transactional 注解来标注事务方法。为了将 方法定义为支持事务处理,可以为方法添加 @Transactional 注解,根据 Spring AOP 基于代理机制,只能标注公有方法。可以在方法或者类级别上添加 @Transactional 注解,当把这个注解应用到类上时,这个类中的所有公有方法都会被定义为支持事务处理。
在 bean 配置时,只需要在配置文件中启用 <tx:annotation-driven> 元素,并为之指定事务管理器就可以了。如果事务处理器名称是 transactionManager,就可以在 <tx:annotation-driven> 元素中省略 transaction-manager 属性。这个元素会自动检测该名称的事务处理器。
默认情况下,如果被注解的数据库操作方法发生了 Unchecked Exception 或 Error,所有的数据库操作将回滚;如果发生的是 Checked Exception,默认情况下数据库操作还是会提交的。
我们也可以来改变默认的规则:
1. 让 Checked Exception 也回滚:在整个方法前加上 @Transactional(rollbackFor=Exception.class)
2. 让 Unchecked Exception 不回滚:@Transactional(noRollbackFor=RuntimeException.class)
3. 不需要事务管理(只查询)的方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)
注意:如果异常被 try-catch块捕获了,事物就不会回滚了,若想让事务回滚必须再往外抛异常,即在catch模块中继续往外抛 Unchecked Exception。
下面用代码来具体介绍用法
在ApplicationContext.xml文件中增加下面几行代码
<!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 启动注解事务 --> <tx:annotation-driven transaction-manager="transactionManager"/>
在 BookShopServiceImp 的 purchase() 方法上增加注解 @Transactional
package com.bupt.springtest.tx; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service("bookShopService") public class BookShopServiceImp implements BookShopService { @Autowired private BookShopDao bookShopDao; //添加事务注解 @Transactional @Override public void purchase(String username, String isbn) { //获取书的单价 int price = bookShopDao.findBookPriceByIsbn(isbn); //更新书的库存 bookShopDao.updateBookStock(isbn); //更新用户余额 bookShopDao.updateUserAccount(username, price); } }
package com.bupt.springtest.tx; import java.util.Arrays; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
//测试类 public class SpringTransactionTest { ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml"); BookShopService bookShopService = ctx.getBean(BookShopService.class); @Test public void testBookShopService() { bookShopService.purchase("Tom", "1001"); }
由结果可以看出,此时 purchase() 方法满足事务的要求。
除了使用上述声明事务的方法, Spring 还可以通过 tx Schema 中定义的 <tx:advice> 元素声明事务通知。
事务管理其实是一种横切关注点,声明了事务通知后,就需要把它与切入点关联起来。由于事务通知是在 <aop:config> 元素外部声明的,所以它无法直接与切入点产生关联。所以必须在 <aop:config> 元素中声明一个增强器通知与切入点关联起来。只有公有方法才能通过 Spring AOP 进行事务管理。
下面我们来看配置方法,将前面配置在xml文件中的 <tx:annotation-driven transaction-manager="transactionManager"/> 删掉,增加如下配置代码
<!-- 声明事务通知,让所有方法都拥有事务 --> <tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*"/>
<tx:method name="get*" read-only="true"/> </tx:attributes> </tx:advice> <!-- 声明事务通知需要通知方法(即需要进行管理的方法) --> <aop:config>
<!-- 配置事务切点,把事务切入点和事务属性关联起来 --> <aop:pointcut id="bookShopOperation" expression="execution(* com.bupt.springtest.tx.BookShopServiceImp.*(..))"/> <aop:advisor advice-ref="bookShopTxAdvice" pointcut-ref="bookShopOperation"/> </aop:config>
删除 BookShopServiceImp.purchase() 方法上标注的 @Transactional 注解,测试后也能正常执行事务。
关于 <tx:method/> 属性的说明
属性 | 说明 |
name | 方法名的匹配模式,通知根据该模式寻找匹配的方法。该属性可以使用通配符(*)。也可以声明为 (xxx*),即代表以xxx开头的所有方法,表示符合此命名规则的方法作为一个事务(此属性是必须要有的) |
propagation | 设定事务定义所用的传播级别(默认值为REQUIRED) |
isolation | 设定事务的隔离级别(默认值为DEFAULT) |
timeout | 指定事务的超时(单位为秒),默认值为-1 |
read-only | 指定事务是否只读,若为true指示事务是只读的(一般来讲,对于只执行查询的事务你会将该属性设为true,如果出现了更新、插入或是删除语句时只读事务就会失败),默认值为 false |
no-rollback-for | 以逗号分隔的异常类的列表,目标方法可以抛出这些异常而不会导致通知执行回滚,如:NullPointException,ArithmeticException... |
rollback-for | 以逗号分隔的异常类列表,当目标方法抛出这些异常时会导致通知执行回滚。默认情况下,该列表为空,因此不在 no-rollback-for列表中的任何运行时异常都会导致回滚 |
Spring 中事务的传播特性
当一个事务被另外一个事务调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事物,并在自己的事务中运行
事务的传播行为可以由传播属性来指定,Spring 定义了七种类型的传播行为
传播属性 | 描述 |
REQUIRED | 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行 |
REQUIRES_NEW | 当前的方法必须启动新的事务,并在它自己的事务内运行。如果有事务正在运行,应该将它挂起来 |
SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中 |
NOT_SUPPORTED | 当前方法不应该运行在事务中,如果有运行的事务,将它挂起来 |
MANDATORY | 当前方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常 |
NEVER | 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常 |
NESTED | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行。否则,就启动一个新的事务,并在它自己的事务内运行 |
下面通过代码来介绍传播特性的用法
配置文件改回 <tx:annotation-driven transaction-manager="transactionManager"/>
package com.bupt.springtest.tx; import java.util.List; //新定义一个接口,表示用户结算操作 public interface Cashier { public void checkout(String name, List<String> isbn); }
package com.bupt.springtest.tx; 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 CashierImp implements Cashier { @Autowired private BookShopService bookShopService; //定义一个事物为 checkout @Transactional @Override public void checkout(String username, List<String> isbns) { for(String isbn : isbns) { bookShopService.purchase(username, isbn); } } }
为 BookShopServiceImp.purchase 方法上的 @Transactional 注解添加属性:propagation=Propagation.REQUIRED
@Service("bookShopService") public class BookShopServiceImp implements BookShopService { @Autowired private BookShopDao bookShopDao; //使用propagation指定事务的传播行为,即当前事务方法被另外一个事物方法调用时 //如何进行事务,默认取值为REQUIRED //添加事务注解 @Transactional(propagation=Propagation.REQUIRED) @Override public void purchase(String username, String isbn) { //获取书的单价 int price = bookShopDao.findBookPriceByIsbn(isbn); //更新书的库存 bookShopDao.updateBookStock(isbn); //更新用户余额 bookShopDao.updateUserAccount(username, price); } }
//测试代码 public class SpringTransactionTest { ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml"); Cashier cashier = ctx.getBean(Cashier.class); @Test public void testTransactionPropogation() { cashier.checkout("Tom", Arrays.asList("1001", "1002")); } }
当 bookService 的 purchase() 方法被另外一个事务方法 checkout() 调用时,它默认会在现有事务内运行。这个默认的传播行为就是 REQUIRED。因此从 checkout() 方法的开始到结束这个事务过程中,只有一个事务在执行。这个事务只在 checkout() 方法结束的时候被提交,若此时账户余额不够支付两本书的价格,就导致用户一本书都买不了。这个流程可以用图表示为:
另一种常见的传播行为是 REQUIRES_NEW ,它表示该方法必须要启动一个新事物,并在自己的事务内运行。如果有事务在运行,就应该先把它挂起。相应的代码修改,只需修改 @Transactional 的属性
@Transactional(propagation=Propagation.REQUIRES_NEW) @Override public void purchase(String username, String isbn){
此时的事务流程可以表示为如下图,此时若账户金钱能买一本但不够两本时,会执行能购买的行为,而对不能购买的行为不执行
同样在xml配置文件中,我们也可以来设置传播属性
<tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="purchase"
propagation="REQUIRES_NEW"/>
</tx:attributes>
</tx:advice>
事务的隔离级别
Spring 支持的事务隔离别如下表所示
隔离级别 | 描述 |
DEFAULT | 使用地城数据库的默认隔离级别,对大多数数据库来说,默认的隔离级别都是 READ_COMMITED |
READ_UNCOMMITTED | 允许事务读取未被其他事务提交的变更。脏读、不可重复读和幻读的问题都会出现 |
READ_COMMITTED | 只允许事务读取已经被其他事务提交的变更。可以避免脏读,但不可重复读和幻读问题仍然可能出现 |
REPEATABLE_READ | 确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新。可以避免脏读和不可重复读,但幻读的问题仍然存在 |
SERIALIZABLE | 确保事务可以冲一个表中读取相同的行。在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。所有并发问题都可以避免,但性能低下 |
Oracle 支持 2 种事务隔离级别:READ_COMMITED,SERIALIZABLE
MySql 支持 4 种
用法如下代码所示
@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED) public void purchase(String username, String isbn){
<tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="purchase" propagation="REQUIRES_NEW" isolation="READ_COMMITTED""/> </tx:attributes> </tx:advice>
设置回滚事务属性
默认情况下只有未检查异常( RuntimeException 和 Error 类型的异常)会导致事务回滚。而受检查异常则不会。(通常情况下我们都是取默认值即可)
事务的回滚规则可以通过 @Transactional 注解的 rollbackFor 和 norollbackFor 属性来定义。这两个属性被声明为 Class[] 类型的,因此可以为这两个属性指定多个异常类,也可以使用属性 rollbackFor=ClassName 和 norollbackFor=ClassName 通过异常类的名字直接指定。
@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED, rollbackFor={IOException.class, SQLException.class}, noRollbackFor=ArithmeticException.class) public void purchase(String username, String isbn){
我们还可以在 <tx:method/> 元素中指定回滚规则,此时属性值要填类全名
<tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="purchase" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.io.IOException, java.sql.SQLExcpetion" no-rollback-for="java.lang.ArithmeticExcpeion"/> </tx:attributes> </tx:advice>
超时和只读属性
超时事务属性:表示事务在强制回滚之前可以保持多久,这样可以防止长期运行的事务占用资源。由于事务可以在行和表上获得锁,因此长事务会占用资源,对整体性能产生影响。
只读事务属性:表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。
超时和只读属性可以在 @Transactional 注解中定义。超时属性以秒为单位计算。
@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED, rollbackFor={IOException.class, SQLException.class}, noRollbackFor=ArithmeticException.class, readOnly=true, timeout=30) public void purchase(String username, String isbn){
也可以在 <tx:method/>元素中进行指定
<tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="purchase" propagation="REQUIRES_NEW" isolation="READ_COMMITTED" rollback-for="java.io.IOException, java.sql.SQLExcpetion" no-rollback-for="java.lang.ArithmeticExcpeion" timeout="30" read-only="false"/> </tx:attributes> </tx:advice>