Spring---AOP事务管理

       事务管理的本质:还是动态代理,将事务方法将给事务管理器代理。对于操作数据库的事务管理器,事务方法一出现异常就回滚,没有异常就正常执行。
       事务管理通俗的理解,可以理解为,Service编写业务逻辑代码,可能会涉及到多个表的增删改查等等,如果在业务方法里面某个Dao出错,那整个方法里面都进行数据库的回滚操作,数据库返回到执行该业务方法之前的状态。

一、数据库基本知识

1、隔离级别概念

       数据库具有隔离并发运行多个事务的能力,是它们不受影响,避免各种并发问题。一个事务和其他事务隔离的程度称作隔离级别。

2、隔离级别的分类

       读未提交:可以读到未提交事务的数据。READ-UNCOMMITTED
       读已提交:只能读到已提交事务的数据。READ-COMMITTED
       可重复读:保证一次事务中读到的数据是同一个。REPEATABLE-READ
       串行化:数据库变成单线程,一个事务执行完了才能轮到下个事务执行。SERIALIZABLE

       查看数据库当前会话的隔离级别的DOS指令:select @@数据库名字_isolation;
       修改当前会话的隔离级别的DOS指令:set SESSION TRANSACTION ISOLATION LEVEL 隔离级别;



二、java异常分类

Alt



三、注解配置事务管理细节

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):
AltAltAlt

① 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("结账成功");
	    }
	}

       运行结果:
Alt


② 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("结账成功");
	    }
	}

       运行结果:
Alt

③ 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");
	    }
	}

       运行结果:
Alt
AltAlt

       数据库中涉及到的表中数据都没有更改。测试结果表明默认情况下编译时异常不会执行回滚操作

④ 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("结账成功");
	    }
	}

       运行结果:
Alt
AltAlt
       数据库中只改了book_stock表中的BOOK_STOCK,而account表中的ACCOUNT_BALANCE没有改变。测试结果表明默认情况下运行时异常会执行回滚操作

⑤ isolation属性

       改变这次连接的隔离级别。
Alt

⑥ propagation属性

       指定事物的传播行为。
Alt

       REQUIRED:如果当前有事务在运行,则该事务方法在事物内部执行。如果当前没有事务运行,就启动新的事物,让事务方法在事物内部执行。
       REQUIRED_NEW:不管当前有没有事务在运行,都要启动新事物。如果当前有事务在运行,则会先把当前事务挂起。

       具体机制见下面LFY大神的图:
Alt


       外层事务方法代码:

	@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(测试双嵌套事务):
Alt
AltAlt
       测试结果表明成功将默认情况下编译时异常不会执行回滚操作,改为编译时异常也会执行回滚操作。




       内层事务代码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(测试双嵌套事务):
Alt
       测试结果表明成功将默认情况下运行时异常会执行回滚操作,改为运行时异常不会执行回滚操作。

AltAlt



四、总结

       可以看出,内层事务的一般属性是继承于外层事务的属性的。
       rollbackFor属性和noRollbackFor属性,一般都是要内层事务和外层事务配合着使用的。因为异常是由内部事务逐层往外抛的。

       个人测试感觉有这么个结论:如果内层事务不通过rollbackFor属性和noRollbackFor属性指定回滚的类型的话,外层是不知道内层事务抛出的异常到底是什么,也无法决定是否回滚。

       重点:假设有两层嵌套事务,一旦内部事务方法执行出了错误需要回滚的时候(产生异常顺序是由内层逐层往外层抛异常的),在外部事务下面的事务方法不会再执行,而且已经执行过的REQUIRED_NEW事务方法则不会收到影响。

发布了33 篇原创文章 · 获赞 5 · 访问量 2287

猜你喜欢

转载自blog.csdn.net/cj1561435010/article/details/103987872