天天看點

Springboot雙資料源的配置 及 Mybatis---級聯查詢 resultType和resultMap的使用方式

背景:

最近項目需要使用到雙資料源的配置,便學習了一下,其中也遇到了一些坑,這裡記錄一下。
           

Part 1:Springboot中使用雙資料源的配置

由于項目中,自己的業務資料庫使用的Mysql,現在有一個業務需要連接配接連一個SqlServer的庫進行查詢操作,是以,項目需要內建雙資料源的配置。
           

第一步:application.yml中的配置

primary:
  datasource:
    druid:
      filters: stat
      driverClassName: com.mysql.cj.jdbc.Driver
      jdbcUrl: jdbc:mysql://IP:3306/cycz?useSSL=false&useUnicode=true&characterEncoding=utf-8&userSSL=false&serverTimezone=CTT&allowMultiQueries=true #jdbc:mysql://172.26.1.4:3306/cycz?useSSL=false&useUnicode=true&characterEncoding=utf-8&userSSL=false&serverTimezone=CTT&allowMultiQueries=true
      username: 資料庫使用者名
      password: 資料庫密碼

secondry:
  datasource:
    druid:
      driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
      jdbcUrl: jdbc:sqlserver://IP:1433;DatabaseName=H2000 
      username: 資料庫使用者名
      password: 資料庫密碼
      driverLocation: C:\Users\Lenovo\.m2\repository\com\microsoft\sqlserver\sqljdbc4\sqljdbc4-4.0.jar

           

第二步:由于使用了雙資料源的配置,是以需要自己進行資料庫的配置。

第一個資料源的配置:
           
package com.soyuan.cycz.web.config;


import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.soyuan.cycz.server.mapper" , sqlSessionFactoryRef =  "primarySqlSessionTemplate")
public class PrimaryDataSourceConfig {

    @Autowired
    private PaginationInterceptor inteceptor;

    @Bean(name = "primaryDatasource")
    @ConfigurationProperties(prefix = "primary.datasource.druid")
    @Primary
    public DataSource primaryDataSource(){
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "primarySqlSessionTemplate")
    @Primary
    public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDatasource") DataSource dataSource) throws Exception {

        MybatisSqlSessionFactoryBean  bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setPlugins(new Interceptor[]{inteceptor});
        MybatisConfiguration configuration =new MybatisConfiguration();

        configuration.setMapUnderscoreToCamelCase(false);//-自動使用駝峰命名屬性映射字段,如userId    user_id
        bean.setConfiguration(configuration);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/cycz/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "primaryTransactionManager")
    @Primary
    public DataSourceTransactionManager primaryTransactionManager(@Qualifier("primaryDatasource") DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}
           

第二個資料源的配置:

package com.soyuan.cycz.web.config;

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import lombok.Data;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Data
@Configuration
@MapperScan(basePackages = "com.soyuan.cycz.server.dao" , sqlSessionFactoryRef =  "secondrySqlSessionTemplate")
public class SecondryDataSourceConfig {
    @Bean(name = "secondryDatasource")
    @ConfigurationProperties(prefix = "secondry.datasource.druid")
    public DataSource secondryDataSource(){
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "secondrySqlSessionTemplate")
    public SqlSessionFactory secondrySqlSessionFactory(@Qualifier("secondryDatasource") DataSource dataSource) throws Exception {

        MybatisSqlSessionFactoryBean  bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);

        MybatisConfiguration configuration =new MybatisConfiguration();
        configuration.setMapUnderscoreToCamelCase(false);//-自動使用駝峰命名屬性映射字段,如userId    user_id
        bean.setConfiguration(configuration);

        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/h2000/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "secondryTransactionManager")
    public DataSourceTransactionManager secondryTransactionManager(@Qualifier("secondryDatasource") DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}
           

為了便于了解,配上我的項目結構圖:

Springboot雙資料源的配置 及 Mybatis---級聯查詢 resultType和resultMap的使用方式
Springboot雙資料源的配置 及 Mybatis---級聯查詢 resultType和resultMap的使用方式

第三步:在啟動類上,去除資料源的自動加載,添加如下代碼在啟動類上:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, MybatisPlusAutoConfiguration.class})
           

