spring事务管理

Posted zhouyeqin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring事务管理相关的知识,希望对你有一定的参考价值。

本节要点:

  •    了解事务的概念
  •   了解jdbc事务管理
  •   掌握spring事务管理的实现方式
    • 编程式事务管理
    • 声明式事务管理
  •  了解事务的隔离级别和传播方式

事务的定义:

数据库系统为了保证数据操作的完整性和一致性,引入了事务这个重要的概念,所谓事务,就是将一系列的数据库操作作为一个整体来执行。如对数据库存取,就是一组SQL指令,这一组SQL指令必须全部执行成功;如果因为某个原因(例如其中一行SQL有错误),则先前所执行过的SQL指令撤销。

 

Jdbc事务管理

在JDBC中,可以用Connection的setAutoCommit()方法,给定它false参数。在一连串的SQL语句后面,调用Connection的commit()来送出变更。如果中间发生错误,则调用rollback()来撤销所有的执行,例如:

try{

    connection.setAutoCommit(false);

    …//一连串SQL操作

    connection.commit();    //执行成功,提交所有变更

}catch(SQLException e){

    connection.rollback();   //发生错误,撤销所有变更

}

案例:操作用户信息

在数据库新建一个表t_user 并包含id和name两个属性。

User

public class User {
     private int id;
     private String name;
    get/set……
}

 

UserDao

public interface UserDao {
     public String getUser(int id);
     public void updUser(int id);
     public void addUser(User user);
     public void delUser(int id);
}

 

UserDaoImpl

/**
 *    JDBC Connection类的事务控制方法:
      setAutoCommit(boolean autoCommit) 设置是否自动提交事务,默认自动
      commit() 提交事务
      rollback() 撤销事务  
 * @author Administrator
 *
 */
public class UserDaoImpl implements UserDao {

     public DataSource dataSource;// 数据源
     private Connection conn;// 数据库连接
     /**
      * 设置数据源并根据数据源获取数据库的连接
      */
     public void setDataSource (DataSource dataSource){
         this.dataSource = dataSource;
         try{
              this.conn = dataSource.getConnection();
         }catch(SQLException e){
              e.printStackTrace();
         }
     }

     @Override
     public String getUser(int id) {
         String name = null;
         try{
              PreparedStatement ps = conn.prepareStatement("select name from " + "t_user where id=?");
              ps.setInt(1, id);
              ResultSet rs = ps.executeQuery();
              if(rs.next()){
                   name = rs.getString(1);
              }
         }catch(SQLException e){
              e.printStackTrace();
         }
         return name;
     }

     @Override
     public void updUser(int id) {
         try{
              PreparedStatement ps = conn.prepareStatement("update t_user set name=? where id=? ");
              ps.setString(1, "25342");
              ps.setInt(2, id);
              ps.executeUpdate();
         }catch(SQLException e){
              e.printStackTrace();
         }
     }

     @Override
     public void addUser(User user) {
         try{
              PreparedStatement ps = conn.prepareStatement("insert into t_user(id,name) values("
                       +user.getId()+","+user.getName()+")");
              ps.executeUpdate();
         }catch(SQLException e){
              e.printStackTrace();
         }
     }

     @Override
     public void delUser(int id) {
         try{
              PreparedStatement ps = conn.prepareStatement("delete from t_user where id=? ");
              ps.setInt(1, id);
              ps.executeUpdate();
         }catch(SQLException e){
              e.printStackTrace();
         }
     }
}

 UserService

public interface UserService {
    public String getUser(int id);
    public void updUser(int id);
    public void addUser(User user);
    public void delUser(int id);
}

 

UserServiceImpl

public class UserServiceImpl implements UserService{

     private UserDao userDao;

     public UserDao getUserDao() {
         return userDao;
     }

     public void setUserDao(UserDao userDao) {
         this.userDao = userDao;
     }

     @Override
     public String getUser(int id) {
         return userDao.getUser(id);
     }
     @Override
     public void updUser(int id) {
         userDao.updUser(id);       
     }
     @Override
     public void addUser(User user) {
         userDao.addUser(user);     
     }
     @Override
     public void delUser(int id) {
         userDao.delUser(id);
     }
}

 Bean.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--数据源配置
    DriverManagerDataSource是spring提供的一个简单的数据源实现类
    这个类实现了javax.sql.DataSource接口,但是它没有提供池化连接的机制,
    每次调用getConnection()获取新连接时,只是简单地创建一个新的连接。
    因此,这个数据源简单应用或测试,因为它不需要额外的依赖类
     -->
     <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
     <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
         <property name="url" value="jdbc:oracle:thin:@localhost:1521:XE"></property>
     <property name="username" value="zhou"></property>
     <property name="password" value="123456"></property>
     </bean>
     <bean id="userService" class="com.silvan.service.UserServiceImpl">
     <property name="userDao" ref="userDao"></property>
     </bean>
     <bean id="userDao" class="com.silvan.dao.UserDaoImpl">
     <property name="dataSource" ref="dataSource"></property>
     </bean>
