Spring Aop实现数据库读写分离

本文介绍了一种数据库读写分离的实现方式,通过主数据库处理事务性查询,从数据库处理SELECT查询,以此降低数据库压力。文章详细介绍了使用Spring AOP实现多数据源切换的方法,并讨论了不同方案的优缺点。

读写分离

数据库读写分离是让主数据库处理事务性查询,而从数据库处理SELECT查询,也就是主数据库主要处理新增,修改,删除类的操作,当然也可以处理查询操作。通过增加物理机器,降低数据库的写压力,主从职责明确,很大程度避免了X锁和s锁的争用。主从分离,适用于可以接受一定程度的读延迟,主从同步是通过主库的binlog日志来同步数据的,会有一定的数据延迟。

方案简单对比分析

要实现读写分离就要考虑解决多数据源的问题。
a.可以采用Spring Transaction对多数据源支持的方案,在项目中配置多个数据源,多个sqlsesssion,在使用时通过注解指定数据源。

@Transactional("transactionManager1")

b.可以采用当当开源ShardingJDBC来实现多数据源的配置。
c.可以采用aop拦截,设定不同的业务采用不同的数据源。

对于方案a如果有的语句不需要在事务中执行,此时没有了注解哪么怎么确定采用哪种数据源呢?而且在事务嵌套中容易出现问题。方案b对事务支持不好,是弱事务的,也就是如果事务中间执行失败了,代码不会回滚,只会以尝试一定次数的方式来保证执行失败的任务重新执行,这对于幂等性、数据一致性都存在一定程度的问题。本文仅探讨aop的方式实现多数据源,从而实现读写分离。

spring aop实现

aop主要是为了在执行数据库操作之前拦截,然后设置数据源,这时考虑到事务的特点,所以对service层进行拦截。创建切面类DataSourceAspect,代码如下:

    @Pointcut(".......")
    public void dataSourceAspectj() {
    }

    @Around(value = "dataSourceAspectj()")
    public Object aroundAdvice(final ProceedingJoinPoint point) throws Throwable {
        try {
            String dataSourceType = DataSourceTypeManager.getDataSourceType();
            if (符合某种条件) {
                DataSourceTypeManager.setDataSourceType(DataSourceTypeManager.DATA_SOURCE_MASTER);
            }else{
                DataSourceTypeManager.setDataSourceType(DataSourceTypeManager.DATA_SOURCE_SLAVER);
            }
            return point.proceed();
        } finally {
            DataSourceTypeManager.clearDataSourceType();
        }

    }
复制代码

DataSourceTypeManager类来保存数据源类型

public class DataSourceTypeManager {
    public static String DATA_SOURCE_SLAVE = "slave";
    public static String DATA_SOURCE_MASTER = "master";

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }
    public static String getDataSourceType() {
        return contextHolder.get();
    }
    public static void clearDataSourceType() {
        contextHolder.remove();
    }
}
复制代码

看了上面的代码你可能存在疑问,设置了数据源类型,在哪里会用到呢?在执行sql语句之前,我们都知道需要先获取数据库的连接,生成statement等一系列步骤才能得到想要的结果,那么我们设置的数据源类型就是在获取数据库连接的时候用到的。获取数据的连接在AbstractRoutingDataSource类中,该类的源码部分如下(可以忽略不看):

	@Override
	public Connection getConnection() throws SQLException {
		return determineTargetDataSource().getConnection();
	}
	。。。。。
	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是个抽象方法,我们只需实现该抽象方法返回自己的数据源即可,因此定义DynamicDataSource类

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
     
        return DataSourceTypeManager.getDataSourceType();
    
    }
}
复制代码

DynamicDataSource继承了AbstractRoutingDataSource, 也是DataSource类,数据源配置的代码如下:

@Bean(name="dataSource")
    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                        @Qualifier("slaveDataSource") DataSource slaveDataSource
                                        ) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceTypeManager.DATA_SOURCE_SLAVE, slaveDataSource);
        targetDataSources.put(DataSourceTypeManager.DATA_SOURCE_MASTER, masterDataSource);
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);
        dataSource.setDefaultTargetDataSource(masterDataSource);
        return dataSource;
    }
复制代码

注意事项

当在service层调用dao层进行数据库处理时,若service没有启动事务机制,则执行的顺序为:切面——>determineCurrentLookupKey——>Dao方法。而当在service层启动事务时,由于在一个事务中执行失败后会回滚之前所执行的所有操作,因此spring会在service方法执行前调用determineCurrentLookupKey,那么需要在切面上设置如下注解,才能保证先执行切面。

@Order(1)

转载于:https://juejin.im/post/5b7e5f5ef265da43531cff5d

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值