Spring数据访问和事务

  • 1、模型
  • 2、解耦
  • 3、实现
    • 3.1 核心接口
    • 3.2 代码分析
      • 3.2.1 事务管理
      • 3.2.2 数据访问
  • 4、使用
    • 4.1 编程模式
    • 4.2 配置模式
      • 4.2.1 声明式配置方式
      • 4.2.2 注解式配置方式
  • 5、总结

1、模型

在一般的编程习惯中,Spring的数据访问和事务处理的层次结构归纳如下图所示:

图. 1

2、解耦

Spring事务作为一个独立的组件,其目的就是为了与数据访问组件进行分离,这也是Spring事务框架设计的原则。根据这一职责清晰的原则,Spring在设计时就对事务和数据访问进行了很好的职责划分,这个可以从spring-tx和spring-jdbc这两个包就可以看出来。

但是在实际的代码中,会遇到一个棘手的问题:事务和数据访问操作都需要同一个数据库连接资源,那么它们之间怎么传递呢?

这里涉及三个方面:一是线程安全,二是资源的唯一性,三是事务和数据访问的解耦。

图. 2

在图2中的1、2和3这三个地方都需要使用数据库连接,并且是同一个连接。Spring的做法是将该连接放在一个统一的地方,要使用该资源,都从这个地方获取,这样就解决了事务模块和数据访问模块之间的紧耦合。

解除耦合之后,对于不同的ORM技术,则需要提供不同的事务管理实现,如下图所示:

图. 3

3、实现

3.1 核心接口

Spring事务框架的核心接口是:TransactionDefinition,TransactionStatus和PlatformTransactionManager。

TransactionDefinition用于定义事务属性,包括事务的隔离级别,事务的传播行为,事务的超时时间和是否为只读事务。对于隔离级别和传播行为这里就不深入了,可以网上找到很多资料。事务的超时时间就是一个事务需要在规定的时间里完成。只读事务表示在一个事务里不允许进行写操作。

TransactionStatus表示整个事务处理过程中的事务状态,通过事务状态可以进行事务的相应操作。

PlatformTransactionManager是对整个事务行为的抽象,定义了一个完整事务过程中的相关操作。对于不同的ORM技术,需要有不同的实现。

3.2 代码分析

下面简单分析一下事务和数据访问之间数据库连接资源是如何传递的,以JdbcTemplate为例,代码如下:

3.2.1 事务管理

图. 4

代码1:事务管理器首先获取事务对象(具体步骤请看代码2),然后根据事务对象判断是否已经存在事务,如果已经存在事务,则根据传播特性做进一步处理,这里就不介绍了;如果不存在事务,则开启一个新事务,开启新事务的具体内容见代码3。

 1 //************************************** 代码1: AbstractPlatformTransactionManager.java ****************************
 2 public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
 3    Object transaction = doGetTransaction();                                                   //获取事务
 4    ......
 5    if (isExistingTransaction(transaction)) {                                                  //如果已经存在事务,则根据传播特性再进一步处理
 6       // Existing transaction found -> check propagation behavior to find out how to behave.
 7       return handleExistingTransaction(definition, transaction, debugEnabled);
 8    }
 9
10    // No existing transaction found -> check propagation behavior to find out how to proceed.
11    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
12       throw new IllegalTransactionStateException(
13             "No existing transaction found for transaction marked with propagation ‘mandatory‘");
14    }
15    else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||  //如果不存在事务,则开启一个新事务
16          definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
17       definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
18       SuspendedResourcesHolder suspendedResources = suspend(null);
19
20       try {
21          boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
22          DefaultTransactionStatus status = newTransactionStatus(
23                definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
24          doBegin(transaction, definition);                                                      //开启事务
25          prepareSynchronization(status, definition);
26          return status;
27       }
28       ......
29    }
30    ......
31 }

代码2:获取事务对象,事务对象中包含了一个数据库连接,这个数据库连接是从事务同步管理器中获取的。事务同步管理器管理了一个ThreadLocal变量,它用于存放当前线程使用的数据连接资源。

