Spring事务之如何保证同一个Connection对象
Spring容器的事务机制的实质是对传统JDBC的封装,也即是Spring事务管理无论是对单数据库实例还是分布式数据库实例,要实现事务管理,那么必须保证在一个事务过程获得Connetion对象是同一个,那么即使在同一个函数中调用其他多个的函数,通过Spring框架的AOP动态代理机制,使得Spring容器底层能够按传统JDBC的方式进行事务处理,从而保证对这个函数做事务控制。
一、传统JDBC事务管理
首先我们先看一下,jdbc的事务配置是在Connection对消里面有对应的方法,比如setAutoCommit,commit,rollback这些方法就是对事务的操作。
conn.setAutoCommit(false);//设置事务非自动提交
conn.commit();//提交事务
conn.rollback();//事务回滚
这样必然存在一些问题,如:把业务逻辑代码和事务处理代码混合起来,同时存在代码重复性。如下是一段典型的控制事务代码:
private DataSource dataSource = null;
public void setDataSource(DataSource dataSource){
this.dataSource = dataSource;
}
public void update() {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false);//设置事务非自动提交
String sql = "update testTable set name='测试数据' where id = '1'";
pstmt = conn.prepareStatement(sql);
pstmt.execute();
conn.commit();//提交事务
} catch (Exception e) {
try {
conn.rollback();//事务回滚
} catch (Exception e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
try {
if(pstmt!=null)
pstmt.close();
if (conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
二、Spring中的事务原理
Spring容器的事务机制的实质是对传统JDBC的封装,也即是Spring事务管理无论是对单数据库实例还是分布式数据库实例,要实现事务管理,那么必须保证在一个事务过程获得Connetion对象是同一个,那么即使在同一个函数中调用其他多个的函数,通过Spring框架的AOP动态代理机制,使得Spring容器底层能够按传统JDBC的方式进行事务处理,从而保证对这个函数做事务控制。
Spring框架具有支持多数据源的特性,在获得数据库Connection对象往往是通过DataSource中获得,DataSource这个接口往往由不同的厂商驱动实现,因此Spring框架往往是对DataSource进一步的封装保证每次获得的Connection为相同的,这就保证了一个业务方法里面进行多次dao操作,调用的都是一个connection对象,同时保证了多个dao都是在一个事务里面。
package javax.sql;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Wrapper;
/**
* <p>A factory for connections to the physical data source that this
* <code>DataSource</code> object represents. An alternative to the
* <code>DriverManager</code> facility, a <code>DataSource</code> object
* is the preferred means of getting a connection. An object that implements
* the <code>DataSource</code> interface will typically be
* registered with a naming service based on the
* Java<sup><font size=-2>TM</font></sup> Naming and Directory (JNDI) API.
* <P>
* The <code>DataSource</code> interface is implemented by a driver vendor.
* There are three types of implementations:
* <OL>
* <LI>Basic implementation -- produces a standard <code>Connection</code>
* object
* <LI>Connection pooling implementation -- produces a <code>Connection</code>
* object that will automatically participate in connection pooling. This
* implementation works with a middle-tier connection pooling manager.
* <LI>Distributed transaction implementation -- produces a
* <code>Connection</code> object that may be used for distributed
* transactions and almost always participates in connection pooling.
* This implementation works with a middle-tier
* transaction manager and almost always with a connection
* pooling manager.
* </OL>
* <P>
* A <code>DataSource</code> object has properties that can be modified
* when necessary. For example, if the data source is moved to a different
* server, the property for the server can be changed. The benefit is that
* because the data source's properties can be changed, any code accessing
* that data source does not need to be changed.
* <P>
* A driver that is accessed via a <code>DataSource</code> object does not
* register itself with the <code>DriverManager</code>. Rather, a
* <code>DataSource</code> object is retrieved though a lookup operation
* and then used to create a <code>Connection</code> object. With a basic
* implementation, the connection obtained through a <code>DataSource</code>
* object is identical to a connection obtained through the
* <code>DriverManager</code> facility.
*
* @since 1.4
*/
public interface DataSource extends CommonDataSource,Wrapper {
/**
* <p>Attempts to establish a connection with the data source that
* this <code>DataSource</code> object represents.
*
* @return a connection to the data source
* @exception SQLException if a database access error occurs
*/
Connection getConnection() throws SQLException;
/**
* <p>Attempts to establish a connection with the data source that
* this <code>DataSource</code> object represents.
*
* @param username the database user on whose behalf the connection is
* being made
* @param password the user's password
* @return a connection to the data source
* @exception SQLException if a database access error occurs
* @since 1.4
*/
Connection getConnection(String username, String password)
throws SQLException;
}
三、Spring中事务处理过程
首先我们先看JdbcTemplate类数据访问类
public Object execute(ConnectionCallback action)
throws DataAccessException
{
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(getDataSource());
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null)
{
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
else
{
conToUse = createConnectionProxy(con);
}
localObject1 = action.doInConnection(conToUse);
}
catch (SQLException ex)
{
Object localObject1;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
}
finally {
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
由上述源码中Connection con = DataSourceUtils.getConnection(getDataSource());这个可以看出,DataSourceUtils类保证当前线程获得的是同一个Connection对象。下面我们主要分析DataSourceUtils类:
public static Connection getConnection(DataSource dataSource)
throws CannotGetJdbcConnectionException
{
try
{
return doGetConnection(dataSource);
} catch (SQLException ex) {
}
throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
}
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();
}
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = dataSource.getConnection();
if (TransactionSynchronizationManager.isSynchronizationActive()) {
logger.debug("Registering transaction synchronization for JDBC Connection");
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;
}
由以上源码可以知道,数据库连接从TransactionSynchronizationManager中获得,如果已经存在则获得,否则重新从DataSource创建一个连接,并把这个连接封装为ConnectionHolder,然后注册绑定到TransactionSynchronizationManager中,并返回Connection对象。同时,可以看出DataSource和ConnectionHolder的存储管理在TransactionSynchronizationManager中,继续分析TransactionSynchronizationManager中的关键代码:
private static final ThreadLocal resources = new NamedThreadLocal("Transactional resources");
public static Object getResource(Object key)
{
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doGetResource(actualKey);
if ((value != null) && (logger.isTraceEnabled())) {
logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
return value;
}
private static Object doGetResource(Object actualKey)
{
Map map = (Map)resources.get();
if (map == null) {
return null;
}
Object value = map.get(actualKey);
if (((value instanceof ResourceHolder)) && (((ResourceHolder)value).isVoid())) {
map.remove(actualKey);
value = null;
}
return value;
}
public static void bindResource(Object key, Object value)
throws IllegalStateException
{
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
Map map = (Map)resources.get();
if (map == null) {
map = new HashMap();
resources.set(map);
}
if (map.put(actualKey, value) != null) {
throw new IllegalStateException("Already value [" + map.get(actualKey) + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
if (logger.isTraceEnabled())
logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" + Thread.currentThread().getName() + "]");
}
分析源码可以得出,
(1)TransactionSynchronizationManager内部用ThreadLocal对象存储资源,ThreadLocal存储的为DataSource生成的actualKey为key值和ConnectionHolder作为value值封装成的Map。
(2)结合DataSourceUtils的doGetConnection函数和TransactionSynchronizationManager的bindResource函数可知:在某个线程第一次调用时候,封装Map资源为:key值为DataSource生成actualKey【Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);】value值为DataSource获得的Connection对象封装后的ConnectionHolder。等这个线程下一次再次访问中就能保证使用的是第一次创建的ConnectionHolder中的Connection对象。
当事务结束后,调用【DataSourceUtils.releaseConnection(con, getDataSource());】将ConnectionHolder从TransactionSynchronizationManager中解除。当谁都不用,这个连接被close。
public static void releaseConnection(Connection con, DataSource dataSource)
{
try
{
doReleaseConnection(con, dataSource);
}
catch (SQLException ex) {
logger.debug("Could not close JDBC Connection", ex);
}
catch (Throwable ex) {
logger.debug("Unexpected exception on closing JDBC Connection", ex);
}
}
public static void doReleaseConnection(Connection con, DataSource dataSource)
throws SQLException
{
if (con == null) {
return;
}
if (dataSource != null) {
ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
if ((conHolder != null) && (connectionEquals(conHolder, con)))
{
conHolder.released();
return;
}
}
if ((!(dataSource instanceof SmartDataSource)) || (((SmartDataSource)dataSource).shouldClose(con))) {
logger.debug("Returning JDBC Connection to DataSource");
con.close();
}
}
更多推荐
所有评论(0)