spring实现读写分离aop注解方式

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring实现读写分离aop注解方式相关的知识,希望对你有一定的参考价值。

1.场景,实现数据库的读写分离。

2.思路,既然是读写分离,那就是需要切换不同的数据源,一种是静态切换,就是提前配置好两个静态数据库资源,还有一种就是动态的切换资源,这里用到spring,那就要知道spring如何动态的切换数据源。

3.spring提供了动态切换数据源接口AbstractRoutingDataSource,关于AbstractRoutingDataSource这个类我们可以看下它的源码

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()这个用方法去获取key,然后根据key去获取对应的数据源,这个时候我们就可以集成这个重写这个方法代码如下:

public class DynamicDataSource extends AbstractRoutingDataSource{

    @Override
    protected Object determineCurrentLookupKey() {
        // TODO Auto-generated method stub
        return DynamicDataSourceHolder.getDataSource();
    }

}

DynamicDataSourceHolder类:

public class DynamicDataSourceHolder {
    
    public final static ThreadLocal<String> holder = new ThreadLocal<String>();
    
    public static void putDataSource(String name){
        holder.set(name);
    }
    
    public static String getDataSource(){
        return holder.get();
    }

}

使用ThreadLocal是为了防止并发带来的问题,保证每个线程用到的是自己的数据源。

上面获取key的方法会在下面会用。

 

3.解决了动态获取数据源下面就是,如何实现当我调用读方法时切换到读数据源,实现写操作时切换到写数据源。

这里就可以用到spring aop 面向切面编程,为了方便操作我们可以写一个自定注解使用自定义的注解去注解那个方法是读操作那个方法是写操作

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

}

 

aop类:

@Component
@Aspect
public class DaoAspect {

    @Pointcut("execution(* com.kedacom.aop.service.*.*(..))")
    public void read(){}
    
    @Before("read()")
    public void beforeRead(JoinPoint point){
        
        Object object = point.getTarget();
        
        //获取方法名称
        String methodName = point.getSignature().getName();
        Class<?>[] parameterTypes = ((MethodSignature)point.getSignature()).getMethod().getParameterTypes();
        
        try {
            //获取到方法
            Method method = object.getClass().getMethod(methodName, parameterTypes);
            
            //获取注解中的值
            DataSource dataSource = method.getAnnotation(DataSource.class);
            
            //获取主从数据库的key以便切换数据库
            String dbKey = dataSource.value();
            
            DynamicDataSourceHolder.putDataSource(dbKey);
            
            System.out.println(dbKey);
            
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }    
    }
}

这里使用 到aop的注解功能。

两个服务了类:

@Service("readService")
public class ReadService {

    @Resource(name="userDao")
    UserDao userDao;
    
    @DataSource(value="slave")
    public User readUser(String id){
        
        return userDao.getUser(id);
    
    }
}
@Service("writeService")
public class WriteService {

    @Resource(name="userDao")
    UserDao userDao;
    
    @DataSource(value="master")
    public void writeDb(User user){
        userDao.save(user);
    }
}

测试代码:

public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        
//        PersonServer bean = (PersonServer)ctx.getBean("personServiceBean");
//        bean.save("fasdfa");
        WriteService ws = (WriteService)ctx.getBean("writeService");
        User user = new User();
        user.setId("123");
        user.setName("xxx");
        user.setPasswd("ok");
        
        ws.writeDb(user);
        
        ReadService rs = (ReadService)ctx.getBean("readService");
        User u = rs.readUser("1");
        System.out.println(u.getId()+"--"+u.getName()+"--"+u.getPasswd());
    }

 

spring的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr"
    xmlns:dwra="http://www.directwebremoting.org/schema/spring-dwr-annotations"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/aop
                        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.2.xsd
                        http://www.springframework.org/schema/tx
                        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
                        http://www.directwebremoting.org/schema/spring-dwr
                        http://www.directwebremoting.org/schema/spring-dwr-3.0.xsd
                        http://www.directwebremoting.org/schema/spring-dwr-annotations    
                          http://www.directwebremoting.org/schema/spring-dwr-annotations.xsd ">
    <context:annotation-config />
    <context:component-scan base-package="com.guo.*,com.kedacom.*" />
    <aop:aspectj-autoproxy/> 
    <bean id="personServiceBean" class="com.guo.test.PersonServerBean"/> 
    <!-- <bean id="myInterceptor" class="com.guo.test.MyInterceptor"/>   -->
    
    <!-- 主数据库源 -->
    <bean id="masterDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/maven?useUnicode=true&amp;characterEncoding=UTF-8"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    </bean> 
    <!-- 从数据库源 -->
    <bean id="slaveDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/test_user?useUnicode=true&amp;characterEncoding=UTF-8"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    </bean> 
    <!-- 配置动态数据源 -->
    <bean id="dataSource" class="com.kedacom.aop.DynamicDataSource">
        <property name="targetDataSources">  
              <map key-type="java.lang.String">  
                  <!-- write -->
                 <entry key="master" value-ref="masterDataSource"/>  
                 <!-- read -->
                 <entry key="slave" value-ref="slaveDataSource"/>  
              </map>               
        </property>  
        <!-- 默认数据源 -->
        <property name="defaultTargetDataSource" ref="masterDataSource"/>  
    </bean>
    
    <bean id="daoJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource" />
    </bean>
</beans>

 

以上是关于spring实现读写分离aop注解方式的主要内容,如果未能解决你的问题,请参考以下文章

读写分离很难吗?Spring Boot 结合 aop 简单就实现了

Spring AOP 实现读写分离

spring AOP 实现事务和主从读写分离

使用Spring AOP实现MySQL读写分离

Spring+Mybatis实现主从数据库读写分离

spring AOP注解实现