Spring的事务管理

Spring的事务管理简化了传统的事务管理流程,提高了开发效率。但是首先先要了解Spring的数据库编程。

Spring的数据库编程

数据库编程是互联网编程的基础,Spring框架为开发者提供了JDBC模板模式,即jdbcTemplate,它可以简化许多代码,但在实际应用中jdbcTemplate使用并不常见,在大多数时候都采用Spring结合MyBatis进行开发。在这里,只讲述Spring的jdbcTemplate开发。

SpringJDBC的配置

本节Spring数据库编程主要使用的是SpringJDBC模块的core和DataSource包,core是JDBC的核心包,包括常用的JdbcTemplate类,DataSource是访问数据源的工具类包。如果要使用SpringJDBC操作数据库,需要进行配置,配置如下:

<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <!--Mysql驱动-->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <!--连接的url-->
    <property name="url" value="jdbc:mysql://localhost:3306/spring?characterEncoding=utf-8"/>
    <!--用户名密码的配置-->
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>
<!--配置JDBC模板-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

在上述示例中,配置JDBC模板需要将dataSource注入到jdbcTemplate,而在数据访问层(Dao)中需要使用jdbcTemplate时也需要将jdbcTemplate注入到对应的bean中。

@Repository("testDao")
public class TestDaoImpl implements TestDao {
    @Autowired //按照类型注入
    private JdbcTemplate jdbcTemplate;

JDBCTemplate的常用方法

public int update(String sql,Object args[]):该方法可以对数据表进行增加、修改、删除。使用args[]设置参数,函数返回的是更新的行数。示例如下:

String insertSQL="insert into user values(NULL,?,?)";
Onject param[] = {"chencheng","m"};
jdbcTemplate.update(insertSQL,param);

public List<T> query(String sql,RowMapper<T> rowmapper,Object args[]):该方法可以对数据表进行查询操作,rowMapper将结果集映射到用户自定义的类中(前提是类的属性名与字段名相同)。示例如下:

jdbcTemplate.query(sql,new BeanPropertyRowMapper<User>(User.class),param);

具体的实现步骤

  1. 创建并编辑配置文件applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!--指定需要扫描的包-->
    <context:component-scan base-package="com.ch5"/>
    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--Mysql驱动-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <!--连接的url-->
        <property name="url" value="jdbc:mysql://localhost:3306/spring?characterEncoding=utf-8"/>
        <!--用户名密码的配置-->
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    <!--配置JDBC模板-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>
  1. 创建映射数据库的实体类
package com.ch5;

public class User {
    private Integer id;
    private String name;
    private double money;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
  1. 创建数据库访问层TestDao和TestDaoImpl
package com.ch5.dao;
import com.ch5.User;
import java.util.List;
public interface TestDao {
    public int update(String sql,Object[] param);
    public List<User> query(String sql,Object[] param);
}
package com.ch5.dao.Impl;
import com.ch5.User;
import com.ch5.dao.TestDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository("testDao")
public class TestDaoImpl implements TestDao {
    @Autowired //按照类型注入
    private JdbcTemplate jdbcTemplate;
    @Override
    public int update(String sql, Object[] param) {
        return jdbcTemplate.update(sql,param);
    }
    @Override
    public List<User> query(String sql, Object[] param) {
        return jdbcTemplate.query(sql,new BeanPropertyRowMapper<User>(User.class),param);
    }
}
  1. 编写测试类JdbcTemplateTest
package com.ch5.Test;

import com.ch5.User;
import com.ch5.dao.TestDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class JdbcTemplateTest {
    public static void main(String[] args) {
        ApplicationContext appCo = new ClassPathXmlApplicationContext("appliationContext.xml");
        TestDao testDao=(TestDao)appCo.getBean("testDao");
        String insertSql="insert into account values(null,?,?)";
        Object param[] = {"chencheng",1050.0};
        testDao.update(insertSql,param);
        String selectSql="select * from account";
        List<User> list=testDao.query(selectSql,null);
        for (User user : list) {
            System.out.println(user);
        }
    }
}

编程式事务管理

在代码中显式的调用beginTransactioncommitrollback等与事务处理相关的方法,这就是编程式事务管理,当只有少数事务操作时,编程式事务管理才比较适合。

基于XML的AOP实现事务控制

