Springmvc+mybatis+spring多数据源配置 AOP+注解方式切换数据源

纸上得来终觉浅,绝知此事要躬行

最终效果就是,通过在service层类或方法上,注解声明需要的数据源名称,从而实现切换数据源的目的

理论知识就不说了,直接上代码(不支持分布式事务)*

1.准备多数据源properties文件

<!-- 数据库1 -->
customer.jdbc.driver = com.mysql.jdbc.Driver
customer.jdbc.url = jdbc:mysql://localhost:3306/demo1?useUnicode=true&characterEncoding=utf-8
customer.jdbc.username = root
customer.jdbc.password = root
<!-- 数据库2 -->
...

2.spring注入数据源

<!-- 数据库1 -->
<bean id="customer" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close" >  
        <property name="driverClass" value="#{database['customer.jdbc.driver']}" />  
        <property name="jdbcUrl" value="#{database['customer.jdbc.url']}" />  
        <property name="user" value="#{database['customer.jdbc.username']}" />  
        <property name="password" value="#{database['customer.jdbc.password']}"/>
</bean> 
<!-- 数据库2 -->
...

3.自定义DataSourceContextHolder类

public class DataSourceContextHolder {

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

    /**
     * @description 提供给AOP去设置当前的线程的数据源的信息
     * @param dbType
     */
    public static void setDbType(String dbType) {
        contextHolder.set(dbType);
    }
    /**
     * @description 提供给AbstractRoutingDataSource的实现类,通过key选择数据源
     * @return
     */
    public static String getDbType() {
        return (contextHolder.get());
    }
    /**
     * @description  使用默认的数据源
     */
    public static void clearDbType() {
        contextHolder.remove();
    }
}

4.自定义DynamicDataSource类继承自AbstractRoutingDataSource抽象类并实现determineCurrentLookupKey方法

public class DynamicDataSource extends AbstractRoutingDataSource{  
    @Override  
    protected Object determineCurrentLookupKey() {  
        return DataSourceContextHolder.getDataSourceType();   
    }
}  

5.spring中注入多数据源

<bean id="dynamicDataSource" class="com.core.DynamicDataSource" >  
        <property name="targetDataSources">  
            <map key-type="java.lang.String">  
                <entry value-ref="customer" key="customer"></entry>  
                ...
            </map>  
        </property>  
        <property name="defaultTargetDataSource" ref="customer">  
        </property>  
    </bean>

6.sqlSessionFactory

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dynamicDataSource" />
        <property name="typeAliasesPackage" value="com.mapper1,com.mapper2" />
        <property name="mapperLocations">
            <list><!-- mapperLocations是数组类型 list array都可以  示例为多路径复杂情况 -->
                <value>classpath:/mybatis/*Mapper.xml</value>
                <value>classpath:/mybatis/base/*Mapper.xml</value>
            </list>
        </property>
    </bean>

7.sqlSessionTemplate通过sqlSessionFactory初始化

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

8.扫描所有mybatis的接口

<!-- 扫描basePaclage下所有mapper接口 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 若使用sqlSessionFactoryBeanName,则忽略上一步
                有小伙伴发现这个成员变量会导致数据库切换失败,我本人测试没问题 
                个人觉得由于事务和数据源的切换顺序导致的问题居多
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
         -->
        <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate" />
        <property name="basePackage" value="com.dao1, com.dao2"></property>
    </bean>

9.自定义AOP动态切换数据源切面类DataSourceAspect
方式1:采用注解方式,自定义注解做为切点标记
自定义注解DataSource

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

//使用自定义切换数据库注解
@DataSource(value="customer")
@Service
public class CustomerService
/**
 * AOP动态切换数据源切面类
 * @author user
 *
 */
@Component//<!-- 确认是否配置了该路径下注释配置bean的自动扫描 -->
@Order(0)//<!-- 设置切换数据源的优先级 -->
@Aspect
public class DataSourceAspect {


    //@within在类上设置
    //@annotation在方法上进行设置
    //事务在service层 因此切换数据库的注解只能放在service层或之前
    @Pointcut("@within(com.core.DataSource)||@annotation(com.core.DataSource)")
    public void pointcut() {}


    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint)
    {
        Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
        DataSource annotationClass = method.getAnnotation(DataSource.class);
        if(annotationClass == null){
            annotationClass = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
            if(annotationClass == null) return;
        } 
        String dataSourceKey = annotationClass.value();
        if(dataSourceKey !=null){
            DatabaseContextHolder.setCustomerType(dataSourceKey);
        }  
    }

    @After("pointcut()")
    public void after(JoinPoint point) {

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        DatabaseContextHolder.clearCustomerType();
        System.out.println("切换默认数据源成功!");
    }

}

注意:这里有个坑,一定记得配置扫描器,确认已经存在的扫描路径可以扫描到这个切面类

<!-- 采用注释的方式配置bean -->
<context:component-scan base-package="com.core"></context:component-scan>
<!-- aop代理 true表示使用CGLIB代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>

方式2:采用xml配置方式,切点表达式作为切点 首先还是定义切面类

public class DataSourceAspect {

    public void doBefore(JoinPoint joinPoint)
    {
        Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
        DataSource annotationClass = method.getAnnotation(DataSource.class);
        if(annotationClass == null){
            annotationClass = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
            if(annotationClass == null) return;
        } 
        String dataSourceKey = annotationClass.value();
        if(dataSourceKey !=null){
            DatabaseContextHolder.setCustomerType(dataSourceKey);
        }  
    } 
    public void after(JoinPoint point) {

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        DatabaseContextHolder.clearCustomerType();
        System.out.println("切换默认数据源成功!");
    }

}

配置切面-切点-设置优先级

<bean id="dataSourceAspect" class="com.core.DataSourceAspect"/>

<aop:config>
        <!--数据源的切换应在事务之前,事务在service层,
        因此数据源的切点至少在service层(或者controller层)-->
        <aop:pointcut id="switchDataSource" expression="execution(* com.service..*.*(..)) "/>
        <!--order的数值越小优先级越高 -->
        <aop:aspect ref="dataSourceAspect" order="0">
            <aop:before method="doBefore" pointcut-ref="switchDataSource"/>
            <aop:after method="after" pointcut-ref="switchDataSource"/>
        </aop:aspect>   
    </aop:config>

设置事务的优先级

<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1" order="1"/>
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值