This article is published at the same time in personal technology blog [Bird does not poop]. For details, you can also scan the qr code at the end of the article to pay attention to personal public number [Bird does not poop].
preface
In actual business scenarios, it is impossible to have only one library, so there is the emergence of sub-database sub-table and multi-data source. To achieve read and write separation, the master library is responsible for adding and deleting, from the library is responsible for the query. This article will implement Spring Boot how to implement multiple data sources, dynamic data source switching, read and write separation and other operations.
Code deployment
Quick new project Spring-boot project
1. Add maven dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
Copy the code
2, Application configure multiple data source read configuration
As in the previous tutorial, first configure application.yml
Set the configuration file to test
spring:
profiles:
active: test
# configuration Mybatis
mybatis:
configuration:
Table(create_time) -> Entity(createTime) Mybatis will automatically recognize 'uppercase letters and underscores'
map-underscore-to-camel-case: true
Print SQL logs
logging:
level:
com.niaobulashi.mapper.*: DEBUG
Copy the code
The printing of SQL logs is represented by * because it is a multi-data source and distinguishes different database source XML files under mapper package.
Configure application-test.yml as follows
Spring: a datasource: # main library master: JDBC - url: JDBC: mysql: / / 127.0.0.1:3306 / test? serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true username: root password: root driver-class-name: Com. Mysql. Cj, JDBC Driver # from library slave: JDBC - url: JDBC: mysql: / / 127.0.0.1:3306 / test2? serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true username: root password: root driver-class-name: com.mysql.cj.jdbc.DriverCopy the code
From the spring. Datasource node, distinguish the master library from slave library. The primary database is connected to test, and the secondary database is connected to test2.
Note: It is important to note that since Spring Boot2, some configurations have changed when configuring multiple data sources, and many tutorials online use spring.datasource. Url. JdbcUrl is required with driverClassName. The problem.
Solution: Change the spring.datasource. Url configuration to spring.datasource. Jdbc-url when configuring multiple data sources
3. Add the master library configuration information
We are going to analyze a wave of posts written by a well-known blogger: Pure Smile
First look at the main library configuration code:
@Configuration
@MapperScan(basePackages = "com.niaobulashi.mapper.master", sqlSessionTemplateRef = "masterSqlSessionTemplate")
public class DataSourceMasterConfig {
/** * The spring.datasource. Master configuration in application-test.yml is valid *@return* /
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
@Primary
public DataSource masterDataSource(a) {
return DataSourceBuilder.create().build();
}
/** * Inject configuration information into SqlSessionFactoryBean *@paramDataSource Database connection information *@return
* @throws Exception
*/
@Bean(name = "masterSqlSessionFactory")
@Primary
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/master/*.xml"));
return bean.getObject();
}
/** * transaction manager, inject master * at instantiation time@param dataSource
* @return* /
@Bean(name = "masterTransactionManager")
@Primary
public DataSourceTransactionManager masterTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/** * SqlSessionTemplate is thread-safe *@param sqlSessionFactory
* @return
* @throws Exception
*/
@Bean(name = "masterSqlSessionTemplate")
@Primary
public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return newSqlSessionTemplate(sqlSessionFactory); }}Copy the code
Question: MasterSqlSessionFactory: SqlSessionFactoryBean obtains only the spring.datasource. Master database connection information. Mybatis. Configuration does not get the configuration information for multiple databases, so we need to configure the camel name rule. The configuration information is not injected into the SqlSessionFactoryBean. User_id, dept_id, create_time: user_id, dept_id, create_time
Solution: Add Configuration to the Configuration
At the same time, inject the configuration information into the SqlSessionFactoryBean
/** * Inject configuration information into SqlSessionFactoryBean *@paramDataSource Database connection information *@return
* @throws Exception
*/
@Bean(name = "slaveSqlSessionFactory")
public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
// The configuration information is loaded into the class and then injected into the SqlSessionFactoryBean
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
bean.setConfiguration(configuration);
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/slave/*.xml"));
return bean.getObject();
}
Copy the code
4. Add the slave library configuration information
It is the same as adding the main library configuration, except that you do not need to add the @primary preferred annotation
The following code
@Configuration
@MapperScan(basePackages = "com.niaobulashi.mapper.slave", sqlSessionTemplateRef = "slaveSqlSessionTemplate")
public class DataSourceSlaveConfig {
/** * The spring.datasource. Master configuration in application-test.yml is valid *@return* /
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource(a) {
return DataSourceBuilder.create().build();
}
/** * Inject configuration information into SqlSessionFactoryBean *@paramDataSource Database connection information *@return
* @throws Exception
*/
@Bean(name = "slaveSqlSessionFactory")
public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
// The configuration information is loaded into the class and then injected into the SqlSessionFactoryBean
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
bean.setConfiguration(configuration);
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/slave/*.xml"));
return bean.getObject();
}
/** * transaction manager, inject master * at instantiation time@param dataSource
* @return* /
@Bean(name = "slaveTransactionManager")
public DataSourceTransactionManager slaveTransactionManager(@Qualifier("slaveDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/** * SqlSessionTemplate is thread-safe *@param sqlSessionFactory
* @return
* @throws Exception
*/
@Bean(name = "slaveSqlSessionTemplate")
public SqlSessionTemplate slaveSqlSessionTemplate(@Qualifier("slaveSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return newSqlSessionTemplate(sqlSessionFactory); }}Copy the code
5. The extended configuration method will report an error
The @ConfigurationProperties annotation provides the following configuration information for Mybatis
/** * Try the mybatis. Configuration configuration in application.yml to take effect, if not actively configured, because@OrderIf the configuration order is different, the configuration will not take effect in a timely manner@return* /
@Bean
@ConfigurationProperties(prefix = "mybatis.configuration")
public org.apache.ibatis.session.Configuration configuration(a) {
return new org.apache.ibatis.session.Configuration();
}
Copy the code
The id of prefix in the master and slave libraries is the same and must be different, otherwise IDEA will prompt an error of Duplicate prefix
As a result, only the master library can execute the Mybatis configuration, and the slave library is invalid.
@Bean(name = "masterSqlSessionFactory")
@Primary
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource, org.apache.ibatis.session.Configuration configuration) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
// The configuration information is loaded into the class and then injected into the SqlSessionFactoryBean
bean.setConfiguration(configuration);
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/master/*.xml"));
return bean.getObject();
}
Copy the code
This validation is valid only for the master library, not for the hump method resolution of the slave library. Check back later…
6. Data layer code
The code structure is as follows
SysUserMasterDao code
public interface SysUserMasterDao {
/** * Query user information based on userId *@paramUserId userId */
List<SysUserEntity> queryUserInfo(Long userId);
/** * Query all user information */
List<SysUserEntity> queryUserAll(a);
/** * Update user's email address and mobile phone number according to userId *@return* /
int updateUserInfo(SysUserEntity user);
}
Copy the code
7. Execute statement under resource
SysCodeMasterDao.xml
<mapper namespace="com.niaobulashi.mapper.master.SysUserMasterDao">
<! -- Query all user information -->
<select id="queryUserAll" resultType="com.niaobulashi.entity.SysUserEntity">
SELECT
ur.*
FROM
sys_user ur
WHERE
1 = 1
</select>
<! Select * from userId where userId = userId;
<select id="queryUserInfo" resultType="com.niaobulashi.entity.SysUserEntity">
SELECT
ur.*
FROM
sys_user ur
WHERE
1 = 1
AND ur.user_id = #{userId}
</select>
<! Update email and phone number according to UserId -->
<update id="updateUserInfo" parameterType="com.niaobulashi.entity.SysUserEntity">
UPDATE sys_user u
<set>
<if test="email ! = null">
u.email = #{email},
</if>
<if test="mobile ! = null">
u.mobile = #{mobile},
</if>
</set>
WHERE
u.user_id = #{userId}
</update>
</mapper>
Copy the code
8. Controller layer test
@RestController
public class SysUserController {
@Autowired
private SysUserMasterDao sysUserMasterDao;
@Autowired
private SysUserSlaveDao sysUserSlaveDao;
/** * Query all user information Master *@return* /
@RequestMapping("/getUserMasterAll")
private List<SysUserEntity> getUserMaster(a) {
System.out.println("Query primary library");
List<SysUserEntity> userList = sysUserMasterDao.queryUserAll();
return userList;
}
/** * Queries information about all users Slave *@return* /
@RequestMapping("/getUserSlaveAll")
private List<SysUserEntity> getUserSlave(a) {
System.out.println("Query slave library");
List<SysUserEntity> userList = sysUserSlaveDao.queryUserAll();
return userList;
}
/** * Query user information based on userId Master *@return* /
@RequestMapping("/getUserMasterById")
private List<SysUserEntity> getUserMasterById(@RequestParam(value = "userId", required = false) Long userId) {
List<SysUserEntity> userList = sysUserMasterDao.queryUserInfo(userId);
return userList;
}
/** * Queries user information based on the userId Slave *@return* /
@RequestMapping("/getUserSlaveById")
private List<SysUserEntity> getUserSlaveById(@RequestParam(value = "userId", required = false) Long userId) {
List<SysUserEntity> userList = sysUserSlaveDao.queryUserInfo(userId);
returnuserList; }}Copy the code
Query all user interfaces
The main library: http://localhost:8080/getUserMasterAll
From the library: http://localhost:8080/getUserSlaveAll
conclusion
1. Achieve read and write separation at the database level through multiple data sources
2. Use spring.datasource. Jdbc-url to connect to multiple data sources
3, Multi-source mybatis. Configuration note: Manual injection of SqlSessionFactory is required
Example code – Github
No shit: A future architect working on Coding