前段时间刚换了家新公司,然后看项目代码里用了数据库读写分离的架构,然后好奇扒了代码简单看了下,总体来说就是运用spring aop切面方式来实现的。看明白后就在自己的个人小项目里运用了下,测试OK,所以下面总结下流程:
1、首先定义一个数据源注解,它有两个值,一个对应写库(主库),一个对应读库(从库)
复制代码
package com.jdd.ds;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String DATA_SOURCE_READ = "dataSourceRead";
String DATA_SOURCE_WRITE = "dataSourceWrite";
String name() default "dataSourceWrite";
}
复制代码
2、在需要拦截的方法上加上该注解
复制代码
package com.jdd.service.impl;
import com.jdd.dao.UserDao;
import com.jdd.ds.DataSource;
import com.jdd.pojo.User;
import com.jdd.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
@DataSource(name=DataSource.DATA_SOURCE_READ)
@Override
public User getUserByNameAndPassword(String name, String password) {
return userDao.getUserByNameAndPassword(name, password);
}
@DataSource(name=DataSource.DATA_SOURCE_WRITE)
@Override
public int insertUser(User user) {
return userDao.insertUser(user);
}
}
复制代码
3、然后在配置文件里加上aop切面配置:
复制代码
复制代码
4、然后我们要在切面类里,获取到方法上的dataSource注解里设置的值,从而决定使用哪个数据源
复制代码
package com.jdd.ds;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
public class DataSourceExchange {
private static Logger logger = LoggerFactory.getLogger(DataSourceExchange.class);
public Object execute(ProceedingJoinPoint pjp){
logger.info("DataSourceExchange==>");
Object obj = null;
Signature signature = pjp.getSignature();
MethodSignature methodSignature = (MethodSignature)signature;
Method targetMethod = methodSignature.getMethod();
Method realMethod = null;
try {
realMethod = pjp.getTarget().getClass().getDeclaredMethod(signature.getName(), targetMethod.getParameterTypes());
if(realMethod.isAnnotationPresent(DataSource.class)){
DataSource datasource = (DataSource) realMethod.getAnnotation(DataSource.class);
DataSourceContext.setDbType(datasource.name());
logger.info(realMethod.getName() +" set dbtype==>"+datasource.name());
}else{
DataSourceContext.setDbType("dataSourceWrite");
}
obj = pjp.proceed();
} catch (Throwable t) {
t.printStackTrace();
}
logger.info(realMethod.getName()+" clear datatype==>");
DataSourceContext.clearDbType();
return obj;
}
}
复制代码
在方法执行前,设置具体数据源,然后方法执行完后,再清除掉该值。
注:注意上面的通过反射获取业务方法的代码, 开始的时候用
MethodSignature signature = (MethodSignature)pjp.getSignature();
Method method = signature.getMethod();
发现通过这种方法获取的method,它是没有注解信息的。后来在网上搜了下,大概说是这种方法获取的是代理方法,不是目标方法。代理方法是不带注解信息的,所以这块注意下。
5、下面看下 DataSourceContext 类,作用自然就是设置数据源上下文。
复制代码
package com.jdd.ds;
public class DataSourceContext {
public static final String DATA_SOURCE_READ = "dataSourceRead";
public static final String DATA_SOURCE_WRITE = "dataSourceWrite";
private static final ThreadLocal
contextHolder = new ThreadLocal();
public DataSourceContext(){
}
public static void setDbType(String dbType){
contextHolder.set(dbType);
}
public static String getDbType(){
return (String)contextHolder.get();
}
public static void clearDbType(){
contextHolder.remove();
}
}
复制代码
6、下面定义 MultipleDataSource类, 继承与spring的 AbstractRoutingDataSource类, 并重写它的 determineCurrentLookupKey 方法, 作用就是从上下文获取当前线程使用的数据源标识:
复制代码
package com.jdd.ds;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class MultipleDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
Object key = DataSourceContext.getDbType();
if(key != null){
this.logger.info("当前线程使用的数据源标识为 [ " + key.toString() + " ].");
}
return key;
}
}
复制代码
7、最后在 配置文件 applicationContext-mysql.xml 里 配上数据源
复制代码
复制代码
8、到这里和spring相关的配置基本就配完了, 其实后面还要再配置一下 mysql的主从复制,就是对写库的操作都同步到从库,这样写库从库的数据才一致。具体配置操作我就不写了,可以自行到网上搜索。
https://www.cnblogs.com/xiexin2015/p/8998647.html