Spring boot + durid + mybatis + docker for mysql read-write separation-2
preface
In the previous article, we built a mysql replication cluster of primary and secondary, and now we separate the read and write through the test project
Project structure
Code interpretation
Data source configuration
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://10.18.32.90:3306/copy_test?characterEncoding=utf-8 username: test password: test slave1: url: jdbc:mysql://10.18.32.90:3307/copy_test?characterEncoding=utf-8 username: test password: test slave2: url: jdbc:mysql://10.18.32.90:3308/copy_test?characterEncoding=utf-8 username: test password: test
Configure three data sources corresponding to one master and two slaves
DataSourceType
Define an enumeration class to declare the key value of the data source
public enum DataSourceType { MASTER, SLAVE1, SLAVE2 }
DBContextHolder
To switch the key classes of data source, we use ThreadLocal to hold the above enumeration variables, and use facet (described later) to switch the slave database data source for query operation. When ThreadLocal is empty, the master source is used by default
@Slf4j public class DBContextHolder { private static ThreadLocal<DataSourceType> threadLocal = new ThreadLocal<>(); private static void set(DataSourceType dataSourceType) { threadLocal.set(dataSourceType); } public static DataSourceType get() { return threadLocal.get() == null ? DataSourceType.MASTER : threadLocal.get(); } public static void remove() { threadLocal.remove(); } public static void slave() { SecureRandom secureRandom = new SecureRandom(); int i = secureRandom.nextInt(DataSourceType.values().length - 1) + 1; log.info("Use slave Library" +i+ "read"); set(DataSourceType.values()[i]); } }
RouteDataSource
The key of multi data source switching, inheriting AbstractRoutingDataSource and implementing determineCurrentLookupKey method
public class RouteDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DBContextHolder.get(); } }
DataSourceConfig
Data source configuration: register all data sources in the configuration file into the container. Here, you need to register one more targetDataSource, which is a configuration for multiple data sources. A small scheduling algorithm is used for the switch of slave
@Configuration public class DataSourceConfig { @Bean("master") @ConfigurationProperties(prefix = "spring.datasource") @Primary public DataSource master() { return new DruidDataSource(); } @Bean("slave1") @ConfigurationProperties(prefix = "spring.datasource.slave1") public DataSource slave1() { return new DruidDataSource(); } @Bean("slave2") @ConfigurationProperties(prefix = "spring.datasource.slave2") public DataSource slave2() { return new DruidDataSource(); } @Bean("targetDataSource") public DataSource targetDataSource( @Qualifier("master") DataSource master , @Qualifier("slave1") DataSource slave1 , @Qualifier("slave2") DataSource slave2 ) { Map<Object, Object> dataSources = new HashMap<>(); dataSources.put(DataSourceType.MASTER, master); dataSources.put(DataSourceType.SLAVE1, slave1); dataSources.put(DataSourceType.SLAVE2, slave2); RouteDataSource routeDataSource = new RouteDataSource(); routeDataSource.setDefaultTargetDataSource(master); routeDataSource.setTargetDataSources(dataSources); return routeDataSource; } }
SqlSessionFactoryConfig
Under multiple data sources, we need to manually register SqlSessionFactoryConfig, in which targetDataSource is the dataSource bean we registered before. Related configurations of mybatis will not be described in detail
@Configuration public class SqlSessionFactoryConfig { @Autowired @Qualifier("targetDataSource") private DataSource targetDataSource; @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(targetDataSource); ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); // Location of entity class bean.setTypeAliasesPackage("com.xsn.entity"); // XML configuration of mybatis bean.setMapperLocations(resolver.getResources("classpath:mappers/*.xml")); return bean.getObject(); } @Bean public DataSourceTransactionManager dataSourceTransactionManager() { return new DataSourceTransactionManager(targetDataSource); } }
ReadOnly
Annotation class, which is used to mark the class that performs the read operation, and switch the data source with tangent
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ReadOnly { }
ReadOnlyAop
Tangent processing class, the key to data source switching. For the method of adding ReadOnly annotation (i.e. performing read operation), switch the data source to slave, and finally clear the ThreadLocal variable to avoid memory leakage (even if ThreadLocal is a weakrecurrence variable, it is also a good habit)
@Component @Aspect @Order(-100) @Slf4j public class ReadOnlyAop { @Pointcut(value = "@annotation(com.xsn.anno.ReadOnly)") public void pointCut() { } @Around(value = "pointCut()") public Object changeDb(ProceedingJoinPoint proceedingJoinPoint) { try { DBContextHolder.slave(); return proceedingJoinPoint.proceed(); } catch (Throwable e) { log.error(e.getMessage(), e); } finally { DBContextHolder.remove(); } return null; } }
mapper related classes
Automatically generated through maven plug-in, no more details, and the source address will be given later
Business
We add @ ReadOnly registration ID to get user information method. This query is executed by the slave library
test
New users
View all databases and add them successfully. The log console does not print and switch from database information. That is to say, the write operation is implemented by the master data source
Get user information
Similarly, getting user information, you will find
Randomly switch data source to read from library
summary
At this point, we have achieved a simple separation of reading and writing. Although the switching of data sources is actually controlled by the programmer himself (through annotation), it is not difficult to avoid the situation of missing and wrong annotation.
Previous: Spring boot + durid + mybatis + docker for mysql read-write separation-1
Source address
https://github.com/dangzhicairang/my-cloud.git
Configuration center address
https://github.com/dangzhicairang/my-config.git