zoukankan      html  css  js  c++  java
  • Spring之声明式事务

    在讲声明式事务之前,先回顾一下基本的编程式事务

    编程式事务:
    //1.获取Connection对象
    Connection conn = JDBCUtils.getConnection();
    try {
    //2.开启事务:取消自动提交
    conn.setAutoCommit(false);
    //3.执行数据库操作
    chain.doFilter(req,resp);
    //4.提交事务
    conn.commit();
    }catch(Exception e) {
    //5.回滚事务
    conn.rollBack();

    }finally{
    //6.释放资源
    }

    然后讲一下Spring之声明式事务
    情景举例:

     小明有1000元,从自助书店买一本价格为100,库存为200本的《三国演义》。

    正常交易时,小明余额现在还有900元,书店库存还有199本。

    假如在小明买书的过程中,交易出现异常,小明花了100元,机器出现故障没有给出书,即书店这本书的库存没有减少。

    这显然是小明亏了,交易不合理。此时,这种交易应该马上“ 取消  ”,即交易不能通过。

    放在程序中表示就是:小明花钱和库存减少应该放在

    一个事务中,有任何一种情况没有正常执行,则整个程序不执行(不交给数据库执行操作)。

    通过程序实现:

    创建一个动态web工程
    1.加入jar包
    com.springsource.net.sf.cglib-2.2.0.jar
    com.springsource.org.aopalliance-1.0.0.jar
    com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
    commons-logging-1.1.3.jar
    spring-aop-4.0.0.RELEASE.jar
    spring-aspects-4.0.0.RELEASE.jar
    spring-beans-4.0.0.RELEASE.jar
    spring-context-4.0.0.RELEASE.jar
    spring-core-4.0.0.RELEASE.jar
    spring-expression-4.0.0.RELEASE.jar
    spring-jdbc-4.0.0.RELEASE.jar
    spring-orm-4.0.0.RELEASE.jar
    spring-tx-4.0.0.RELEASE.jar

    然后是mysql驱动包即C3P0的jar包:
    c3p0-0.9.1.2.jar
    mysql-connector-java-5.1.37-bin.jar
    2.创建一份jdbc.properties文件
    jdbc.user=root
    jdbc.passowrd=123456
    jdbc.url=jdbc:mysql://localhost:3306/tx
    jdbc.driver=com.mysql.jdbc.Driver

    3.在spring配置文件中配置数据源
    <!-- 引入外部属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- 配置数据源 -->
    <bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="user" value="${jdbc.user}"></property>
    <property name="password" value="${jdbc.passowrd}"></property>
    <property name="jdbcUrl" value="${jdbc.url}"></property>
    <property name="driverClass" value="${jdbc.driver}"></property>
    </bean>

    4.测试数据源:
    public class TestDataSource {
    private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    @Test
    public void test() throws SQLException {
    DataSource bean = ioc.getBean(DataSource.class);
    System.out.println(bean.getConnection());
    }

    }
    5.配置jdbcTemplate:
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="comboPooledDataSource"></property>
    </bean>

    6.创建Dao类
    <!-- 设置扫描的包 -->
    <context:component-scan base-package="com.neuedu"></context:component-scan>

    @Repository
    public class BookDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;


    public int findPriceByIsbn(String isbn){
    String sql = "SELECT price FROM book WHERE isbn = ?";
    Integer price = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
    return price;
    }
    }

    //[2]根据isbn的值减少书的库存,假设每次都只买1本书
    public void updateStockByIsbn(String isbn){
    String sql = "UPDATE book_stock SET stock = stock -1 WHERE isbn = ?";
    jdbcTemplate.update(sql, isbn);
    }
    //[3]根据用户名减少用户账户中的余额,减少的额度就是书的价格
    public void updateBalance(String userName,int price){
    String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
    jdbcTemplate.update(sql, price,userName);
    }
    7.创建Service层:
    @Service
    public class BookService {
    @Autowired
    private BookDao bookDao;

    public void doCash(String isbn,String username){
    int price = bookDao.findPriceByIsbn(isbn);
    bookDao.updateStockByIsbn(isbn);
    bookDao.updateBalance(username, price);
    }
    }
    8.注意哦:上面doCash方法中调用的三个方法应该在同一个事务中,要么同时成功,要么同时失败!
    先统一设置一下:
    然后先正常测试一下service中的doCash方法:
    public class TestDataSource {
    private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    private BookDao bean = ioc.getBean(BookDao.class);
    private BookService bookService = ioc.getBean(BookService.class);

    @Test
    public void test04() throws SQLException {
    bookService.doCash("ISBN-001","Tom");
    }
    }

    由于此时该方法没在事务中,如果doCash方法调用dao层的方法的时候,在中间位置出现了错误,此时就会造成一部分数据改掉了,而
    另一部分数据没有改掉,这就麻烦了,所以此时应该加入事务机制!


    9.如果想开启事务,就需要在spring的配置文件中配置事务管理器

    <!-- 配置事务管理器,并为事务管理器配置数据源!-->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="comboPooledDataSource"></property>
    </bean>
    <!-- 开启基于注解的声明式事务功能,需要设置transaction-manager属性-->
    <!-- 如果 事务管理器的id正好是transaction-manager的默认值transactionManager,则可以省略-->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

    然后在service层的doCash方法上加上:@Transactional注解这样就开启了事务!需要注意的是事务一般是加在service层的!


    整个业务逻辑的数据库操作:
    [1]根据isbn的值查询书的价格
    [2]根据isbn的值减少书的库存,假设每次都只买1本书
    [3]根据用户名减少用户账户中的余额,减少的额度就是书的价格


    总结:事务可以分为编程式事务和声明式事务

    声明式事务
    ①基本原理:AOP
    [1]前置通知:开启事务
    [2]返回通知:提交事务
    [3]异常通知:回滚事务
    [4]后置通知:释放资源
    ②事务管理器

    ③导入jar包
    [1]IOC容器需要的jar包
    [2]AOP需要的jar包
    [3]JdbcTemplate操作需要的jar包
    [5]MySQL驱动和C3P0

    ④配置
    [1]配置数据源
    [2]配置JdbcTemplate,并装配数据源
    [3]配置事务管理器,并装配数据源
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
    <property name= "dataSource" ref ="dataSource"/>
    </bean >
    [4]开启基于注解的声明式事务功能
    <tx:annotation-driven transaction-manager ="dataSourceTransactionManager"/>
    如果事务管理器的bean的id正好是transaction-manager的默认值transactionManager,则可以省略

    [5]在事务方法上加@Transactional注解



    4.事务属性的设置
    ①事务的传播行为
    ②事务的隔离级别
    ③事务根据什么异常不进行回滚
    ④事务的超时属性
    ⑤事务的只读属性


    ①事务的传播行为[参见第8章world文档]
    [1]解释:一个事务运行在另一个有事务的方法中,那么当前方法是开启新事务还是在原有的事务中运行。
    [2]设置事务方法在调用其他事务方法时,自己的事务如何传播给被调用的方法。[默认是required]
    [3]设置方式

    案例演示:
    在上面的dao中增加一个方法,用于演示事务!
    //为了测试事务的传播行为,需要增加一个数据库操作
    public void updatePrice(String isbn, int price){
    String sql = "UPDATE book SET price = ? WHERE isbn = ?";
    jdbcTemplate.update(sql, price,isbn);
    }

    同样,在service类中添加一个方法,如下所示:
    @Transactional
    public void updatePrice(String isbn, int price){
    bookDao.updatePrice(isbn, price);
    }
    此时当前service类中就有两个事务方法了,然后我们现在新创建一个service类,加入到IOC容器中,并将bookService注入,
    然后新建一个事务方法,如下所示:
    @Component
    public class MultiTX {
    @Autowired
    private BookService bookService;

    @Transactional
    public void multiTx(){
    bookService.doCash("ISBN-003","Tom");
    bookService.updatePrice("ISBN-005",888);
    }
    }

    为了演示该service方法中调用的另外两个方法是不是使用了当前service方法的事务,这里我们将该service方法调用的第二个方法
    弄出一个异常,然后看看该service方法调用的第一个service方法是不是回滚了!

    先统一设置一下表中的数据:
    UPDATE `account` SET balance = 10000;
    UPDATE book_stock SET stock = 1000;
    UPDATE book SET price = 1000;

    用测试类进行测试:先正常测试一遍,然后再整出个异常测试一下:

    public class TestDataSource {
    private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    private BookDao bean = ioc.getBean(BookDao.class);
    private BookService bookService = ioc.getBean(BookService.class);
    private MultiTX multiTx = ioc.getBean(MultiTX.class);
    @Test
    public void test04() throws SQLException {
    multiTx.multiTx();
    }
    }

    会发现@Transactional注解默认使用的传播属性就是required!
    此时可以将BookService类中的两个方法都加上propagation=Propagation.REQUIRES_NEW 属性,也就是如下所示:
    @Transactional(propagation=Propagation.REQUIRES_NEW)

    然后在执行刚才的有异常的测试方法!



    ②事务的隔离级别 :用于解决并发问题[在两次获取价格的方法中间打断点]
    isolation=Isolation. READ_COMMITTED

    先恢复数据表中的数据:
    UPDATE `account` SET balance = 10000;
    UPDATE book_stock SET stock = 1000;
    UPDATE book SET price = 1000;
    修改service类中的doCash方法为:【即两次获取价格!】
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void doCash(String isbn,String username){
    int price = bookDao.findPriceByIsbn(isbn);
    System.out.println("price1:"+price);
    bookDao.updateStockByIsbn(isbn);
    bookDao.updateBalance(username, price);

    price = bookDao.findPriceByIsbn(isbn);
    System.out.println("price2:"+price);
    }
    在测试方法中调用BookService类中的doCash方法,然后在doCash方法中两次获取价格的方法中间打断点,就会发现
    第一次是1000元,然后我们修改了数据库之后,读出来的还是1000元,这是因为数据库默认的隔离级别就是可重复读!

    如果我们在doCash方法的事务注解上加一个isolation属性,如下所示:
    @Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED)
    public void doCash(String isbn,String username){
    int price = bookDao.findPriceByIsbn(isbn);
    System.out.println("price1:"+price);
    bookDao.updateStockByIsbn(isbn);
    bookDao.updateBalance(username, price);

    price = bookDao.findPriceByIsbn(isbn);
    System.out.println("price2:"+price);
    }
    就会发现如果在两次获取价格之间打了断点,然后修改了值的话就会读出不一样的效果,说明事务的隔离级别设置就生效了!


    ③事务根据什么异常不进行回滚
    默认情况下,出现异常就回滚,noRollbackFor属性可以设置出现什么异常不进行回滚:
    noRollbackFor=ArithmeticException.class

    @Transactional(propagation=Propagation.REQUIRES_NEW,
    isolation=Isolation.READ_COMMITTED,
    noRollbackFor=ArithmeticException.class)
    public void doCash(String isbn,String username){
    int price = bookDao.findPriceByIsbn(isbn);
    System.out.println("price1:"+price);
    bookDao.updateStockByIsbn(isbn);
    bookDao.updateBalance(username, price);
    System.out.println(10/0);//出了异常信息
    }

    ④事务的超时属性【timeout=3】
    [1]数据库事务在执行过程中,会占用数据库资源,所以如果某一个事务执行的时间太长,
    那么就会导致资源被长时间占用,影响其他事务执行。[死循环,网络问题]

    [2]可以通过设置超时属性,将超时没有执行完的事务回滚,相当于对该操作进行撤销。
    @Transactional(propagation=Propagation.REQUIRES_NEW,
    isolation=Isolation.READ_COMMITTED,
    noRollbackFor=ArithmeticException.class,
    timeout=3)
    public void doCash(String isbn,String username){
    int price = bookDao.findPriceByIsbn(isbn);
    System.out.println("price1:"+price);
    bookDao.updateStockByIsbn(isbn);
    try {
    Thread.sleep(1000*5);
    } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    bookDao.updateBalance(username, price);
    }

    ⑤事务的只读属性【readOnly=true】
    数据库会对事务进行优化,如果是一个查询操作,那么数据库可以有针对性的进行优化。我们可以通过设置事务属性,
    告诉数据库当前操作是一个只读操作,便于数据库进行优化。


    5.基于XML的声明式事务
    <!-- 配置基于XML文件的声明式事务 -->
    <aop:config>
    <!-- 配置切入点表达式 -->
    <aop:pointcut expression="execution(* com.neuedu.tx.service.BookService.*(String, String))" id="txPointCut"/>
    <!-- 将事务切入点和事务建议的配置联系起来 -->
    <aop:advisor advice-ref="bookTransaction" pointcut-ref="txPointCut"/>
    </aop:config>
    <!-- 设置事务属性 -->
    <tx:advice id="bookTransaction" transaction-manager="dataSourceTransactionManager">
    <tx:attributes>
    <tx:method name="doCash"
    propagation="REQUIRED"
    isolation="READ_COMMITTED"
    read-only="false"
    no-rollback-for="java.lang.ArithmeticException"
    timeout="3" />
    <!-- 将某一类方法统一设置为只读 -->
    <tx:method name="get*" read-only="true"/>
    <tx:method name="find*" read-only="true"/>
    <tx:method name="query*" read-only="true"/>
    </tx:attributes>
    </tx:advice>



  • 相关阅读:
    设计模式的读书笔记
    腾讯历年笔试题
    原始套接字的简单tcp包嗅探
    一些有意思,有深度,容易错的代码收集
    sizeof的用法的一些归纳
    一道有趣的题目
    C++的一些设计注意点
    Gazebo Ros入门
    机器学习之多变量线性回归(Linear Regression with multiple variables)
    机器学习之单变量线性回归(Linear Regression with One Variable)
  • 原文地址:https://www.cnblogs.com/xuningchuanblogs/p/7459209.html
Copyright © 2011-2022 走看看