上周末,室友通宵达旦的敲代码处理他的多数据源的问题,搞的非常的紧张,也和我聊了聊天,大概的了解了他的业务的需求。一般的情况下我们都是使用SSH或者SSM框架进行处理我们的数据源的信息。
操作数据一般都是在DAO层进行处理,可以选择直接使用JDBC进行编程(http://blog.csdn.net/yanzi1225627/article/details/26950615/)
或者是使用多个DataSource 然后创建多个SessionFactory,在使用Dao层的时候通过不同的SessionFactory进行处理,不过这样的入侵性比较明显,一般的情况下我们都是使用继承HibernateSupportDao进行封装了的处理,如果多个SessionFactory这样处理就是比较的麻烦了,修改的地方估计也是蛮多的
最后一个,也就是使用AbstractRoutingDataSource的实现类通过AOP或者手动处理实现动态的使用我们的数据源,这样的入侵性较低,非常好的满足使用的需求。比如我们希望对于读写分离或者其他的数据同步的业务场景
单数据源的场景(一般的Web项目工程这样配置进行处理,就已经比较能够满足我们的业务需求)
使用AbstractRoutingDataSource 的实现类,进行灵活的切换,可以通过AOP或者手动编程设置当前的DataSource,不用修改我们编写的对于继承HibernateSupportDao的实现类的修改,这样的编写方式比较好,至于其中的实现原理,让我细细到来。我们想看看如何去应用,实现原理慢慢的说!
编写AbstractRoutingDataSource的实现类,HandlerDataSource就是提供给我们动态选择数据源的数据的信息,我们这里编写一个根据当前线程来选择数据源,然后通过AOP拦截特定的注解,设置当前的数据源信息,也可以手动的设置当前的数据源,在编程的类中。
package com.common.utils.manydatasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* descrption: 多数据源的选择
* authohr: wangji
* date: 2017-08-21 10:32
*/
public class MultipleDataSourceToChoose extends AbstractRoutingDataSource {
/**
* @desction: 根据Key获取数据源的信息,上层抽象函数的钩子
* @author: wangji
* @date: 2017/8/21
* @param:
* @return:
*/
@Override
protected Object determineCurrentLookupKey() {
return HandlerDataSource.getDataSource();
}
}
package com.common.utils.manydatasource;
/**
* descrption: 根据当前线程来选择具体的数据源
* authohr: wangji
* date: 2017-08-21 10:36
*/
public class HandlerDataSource {
private static ThreadLocal handlerThredLocal = new ThreadLocal();
/**
* @desction: 提供给AOP去设置当前的线程的数据源的信息
* @author: wangji
* @date: 2017/8/21
* @param: [datasource]
* @return: void
*/
public static void putDataSource(String datasource) {
handlerThredLocal.set(datasource);
}
/**
* @desction: 提供给AbstractRoutingDataSource的实现类,通过key选择数据源
* @author: wangji
* @date: 2017/8/21
* @param: []
* @return: java.lang.String
*/
public static String getDataSource() {
return handlerThredLocal.get();
}
/**
* @desction: 使用默认的数据源
*/
public static void clear() {
handlerThredLocal.remove();
}
}
package com.common.utils.manydatasource;
import java.lang.annotation.*;
/**
* @description: 创建拦截设置数据源的注解
* Created by wangji on 2017/8/21.
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicSwitchDataSource {
String dataSource() default "";
}
package com.common.utils.manydatasource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* descrption: 使用AOP拦截特定的注解去动态的切换数据源
* authohr: wangji
* date: 2017-08-21 10:42
*/
@Aspect
@Slf4j
@Component
@Order(1)
public class HandlerDataSourceAop {
//@within在类上设置
//@annotation在方法上进行设置
@Pointcut("@within(com.common.utils.manydatasource.DynamicSwitchDataSource)||@annotation(com.common.utils.manydatasource.DynamicSwitchDataSource)")
public void pointcut() {}
@Before("pointcut()")
public void doBefore(JoinPoint joinPoint)
{
Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
DynamicSwitchDataSource annotationClass = method.getAnnotation(DynamicSwitchDataSource.class);//获取方法上的注解
if(annotationClass == null){
annotationClass = joinPoint.getTarget().getClass().getAnnotation(DynamicSwitchDataSource.class);//获取类上面的注解
if(annotationClass == null) return;
}
//获取注解上的数据源的值的信息
String dataSourceKey = annotationClass.dataSource();
if(dataSourceKey !=null){
//给当前的执行SQL的操作设置特殊的数据源的信息
HandlerDataSource.putDataSource(dataSourceKey);
}
log.info("AOP动态切换数据源,className"+joinPoint.getTarget().getClass().getName()+"methodName"+method.getName()+";dataSourceKey:"+dataSourceKey==""?"默认数据源":dataSourceKey);
}
@After("pointcut()")
public void after(JoinPoint point) {
//清理掉当前设置的数据源,让默认的数据源不受影响
HandlerDataSource.clear();
}
}
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis
jdbc.username=root
jdbc.password=root
jdbc2.url=jdbc:mysql://127.0.0.1:3306/datasource2
<bean id="dataSource0" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="10"/>
bean>
<bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc2.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="10"/>
bean>
bean id="dataSource" class="com.common.utils.manydatasource.MultipleDataSourceToChoose" lazy-init="true">
<description>数据源description>
<property name="targetDataSources">
<map key-type="java.lang.String" value-type="javax.sql.DataSource">
<entry key="datasource0" value-ref="dataSource0" />
<entry key="datasource1" value-ref="dataSource1" />
map>
property>
<property name="defaultTargetDataSource" ref="dataSource0" />
bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialectprop>
<prop key="hibernate.show_sql">falseprop>
<prop key="hibernate.format_sql">falseprop>
<prop key="hibernate.hbm2ddl.auto">updateprop>
<prop key="hibernate.autoReconnect">trueprop>
<prop key="hibernate.jdbc.batch_size">50prop>
<prop key="hibernate.connection.autocommit">falseprop>
<prop key="hibernate.connection.release_mode">after_transactionprop>
<prop key="hibernate.bytecode.use_reflection_optimizer">falseprop>
props>
property>
<property name="packagesToScan">
<list>
<value>com.modulevalue>
list>
property>
bean>
@Service
@Slf4j
public class UserInfoService implements IUserInfoService {
@Resource
private UserDao userDao;
@Autowired
private CommonHibernateDao commonDao;
@TestValidateParam
public User getUserInfoById(Integer id) {
return userDao.findById(id);
}
@DynamicSwitchDataSource(dataSource = "datasource0")
public void save(User user) {
userDao.save(user);
}
@DynamicSwitchDataSource(dataSource = "datasource1")
public List findAll(){
String sql = "select u.userName as name,u.userAge as age,u.userAddress as address,u.id from user u";
List list =commonDao.findListBySQL(sql,User.class);
return list;
}
}
public void test(){
HandlerDataSource.putDataSource("datasource1");
String sql = "select u.userName as name,u.userAge as age,u.userAddress as address,u.id from user u";
List list =commonDao.findListBySQL(sql,User.class);
HandlerDataSource.putDataSource("datasource0");
commonDao.deleteById("2",User.class);
}
<bean id="dataSource" class="com.common.utils.manydatasource.MultipleDataSourceToChoose" lazy-init="true">
<description>数据源description>
<property name="targetDataSources">
<map key-type="java.lang.String" value-type="javax.sql.DataSource">
<entry key="datasource0" value-ref="dataSource0" />
<entry key="datasource1" value-ref="dataSource1" />
map>
property>
<property name="defaultTargetDataSource" ref="dataSource0" />
bean>
targetDataSources,是一个Map对于数据源的引用
public void setTargetDataSources(Map
对于实现SQL的Connection getConnection() throws SQLException的实现,其实就是代理模式找到之前Map的引用,通过key,而这个key就是我们灵活配置的key,通过这个key就可以寻找到这个值。
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
这里说的非常的详细,通过钩子函数让子类去实现,寻找特定的key,然后选择DataSource 的时候就可以很灵活的使用啦!
/**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
这个就是模板方法模式中常见的钩子函数,在HttpServlet中也有类似的使用钩子,非常的棒,不过这个是必须实现,httpServlet不是必须实现,只是添加一些补充。由于每次执行数据库的调用,总会执行这个getConnection方法,每次都查看AOP中是否设置了当前的数据源,然后找到Map的引用的代理的数据源的Connection方法,原理没有变化的。
/**
* Determine the current lookup key. This will typically be
* implemented to check a thread-bound transaction context.
* Allows for arbitrary keys. The returned key needs
* to match the stored lookup key type, as resolved by the
* {@link #resolveSpecifiedLookupKey} method.
*/
protected abstract Object determineCurrentLookupKey();
这里就是我们的实现的数据源的选择哦!
/**
* descrption: 多数据源的选择
* authohr: wangji
* date: 2017-08-21 10:32
*/
public class MultipleDataSourceToChoose extends AbstractRoutingDataSource {
/**
* @desction: 根据Key获取数据源的信息,上层抽象函数的钩子
* @author: wangji
* @date: 2017/8/21
* @param:
* @return:
*/
@Override
protected Object determineCurrentLookupKey() {
return HandlerDataSource.getDataSource();
}
}