  1. 编写事务管理的类
package com.itheima.utils;
/**
 * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
 */
public class TransactionManager {
    private ConnectionUtils connectionUtils;
    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }
    /**
     * 开启事务
     */
    public  void beginTransaction(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 提交事务
     */
    public  void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 回滚事务
     */
    public  void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 释放连接
     */
    public  void release(){
        try {
            connectionUtils.getThreadConnection().close();//还回连接池中
            connectionUtils.removeConnection();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
  1. 配置aop
<!-- 配置事务管理器-->
<bean id="txManager" class="com.itheima.utils.TransactionManager">
    <!-- 注入ConnectionUtils -->
   <property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<aop:config>
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..)"/>
        <aop:aspect id="txAdvice" ref="txManager">
        <!--配置前置事务,开启事务-->
            <aop:before method="beginTransaction" pointcut-ref="pt1"/>
        <!--配置后置事务,提交事务-->
            <aop:after-returning method="commit" pointcut-ref="pt1"/>
        <!--配置异常事务,回滚事务-->
            <aop:after-throwing method="rollback" pointcut-ref="pt1"/>
        <!--配置最终事务,释放连接-->
            <aop:after method="release" pointcut-ref="pt1"/>
        </aop:aspect>
    </aop:config>

基于底层API的编程式事务管理

基于底层API的编程式事务管理就是根据PlatformTransactionManagerTransactionDefinitionTeansactionStatus等几个核心接口,通过编程的方式进行事务管理,下面通过一个实例描述底层API的事务管理实现:

  1. 给数据源配置事务管理器
<!--配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
  1. 创建数据访问类
package com.ch5.dao.Impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

@Repository("codeTransaction")
public class CodeTransaction {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private DataSourceTransactionManager transactionManager;
    public String testTransaction(){
        //默认事务定义
        TransactionDefinition definition=new DefaultTransactionDefinition();
        //开启事务
        TransactionStatus transactionStatus = transactionManager.getTransaction(definition);
        String message="执行成功,没有回滚";
        try{
            String sql = "delete * from account";
            String insertSql = "insert into account values(?,?,?)";
            Object param[] = {"1","chenheng",2000};
            jdbcTemplate.update(sql);
            //id重复,因此发生错误。
            jdbcTemplate.update(insertSql,param);
            jdbcTemplate.update(insertSql,param);
            //提交事务
            transactionManager.commit(transactionStatus);
        }catch (Exception e){
            //出现异常,回滚
            transactionManager.rollback(transactionStatus);
            message="事务回滚";
            e.printStackTrace();
        }
        return message;
    }
}
  1. 定义测试类
package com.ch5.Test;

import com.ch5.dao.Impl.CodeTransaction;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TransactionMangagerTest {
    public static void main(String[] args) {
        ApplicationContext appCo=new ClassPathXmlApplicationContext("appliationContext.xml");
        CodeTransaction codeTransaction = (CodeTransaction)appCo.getBean("codeTransaction");
        String result = codeTransaction.testTransaction();
        System.out.println(result);
    }
}

基于TransactionTemplate的编程式事务管理

事务处理的代码散落在业务逻辑代码中,破坏了原有代码的条理性,并且每一个事务都会有类似的启动事务,提交以及回滚事务的代码。

TransactionTemplateexcute方法有一个TransactionCallback接口类型的参数,该接口定义了一个DoInTransaction的方法,通常以匿名内部类的方式实现TransactionCallback接口,并在其doInTransaction方法中写业务逻辑代码。在这里可以使用默认的事务提交和回滚规则,在业务代码中不需要显式调用任何事务处理的API,doInTransaction方法有一个TransactionStatus类型的参数,可以在方法的任何位置调用该参数的setRollbackOnly方法将事务标识为回滚,以执行事务回滚。

根据默认规则,如果在执行回调方法的过程中抛出未检查异常,或者显式调用了setRollbackOnly方法,则回滚事务;如果事务执行完成或者抛出了checked类型的异常,则提交事务。

基于TransactionTemplate的编程式事务管理的步骤如下:

  1. 为事务管理添加事务模板:在基于底层的API开发的applicationContext.xml配置文件上使用springframwork提供的org,springframework,transaction.support.TransactionTemplate类为事务管理器添加事务模板。完整的配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!--指定需要扫描的包-->
    <context:component-scan base-package="com.ch5"/>
    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--Mysql驱动-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <!--连接的url-->
        <property name="url" value="jdbc:mysql://localhost:3306/spring?characterEncoding=utf-8"/>
        <!--用户名密码的配置-->
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    <!--配置事务管理器-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--为事务管理器txManager创建transactionTemplate-->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="txManager"/>
    </bean>
    <!--配置JDBC模板-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>
  1. 创建数据访问类TransactionTemplateDao
package com.ch5;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
@Repository("transactionTemplateDao")
public class TransactionTemplateDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private TransactionTemplate transactionTemplate;
    String message = "";
    public String TransactionTemplateTest(){
        //以你命好内部类的方式实现TransactionCallback接口。使用默认的事务规则。
        transactionTemplate.execute(new TransactionCallback<Object>() {
            @Override
            public Object doInTransaction(TransactionStatus status) {
                String insertSql = "insert into account values(?,?,?)";
                Object param[] = {9,"chen",5000.0};
                try{
                    jdbcTemplate.update(insertSql,param);
                    jdbcTemplate.update(insertSql,param);
                    message="执行成功,未回滚";
                }catch (Exception e){
                    message="事务回滚";
                }
                return message;
            }
        });
        return message;
    }
}
  1. 创建测试类TransactionTemplateDaoTest
package com.ch5.Test;

import com.ch5.TransactionTemplateDao;
import com.ch5.dao.Impl.CodeTransaction;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TransactionTemplateDaoTest {
    public static void main(String[] args) {
        ApplicationContext appCo=new ClassPathXmlApplicationContext("appliationContext.xml");
        TransactionTemplateDao transactionTemplateDao = appCo.getBean("transactionTemplateDao", TransactionTemplateDao.class);
        String result = transactionTemplateDao.TransactionTemplateTest();
        System.out.println(result);
    }
}

声明式事务管理

Spring的声明式事务管理是通过AOP技术实现的事务管理,其本质是对方法前后拦截,然后再目标方法开始之前创建一个事务,在执行完成后提交或回滚事务。

与编程式事务管理相比较,声明式事务唯一不足的地方是最细粒度只能作用到方法级别,无法做到像编程式事务管理那样可以作用到代码块级别,但即便有这样的需要,可以通过变通方法进行解决。例如可以将要进行事务处理的代码块单独封装为方法。

Spring声明式事务管理可以通过两种方式实现,一是基于XML方式,二是基于@Transactional注解的方式

基于XML方式的声明式事务管理

基于XML方式的声明式事务管理是通过在配置文件中配置事务规则的相关声明来实现的。Spring提供了tx命名空间来配置事务管理,提供了<tx:advice>元素来配置事务的通知,在配置<tx:advice>时一般要指定id和transaction-manager属性,其中id是配置文件的唯一标识。transaction-manager指定了事务管理器。另外还需要配置<tx:attributes>子元素,该子元素可配置多个<tx:method>子元素决定执行事务的细节。

<tx:advice>元素配置了事务的增强处理后就可以通过编写AOP配置让Spring自动对目标对象生成代理,下面通过实例演示XML方式让Spring实现声明式事务管理。为了体现事务管理的流程,创建Dao、Service、Controller3层实现。

  1. 创建Dao接口和实现类
package statment.dao;

public interface TestDao {
    public int save(String sql,Object param[]);
    public int delete(String sql,Object param[]);
}
package statment.dao.Impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import statment.dao.TestDao;
@Repository("testDao")
public class TestDaoImpl implements TestDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public int save(String sql, Object[] param) {
        return jdbcTemplate.update(sql,param);
    }

    public int delete(String sql, Object[] param) {
        return jdbcTemplate.update(sql,param);
    }
}
  1. 创建Service接口和实现类
package statment.Service;

public interface TestService {
    public void test();
}
package statment.Service.Impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import statment.Service.TestService;
import statment.dao.TestDao;
@Service("testService")
public class TestServiceImpl implements TestService {
    @Autowired
    private TestDao testDao;

