项目采用的是struts2+spring+ibatis架构,下面是关键部分代码:
applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd" default-autowire="byName" default-lazy-init="false"> <context:component-scan base-package="com.ssi.*" /> <!-- 属性文件读入 --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath*:jdbc.properties</value> </list> </property> </bean> <!-- 配置sqlMapclient --> <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="configLocation" value="classpath:ibatis-sqlmap-config.xml" /> <property name="dataSource" ref="dataSource" /> </bean> <bean id="sqlMapClient1" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="configLocation" value="classpath:ibatis-sqlmap-config.xml" /> <property name="dataSource" ref="db1" /> </bean> <bean id="sqlMapClient2" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="configLocation" value="classpath:ibatis-sqlmap-config.xml" /> <property name="dataSource" ref="db2" /> </bean> <bean id="sqlMapClientCenter" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="configLocation" value="classpath:ibatis-sqlmap-config.xml" /> <property name="dataSource" ref="center" /> </bean> <bean id="dynamicSqlMapClientDaoSupport" class="com.ssi.dao.DynamicSqlClientDaoSupport"> <property name="targetSqlMapClients"> <map> <entry key="db1" value-ref="sqlMapClient1" /> <entry key="db2" value-ref="sqlMapClient2" /> <entry key="center" value-ref="sqlMapClientCenter" /> </map> </property> <property name="defaultSqlMapClient" ref="sqlMapClientCenter" /> </bean> <bean id="ibatisDaoSupport" class="com.ssi.dao.IbatisDaoSupport" parent="dynamicSqlMapClientDaoSupport"></bean> <bean id="userDao" class="com.ssi.dao.impl.UserDaoImpl" parent="ibatisDaoSupport"></bean> <!-- 支持 @AspectJ 标记--> <aop:aspectj-autoproxy proxy-target-class="true"/> <!-- 配置JTA的事务管理器 --> <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close"> <property name="forceShutdown" value="true" /> </bean> <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp"> <property name="transactionTimeout" value="300" /> </bean> <bean id="springTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManager" ref="atomikosTransactionManager" /> <property name="userTransaction" ref="atomikosUserTransaction" /> </bean> <!-- 配置通知 --> <tx:advice id="txAdvice" transaction-manager="springTransactionManager"> <tx:attributes> <tx:method name="*" rollback-for="Exception,RuntimeException,com.ssi.exception.SystemException" propagation="REQUIRED" /> </tx:attributes> </tx:advice> <!-- 以AspectJ方式 定义 AOP --> <aop:config> <aop:advisor pointcut="execution(* com.ssi.service..*Service*.*(..))" advice-ref="txAdvice" /> </aop:config> <!-- spring 定时器任务开始 --> <bean name="job" class="org.springframework.scheduling.quartz.JobDetailBean"> <property name="jobClass"> <value>com.ssi.action.TimerAction</value> </property> <property name="jobDataAsMap"> <map> <!-- timeout属性设定了当服务器启动后过10秒钟首次调用你的JobAction --> <entry key="timeout"> <value>10</value> </entry> </map> </property> </bean> <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail"> <ref bean="job"/> </property> <property name="cronExpression"> <value>0 53 15 ? * MON-FRI</value> </property> </bean> <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean" autowire="no"> <property name="triggers"> <list> <ref local="cronTrigger"/> </list> </property> </bean> <!-- spring 定时器任务结束 --> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!--指定Spring配置中用到的属性文件--> <bean id="propertyConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:jdbc.properties</value> </list> </property> </bean> <!-- JTA 数据源配置 --> <bean id="center" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName"> <value>mysql/center</value> </property> <property name="xaDataSourceClassName"> <value>${jta.driver.className}</value> </property> <property name="xaProperties"> <props> <prop key="url">${center.jdbc.driver.url}</prop> <prop key="user">${center.sql.user.name}</prop> <prop key="password">${center.sql.user.password}</prop> </props> </property> <property name="testQuery" value="select 1" /> <property name="poolSize"> <value>${poolsize}</value> </property> <property name="maxPoolSize"> <value>${maxPoolSize}</value> </property> <property name="borrowConnectionTimeout"><value>${borrowConnectionTimeout}</value></property> </bean> <bean id="db1" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName"> <value>mysql/db1</value> </property> <property name="xaDataSourceClassName"> <value>${jta.driver.className}</value> </property> <property name="xaProperties"> <props> <prop key="url">${db1.jdbc.driver.url}</prop> <prop key="user">${company.sql.user.name}</prop> <prop key="password">${company.sql.user.password}</prop> </props> </property> <property name="testQuery" value="select 1" /> <property name="poolSize"> <value>${poolsize}</value> </property> <property name="maxPoolSize"> <value>${maxPoolSize}</value> </property> <property name="borrowConnectionTimeout"><value>${borrowConnectionTimeout}</value></property> </bean> <bean id="db2" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName"> <value>mysql/db2</value> </property> <property name="xaDataSourceClassName"> <value>${jta.driver.className}</value> </property> <property name="xaProperties"> <props> <prop key="url">${db2.jdbc.driver.url}</prop> <prop key="user">${company.sql.user.name}</prop> <prop key="password">${company.sql.user.password}</prop> </props> </property> <property name="testQuery" value="select 1" /> <property name="poolSize"> <value>${poolsize}</value> </property> <property name="maxPoolSize"> <value>${maxPoolSize}</value> </property> <property name="borrowConnectionTimeout"><value>${borrowConnectionTimeout}</value></property> </bean> <bean id="dataSource" class="com.ssi.datasource.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <entry key="db1" value-ref="db1" /> <entry key="db2" value-ref="db2" /> <entry key="center" value-ref="center" /> </map> </property> <property name="defaultTargetDataSource" ref="center" /> </bean> </beans>
DynamicSqlClientDaoSupport.java
package com.ssi.dao; import java.util.Map; import javax.sql.DataSource; import org.springframework.beans.factory.InitializingBean; import org.springframework.dao.support.DaoSupport; import org.springframework.orm.ibatis.SqlMapClientTemplate; import org.springframework.util.Assert; import com.ibatis.sqlmap.client.SqlMapClient; import com.ssi.datasource.DbContextHolder; public class DynamicSqlClientDaoSupport extends DaoSupport implements InitializingBean{ private SqlMapClientTemplate sqlMapClientTemplate = new SqlMapClientTemplate(); private Map<String,SqlMapClient> targetSqlMapClients; private SqlMapClient defaultSqlMapClient; private boolean externalTemplate = false; /** * Set the JDBC DataSource to be used by this DAO. * Not required: The SqlMapClient might carry a shared DataSource. * @see #setSqlMapClient */ public final void setDataSource(DataSource dataSource) { if (!this.externalTemplate) { this.sqlMapClientTemplate.setDataSource(dataSource); } } /** * Return the JDBC DataSource used by this DAO. */ public final DataSource getDataSource() { return this.sqlMapClientTemplate.getDataSource(); } /** * Set the iBATIS Database Layer SqlMapClient to work with. * Either this or a "sqlMapClientTemplate" is required. * @see #setSqlMapClientTemplate */ public final void setSqlMapClient(SqlMapClient sqlMapClient) { if (!this.externalTemplate) { this.sqlMapClientTemplate.setSqlMapClient(sqlMapClient); } } /** * Return the iBATIS Database Layer SqlMapClient that this template works with. */ public final SqlMapClient getSqlMapClient() { return this.sqlMapClientTemplate.getSqlMapClient(); } /** * Set the SqlMapClientTemplate for this DAO explicitly, * as an alternative to specifying a SqlMapClient. * @see #setSqlMapClient */ public final void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate) { Assert.notNull(sqlMapClientTemplate, "SqlMapClientTemplate must not be null"); this.sqlMapClientTemplate = sqlMapClientTemplate; this.externalTemplate = true; } /** * Return the SqlMapClientTemplate for this DAO, * pre-initialized with the SqlMapClient or set explicitly. */ public final SqlMapClientTemplate getSqlMapClientTemplate() { String dbtype = DbContextHolder.getDbType(); if(targetSqlMapClients!=null&&targetSqlMapClients.containsKey(dbtype)){ SqlMapClient sqlMapClient = targetSqlMapClients.get(dbtype); sqlMapClientTemplate = new SqlMapClientTemplate(sqlMapClient); } return this.sqlMapClientTemplate; } @Override protected final void checkDaoConfig() { if (!this.externalTemplate) { this.sqlMapClientTemplate.afterPropertiesSet(); } } public Map<String, SqlMapClient> getTargetSqlMapClients() { return targetSqlMapClients; } public void setTargetSqlMapClients(Map<String, SqlMapClient> targetSqlMapClients) { this.targetSqlMapClients = targetSqlMapClients; } public SqlMapClient getDefaultSqlMapClient() { return defaultSqlMapClient; } public void setDefaultSqlMapClient(SqlMapClient defaultSqlMapClient) { this.defaultSqlMapClient = defaultSqlMapClient; } }IbatisDaoSupport.java
package com.ssi.dao; import java.io.Serializable; import java.sql.SQLException; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.orm.ibatis.SqlMapClientCallback; import com.ibatis.sqlmap.client.SqlMapExecutor; @SuppressWarnings("unchecked") public class IbatisDaoSupport<Entity> extends DynamicSqlClientDaoSupport implements IEntityDao<Entity> { protected final Log log = LogFactory.getLog(getClass()); public Entity get(String sqlId, Serializable id) { return (Entity) getSqlMapClientTemplate().queryForObject(sqlId, id); } public Entity getByParamMap(String sqlId, Object param) { return (Entity) getSqlMapClientTemplate().queryForObject(sqlId, param); } public Object save(String sqlId, Object o) { return getSqlMapClientTemplate().insert(sqlId, o); } public Object batchSave(final String sqlId,final List<Entity> entityList) throws Exception{ // 执行回调 return getSqlMapClientTemplate().execute(new SqlMapClientCallback() { // 实现回调接口 public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException{ // 开始批处理 executor.startBatch(); for (Entity entity : entityList) { executor.insert(sqlId, entity); } return executor.executeBatch(); } }); } public Integer remove(String sqlId, Object o) { return getSqlMapClientTemplate().delete(sqlId, o); } public Integer removeById(String sqlId, Serializable id) { return getSqlMapClientTemplate().delete(sqlId, id); } public Integer update(String sqlId, Object o) { return getSqlMapClientTemplate().update(sqlId, o); } public Long totalCount(String sqlId, Object o){ return (Long) getSqlMapClientTemplate().queryForObject(sqlId, o); } public List<Entity> pagedList(String sqlId, Map<String, Object> map,int pageSize, int pageNum) { int start = (pageNum - 1) * pageSize; map.put("start", start); map.put("pageSize", pageSize); List<Entity> list = getSqlMapClientTemplate().queryForList(sqlId, map); return list; } public List<Entity> list(String sqlId,Object o){ return getSqlMapClientTemplate().queryForList(sqlId,o); } public List<Entity> list(String sqlId){ return getSqlMapClientTemplate().queryForList(sqlId); } }
UserDaoImpl.java:
package com.ssi.dao.impl; import org.springframework.stereotype.Repository; import com.ssi.dao.IUserDao; import com.ssi.dao.IbatisDaoSupport; import com.ssi.model.User; @Repository("userDao") public class UserDaoImpl extends IbatisDaoSupport<User> implements IUserDao { public Integer addUser(User user) throws Exception{ return (Integer) this.save("User.insert", user); } }
UserServiceImpl.java
package com.ssi.service.impl; import javax.annotation.Resource; import org.springframework.stereotype.Service; import com.ssi.dao.IUserDao; import com.ssi.datasource.DbContextHolder; import com.ssi.model.User; import com.ssi.service.IUserService; @Service("userService") public class UserServiceImpl implements IUserService { @Resource private IUserDao userDao; /** * 测试在service中切换数据源 异常是否回滚 */ public void addUser(User user) throws Exception{ DbContextHolder.setDbType("db1"); userDao.addUser(user); DbContextHolder.setDbType("db2"); user.setUserName("user2"); userDao.addUser(user); DbContextHolder.setDbType("center"); user.setUserName("user3"); userDao.addUser(user); //System.out.println(1/0); } }
DynamicDataSource.java:
public class DynamicDataSource extends AbstractRoutingDataSource { static Logger log = Logger.getLogger(DynamicDataSource.class); protected Object determineCurrentLookupKey() { return DbContextHolder.getDbType(); } }DbContextHolder.java:
public class DbContextHolder { private static final ThreadLocal contextHolder = new ThreadLocal(); public static void setDbType(String dbType) { contextHolder.set(dbType); } public static String getDbType() { return (String) contextHolder.get(); } public static void clearDbType() { contextHolder.remove(); } }
三个数据库:dbcenter、db1、db2 表结构均相同
脚本:
DROP TABLE IF EXISTS `tb_user`; CREATE TABLE `tb_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `userName` varchar(20) DEFAULT NULL, `password` varchar(60) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
单元测试:
public class JunitTest{ public ApplicationContext cxt; @Test public void init() throws Exception{ cxt = new ClassPathXmlApplicationContext(new String[] {"applicationContext.xml","applicationContext-datasource.xml"}); testInsertUser(); } private void testInsertUser() throws Exception{ IUserService userService = (IUserService)cxt.getBean("userService"); User user = new User(); user.setUserName("user1"); user.setPassword("0"); userService.addUser(user); } private void testInsertUser2() throws Exception{ }