环境:springboot2.2.6RELEASE
创新互联是专业的老城网站建设公司,老城接单;提供成都网站设计、网站制作,网页设计,网站设计,建网站,PHP网站建设等专业做网站服务;采用PHP框架,可快速的进行老城网站开发网页制作和功能扩展;专业做搜索引擎喜爱的网站,专业的做网站团队,希望更多企业前来合作!
实现目标:一写多读,读可以任意配置多个,默认都是从写库中进行操作,只有符合条件的方法(指定的目标方法或者标有指定注解的方法才会从读库中操作)。独立打成一个jar包放入本地仓库。
实现原理:通过aop。
org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-configuration-processor true
- pack:
- datasource:
- pointcut: execution(public * net.greatsoft.service.base.*.*(..)) || execution(public * net.greatsoft.service.xxx.*.*(..))
- master:
- driverClassName: oracle.jdbc.driver.OracleDriver
- jdbcUrl: jdbc:oracle:thin:@10.100.102.113:1521/orcl
- username: test
- password: test
- minimumIdle: 10
- maximumPoolSize: 200
- autoCommit: true
- idleTimeout: 30000
- poolName: MbookHikariCP
- maxLifetime: 1800000
- connectionTimeout: 30000
- connectionTestQuery: SELECT 1 FROM DUAL
- slaves:
- - driverClassName: oracle.jdbc.driver.OracleDriver
- jdbcUrl: jdbc:oracle:thin:@10.100.102.113:1521/orcl
- username: dc
- password: dc
- minimumIdle: 10
- maximumPoolSize: 200
- autoCommit: true
- idleTimeout: 30000
- poolName: MbookHikariCP
- maxLifetime: 1800000
- connectionTimeout: 30000
- connectionTestQuery: SELECT 1 FROM DUAL
- - driverClassName: oracle.jdbc.driver.OracleDriver
- jdbcUrl: jdbc:oracle:thin:@10.100.102.113:1521/orcl
- username: empi
- password: empi
- minimumIdle: 10
- maximumPoolSize: 200
- autoCommit: true
- idleTimeout: 30000
- poolName: MbookHikariCP
- maxLifetime: 1800000
- connectionTimeout: 30000
- connectionTestQuery: SELECT 1 FROM DUAL
pointcut:定义切点,那些方法是需要拦截(从读库中操作)。
master:写库配置。
slaves:读库配置(List集合)。
- @Component
- @ConfigurationProperties(prefix = "pack.datasource")
- public class RWDataSourceProperties {
- private String pointcut ;
- private HikariConfig master ;
- private List
slaves = new ArrayList<>(); - }
- public class RWConfig {
- private static Logger logger = LoggerFactory.getLogger(RWConfig.class) ;
- @Bean
- public HikariDataSource masterDataSource(RWDataSourceProperties rwDataSourceProperties) {
- return new HikariDataSource(rwDataSourceProperties.getMaster()) ;
- }
- @Bean
- public List
slaveDataSources(RWDataSourceProperties rwDataSourceProperties) { - List
lists = new ArrayList<>() ; - for(HikariConfig config : rwDataSourceProperties.getSlaves()) {
- lists.add(new HikariDataSource(config)) ;
- }
- return lists ;
- }
- @Bean
- @Primary
- @DependsOn({"masterDataSource", "slaveDataSources"})
- public AbstractRoutingDataSource routingDataSource(@Qualifier("masterDataSource")DataSource masterDataSource,
- @Qualifier("slaveDataSources")List
slaveDataSources) { - BaseRoutingDataSource ds = new BaseRoutingDataSource() ;
- Map
- targetDataSources.put("master", masterDataSource) ;
- for (int i = 0; i < slaveDataSources.size(); i++) {
- targetDataSources.put("slave-" + i, slaveDataSources.get(i)) ;
- }
- ds.setDefaultTargetDataSource(masterDataSource) ;
- ds.setTargetDataSources(targetDataSources) ;
- return ds ;
- }
- }
- public class BaseRoutingDataSource extends AbstractRoutingDataSource {
- @Resource
- private DataSourceHolder holder;
- @Override
- protected Object determineCurrentLookupKey() {
- return holder.get() ;
- }
- }
- public class DataSourceHolder {
- private ThreadLocal
context = new ThreadLocal () { - @Override
- protected Integer initialValue() {
- return 0 ;
- }
- };
- @Resource
- private BaseSlaveLoad slaveLoad ;
- public String get() {
- Integer type = context.get() ;
- return type == null || type == 0 ? "master" : "slave-" + slaveLoad.load() ;
- }
- public void set(Integer type) {
- context.set(type) ;
- }
- }
通过aop动态设置context的内容值,0为从写库中操作,其它的都在读库中操作。
BaseSlaveLoad类为到底从那个读库中选取的一个算法类,默认实现使用的是轮询算法。
- public interface BaseSlaveLoad {
- int load() ;
- }
- public abstract class AbstractSlaveLoad implements BaseSlaveLoad {
- @Resource
- protected List
slaveDataSources ; - }
这里定义一个抽象类注入了读库列表,所有的实现类从该类中继承即可。
- public class PollingLoad extends AbstractSlaveLoad {
- private int index = 0 ;
- private int size = 1 ;
- @PostConstruct
- public void init() {
- size = slaveDataSources.size() ;
- }
- @Override
- public int load() {
- int n = index ;
- synchronized (this) {
- index = (++index) % size ;
- }
- return n ;
- }
- }
配置成Bean
- @Bean
- @ConditionalOnMissingBean
- public BaseSlaveLoad slaveLoad() {
- return new PollingLoad() ;
- }
- @Bean
- public DataSourceHolder dataSourceHolder() {
- return new DataSourceHolder() ;
- }
- public class DataSourceAspect implements MethodInterceptor {
- private DataSourceHolder holder ;
- public DataSourceAspect(DataSourceHolder holder) {
- this.holder = holder ;
- }
- @Override
- public Object invoke(MethodInvocation invocation) throws Throwable {
- Method method = invocation.getMethod() ;
- String methodName = method.getName() ;
- SlaveDB slaveDB = method.getAnnotation(SlaveDB.class) ;
- if (slaveDB == null) {
- slaveDB = method.getDeclaringClass().getAnnotation(SlaveDB.class) ;
- }
- if (methodName.startsWith("find")
- || methodName.startsWith("get")
- || methodName.startsWith("query")
- || methodName.startsWith("select")
- || methodName.startsWith("list")
- || slaveDB != null) {
- holder.set(1) ;
- } else {
- holder.set(0) ;
- }
- return invocation.proceed();
- }
- }
应该切点需要动态配置,所以这里采用spring aop的方式来配置
- @Bean
- public AspectJExpressionPointcutAdvisor logAdvisor(RWDataSourceProperties props, DataSourceHolder holder) {
- AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor() ;
- logger.info("执行表达式:{}", props.getPointcut()) ;
- advisor.setExpression(props.getPointcut()) ;
- advisor.setAdvice(new DataSourceAspect(holder)) ;
- return advisor ;
- }
- public class RWImportSelector implements ImportSelector {
- @Override
- public String[] selectImports(AnnotationMetadata importingClassMetadata) {
- return new String[] {RWConfig.class.getName()} ;
- }
- }
这里的RWConfig为我们上面的配置类
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.TYPE)
- @Documented
- @Import({RWImportSelector.class})
- public @interface EnableRW {
- }
- @Documented
- @Retention(RUNTIME)
- @Target({ TYPE, METHOD })
- public @interface SlaveDB {
- }
有@SlaveDB的注解方法会类都会从读库中操作。
到此读写分离组件开发完成。
- mvn install -Dmaven.test.skip=true
引入依赖
com.pack xg-component-rw 1.0.0
启动类添加注解开启读写分离功能
- @SpringBootApplication
- @EnableRW
- public class BaseWebApplication {
- public static void main(String[] args) {
- SpringApplication.run(BaseWebApplication.class, args);
- }
- }
测试:
第一次查询:
第二次查询:
为了区别两个从库设置不同的数据
这里是写库
完毕!!!
文章名称:SpringBoot读写分离组件开发详解
当前链接:http://www.shufengxianlan.com/qtweb/news36/497736.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联