博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
声明式事务
阅读量:4881 次
发布时间:2019-06-11

本文共 12496 字,大约阅读时间需要 41 分钟。

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配置文件中配置数据源            
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:
创建数据库连接池            

  3.创建测试用例

    Dao类:      

      
@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
3
4
5
6
7
                             
然后在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      
2
3
4
5
6
7
8
9
10
11
17
18
19
20
21
22
23

 

    
          
       

转载于:https://www.cnblogs.com/kangxingyue-210/p/7488575.html

你可能感兴趣的文章
面试题巩固
查看>>
如何用.net制作一个简易爬虫抓取华为应用市场数据
查看>>
LeetCode:路径总和【112】
查看>>
国内首个HTML5应用开发平台AppCan近期将公测
查看>>
多线程循环打印ABC10次
查看>>
有些事情如果现在不做,以后也不会做了
查看>>
nginx 服务器 在 centos7 系统下的两种方式
查看>>
有一列数的规则如下 1、1、2、3、5、8、13、21、34... 求第30位数是多少.写出相关函数和算法名称...
查看>>
网页的结构
查看>>
[POJ1061] 青蛙的约会
查看>>
xcode6.2设置启动页
查看>>
神经网络 误差逆传播算法推导 BP算法
查看>>
POJ1125
查看>>
【codeforces】【比赛题解】#855 Codefest 17
查看>>
JAVA 设计模式之原型模式
查看>>
selenium 和robot framework自动化测试环境搭建
查看>>
123D
查看>>
你知道各调的特点吗?
查看>>
luogu P1908 逆序对
查看>>
linux用户和组管理,/etc/passwd 、/etc/shadow和/etc/group 文件内容解释
查看>>