//***************************************** 代码2: DataSourceTransactionManager.java *****************************************
@Override
protected Object doGetTransaction() {
   DataSourceTransactionObject txObject = new DataSourceTransactionObject();
   txObject.setSavepointAllowed(isNestedTransactionAllowed());
   ConnectionHolder conHolder =
      (ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);  //通过事务同步管理器获取连接,从当前线程的ThreadLocal中获取
   txObject.setConnectionHolder(conHolder, false);
   return txObject;
}

代码3:开启事务,从事务对象中获取连接,如果连接不存在则从数据库连接池中获取新的连接,关闭自动提交,设定事务超时时间,最后将数据库连接存储在事务同步管理中,即绑定在当前线程上。

//***************************************** 代码3: DataSourceTransactionManager.java *****************************************
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
   DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
   Connection con = null;

   try {
      if (txObject.getConnectionHolder() == null ||                                  //如果事务对象里没有连接,则从数据源(连接池)中获取新的连接
            txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
         Connection newCon = this.dataSource.getConnection();
         if (logger.isDebugEnabled()) {
            logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
         }
         txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
      }

      txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
      con = txObject.getConnectionHolder().getConnection();

      Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
      txObject.setPreviousIsolationLevel(previousIsolationLevel);

      // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
      // so we don‘t want to do it unnecessarily (for example if we‘ve explicitly
      // configured the connection pool to set it already).
      if (con.getAutoCommit()) {
         txObject.setMustRestoreAutoCommit(true);
         if (logger.isDebugEnabled()) {
            logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
         }
         con.setAutoCommit(false);
      }
      txObject.getConnectionHolder().setTransactionActive(true);

      int timeout = determineTimeout(definition);
      if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
         txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
      }

      // Bind the session holder to the thread.
      if (txObject.isNewConnectionHolder()) {
         TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder()); //将连接绑定到ThreadLocal中
      }
   }

   catch (Throwable ex) {
      DataSourceUtils.releaseConnection(con, this.dataSource);
      throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
   }
}

3.2.2 数据访问

图. 5

代码4:数据访问,先获取数据库连接:a、从事务同步管理器中获取连接,如果当前数据访问在一个事务中,那么一定可以获得一个连接;b、如果当前数据方面没有在一个事务中,那么将从数据库连接池中获取新的连接。

//***************************************** 代码4: JdbcTemplate.java *****************************************
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
   Assert.notNull(action, "Callback object must not be null");

   Connection con = DataSourceUtils.getConnection(getDataSource());
   ......
}

//***************************************** DataSourceUtils.java *********************************************
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
   Assert.notNull(dataSource, "No DataSource specified");

   ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); //先从ThreadLocal中获取连接
   if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
      conHolder.requested();
      if (!conHolder.hasConnection()) {
         logger.debug("Fetching resumed JDBC Connection from DataSource");
         conHolder.setConnection(dataSource.getConnection());
      }
      return conHolder.getConnection();
   }
   // Else we either got no holder or an empty thread-bound holder here.

   logger.debug("Fetching JDBC Connection from DataSource");
   Connection con = dataSource.getConnection();                                                       //如果ThreadLocal中没有连接,则从数据源(连接池)中获取新的连接
   ......
}

4、使用

对于Spring事务的使用最原始的是使用编程模式直接操作事务,这样可以控制事务的所有行为;另一个是我们都熟悉的配置模式。

4.1 编程模式

编程模式使用示例,以JdbcTemplate为例:

首先需要进行适当的配置,从这些配置中间我们可以得到清晰的脉络,从数据源到ORM,再到事务。从另一个角度可以看到,ORM和事务管理器的配置都依赖了数据源,这也说明它们之间在数据库连接上存在不寻常的关系,而这一点已经在上文中进行了讲解。

1. 配置数据源(包括数据库驱动和连接池)

<bean id="meilvDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${driver}"/> <property name="jdbcUrl" value="${meilv_url}"/> <property name="user" value="${meilv_username}"/> <property name="password" value="${meilv_password}"/></bean>