    public void test() {
        String deleteSql="delete from account";
        String saveSql="insert into account values(?,?,?)";
        Object param[] = {1,"shitji",5000};
        testDao.delete(deleteSql,null);
        testDao.save(saveSql,param);
    }
}
  1. 创建Controller类
package statment.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import statment.Service.TestService;
@Controller
public class StatementController {
    @Autowired
    private TestService testService;
    public void test(){
        testService.test();
    }
}
  1. 编写配置文件bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">
    <context:component-scan base-package="statment"/>
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <tx:advice id="myAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="txPonintCut" expression="execution(* statment.Service.*.*(..))"/>
        <aop:advisor advice-ref="myAdvice" pointcut-ref="txPonintCut"/>
    </aop:config>
</beans>
  1. 编写测试类
package statment.Test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import statment.controller.StatementController;
public class XMLTest {
    public static void main(String[] args) {
        ApplicationContext appCo=new ClassPathXmlApplicationContext("bean.xml");
        StatementController controller = appCo.getBean("statementController", StatementController.class);
        controller.test();
    }
}

基于注解的声明式事务管理

package statment.Service.Impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import statment.Service.TestService;
import statment.dao.TestDao;
@Service("testService")
@Transactional
public class TestServiceImpl implements TestService {
    @Autowired
    private TestDao testDao;

    public void test() {
        String deleteSql="delete from account";
        String saveSql="insert into account values(?,?,?)";
        Object param[] = {1,"shitji",5000};
        testDao.delete(deleteSql,null);
        testDao.save(saveSql,param);
    }
}

在事务处理中捕获异常

声明式事务处理的流程是:

  1. Spring根据配置完成事务定义,设置事务属性。
  2. 执行开发者的代码逻辑。
  3. 如果开发者的代码产生异常并且满足事务回滚的配置条件,则事务回滚,否则提交事务。
  4. 事务资源释放。

如果开发者在代码逻辑中加入try...catch语句,Spring不能在声明式事务处理中正常执行事务的回滚。原因是Spring只在发生未被捕获的RuntimeException时才会回滚事务。因此需要处理这种问题。

基于XML方式的声明式事务管理中捕获异常

在基于XML方式的声明式事务管理捕获异常,需要补充两个步骤:

  1. 修改声明事务的配置
<tx:method name="*" rollback-for="java.lang.Exception"/>
  1. 在catch语句中添加throw new RuntimeException();

基于注解方式的声明式事务管理中捕获异常

  1. 修改注解内容,添加rollbackFor属性
@Transactional(rollbackFor = {Exception.class})
  1. 在catch语句中添加throw new RuntimeException();
01-23 07:18