</beans>

 Test

public class Test {
     public static void main(String[] args) throws Exception{
         ApplicationContext applicationContext=new ClassPathXmlApplicationContext("beans.xml");
         UserService userService=(UserService)applicationContext.getBean("userService");
         User user = new User();
//       user.setId(1);
//       user.setName("1231");
//       userService.addUser(user);
         userService.delUser(1);
     }
}

 

UserDaoImpl中的新增方法修改成如下,体验事务的功能:如果事务过程中出现异常,捕获异常后对数据进行回滚,可以保证数据的完整性。

public void addUser(User user) {
         try{
              conn.setAutoCommit(false);
              PreparedStatement ps = conn.prepareStatement("insert into t_user(id,name) values(" +user.getId()+","+user.getName()+")");
              ps.executeUpdate();
              Integer.parseInt("asfdas");
              conn.commit();
         }catch(Exception e){
              e.printStackTrace();
              try {
                   conn.rollback();
              } catch (SQLException e1) {
                   e1.printStackTrace();
              }
         }
     }

 Spring事务概述

Spring框架提供了极其强大而简便的事务处理功能,其核心便是PlatformTransactionManager抽象接口。Spring将所有的事务管理都抽象为PlatformTransactionManager、TransactionStatus和TransactionDefinition这3个接口,而无论其底层关联的具体的事务究竟是JDBC事务、JTA事务,还是ORM框架自定义的事务。

  • PlatformTransactionManager:定义了事务管理器,所有与事务相关的操作都有它管理;
  • TransactionStatus:代表事务本身,它提供了简单的控制事务执行和查询事务状态的方法;
  • TransactionDefinition :定义了事务的规则(事务的隔离级别、传播行为、事务超时、只读状态), 在启动事务时,事务管理器会根据TransactionDefinition来启动合适的事务;

在Spring中实现事务管理有两种方式,一种是传统的编程式事务管理,也就是程序员在编写程序代码实现事务的管理,具体包括定义事务的开始、在程序异常时进行事务的回滚及程序正常执行后的事务提交。

另一种则是基于AOP技术实现的声明式事务管理,事务管理本身是一项共有的系统级服务功能,完全可以将事务管理抽象成一个事务切面,程序员不再关心事务管理的问题,把主要精力放在核心业务逻辑代码的编写上,然后在需要进行事务管理的方法上切入事务切面,使之具有事务管理的功能,达到事务管理的目的。

 

Spring编程式事务

PlatformTransactionManager:

  • 事务管理器接口, 只定义了3个方法:getTransaction()获取事务的状态; commit();rollback();
  • 事务管理器的实现类有多种,根据具体的持久层框架的不同而不同;
    • 实现类: DataSourceTransactionManager,HibernateTransactionManager,JdoTransactionManager等
  • 可以使用PlatformTransactionManager直接管理事务。简单地通过一个bean引用给你的bean传递一个你使用的 PlatformTransaction对象。然后,使用TransactionDefinition和TransactionStatus对象就可以发起、回滚、提交事务。
  •  流程一般如下:
    • 1 .声明数据源
    •   2 .声明一个事务管理类,例如 DataSourceTransactionManager, HibernateTransactionManger, JTATransactionManager等
    •   3 .在我们的代码中加入事务处理代码

userDaoImpl

package com.silvan.dao;

import java.util.List;
import java.util.Map;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import com.silvan.pojo.User;
/**
 *spring编程式事务
 * @author Administrator
 */
public class UserDaoImpl implements UserDao {
     public DataSourceTransactionManager transactionManager;// 事务管理器的实现类,作用如创建事务,管理事务等
     public JdbcTemplate jdbcTemplate;//spring中要使用Jdbctemplate对象来完成jdbc 操作

     public void setTransactionManager(
              DataSourceTransactionManager transactionManager) {
         this.transactionManager = transactionManager;
     }

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

     @Override
     public String getUser(int id) {
         String name = null;
         try{
              List<Map<String, Object>> result = jdbcTemplate.queryForList("select name from  t_user where id=?", id);
              if(result.iterator() != null){
                   name = (String) result.get(0).get("name");
              }
         }catch(Exception e){
              e.printStackTrace();
         }
         return name;
     }

