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: AbstractPlatformTransactionManager.java ****************************
public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
Object transaction = doGetTransaction(); //获取事务
......
if (isExistingTransaction(transaction)) { //如果已经存在事务,则根据传播特性再进一步处理
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(definition, transaction, debugEnabled);
} // No existing transaction found -> check propagation behavior to find out how to proceed.
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || //如果不存在事务,则开启一个新事务
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null); try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition); //开启事务
prepareSynchronization(status, definition);
return status;
}
......
}
......
}
代码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对这两者关系的处理很值得我们学习和借鉴,不管是多线程编程的线程安全性,还是模块之间的解耦。
Spring数据访问和事务的更多相关文章
- Spring ( 五 )Spring之数据访问与事务管理
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.Spring之数据访问 1.Spring数据访问工程环境搭建 jdbc.properties配置 ...
- SpringBoot之数据访问和事务-专题三
SpringBoot之数据访问和事务-专题三 四.数据访问 4.1.springboot整合使用JdbcTemplate 4.1.1 pom文件引入 <parent> <groupI ...
- Solon Web 开发,五、数据访问、事务与缓存应用
Solon Web 开发 一.开始 二.开发知识准备 三.打包与运行 四.请求上下文 五.数据访问.事务与缓存应用 六.过滤器.处理.拦截器 七.视图模板与Mvc注解 八.校验.及定制与扩展 九.跨域 ...
- Spring数据访问之JdbcTemplate
Spring数据访问之JdbcTemplate 使用JdbcTemplate的基本操作步骤 1.引jar包
- Spring 4 官方文档学习(九)数据访问之事务管理
说明:未整理版,未完待续,请绕行 本部分的重点是数据访问以及数据访问层与业务层之间的交互. 1.Spring框架的事务管理 介绍 http://docs.spring.io/spring/docs/c ...
- 三、Spring——数据访问
1.Spring 对 DAO的支持 Spring支持目前大多数常用的数据持久化技术,Spring定义了一套面向DAO层的异常体系,并未各种支持的持久化技术提供了异常转换器.这样,我们在设计DAO接口时 ...
- Spring数据访问1 - 数据源配置及数据库连接池的概念
无论你要选择哪种数据访问方式,首先你都需要配置好数据源引用. Spring中配置数据源的几种方式 通过在JDBC驱动程序定义的数据源: 通过JNDI查找的数据源: 连接池的数据源: 对于即将发布到生产 ...
- Spring数据访问2 - 通过JDBC访问数据库
因为原生的jdbc操作太复杂,几乎都是建立连接.关闭连接和处理例外等模板式的代码,Spring对此进行了抽象——使用模板来消除样板式代码 ,JdbcTemplate承担了简化数据库访问这块的任务. 利 ...
- Spring 梳理-数据访问-DB
针对接口编程 DAO是指数据访问对象(data access object),它提供了数据读取和写入到数据库中的一种方式.Spring认为,它应该以接口的方式发布功能,而应用程序的其他部分需要通过接口 ...
随机推荐
- 封装locaostorage
const ls = localStorage export default { setItem(name, value) { ls.setItem(name, JSON.stringify(valu ...
- jQuery元素操作1
元素操作 1.2.1 高度和宽度 $(“div”).height(); // 高度 $(“div”).width(); // 宽度 .height()方法和.css(“height”)的区别: 1. ...
- 0068 Git入门的第一节课
这是 猴子都懂的Git入门 的学习笔记 Git安装与配置 下载安装Git:http://git-scm.com/ 从开始菜单启动Git Bash $ git --version git version ...
- error: no matching function for call to 'Ui::GoToCellDialog::setupUi(QDialog*&)' ui.setupUi(dialog); ^
环境:Qt5.3 参考书是:C++ GUI Qt4编程 问题描述: 按照书中的例子2-2做,编译时遇到的问题,从字面意思看是没有匹配的函数可用,UI::GotoCellDialog类是自动生成的,所以 ...
- 基于jquery的适合电子商务网站首页的图片滑块
今天给大家分享一款基于Sequence.js 的图片滑动效果,特别适合电子商务网站或者企业产品展示功能.带有图片缩率图,能够呈现全屏图片浏览效果.结合 CSS3 Transition 实现响应式的滑块 ...
- https 单向双向认证说明_数字证书, 数字签名, SSL(TLS) , SASL_转
转自:https 单向双向认证说明_数字证书, 数字签名, SSL(TLS) , SASL 因为项目中要用到TLS + SASL 来做安全认证层. 所以看了一些网上的资料, 这里做一个总结. 1. 首 ...
- 《敏捷软件开发-原则、方法与实践》-Robert C. Martin读书笔记(转)
Review of Agile Software Development: Principles, Patterns, and Practices 本书主要包含4部分内容,这些内容对于今天的软件工程师 ...
- sublime window 配置记录 (转)
大家好,今天给大家分享一款编辑器:sublime text2 我用过很多编辑器,EditPlus.EmEditor.Notepad++.Notepad2.UltraEdit.Editra.Vim ...
- python3----datetime模块分析
datetime模块用于是date和time模块的合集,datetime有两个常量,MAXYEAR和MINYEAR,分别是9999和1. datetime模块定义了5个类,分别是 1.datetime ...
- easyui上次图片
easyuiForm提交: 前台代码: <form id="importFileForm" method="post" enctype="mul ...