基于spring AOP进行数据源的切换,AbstractRoutingDataSource源码解析

我们准备了三个数据源,一主两从,并且配置了主从复制。

这里我们之说一下多数据源的配置,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();
}

UserServiceImpl

@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方法
在这里插入图片描述
使用的是主数据源

猜你喜欢

转载自blog.csdn.net/ROAOR1/article/details/88395912