2. 配置数据访问方式(ORM)

<!-- jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="meilvDataSource"/>
</bean>

3. 配置事务管理器

<!-- 数据库的事务管理器配置 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="meilvDataSource"/>
</bean>

4. 编码

然后就是我们自己编写代码了:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class TransactionThreadTest {

    private static final ExecutorService executor = Executors.newFixedThreadPool(1);

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Autowired
    private IUserDAO userDAO;

    @Test
    public void testTransactionThread() {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        //def.setReadOnly(true);
        Boolean flag = def.isReadOnly();
        TransactionStatus status = transactionManager.getTransaction(def);

        try {

            // 更新库存
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        syncDisneyPluStock();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            executor.execute(runnable);

            Thread.sleep(1000);

            UserDO userDO = new UserDO();
            userDO.setName("lanlan");
            userDAO.insert(userDO);

        } catch (Exception e) {
            transactionManager.rollback(status);
        }
        transactionManager.commit(status);
    }

    public void syncDisneyPluStock() throws Exception {
        UserDO userDO = new UserDO();
        userDO.setName("dd");
        userDAO.insert(userDO);
        //throw new Exception();
    }
}

对于编程式事务管理,缺点是事务管理代码和业务逻辑代码相互混杂,并且所有代码都要自己写,异常也都要自己处理,这些都是很繁琐的事情。针对这个情况,Spring提供了TransactionTemplate的编程方式,这种模板方法设计模式统一处理了事务的流程,用户只需要关注自己的特定操作就可以了,具体通过实现相关的回调接口来完成。

顺便提一句:这种模式跟JDBCTemplate的编程方式是一样的,两者虽然在不同模块内,但是设计模式确是相同的。

4.2 配置模式

Spring的事务配置有多种方式,最常见的有XML中的声明式配置方式,还有注解式的配置方式。

4.2.1 声明式配置方式

Spring的事务最终还是通过使用AOP来实现的。声明式配置方式中,通过<aop:pointcut>定义切点,通过<tx:advice>定义通知,通过<aop:advisor>将这两者联系起来形成切面。

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="meilvDataSource"/>
</bean>

<!-- 使用tx/aop来配置 -->
<aop:config>
    <!-- 通过aop定义事务增强切面 -->
    <aop:pointcut id="serviceMethod" expression="execution(* com.test.aop.*.*(..))" />
    <!-- 引用事务增强 -->
    <aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice" />
</aop:config>

<!--事务增强 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!-- 事务属性定义 -->
    <tx:attributes>
        <tx:method name="action" read-only="false" />
        <tx:method name="work" rollback-for="RuntimeException" />
    </tx:attributes>
</tx:advice>

4.2.2 注解式配置方式

对于注解式配置方式,只需要在有事务的方法上增加事务注解就会生效。

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="meilvDataSource"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>

在类或者方法上使用注解@Transactional就可以进行事务管理了。

5、总结

Spring的事务管理和数据访问模块职责相当清晰,认识这一设计原则对我们学习他们具有根本性的作用,本文试图理清两者之间的关系,而这所谓的“关系”就是数据库连接,这也是Spring事务和数据访问都需要依赖的基础。

从另一方面来看,Spring对这两者关系的处理很值得我们学习和借鉴,不管是多线程编程的线程安全性,还是模块之间的解耦。

时间: 2024-10-11 16:24:59

Spring数据访问和事务的相关文章

Spring数据访问之JdbcTemplate

Spring数据访问之JdbcTemplate 使用JdbcTemplate的基本操作步骤 1.引jar包 项目的基本架构 这里重点看实现类的内容 1 package cn.hmy.dao.impl; 2 3 4 import java.util.List; 5 6 import org.springframework.jdbc.core.support.JdbcDaoSupport; 7 8 import cn.hmy.beans.Emp; 9 import cn.hmy.dao.IEmpDa

Spring数据访问层Dao案例

