1、读写分离 2、分库存储
实现数据源切换的功能就是自定义一个类扩展AbstractRoutingDataSource抽象类,其实该相当于数据源DataSourcer的路由中介,可以实现当项目运行时根据相应key值切换到对应的数据源DataSource上
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
private Map<Object, Object> targetDataSources;
private Object defaultTargetDataSource;
private boolean lenientFallback = true;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
private Map<Object, DataSource> resolvedDataSources;
private DataSource resolvedDefaultDataSource;
通过源码可以看出该类是一个抽象类,定义了6个属性。 targetDataSources:是一个map类型该属性正是用来维护项目中多个数据源 defaultTargetDataSource:通过属性名很直观的可以理解它的作用(默认数据源) lenientFallback:默认为true,无需改动 dataSourceLookup:查找数据源接口的名称 resolvedDataSources:改变后的数据源 resolvedDefaultDataSource:如果该字段没有赋值,就是targetDataSources
获取数据源的本质其实就是获取数据源的连接,我们看下该类获取连接的具体代码片段
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
直接进入determineTargetDataSource方法
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;
}
由该代码片段我们可以很直观的发现,这个方法的作用就是用来实现查找目标数据源,通过代码我们可以查找数据源是根据determineCurrentLookupKey()这个方法。因此我们只需重写determineCurrentLookupKey()方法即可
1.动态配置多数据源
创建设置上下文环境的类(DataSourceContextHolder),主要负责改变上下文数据源的名称。通过ThreadLocal类使每个线程获取独立的数据源,防止并发访问时获取错误的数据源
public class DataSourceContextHolder {
@SuppressWarnings("rawtypes")
private static final ThreadLocal contextHolder = new ThreadLocal();
@SuppressWarnings("unchecked")
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return (String) contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
}
2.建立动态数据源类
实现AbstractRoutingDataSource重写determineCurrentLookupKey。该方法返回一个Object,一般是返回字符串
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// TODO Auto-generated method stub
return DataSourceContextHolder.getDataSourceType();
}
}
3.spring配置文件配置多个数据源
<bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="close">
<property name="driverClassName" value="${master.jdbc.driverClassName}" />
<property name="url" value="${master.jdbc.url}" />
<property name="username" value="${master.jdbc.username}" />
<property name="password" value="${master.jdbc.password}" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${jdbc.initialSize}" />
<property name="minIdle" value="${jdbc.minIdle}" />
<property name="maxActive" value="${jdbc.maxActive}" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${jdbc.maxWait}" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" />
<property name="validationQuery" value="${jdbc.validationQuery}" />
<property name="testWhileIdle" value="${jdbc.testWhileIdle}" />
<property name="testOnBorrow" value="${jdbc.testOnBorrow}" />
<property name="testOnReturn" value="${jdbc.testOnReturn}" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="${jdbc.poolPreparedStatements}" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="${jdbc.maxPoolPreparedStatementPerConnectionSize}" />
<!-- 配置监控统计拦截的filters,去掉后监控界面sql无法统计 -->
<property name="filters" value="${jdbc.filters}" />
</bean>
<bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="close">
<property name="driverClassName" value="${slave.jdbc.driverClassName}" />
<property name="url" value="${slave.jdbc.url}" />
<property name="username" value="${slave.jdbc.username}" />
<property name="password" value="${slave.jdbc.password}" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${jdbc.initialSize}" />
<property name="minIdle" value="${jdbc.minIdle}" />
<property name="maxActive" value="${jdbc.maxActive}" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${jdbc.maxWait}" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" />
<property name="validationQuery" value="${jdbc.validationQuery}" />
<property name="testWhileIdle" value="${jdbc.testWhileIdle}" />
<property name="testOnBorrow" value="${jdbc.testOnBorrow}" />
<property name="testOnReturn" value="${jdbc.testOnReturn}" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="${jdbc.poolPreparedStatements}" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="${jdbc.maxPoolPreparedStatementPerConnectionSize}" />
<!-- 配置监控统计拦截的filters,去掉后监控界面sql无法统计 -->
<property name="filters" value="${jdbc.filters}" />
</bean>
<bean id="dataSource" class="com.demo.dynamic.helpper.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="master" value-ref="masterDataSource"/>
<entry key="slave" value-ref="slaveDataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="masterDataSource"></property>
</bean>
4.基于自定义注解+aop实现数据源动态切换
自定义注解:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value() default "";
}
定义aop实现类:
@Aspect
@Component
@Order(1)
public class DynamicDataSourceAspect {
private static Logger logger = org.slf4j.LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Before("@annotation(com.demo.dynamic.helpper.DataSource)")
public void before(JoinPoint point) {
operatePointCut(point, false);
}
@After("@annotation(com.demo.dynamic.helpper.DataSource)")
public void after(JoinPoint point) {
operatePointCut(point, true);
}
private void operatePointCut(JoinPoint point, boolean isReset) {
Class<?> target = point.getTarget().getClass();
MethodSignature signature = (MethodSignature) point.getSignature();
// 默认使用目标类型的注解,如果没有则使用其实现接口的注解类
for (Class<?> cls : target.getInterfaces()) {
operateDataSource(cls, signature.getMethod(), isReset);
}
operateDataSource(target, signature.getMethod(), isReset);
}
private void operateDataSource(Class<?> cls, Method method, boolean isReset) {
try {
Class<?>[] types = method.getParameterTypes();
if (!isReset) {
if (cls.isAnnotationPresent(DataSource.class)) {
DataSource source = cls.getAnnotation(DataSource.class);
DataSourceContextHolder.setDataSourceType(source.value());
}
}
// 方法注解可以覆盖类注解
Method m = cls.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource source = m.getAnnotation(DataSource.class);
if (isReset) {
DataSourceContextHolder.clearDataSourceType();
} else {
DataSourceContextHolder.setDataSourceType(source.value());
}
}
} catch (Exception e) {
logger.error(cls + ":" + e.getMessage(), e);
}
}
}
开启aop自动代理
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
1.测试数据准备
a、master_db t_user
b、slave_db t_user
2.测试service编写
@Service
@Transactional
public class UserService {
@Autowired
private UserMapper userMapper;
@DataSource("master")
public User getUserMasterById(Long id) {
return userMapper.selectByPrimaryKey(id);
}
@DataSource("slave")
public User getUserSlaveById(Long id) {
return userMapper.selectByPrimaryKey(id);
}
}
3.单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:spring.xml" })
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void testQuery() {
User masterUser = userService.getUserMasterById(1L);
System.out.println(masterUser);
System.out.println("-------------------------------分割线---------------------------------");
User slaveUser = userService.getUserSlaveById(1L);
System.out.println(slaveUser);
}
}
4.运行结果
User [id=1, userName=zhangsan, password=1, age=10]
-------------------------------分割线---------------------------------
User [id=1, userName=张三, password=zs, age=12]
说明多数据源配置成功
https://github.com/lyb-geek/spring-dynamic-db