Home > Java > javaTutorial > How Spring boot implements database read-write separation

How Spring boot implements database read-write separation

高洛峰
Release: 2018-05-18 11:14:29
Original
2483 people have browsed it

Background

After configuring the master-slave database, how to achieve read-write separation at the code level?

User-defined setting of database routing

Spring boot provides AbstractRoutingDataSource to select the current database according to user-defined rules, so that we can set the reading slave library before executing the query, and after the execution is completed Afterwards, restore to the main database.

Implement a dynamically routable data source and execute it before each database query operation

ReadWriteSplitRoutingDataSource.java

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 
/**
 * @author songrgg
 * @since 1.0
 */
public class ReadWriteSplitRoutingDataSource extends AbstractRoutingDataSource {
  @Override
  protected Object determineCurrentLookupKey() {
    return DbContextHolder.getDbType();
  }
}
Copy after login

Thread private routing configuration, used for ReadWriteSplitRoutingDataSource dynamically Read configuration

DbContextHolder.java

/**
 * @author songrgg
 * @since 1.0
 */
public class DbContextHolder {
  public enum DbType {
    MASTER,
    SLAVE
  }
 
  private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<>();
 
  public static void setDbType(DbType dbType) {
    if(dbType == null){
      throw new NullPointerException();
    }
    contextHolder.set(dbType);
  }
 
  public static DbType getDbType() {
    return contextHolder.get() == null ? DbType.MASTER : contextHolder.get();
  }
 
  public static void clearDbType() {
    contextHolder.remove();
  }
}
Copy after login

AOP optimization code

Use AOP to extract the operation of setting the database from the code. The granularity control here is in the method level, so use the form of annotations to mark the database transactions involved in this method as read-only and go from the database.

Read-only annotation, used to mark the database operation of the method only from the database.

ReadOnlyConnection.java

package com.wallstreetcn.hatano.config;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
/**
 * Indicates the database operations is bound to the slave database.
 * AOP interceptor will set the database to the slave with this interface.
 * @author songrgg
 * @since 1.0
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnlyConnection {
}
Copy after login

ReadOnlyConnectionInterceptor.java

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
 
/**
 * Intercept the database operations, bind database to read-only database as this annotation
 * is applied.
 * @author songrgg
 * @since 1.0
 */
@Aspect
@Component
public class ReadOnlyConnectionInterceptor implements Ordered {
 
  private static final Logger logger = LoggerFactory.getLogger(ReadOnlyConnectionInterceptor.class);
 
  @Around("@annotation(readOnlyConnection)")
  public Object proceed(ProceedingJoinPoint proceedingJoinPoint, ReadOnlyConnection readOnlyConnection) throws Throwable {
    try {
      logger.info("set database connection to read only");
      DbContextHolder.setDbType(DbContextHolder.DbType.SLAVE);
      Object result = proceedingJoinPoint.proceed();
      return result;
    } finally {
      DbContextHolder.clearDbType();
      logger.info("restore database connection");
    }
  }
 
  @Override
  public int getOrder() {
    return 0;
  }
}
Copy after login

UserService.java

@ReadOnlyConnection
public List<User> getUsers(Integer page, Integer limit) {
  return repository.findAll(new PageRequest(page, limit));
}
Copy after login

Configure Druid database connection pool

build.gradle

compile("com.alibaba:druid:1.0.18")

groovy dependency injection

Configure dataSource as a routable data source

context.groovy

import com.alibaba.druid.pool.DruidDataSource
import DbContextHolder
import ReadWriteSplitRoutingDataSource
 
** SOME INITIALIZED CODE LOAD PROPERTIES **
def dataSourceMaster = new DruidDataSource()
dataSourceMaster.url = properties.get(&#39;datasource.master.url&#39;)
println("master set to " + dataSourceMaster.url)
dataSourceMaster.username = properties.get(&#39;datasource.master.username&#39;)
dataSourceMaster.password = properties.get(&#39;datasource.master.password&#39;)
 
def dataSourceSlave = new DruidDataSource()
dataSourceSlave.url = properties.get(&#39;datasource.slave.url&#39;)
println("slave set to " + dataSourceSlave.url)
dataSourceSlave.username = properties.get(&#39;datasource.slave.username&#39;)
dataSourceSlave.password = properties.get(&#39;datasource.slave.password&#39;)
beans {
  dataSource(ReadWriteSplitRoutingDataSource) { bean ->
    targetDataSources = [
        (DbContextHolder.DbType.MASTER): dataSourceMaster,
        (DbContextHolder.DbType.SLAVE): dataSourceSlave
    ]
  }
}
Copy after login

The above is the entire content of this article. I hope it will be helpful to everyone's learning. I also hope that everyone will support the PHP Chinese website.

For more articles related to Spring boot’s method of implementing database read-write separation, please pay attention to the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template