美文网首页
Spring事务的管理

Spring事务的管理

作者: CoderHong | 来源:发表于2017-12-03 09:29 被阅读16次

    事务的概念

    什么是事务
    事务逻辑上的一组操作。组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。

    事务特性

    原子性:强调事务的不可分割
    一致性:事务的执行前后数据完整性保持一致。
    隔离性:一个事务执行过程中,不应该受到其它事务的干扰
    持久性:事务一旦执行,数据就持久到数据库
    

    如果不考虑隔离性引发的安全性问题

    脏读:一个事务读到了另一个事物未提交的数据
    不可重复读:一个事务读到了另一个事务提交的update的数据导致多次查询结果不一致。
    虚度:一个事务读到了另个一个事务已经提交的insert的数据导致多次查询结果不一致。
    

    平台事务管理器

    JdbcDaoSupport学习

    如果想用Spring来管理事务需要用到Spring相关的类和API

    PlatformTransactionManager接口

    image.png

    如果用Spring管理事物 第一步就得配置 PlatformTransactionManager这个接口。平台事务管理器(真正的管理事务类)。该类有具体的实现类,根据不同的框架,需要选择不同的实现类

    TransactionDefinition
    事物的定义信息接口(事物的隔离级别、传播行为,超时,只读)

    • 事务的隔离级别,一般使用默认的
    事务隔离级别常量
    static  int ISOLATION_DEFAULT - 采用数据库的默认隔离级别
    
    • 事务的传播行为常量
    PROPAGATION_REQUIRED -- A中有事务,使用A中的事物,如果没有,B就会开启一个新的事物,将A包含进来。(保证AB在同一个事物) 默认值。
    

    事务管理的使用

    提示:这里使用Spring的JDBC模板方式操作数据库。连接池使用的c3p0。

    • 第一步 导入Jar包
    image.png
    • 第二步 搭建项目结构


      image.png
    • 配置JDBC的模板 跟连接池

    <!-- c3p0连接池 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_day03?useUnicode=true&amp;characterEncoding=utf8"></property>
            <property name="user" value="root"></property>
            <property name="password" value="123456"></property>
        </bean>
    <!-- 配置JDBC模板类 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            引用属性
            <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    
    • 配置业务层和持久层
    <!-- 配置业务层和持久层 -->
        <bean id="accountService" class="com.coderhong.demo1.AccountServiceIml">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
        
        <bean id="accountDao" class="com.coderhong.demo1.AccountDaoImpl">
            <property name="jdbcTemplate" ref="org.springframework.jdbc.core.JdbcTemplate"></property>
        </bean>
    

    Dao操作数据库,可以将JDBC模板注入到Dao中

    // 继承 JdbcDaoSupport
    public class AccountDaoImpl  implements AccountDao {
    
        private JdbcTemplate jdbcTemplate;
        
        public void setJdbcTemplate(JdbcTemplate template) {
                this.jdbcTemplate = jdbcTemplate;
        }
    
        /**
         * 扣钱
         */
        @Override
        public void outMoney(String out, double money) {
            this.getJdbcTemplate().update("update t_account set money = money - ? where name=?", money, out);
        }
        
        /**
         * 加钱
         */
        @Override
        public void inMoney(String in, double money) {
            this.getJdbcTemplate().update("update t_account set money = money + ? where name=?", money, in);
        }
    
    }
    
    • 修改配置文件中将JDBC模板注入到Dao中注入
    <bean id="accountDao" class="com.coderhong.demo1.AccountDaoImpl">
            <property name="jdbcTemplate" ref="org.springframework.jdbc.core.JdbcTemplate"></property>
        </bean>
    

    以上配置及代码完成了业务层注入了dao,dao注入的JDBC模板。可以开发没问题。但是有一个不好的地方,就是我们每一个模块的dao都需要内部写一个JDBC模板成员并提供set方法,并且在配置文件中,到注入模板。

    这个时候,Spring为我们提供了一个父类JdbcDaoSupport,到我们让我们的dao继承JdbcDaoSupport,就会报错。原因是JdbcDaoSupport这个父类已经有了这个jdbcTemplate属性并提供了set方法。

    JdbcDaoSupport

    dao继承了JdbcDaoSupport,需要注释掉JDBC模板属性.在到使用模板通过父类的getJdbcTemplate()获取JDBC模板。

    import org.springframework.jdbc.core.support.JdbcDaoSupport;
    
    // 继承 JdbcDaoSupport
    public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
        
        /* 继承父类省略 父类已经实现
            private JdbcTemplate jdbcTemplate;
        
            public void setJdbcTemplate(JdbcTemplate template) {
                this.jdbcTemplate = jdbcTemplate;
            }
        */
        
        /**
         * 扣钱
         */
        @Override
        public void outMoney(String out, double money) {
            this.getJdbcTemplate().update("update t_account set money = money - ? where name=?", money, out);
        }
        
        /**
         * 加钱
         */
        @Override
        public void inMoney(String in, double money) {
            this.getJdbcTemplate().update("update t_account set money = money + ? where name=?", money, in);
        }
    
    }
    

    这样就就解决了只要我们编写dao继承JdbcDaoSupport,配置文件注入JDBC模板就可以了。不用再Dao内部引入JDBC模板属性跟set方法。

    现在整体的流程如下:


    image.png

    在阅读JdbcDaoSupport源码发现下面代码:

    image.png

    那就是说我们的dao继承了JdbcDaoSupport可以不用注入Jdbc模板。如果内有,直接帮我们创建,并将连接池放入Jdbc模板。可见在配置文件中以后我们不需要配置jdbc模板,之间在到注入连接池。这些前提都是dao继承JdbcDaoSupport。

    修改配置配文件

    <!-- c3p0连接池 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_day03?useUnicode=true&amp;characterEncoding=utf8"></property>
            <property name="user" value="root"></property>
            <property name="password" value="123456"></property>
        </bean>
    
    <!-- 配置业务层和持久层 -->
        <bean id="accountService" class="com.coderhong.demo1.AccountServiceIml">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
    
    <bean id="accountDao" class="com.coderhong.demo1.AccountDaoImpl">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    

    现在的结构:


    image.png

    到这里就是最终方案:

    1. 编写的dao继承JdbcDaoSupport
    2. dao中配置文件注入连接池。不需要提供jdbc模板属性跟set方法,父类中已有。

    事务管理的引入

    如上面的案例,我们使用了JDBC模板来操作转账
    我们看下业务层代码

    public class AccountServiceIml implements AccountService {
        
        private AccountDao accountDao;
        
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
    
        @Override
        public void pay(final String out, final String in, final double monye) {
                    
            // 扣钱
            accountDao.outMoney(out, monye);
            //int a = 10 /0 ;
            // 加钱
            accountDao.inMoney(in, monye);
        }
    

    这里存在的问题:
    业务层的扣钱跟加钱是两个不同的事务,一旦中间出现异常数据就出现问题,不会回滚。
    这里就需要事物来管理了。

    Spring框架事务管理分类

    • 手动编写事务管理代码 (不推荐)
    • 声明方式事务管理(底层采用AOP技术)

    手动编写事务管理代码 (了解)
    不管使用哪种事务分类,都是使用了Spring事务管理接口PlatformTransactionManager来管理事务,所以需要使用到接口的实现类。

    提示:这里使用的是JDBC模板,需要使用的实现类是DataSourceTransactionManager

    第一步 配置事务管理器

    平台事务管理器需要连接池
    因为连接池中有连接,平台事务管理器需要拿到连接才可以管理连接。

    <!-- 配置平台管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 连接池中有 连接 -->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    

    如果使用手动编码的方式,Spring提供了一个类TransactionTemplate模板类。
    使用这个类操作事物管理。

    配置:

    <!-- 配置平台管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 连接池中有 连接 -->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        
        <!-- 手动编码 提供了模板类 使用该类管理事物比较简单版 -->
        <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
            <property name="transactionManager" ref="transactionManager"></property>
        </bean>
    

    现在整体的结构


    Snip20171203_57.png

    我们操作TransactionTemplate这个类,其实就是底层AOP技术操作事务管理器
    DataSourceTransactionManager。

    以上配置完成了,需要在Service中注入TransactionTemplate。使用TransactionTemplate管理事务。

    <!-- 配置业务层和持久层 -->
        <bean id="accountService" class="com.coderhong.demo1.AccountServiceIml">
            <property name="accountDao" ref="accountDao"></property>
            <property name="transactionTemplate" ref="transactionTemplate"></property>
        </bean>
    

    在Service使用模板类

    public class AccountServiceIml implements AccountService {
        
        // dao
        private AccountDao accountDao;
        
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
        
        // 事务模板
        private TransactionTemplate transactionTemplate;
    
        public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
            this.transactionTemplate = transactionTemplate;
        }
    
    
        @Override
        public void pay(final String out, final String in, final double monye) {
            
            // 事物的执行
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus arg0) {
                    
                    // 扣钱
                    accountDao.outMoney(out, monye);
                    // int a = 10 /0 ;
                    // 加钱
                    accountDao.inMoney(in, monye);
                }
            });
    
        }
    
    }
    

    声明方式事务管理(底层采用AOP技术)
    两种方式

    • 基于AspectJ的XML方式
    • 基于AspetJ的注解方式

    基于AspectJ的XML方式

    • 配置
    <!-- 配置连接池 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://localhost:3306/spring_day03?useUnicode=true&amp;characterEncoding=utf8"></property>
            <property name="username" value="root"></property>
            <property name="password" value="123456"></property>
        </bean>
    
    <!-- 配置平台管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 连接池中有 连接 -->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
    <!-- 声明式事物 (采用XML配置文件的方式) -->
    <!-- 先配置通知 -->
        <tx:advice id="MyAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <!-- 可以给方法设置方法属性(隔离级别 传播行为) -->
                <tx:method name="pay" propagation="REQUIRED"/>
                <!-- 可以添加多个方法 -->
            </tx:attributes>
        </tx:advice>
    
    !-- 配置AOP 
            如果是自己编写的aop 使用<aop:aspect></aop:aspect>这个切面
            如果使用Spring提供的切面 <aop:advisor advice-ref=""/>
            -->
        <aop:config>
            <!-- aop:advisor是spring框架提供的通知  -->
            <aop:advisor advice-ref="MyAdvice" pointcut="execution(public * com.coderhong.demo2.AccountServiceIml.pay(..))"/>
        </aop:config>
    
    <!-- 配置业务层和持久层 -->
        <bean id="accountService" class="com.coderhong.demo2.AccountServiceIml">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
        
        <bean id="accountDao" class="com.coderhong.demo2.AccountDaoImpl">
            <!-- <property name="jdbcTemplate" ref="org.springframework.jdbc.core.JdbcTemplate"></property> -->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    

    关系图:


    image.png

    然后配置完成业务层跟dao层的代码就很简单了。
    业务层

    public class AccountServiceIml implements AccountService {
        
        private AccountDao accountDao;
        
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
        
        @Override
        public void pay(final String out, final String in, final double monye) {
    
                // 扣钱
                accountDao.outMoney(out, monye);
                // int a = 10 /0 ;
                // 加钱
                accountDao.inMoney(in, monye);
        }
        
    }
    

    dao层

    // 继承 JdbcDaoSupport
    public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {  
        /**
         * 扣钱
         */
        @Override
        public void outMoney(String out, double money) {
            this.getJdbcTemplate().update("update t_account set money = money - ? where name=?", money, out);
        }
        
        /**
         * 加钱
         */
        @Override
        public void inMoney(String in, double money) {
            this.getJdbcTemplate().update("update t_account set money = money + ? where name=?", money, in);
        }
    
    }
    

    基于AspetJ的注解方式

    配置文件- 开启事务注解

    <!-- dbcp连接池 -->
         <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://localhost:3306/spring_day03?useUnicode=true&amp;characterEncoding=utf8"></property>
            <property name="username" value="root"></property>
            <property name="password" value="123456"></property>
        </bean> 
        
        <!-- 配置平台管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 连接池中有 连接 -->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        
        <!-- 开启事物的注解 -->
        <tx:annotation-driven transaction-manager="transactionManager"/>
        
        <!-- 配置业务层和持久层 -->
        <bean id="accountService" class="com.coderhong.demo3.AccountServiceIml">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
        
        <bean id="accountDao" class="com.coderhong.demo3.AccountDaoImpl">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
    

    在业务层类添加注解@Transactional代表给类的所有方法全部都有了事物
    业务层的代码

    @Transactional
    public class AccountServiceIml implements AccountService {
        
        private AccountDao accountDao;
        
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
        
        @Override
        public void pay(final String out, final String in, final double monye) {
    
                // 扣钱
                accountDao.outMoney(out, monye);
                // int a = 10 /0 ;
                // 加钱
                accountDao.inMoney(in, monye);
        }
        
    }
    

    dao层代码不变。

    相关文章

      网友评论

          本文标题:Spring事务的管理

          本文链接:https://www.haomeiwen.com/subject/mtgobxtx.html