本文描述的是通过spring框架实现数据持久化操作,具体内容如下: 1,POJO类定义: 1 import java.io.Serializable; 2 import java.util.Date; 3 4 public class User implements Serializable{ 5 private int userId; 6 7 private String userName; 8 9 private String password; 10 11 private int cred

Java面试--Spring技术要点--Spring数据访问

24  Spring对DAO的支持 Spring对数据访问对象(DAO)的支持旨在简化它和数据访问技术如JDBC,Hibernateor JDO 结合使用.这使我们可以方便切换持久层.编码时也不用担心会捕获每种技术特有的异常. 优化了的异常类型体系:细化了数据访问异常,丰富了异常类型.(都是 Unchecked Exception,这种异常不会变动,采用同一种异常,表示同一种现象,与使用的持久化技术无关) 使用模板回调模式,开发者不再写模式化代码,简化编程: 不变:资源的获取,资源的释放,异常转

数据访问——关于事务

事务——transaction ,它有一个典型的特点:ACID A——原子性:事务是一个整体,不能再拆分,要么都执行要么都不执行: C——一致性:事务执行之前和执行之后的数据要一致: I——隔离性:事务在执行过程中,所有与事务相关的对象都不能再执行其他的操作(相当于被隔离起来) D——持久性:事务在遇到某种特殊情况导致操作未执行完的时候,数据会回滚到最初状态 事务分为两类: 一,链接内事务:在链接打开后,使用事务控制多条语句的执行 1,创建事务对象 SqlTransaction trans=co

spring数据持久化

1.spring提供了一组数据访问框架,集成了多种数据访问技术.支持JDBC.ibatis.hibernate.jpa等持久化框架. 2.spring的数据访问哲学 spring的目标之一就是允许开发人员在开发应用程序时,能够遵循面向对象原则中的"针对接口编程".spring对数据访问的支持也不例外.DAO是数据访问对象(data access object)的缩写,DAO提供了数据读取和写入到数据库中的一种方式.他们以接口的方式发布功能,应用程序的其他部分可以通过接口来进行访问.如图

Spring的数据访问---------------事务管理

ThreadLocal ThreadLocal为每一个使用该变量的线程分配一个变量副本,所以每一个线程在改变自己的副本时,不会改变其他线程的副本信息.该类主要包含四个方法: public void set(Object obj) public Object get() public void remove() protected Object InitialValue() package thread; public class ThreadLocalTest { private static

在Spring中基于JDBC进行数据访问时如何控制超时

超时分类 超时根据作用域可做如下层级划分: Transaction Timeout > Statement Timeout > JDBC Driver Socket Timeout Transaction Timeout指一组SQL操作执行时应在设定的时间内完成(提交或回滚),否则将引发超时.它的值应大于 N(语句数) * Statement Timeout Statement Timeout指完成单条SQL语句执行的最大允许时间.它的值应小于JDBC Driver Socket Timeou

Spring 4 官方文档学习(十)数据访问之JDBC

1.Spring框架JDBC的介绍 Spring JDBC - who does what? 动作 Spring 你 定义连接参数   是 打开连接 是   指定SQL语句   是 声明参数,提供参数值   是 准备.执行语句 是   迭代结果(如果有) 是   操作每个迭代   是 处理任何异常 是   处理事务 是   关闭连接.语句.结果集 是   一句话,Spring框架负责所有的低级别细节操作. 1.1.选择JDBC数据库访问的路径 所有的路径都需要兼容JDBC 2.0的驱动,部分特性

Java Web学习系列——Maven Web项目中集成使用Spring、MyBatis实现对MySQL的数据访问

本篇内容还是建立在上一篇Java Web学习系列——Maven Web项目中集成使用Spring基础之上,对之前的Maven Web项目进行升级改造,实现对MySQL的数据访问. 添加依赖Jar包 这部分内容需要以下Jar包支持 mysql-connector:MySQL数据库连接驱动,架起服务端与数据库沟通的桥梁: MyBatis:一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架: log4j:Apache的开源项目,一个功能强大的日志组件,提供方便的日志记录: 修改后的pom.xm