Spring+mybatis 使用注解配置多数据库源,支持读写分离

时间:2021-03-23 05:09:32

第一步:创建一个DynamicDataSource的类,继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法,代码如下:

package spring;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
   @Override
   protected Object determineCurrentLookupKey() {

      return DynamicDataSourceHolder.getDataSource();
   }
}
第二步:创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识

package spring;

public class DynamicDataSourceHolder {




   private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>();

   public static String getDataSource() {
      return THREAD_DATA_SOURCE.get();
   }

   public static void setDataSource(String dataSource) {
      THREAD_DATA_SOURCE.set(dataSource);
   }

   public static void clearDataSource() {
      THREAD_DATA_SOURCE.remove();
   }


}
第三步:配置多个数据源和第一步里创建的DynamicDataSource的bean,简化的配置如下:

<bean id="readServerDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.mysql.driver}"/>
    <property name="url" value="${jdbc.mysql.url}"/>
    <property name="username" value="${jdbc.mysql.username}"/>
    <property name="password" value="${jdbc.mysql.password}"/>
    <property name="initialSize" value="${jdbc.initialSize}"/>
    <property name="minIdle" value="${jdbc.minIdle}"/>
    <property name="maxIdle" value="${jdbc.maxIdle}"/>
    <!--<property name="maxActive" value="${jdbc.maxActive}"/>-->
    <!--<property name="maxWait" value="${jdbc.maxWait}"/>-->
    <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/>
    <!--<property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>-->
    <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/>
    <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/>
    <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
    <property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/>
    <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
</bean>


<bean id="writeServerDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.mysql.driver}"/>
    <property name="url" value="${jdbc.mysql.url}"/>
    <property name="username" value="${jdbc.mysql.username}"/>
    <property name="password" value="${jdbc.mysql.password}"/>
    <property name="initialSize" value="${jdbc.initialSize}"/>
    <property name="minIdle" value="${jdbc.minIdle}"/>
    <property name="maxIdle" value="${jdbc.maxIdle}"/>
    <!--<property name="maxActive" value="${jdbc.maxActive}"/>-->
    <!--<property name="maxWait" value="${jdbc.maxWait}"/>-->
    <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/>
    <!--<property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>-->
    <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/>
    <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/>
    <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
    <property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/>
    <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
</bean>
<bean id="dataSource" class="spring.DynamicDataSource">
<property name="targetDataSources">
    <map key-type="java.lang.String">
        <!-- 指定lookupKey和与之对应的数据源 -->
        <entry key="readServerDataSource" value-ref="readServerDataSource"></entry>
        <entry key="writeServerDataSource" value-ref="writeServerDataSource"></entry>
    </map>
</property>
<!-- 这里可以指定默认的数据源 -->
<property name="defaultTargetDataSource" ref="readServerDataSource"/>
</bean>

<!-- MyBatis配置 -->
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <!-- 自动扫描domain目录, 省掉Configuration.xml里的手工配置 -->
    <property name="typeAliasesPackage" value="order.dao.domain"/>
    <!-- 显式指定Mapper文件位置 -->
    <property name="mapperLocations" value="classpath*:mybatis/*.xml"/>
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
到这里已经可以使用多数据源了,在操作数据库之前只要DynamicDataSourceHolder.setDataSource(" readServerDataSource ")即可切换到数据源对其只进行读的操作,需要使用只要DynamicDataSourceHolder.setDataSource(" writeServerDataSource ")

接下来配置自定义注解用来切换数据源,这样操作起来更加的简便,不用每次都去set

首先,我们得定义一个名为DataSource的注解

package spring;

import org.springframework.stereotype.Component;

import java.lang.annotation.*;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
 public @interface DataSource {
     String value();
}
然后,定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中

package spring;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

@Component
@Aspect
public class DataSourceAspect {
   /**
    * 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
    *
    * @param point  * @throws Exception
    */
   public void intercept(JoinPoint point) throws Exception {
      Class<?> target = point.getTarget().getClass();
      MethodSignature signature = (MethodSignature) point.getSignature();
      // 默认使用目标类型的注解,如果没有则使用其实现接口的注解
      for (Class<?> clazz : target.getInterfaces()) {
         resolveDataSource(clazz, signature.getMethod());
      }
      resolveDataSource(target, signature.getMethod());
   }

   /**
    * 提取目标对象方法注解和类型注解中的数据源标识
    *
    * @param clazz  * @param method  */
   private void resolveDataSource(Class<?> clazz, Method method) {
      try {
         Class<?>[] types = method.getParameterTypes();
         // 默认使用类型注解
         if (clazz.isAnnotationPresent((Class<? extends Annotation>) DataSource.class)) {
            DataSource source = clazz.getAnnotation(DataSource.class);
            DynamicDataSourceHolder.setDataSource(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());
         }
      } catch (Exception e) {
         System.out.println(clazz + ":" + e.getMessage());
      }
   }
}
OK,这样就可以直接在类或者方法上使用注解@DataSource来指定数据源,不需要每次都手动设置了。

Spring+mybatis 使用注解配置多数据库源,支持读写分离提示:注解@DataSource既可以加在方法上,也可以加在接口或者接口的实现类上,优先级别:方法>实现类>接口。也就是说如果接口、接口实现类以及方法上分别加了@DataSource注解来指定数据源,则优先以方法上指定的为准。

例子用读的数据源:

Spring+mybatis 使用注解配置多数据库源,支持读写分离Spring+mybatis 使用注解配置多数据库源,支持读写分离

@Service("ProductDAOImpl")
@DataSource("readServerDataSource")
public class ProductDAOImpl extends MyAbstractPageService<IProductDAO, Product> {

   @Autowired
   private IProductDAO dao;

   @Override
   public IProductDAO getDao() {
      return dao;
   }

   public IProductDAO querySum() {
      return dao;
   }

   public IProductDAO queryId(String id) {
      return dao;
   }
}
然后自己写个数据库的操作测试一下是否成功