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

    1.编程式事务
        //1.获取Connection对象
        Connection conn = JDBCUtils.getConnection();
        try {
            //2.开启事务:取消自动提交
            conn.setAutoCommit(false);
            //updatePrice()数据库操作
              updateBalance()

      //提交事务
            conn.commit();
        }catch(Exception e) {
            //5.回滚事务
            conn.rollBack();

        }finally{
            //6.释放资源
        }
        
    事务:在执行多次sql语句时较使用,执行在service层
        
    2.情景举例
        ①导入SQL文件:declaration_transaction.sql
        ②创建一个动态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.创建数据库连接池 

      
    1、创建一份jdbc.properties文件
                jdbc.user=root
                jdbc.passowrd=123456
                jdbc.url=jdbc:mysql://localhost:3306/tx
                jdbc.driver=com.mysql.jdbc.Driver
                
          2.在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>
              
          3.测试数据源:
              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());
                }
    
            }
          4.配置jdbcTemplate:
                <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
                    <property name="dataSource" ref="comboPooledDataSource"></property>
                </bean>
    创建数据库连接池            

      3.创建测试用例

        Dao类:      

          
     <!-- 设置扫描的包 -->
                <context:component-scan base-package="com.neuedu"></context:component-scan>
                
                @Repository
                public class BookDao {
                    @Autowired
                    private JdbcTemplate  jdbcTemplate;
                    /**
                     *  [1]根据isbn的值查询书的价格
                        [2]根据isbn的值减少书的库存,假设每次都只买1本书
                        [3]根据用户名减少用户账户中的余额,减少的额度就是书的价格
                     */
                    
                    public int findPriceByIsbn(String isbn){
                        String sql = "SELECT price FROM book WHERE isbn = ?";
                        Integer price = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
                        return price;
                    }
                }
    
              测试上面的该方法:
                  public class TestDataSource {
                        private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
                        private BookDao bean = ioc.getBean(BookDao.class);
                        @Test
                        public void test01() throws SQLException {        
                            
                            int findPriceByIsbn = bean.findPriceByIsbn("ISBN-005");
                            System.out.println(findPriceByIsbn);
                        }
                  }
                
              然后在dao类中继续编写如下方法:
                    //[2]根据isbn的值减少书的库存,假设每次都只买1本书
                    public void updateStockByIsbn(String isbn){
                        String sql = "UPDATE book_stock SET stock = stock -1 WHERE isbn = ?";
                        jdbcTemplate.update(sql, isbn);
                    }
              继续测试:
                  public class TestDataSource {
                        private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
                        private BookDao bean = ioc.getBean(BookDao.class);
                        
                        @Test
                        public void test02() throws SQLException {        
                            bean.updateStockByIsbn("ISBN-004");
                        }
                  }
              继续编写Dao类中的方法:
                    //[3]根据用户名减少用户账户中的余额,减少的额度就是书的价格
                    public void updateBalance(String userName,int price){
                        String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
                        jdbcTemplate.update(sql, price,userName);
                    }
              继续测试:
                    @Test
                    public void test03() throws SQLException {        
                        bean.updateBalance("Tom", 1000);
                    }
    Dao类方法                         

             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);
                    }
                }
        注意:上面doCash方法中调用的三个方法应该在同一个事务中,要么同时成功,要么同时失败!
               先统一设置一下:
                UPDATE account SET balance = 10000;
                UPDATE book_stock SET stock = 1000;
                
            然后先正常测试一下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");
                    }
               }
              
    service层

            由于此时该方法没在事务中,如果doCash方法调用dao层的方法的时候,在中间位置出现了错误,此时就会造成一部分数据改掉了,而
             另一部分数据没有改掉,这就麻烦了,所以此时应该加入事务机制!        
           
          9.如果想开启事务,就需要在spring的配置文件中配置事务管理器      

    1         <!-- 配置事务管理器,并为事务管理器配置数据源!-->
    2             <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    3                 <property name="dataSource" ref="comboPooledDataSource"></property>
    4             </bean>
    5             <!-- 开启基于注解的声明式事务功能,需要设置transaction-manager属性-->
    6             <!-- 如果 事务管理器的id正好是transaction-manager的默认值transactionManager,则可以省略-->
    7             <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>                              
      然后在service层的doCash方法上加上:@Transactional注解这样就开启了事务!需要注意的是事务一般是加在service层的!

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

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

    2.声明式事务
        ①基本原理: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.事务属性的设置
        ①事务的传播行为
        ②事务的隔离级别
        ③事务根据什么异常不进行回滚
        ④事务的超时属性
        ⑤事务的只读属性

        ①事务的传播行为
            [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)
            
            然后在执行刚才的有异常的测试方法!
                   1、 Spring定义了7种类传播行为:

          

        2、关于传播属性的说明:      

          ①REQUIRED传播行为

            当bookServicepurchase()方法被另一个事务方法checkout()调用时,它默认会在现有的事务checkout()内运行。这个默认的传播行为就是REQUIRED。因此checkout()方法的开始和终止边界内只有一个事务。这个事务只在checkout()方法结束的时候被提交,结果用户一本书都买不了。

          

          

          ②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);
                }
            就会发现如果在两次获取价格之间打了断点,然后修改了值的话就会读出不一样的效果,说明事务的隔离级别设置就生效了!     

    隔离级别属性
    数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
    ①读未提交:READ_UNCOMMITTED
    允许Transaction01读取Transaction02未提交的修改。
    ②读已提交:READ_COMMITTED
        要求Transaction01只能读取Transaction02已提交的修改。
    ③可重复读:REPEATABLE_READ
        确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。
    ④串行化:SERIALIZABLE
        确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

           
        ③事务根据什么异常不进行回滚

         默认情况,捕获到RuntimeExceptionError时回滚,而捕获到编译时异常不回滚。

        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的声明式事务      

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


        

              
           

  • 相关阅读:
    【转】 JavaSE学习笔记(3)---面向对象三大特性
    【转】 JavaSE学习笔记(2)---面向对象基础
    【转】 JavaSE学习笔记(1)---数据类型、运算符、控制结构
    对象的循环
    对象的基本操作
    记录字符串出现的次数
    截取文件的扩展名
    字符串的截取 随机验证码
    字符串常用方法
    选择排序算法
  • 原文地址:https://www.cnblogs.com/kangxingyue-210/p/7488575.html
Copyright © 2011-2022 走看看