Spring实现数据库的读写分离

    xiaoxiao2021-03-25  119

    在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; } }
    转载请注明原文地址: https://ju.6miu.com/read-19815.html

    最新回复(0)