- 浏览: 212602 次
- 性别:
- 来自: 深圳
文章分类
最新评论
-
jongde1:
Axure太难学了,分享mockplus工具,有兴趣可以去了解 ...
Axure RP 原型设计工具 -
di1984HIT:
这里面提到了好几种解决办法。
Spring AOP对日志记录、Exception日志记录 -
di1984HIT:
学习一下。
spring struts2 零配置 -
di1984HIT:
不错,不错啊
Struts2防止表单重复提交 -
di1984HIT:
kettle怎么样啊。
Kettle初探
Spring 事务管理高级应用难点剖析: 第 3 部分
简介: 本文是“Spring 事务管理高级应用难点剖析”系列文章的第 3 部分,作者将继续深入剖析在实际 Spring 事务管理应用中容易遇见的一些难点,包括在使用 Spring JDBC 时如果直接获取 Connection,哪些情况会造成数据连接的泄漏与如何应对,以及除 Spring JDBC 外,其它数据访问技术数据连接泄漏的应对方案。
概述
对于应用开发者来说,数据连接泄漏无疑是一个可怕的梦魇。如果存在数据连接泄漏问题,应用程序将因数据连接资源的耗尽而崩溃,甚至还可能引起数据库的崩溃。数据连接泄漏像黑洞一样让开发者避之唯恐不及。
Spring DAO 对所有支持的数据访问技术框架都使用模板化技术进行了薄层的封装。只要您的程序都使用 Spring DAO 模板(如 JdbcTemplate、HibernateTemplate 等)进行数据访问,一定不会存在数据连接泄漏的问题 ―― 这是 Spring 给予我们郑重的承诺!因此,我们无需关注数据连接(Connection)及其衍生品(Hibernate 的 Session 等)的获取和释放的操作,模板类已经通过其内部流程替我们完成了,且对开发者是透明的。
但是由于集成第三方产品,整合遗产代码等原因,可能需要直接访问数据源或直接获取数据连接及其衍生品。这时,如果使用不当,就可能在无意中创造出一个魔鬼般的连接泄漏问题。
我们知道:当 Spring 事务方法运行时,就产生一个事务上下文,该上下文在本事务执行线程中针对同一个数据源绑定了一个唯一的数据连接(或其衍生品),所有被该事务上下文传播的方法都共享这个数据连接。这个数据连接从数据源获取及返回给数据源都在 Spring 掌控之中,不会发生问题。如果在需要数据连接时,能够获取这个被 Spring 管控的数据连接,则使用者可以放心使用,无需关注连接释放的问题。
那么,如何获取这些被 Spring 管控的数据连接呢? Spring 提供了两种方法:其一是使用数据资源获取工具类,其二是对数据源(或其衍生品如 Hibernate SessionFactory)进行代理。在具体介绍这些方法之前,让我们先来看一下各种引发数据连接泄漏的场景。
Spring JDBC 数据连接泄漏
如果直接从数据源获取连接,且在使用完成后不主动归还给数据源(调用 Connection#close()),则将造成数据连接泄漏的问题。
一个具体的实例
下面,来看一个具体的实例:
清单 1.JdbcUserService.java:主体代码
----------------------------------------------
package user.connleak;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import java.sql.Connection;
@Service("jdbcUserService")
public class JdbcUserService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void logon(String userName) {
try {
// ①直接从数据源获取连接,后续程序没有显式释放该连接
Connection conn = jdbcTemplate.getDataSource().getConnection();
String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
Thread.sleep(1000);// ②模拟程序代码的执行时间
} catch (Exception e) {
e.printStackTrace();
}
}
}
----------------------------------------------
JdbcUserService 通过 Spring AOP 事务增强的配置,让所有 public 方法都工作在事务环境中。即让 logon() 和 updateLastLogonTime() 方法拥有事务功能。在 logon() 方法内部,我们在①处通过调用 jdbcTemplate.getDataSource().getConnection()显式获取一个连接,这个连接不是 logon() 方法事务上下文线程绑定的连接,所以如果开发者如果没有手工释放这连接(显式调用 Connection#close() 方法),则这个连接将永久被占用(处于 active 状态),造成连接泄漏!下面,我们编写模拟运行的代码,查看方法执行对数据连接的实际占用情况:
清单 2.JdbcUserService.java:模拟运行代码
----------------------------------------------
…
@Service("jdbcUserService")
public class JdbcUserService {
…
//①以异步线程的方式执行JdbcUserService#logon()方法,以模拟多线程的环境
public static void asynchrLogon(JdbcUserService userService, String userName) {
UserServiceRunner runner = new UserServiceRunner(userService, userName);
runner.start();
}
private static class UserServiceRunner extends Thread {
private JdbcUserService userService;
private String userName;
public UserServiceRunner(JdbcUserService userService, String userName) {
this.userService = userService;
this.userName = userName;
}
public void run() {
userService.logon(userName);
}
}
//② 让主执行线程睡眠一段指定的时间
public static void sleep(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//③ 汇报数据源的连接占用情况
public static void reportConn(BasicDataSource basicDataSource) {
System.out.println("连接数[active:idle]-[" +
basicDataSource.getNumActive()+":"+basicDataSource.getNumIdle()+"]");
}
public static void main(String[] args) {
ApplicationContext ctx =
new ClassPathXmlApplicationContext("user/connleak/applicatonContext.xml");
JdbcUserService userService = (JdbcUserService) ctx.getBean("jdbcUserService");
BasicDataSource basicDataSource = (BasicDataSource) ctx.getBean("dataSource");
//④汇报数据源初始连接占用情况
JdbcUserService.reportConn(basicDataSource);
JdbcUserService.asynchrLogon(userService, "tom");
JdbcUserService.sleep(500);
//⑤此时线程A正在执行JdbcUserService#logon()方法
JdbcUserService.reportConn(basicDataSource);
JdbcUserService.sleep(2000);
//⑥此时线程A所执行的JdbcUserService#logon()方法已经执行完毕
JdbcUserService.reportConn(basicDataSource);
JdbcUserService.asynchrLogon(userService, "john");
JdbcUserService.sleep(500);
//⑦此时线程B正在执行JdbcUserService#logon()方法
JdbcUserService.reportConn(basicDataSource);
JdbcUserService.sleep(2000);
//⑧此时线程A和B都已完成JdbcUserService#logon()方法的执行
JdbcUserService.reportConn(basicDataSource);
}
----------------------------------------------
在 JdbcUserService 中添加一个可异步执行 logon() 方法的 asynchrLogon() 方法,我们通过异步执行 logon() 以及让主线程睡眠的方式模拟多线程环境下的执行场景。在不同的执行点,通过 reportConn() 方法汇报数据源连接的占用情况。
使用如下的 Spring 配置文件对 JdbcUserServie 的方法进行事务增强:
清单 3.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:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<context:component-scan base-package="user.connleak"/>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="oracle.jdbc.driver.OracleDriver"
p:url="jdbc:oracle:thin:@localhost:1521:orcl"
p:username="test"
p:password="test"
p:defaultAutoCommit="false"/>
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource"/>
<bean id="jdbcManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<!-- 对JdbcUserService的所有方法实施事务增强 -->
<aop:config proxy-target-class="true">
<aop:pointcut id="serviceJdbcMethod"
expression="within(user.connleak.JdbcUserService+)"/>
<aop:advisor pointcut-ref="serviceJdbcMethod"
advice-ref="jdbcAdvice" order="0"/>
</aop:config>
<tx:advice id="jdbcAdvice" transaction-manager="jdbcManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
</beans>
----------------------------------------------
保证 BasicDataSource 数据源的配置默认连接为 0,运行以上程序代码,在控制台中将输出以下的信息:
清单 4. 输出日志
----------------------------------------------
连接数 [active:idle]-[0:0]
连接数 [active:idle]-[2:0]
连接数 [active:idle]-[1:1]
连接数 [active:idle]-[3:0]
连接数 [active:idle]-[2:1]
----------------------------------------------
我们通过下表对数据源连接的占用和泄漏情况进行描述:
表 1. 执行过程数据源连接占用情况
时间 执行线程 1 执行线程 2 数据源连接
active idle leak
T0 未启动 未启动 0 0 0
T1 正在执行方法 未启动 2 0 0
T2 执行完毕 未启动 1 1 1
T3 执行完毕 正式执行方法 3 0 1
T4 执行完毕 执行完毕 2 1 2
可见在执行线程 1 执行完毕后,只释放了一个数据连接,还有一个数据连处于 active 状态,说明泄漏了一个连接。相似的,执行线程 2 执行完毕后,也泄漏了一个连接:原因是直接通过数据源获取连接(jdbcTemplate.getDataSource().getConnection())而没有显式释放造成的。
通过 DataSourceUtils 获取数据连接
Spring 提供了一个能从当前事务上下文中获取绑定的数据连接的工具类,那就是 DataSourceUtils。Spring 强调必须使用 DataSourceUtils 工具类获取数据连接,Spring 的 JdbcTemplate 内部也是通过 DataSourceUtils 来获取连接的。DataSourceUtils 提供了若干获取和释放数据连接的静态方法,说明如下:
static Connection doGetConnection(DataSource dataSource):首先尝试从事务上下文中获取连接,失败后再从数据源获取连接;
static Connection getConnection(DataSource dataSource):和 doGetConnection 方法的功能一样,实际上,它内部就是调用 doGetConnection 方法获取连接的;
static void doReleaseConnection(Connection con, DataSource dataSource):释放连接,放回到连接池中;
static void releaseConnection(Connection con, DataSource dataSource):和 doReleaseConnection 方法的功能一样,实际上,它内部就是调用 doReleaseConnection 方法获取连接的;
来看一下 DataSourceUtils 从数据源获取连接的关键代码:
清单 5. DataSourceUtils.java 获取连接的工具类
----------------------------------------------
public abstract class DataSourceUtils {
…
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
//①首先尝试从事务同步管理器中获取数据连接
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
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();
}
//②如果获取不到,则直接从数据源中获取连接
Connection con = dataSource.getConnection();
//③如果拥有事务上下文,则将连接绑定到事务上下文中
if (TransactionSynchronizationManager.isSynchronizationActive()) {
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {holderToUse.setConnection(con);}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(
dataSource, holderToUse);
}
}
return con;
}
…
}
----------------------------------------------
它首先查看当前是否存在事务管理上下文,并尝试从事务管理上下文获取连接,如果获取失败,直接从数据源中获取连接。在获取连接后,如果当前拥有事务上下文,则将连接绑定到事务上下文中。
我们在清单 1 的 JdbcUserService 中,使用 DataSourceUtils.getConnection() 替换直接从数据源中获取连接的代码:
清单 6. JdbcUserService.java:使用 DataSourceUtils 获取数据连接
----------------------------------------------
public void logon(String userName) {
try {
//Connection conn = jdbcTemplate.getDataSource().getConnection();
//①使用DataSourceUtils获取数据连接
Connection conn = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
----------------------------------------------
重新运行代码,得到如下的执行结果:
清单 7. 输出日志
----------------------------------------------
连接数 [active:idle]-[0:0]
连接数 [active:idle]-[1:0]
连接数 [active:idle]-[0:1]
连接数 [active:idle]-[1:0]
连接数 [active:idle]-[0:1]
----------------------------------------------
对照清单 4 的输出日志,我们可以看到已经没有连接泄漏的现象了。一个执行线程在运行 JdbcUserService#logon() 方法时,只占用一个连接,而且方法执行完毕后,该连接马上释放。这说明通过 DataSourceUtils.getConnection() 方法确实获取了方法所在事务上下文绑定的那个连接,而不是像原来那样从数据源中获取一个新的连接。
使用 DataSourceUtils 获取数据连接也可能造成泄漏!
是否使用 DataSourceUtils 获取数据连接就可以高枕无忧了呢?理想很美好,但现实很残酷:如果 DataSourceUtils 在没有事务上下文的方法中使用 getConnection() 获取连接,依然会造成数据连接泄漏!
保持代码清单 6 的代码不变,调整 Spring 配置文件,将清单 3 中 Spring AOP 事务增强配置的代码注释掉,重新运行清单 6 的代码,将得到如下的输出日志:
清单 8. 输出日志
----------------------------------------------
连接数 [active:idle]-[0:0]
连接数 [active:idle]-[1:1]
连接数 [active:idle]-[1:1]
连接数 [active:idle]-[2:1]
连接数 [active:idle]-[2:1]
----------------------------------------------
我们通过下表对数据源连接的占用和泄漏情况进行描述:
表 2. 执行过程数据源连接占用情况
时间 执行线程 1 执行线程 2 数据源连接
active idle leak
T0 未启动 未启动 0 0 0
T1 正在执行方法 未启动 1 1 0
T2 执行完毕 未启动 1 1 1
T3 执行完毕 正式执行方法 2 1 1
T4 执行完毕 执行完毕 2 1 2
仔细对照表 1 的执行过程,我们发现在 T1 时,有事务上下文时的 active 为 2,idle 为 0,而此时由于没有事务管理,则 active 为 1 而 idle 也为 1。这说明有事务上下文时,需要等到整个事务方法(即 logon())返回后,事务上下文绑定的连接才释放。但在没有事务上下文时,logon() 调用 JdbcTemplate 执行完数据操作后,马上就释放连接。
在 T2 执行线程完成 logon() 方法的执行后,有一个连接没有被释放(active),所以发生了连接泄漏。到 T4 时,两个执行线程都完成了 logon() 方法的调用,但是出现了两个未释放的连接。
要堵上这个连接泄漏的漏洞,需要对 logon() 方法进行如下的改造:
清单 9.JdbcUserService.java:手工释放获取的连接
----------------------------------------------
public void logon(String userName) {
Connection conn = null;
try {
conn = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
Thread.sleep(1000);
// ①
} catch (Exception e) {
e.printStackTrace();
}finally {
// ②显式使用DataSourceUtils释放连接
DataSourceUtils.releaseConnection(conn,jdbcTemplate.getDataSource());
}
}
----------------------------------------------
在 ② 处显式调用 DataSourceUtils.releaseConnection() 方法释放获取的连接。特别需要指出的是:一定不能在 ① 处释放连接!因为如果 logon() 在获取连接后,① 处代码前这段代码执行时发生异常,则①处释放连接的动作将得不到执行。这将是一个非常具有隐蔽性的连接泄漏的隐患点。
JdbcTemplate 如何做到对连接泄漏的免疫
分析 JdbcTemplate 的代码,我们可以清楚地看到它开放的每个数据操作方法,首先都使用 DataSourceUtils 获取连接,在方法返回之前使用 DataSourceUtils 释放连接。
来看一下 JdbcTemplate 最核心的一个数据操作方法 execute():
清单 10.JdbcTemplate#execute()
----------------------------------------------
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
//① 首先根据DataSourceUtils获取数据连接
Connection con = DataSourceUtils.getConnection(getDataSource());
Statement stmt = null;
try {
Connection conToUse = con;
…
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate(
"StatementCallback", getSql(action), ex);
}
finally {
JdbcUtils.closeStatement(stmt);
//② 最后根据DataSourceUtils释放数据连接
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
----------------------------------------------
在 ① 处通过 DataSourceUtils.getConnection() 获取连接,在 ② 处通过 DataSourceUtils.releaseConnection() 释放连接。所有 JdbcTemplate 开放的数据访问方法最终都是通过 execute(StatementCallback<T> action)执行数据访问操作的,因此这个方法代表了 JdbcTemplate 数据操作的最终实现方式。
正是因为 JdbcTemplate 严谨的获取连接,释放连接的模式化流程保证了 JdbcTemplate 对数据连接泄漏问题的免疫性。所以,如有可能尽量使用 JdbcTemplate,HibernateTemplate 等这些模板进行数据访问操作,避免直接获取数据连接的操作。
使用 TransactionAwareDataSourceProxy
如果不得已要显式获取数据连接,除了使用 DataSourceUtils 获取事务上下文绑定的连接外,还可以通过 TransactionAwareDataSourceProxy 对数据源进行代理。数据源对象被代理后就具有了事务上下文感知的能力,通过代理数据源的 getConnection() 方法获取的连接和使用 DataSourceUtils.getConnection() 获取连接的效果是一样的。
下面是使用 TransactionAwareDataSourceProxy 对数据源进行代理的配置:
清单 11.applicationContext.xml:对数据源进行代理
----------------------------------------------
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="oracle.jdbc.driver.OracleDriver"
p:url="jdbc:oracle:thin:@localhost:1521:orcl"
p:username="test"
p:password="test"
p:defaultAutoCommit="false"/>
<!-- ①对数据源进行代理-->
<bean id="dataSourceProxy"
class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"
p:targetDataSource-ref="dataSource"/>
<!-- ②直接使用数据源的代理对象-->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSourceProxy"/>
<!-- ③直接使用数据源的代理对象-->
<bean id="jdbcManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSourceProxy"/>
----------------------------------------------
对数据源进行代理后,我们就可以通过数据源代理对象的 getConnection() 获取事务上下文中绑定的数据连接了。
因此,如果数据源已经进行了 TransactionAwareDataSourceProxy 的代理,而且方法存在事务上下文,那么清单 1 的代码也不会生产连接泄漏的问题。
其它数据访问技术的等价类
理解了 Spring JDBC 的数据连接泄漏问题,其中的道理可以平滑地推广到其它框架中去。Spring 为每个数据访问技术框架都提供了一个获取事务上下文绑定的数据连接(或其衍生品)的工具类和数据源(或其衍生品)的代理类。
DataSourceUtils 的等价类
下表列出了不同数据访问技术对应 DataSourceUtils 的等价类:
表 3. 不同数据访问框架 DataSourceUtils 的等价类
数据访问技术框架 连接 ( 或衍生品 ) 获取工具类
Spring JDBC org.springframework.jdbc.datasource.DataSourceUtils
Hibernate org.springframework.orm.hibernate3.SessionFactoryUtils
iBatis org.springframework.jdbc.datasource.DataSourceUtils
JPA org.springframework.orm.jpa.EntityManagerFactoryUtils
JDO org.springframework.orm.jdo.PersistenceManagerFactoryUtils
TransactionAwareDataSourceProxy 的等价类
下表列出了不同数据访问技术框架下 TransactionAwareDataSourceProxy 的等价类:
表 4. 不同数据访问框架 TransactionAwareDataSourceProxy 的等价类
数据访问技术框架 连接 ( 或衍生品 ) 获取工具类
Spring JDBC org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
Hibernate org.springframework.orm.hibernate3.LocalSessionFactoryBean
iBatis org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
JPA 无
JDO org.springframework.orm.jdo.
TransactionAwarePersistenceManagerFactoryProxy
小结
在本文中,我们通过剖析了解到以下的真相:
使用 Spring JDBC 时如果直接获取 Connection,可能会造成连接泄漏。为降低连接泄漏的可能,尽量使用 DataSourceUtils 获取数据连接。也可以对数据源进行代理,以便将其拥有事务上下文的感知能力;
可以将 Spring JDBC 防止连接泄漏的解决方案平滑应用到其它的数据访问技术框架中。
原文:http://www.ibm.com/developerworks/cn/java/j-lo-spring-ts3/index.html
简介: 本文是“Spring 事务管理高级应用难点剖析”系列文章的第 3 部分,作者将继续深入剖析在实际 Spring 事务管理应用中容易遇见的一些难点,包括在使用 Spring JDBC 时如果直接获取 Connection,哪些情况会造成数据连接的泄漏与如何应对,以及除 Spring JDBC 外,其它数据访问技术数据连接泄漏的应对方案。
概述
对于应用开发者来说,数据连接泄漏无疑是一个可怕的梦魇。如果存在数据连接泄漏问题,应用程序将因数据连接资源的耗尽而崩溃,甚至还可能引起数据库的崩溃。数据连接泄漏像黑洞一样让开发者避之唯恐不及。
Spring DAO 对所有支持的数据访问技术框架都使用模板化技术进行了薄层的封装。只要您的程序都使用 Spring DAO 模板(如 JdbcTemplate、HibernateTemplate 等)进行数据访问,一定不会存在数据连接泄漏的问题 ―― 这是 Spring 给予我们郑重的承诺!因此,我们无需关注数据连接(Connection)及其衍生品(Hibernate 的 Session 等)的获取和释放的操作,模板类已经通过其内部流程替我们完成了,且对开发者是透明的。
但是由于集成第三方产品,整合遗产代码等原因,可能需要直接访问数据源或直接获取数据连接及其衍生品。这时,如果使用不当,就可能在无意中创造出一个魔鬼般的连接泄漏问题。
我们知道:当 Spring 事务方法运行时,就产生一个事务上下文,该上下文在本事务执行线程中针对同一个数据源绑定了一个唯一的数据连接(或其衍生品),所有被该事务上下文传播的方法都共享这个数据连接。这个数据连接从数据源获取及返回给数据源都在 Spring 掌控之中,不会发生问题。如果在需要数据连接时,能够获取这个被 Spring 管控的数据连接,则使用者可以放心使用,无需关注连接释放的问题。
那么,如何获取这些被 Spring 管控的数据连接呢? Spring 提供了两种方法:其一是使用数据资源获取工具类,其二是对数据源(或其衍生品如 Hibernate SessionFactory)进行代理。在具体介绍这些方法之前,让我们先来看一下各种引发数据连接泄漏的场景。
Spring JDBC 数据连接泄漏
如果直接从数据源获取连接,且在使用完成后不主动归还给数据源(调用 Connection#close()),则将造成数据连接泄漏的问题。
一个具体的实例
下面,来看一个具体的实例:
清单 1.JdbcUserService.java:主体代码
----------------------------------------------
package user.connleak;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import java.sql.Connection;
@Service("jdbcUserService")
public class JdbcUserService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void logon(String userName) {
try {
// ①直接从数据源获取连接,后续程序没有显式释放该连接
Connection conn = jdbcTemplate.getDataSource().getConnection();
String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
Thread.sleep(1000);// ②模拟程序代码的执行时间
} catch (Exception e) {
e.printStackTrace();
}
}
}
----------------------------------------------
JdbcUserService 通过 Spring AOP 事务增强的配置,让所有 public 方法都工作在事务环境中。即让 logon() 和 updateLastLogonTime() 方法拥有事务功能。在 logon() 方法内部,我们在①处通过调用 jdbcTemplate.getDataSource().getConnection()显式获取一个连接,这个连接不是 logon() 方法事务上下文线程绑定的连接,所以如果开发者如果没有手工释放这连接(显式调用 Connection#close() 方法),则这个连接将永久被占用(处于 active 状态),造成连接泄漏!下面,我们编写模拟运行的代码,查看方法执行对数据连接的实际占用情况:
清单 2.JdbcUserService.java:模拟运行代码
----------------------------------------------
…
@Service("jdbcUserService")
public class JdbcUserService {
…
//①以异步线程的方式执行JdbcUserService#logon()方法,以模拟多线程的环境
public static void asynchrLogon(JdbcUserService userService, String userName) {
UserServiceRunner runner = new UserServiceRunner(userService, userName);
runner.start();
}
private static class UserServiceRunner extends Thread {
private JdbcUserService userService;
private String userName;
public UserServiceRunner(JdbcUserService userService, String userName) {
this.userService = userService;
this.userName = userName;
}
public void run() {
userService.logon(userName);
}
}
//② 让主执行线程睡眠一段指定的时间
public static void sleep(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//③ 汇报数据源的连接占用情况
public static void reportConn(BasicDataSource basicDataSource) {
System.out.println("连接数[active:idle]-[" +
basicDataSource.getNumActive()+":"+basicDataSource.getNumIdle()+"]");
}
public static void main(String[] args) {
ApplicationContext ctx =
new ClassPathXmlApplicationContext("user/connleak/applicatonContext.xml");
JdbcUserService userService = (JdbcUserService) ctx.getBean("jdbcUserService");
BasicDataSource basicDataSource = (BasicDataSource) ctx.getBean("dataSource");
//④汇报数据源初始连接占用情况
JdbcUserService.reportConn(basicDataSource);
JdbcUserService.asynchrLogon(userService, "tom");
JdbcUserService.sleep(500);
//⑤此时线程A正在执行JdbcUserService#logon()方法
JdbcUserService.reportConn(basicDataSource);
JdbcUserService.sleep(2000);
//⑥此时线程A所执行的JdbcUserService#logon()方法已经执行完毕
JdbcUserService.reportConn(basicDataSource);
JdbcUserService.asynchrLogon(userService, "john");
JdbcUserService.sleep(500);
//⑦此时线程B正在执行JdbcUserService#logon()方法
JdbcUserService.reportConn(basicDataSource);
JdbcUserService.sleep(2000);
//⑧此时线程A和B都已完成JdbcUserService#logon()方法的执行
JdbcUserService.reportConn(basicDataSource);
}
----------------------------------------------
在 JdbcUserService 中添加一个可异步执行 logon() 方法的 asynchrLogon() 方法,我们通过异步执行 logon() 以及让主线程睡眠的方式模拟多线程环境下的执行场景。在不同的执行点,通过 reportConn() 方法汇报数据源连接的占用情况。
使用如下的 Spring 配置文件对 JdbcUserServie 的方法进行事务增强:
清单 3.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:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<context:component-scan base-package="user.connleak"/>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="oracle.jdbc.driver.OracleDriver"
p:url="jdbc:oracle:thin:@localhost:1521:orcl"
p:username="test"
p:password="test"
p:defaultAutoCommit="false"/>
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource"/>
<bean id="jdbcManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<!-- 对JdbcUserService的所有方法实施事务增强 -->
<aop:config proxy-target-class="true">
<aop:pointcut id="serviceJdbcMethod"
expression="within(user.connleak.JdbcUserService+)"/>
<aop:advisor pointcut-ref="serviceJdbcMethod"
advice-ref="jdbcAdvice" order="0"/>
</aop:config>
<tx:advice id="jdbcAdvice" transaction-manager="jdbcManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
</beans>
----------------------------------------------
保证 BasicDataSource 数据源的配置默认连接为 0,运行以上程序代码,在控制台中将输出以下的信息:
清单 4. 输出日志
----------------------------------------------
连接数 [active:idle]-[0:0]
连接数 [active:idle]-[2:0]
连接数 [active:idle]-[1:1]
连接数 [active:idle]-[3:0]
连接数 [active:idle]-[2:1]
----------------------------------------------
我们通过下表对数据源连接的占用和泄漏情况进行描述:
表 1. 执行过程数据源连接占用情况
时间 执行线程 1 执行线程 2 数据源连接
active idle leak
T0 未启动 未启动 0 0 0
T1 正在执行方法 未启动 2 0 0
T2 执行完毕 未启动 1 1 1
T3 执行完毕 正式执行方法 3 0 1
T4 执行完毕 执行完毕 2 1 2
可见在执行线程 1 执行完毕后,只释放了一个数据连接,还有一个数据连处于 active 状态,说明泄漏了一个连接。相似的,执行线程 2 执行完毕后,也泄漏了一个连接:原因是直接通过数据源获取连接(jdbcTemplate.getDataSource().getConnection())而没有显式释放造成的。
通过 DataSourceUtils 获取数据连接
Spring 提供了一个能从当前事务上下文中获取绑定的数据连接的工具类,那就是 DataSourceUtils。Spring 强调必须使用 DataSourceUtils 工具类获取数据连接,Spring 的 JdbcTemplate 内部也是通过 DataSourceUtils 来获取连接的。DataSourceUtils 提供了若干获取和释放数据连接的静态方法,说明如下:
static Connection doGetConnection(DataSource dataSource):首先尝试从事务上下文中获取连接,失败后再从数据源获取连接;
static Connection getConnection(DataSource dataSource):和 doGetConnection 方法的功能一样,实际上,它内部就是调用 doGetConnection 方法获取连接的;
static void doReleaseConnection(Connection con, DataSource dataSource):释放连接,放回到连接池中;
static void releaseConnection(Connection con, DataSource dataSource):和 doReleaseConnection 方法的功能一样,实际上,它内部就是调用 doReleaseConnection 方法获取连接的;
来看一下 DataSourceUtils 从数据源获取连接的关键代码:
清单 5. DataSourceUtils.java 获取连接的工具类
----------------------------------------------
public abstract class DataSourceUtils {
…
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
//①首先尝试从事务同步管理器中获取数据连接
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
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();
}
//②如果获取不到,则直接从数据源中获取连接
Connection con = dataSource.getConnection();
//③如果拥有事务上下文,则将连接绑定到事务上下文中
if (TransactionSynchronizationManager.isSynchronizationActive()) {
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {holderToUse.setConnection(con);}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(
dataSource, holderToUse);
}
}
return con;
}
…
}
----------------------------------------------
它首先查看当前是否存在事务管理上下文,并尝试从事务管理上下文获取连接,如果获取失败,直接从数据源中获取连接。在获取连接后,如果当前拥有事务上下文,则将连接绑定到事务上下文中。
我们在清单 1 的 JdbcUserService 中,使用 DataSourceUtils.getConnection() 替换直接从数据源中获取连接的代码:
清单 6. JdbcUserService.java:使用 DataSourceUtils 获取数据连接
----------------------------------------------
public void logon(String userName) {
try {
//Connection conn = jdbcTemplate.getDataSource().getConnection();
//①使用DataSourceUtils获取数据连接
Connection conn = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
----------------------------------------------
重新运行代码,得到如下的执行结果:
清单 7. 输出日志
----------------------------------------------
连接数 [active:idle]-[0:0]
连接数 [active:idle]-[1:0]
连接数 [active:idle]-[0:1]
连接数 [active:idle]-[1:0]
连接数 [active:idle]-[0:1]
----------------------------------------------
对照清单 4 的输出日志,我们可以看到已经没有连接泄漏的现象了。一个执行线程在运行 JdbcUserService#logon() 方法时,只占用一个连接,而且方法执行完毕后,该连接马上释放。这说明通过 DataSourceUtils.getConnection() 方法确实获取了方法所在事务上下文绑定的那个连接,而不是像原来那样从数据源中获取一个新的连接。
使用 DataSourceUtils 获取数据连接也可能造成泄漏!
是否使用 DataSourceUtils 获取数据连接就可以高枕无忧了呢?理想很美好,但现实很残酷:如果 DataSourceUtils 在没有事务上下文的方法中使用 getConnection() 获取连接,依然会造成数据连接泄漏!
保持代码清单 6 的代码不变,调整 Spring 配置文件,将清单 3 中 Spring AOP 事务增强配置的代码注释掉,重新运行清单 6 的代码,将得到如下的输出日志:
清单 8. 输出日志
----------------------------------------------
连接数 [active:idle]-[0:0]
连接数 [active:idle]-[1:1]
连接数 [active:idle]-[1:1]
连接数 [active:idle]-[2:1]
连接数 [active:idle]-[2:1]
----------------------------------------------
我们通过下表对数据源连接的占用和泄漏情况进行描述:
表 2. 执行过程数据源连接占用情况
时间 执行线程 1 执行线程 2 数据源连接
active idle leak
T0 未启动 未启动 0 0 0
T1 正在执行方法 未启动 1 1 0
T2 执行完毕 未启动 1 1 1
T3 执行完毕 正式执行方法 2 1 1
T4 执行完毕 执行完毕 2 1 2
仔细对照表 1 的执行过程,我们发现在 T1 时,有事务上下文时的 active 为 2,idle 为 0,而此时由于没有事务管理,则 active 为 1 而 idle 也为 1。这说明有事务上下文时,需要等到整个事务方法(即 logon())返回后,事务上下文绑定的连接才释放。但在没有事务上下文时,logon() 调用 JdbcTemplate 执行完数据操作后,马上就释放连接。
在 T2 执行线程完成 logon() 方法的执行后,有一个连接没有被释放(active),所以发生了连接泄漏。到 T4 时,两个执行线程都完成了 logon() 方法的调用,但是出现了两个未释放的连接。
要堵上这个连接泄漏的漏洞,需要对 logon() 方法进行如下的改造:
清单 9.JdbcUserService.java:手工释放获取的连接
----------------------------------------------
public void logon(String userName) {
Connection conn = null;
try {
conn = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
Thread.sleep(1000);
// ①
} catch (Exception e) {
e.printStackTrace();
}finally {
// ②显式使用DataSourceUtils释放连接
DataSourceUtils.releaseConnection(conn,jdbcTemplate.getDataSource());
}
}
----------------------------------------------
在 ② 处显式调用 DataSourceUtils.releaseConnection() 方法释放获取的连接。特别需要指出的是:一定不能在 ① 处释放连接!因为如果 logon() 在获取连接后,① 处代码前这段代码执行时发生异常,则①处释放连接的动作将得不到执行。这将是一个非常具有隐蔽性的连接泄漏的隐患点。
JdbcTemplate 如何做到对连接泄漏的免疫
分析 JdbcTemplate 的代码,我们可以清楚地看到它开放的每个数据操作方法,首先都使用 DataSourceUtils 获取连接,在方法返回之前使用 DataSourceUtils 释放连接。
来看一下 JdbcTemplate 最核心的一个数据操作方法 execute():
清单 10.JdbcTemplate#execute()
----------------------------------------------
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
//① 首先根据DataSourceUtils获取数据连接
Connection con = DataSourceUtils.getConnection(getDataSource());
Statement stmt = null;
try {
Connection conToUse = con;
…
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate(
"StatementCallback", getSql(action), ex);
}
finally {
JdbcUtils.closeStatement(stmt);
//② 最后根据DataSourceUtils释放数据连接
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
----------------------------------------------
在 ① 处通过 DataSourceUtils.getConnection() 获取连接,在 ② 处通过 DataSourceUtils.releaseConnection() 释放连接。所有 JdbcTemplate 开放的数据访问方法最终都是通过 execute(StatementCallback<T> action)执行数据访问操作的,因此这个方法代表了 JdbcTemplate 数据操作的最终实现方式。
正是因为 JdbcTemplate 严谨的获取连接,释放连接的模式化流程保证了 JdbcTemplate 对数据连接泄漏问题的免疫性。所以,如有可能尽量使用 JdbcTemplate,HibernateTemplate 等这些模板进行数据访问操作,避免直接获取数据连接的操作。
使用 TransactionAwareDataSourceProxy
如果不得已要显式获取数据连接,除了使用 DataSourceUtils 获取事务上下文绑定的连接外,还可以通过 TransactionAwareDataSourceProxy 对数据源进行代理。数据源对象被代理后就具有了事务上下文感知的能力,通过代理数据源的 getConnection() 方法获取的连接和使用 DataSourceUtils.getConnection() 获取连接的效果是一样的。
下面是使用 TransactionAwareDataSourceProxy 对数据源进行代理的配置:
清单 11.applicationContext.xml:对数据源进行代理
----------------------------------------------
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="oracle.jdbc.driver.OracleDriver"
p:url="jdbc:oracle:thin:@localhost:1521:orcl"
p:username="test"
p:password="test"
p:defaultAutoCommit="false"/>
<!-- ①对数据源进行代理-->
<bean id="dataSourceProxy"
class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"
p:targetDataSource-ref="dataSource"/>
<!-- ②直接使用数据源的代理对象-->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSourceProxy"/>
<!-- ③直接使用数据源的代理对象-->
<bean id="jdbcManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSourceProxy"/>
----------------------------------------------
对数据源进行代理后,我们就可以通过数据源代理对象的 getConnection() 获取事务上下文中绑定的数据连接了。
因此,如果数据源已经进行了 TransactionAwareDataSourceProxy 的代理,而且方法存在事务上下文,那么清单 1 的代码也不会生产连接泄漏的问题。
其它数据访问技术的等价类
理解了 Spring JDBC 的数据连接泄漏问题,其中的道理可以平滑地推广到其它框架中去。Spring 为每个数据访问技术框架都提供了一个获取事务上下文绑定的数据连接(或其衍生品)的工具类和数据源(或其衍生品)的代理类。
DataSourceUtils 的等价类
下表列出了不同数据访问技术对应 DataSourceUtils 的等价类:
表 3. 不同数据访问框架 DataSourceUtils 的等价类
数据访问技术框架 连接 ( 或衍生品 ) 获取工具类
Spring JDBC org.springframework.jdbc.datasource.DataSourceUtils
Hibernate org.springframework.orm.hibernate3.SessionFactoryUtils
iBatis org.springframework.jdbc.datasource.DataSourceUtils
JPA org.springframework.orm.jpa.EntityManagerFactoryUtils
JDO org.springframework.orm.jdo.PersistenceManagerFactoryUtils
TransactionAwareDataSourceProxy 的等价类
下表列出了不同数据访问技术框架下 TransactionAwareDataSourceProxy 的等价类:
表 4. 不同数据访问框架 TransactionAwareDataSourceProxy 的等价类
数据访问技术框架 连接 ( 或衍生品 ) 获取工具类
Spring JDBC org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
Hibernate org.springframework.orm.hibernate3.LocalSessionFactoryBean
iBatis org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
JPA 无
JDO org.springframework.orm.jdo.
TransactionAwarePersistenceManagerFactoryProxy
小结
在本文中,我们通过剖析了解到以下的真相:
使用 Spring JDBC 时如果直接获取 Connection,可能会造成连接泄漏。为降低连接泄漏的可能,尽量使用 DataSourceUtils 获取数据连接。也可以对数据源进行代理,以便将其拥有事务上下文的感知能力;
可以将 Spring JDBC 防止连接泄漏的解决方案平滑应用到其它的数据访问技术框架中。
原文:http://www.ibm.com/developerworks/cn/java/j-lo-spring-ts3/index.html
发表评论
-
Replace constructor-arg by annotation
2013-05-24 04:34 1104replace constructor-arg by anno ... -
spring struts2 零配置
2013-05-23 18:04 6861.ssh(struts2.3.8+spring3.2+hei ... -
使用Spring进行统一日志管理 + 统一异常管理
2013-03-15 16:23 1229使用Spring进行统一日志管理 + 统一异常管理 统一日志 ... -
Spring 配置(事务和日志)
2013-03-15 16:11 1851Spring的那些配置(事务和日志) Spring整合Iba ... -
Spring 自定义注解实现操作日志记录功能
2013-03-15 09:55 2346Spring 自定义注解实现操作日志记录功能 最近项目组长分 ... -
Spring AOP对日志记录、Exception日志记录
2013-03-14 18:14 6505Spring AOP对日志记录、Exception日志记录 ... -
Struts2防止表单重复提交
2013-03-14 10:18 916Struts2防止表单重复提交 最近开发中涉及到了表单重复提 ... -
Spring事务配置的五种方式
2013-03-05 10:08 739Spring事务配置的五种方式 前段时间对Spring的事 ... -
spring 异常与事务
2013-03-01 11:21 1831Service层捕获异常,并抛出RuntimeExceptio ... -
如何将基于 Struts、Spring 和 Hibernate 的应用从 Tomcat 迁移到 WebSphere Application Server
2012-12-21 10:28 1193引言 现在很多的企业都 ... -
Spring 事务管理高级应用难点剖析: 第 2 部分
2011-04-26 17:20 925Spring 事务管理高级应用 ... -
Spring 事务管理高级应用难点剖析: 第 1 部分
2011-04-26 17:05 799Spring 事务管理高级应用难点剖析: 第 1 部分 简介 ...
相关推荐
总的来说,Spring 事务管理的高级应用不仅涉及到事务的启动、提交和回滚,还涵盖了事务的传播行为和隔离级别,这些都是保证软件系统数据一致性的重要工具。了解并合理利用这些特性,可以帮助开发者编写出更健壮、...
Spring最成功,最吸引人的地方莫过于轻量级的声明式事务管理,仅此一点,它就宣告了重量级EJB容器的覆灭。Spring声明式事务管理将开发者从繁复的事务管理代码中解脱出来,专注于业务逻辑的开发上,这是一件可以被拿...
本篇文章主要探讨的是Spring事务管理在高级应用中的难点和一些常见误解。 首先,一些开发者误认为使用Spring事务管理必须按照传统的Web、Service和DAO三层架构来组织代码。他们倾向于在每一层都定义接口和实现类,...
在本文中,我们将深入探讨Spring事务管理的一些高级应用难点,特别是涉及多线程场景下的事务处理。 首先,Spring事务管理器利用了ThreadLocal来在每个线程中保存数据访问基础设施,如数据库连接。ThreadLocal是一种...
第10章:对实际应用中Spring事务管理各种疑难问题进行透彻的剖析,让读者对Spring事务管理不再有云遮雾罩的感觉。 第11章:讲解了如何使用Spring JDBC进行数据访问操作,我们还重点讲述了LOB字段处理、主键产生...
第10章:对实际应用中Spring事务管理各种疑难问题进行透彻的剖析,让读者对Spring事务管理不再有云遮雾罩的感觉。 第11章:讲解了如何使用Spring JDBC进行数据访问操作,我们还重点讲述了LOB字段处理、主键产生...
第10章 Spring的事务管理难点剖析 10.1 DAO和事务管理的牵绊 10.1.1 JDBC访问数据库 10.1.2 Hibernate访问数据库 10.2 应用分层的迷惑 10.3 事务方法嵌套调用的迷茫 10.3.1 Spring事务传播机制回顾 10.3.2 相互嵌套...
第10章 Spring的事务管理难点剖析 10.1 DAO和事务管理的牵绊 10.1.1 JDBC访问数据库 10.1.2 Hibernate访问数据库 10.2 应用分层的迷惑 10.3 事务方法嵌套调用的迷茫 10.3.1 Spring事务传播机制回顾 10.3.2 相互嵌套...
面试必考之HashMap源码分析与实现 探索JVM底层奥秘ClassLoader源码分析与案例讲解 面试必备技能之Dubbo企业实战 ...互联网系统垂直架构之Session解决方案 分库分表之后分布式下如何...无处不在的Spring AOP事务及踩过的坑
“Spring 事务管理高级应用难点剖析”系列文章,聚焦于Spring框架的事务管理,这是Java企业级应用开发中的关键部分。这部分内容可能涵盖了事务的ACID特性,以及Spring中的声明式和编程式事务管理,对于处理并发和...
5. **事务管理**:Spring的事务管理功能可以透明地处理事务,无论是编程式还是声明式的事务管理,都能确保事务的一致性。 6. **测试支持**:Spring的测试模块提供了一套完整的测试工具,包括单元测试、集成测试和...
利用 Spring Boot 的事务管理机制和 Spring Security 进行用户权限管理,保证多用户并发操作时的数据一致性及安全。 - **系统性能优化** 采用缓存技术、数据库索引优化等方式提高系统响应速度;同时采用前后端...
主要课程内容 ...Spring IoC 源码深度剖析 设计⾮常优雅 设计模式 注意:原则、⽅法和技巧 Spring AOP ⾼级应⽤ 声明式事务控制 Spring AOP 源码深度剖析 必要的笔记、必要的图、通俗易懂的语⾔化解知识难点
- **Spring源码分析**:深入剖析Spring框架内部机制,如IOC容器、AOP实现原理等。 - **MyBatis源码分析**:解读MyBatis的工作流程、执行过程及SQL映射机制。 - **事务管理**:讨论非传统事务处理方式——柔性事务...
3.针对库存业务分析:事务(1>.减内存 2>.记录购买明细) 4.记录秒杀成功信息 (1)购买成功的对象 (2)成功的时间/有效期 (3)付款/发货信息 二、异常情况分析 减库存没有记录购买明细 记录明细但没有减...
SSH是一个流行的企业级Web应用开发框架,它结合了Struts的MVC设计模式、Spring的依赖注入和事务管理以及Hibernate的对象关系映射功能,提供了一套完整的解决方案。 1. Struts2:作为MVC框架,Struts2负责处理HTTP...
### 基于SpringBoot的高校办公室行政事务管理系统 #### 一、系统概述与背景 在信息化快速发展的今天,高效地管理和处理各种行政事务成为各高校办公室的重要任务之一。随着业务量的增加以及数据处理复杂性的提升,...
在本项目中,Spring主要负责管理对象的生命周期和事务控制。 2. SpringMVC:SpringMVC是Spring框架的一个模块,专为构建Web应用程序而设计。它将模型、视图和控制器分离,使得代码结构清晰,易于维护。 3. MyBatis:...
6. **论文**:附带的“音像店租赁管理系统的设计与实现.doc”可能是一篇详细阐述系统设计思路、开发过程和技术难点的学术论文,包含了需求分析、系统架构设计、数据库设计、功能实现、测试和优化等内容。 7. **说明...