1. 程式人生 > >Spring Boot2(四):使用Spring Boot多資料來源實現讀寫分離

Spring Boot2(四):使用Spring Boot多資料來源實現讀寫分離

前言

實際業務場景中,不可能只有一個庫,所以就有了分庫分表,多資料來源的出現。實現了讀寫分離,主庫負責增改刪,從庫負責查詢。這篇文章將實現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);
    }
}

問題:看這塊masterSqlSessionFactorySqlSessionFactoryBean只獲取了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

示例程式碼-gith