事务管理的本质:还是动态代理,将事务方法将给事务管理器代理。对于操作数据库的事务管理器,事务方法一出现异常就回滚,没有异常就正常执行。
事务管理通俗的理解,可以理解为,Service编写业务逻辑代码,可能会涉及到多个表的增删改查等等,如果在业务方法里面某个Dao出错,那整个方法里面都进行数据库的回滚操作,数据库返回到执行该业务方法之前的状态。
一、数据库基本知识
1、隔离级别概念
数据库具有隔离并发运行多个事务的能力,是它们不受影响,避免各种并发问题。一个事务和其他事务隔离的程度称作隔离级别。
2、隔离级别的分类
读未提交:可以读到未提交事务的数据。READ-UNCOMMITTED
读已提交:只能读到已提交事务的数据。READ-COMMITTED
可重复读:保证一次事务中读到的数据是同一个。REPEATABLE-READ
串行化:数据库变成单线程,一个事务执行完了才能轮到下个事务执行。SERIALIZABLE
查看数据库当前会话的隔离级别的DOS指令:select @@数据库名字_isolation;
修改当前会话的隔离级别的DOS指令:set SESSION TRANSACTION ISOLATION LEVEL 隔离级别;
二、java异常分类
三、注解配置事务管理细节
1、开启注解的事务管理步骤
① 开启注解注入事务管理。
② 在XML中配置事务管理器(就是切面类)。
③ 给事务方法加上注解。
在默认情况下,只有运行时异常会回滚,而编译时异常则不会回滚。
2、Transactional注解的属性
公共代码
transaction.xml配置:
<context:component-scan base-package="cj.transaction"></context:component-scan>
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean class="org.apache.commons.dbcp.BasicDataSource" id="dataSource">
<property name="username" value="${c.username}"></property>
<property name="password" value="${password}"></property>
<property name="url" value="${url}"></property>
<property name="driverClassName" value="${classname}"></property>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" value="#{dataSource}"></property>
</bean>
<!-- 开启注解的事务管理-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
前五个属性的公共测试代码:
public class TestDemo {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("transaction.xml");
BookService bookService = (BookService) applicationContext.getBean("bookService");
bookService.checkout("Sarsh", 1);
}
}
前五个属性的公共Dao代码:
@Repository
public class BookDao {
@Autowired
JdbcTemplate jdbcTemplate;
public int getPrice(int bookId) {
String sql = "select BOOK_PRICE from book where BOOK_ID=?";
return jdbcTemplate.queryForObject(sql, Integer.class, bookId);
}
public void updateStock(int bookId) {
String sql = "update book_stock set BOOK_STOCK=BOOK_STOCK-1 where BOOK_ID=?";
jdbcTemplate.update(sql, bookId);
}
public void updateBalance(String accountName, int bookPrice) {
String sql = "update account set ACCOUNT_BALANCE=ACCOUNT_BALANCE-? where ACCOUNT_NAME=?";
jdbcTemplate.update(sql, bookPrice, accountName);
}
}
初始的三张表(account、book和book_stock):
① timeout属性
这个属性以秒为单位,当事务方法执行时间超过这个指定时间的是就会报错。
事务方法代码(简单的测试单个事务):
@Service
public class BookService {
@Autowired
private BookDao bookDao;
@Transactional(timeout = 3)
public void checkout(String accountName, int bookId) {
bookDao.updateStock(bookId);
try {
Thread.sleep(3000); //以毫秒为单位
} catch (InterruptedException e) {
e.printStackTrace();
}
bookDao.updateBalance(accountName, bookDao.getPrice(bookId));
System.out.println("结账成功");
}
}
运行结果:
② readOnly属性
当这个属性为true的时候,表示事务方法只能读取数据而不能修改数据。
事务方法代码(简单的测试单个事务):
@Service
public class BookService {
@Autowired
private BookDao bookDao;
@Transactional(readOnly = true)
public void checkout(String accountName, int bookId) {
bookDao.updateStock(bookId); //修改了数据
bookDao.updateBalance(accountName, bookDao.getPrice(bookId)); //修改了数据
System.out.println("结账成功");
}
}
运行结果:
③ rollbackFor属性
Spring的事务管理默认不会对编译时异常(非运行时异常)进行回滚,但是可以自己设置编译时异常的回滚。
事务方法代码(简单的测试单个事务):
@Service
public class BookService {
@Autowired
private BookDao bookDao;
@Transactional(rollbackFor = FileNotFoundException.class)
public void checkout(String accountName, int bookId) throws FileNotFoundException {
bookDao.updateStock(bookId);
bookDao.updateBalance(accountName, bookDao.getPrice(bookId));
System.out.println("结账成功");
new FileInputStream("D:/xxx.xxx.xxx");
}
}
运行结果:
数据库中涉及到的表中数据都没有更改。测试结果表明默认情况下编译时异常不会执行回滚操作。
④ noRollbackFor属性
自己设置运行时除数为零的异常不回滚。
事务方法代码(简单的测试单个事务):
@Service
public class BookService {
@Autowired
private BookDao bookDao;
@Transactional(noRollbackFor = ArithmeticException.class)
public void checkout(String accountName, int bookId) {
bookDao.updateStock(bookId);
int x = 10 / 0;
bookDao.updateBalance(accountName, bookDao.getPrice(bookId));
System.out.println("结账成功");
}
}
运行结果:
数据库中只改了book_stock表中的BOOK_STOCK,而account表中的ACCOUNT_BALANCE没有改变。测试结果表明默认情况下运行时异常会执行回滚操作。
⑤ isolation属性
改变这次连接的隔离级别。
⑥ propagation属性
指定事物的传播行为。
REQUIRED:如果当前有事务在运行,则该事务方法在事物内部执行。如果当前没有事务运行,就启动新的事物,让事务方法在事物内部执行。
REQUIRED_NEW:不管当前有没有事务在运行,都要启动新事物。如果当前有事务在运行,则会先把当前事务挂起。
具体机制见下面LFY大神的图:
外层事务方法代码:
@Component
public class MutilTA {
@Autowired
private BookService bookService;
@Transactional(timeout = 3, rollbackFor = FileNotFoundException.class)
public void init() {
bookService.checkout1("Sarsh",1);
try {
bookService.checkout2("Wuyifan", 2);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
测试代码:
public class TestDemo2 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("transaction.xml");
MutilTA mutilTA = (MutilTA) applicationContext.getBean("mutilTA");
mutilTA.init();
}
}
内层事务代码1:
@Service
public class BookService {
@Autowired
private BookDao bookDao;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void checkout1(String accountName, int bookId) {
bookDao.updateStock(bookId);
bookDao.updateBalance(accountName, bookDao.getPrice(bookId));
System.out.println(accountName + "结账成功");
}
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = FileNotFoundException.class)
public void checkout2(String accountName, int bookId) throws FileNotFoundException {
bookDao.updateStock(bookId);
bookDao.updateBalance(accountName, bookDao.getPrice(bookId));
System.out.println(accountName + "结账成功");
new FileInputStream("D:/xxx.xxx.xxx");
}
}
运行结果1(测试双嵌套事务):
测试结果表明成功将默认情况下编译时异常不会执行回滚操作,改为编译时异常也会执行回滚操作。
内层事务代码2:
@Service
public class BookService {
@Autowired
private BookDao bookDao;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void checkout1(String accountName, int bookId) {
bookDao.updateStock(bookId);
bookDao.updateBalance(accountName, bookDao.getPrice(bookId));
System.out.println(accountName + "结账成功");
}
@Transactional(propagation = Propagation.REQUIRED, noRollbackFor = ArithmeticException.class)
public void checkout2(String accountName, int bookId) {
bookDao.updateStock(bookId);
int x = 10 / 0;
bookDao.updateBalance(accountName, bookDao.getPrice(bookId));
System.out.println(accountName + "结账成功");
}
}
运行结果2(测试双嵌套事务):
测试结果表明成功将默认情况下运行时异常会执行回滚操作,改为运行时异常不会执行回滚操作。
四、总结
可以看出,内层事务的一般属性是继承于外层事务的属性的。
rollbackFor属性和noRollbackFor属性,一般都是要内层事务和外层事务配合着使用的。因为异常是由内部事务逐层往外抛的。
个人测试感觉有这么个结论:如果内层事务不通过rollbackFor属性和noRollbackFor属性指定回滚的类型的话,外层是不知道内层事务抛出的异常到底是什么,也无法决定是否回滚。
重点:假设有两层嵌套事务,一旦内部事务方法执行出了错误需要回滚的时候(产生异常顺序是由内层逐层往外层抛异常的),在外部事务下面的事务方法不会再执行,而且已经执行过的REQUIRED_NEW事务方法则不会收到影响。