Spring DataSource初窥

来源:互联网 发布:小照域名授权系统 编辑:程序博客网 时间:2024/06/08 10:55

        最近一直没时间去更新博客了,发现自己一不更新博客,去学习的动力好像就没了,要时刻提醒自己,当前自己只是扮演了一个功能型程序员的开发而已,不要以为自己在所谓的SSM和SSH框架下做点数据搬运,展示的工作就以为自己有多能干一样,需要去巩固的地方很多。不光是业务,也需要去提升自己对于当前框架的内部知识,以及java方面的知识的深层研究。


废话说多了,说一说最近遇到的一个问题吧,背景是这样的,系统中有个下载按钮,但是下载按钮在前端没有限制点击次数,也就是点击一次就能执行一次后台,这就有点麻烦了,并且打包操作啊什么的都是在后台服务器新建路径,写的不好就有并发问题,其实我对于并发不是很了解,也写不出那么好的并发代码,关于并发的知识的补充已经列在我的读书目录内,等读到的时候,再回头来看看吧。


现阶段对于并发问题的解决,项目要求没那么高,就是采用同步的策略,加个锁,这肯定不是最优的办法,但是系统的并发要求不高,所以项目经理没那么多要求。本以为加锁还不简单,在service方法中加一下不就好了?但是事实并没有那么简单。


说一下现在的执行逻辑,

downLoadZipFileAction --> downLoadFileService ,主要逻辑在这个service中,执行图片的读取,压缩操作,执行下载动作。

一开始想着,把synchronized 放在service实现类之前应该就可以吧, 就是放在 public void synchronized downLoadFileService,不知道其他人看见是什么反应,不过我是觉得ok啊,没什么问题啊,很正常的一个写法啊。但是我在调试时发现了问题,当我点击次数少时没问题,一切正常,能够执行下载,但是当我点击次数多了之后,大约在20次时,发现了问题,后台日志界面报错,connection time out,为什么会有connection time out呢?按理来说的话, service还未执行啊? 根本没进入service方法,哪儿来的connection呢?


首先肯定去看dataSource的配置了。发现当前系统中 maxWait的时间是 50s,后来想起来执行次数少时,肯定不到50s,那就不会出问题,次数多了,超过50s了,出了问题,

初步定为问题出现在这里。

以前一直说事务事务,怎么就忘了呢,贴图吧。trx中注入了dataSource,那么其实就是在执行过程中受到了事务的影响了?


但是,我在想 那个方法名是downLoad开头啊?并没有给它事务啊?为什么会受到了事务影响呢?在这边纳闷了半天,难道自己以前的理解全都错了。

突然看见<tx method> 最后一行,对于所有的方法都分配了一个read-only的只读事务,只怪自己,平时都是在框架里开发,写action,写service,写sql,事务都不需要自己关心,也看的少,连基本的意识都没有了,这边有个只读事务存在的,只读事务啊,只读事务里的方法不也是要执行sql查询嘛???????不照样用到了连接池啊,抽自己嘴巴啊。

突然发现自己对于Spring的事务获取以及dataSource获取自己竟然完全没有自己的理解啊,好像自己完全没有去看过,没有关注过,后怕啊。

赶紧去看吧,别放过任何一个研究的机会了,只怪自己以前太懒惰了。

至于Spring的事务是怎么样以Aop的方式去运行的,不去关注了,只关注程序运行期间事务是怎么去获取的。

以下就是TransactionInterceptor中的核心代码,事务拦截器。 txAttr 获取到的其实就是spring基于方法的配置时配置的何种事务,笔者项目中对于多有的方法都配置了一个默认的只读事务,那么这边获取的txAttr其实就是标注了当前是只读事务。这些debug就能看见是什么。

