前言
实际业务场景中, 不可能只有一个库, 所以就有了分库分表, 多数据源的出现. 实现了读写分离, 主库负责增改删, 从库负责查询. 这篇文章将实现 Spring Boot 如何实现多数据源, 动态数据源切换, 读写分离等操作.
代码部署
快速新建项目 spring-boot 项目
1, 添加 maven 依赖
- <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>
2,application 配置多数据源读取配置
和之前教程一样, 首先配置 application.YAML
- # 指定配置文件为 test
- spring:
- profiles:
- active: test
- # 配置 Mybatis
- mybatis:
- configuration:
- # 开启驼峰命名转换, 如: Table(create_time) -> Entity(createTime). 不需要我们关心怎么进行字段匹配, mybatis 会自动识别 ` 大写字母与下划线 `
- map-Underscore-to-camel-case: true
- # 打印 SQL 日志
- logging:
- level:
- com.niaobulashi.mapper.*: DEBUG
其中打印 SQL 日志这块, 因为是多数据源, 在 mapper 包下面区分不同的数据库来源 xml 文件, 所以用 * 表示.
配置 application-test.YAML 如下
- spring:
- datasource:
- #主库
- 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
- #从库
- 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.Driver
从 spring.datasource 节点开始, 区分主库 master, 从库 slave. 主库连接的数据库为 test, 从库连接的数据库为 test2.
注意: 这里需要注意的是, 从 Spring Boot2 开始, 在配置多数据源时有些配置发生了变化, 网上许多教程使用的是 spring.datasource.url. 会出现 jdbcUrl is required with driverClassName. 的问题.
解决方法: 配置多数据源时, 将 spring.datasource.url 配置改为 spring.datasource.jdbc-url
3, 添加主库配置信息
依据知名博主: 纯洁的微笑, 写的博文我们来分析一波
首先看主库配置的代码:
- @Configuration
- @MapperScan(basePackages = "com.niaobulashi.mapper.master", sqlSessionTemplateRef = "masterSqlSessionTemplate")
- public class DataSourceMasterConfig {
- /**
- * 是 application-test.YAML 中的 spring.datasource.master 配置生效
- * @return
- */
- @Bean(name = "masterDataSource")
- @ConfigurationProperties(prefix = "spring.datasource.master")
- @Primary
- public DataSource masterDataSource() {
- return DataSourceBuilder.create().build();
- }
- /**
- * 将配置信息注入到 SqlSessionFactoryBean 中
- * @param dataSource 数据库连接信息
- * @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();
- }
- /**
- * 事务管理器, 在实例化时注入主库 master
- * @param dataSource
- * @return
- */
- @Bean(name = "masterTransactionManager")
- @Primary
- public DataSourceTransactionManager masterTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) {
- return new DataSourceTransactionManager(dataSource);
- }
- /**
- * SqlSessionTemplate 具有线程安全性
- * @param sqlSessionFactory
- * @return
- * @throws Exception
- */
- @Bean(name = "masterSqlSessionTemplate")
- @Primary
- public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
- return new SqlSessionTemplate(sqlSessionFactory);
- }
- }
问题: 看这块 masterSqlSessionFactory,SqlSessionFactoryBean 只获取了 spring.datasource.master 数据库连接信息, 并没有获取多数据库的配置信息 mybatis.configuration 导致我们需要配置驼峰命名规则, 配置信息并没有注入到 SqlSessionFactoryBean. 这样就导致在查询是, 遇到下划线无法解析相应字段 user_id,dept_id,create_time
解决方法: 在配置中添加 Configuration
同时, 将配置信息注入到 SqlSessionFactoryBean
- /**
- * 将配置信息注入到 SqlSessionFactoryBean 中
- * @param dataSource 数据库连接信息
- * @return
- * @throws Exception
- */
- @Bean(name = "slaveSqlSessionFactory")
- public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
- SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
- // 使配置信息加载到类中, 再注入到 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();
- }
4, 添加从库配置信息
和添加主库配置信息一样, 只不过不同的是, 不需要添加 @Primary 首选注解
代码如下
- @Configuration
- @MapperScan(basePackages = "com.niaobulashi.mapper.slave", sqlSessionTemplateRef = "slaveSqlSessionTemplate")
- public class DataSourceSlaveConfig {
- /**
- * 是 application-test.YAML 中的 spring.datasource.master 配置生效
- * @return
- */
- @Bean(name = "slaveDataSource")
- @ConfigurationProperties(prefix = "spring.datasource.slave")
- public DataSource slaveDataSource() {
- return DataSourceBuilder.create().build();
- }
- /**
- * 将配置信息注入到 SqlSessionFactoryBean 中
- * @param dataSource 数据库连接信息
- * @return
- * @throws Exception
- */
- @Bean(name = "slaveSqlSessionFactory")
- public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
- SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
- // 使配置信息加载到类中, 再注入到 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();
- }
- /**
- * 事务管理器, 在实例化时注入主库 master
- * @param dataSource
- * @return
- */
- @Bean(name = "slaveTransactionManager")
- public DataSourceTransactionManager slaveTransactionManager(@Qualifier("slaveDataSource") DataSource dataSource) {
- return new DataSourceTransactionManager(dataSource);
- }
- /**
- * SqlSessionTemplate 具有线程安全性
- * @param sqlSessionFactory
- * @return
- * @throws Exception
- */
- @Bean(name = "slaveSqlSessionTemplate")
- public SqlSessionTemplate slaveSqlSessionTemplate(@Qualifier("slaveSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
- return new SqlSessionTemplate(sqlSessionFactory);
- }
- }
5, 扩展配置方法会报错
在网上还看到这样一种配置, 单独通过 @ConfigurationProperties 注解配置 Mybatis 的配置信息如下
- /**
- * 试 application.YAML 中的 mybatis.configuration 配置生效, 如果不主动配置, 由于 @Order 配置顺序不同, 讲导致配置不能及时生效
- * 使配置信息加载到类中, 再注入到 SqlSessionFactoryBean
- * @return
- */
- @Bean
- @ConfigurationProperties(prefix = "mybatis.configuration")
- public org.apache.ibatis.session.Configuration configuration() {
- return new org.apache.ibatis.session.Configuration();
- }
其中 prefix, 在主库和从库中的 id 是一样的, 必须保持不同, 否则 idea 就会提示报错 Duplicate prefix
导致只有主库可以执行 Mybatis 的配置, 从库无效.
- @Bean(name = "masterSqlSessionFactory")
- @Primary
- public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource, org.apache.ibatis.session.Configuration configuration) throws Exception {
- SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
- // 使配置信息加载到类中, 再注入到 SqlSessionFactoryBean
- bean.setConfiguration(configuration);
- bean.setDataSource(dataSource);
- bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/master/*.xml"));
- return bean.getObject();
- }
这块验证只有主库有效, 从库的驼峰方法解析无效. 后续再来研究下...
6, 数据层代码
代码结构如下
其中 SysUserMasterDao 代码
- public interface SysUserMasterDao {
- /**
- * 根据 userId 查询用户信息
- * @param userId 用户 ID
- */
- List<SysUserEntity> queryUserInfo(Long userId);
- /**
- * 查询所有用户信息
- */
- List<SysUserEntity> queryUserAll();
- /**
- * 根据 userId 更新用户的邮箱和手机号
- * @return
- */
- int updateUserInfo(SysUserEntity user);
- }
7,resource 下数据执行语句
SysCodeMasterDao.xml
- <mapper namespace="com.niaobulashi.mapper.master.SysUserMasterDao">
- <!-- 查询所有用户信息 -->
- <select id="queryUserAll" resultType="com.niaobulashi.entity.SysUserEntity">
- SELECT
- ur.*
- FROM
- sys_user ur
- WHERE
- 1 = 1
- </select>
- <!-- 根据用户 userId 查询用户信息 -->
- <select id="queryUserInfo" resultType="com.niaobulashi.entity.SysUserEntity">
- SELECT
- ur.*
- FROM
- sys_user ur
- WHERE
- 1 = 1
- AND ur.user_id = #{userId}
- </select>
- <!-- 根据 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>
8,Controller 层测试
- @RestController
- public class SysUserController {
- @Autowired
- private SysUserMasterDao sysUserMasterDao;
- @Autowired
- private SysUserSlaveDao sysUserSlaveDao;
- /**
- * 查询所有用户信息 Master
- * @return
- */
- @RequestMapping("/getUserMasterAll")
- private List<SysUserEntity> getUserMaster() {
- System.out.println("查询主库");
- List<SysUserEntity> userList = sysUserMasterDao.queryUserAll();
- return userList;
- }
- /**
- * 查询所有用户信息 Slave
- * @return
- */
- @RequestMapping("/getUserSlaveAll")
- private List<SysUserEntity> getUserSlave() {
- System.out.println("查询从库");
- List<SysUserEntity> userList = sysUserSlaveDao.queryUserAll();
- return userList;
- }
- /**
- * 根据 userId 查询用户信息 Master
- * @return
- */
- @RequestMapping("/getUserMasterById")
- private List<SysUserEntity> getUserMasterById(@RequestParam(value = "userId", required = false) Long userId) {
- List<SysUserEntity> userList = sysUserMasterDao.queryUserInfo(userId);
- return userList;
- }
- /**
- * 根据 userId 查询用户信息 Slave
- * @return
- */
- @RequestMapping("/getUserSlaveById")
- private List<SysUserEntity> getUserSlaveById(@RequestParam(value = "userId", required = false) Long userId) {
- List<SysUserEntity> userList = sysUserSlaveDao.queryUserInfo(userId);
- return userList;
- }
- }
发送查询所有用户接口
主库: http://localhost:8080/getUserMasterAll
从库: http://localhost:8080/getUserSlaveAll
总结
1, 通过多数据源方式实现数据库层面的读写分离
2, 多数据源链接数据库是, 使用 spring.datasource.jdbc-url
3, 多数据源的 mybatis.configuration 配置注意需要手动注入 SqlSessionFactory
示例代码 - GitHub
来源: https://www.cnblogs.com/niaobulashi/p/mybatis-mutls.html