SSM多数据源配置
一、基本配置
1. 数据源配置
<?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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd"
default-autowire="byName">
<!-- db配置目录 -->
<context:property-placeholder file-encoding="utf-8" location="classpath:db.properties" />
<bean id="peisDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${peis.driver}" />
<property name="jdbcUrl" value="${peis.jdbcUrl}" />
<property name="user" value="${peis.user}" />
<property name="password" value="${peis.password}" />
</bean>
<bean id="mobileHealthDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${mobileHealth.driver}" />
<property name="jdbcUrl" value="${mobileHealth.jdbcUrl}" />
<property name="user" value="${mobileHealth.user}" />
<property name="password" value="${mobileHealth.password}" />
</bean>
</beans>
2. 定义一个类实现数据库切换的类
定义一个类DynamicDataSource
继承AbstractRoutingDataSource
实现determineCurrentLookupKey
方法,实现数据库切换。
DynamicDataSource.java
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 返回当前数据源的key值
*/
@Override
protected Object determineCurrentLookupKey() {
System.out.println("getDataSource: " + DynamicDataSourceHolder.getDatasource());
return DynamicDataSourceHolder.getDatasource();
}
}
-
说明:使用
Spring
的AbstractRoutingDataSource
类来进行拓展多数据源。
该类就相当于一个dataSource
的路由,用于根据key
值来进行切换对应的dataSource
。可以看到其中获取链接的方法getConnection()
调用的determineTargetDataSource
是关键方法。该方法用于返回我们使用的数据源。其中又是determineCurrentLookupKey()
方法来返回当前数据源的key值。之后通过该key值在resolvedDataSources
这个map中找到对应的value(该value就是数据源)。resolvedDataSources
这个map则是在:afterPropertiesSet()
这个方法中通过targetDataSources
这个map来进行赋值的。我们在配置文件中进行会对targetDataSources
赋值:
<bean id="dynamicDataSource" class="com.zhougl.mobilehealth.util.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="peisDataSource" value-ref="peisDataSource"></entry>
<entry key="mobileHealthDataSource" value-ref="mobileHealthDataSource"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="peisDataSource"></property>
</bean>
3. 定义动态切换数据源的工具类
DynamicDataSourceHolder.java
public class DynamicDataSourceHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
/**
* 参数为要切换的数据源名称
*
* @param dastsource
*/
public static void setDatasource(String datasource) {
System.out.println("setDataSource: " + datasource);
contextHolder.set(datasource);
}
/**
* 通过线程的方式获取数据源,目的是为了在并发的情况下依然能够正常切换
*
* @return 线程
*/
public static String getDatasource() {
return (String) contextHolder.get();
}
/**
* 用于关闭当前切换的数据源
*/
public static void clearDatasource() {
contextHolder.remove();
}
}
使用了ThreadLocal
来保存了数据源:解密ThreadLocal
4. 动态数据源、事务配置
<!-- 自动装配Bean -->
<context:component-scan base-package="com.zhougl"/>
<!-- 强制使用CGLIB代理 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<bean id="dynamicDataSource" class="com.zhougl.mobilehealth.util.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="peisDataSource" value-ref="peisDataSource"></entry>
<entry key="mobileHealthDataSource" value-ref="mobileHealthDataSource"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="peisDataSource"></property>
</bean>
<!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
<bean id="DynamicSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dynamicDataSource" />
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath*:com/**/*.mapper.xml"></property>
<!-- pagehelper分页插件拦截器 -->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<value>
helperDialect=oracle
reasonable=false
supportMethodsArguments=true
params=count=countSql
autoRuntimeDialect=true
</value>
</property>
</bean>
</array>
</property>
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.**.dao" />
<property name="sqlSessionFactoryBeanName" value="DynamicSqlSessionFactory" />
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dynamicDataSource" />
</bean>
<!-- 拦截器方式配置事物 -->
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="modify*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="list*" propagation="SUPPORTS" read-only="true" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="select*" propagation="SUPPORTS" read-only="true" />
<tx:method name="seach*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- spring aop事物管理 -->
<aop:config>
<aop:pointcut id="transactionPointcut"
expression="execution(* com.zhougl..service..*.*(..))" />
<!-- 设置order的值为2,使得数据库事物开启在数据源切换之后,否则数据源切换不会达到效果 -->
<aop:advisor pointcut-ref="transactionPointcut"
advice-ref="transactionAdvice" order="2" />
</aop:config>
二、注解切换数据源(手动切换)
通过注解内容的不同切换数据源,不需要分包,一个service层可以使用多个数据源
1. 定义注解
通过注解的值来获取当前数据源,并进行切换。
DataSource.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value();
}
2. 定义数据源动态切换拦截器
DynamicDataSourceInterceptor.java
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
/**
* 数据源动态切换拦截器
* @author zgldo
* 设置执行顺序为1,并使用 aop:before 在数据库事物开启前进行数据源切换
*/
public class DynamicDataSourceInterceptor {
/**
* 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
* @param point
* @throws Exception
*/
public void intercept(JoinPoint point) throws Exception {
// 获取目标对象的类类型
Class<?> target = point.getTarget().getClass();
MethodSignature signature = (MethodSignature) point.getSignature();
resolveDataSource(target, signature.getMethod());
}
/**
* 提取目标对象方法注解和类型注解中的数据源标识
* @param clazz
* @param method
*/
private void resolveDataSource(Class<?> clazz, Method method) {
try {
Class<?>[] types = method.getParameterTypes();
// 默认使用类型注解
if (clazz.isAnnotationPresent(DataSource.class)) {
DataSource source = clazz.getAnnotation(DataSource.class);
DynamicDataSourceHolder.setDatasource(source.value());
System.out.println("mmmmmmmmmmmmm" + source.value());
}
// 方法注解可以覆盖类型注解
Method m = clazz.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource source = m.getAnnotation(DataSource.class);
DynamicDataSourceHolder.setDatasource(source.value());
System.out.println("nnnnnnnnnnnn" + source.value());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3. 配置切面,数据源动态切换
<!-- 数据源动态切换切面配置 设置执行顺序为1,并使用 aop:before 在数据库事物开启前进行数据源切换 -->
<aop:config>
<aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor"
order="1">
<!-- 拦截所有service实现类的方法 -->
<aop:pointcut id="dataSourcePointcut"
expression="execution(* com.zhougl..service..*.*(..))" />
<aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
</aop:aspect>
</aop:config>
<!-- 数据源动态切换实体 -->
<bean id="dataSourceInterceptor"
class="com.zhougl.mobilehealth.util.DynamicDataSourceInterceptor" />
4. 完整配置
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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd"
default-autowire="byName">
<!-- 设置AOP代理开启 -->
<!-- AOP的代理开启放在必须在所有加载的 最前面 -->
<!-- 强制使用CGLIB代理 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<!-- db配置目录 -->
<context:property-placeholder file-encoding="utf-8" location="classpath:db.properties" />
<bean id="peisDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${peis.driver}" />
<property name="jdbcUrl" value="${peis.jdbcUrl}" />
<property name="user" value="${peis.user}" />
<property name="password" value="${peis.password}" />
</bean>
<bean id="mobileHealthDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${mobileHealth.driver}" />
<property name="jdbcUrl" value="${mobileHealth.jdbcUrl}" />
<property name="user" value="${mobileHealth.user}" />
<property name="password" value="${mobileHealth.password}" />
</bean>
<bean id="dynamicDataSource" class="com.zhougl.mobilehealth.util.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="peisDataSource" value-ref="peisDataSource"></entry>
<entry key="mobileHealthDataSource" value-ref="mobileHealthDataSource"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="peisDataSource"></property>
</bean>
<!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
<bean id="DynamicSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dynamicDataSource" />
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath*:com/**/*.mapper.xml"></property>
<!-- pagehelper分页插件拦截器 -->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<value>
helperDialect=oracle
reasonable=false
supportMethodsArguments=true
params=count=countSql
autoRuntimeDialect=true
</value>
</property>
</bean>
</array>
</property>
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.**.dao" />
<property name="sqlSessionFactoryBeanName" value="DynamicSqlSessionFactory" />
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dynamicDataSource" />
</bean>
<!-- 拦截器方式配置事物 -->
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="modify*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="list*" propagation="SUPPORTS" read-only="true" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="select*" propagation="SUPPORTS" read-only="true" />
<tx:method name="seach*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- spring aop事物管理 -->
<aop:config>
<aop:pointcut id="transactionPointcut"
expression="execution(* com.zhougl..service..*.*(..))" />
<!-- 设置order的值为2,使得数据库事物开启在数据源切换之后,否则数据源切换不会达到效果 -->
<aop:advisor pointcut-ref="transactionPointcut"
advice-ref="transactionAdvice" order="2" />
</aop:config>
<!-- 数据源动态切换切面配置 设置执行顺序为1,并使用 aop:before 在数据库事物开启前进行数据源切换 -->
<aop:config>
<aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor"
order="1">
<!-- 拦截所有service实现类的方法 -->
<aop:pointcut id="dataSourcePointcut"
expression="execution(* com.zhougl..service..*.*(..))" />
<aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
</aop:aspect>
</aop:config>
<!-- 数据源动态切换实体 -->
<bean id="dataSourceInterceptor"
class="com.zhougl.mobilehealth.util.DynamicDataSourceInterceptor" />
</beans>
5. 注解使用示例
-
@DataSource
注解是定义在类上
@Service
@DataSource("peisDataSource")
public class UsedDetailServiceImpl implements UsedDetailService {
}
-
@DataSource
定义在方法上,方法上定义的注解优先级高于定义在类上的注解
@Service
public class OrderInfoServiceImpl implements OrderInfoService {
@Autowired
private OrderInfoDao orderInfoDao;
@Autowired
private OrderSetInfoDao orderSetInfoDao;
@DataSource("mobileHealthDataSource")
@Override
public UserInfo getUserInfoById(String userId) {
UserInfo info = this.orderInfoDao.getUserInfoById(userId);
return info;
}
@DataSource("peisDataSource")
@Override
public List<OrderInfo> listOrderInfo(String userId, String peStatus) {
...
}
}
三、分包切换数据源
为每个数据源service层单独建一个包,一个Service只能使用一个数据源。
1. 定义数据源动态切换拦截器
DynamicDataSourceInterceptor.java
/**
* 数据源动态切换拦截器
* @author zgldo
* 设置执行顺序为1,并使用 aop:before 在数据库事物开启前进行数据源切换
*/
public class DynamicDataSourceInterceptor {
/** 切换到数据源1 */
public void setDataSourceOne() {
DynamicDataSourceHolder.setDataSource("peisDataSource");
}
/** 切换到数据源2 */
public void setDataSourceSecond() {
DynamicDataSourceHolder.setDataSource("mobileHealthDataSource");
}
}
2. 配置切面,数据源动态切换
<!-- 数据源动态切换切面配置 设置执行顺序为1,并使用 aop:before 在数据库事物开启前进行数据源切换 -->
<aop:config>
<aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor"
order="1">
<!-- 拦截所有service实现类的方法 -->
<aop:pointcut id="dataSourcePointcutOne"
expression="execution(* com.zhougl.peis..service..*.*(..))" />
<aop:before pointcut-ref="dataSourcePointcutOne" method="setDataSourceOne" />
<aop:pointcut id="dataSourcePointcutTwo"
expression="execution(* com.zhougl.mobile..service..*.*(..))" />
<aop:before pointcut-ref="dataSourcePointcutTwo" method="setDataSourceSecond" />
</aop:aspect>
</aop:config>
<!-- 数据源动态切换实体 -->
<bean id="dataSourceInterceptor"
class="com.zhougl.mobilehealth.util.DynamicDataSourceInterceptor" />
4. 完整配置
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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd"
default-autowire="byName">
<!-- 设置AOP代理开启 -->
<!-- AOP的代理开启放在必须在所有加载的 最前面 -->
<!-- 强制使用CGLIB代理 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<!-- db配置目录 -->
<context:property-placeholder file-encoding="utf-8" location="classpath:db.properties" />
<bean id="peisDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${peis.driver}" />
<property name="jdbcUrl" value="${peis.jdbcUrl}" />
<property name="user" value="${peis.user}" />
<property name="password" value="${peis.password}" />
</bean>
<bean id="mobileHealthDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${mobileHealth.driver}" />
<property name="jdbcUrl" value="${mobileHealth.jdbcUrl}" />
<property name="user" value="${mobileHealth.user}" />
<property name="password" value="${mobileHealth.password}" />
</bean>
<bean id="dynamicDataSource" class="com.zhougl.mobilehealth.util.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="peisDataSource" value-ref="peisDataSource"></entry>
<entry key="mobileHealthDataSource" value-ref="mobileHealthDataSource"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="peisDataSource"></property>
</bean>
<!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
<bean id="DynamicSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dynamicDataSource" />
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath*:com/**/*.mapper.xml"></property>
<!-- pagehelper分页插件拦截器 -->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<value>
helperDialect=oracle
reasonable=false
supportMethodsArguments=true
params=count=countSql
autoRuntimeDialect=true
</value>
</property>
</bean>
</array>
</property>
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.**.dao" />
<property name="sqlSessionFactoryBeanName" value="DynamicSqlSessionFactory" />
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dynamicDataSource" />
</bean>
<!-- 拦截器方式配置事物 -->
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="modify*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="list*" propagation="SUPPORTS" read-only="true" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="select*" propagation="SUPPORTS" read-only="true" />
<tx:method name="seach*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- spring aop事物管理 -->
<aop:config>
<aop:pointcut id="transactionPointcut"
expression="execution(* com.zhougl..service..*.*(..))" />
<!-- 设置order的值为2,使得数据库事物开启在数据源切换之后,否则数据源切换不会达到效果 -->
<aop:advisor pointcut-ref="transactionPointcut"
advice-ref="transactionAdvice" order="2" />
</aop:config>
<!-- 数据源动态切换切面配置 设置执行顺序为1,并使用 aop:before 在数据库事物开启前进行数据源切换 -->
<aop:config>
<aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor"
order="1">
<!-- 拦截所有service实现类的方法 -->
<!-- 配置数据源一 -->
<aop:pointcut id="dataSourcePointcutOne"
expression="execution(* com.zhougl.peis..service..*.*(..))" />
<aop:before pointcut-ref="dataSourcePointcutOne" method="setDataSourceOne" />
<!-- 配置数据源二 -->
<aop:pointcut id="dataSourcePointcutTwo"
expression="execution(* com.zhougl.mobile..service..*.*(..))" />
<aop:before pointcut-ref="dataSourcePointcutTwo" method="setDataSourceSecond" />
</aop:aspect>
</aop:config>
<!-- 数据源动态切换实体 -->
<bean id="dataSourceInterceptor"
class="com.zhougl.mobilehealth.util.DynamicDataSourceInterceptor" />
</beans>
- 另一种方法:通过aop的after,before的配置拦截器,在拦截器的before方法里,通过获取模板对象的类名来获取所在包名,不同的包配置不同的数据源。
网友评论