在mysql的主从架构,或者一主多从的架构中,需要做读写分离。除了使用中间件实现外,还可以使用SpringAOP在代码层面实现。下面是配置的过程以及代码。
1.使用Spring提供的AbstractRoutingDataSource实现多数据源的切换。
使用时需要继承这个类,实现其中的抽象方法。定义一个类RoutingDataSource继承AbstractRoutingDataSource
public class RoutingDataSource extends AbstractRoutingDataSource{
private AtomicInteger count = new AtomicInteger(-1);
//存储所有的从库key
private List<Object> slaves = new ArrayList<Object>();
protected Object determineCurrentLookupKey() {
return RoutingDataSourceHandler.isMaster()?
RoutingDataSourceHandler.getDataSourceKey():getSlaveKey();
}
//轮询实现获取key
private Object getSlaveKey(){
Integer index = count.incrementAndGet() % slaves.size();
if (count.get() > 9999) // 以免超出Integer范围
count.set(-1); // 还原
return slaves.get(index);
}
//重写此方法初始化这个类
public void afterPropertiesSet() {
super.afterPropertiesSet();
//通过反射获取父类私有属性
Field field = ReflectionUtils.findField(AbstractRoutingDataSource.class,
"resolvedDataSources");
try{
Map<Object, DataSource> resolvedDataSources =
(Map<Object, DataSource>) field.get(this);
for (Map.Entry<Object, DataSource> entry : resolvedDataSources.entrySet()) {
if (DataSourceType.MASTER.equals(entry.getKey()))
continue;
slaves.add(entry.getKey());
}
}catch(Exception e){
}
}
}
2.使用ThreadLocal解决线程安全问题。
首先定义一个枚举
public enum DataSourceType {
MASTER,SLAVE
}
RoutingDataSourceHandler:
public class RoutingDataSourceHandler {
private static final ThreadLocal<DataSourceType> typeContent =
new ThreadLocal<DataSourceType>();
public static void putDataSourceKey(DataSourceType key) {
typeContent.set(key);
}
public static DataSourceType getDataSourceKey() {
return typeContent.get();
}
public static void markMaster() {
putDataSourceKey(DataSourceType.MASTER);
}
public static void markSlave() {
putDataSourceKey(DataSourceType.SLAVE);
}
public static boolean isMaster() {
return DataSourceType.MASTER.equals(getDataSourceKey());
}
}
3.定义一个注解,用在方法上标记方法为读或写。若不做注解,则按照方法名开头来决定使用读库或者写库。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ChoseDataSource {
DataSourceType value() default DataSourceType.MASTER;
}
4.定义一个切面类DataSourceAspect
public class DataSourceAspect{
private List<String> slaveMethodPattern = new ArrayList<String>();
@SuppressWarnings("unchecked")
public void setTxAdvice(TransactionInterceptor txAdvice) throws Exception{
Assert.notNull(txAdvice, "请注入事务策略");
// 获取事务配置信息
TransactionAttributeSource transactionAttributeSource
= txAdvice.getTransactionAttributeSource();
if (!(transactionAttributeSource instanceof NameMatchTransactionAttributeSource))
return;
NameMatchTransactionAttributeSource matchTransactionAttributeSource =
(NameMatchTransactionAttributeSource) transactionAttributeSource;
Field nameMapField = ReflectionUtils.findField(
matchTransactionAttributeSource.getClass(), "nameMap");
nameMapField.setAccessible(true); // 设置该字段可访问
// 获取nameMap的值
Map<String, TransactionAttribute> map =
(Map<String, TransactionAttribute>) nameMapField
.get(matchTransactionAttributeSource);
// 遍历nameMap
for (Map.Entry<String, TransactionAttribute> entry : map.entrySet()) {
if (!entry.getValue().isReadOnly())
continue;
slaveMethodPattern.add(entry.getKey());
}
public void before(JoinPoint point) {
Object target = point.getTarget();
String methodName = point.getSignature().getName();
Class<?> clazz = target.getClass();
Method method = ReflectionUtils.findMethod(clazz, methodName, (Class<?>) null);
Assert.notNull(method);
if (method.isAnnotationPresent(ChoseDataSource.class) || isSlave(methodName,slaveMethodPattern{
RoutingDataSourceHandler.putDataSourceKey(method.getAnnotation(ChoseDataSource.class).type());
}
}
private Boolean isSlave(String methodName,List<String> slaveMethodPattern) {
for (String mappedName : slaveMethodPattern) {
if (PatternMatchUtils.simpleMatch(methodName, mappedName)) {
return true;
}
}
return false;
}
}