至此,springboot對雙資料源的內建就介紹完了,這裡記錄一個我在使用的過程中,碰到的一個坑,由于第二個資料庫的查詢是後來加入的,是以之前在進行分頁的時候,使用的Mybatis-plus的分頁插件,但是,當配置了雙資料源之後,該配置就不再起作用了,是以需要在每個資料源的配置類中添加對分頁插件的支援,具體的代碼在上面的配置類中已經添加過了。

Part 2: resultMap和resultType的使用,一些遇到的坑。

為了友善了解每個配置的意義,這裡樓主在網上,找了些相關的配置的意義。

resultMap結果映射集詳解

resultmap是mybatis中最複雜的元素之一,它描述如何從結果集中加載對象,主要作用是定義映射規則、級聯的更新、定制類型轉化器。

resultmap構成元素

Springboot雙資料源的配置 及 Mybatis---級聯查詢 resultType和resultMap的使用方式

id、result語句屬性配置細節:

Springboot雙資料源的配置 及 Mybatis---級聯查詢 resultType和resultMap的使用方式

其中,association可以實作一對一,而collection可以實作一對多的配置。我項目裡的需求是:一個集裝箱下會存在多個視訊和圖檔,及對多的關系,但是,前端顯示的時候,隻需要顯示最新的視訊和圖檔即可。是以,雖然是一對多的關系,但是實際的資料隻有一條,是以使用association進行接收,具體代碼如下:

<select id="trackBackList_2" parameterType="com.soyuan.cycz.api.request.TrackBackParam" resultMap="trackBackListMap">

        select
        ci.Id as containerInfoId,
        ci.ContainerNo as containerNo,
        ci.EntryNo as entryNo,
        ci.ExamineRecordNo as examineRecordNo,
        ci.BillNo as billNo,
        ci.CamerapositionId as camerapositionId,
        ci.OwnerName as ownerName,
        ci.TradeName as tradeName,
        ci.ExamFieldId as examFieldId,
        ci.Goods as goods,
        ci.Inspector as inspector,
        ci.IsError as isError,
        ci.ExamineWorkFlowId as examineWorkFlowId
        from container_info ci
        left join examine_work_flow exf on exf.Id = ci.ExamineWorkFlowId
        <trim prefix="WHERE" prefixOverrides="AND | OR">
            <if test="tbp.entryNo != null and tbp.entryNo != '' ">
                AND ci.EntryNo like '%${tbp.entryNo}%'
            </if>
            <if test="tbp.containerNo != null and tbp.containerNo != '' ">
                AND ci.ContainerNo like '%${tbp.containerNo}%'
            </if>
            <if test="tbp.examineRecordNo != null and tbp.examineRecordNo != '' ">
                AND ci.ExamineRecordNo like '%${tbp.examineRecordNo}%'
            </if>
            <if test="tbp.billNo != null and tbp.billNo != '' ">
                AND ci.BillNo like '%${tbp.billNo}%'
            </if>
            <if test="tbp.camerapositionId != null ">
                AND ci.CamerapositionId = #{tbp.camerapositionId}
            </if>
            <if test="tbp.examStartTime != null ">
                exf.ActionTime &gt;= #{tbp.examStartTime}
            </if>
            <if test="tbp.examEndTime != null ">
                exf.ActionTime &lt;= #{tbp.examEndTime}
            </if>
            <if test="tbp.examFieldId != null ">
                AND ci.ExamFieldId = #{tbp.examFieldId}
            </if>
            <if test="tbp.ownerName != null and tbp.ownerName != '' ">
                AND ci.OwnerName like '%${tbp.ownerName}%'
            </if>
            And ci.IsCompleted = 1
        </trim>
    </select>

    <resultMap id="trackBackListMap" type="com.soyuan.cycz.api.response.TrackBack">
        <id property="containerInfoId" column="containerInfoId"/>
        <result property="containerNo" column="ContainerNo"/>
        <result property="entryNo" column="EntryNo"/>
        <result property="examineRecordNo" column="ExamineRecordNo"/>
        <result property="billNo" column="BillNo"/>
        <result property="camerapositionId" column="CamerapositionId"/>
        <result property="ownerName" column="OwnerName"/>
        <result property="examFieldId" column="ExamFieldId"/>
        <result property="goods" column="Goods"/>
        <result property="inspector" column="Inspector"/>
        <result property="isError" column="IsError"/>
        <result property="examineWorkFlowId" column="ExamineWorkFlowId"/>
        <association property="videoAndPictureVo" javaType="com.soyuan.cycz.api.response.VideoAndPictureVo" column="containerInfoId" select="seleceVideoAndPictureById">
            <result property="videoUrl" column="VideoUrl"/>
            <result property="pictureUrl" column="PictureUrl"/>
            <result property="isUpload" column="isUpload"/>
        </association>
    </resultMap>

    <select id="seleceVideoAndPictureById" parameterType="java.lang.Long" resultType="com.soyuan.cycz.api.response.VideoAndPictureVo">
        select
          ev.VideoUrl as videoUrl,
          ev.PictureUrl as pictureUrl,
          ev.isUpload as isUpload
        from container_info ci
        left join examine_video ev on ci.Id = ev.ContainerInfoId
        where
          ev.ContainerInfoId = #{id}
        order by
          ev.CreationTime desc
        limit 1
    </select>

           