     @Override
     public void updUser(int id) {
         try{
              jdbcTemplate.update("update t_user set name=? where id=? ","2544",id);
         }catch(Exception e){
              e.printStackTrace();
         }
     }
     @Override
     public void addUser(User user) {
         TransactionStatus ts=null;
         try{
             //定义事务规则
            TransactionDefinition td=new DefaultTransactionDefinition();
            //根据事务规则,创建一个新的事务或者获取之前已经创建的事务
            ts=transactionManager.getTransaction(td);
            jdbcTemplate.update("insert into t_user(id,name) values(?,?)",user.getId(),user.getName());
//            Integer.parseInt("asfdas");
              transactionManager.commit(ts);
         }catch(Exception e){
              e.printStackTrace();
              try {
                   transactionManager.rollback(ts);
              }catch (Exception e1) {
                   e1.printStackTrace();
              }
         }
     }
     @Override
     public void delUser(int id) {
         try{
              jdbcTemplate.update("delete from t_user where id=?",id);
         }catch(Exception e){
              e.printStackTrace();
         }
     }
}

 Beans

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
     <!-- 创建数据源对象 -->
     <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
     <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
    <property name="url" value="jdbc:oracle:thin:@localhost:1521:zhouyq"></property>
     <property name="username" value="zhou"></property>
     <property name="password" value="123456"></property>
     </bean>
     <!-- 创建事务管理对象,并注入数据源 -->
     <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
     <property name="dataSource" ref="dataSource"></property>
     </bean>
     <!-- 创建jdbc操作对象 -->
     <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
          <property name="dataSource" ref="dataSource"/>
      </bean>
     <bean id="userService" class="com.silvan.service.UserServiceImpl">
     <property name="userDao" ref="userDao"></property>
     </bean>
     <!-- 注入jdbc事务操作对象和事务管理对象 -->
     <bean id="userDao" class="com.silvan.dao.UserDaoImpl">
     <property name="jdbcTemplate" ref="jdbcTemplate"></property>
     <property name="transactionManager" ref="transactionManager"></property>
     </bean>
</beans>

 Spring声明式事务

Spring为声明式事务提供了简单而强大的支持,所谓声明式事务,是指在Spring的配置文件中使用相应的标签对事务进行配置,这样做的好处是Spring可以帮助我们管理事务,例如:什么时候提交事务、什么时候回滚事务等。

从开发效率与易维护的角度来看,Spring声明式事务管理是实际开发中比较常用的。

基于 <tx> 命名空间的声明式事务管理:

①   在xml中启用tx和aop两个命名空间

xmlns:tx=http://www.springframework.org/schema/tx

xsi:schemaLocation="http://www.springframework.org/schema/tx          http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"

②   在xml中配置通知、切入点以及Advisor

 

修改一下文件:

UserDaoImpl

package com.silvan.dao;

import java.util.List;
import java.util.Map;
import org.springframework.jdbc.core.JdbcTemplate;
import com.silvan.pojo.User;
/**
 *spring编程式事务
 * @author Administrator
 *
 */
public class UserDaoImpl implements UserDao {
     public JdbcTemplate jdbcTemplate;//spring中要使用Jdbctemplate对象来完成jdbc 操作
     public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
         this.jdbcTemplate = jdbcTemplate;
     }
     @Override
     public String getUser(int id) {
         String name = null;
         try{
              List<Map<String, Object>> result = jdbcTemplate.queryForList("select name from  t_user where id=?", id);
              if(result.iterator() != null){
                   name = (String) result.get(0).get("name");
              }
         }catch(Exception e){
              e.printStackTrace();
         }
         return name;
     }
     @Override
     public void updUser(int id) {
         try{
              jdbcTemplate.update("update t_user set name=? where id=? ","2544",id);
         }catch(Exception e){
              e.printStackTrace();
         }
     }
     @Override
     public void addUser(User user) {
         try{
              jdbcTemplate.update("insert into t_user(id,name) values(?,?)",user.getId(),user.getName());
              Integer.parseInt("asfdas");
         }catch(Exception e){
              e.printStackTrace();
              throw new RuntimeException();
         }
     }
     @Override
     public void delUser(int id) {
         try{
              jdbcTemplate.update("delete from t_user where id=?",id);
         }catch(Exception e){
              e.printStackTrace();
         }
     }
}

 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:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
     <!-- 创建数据源对象 -->
     <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
     <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
    <property name="url" value="jdbc:oracle:thin:@localhost:1521:zhouyq"></property>
     <property name="username" value="zhou"></property>
     <property name="password" value="123456"></property>
     </bean>
      <!--      配置JdbcTemplate,如果不用spring的jdbc可以省略  -->
      <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
          <property name="dataSource" ref="dataSource"/>
      </bean>
      <!--       创建事务管理对象 -->
     <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
     <property name="dataSource" ref="dataSource"></property>
     </bean>
     <tx:advice id="testAdvice" transaction-manager="transactionManager">
     <tx:attributes>
          <tx:method name="*" propagation="REQUIRED"/>
     </tx:attributes>
     </tx:advice>
     <!-- 配置切入点和advisor -->
     <aop:config>
     <aop:pointcut expression="execution(* com.silvan.service.*.*(..))" id="pointcut"/>
     <aop:advisor advice-ref="testAdvice" pointcut-ref="pointcut"/>
     </aop:config>
     <bean id="userService" class="com.silvan.service.UserServiceImpl">
     <property name="userDao" ref="userDao"></property>
     </bean>
     <bean id="userDao" class="com.silvan.dao.UserDaoImpl">
     <property name="jdbcTemplate" ref="jdbcTemplate"/>
     </bean>
