`
cywhoyi
  • 浏览: 421061 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

BoneCP的transactionRecoveryEnabled涵义

 
阅读更多

BoneCP framework的整个框架设计,其实还是比较容易看懂的,大致用两天左右时间把源代码看了大概。

 

发现能够拿得出比较有意思的点应该是配置transactionRecoveryEnabled参数的意义,虽然在xml配置里面只是配置boolean类型true、false,但是内置的涵义挺有趣、挺cool。

 

了解transactionRecoveryEnabled参数首先需要把MemorizeTransactionProxy

public class MemorizeTransactionProxy implements InvocationHandler {
	/** Target of proxy. */
	private Object target;
	/** Connection handle. Keep a WeakReference here because we want the GC to kick in if the application loses a handle on it.*/
	private WeakReference<ConnectionHandle> connectionHandle;
	/** List of methods that will trigger a reset of the transaction. */
	private static final ImmutableSet<String> clearLogConditions = ImmutableSet.of("rollback", "commit", "close"); 
	/** Class logger. */
	private static final Logger logger = LoggerFactory.getLogger(MemorizeTransactionProxy.class);


	/**
	 * Default constructor. 
	 */
	public MemorizeTransactionProxy(){
		// not needed
	}
	/** Wrap connection with a proxy.
	 * @param target connection handle
	 * @param connectionHandle originating bonecp connection
	 * @return Proxy to a connection.
	 */
	protected static Connection memorize(final Connection target, final ConnectionHandle connectionHandle) {

		return (Connection) Proxy.newProxyInstance(
				ConnectionProxy.class.getClassLoader(),
				new Class[] {ConnectionProxy.class},
				new MemorizeTransactionProxy(target, connectionHandle));
	}

	/** Wrap Statement with a proxy.
	 * @param target statement handle
	 * @param connectionHandle originating bonecp connection
	 * @return Proxy to a statement.
	 */
	protected static Statement memorize(final Statement target, final ConnectionHandle connectionHandle) {
		return (Statement) Proxy.newProxyInstance(
				StatementProxy.class.getClassLoader(),
				new Class[] {StatementProxy.class},
				new MemorizeTransactionProxy(target, connectionHandle));
	}

	/** Wrap PreparedStatement with a proxy.
	 * @param target statement handle
	 * @param connectionHandle originating bonecp connection
	 * @return Proxy to a Preparedstatement.
	 */
	protected static PreparedStatement memorize(final PreparedStatement target, final ConnectionHandle connectionHandle) {
		return (PreparedStatement) Proxy.newProxyInstance(
				PreparedStatementProxy.class.getClassLoader(),
				new Class[] {PreparedStatementProxy.class},
				new MemorizeTransactionProxy(target, connectionHandle));
	}


	/** Wrap CallableStatement with a proxy.
	 * @param target statement handle
	 * @param connectionHandle originating bonecp connection
	 * @return Proxy to a Callablestatement.
	 */
	protected static CallableStatement memorize(final CallableStatement target, final ConnectionHandle connectionHandle) {
		return (CallableStatement) Proxy.newProxyInstance(
				CallableStatementProxy.class.getClassLoader(),
				new Class[] {CallableStatementProxy.class},
				new MemorizeTransactionProxy(target, connectionHandle));
	}

	/** Main constructor
	 * @param target target to actual handle
	 * @param connectionHandle bonecp ref
	 */
	private MemorizeTransactionProxy(Object target, ConnectionHandle connectionHandle) {
		this.target = target;
		this.connectionHandle = new WeakReference<ConnectionHandle>(connectionHandle);
	}

	// @Override 
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

		Object result = null;
		ConnectionHandle con = this.connectionHandle.get();
		if (con != null){ // safety! 

			if (method.getName().equals("getProxyTarget")){  // special "fake" method to return our proxy target
				return this.target;
			} 

			if (con.isInReplayMode()){ // go straight through when flagged as in playback (replay) mode.
				try{
					return method.invoke(this.target, args);
				} catch (InvocationTargetException t){
					throw t.getCause(); // we tried running it, but playback mode also blew up. Throw out the cause, not the
					// wrapped invocationtargetexception.
				} 
			}


			if (con.recoveryResult != null){ // if we previously failed, do the mapping to the new connection/statements
				Object remap = con.recoveryResult.getReplaceTarget().get(this.target);
				if (remap != null){
					this.target = remap; 
				}
				remap = con.recoveryResult.getReplaceTarget().get(con);
				if (remap != null){
					con = (ConnectionHandle) remap;
				}
			}

			// record this invocation
			if (!con.isInReplayMode() && !method.getName().equals("hashCode") 
					&& !method.getName().equals("equals") && !method.getName().equals("toString")){
				con.getReplayLog().add(new ReplayLog(this.target, method, args));
			}




			try{
				// run and swap with proxies if we encounter prepareStatement calls
				result = runWithPossibleProxySwap(method, this.target, args); 

				// when we commit/close/rollback, destroy our log. Does this work if we have nested transactions???? Fixme?
				if (!con.isInReplayMode() && (this.target instanceof Connection) && clearLogConditions.contains(method.getName())){
					con.getReplayLog().clear();
					//					con.recoveryResult.getReplaceTarget().clear();
				}



			} catch (Throwable t){  
				// if we encounter problems, grab a connection and replay back our log
				List<ReplayLog> oldReplayLog = con.getReplayLog();
				con.setInReplayMode(true); // stop recording

				// this will possibly terminate all connections here
				if (t instanceof SQLException || (t.getCause() != null && t.getCause() instanceof SQLException)){
					con.markPossiblyBroken((SQLException)t.getCause());
				}

				if (!con.isPossiblyBroken()){ // connection is possibly recoverable...
					con.setInReplayMode(false); // start recording again
					con.getReplayLog().clear();
				} else { // connection is possibly recoverable...
					logger.error("Connection failed. Attempting to recover transaction on Thread #"+ Thread.currentThread().getId());
					// let's try and recover
					try{
						con.recoveryResult = attemptRecovery(oldReplayLog); // this might also fail
						con.setReplayLog(oldReplayLog); // attemptRecovery will probably destroy our original connection handle
						con.setInReplayMode(false); // start recording again
						logger.error("Recovery succeeded on Thread #" + Thread.currentThread().getId());
						con.possiblyBroken = false;

						// return the original result the application was expecting
						return con.recoveryResult.getResult();
					} catch(Throwable t2){
						con.setInReplayMode(false); // start recording again
						con.getReplayLog().clear();
						/* #ifdef JDK6
						throw new SQLException("Could not recover transaction.", t.getCause());
						#endif JDK6 */
						/* #ifdef JDK5
			 			throw new SQLException("Could not recover transaction. Original exception follows." + t.getCause());
						#endif JDK5 */

					}
				} 



				// it must some user-level error eg setting a preparedStatement parameter that is out of bounds. Just throw it back to the user.
				throw t.getCause();

			}

		}
		return result; // normal state
	}

	/** Runs the given method with the specified arguments, substituting with proxies where necessary
	 * @param method
	 * @param target proxy target 
	 * @param args
	 * @return Proxy-fied result for statements, actual call result otherwise
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	private Object runWithPossibleProxySwap(Method method, Object target, Object[] args)
			throws IllegalAccessException, InvocationTargetException {
		Object result;
		// swap with proxies to these too.
		if (method.getName().equals("createStatement")){
			result = memorize((Statement)method.invoke(target, args), this.connectionHandle.get());
		}
		else if (method.getName().equals("prepareStatement")){
			result = memorize((PreparedStatement)method.invoke(target, args), this.connectionHandle.get());
		}
		else if (method.getName().equals("prepareCall")){
			result = memorize((CallableStatement)method.invoke(target, args), this.connectionHandle.get());
		}
		else result = method.invoke(target, args);
		return result;
	}

	/** Play back a transaction
	 * @param oldReplayLog 
	 * @return map + result
	 * @throws SQLException 
	 * 
	 */
	private TransactionRecoveryResult attemptRecovery(List<ReplayLog> oldReplayLog) throws SQLException{
		boolean tryAgain = false;
		Throwable failedThrowable = null;

		ConnectionHandle con = this.connectionHandle.get();
		if (con == null){ // safety! 
			throw PoolUtil.generateSQLException("ConnectionHandle is gone!", new IllegalStateException());
		}
		TransactionRecoveryResult recoveryResult = con.recoveryResult;
		ConnectionHook connectionHook = con.getPool().getConfig().getConnectionHook();

		int acquireRetryAttempts = con.getPool().getConfig().getAcquireRetryAttempts();
		long acquireRetryDelay = con.getPool().getConfig().getAcquireRetryDelayInMs();
		AcquireFailConfig acquireConfig = new AcquireFailConfig();
		acquireConfig.setAcquireRetryAttempts(new AtomicInteger(acquireRetryAttempts));
		acquireConfig.setAcquireRetryDelayInMs(acquireRetryDelay);
		acquireConfig.setLogMessage("Failed to replay transaction");

		Map<Object, Object> replaceTarget = new HashMap<Object, Object>();
		do{
			replaceTarget.clear(); 
			// make a copy
			for (Entry<Object, Object> entry: recoveryResult.getReplaceTarget().entrySet()){
				replaceTarget.put(entry.getKey(), entry.getValue());
			}

			List<PreparedStatement> prepStatementTarget = new ArrayList<PreparedStatement>();
			List<CallableStatement> callableStatementTarget = new ArrayList<CallableStatement>();
			List<Statement> statementTarget = new ArrayList<Statement>();
			Object result = null;
			tryAgain = false;
			// this connection is dead
			con.setInReplayMode(true); // don't go in a loop of saving our saved log!
			try{
				con.clearStatementCaches(true);
				con.getInternalConnection().close();
			} catch(Throwable t){
				// do nothing - also likely to fail here
			}
			try{
				con.setInternalConnection(memorize(con.getPool().obtainInternalConnection(con), con));
			} catch(SQLException e){
				throw con.markPossiblyBroken(e);
			}

			con.getOriginatingPartition().trackConnectionFinalizer(con); // track this too.

			for (ReplayLog replay: oldReplayLog){

				// we got new connections/statement handles so replace what we've got with the new ones
				if (replay.getTarget() instanceof Connection){
					replaceTarget.put(replay.getTarget(), con.getInternalConnection());
				}  else if (replay.getTarget() instanceof CallableStatement){
					if (replaceTarget.get(replay.getTarget()) == null){
						replaceTarget.put(replay.getTarget(), callableStatementTarget.remove(0));
					}
				} else if (replay.getTarget() instanceof PreparedStatement){
					if (replaceTarget.get(replay.getTarget()) == null){
						replaceTarget.put(replay.getTarget(), prepStatementTarget.remove(0));
					}
				}else if (replay.getTarget() instanceof Statement){
					if (replaceTarget.get(replay.getTarget()) == null){
						replaceTarget.put(replay.getTarget(), statementTarget.remove(0));
					}
				}


				try {
					// run again using the new connection/statement
					//					result = replay.getMethod().invoke(, replay.getArgs());
					result = runWithPossibleProxySwap(replay.getMethod(), replaceTarget.get(replay.getTarget()), replay.getArgs());

					// remember what we've got last 
					recoveryResult.setResult(result);

					// if we got a new statement (eg a prepareStatement call), save it, we'll use it for our search/replace
					if (result instanceof CallableStatement){
						callableStatementTarget.add((CallableStatement)result);
					} else if (result instanceof PreparedStatement){
						prepStatementTarget.add((PreparedStatement)result);
					} else if (result instanceof Statement){
						statementTarget.add((Statement)result);
					}  
				} catch (Throwable t) {
					// It blew up again, let's try a couple more times before giving up...
					// call the hook, if available.
					if (connectionHook != null){
						tryAgain = connectionHook.onAcquireFail(t, acquireConfig);
					} else {

						logger.error("Failed to replay transaction. Sleeping for "+acquireRetryDelay+"ms and trying again. Attempts left: "+acquireRetryAttempts+". Exception: "+t.getCause() + " Message:"+t.getMessage());

						try {
							Thread.sleep(acquireRetryDelay);
							if (acquireRetryAttempts > 0){
								tryAgain = (--acquireRetryAttempts) != 0;
							}
						} catch (InterruptedException e) {
							tryAgain=false;
						}
					}
					if (!tryAgain){
						failedThrowable = t;
					}
					break;
				}
			}
		} while (tryAgain);

		// fill last successful results
		for (Entry<Object, Object> entry: replaceTarget.entrySet()){
			recoveryResult.getReplaceTarget().put(entry.getKey(), entry.getValue());
		}

		for (ReplayLog replay: oldReplayLog){
			replay.setTarget(replaceTarget.get(replay.getTarget())); // fix our log
		}

		if (failedThrowable != null){
			throw PoolUtil.generateSQLException(failedThrowable.getMessage(), failedThrowable);
		}


		return recoveryResult;
	}

} 

 其实就是类似于录影功能,通过代理方式,重新播放一次操作,有没有觉得其实我们可以把类似的想法放到我们开发中,诸如:短信邮件的发送、重复性动作的模仿操作......

 

分享到:
评论

相关推荐

    BoneCP数据源应用

    BoneCP 数据源是一种高效、快速的数据连接池技术,它被设计用于提高应用程序处理数据库连接的性能和效率。在Java环境中,数据库连接池是管理数据库连接的关键组件,它减少了创建和销毁连接的开销,从而提升了整体...

    bonecp-0.8.0.RELEASE.jar

    BoneCP 是一个高效的开源连接池实现,主要用于Java应用程序中数据库连接的管理。它提供了一种高效、可配置的方式来管理和复用数据库连接,从而提高应用程序的性能和稳定性。在标题中提到的 "bonecp-0.8.0.RELEASE....

    bonecp连接池demo

    BoneCP是一个轻量级的Java数据库连接池,它在设计时考虑了性能和资源的高效利用,特别适合于高并发的Web应用环境。本篇将详细介绍BoneCP连接池的使用和核心概念,以及如何通过提供的示例代码进行配置和集成。 ...

    bonecp 0.7.1 jar包以及源码

    BoneCP是一款高效的、开源的Java数据库连接池(JDBC Connection Pool)框架,它在性能上优于其他同类连接池,如C3P0、DBCP等。这个标题提及的是"bonecp 0.7.1 jar包以及源码",这意味着我们拥有 BoneCP 的特定版本...

    bonecp0.7 所有jar包

    BoneCP是一款轻量级的Java数据库连接池,它以其高效性能和简洁的API而受到开发者们的欢迎。在Java应用程序中,数据库连接池是至关重要的组件,它可以有效地管理和复用数据库连接,减少创建和销毁连接的开销,提高...

    BoneCP 连接池学习笔记

    ** BoneCP 连接池学习笔记 ** BoneCP 是一个高效、轻量级的 Java 数据库连接池(JDBC Connection Pool)实现。它在性能上优于其他同类连接池,如 C3P0 和 DBCP,尤其适用于高并发场景。在深入理解 BoneCP 之前,...

    bonecp数据库连接池jar包

    bonecp数据库连接池jar包0.7.1: bonecp-0.7.1.RELEASE.jar bonecp-provider-0.7.1-rc2.jar bonecp-spring-0.7.1.RELEASE.jar

    BoneCP(连接oracle例子+jar包)

    BoneCP是一款高效的、开源的Java数据库连接池(JDBC Connection Pool)框架,它在性能上优于其他同类连接池,如C3P0和DBCP。本资料提供了使用BoneCP连接Oracle数据库的实例以及所需的jar包,帮助开发者快速理解和...

    Bonecp参数配置.doc

    ### BoneCP参数配置详解 BoneCP,全称Bone Connection Pool,是Java环境下一款高效、轻量级的数据库连接池解决方案,特别适用于高并发环境。它通过优化连接管理和资源分配策略,能够显著提升数据库访问效率,降低...

    bonecp 相关所有jar包以及连接demo

    BoneCP 是一个高性能的 JDBC 连接池,它在 Java 应用中被广泛使用,以提高数据库连接的效率和管理。在这个压缩包中,包含了 BoneCP 的所有必需的 JAR 包,以及一个连接示例(demo),帮助开发者快速理解和使用 ...

    BoneCP的xml使用实例

    BoneCP是一种高效的、开源的Java连接池实现,它旨在提供比其他常见的数据库连接池如C3P0和DBCP更高的性能。在这个实例中,我们将学习如何通过XML配置文件来使用BoneCP,以及如何在Java代码中加载这个配置。 首先,...

    BoneCP所需依赖包

    BoneCP是一种高效的、轻量级的Java数据库连接池(JDBC Connection Pool)实现,它旨在提供比其他连接池更快的性能。在这个“BoneCP所需依赖包”中,包含了使用BoneCP时必要的第三方库,让我们逐一解析这些依赖: 1....

    bonecp相关所有jar包

    BoneCP是一款轻量级的Java数据库连接池(JDBC Connection Pool)框架,它以其高效、易用性而受到开发者的欢迎。在这个压缩包文件中,包含了BoneCP运行所需的多个核心库,下面将对这些库进行详细解读。 1. **bonecp-...

    bonecp-0.7.0.jar

    bonecp-0.7.0.jar bonecp-0.7.0.jar bonecp-0.7.0.jar bonecp-0.7.0.jar bonecp-0.7.0.jar bonecp-0.7.0.jar bonecp-0.7.0.jar bonecp-0.7.0.jar

    bonecp连接池

    在用C3P0数据连接池的时候,一旦并发上来就坑不住了,因为C3P0存在...在Hibernate中使用BoneCP除了需要上面提到的jar包之外,还需要下载一个名为bonecp-provider-0.7.0.jar的bonecp-provider的jar包,它的下载位置是:...

    Bonecp实例

    《BoneCP:一款高效数据库连接池的深度解析》 BoneCP是一款开源的、高性能的Java数据库连接池。在深入理解 BoneCP之前,我们先要明白数据库连接池的基本概念。数据库连接池是在应用服务器启动时创建的,它可以提供...

    连接池bonecp-0.8.1

    bonecp-0.8.1-20131105.191813-1.jar bonecp-jdk-compat-0.8.1-20131105.191752-1.jar bonecp-provider-0.8.1-20131105.191842-1.jar slf4j-api-1.7.7.jar slf4j-log4j12-1.7.7.jar

    BoneCP数据源jar包及配置

    骨CP(BoneCP)是一种高性能、轻量级的Java数据库连接池(JDBC Connection Pool)实现,被设计成能够提供比其他同类连接池更高的效率。它由Jeffrey Resnick开发,旨在解决传统连接池在并发处理上的性能问题。在本文...

    BoneCp连接池详解及和Hibernate配置(当今最快的连接池)

    BoneCP是一款高效的数据库连接池,它以其出色的性能和稳定性赢得了开发者们的青睐。相较于其他常见的连接池如c3p0,BoneCP宣称能提供超过25倍的性能提升,这对于高并发、大数据量的Web应用来说尤其重要。在本文中,...

    数据库连接池BoneCP源码分析报告

    BoneCP是一款高效、轻量级的数据库连接池实现,它以其优秀的性能和灵活的配置赢得了开发者们的青睐。这篇源码分析报告将深入探讨BoneCP的工作原理、核心功能以及其在实际应用中的优化策略。 首先,我们要理解...

Global site tag (gtag.js) - Google Analytics