/**
* Copyright 1999-2011 Alibaba Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cobar.client.transaction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.sql.DataSource;
import org.apache.commons.lang.Validate;
import org.slf4j.Logger;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import com.alibaba.cobar.client.datasources.ICobarDataSourceService;
/**
* use {@link org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy} to wrap all of the data sources we
* may use in TransactionManager and DAOs. {@link org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy}
* will only fetch a connection when first statement get executed. So even we
* start transaction on such data sources which are wrapped by
* {@link org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy}, there is no performance penalty at
* not.
*
*
*
* @author fujohnwang
* @since 1.0, Jan 28, 2010
*/
public class MultipleDataSourcesTransactionManager extends
AbstractPlatformTransactionManager implements InitializingBean {
protected transient Logger logger = org.slf4j.LoggerFactory
.getLogger(MultipleDataSourcesTransactionManager.class);
private static final long serialVersionUID = 4712923770419532385L;
private ICobarDataSourceService cobarDataSourceService;
private List<PlatformTransactionManager> transactionManagers = new ArrayList<PlatformTransactionManager>();
@Override
protected Object doGetTransaction() throws TransactionException {
return new ArrayList<DefaultTransactionStatus>();
}
/**
* We need to disable transaction synchronization so that the shared
* transaction synchronization state will not collide with each other. BUT,
* for LOB creators to use, we have to pay attention here:
* <ul>
* <li>if the LOB creator use standard preparedStatement methods, this
* transaction synchronization setting is OK;</li>
* <li>if the LOB creator don't use standard PS methods, you have to find
* other way to make sure the resources your LOB creator used should be
* cleaned up after the transaction.</li>
* </ul>
*/
@Override
protected void doBegin(Object transactionObject,
TransactionDefinition transactionDefinition)
throws TransactionException {
@SuppressWarnings("unchecked")
List<DefaultTransactionStatus> list = (List<DefaultTransactionStatus>) transactionObject;
for (PlatformTransactionManager transactionManager : transactionManagers) {
DefaultTransactionStatus element = (DefaultTransactionStatus) transactionManager
.getTransaction(transactionDefinition);
list.add(element);
}
}
@Override
protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
@SuppressWarnings("unchecked")
List<DefaultTransactionStatus> list =
(List<DefaultTransactionStatus>) status.getTransaction();
logger.info("prepare to commit transactions on multiple data sources.");
Validate.isTrue(list.size() <= this.getTransactionManagers().size());
TransactionException lastException = null;
for(int i=list.size()-1; i>=0;i--){
PlatformTransactionManager transactionManager=this.getTransactionManagers().get(i);
TransactionStatus localTransactionStatus=list.get(i);
try{
transactionManager.commit(localTransactionStatus);
}
catch (TransactionException e) {
lastException=e;
logger.error("Error in commit", e);
}
}
if (lastException != null) {
throw lastException;
// Rollback will ensue as long as rollbackOnCommitFailure=true
}
}
@Override
protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
@SuppressWarnings("unchecked")
List<DefaultTransactionStatus> list =
(List<DefaultTransactionStatus>) status.getTransaction();
logger.info("prepare to rollback transactions on multiple data sources.");
Validate.isTrue(list.size() <= this.getTransactionManagers().size());
TransactionException lastException = null;
for(int i=list.size()-1; i>=0; i--){
PlatformTransactionManager transactionManager=this.getTransactionManagers().get(i);
TransactionStatus localTransactionStatus=list.get(i);
try {
transactionManager.rollback(localTransactionStatus);
} catch (TransactionException e) {
// Log exception and try to complete rollback
lastException = e;
logger.error("error occured when rolling back the transaction. \n{}",e);
}
}
if (lastException != null) {
throw lastException;
}
}
public void setCobarDataSourceService(
ICobarDataSourceService cobarDataSourceService) {
this.cobarDataSourceService = cobarDataSourceService;
}
public ICobarDataSourceService getCobarDataSourceService() {
return cobarDataSourceService;
}
public void afterPropertiesSet() throws Exception {
Validate.notNull(cobarDataSourceService);
for (DataSource dataSource : getCobarDataSourceService()
.getDataSources().values()) {
PlatformTransactionManager txManager = this.createTransactionManager(dataSource);
getTransactionManagers().add(txManager);
}
//Collections.reverse(getTransactionManagers());
}
protected PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
public List<PlatformTransactionManager> getTransactionManagers() {
return transactionManagers;
}
}