</beans>

事务回滚:  

默认spring事务只在发生未被捕获的 runtimeexcetpion时才回滚。  

 spring aop  异常捕获原理:被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚,默认情况下aop只捕获runtimeexception的异常,但可以通过配置来捕获特定的异常并回滚,换句话说在service的方法中不使用try catch 或者在catch中最后加上throw new runtimeexcetpion(),这样程序异常时才能被aop捕获进而回滚

事务的传播方式:

事务的传播行为用于指定在多个事务方法间调用时,事务是如何在这些方法间传播的,spring共支持7种传播行为。

  • PROPAGATION_REQUIRED

     表示业务逻辑方法需要在一个事务中运行,如果该方法在运行时,已经处在一个事务中,则直接加入到该事务中,否则自己创建一个新的事务。即:如果存在一个事务,则支持当前事务。如果没有事务则开启。(在实际开发中常用该传播方式。)

  • PROPAGATION_SUPPORTS

      表示业务逻辑方法如果在某个事务范围内被调用,则该方法直接成为当前事务的一部分。如果该方法在事务范围外被调用,则该方法在无事务的环境下执行。即:如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。

  • PROPAGATION_MANDATORY

      表示业务逻辑方法只能在一个已经存在的事务中执行,该方法不能创建自己的事务,如果该方法在没有事务的环境下被调用,容器就会抛出事务不存在的异常。 即:如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

  • PROPAGATION_REQUIRES_NEW

      表示不管当前是否有事务存在,该业务逻辑方法都会为自己创建一个全新的事务。如果该方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到该方法执行结束后新事务才算结束,原先的事务再恢复执行。即: 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起,新事务运行完毕后,再接着运行被挂起的事务。

  • PROPAGATION_NOT_SUPPORTED

      表示业务逻辑方法不需要事务。如果该方法目前没有关联到某个事务,容器不会为它创建事务。如果该方法在一个事务中被调用,则该事务会被挂起,在方法调用结束后原先的事务才会恢复执行。即:总是非事务地执行,并挂起任何存在的事务,当前方法运行完毕后,被挂起的事务才恢复执行。

  • PROPAGATION_NEVER

      表示业务逻辑方法绝对不能在事务范围内执行。如果该方法在某个事务中执行,容器会抛出异常,只有没有关联到任何事务时该方法才能正常执行。即:总是非事务地执行,如果存在一个活动事务,则抛出异常

  • PROPAGATION_NESTED

      表示如果一个活动的事务存在,业务逻辑方法则运行在一个嵌套的事务中,如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点,内部事务的回滚不会对外部事务造成影响,它只对DataSourceTransactionManager事务管理器生效。即:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。

事务传播特性:

对基于方法级别的事务管理而言,方法开始执行时创建事务,方法运行过程中若出现异常则进行事务回滚,方法如果正常执行完成则进行事务的提交。因而事务管理的主要任务就是事务的创建、事务的回滚与事务的提交,其中是否需要创建事务及如何创建事务时由事务传播行为控制的,通常数据的读取时不需要事务管理的,或者也可为其指定只读事务,而对于插入、修改与删除数据的方法来说,就有必要进行事务管理了,在未指定事务传播行为时,Spring2.x将启用默认的REQUIRED。

Spring中事务隔离级别

  • ISOLATION_DEFAULT

    这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.

以下四个与JDBC的隔离级别相对应

  •  ISOLATION_READ_UNCOMMITTED

     这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读、不可重复读和幻像读。

  •  ISOLATION_READ_COMMITTED

     保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。

  • ISOLATION_REPEATABLE_READ

    这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生,即:不可重复读。

  •  ISOLATION_SERIALIZABLE

    这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读、不可重复读外,还避免了幻像读。但是并发性最差。

 

以上是关于spring事务管理的主要内容,如果未能解决你的问题,请参考以下文章

初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段

Spring boot:thymeleaf 没有正确渲染片段

What's the difference between @Component, @Repository & @Service annotations in Spring?(代码片段

spring练习,在Eclipse搭建的Spring开发环境中,使用set注入方式,实现对象的依赖关系,通过ClassPathXmlApplicationContext实体类获取Bean对象(代码片段

Spring Rest 文档。片段生成时 UTF-8 中间字节无效 [重复]

使用 Git 来管理 Xcode 中的代码片段