Spring中JDBCTemplate的事务实现

一、事务的定义及特性

(一)、事务的定义

1、事务是一个最小的不可再分割的执行单元,它由批量的DML语句构成,这些语句要么全部执行,要不全都不执行。

2、通常一个事务对应一个完整的业务。比如典型的转账业务,从账户A中转账1000元给账户B。操作过程分为两步,第一步从账户A中扣去1000元,第二步往账户B中加上1000元。显然这两个操作要么都得做,要么都不做。如果只做其中一个操作,数据库的数据就会出现问题。

3、注意:只有DML语句才有事务,DDL和DCL没有事务。

 

(二)、事务的四个特性(ACID)

1、原子性(Atomicity):原子性是指事务操作为最小单位,不可再分。要执行就全部执行,否则都不执行,不能出现执行其中一部分的情况。

2、一致性(Consistency):一致性是指一个事务操作结束后数据库必须仍处于一致性状态。所谓的一致性状态,简单理解就是数据库的数据总体是保持不变的。

比如转账过程中。事务执行前账户A有2000元,账户B有1000元。那么“账户A转账1000元给账户B”这个事务执行完成后,账户A和账户B的金钱总数仍要等于3000元。

3、隔离性(Isolation):隔离性是指并发执行的事务之间互不干扰,相互隔离。

4、持久性(Durability):持久性是指事务一旦被提交,它对数据库数据的改变就是永久性的。

 

(三)、事务的操作步骤

1、确保数据库支持事务功能

A、查看数据库支持的引擎

mysql语句:show ENGINES;

从查询结果我们会发现数据库支持多种引擎,但其中能提供事务功能的只有InnoDB。

 

B、查看待操作表的引擎

mysql语句:show create table 表名;

可以看到我的account表执行引擎已经是InnoDB了

 

C、如果不是InnoDB,则将其改为InnovationDB

mysql语句:ALTER TABLE 表名 ENGINE = INNODB;

 

2、具体步骤

A、开启事务

start transaction;

B、定义事务的DML语句

insert into account values(50,"test");

此时我们看下account表会发现表中仍然没有money=50,user=test这个记录的。说明这个DML语句还没有真正被执行。

C、提交 or 回滚

提交:commit

这时我们再查看下数据库,就会发现新纪录已经被添加到表中了。

回滚:rollback

(需要注意的是每次执行完commit或者rollback后,整个事务就结束了,因此这里我们需要重新开启一个事务)

我们看下account表,会发现DML语句确实没有被执行

 

二、Spring中JDBC开启事务支持

(一)、Spring的主要配置

定义一个数据源。根据自己的实际情况来配置,笔者这里用的是DBCP的数据源,用其他数据源也是可以的。

    <!--  定义一个使用DBCP实现的数据源 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close"
          p:driverClassName="com.mysql.jdbc.Driver"
          p:url="jdbc:mysql://localhost:3306/library?characterEncoding=UTF8"
          p:username="root"
          p:password="admin"/>
    <!-- 定义JDBC模板Bean   -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
          p:dataSource-ref="dataSource"/>

(二)、创建相应的DAO和Service类

1、AccountDAO类

package com.book.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class AccountDao {

    private JdbcTemplate jdbcTemplate;

    @Autowired
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void reduce() {
        jdbcTemplate.update("update account set money=money-10 where user='红红'");
    }
    public void add(){
//        int i = 2/0;
        jdbcTemplate.update("update account set money=money+10 where user='小明'"); }
}

2、AccountService类

package com.book.service;

import com.book.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class AccountService {
    private AccountDao accountDao;

    @Autowired
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void  doAccount() {
        accountDao.reduce();
//        int i=2/0;
        accountDao.add();
    }

}

3、测试代码

package com.book.web;

import com.book.service.LendService;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Test {

    public static void main(String[] args){
        //进行类加载,并实例化对象
        BeanFactory bf = new XmlBeanFactory(new ClassPathResource("book-context.xml"));
        //获取实例化的对象
        LendService bean = (LendService) bf.getBean("lendService");
        System.out.println("Done");
        try{
            bean.doAccount();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    
}

 

三、Spring使用事务的两种方式

(一)、基于AOP的注解方式

1、xml文件中添加开启事务的注解

    <!-- 第一步配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 第二步 开启事务注解 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

2、代码中添加注解

在需要申明为事务的方法前添加@Transactional(rollbackFor = { Exception.class })字段即可,Spring的事务回滚默认只处理RuntimeException,也就是运行时异常,对于一些程序员自己定义的异常并不会触发事务的回滚。rollbackFor = {Exception.class}则指明我们要处理所有类型的异常。如下图

3、运行测试代码

运行前的数据库状态

A、没有异常时运行结果如下:

B、手动构建异常(开启事务的状态)

运行结果如下:

用户“红红”钱没有减少,小明的钱也没有增加。说明事务的回滚生效了。add()操作的异常导致reduce()操作被回滚,没有执行成功。

C、手动构建异常(关闭事务的状态)

运行结果如下,我们会发现账号名为“红红”的用户money少了10,但是账号名为“小明”的用户money却没有变

 

(二)、基于AOP的xml配置方式

1、xml中添加注解

    <!-- 第一步 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入dataSource -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 第二步 配置事务增强 -->
    <tx:advice id="txadvice" transaction-manager="transactionManager">
        <!-- 做事务操作 -->
        <tx:attributes>
            <!-- 设置进行事务操作的方法匹配规则  以do开头的方法-->
            <tx:method name="do*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!-- 第三步 配置切面 -->
    <aop:config>
        <!-- 切入点 -->
        <aop:pointcut expression="execution(* com.book.service.AccountService.*(..))" id="pointcut"/>
        <!-- 切面 -->
        <aop:advisor advice-ref="txadvice" pointcut-ref="pointcut"/>
    </aop:config>

2、运行结果如下

当出现异常时两个用户的money都不变,说明事务回滚成功。

 

三、事务不起作用的常见原因

(一)、确保你所操作的表支持事务机制

最好自己先在mysql中写个简单的事务试试。

(二)、service层处理了异常

1、原因

service层类中使用try catch 去捕获异常后,由于该类的异常并没有抛出,就无法触发事务管理机制。因为在你将doAccount()方法申明为事务方法后,只有当doAccount()方法向上抛出异常时才会触发事务的回滚机制。如果你自己在doAcount方法中用try...catch处理完异常了,事务管理器就不知道出现了异常,自然不会执行回滚操作。

2、测试

如下,我们在AccountService类的doAccount()方法中增加try...catch处理机制进行测试。

我们查看下运行结果,发现事务失效了。

3、解决方案

A、将service层的try..catch提到web层中(web开发通常分为dao、service和web三层,不理解的直接将try...catch去掉即可)。

B、使用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()方法手动回滚(不推荐,会增加代码之间的耦合度)

运行结果如下,说明事务起作用了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章