本文在個人技術博客【鳥不拉屎】同步發布,詳情可猛戳 亦可掃描文章末尾二維碼關注個人公眾號【鳥不拉屎】
前言
實際業務場景中,不可能只有一個庫,所以就有了分庫分表,多數據源的出現。實現了讀寫分離,主庫負責增改刪,從庫負責查詢。這篇文章將實現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.yml
#指定配置文件為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.yml如下
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.yml中的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.yml中的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.yml中的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