我们准备了三个数据源,一主两从,并且配置了主从复制。
这里我们之说一下多数据源的配置,ssm整合配置就不列出来了。
db.properties
jdbc.url.master=jdbc:mysql://localhost:3307/bike
jdbc.url.slave_1=jdbc:mysql://localhost:3308/bike
jdbc.url.slave_2=jdbc:mysql://localhost:3309/bike
jdbc.username=root
jdbc.password=root
spring-dao文件
//主数据源
<bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="${jdbc.url.master}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
//从数据源
<bean id="slaveDataSource_1" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="${jdbc.url.slave_1}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
//从数据源
<bean id="slaveDataSource_2" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="${jdbc.url.slave_2}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--DynamicDataSource类来整合数据源-->
<bean id="dataSource" class="top.common.DynamicDataSource">
<property name="targetDataSources">
<map>
//会以map的形式存储,通过这个key,拿到数据源
<entry key="master" value-ref="masterDataSource"/>
<entry key="slave_1" value-ref="slaveDataSource_1"/>
<entry key="slave_2" value-ref="slaveDataSource_2"/>
</map>
</property>
//默认是主数据源
<property name="defaultTargetDataSource" ref="masterDataSource"/>
</bean>
创建一个枚举类,表明数据源类型
public enum DataSourceType {
MASTER,SLAVE;
}
自定义注解,基于这个注解切换数据源,注解默认是MASTER
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
DataSourceType value() default DataSourceType.MASTER;
}
控制数据源的切换
public class DynamicDataSourceHolder {
//数据源的key和线程绑定,防止出现并发问题
private static final ThreadLocal<String> holder = new ThreadLocal<>();
private static final AtomicInteger count = new AtomicInteger(-1);
private static final String MASTER = "master";
private static final String SLAVE_1 = "slave_1";
private static final String SLAVE_2 = "slave_2";
//设置数据源
public static void setDataSource(DataSourceType dataSourceType){
if (dataSourceType==DataSourceType.MASTER){
//设置数据源的key
holder.set(MASTER);
System.out.println("------------master--------------");
}else if (dataSourceType==DataSourceType.SLAVE){
holder.set(RoundRobinSlaveKey());
}
}
public static String getDataSource(){
return holder.get();
}
//轮询两个从数据源
private static String RoundRobinSlaveKey() {
if (count.get()>9999){
count.set(-1);
}
if (count.incrementAndGet()%2==0){
System.out.println("------------slave1--------------");
return SLAVE_1;
}else {
System.out.println("------------slave2--------------");
return SLAVE_2;
}
}
}
继承AbstractRoutingDataSource类去切换数据源
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
//通过这个key去拿数据源
return DynamicDataSourceHolder.getDataSource();
}
}
AbstractRoutingDataSource源码:
private Map<Object, Object> targetDataSources; //目标数据源,我们配置的数据源放在这个map集合中
private Object defaultTargetDataSource; 默认的数据源
private Map<Object, DataSource> resolvedDataSources;//存储我们配置的目标数据源
private DataSource resolvedDefaultDataSource; //存储默认数据源
//设置目标数据源
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
}
//设置默认数据源
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
this.defaultTargetDataSource = defaultTargetDataSource;
}
//将我们配置的数据源存到本地map集合
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
} else {
//将目标数据源存储到本地
this.resolvedDataSources = new HashMap(this.targetDataSources.size());
this.targetDataSources.forEach((key, value) -> {
//lookupKey 就是我们key
Object lookupKey = this.resolveSpecifiedLookupKey(key);
DataSource dataSource = this.resolveSpecifiedDataSource(value);
this.resolvedDataSources.put(lookupKey, dataSource);
});
if (this.defaultTargetDataSource != null) {
//将默认数据源存储到本地
this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
}
//决定目标数据源
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
//拿到我们设置的数据源的key
Object lookupKey = this.determineCurrentLookupKey();
//通过key去获取数据源
DataSource 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 + "]");
} else {
return dataSource;
}
}
@Nullable
//我们实现这个方法,去设置数据源的key
protected abstract Object determineCurrentLookupKey();
通过源码我们发现,我们只需要实现determineCurrentLookupKey方法,返回一个数据源的key,spring就会帮我们拿到我们配置的数据源。
//拦截方法,判断方法上是否存在数据源注解,存在就切换到对应的数据源上
@Component
@Aspect
public class DataSourceAspect {
@Pointcut("execution(* top.user.service.*.*(..))")
public void pointCut(){
}
@Before("pointCut()")
public void doBefore(JoinPoint joinPoint) throws NoSuchMethodException {
//获取连接点所在的目标对象
Object target = joinPoint.getTarget();
//获取目标对象的字节码文件对象
Class clazz = target.getClass();
//获取方法名
String methodName = joinPoint.getSignature().getName();
//获取参数类型
Class<?>[] parameterTypes = ((MethodSignature)joinPoint.getSignature()).getMethod().getParameterTypes();
Method method = clazz.getDeclaredMethod(methodName,parameterTypes);
//方法是否有这个注解
if (method!=null && method.isAnnotationPresent(DataSource.class)){
DataSource dataSource = method.getAnnotation(DataSource.class);
//设置数据源
DynamicDataSourceHolder.setDataSource(dataSource.value());
}
}
}
下面是测试方法,大家可以自己测试。
UserService
public interface UserService {
User selectById(long id);
void setUser();
}
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
//使用从数据源
@DataSource(DataSourceType.SLAVE)
public User selectById(long id){
return userMapper.selectByPrimaryKey(id);
}
@Transactional
//默认是主数据源
@DataSource
public void setUser() {
User user = new User();
user.setMobile("123asddwq45665");
User user1 = new User();
user1.setId(2L);
user1.setMobile("32142536");
userMapper.insertSelective(user);
userMapper.insertSelective(user1);
}
}
controller
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/selectByUserId")
@ResponseBody
public User selectByUserId(){
User user = userService.selectById(1L);
return user;
}
@RequestMapping("/setUser")
public void setUser(){
userService.setUser();
}
}
接着我们调用方法进行测试
首先执行selectByUserId方法
我们可以看到使用的是从服务器
接着执行setUser方法
使用的是主数据源