使用spring注解在方法或类上动态切换数据源

1 相关知识
1.1  Spring的AbstractRoutingDataSource抽象类,该类可以充当数据源的路由中介,可以根据名字动态切换数据源

1.2  SpringAop 

1.3  Spring自定义注解

2 思路

2.1 在类或方法上添加自定义注解,其值为数据源的名字

2.2 通过SpringAop在项目运行时获取到类或方法上自定义注解的值

2.3 将拿到的注解值通过数据源路由动态的设置数据源

3 实战

3.1 首先实现数据源路由

3.1.1 通过继承AbstractRoutingDataSource类来实现数据源切换

public class DynamicDataSource extends AbstractRoutingDataSource {
 
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDbType();
    }
 
}
该类中只有一个方法,该方法通过返回数据源的名称动态的切换数据源

3.1.2 通过ThreadLocal 线程本地变量设置数据源名称来保证线程安全


public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();  
      
    public static void setDbType(String dbType) {  
           contextHolder.set(dbType);  
    }  
 
    public static String getDbType() {  
           return ((String) contextHolder.get());
    }  
 
    public static void clearDbType() {  
           contextHolder.remove();  
    }
}
ThreadLocal为每个线程创建一个实例,使每个线程保持各自独立的一个对象。通过contextHolder.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行contextHolder.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。在本列子中就是为了保证多个人可以同时访问不同的数据源。

3.1.3 数据源名称管理类


public interface DataSourceName {
   static final String DATA_CHAT="test1"; //测试1库
   static final String DATA_USER="test2"; //测试2库
}
3.1.3 配置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:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd 
http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
    <!--测试1-->
    <bean id="test1" class="com.alibaba.druid.pool.DruidDataSource"  
        destroy-method="close">  
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />  
        <property name="url" value="jdbc:mysql://XXXX/test1?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true" />  
        <property name="username" value="root" />  
        <property name="password" value="isoftadmin" />  
        <!-- 初始化连接大小 -->  
        <property name="initialSize" value="1"></property>  
        <!-- 连接池最大数量 -->  
        <property name="maxActive" value="50"></property>  
        <!-- 连接池最大空闲 -->  
        <property name="maxIdle" value="2"></property>  
        <!-- 连接池最小空闲 -->  
        <property name="minIdle" value="1"></property>  
        <!-- 获取连接最大等待时间 -->  
        <property name="maxWait" value="10000"></property>  
    </bean>
    <!--测试2-->
    <bean id="test2" class="com.alibaba.druid.pool.DruidDataSource"  
        destroy-method="close">  
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />  
        <property name="url" value="jdbc:mysql://XXXX/test2?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true" />  
        <property name="username" value="root" />  
        <property name="password" value="isoftadmin" />  
        <!-- 初始化连接大小 -->  
        <property name="initialSize" value="1"></property>  
        <!-- 连接池最大数量 -->  
        <property name="maxActive" value="50"></property>  
        <!-- 连接池最大空闲 -->  
        <property name="maxIdle" value="2"></property>  
        <!-- 连接池最小空闲 -->  
        <property name="minIdle" value="1"></property>  
        <!-- 获取连接最大等待时间 -->  
        <property name="maxWait" value="10000"></property>  
    </bean>
     <!--数据源路由 DynamicDataSource就是自己编写的数据源路由类 -->
     <bean id="dataSource" class="com.gcx.api.common.dataSource.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="test1" value-ref="test1"/>
                <entry key="test2" value-ref="test2"/>
            </map>
        </property>
         <!-- 默认数据源为test1 -->
        <property name="defaultTargetDataSource" ref="test1"/>
    </bean>
    <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->  
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="dataSource" />  
        <!-- 自动扫描mapping.xml文件 -->  
        <property name="mapperLocations" value="classpath:com/gcx/api/mapping/*.xml"></property> 
    </bean> 
    <!-- DAO接口所在包名,Spring会自动查找其下的类 -->  
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
        <property name="basePackage" value="com.gcx.api.dao" />  
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>  
    </bean>
     <!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->  
    <bean id="transactionManager"  
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="dataSource" />  
    </bean>  
</beans>


3.2 编写自定义注解

3.2.1 自定义注解类,用来在类或方法上设置数据源名称

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomDataSource {
    String value();
}


3.3 编写AOP

3.3.1 通过切面获取注解的值


@Component
@Aspect
public class DataAop {
   
    @Before("execution(* com.gcx.api.service.impl.*.*(..))")
    public void switchDataSource(JoinPoint pjp) throws Throwable{
        Signature signature = pjp.getSignature();    
        MethodSignature methodSignature = (MethodSignature)signature;  
        Method targetMethod = methodSignature.getMethod();
        Method realMethod = pjp.getTarget().getClass().getDeclaredMethod(signature.getName(), targetMethod.getParameterTypes());
            //首先判断方法级别
            CustomDataSource cds=realMethod.getAnnotation(CustomDataSource.class);
            if(cds==null){ //判断类级别
                 cds= AnnotationUtils.findAnnotation(realMethod.getDeclaringClass(), CustomDataSource.class);
            }
            if(cds==null){  //默认库
                DataSourceContextHolder.setDbType(DataSourceName.DATA_CHAT);
                return;
            }
            String dataSourceName=cds.value(); //获取注解的值
               if(dataSourceName!=null&&!dataSourceName.equals(""))  //通过数据源路由类切换数据源
                  DataSourceContextHolder.setDbType(dataSourceName);
    }
    
}
该切面只拦截了Service实现层,所有注解需要写在Service实现层的类或方法中。切面会在方法或类执行之前优先获取方法上的注解,如果方法上没有,则会去类上找,如果类上也没有则会使用默认库。


3.4 在项目中使用

3.4.1 在Service实现层使用自定义注解切换数据源

@CustomDataSource(DataSourceName.DATA_USER) //使用自定义注解切换数据源
@Service
public class UserServiceImpl implements UserService {
 
    @Autowired
    private UserMapper userDao;
 
    // 查询所有记录 
    public MyResult findAllRecords(User record) throws Exception{
            List<User> list = userDao.findAllList(record);
            int count = (int) userDao.findAllListCount(record);
            return MyResult.ok(count, list);
    }
 
      //添加记录      
       @CustomDataSource(DataSourceName.DATA_CHAT)
       public MyResult addRecord(User record) throws Exception{
                        // UUID主键 
                        long nanoTime = System.nanoTime(); 
                        record.setUserId(new BigDecimal(nanoTime));
                        int i = userDao.insertSelective(record);
                        return MyResult.ok(i, "新增");
       }
 
      // 删除记录
     public MyResult delRecord(String id) throws Exception{
                       int i = userDao.deleteByPrimaryKey(id);
                       return MyResult.ok(i, "删除");
      }
}
该类的find和del方法会使用test2库,add方法会使用test1库。
如果需要全部使用test1库,则在方法或类上无需定义任何自定义注解。
--------------------- 
作者:Change_Code 
来源:CSDN 
原文:https://blog.csdn.net/qq_33251859/article/details/78365055 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值