多数据源配置,都差不多都是大同小异。不外乎多配置几个datasource,在架构执行service层代码时,选择自己所需要业务操作的数据源而已。根据业务量的多少配置手动选择数据源还是通过AOP自动选择数据源而已!
我这里介绍通过aop自动选择数据源配置(手动可以下来初步了解下,基本上没啥技术含量)
介绍我的数据源代码:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- 配置数据源 -->
<!--统一的dataSource-->
<bean id="dynamicDataSource" class="com.dome.utils.datasource.DynamicDataSource" >
<property name="targetDataSources">
<map key-type="java.lang.String">
<!--通过不同的key决定用哪个dataSource-->
<entry value-ref="mysql" key="mysql"></entry>
<entry value-ref="sqlserver" key="sqlserver"></entry>
</map>
</property>
<!--设置默认的dataSource-->
<property name="defaultTargetDataSource" ref="mysql">
</property>
</bean>
<!-- mysql数据源 -->
<bean id="mysql" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${dataSourceB.url}" />
<property name="username" value="${dataSourceB.username}" />
<property name="password" value="${dataSourceB.password}" />
<property name="connectionProperties" value="${dataSourceB.driver}"></property>
<property name="driverClassName" value="${dataSourceB.driver}"></property>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="50" />
<property name="minIdle" value="1" />
<property name="maxActive" value="1000" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<!-- 设置自动回收超时连接 -->
<property name="removeAbandoned" value="true"/>
<!-- 自动回收超时时间(以秒数为单位) -->
<property name="removeAbandonedTimeout" value="10"/>
</bean>
<!-- sql server数据源 -->
<bean id="sqlserver" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${dataSourceC.url}" />
<property name="username" value="${dataSourceC.username}" />
<property name="password" value="${dataSourceC.password}" />
<property name="connectionProperties" value="${dataSourceC.driver}"></property>
<property name="driverClassName" value="${dataSourceC.driver}"></property>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="50" />
<property name="minIdle" value="1" />
<property name="maxActive" value="1000" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<!-- 设置自动回收超时连接 -->
<property name="removeAbandoned" value="true"/>
<!-- 自动回收超时时间(以秒数为单位) -->
<property name="removeAbandonedTimeout" value="10"/>
</bean>
<!-- SqlSessionFactory连接设置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dynamicDataSource" />
<!-- 扫描的实体包 -->
<property name="typeAliasesPackage" value="com.dome.bean" />
<!-- 扫描的mybatis配置 -->
<property name="configLocation" value="classpath:config/mybatisConfig.xml" />
<!--扫描mapper.xml配置 -->
<property name="mapperLocations" value="classpath*:com/dome/mapper/**/*.xml" />
</bean>
<!--dao层的连接与数据库 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.dome.dao.source" /><!-- 不能取名 .dao 会与其他冲突 -->
</bean>
<!-- 配置事务 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dynamicDataSource"/>
</bean>
<!--配置哪些数据源需要开启事务-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="batch*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Exception"/>
<tx:method name="save*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Exception"/>
<tx:method name="insert*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Exception"/>
<tx:method name="import*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Exception"/>
<tx:method name="add*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Exception"/>
<tx:method name="remove*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Exception"/>
<tx:method name="delete*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Exception"/>
<tx:method name="update*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Exception"/>
<tx:method name="find*" read-only="true" />
<tx:method name="get*" read-only="true" />
<tx:method name="select*" read-only="true" />
<tx:method name="login*" read-only="true" />
</tx:attributes>
</tx:advice>
<bean id="dataSourceExchange" class="com.dome.utils.datasource.DataSourceExchange"/>
<!-- 配置哪些类的方法需要进行事务管理 -->
<aop:config>
<aop:pointcut id="allManagerMethod" expression="execution(* com.dome.service.*.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="allManagerMethod" order="2"/>
<aop:advisor pointcut-ref="allManagerMethod" advice-ref="dataSourceExchange" order="1"/>
</aop:config>
</beans>
这里必须强调order级别的选择数字越小,级别越高,先执行
通过aop自动数据源切换,以及事物的开启都是由aop完成的,这里就要涉及到先后的问题,要不然事物完成在前,选择数据源配置在后,就会出现事物不起作用的问题;
DataSourceExchange;自动选择配置数据源类
package com.dome.utils.datasource;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
public class DataSourceExchange implements MethodBeforeAdvice,AfterReturningAdvice
{
@Override
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
CustomerContextHolder.clearCustomerType();
}
@Override
public void before(Method method, Object[] args, Object target)
throws Throwable {
//这里DataSource是自定义的注解,不是java里的DataSource接口
Method originalMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
if (originalMethod.isAnnotationPresent(DbToLoad.class))
{
CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_MSSQL);
}
else
{
//target是被织入增强处理的目标对象,通过获取getDataSourceName函数来获取target的数据源名称
CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_MYSQL);
}
}
}
DataSourceExchange中 before方法与afterReturning的含义是分别在方法指向前执行数据源选择,方法后执行数据源清空;
都通过CustomerContextHolder类来实现;而由DynamicDataSource类来决定spring选择哪个数据源进行连接。以及一个自定义注解DbToLoad类,用来判断在哪些方法上需要执行数据源选择;直接在此方法上使用该注解就可实现数据源选择操作啦;
注意
我看见有很多博客都是通过method.isAnnotationPresent(xx.class)来判断是否有该注解
我想说的是在通过aop自动选择数据源上通过该方法来判断此方法上是否有该注解 是错误的 这个并不能通过这样获取
不信的小朋友可以去尝试下就知道啦,必须通过
Method originalMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
originalMethod.isAnnotationPresent(DbToLoad.class);
反射获取才行;
分别的类如下:
CustomerContextHolder:
package com.dome.utils.datasource;
import org.apache.commons.lang.StringUtils;
public class CustomerContextHolder {
public static final String DATA_SOURCE_MYSQL = "mysql";
public static final String DATA_SOURCE_MSSQL = "sqlserver";
//用ThreadLocal来设置当前线程使用哪个dataSource
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
public static String getCustomerType() {
String dataSource = contextHolder.get();
if (StringUtils.isEmpty(dataSource)) {
return DATA_SOURCE_MYSQL;
}else {
return dataSource;
}
}
public static void clearCustomerType() {
contextHolder.remove();
}
}
DynamicDataSource:
package com.dome.utils.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return CustomerContextHolder.getCustomerType();
}
}
DbToLoad:
package com.dome.utils.datasource;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DbToLoad {
String value() default "mysql";
}
这些都很基础,我就不一一去注解啥意思啦;
怎么去实现,验证呢!
首先service层的实现类:
@DbToLoad
@Override
public PageInfo<User> listUsers__mysql(int pageNum,int pageSize) {
xxxxxxxxxxxxxx;
}
在此方法上使用@DbToLoad这个自定义注解就可以啦
程序运行时,aop就会自动去判断使用哪个数据源;
有问题的可以留言给我!