最近项目中用到了多数据源,同时或者不同场景下操作不同的数据库,故而总结此篇文章。
JOTM (Java Open Transaction Manager)是由ObjectWeb协会开发的功能完整的且资源开放的独立的事务管理器。完全分布式事务支持.如果数据层、业务层、表示层运行在不同的 JVM 上,则有可能有一个全程的事务跨度这些JVM,事务的内容在 RMI/JRMP 和 RMI/IIOP 上传播。并且Spring对JOTM提供了较好的支持。
#oracle
portal.jdbc.driverClass=oracle.jdbc.driver.OracleDriver
portal.jdbc.jdbcUrl=jdbc:oracle:thin:@1.1.1.1:1521:orapub
portal.jdbc.user=testuser
portal.jdbc.password=123456
#JOTM分布式数据源连接池
#最小连接数
jdbc.minPoolSize=0
#最大连接数
jdbc.maxPoolSize=200
#PoolKeeper检测时间间隔
jdbc.sleepTime=120
#连接生命周期(上次访问时间-当前时间)
jdbc.lifeTime=120
#超过最大连接之后的调用getConnection的等待时间
jdbc.deadLockMaxWait=120
#超过最大连接之后的调用getConnection等待,在等待中重试的时间间隔
jdbc.deadLockRetryWait=100
<!-- JOTM实例 : JotmFactoryBean实现FactoryBean, DisposableBean -->
<bean id="jotm" class="com.utils.jotmutil.JotmFactoryBean">
<property name="defaultTimeout" value="500000"/>
</bean>
<bean id="myJtaManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="userTransaction">
<ref local="jotm"/>
</property>
</bean>
<!-- 通过扫描的模式,扫描目录在com/hoo/任意目录下的mapper目录下,所有的mapper都需要继承SqlMapper接口的接口 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.hoo.**.mapper" />
<property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate"/>
</bean>
<!-- 配置自定义的SqlSessionTemplate模板,注入相关配置,CustomSqlSessionTemplate继承SqlSessionTemplate重写相关方法 -->
<bean id="sqlSessionTemplate" class="com.utils.jotmutil.CustomSqlSessionTemplate">
<constructor-arg ref="portalSqlSessionFactory" />
<property name="targetSqlSessionFactorys">
<map> </map>
</property>
</bean>
<!-- 配置DataSource数据源 -->
<!-- 数据源portal 常用数据库 -->
<bean id="portal" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource" destroy-method="shutdown">
<property name="dataSource">
<bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown">
<property name="transactionManager" ref="jotm"/>
<property name="driverName" value="${portal.jdbc.driverClass}"/>
<property name="url" value="${portal.jdbc.jdbcUrl}"/>
</bean>
</property>
<property name="user" value="${portal.jdbc.user}"/>
<property name="password" value="${portal.jdbc.password}"/>
<property name="minSize" value="${jdbc.minPoolSize}"/>
<property name="maxSize" value="${jdbc.maxPoolSize}"/>
<property name="sleepTime" value="${jdbc.sleepTime}"/>
<property name="lifeTime" value="${jdbc.lifeTime}"/>
<property name="deadLockMaxWait" value="${jdbc.deadLockMaxWait}"/>
<property name="deadLockRetryWait" value="${jdbc.deadLockRetryWait}"/>
</bean>
<!--创建sqlSessionFactory -->
<bean id="portalSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="portal"/>
<property name="mapperLocations" value="classpath*:com/hoo/**/*.xml" />
</bean>
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" >
<ref bean="portal"/>
</property>
</bean>
<!-- DynamicCreateDataSourceBean动态创建数据源 -->
<bean id="applicationEventListener" class="com.common.core.DynamicCreateDataSourceBean">
<property name="jdbcTemplate" ref="jdbcTemplate" ></property>
<property name="sqlSessionTemplate" ref="sqlSessionTemplate" ></property>
</bean >
<!-- 事务切面配置 -->
<aop:config>
<aop:pointcut id="serviceOperation" expression="execution(* com.service.*.*(..))"/>
<aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>
</aop:config>
<!-- 通知配置 -->
<tx:advice id="txAdvice" transaction-manager="myJtaManager">
<tx:attributes>
<tx:method name="del*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" no-rollback-for="java.lang.RuntimeException" />
<tx:method name="insert*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="update*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="add*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="modify*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="correction*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="find*" propagation="SUPPORTS" />
<tx:method name="query*" propagation="SUPPORTS" />
<tx:method name="get*" propagation="SUPPORTS" />
<tx:method name="select*" propagation="SUPPORTS" />
<tx:method name="list*" propagation="SUPPORTS" />
<tx:method name="*" propagation="SUPPORTS" />
</tx:attributes>
</tx:advice>
public class DynamicCreateDataSourceBean implements ApplicationContextAware,
ApplicationListener<ApplicationEvent> {
private static final String DBFILE_PATH = "/jdbc.properties";
private ConfigurableApplicationContext app;
private JdbcTemplate jdbcTemplate;
private CustomSqlSessionTemplate sqlSessionTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void setSqlSessionTemplate(CustomSqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
@Override
public void setApplicationContext(ApplicationContext app)
throws BeansException {
this.app = (ConfigurableApplicationContext)app;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
// 如果是容器刷新事件OR Start Event
if (event instanceof ContextRefreshedEvent) {
try {
regDynamicBean();
} catch (IOException e) {
e.printStackTrace();
}
// System.out.println(event.getClass().getSimpleName()+" 事件已发生!");
}
}
private void regDynamicBean() throws IOException {
// 解析属性文件,得到数据源Map
Map<String, DbConfig> mapCustom = parsePropertiesFile();
// 把数据源bean注册到容器中
addSqlSessionFactorySourceToApp(mapCustom);
}
/**
* 功能说明:GET ALL SM_STATIONS FROM DB1 多个数据源配置账号相关存在数据库中,此方法获取多数据源配置
* 本次用id_company来标识不同数据源
* @return
* @throws IOException
*/
@SuppressWarnings("rawtypes")
private Map<String, DbConfig> parsePropertiesFile()
throws IOException {
String sql = "SELECT ID_COMPANY,DRIVERCLASS,JDBCURL,USERNAME,PASSWORD FROM CRS_DB_CONFIG WHERE IS_DEL=0 AND IS_ENABLED=10091020";
List list = jdbcTemplate.queryForList(sql);
Iterator iterator = list.iterator();
Map<String, DbConfig> mds = new HashMap<String, DbConfig>();
while (iterator.hasNext()) {
Map map4station = (Map) iterator.next();
DbConfig dsi = new DbConfig();
dsi.setIdCompany((String)map4station.get("ID_COMPANY"));
dsi.setDriverclass((String)map4station.get("DRIVERCLASS"));
dsi.setJdbcurl((String)map4station.get("JDBCURL"));
dsi.setUsername((String)map4station.get("USERNAME"));
dsi.setPassword((String)map4station.get("PASSWORD"));
mds.put(dsi.getIdCompany(), dsi);
}
return mds;
}
private void addSqlSessionFactorySourceToApp(Map<String, DbConfig> mapCustom){
DefaultListableBeanFactory acf = (DefaultListableBeanFactory) app.getAutowireCapableBeanFactory();
PropertyUtil propertyUtil = new PropertyUtil();
String minSize = propertyUtil.getProperty(DBFILE_PATH, "jdbc.minPoolSize");
String maxSize = propertyUtil.getProperty(DBFILE_PATH, "jdbc.maxPoolSize");
String sleepTime = propertyUtil.getProperty(DBFILE_PATH, "jdbc.sleepTime");
String lifeTime = propertyUtil.getProperty(DBFILE_PATH, "jdbc.lifeTime");
String deadLockMaxWait = propertyUtil.getProperty(DBFILE_PATH, "jdbc.deadLockMaxWait");
String deadLockRetryWait = propertyUtil.getProperty(DBFILE_PATH, "jdbc.deadLockRetryWait");
String DATASOURCE_BEAN_CLASS = "org.enhydra.jdbc.pool.StandardXAPoolDataSource";
BeanDefinitionBuilder bdb;
BeanDefinitionBuilder bdb_child;
BeanDefinitionBuilder bdb_ssf;
Iterator<String> iter = mapCustom.keySet().iterator();
Map<Object, SqlSessionFactory> targetSqlSessionFactorys = new LinkedHashMap<Object, SqlSessionFactory>();
// 将默认SqlSessionFactory放入 targetSqlSessionFactorys map中
targetSqlSessionFactorys.put("portal", (SqlSessionFactory)app.getBean("portalSqlSessionFactory"));
while (iter.hasNext()) {
// bean ID
String beanKey = iter.next();
// 创建连接池bean
bdb = BeanDefinitionBuilder.rootBeanDefinition(DATASOURCE_BEAN_CLASS);
bdb.getBeanDefinition().setAttribute("id", beanKey);
bdb.getBeanDefinition().setAttribute("destroy-method", "shutdown");
//创建数据源
bdb_child = BeanDefinitionBuilder.rootBeanDefinition("org.enhydra.jdbc.standard.StandardXADataSource");
bdb_child.getBeanDefinition().setAttribute("destroy-method", "shutdown");
bdb_child.addPropertyValue("transactionManager", app.getBean("jotm"));
bdb_child.addPropertyValue("driverName", mapCustom.get(beanKey).getDriverclass());
bdb_child.addPropertyValue("url", mapCustom.get(beanKey).getJdbcurl());
acf.registerBeanDefinition("ds_child" + beanKey, bdb_child.getBeanDefinition());
bdb.addPropertyValue("dataSource", app.getBean("ds_child" + beanKey));
bdb.addPropertyValue("user", mapCustom.get(beanKey).getUsername());
bdb.addPropertyValue("password", mapCustom.get(beanKey).getPassword());
bdb.addPropertyValue("minSize", Integer.parseInt(minSize));
bdb.addPropertyValue("maxSize", Integer.parseInt(maxSize));
bdb.addPropertyValue("sleepTime", Integer.parseInt(sleepTime));
bdb.addPropertyValue("lifeTime", Integer.parseInt(lifeTime));
bdb.addPropertyValue("deadLockMaxWait", Integer.parseInt(deadLockMaxWait));
bdb.addPropertyValue("deadLockRetryWait", Integer.parseInt(deadLockRetryWait));
// 注册连接池bean
acf.registerBeanDefinition("ds" + beanKey, bdb.getBeanDefinition());
//创建SqlSessionFactory
bdb_ssf = BeanDefinitionBuilder.rootBeanDefinition("org.mybatis.spring.SqlSessionFactoryBean");
bdb_ssf.getBeanDefinition().setAttribute("id", "sqlSessionFactory" + beanKey);
bdb_ssf.addPropertyValue("dataSource", bdb.getBeanDefinition());
bdb_ssf.addPropertyValue("mapperLocations", "classpath*:com/**/*.xml");
//注册SqlSessionFactory
acf.registerBeanDefinition("sqlSessionFactory" + beanKey, bdb_ssf.getBeanDefinition());
targetSqlSessionFactorys.put(beanKey, (SqlSessionFactory)app.getBean("sqlSessionFactory" + beanKey));
}
sqlSessionTemplate.setTargetSqlSessionFactorys(targetSqlSessionFactorys);
sqlSessionTemplate.setDefaultTargetSqlSessionFactory((SqlSessionFactory)app.getBean("portalSqlSessionFactory"));
}
}
/**
* <b>function:</b> 继承SqlSessionTemplate 重写相关方法
*/
public class CustomSqlSessionTemplate extends SqlSessionTemplate {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
private Map<Object, SqlSessionFactory> targetSqlSessionFactorys;
private SqlSessionFactory defaultTargetSqlSessionFactory;
public void setTargetSqlSessionFactorys(Map<Object, SqlSessionFactory> targetSqlSessionFactorys) {
this.targetSqlSessionFactorys = targetSqlSessionFactorys;
}
public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {
this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;
}
public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration()
.getEnvironment().getDataSource(), true));
}
public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
super(sqlSessionFactory, executorType, exceptionTranslator);
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
this.defaultTargetSqlSessionFactory = sqlSessionFactory;
}
@Override
public SqlSessionFactory getSqlSessionFactory() {
SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(CustomerContextHolder.getContextType());
if (targetSqlSessionFactory != null) {
return targetSqlSessionFactory;
} else if (defaultTargetSqlSessionFactory != null) {
return defaultTargetSqlSessionFactory;
} else {
Assert.notNull(targetSqlSessionFactorys, "Property 'targetSqlSessionFactorys' or 'defaultTargetSqlSessionFactory' are required");
Assert.notNull(defaultTargetSqlSessionFactory, "Property 'defaultTargetSqlSessionFactory' or 'targetSqlSessionFactorys' are required");
}
return this.sqlSessionFactory;
}
@Override
public Configuration getConfiguration() {
return this.getSqlSessionFactory().getConfiguration();
}
public ExecutorType getExecutorType() {
return this.executorType;
}
public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
return this.exceptionTranslator;
}
/**
* {@inheritDoc}
*/
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.<T> selectOne(statement);
}
/**
* {@inheritDoc}
*/
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.<T> selectOne(statement, parameter);
}
/**
* {@inheritDoc}
*/
public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
return this.sqlSessionProxy.<K, V> selectMap(statement, mapKey);
}
/**
* {@inheritDoc}
*/
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey);
}
/**
* {@inheritDoc}
*/
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey, rowBounds);
}
/**
* {@inheritDoc}
*/
public <E> List<E> selectList(String statement) {
return this.sqlSessionProxy.<E> selectList(statement);
}
/**
* {@inheritDoc}
*/
public <E> List<E> selectList(String statement, Object parameter) {
return this.sqlSessionProxy.<E> selectList(statement, parameter);
}
/**
* {@inheritDoc}
*/
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return this.sqlSessionProxy.<E> selectList(statement, parameter, rowBounds);
}
/**
* {@inheritDoc}
*/
public void select(String statement, ResultHandler handler) {
this.sqlSessionProxy.select(statement, handler);
}
/**
* {@inheritDoc}
*/
public void select(String statement, Object parameter, ResultHandler handler) {
this.sqlSessionProxy.select(statement, parameter, handler);
}
/**
* {@inheritDoc}
*/
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
}
/**
* {@inheritDoc}
*/
public int insert(String statement) {
return this.sqlSessionProxy.insert(statement);
}
/**
* {@inheritDoc}
*/
public int insert(String statement, Object parameter) {
return this.sqlSessionProxy.insert(statement, parameter);
}
/**
* {@inheritDoc}
*/
public int update(String statement) {
return this.sqlSessionProxy.update(statement);
}
/**
* {@inheritDoc}
*/
public int update(String statement, Object parameter) {
return this.sqlSessionProxy.update(statement, parameter);
}
/**
* {@inheritDoc}
*/
public int delete(String statement) {
return this.sqlSessionProxy.delete(statement);
}
/**
* {@inheritDoc}
*/
public int delete(String statement, Object parameter) {
return this.sqlSessionProxy.delete(statement, parameter);
}
/**
* {@inheritDoc}
*/
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
/**
* {@inheritDoc}
*/
public void commit() {
throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
}
/**
* {@inheritDoc}
*/
public void commit(boolean force) {
throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
}
/**
* {@inheritDoc}
*/
public void rollback() {
throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
}
/**
* {@inheritDoc}
*/
public void rollback(boolean force) {
throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
}
/**
* {@inheritDoc}
*/
public void close() {
throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
}
/**
* {@inheritDoc}
*/
public void clearCache() {
this.sqlSessionProxy.clearCache();
}
/**
* {@inheritDoc}
*/
public Connection getConnection() {
return this.sqlSessionProxy.getConnection();
}
/**
* {@inheritDoc}
* @since 1.0.2
*/
public List<BatchResult> flushStatements() {
return this.sqlSessionProxy.flushStatements();
}
/**
* Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
* unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to
* the {@code PersistenceExceptionTranslator}.
*/
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final SqlSession sqlSession = getSqlSession(
CustomSqlSessionTemplate.this.getSqlSessionFactory(),
CustomSqlSessionTemplate.this.executorType,
CustomSqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory())) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (CustomSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
Throwable translated = CustomSqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
closeSqlSession(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory());
}
}
}
}
/**
* <b>function:</b> 多数据源
*/
public abstract class CustomerContextHolder {
public final static String SESSION_FACTORY_PORTAL1 = "portal1";
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setContextType(String contextType) {
contextHolder.set(contextType);
}
public static String getContextType() {
return contextHolder.get();
}
public static void clearContextType() {
contextHolder.remove();
}
}
使用数据库之前先将id_company放到线程中,等提交事务获取数据库连接时,会从线程中根据id_company拿到想要的链接。