Spring事务的管理

2017-12-03  本文已影响16人  CoderHong

事务的概念

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

事务特性

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

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

脏读:一个事务读到了另一个事物未提交的数据
不可重复读:一个事务读到了另一个事务提交的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。

image.png
<!-- 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);
    }

}
<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框架事务管理分类

手动编写事务管理代码 (了解)
不管使用哪种事务分类,都是使用了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方式

<!-- 配置连接池 -->
<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层代码不变。

上一篇 下一篇

猜你喜欢

热点阅读