protected Object invokeWithinTransaction(Method method, Class targetClass, final InvocationCallback invocation)throws Throwable {// If the transaction attribute is null, the method is non-transactional.final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);final PlatformTransactionManager tm = determineTransactionManager(txAttr);final String joinpointIdentification = methodIdentification(method, targetClass);if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {// Standard transaction demarcation with getTransaction and commit/rollback calls.TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);Object retVal = null;try {// This is an around advice: Invoke the next interceptor in the chain.// This will normally result in a target object being invoked.retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// target invocation exceptioncompleteTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}commitTransactionAfterReturning(txInfo);return retVal;}

关注主要代码,分析其逻辑。里面最重要的还是这个create方法。这个方法其他地方暂时没什么需要关注的。主要去看get方法。

本项目使用的是DataSourceTransactionManager,所以进这个实现类去看下。

首先关注doBegin。doBegin的话会做一些预先操作。其他不关心,只关心Bind the session holder to the thread.,这个是最重要的,通过注释可以很清楚的知道,将dataSource绑定到当前线程上。

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());}}catch (Throwable ex) {DataSourceUtils.releaseConnection(con, this.dataSource);throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);}}
先关注这个方法:本质上维护了一个map格式的数据,存放在resource这个变量中,resource这个变量是个ThreadLocal形式的,也就是每个线程其实维护了各自的dataSource数据。那么就可以知道了,对于一次请求来说,对于这个线程来说的话,也就是一开始会获取一下dataSource,后面的用到sql的地方都是从以前绑定的ThreadLocal

public static void bindResource(Object key, Object value) throws IllegalStateException {Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);Assert.notNull(value, "Value must not be null");Map<Object, Object> map = resources.get();// set ThreadLocal Map if none foundif (map == null) {map = new HashMap<Object, Object>();resources.set(map);}Object oldValue = map.put(actualKey, value);// Transparently suppress a ResourceHolder that was marked as void...if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {oldValue = null;}if (oldValue != null) {throw new IllegalStateException("Already value [" + oldValue + "] 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() + "]");}}


再回到一开始获取dataSource的地方看下,其中的getResource就是在看当前是否已经存在过获取到的conHolder,对于这个conHolder,暂时不清楚存在的意义,以及维护的东西,不过后续的不管是加了什么事务配置的方法都会获取到当前线程中的这个conHolder

protected Object doGetTransaction() {DataSourceTransactionObject txObject = new DataSourceTransactionObject();txObject.setSavepointAllowed(isNestedTransactionAllowed());ConnectionHolder conHolder =(ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);txObject.setConnectionHolder(conHolder, false);return txObject;}

一旦获取到之前绑定在线程中的conHolder,那么就会进入HandleExiting流程中。而不会再进入doBegin流程,去做新事务的一些准备的流程中。

if (isExistingTransaction(transaction)) {// Existing transaction found -> check propagation behavior to find out how to behave.return handleExistingTransaction(definition, transaction, debugEnabled);}

执行完上面一系列的操作,最终回到那个一开始的TransactionAspectSupport中,会执行当前方法,proceedWithInvacation,执行完之后再执行清除cleanUp。

if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {// Standard transaction demarcation with getTransaction and commit/rollback calls.TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);Object retVal = null;try {// This is an around advice: Invoke the next interceptor in the chain.// This will normally result in a target object being invoked.retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// target invocation exceptioncompleteTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}commitTransactionAfterReturning(txInfo);return retVal;}


分析了上述一些列的流程,发现对于我之前遇到的问题,也就迎刃而解了,为什么在service方法前面配置 同步会超时呢?

其实对于那30几次请求来说的话,那就是30几个线程,每个线程再进入service方法之前都会去获取一个只读事务,对于每个只读事务的话会获取一个dataSource,各自维护各自的,碰巧的是同步加在了service上,对于事务根本没有控制住,虽然具体的逻辑没有得以执行,但是dataSource的获取确实全部获取到了,啼笑皆非了。再次抽自己一个嘴巴。

解决方法呢。我能想到的暂时是2个。

1.synchronized加在action层中,从源头杜绝到,保证每次请求到action停止。暴力不,,,hh。

2.第二种方法呢,我看了下这个service,其实都是只读,并没有什么需要改变数据库的。那么这个只读事务加了干嘛呢?因此在配置文件中对于这个方法可以配置成

<tx:method name="downLoadService" propagation="NOT_SUPPORTED" /> ,让它以非事务的方式运行,具体的事务获取呢等到service中需要sql读取的时候再去获取吧,这样就能保证每个请求被挡在了service外面,并且不执行任何事务有关方面的获取,也就避免dataSource的获取了。

我能想到的就这两个方法,至于有什么其他解决办法呢?我暂时不知道,各位看官要是有什么好的建议,可以帮帮忙。



0 0
原创粉丝点击