在resultmap的association節點下,配置了嵌套查詢的語句,以及對應的字段名稱。select屬性配置的是嵌套查詢的子語句的id,id屬性配置的是将前面查詢結果作為本次查詢的條件,對應的是查詢出來的字段名稱。

當存在一對多的關系時,使用collection接收即可,而且不配置resultMap也可以,隻需要resultType中對應的java bean中,配置一個List的屬性,既可以自動映射成多個結果,如下面代碼:

對應的實體類:

@Data
public class CheckDetailVo {

    /**
     * 車載編号
     */
    private String carryCargoCode;

    /**
     * 車牌号
     */
    private String carNumber;

    /**
     * 過卡時間
     */
    private LocalDateTime passTime;

    /**
     * 卡口
     */
    private String placeCode;

    /**
     * 卡口名稱
     */
    private String placeName;

    /**
     * 通道号
     */
    private String cameraPositionCode;

    /**
     * 錄影機位名稱
     */
    private String cameraPositionName;

    /**
     * 隸屬關名稱
     */
    private String subCustomName;

    /**
     * 區域名稱
     */
    private String regionName;

    /**
     * 承運人
     */
    private String carrier;

    /**
     * 視訊url
     */
    private String videoUrl;

    /**
     * 圖檔url
     */
    private String pictureUrl;

    /**
     * 是否上雲
     */
    private Boolean isUpload;

    /**
     * 貨物資訊
     */
    List<CargoVo> cargoVoList;
}
           

Mapper層代碼:

<resultMap id="checkDetail" type="com.equaker.blank.api.response.CheckDetailVo">
        <result property="carryCargoCode" column="CarryCargoCode"></result>
        <result property="carNumber" column="CarNumber"></result>
        <result property="placeCode" column="PlaceCode"></result>
        <result property="carrier" column="Carrier"></result>
        <collection property="cargoVoList" ofType="com.equaker.blank.api.response.CargoVo">
            <result property="declareCode" column="DeclareCode"></result>
            <result property="borderPeopleName" column="BorderPeopleName"></result>
            <result property="declarePlace" column="DeclarePlace"></result>
            <result property="declareTime" column="DeclareTime"></result>
            <result property="declarationStatusName" column="DeclarationStatusName"></result>
        </collection>
    </resultMap>

    <!--擷取存證詳情-->
    <select id="getCheckDetail" parameterType="java.lang.String" resultMap="checkDetail">

        select
          ccg.CarryCargoCode as carryCargoCode,
          ccg.CarNum as carNumber,
          ccg.PlaceCode as placeCode,
          ccg.Carrier as carrier,
          di.DeclareCode as declareCode,di.BorderPeopleName as borderPeopleName,di.DeclarePlace as declarePlace,
          di.DeclareTime as declareTime,di.DeclarationStatusName as declarationStatusName
        from carrycargo ccg
        left join carrycargoitems cci on ccg.Id = cci.CarryCargoId
        inner join declareinfo di on di.Id = cci.DeclareInfoId
        where
              di.DeclareCode = #{declareCode}
    </select>
           

這裡記錄一個坑,當查詢結果使用的别名時,在result的id節點中的column屬性應配置為别名,而不是資料庫的實際列名,否則無法正确映射。

總結:希望本文讀完之後,能夠對大家在工作中能夠起到一定的幫助。

最後和大家分享最近一直在聽幾句歌詞:

我想和你一起闖進森林潛入海底,

我想和你一起看日出到日落天氣。

繼續閱讀