This article introduces Spring Boot + MyBatis read and write separation. Those who need to know Spring + MyBatis read and write separation can refer to it. Hope this article is helpful to you.
Its final implementation functions:
- The default update operation uses the write data source
- Read operations use slave data source
- Special settings: You can specify the type and name of the data source to be used (if there is a name, the corresponding data source will be used according to the name)
The implementation principle is as follows:
- Intercept the dao layer interface through Spring AOP, and set the data source type and name of the interface that needs to specify the data source in ThradLocal
- Through MyBatsi plug-in, set the data source in ThreadLocal based on update or query operations (if the dao layer is not specified)
- Inherit the AbstractRoutingDataSource class.
Here we write it directly and use HikariCP as the data source
The implementation steps are as follows:
- Define its data source configuration file and parse it into a data source
- Define AbstractRoutingDataSource class and other annotations
- Define Aop Intercept
- Define MyBatis plugin
- Integrate together
1. Configuration and parsing classes
Its configuration parameters are directly used by HikariCP configuration, and the specific parameters can be used as reference.HikariCP。
Used hereyaml
Format, name, the content is as follows:
dds: write: jdbcUrl: jdbc:mysql://localhost:3306/order password: liu123 username: root maxPoolSize: 10 minIdle: 3 poolName: master read: - jdbcUrl: jdbc:mysql://localhost:3306/test password: liu123 username: root maxPoolSize: 10 minIdle: 3 poolName: slave1 - jdbcUrl: jdbc:mysql://localhost:3306/test2 password: liu123 username: root maxPoolSize: 10 minIdle: 3 poolName: slave2
Define the bean corresponding to this configuration, with the name DBConfig, and the content is as follows:
@Component @ConfigurationProperties(locations = "classpath:", prefix = "dds") public class DBConfig { private List<HikariConfig> read; private HikariConfig write; public List<HikariConfig> getRead() { return read; } public void setRead(List<HikariConfig> read) { = read; } public HikariConfig getWrite() { return write; } public void setWrite(HikariConfig write) { = write; } }
The tool class that converts configuration to DataSource, name:DataSourceUtil
, the content is as follows:
import ; import ; import ; import ; import ; public class DataSourceUtil { public static DataSource getDataSource(HikariConfig config) { return new HikariDataSource(config); } public static List<DataSource> getDataSource(List<HikariConfig> configs) { List<DataSource> result = null; if (configs != null && () > 0) { result = new ArrayList<>(()); for (HikariConfig config : configs) { (getDataSource(config)); } } else { result = new ArrayList<>(0); } return result; } }
2. Annotations and dynamic data sources
Define annotation @DataSource, which is used to specify the data source to be used for individual methods (such as a read operation needs to be executed on the master, but another read method b needs to be executed on the specific one of the read data source)
@Retention() @Target() public @interface DataSource { /** * Type, which means whether to use read or write * @return */ DataSourceType type() default ; /** * Specify the name of the DataSource to use * @return */ String name() default ""; }
Define the data source type, which is divided into two types: READ, WRITE, and the content is as follows:
public enum DataSourceType { READ, WRITE; }
Define the class that holds this shared informationDynamicDataSourceHolder
, in which two are definedThreadLocal
and a map,holder
Used to save the data source type (read or write) of the current thread.pool
Used to save the data source name (if specified), its content is as follows:
import ; import ; public class DynamicDataSourceHolder { private static final Map<String, DataSourceType> cache = new ConcurrentHashMap<>(); private static final ThreadLocal<DataSourceType> holder = new ThreadLocal<>(); private static final ThreadLocal<String> pool = new ThreadLocal<>(); public static void putToCache(String key, DataSourceType dataSourceType) { (key,dataSourceType); } public static DataSourceType getFromCach(String key) { return (key); } public static void putDataSource(DataSourceType dataSourceType) { (dataSourceType); } public static DataSourceType getDataSource() { return (); } public static void putPoolName(String name) { if (name != null && () > 0) { (name); } } public static String getPoolName() { return (); } public static void clearDataSource() { (); (); } }
Dynamic data source class isDynamicDataSoruce
, it inherits fromAbstractRoutingDataSource
, you can switch to the corresponding data source according to the returned key, and the content is as follows:
import ; import ; import ; import ; import ; import ; import ; import ; public class DynamicDataSource extends AbstractRoutingDataSource { private DataSource writeDataSource; private List<DataSource> readDataSource; private int readDataSourceSize; private Map<String, String> dataSourceMapping = new ConcurrentHashMap<>(); @Override public void afterPropertiesSet() { if ( == null) { throw new IllegalArgumentException("Property 'writeDataSource' is required"); } setDefaultTargetDataSource(writeDataSource); Map<Object, Object> targetDataSource = new HashMap<>(); ((), writeDataSource); String poolName = ((HikariDataSource)writeDataSource).getPoolName(); if (poolName != null && () > 0) { (poolName,()); } if ( == null) { readDataSourceSize = 0; } else { for (int i = 0; i < (); i++) { (() + i, (i)); poolName = ((HikariDataSource)(i)).getPoolName(); if (poolName != null && () > 0) { (poolName,() + i); } } readDataSourceSize = (); } setTargetDataSources(targetDataSource); (); } @Override protected Object determineCurrentLookupKey() { DataSourceType dataSourceType = (); String dataSourceName = null; if (dataSourceType == null ||dataSourceType == || readDataSourceSize == 0) { dataSourceName = (); } else { String poolName = (); if (poolName == null) { int idx = ().nextInt(0, readDataSourceSize); dataSourceName = () + idx; } else { dataSourceName = (poolName); } } (); return dataSourceName; } public void setWriteDataSource(DataSource writeDataSource) { = writeDataSource; } public void setReadDataSource(List<DataSource> readDataSource) { = readDataSource; } }
Intercept
If custom configuration is done in the corresponding dao layer (specifying the data source), some processing is performed. Analyze the corresponding method@DataSource
Note, if it exists, and save the corresponding information to the aboveDynamicDataSourceHolder
middle. Here's theThe package is intercepted. The content is as follows:
import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; /** * Use AOP interception, you can specify the data source name to use (corresponding to the connection pool name) for special methods. */ @Aspect @Component public class DynamicDataSourceAspect { @Pointcut("execution(public * .*.*(*))") public void dynamic(){} @Before(value = "dynamic()") public void beforeOpt(JoinPoint point) { Object target = (); String methodName = ().getName(); Class<?>[] clazz = ().getInterfaces(); Class<?>[] parameterType = ((MethodSignature)()).getMethod().getParameterTypes(); try { Method method = clazz[0].getMethod(methodName,parameterType); if (method != null && ()) { DataSource datasource = (); (()); String poolName = (); (poolName); (clazz[0].getName() + "." + methodName, ()); } } catch (Exception e) { (); } } @After(value = "dynamic()") public void afterOpt(JoinPoint point) { (); } }
Plugin
If the corresponding data source to be used is not specified at the dao layer, intercepting is performed here. According to whether the data source type is updated or queryed, the content is as follows:
import ; import ; import ; import .*; import ; import ; import ; @Intercepts({ @Signature(type = , method = "update", args = {, }), @Signature(type = , method = "query", args = {, , , }) }) public class DynamicDataSourcePlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement ms = (MappedStatement)()[0]; DataSourceType dataSourceType = null; if ((dataSourceType = (())) == null) { if (().equals()) { dataSourceType = ; } else { dataSourceType = ; } ((), dataSourceType); } (dataSourceType); return (); } @Override public Object plugin(Object target) { if (target instanceof Executor) { return (target, this); } else { return target; } } @Override public void setProperties(Properties properties) { } }
5. Integration
Define what MyBatis is to use andDataSource
, the content is as follows:
import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; @Configuration @MapperScan(value = "", sqlSessionFactoryRef = "sqlSessionFactory") public class DataSourceConfig { @Resource private DBConfig dbConfig; @Bean(name = "dataSource") public DynamicDataSource dataSource() { DynamicDataSource dataSource = new DynamicDataSource(); ((())); ((())); return dataSource; } @Bean(name = "transactionManager") public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("dataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "sqlSessionFactory") public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean(); (new ClassPathResource("")); (new PathMatchingResourcePatternResolver() .getResources("classpath*:mapper/*.xml")); (dataSource); return (); } }
If you are not clear, you can check the source code on githuborderdemo
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.