Source code for this article: GitHub. Click here || GitEE. Click here
1. Application of Multiple Data Sources
1. Basic Description
In relatively complex application services, configuring multiple data sources is a common phenomenon, such as: configuring a master-slave database to write data, and configuring a slave database to read data. This read-write separation mode can alleviate database pressure, improve concurrency and stability of the system, and improve execution efficiency.
2. Core API
To address this common problem, learn to query the API of the Service Infrastructure. To put it straight, it's the API of the Query Spring Framework (you haven't used a framework other than Spring for several years). This common business model is basically supported by Spring.
Core API: AbstractRoutingDataSource
The underlying maintenance Map container holds the collection of data sources and provides an abstract way to implement custom routing strategies.
@Nullable private Map<Object, DataSource> resolvedDataSources; @Nullable protected abstract Object determineCurrentLookupKey();
One more sentence: Why is the principle of the frame difficult to understand in an article?Because of the lack of use, the basic consciousness has not been formed, and the basic requirements of familiarizing yourself with the framework principles are: familiar with the various functions of the framework, frequently used, naturally you will understand that salted fish is only tasty after long exposure to salt.
2. Data Source Routing
1. Data Source Management
Configure two data sources
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.jdbc.Driver master: url: jdbc:mysql://localhost:3306/data_master username: root password: 123456 slave: url: jdbc:mysql://localhost:3306/data_slave username: root password: 123456
From the practical development point of view, these two data sources need to configure the master-slave replication process. Then, from the security point of view, the write library can only give write permissions and the read library can only give read permissions.
Map Container Loading
@Configuration public class DruidConfig { // Ignore parameter loading, there are @Bean @Primary public DataSource primaryDataSource() { Map<Object, Object> map = new HashMap<>(); map.put("masterDataSource", masterDataSource()); map.put("slaveDataSource", slaveDataSource()); RouteDataSource routeDataSource = new RouteDataSource(); routeDataSource.setTargetDataSources(map); routeDataSource.setDefaultTargetDataSource(masterDataSource()); return routeDataSource ; } private DataSource masterDataSource() { return getDefDataSource(masterUrl,masterUsername,masterPassword); } private DataSource slaveDataSource() { return getDefDataSource(slaveUrl,slaveUsername,slavePassword); } private DataSource getDefDataSource (String url,String userName,String passWord){ DruidDataSource datasource = new DruidDataSource(); datasource.setDriverClassName(driverClassName); datasource.setUrl(url); datasource.setUsername(userName); datasource.setPassword(passWord); return datasource; } }
The Map container here manages two keys, master DataSource and slaveDataSource representing two different libraries, which are loaded using different keys.
2. Container Key Management
ThreadLocal is used to manage thread parameters in the current session and is extremely convenient to access and use.
public class RouteContext implements AutoCloseable { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void setRouteKey (String key){ threadLocal.set(key); } public static String getRouteKey() { String key = threadLocal.get(); return key == null ? "masterDataSource" : key; } @Override public void close() { threadLocal.remove(); } }
3. Routing Key Implementation
Gets the key of the current data source in ThreadLocal and adapts the associated data source.
public class RouteDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return RouteContext.getRouteKey(); } }
3. Separation of reading and writing
1. AOP thinking
Based on AOP's facet idea, different method types set corresponding routing keys so that you can switch to different data sources before the business logic executes.
Aspect @Component @Order(1) public class ReadWriteAop { private static Logger LOGGER = LoggerFactory.getLogger(ReadWriteAop.class) ; @Before("execution(* com.master.slave.controller.*.*(..))") public void setReadDataSourceType() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String method = request.getRequestURI() ; boolean rwFlag = readOrWrite(method) ; if (rwFlag){ RouteContext.setRouteKey("slaveDataSource"); } else { RouteContext.setRouteKey("masterDataSource"); } LOGGER.info("Request Method:"+method+";Execution Library:"+RouteContext.getRouteKey()); } private String[] readArr = new String[]{"select","count","query","get","find"} ; private boolean readOrWrite (String method){ for (String readVar:readArr) { if (method.contains(readVar)){ return true ; } } return false ; } }
Common methods of reading: select, count, query, get, find, and so on. The naming of methods follows custom routing rules.
2. Provide test interfaces
Control Layer API
import com.master.slave.entity.User; import com.master.slave.service.UserService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController public class UserController { @Resource private UserService userService ; @GetMapping("/selectById") public User selectById (@RequestParam("id") Integer id) { return userService.selectById(id) ; } @GetMapping("/insert") public String insert () { User user = new User("Zhang San","write") ; userService.insert(user) ; return "success" ; } }
Service implementation
@Service public class UserService { @Resource private UserMapper userMapper ; public User selectById (Integer id) { return userMapper.selectById(id) ; } public void insert (User user){ userMapper.insert(user); } }
This allows data sources to switch dynamically over and over based on different types of methods.
4. Source code address
GitHub·address https://github.com/cicadasmile/data-manage-parent GitEE·address https://gitee.com/cicadasmile/data-manage-parent