Spring中JDBCTemplate的事务实现

    xiaoxiao2022-06-24  225

    一、事务的定义及特性

    (一)、事务的定义

    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()方法手动回滚(不推荐,会增加代码之间的耦合度)

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

     


    最新回复(0)