MyBatis 及 MyBatis Plus 純註解方式配置(Spring Boot + Postgresql)

来源:https://www.cnblogs.com/milton/archive/2022/04/04/16100143.html
-Advertisement-
Play Games

說明 當前的版本為 MyBatis 3.5.9 MyBatis Plus 3.5.1 Spring Boot 2.6.4 Postgresql 42.3.3 與 Spring Boot 結合使用 MyBatis 以下說明Spring Boot下完全以註解方式進行的配置, 覆蓋大部分功能場景 項目依賴 ...


說明

當前的版本為

  • MyBatis 3.5.9
  • MyBatis Plus 3.5.1
  • Spring Boot 2.6.4
  • Postgresql 42.3.3

與 Spring Boot 結合使用 MyBatis

以下說明Spring Boot下完全以註解方式進行的配置, 覆蓋大部分功能場景

項目依賴

需要以下的依賴, 版本由Spring Boot指定, 或者參考上面的版本號

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>

配置

1.增加 MapperScan 註解

這個註解可以和@SpringBootApplication放在一起, 也可以放在單獨的一個 @Configuration

@SpringBootApplication
@MapperScan(basePackages = "com.yourdomain.demo.commons.impl.mapper")
public class CommonsBoot {
    public static void main(String[] args) {
        SpringApplication.run(CommonsBoot.class, args);
    }
}

2. 準備POJO(PO或者DTO)

public class RoleItemDTO {
    private Integer id;
    private int roleId;
    private int itemId;
    private Date createTime;

    // getters and setters
    // ...
}

3. 準備Mapper

Mapper必須位於 @MapperScan 指定的包路徑, 其中 @Repository 用於指定 Bean 名稱, 內部用 @Select @Update @Delete @Insert 等編寫對應操作的SQL

@Repository("roleItemMapper")
public interface RoleItemMapper {

    @Select("SELECT * FROM test_role_item WHERE role_id = #{roleId}")
    List<RoleItemDTO> listByRoleId(@Param("roleId") int roleId);
}

4. 調用

測試用例

@ExtendWith(SpringExtension.class)
@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class RoleItemTest {
    private static final Logger log = LoggerFactory.getLogger(RoleItemTest.class);

    @Resource
    private RoleItemMapper roleItemMapper;

    @Test
    @Order(1)
    void insert() {
        RoleItemDTO rc = new RoleItemDTO();
        rc.setItemId(1);
        rc.setRoleId(2);
        rc.setCreateTime(new Date());
        Assertions.assertThat(roleItemMapper.insert(rc)).isEqualTo(1);
        List<RoleItemDTO> dummies = roleItemMapper.listByRoleId(2);
        log.info(JacksonUtil.compress(dummies));
        Assertions.assertThat(dummies).isNotNull();

    }
}

註解使用

典型用例

public interface VillageMapper {
    @Results({
        @Result(property = "vid",         column = "id"),
        @Result(property = "villageName", column = "name"),
        @Result(property = "district",    column = "district")
    })
    @Select("SELECT id, name, district from village WHERE id = #{id}")
    Village selectVillage(int id);

    @Insert("INSERT into village(name,district) VALUES(#{villageName}, #{district})")
    void insertVillage(Village village);

    @Update("UPDATE village SET name=#{villageName}, district =#{district} WHERE id =#{vid}")
    void updateVillage(Village village);

    @Delete("DELETE FROM village WHERE id =#{id}")
    void deleteVillage(int id);
}

傳參註解@Param${},#{}

在Mapper介面中, 使用@Param("paramName")可以對傳入的參數進行命名, 在SQL中通過${paramName}#{paramName}取值, 避免歧義.

  1. ${}會在SQL字元串中直接替換, 非必要不建議使用這種方式
  2. #{}是根據參數類型進行填充, 對於字元串會增加引號包圍, 是通常使用的傳參方式
  3. #{}支持對象傳入, 在引用時通過 #{obj.variable}的方式取值, 參考下麵 Insert 的例子

Select和結果集映射

對於普通類型例如primitive類型, 數值(Integer, Long, Double, BigDecimal等), 字元串String, 日期Date, MyBatis都已經做了自動轉換不需要手工設定. 對於特殊類型, 例如對應Postgresql的 Array, Jsonb, 需要使用註解 @Results 進行指定, 在註解中, 對於已經有預設handler的欄位可以省略, 只需要添加特殊指定hanler的欄位.

Array類型欄位的例子, 對templates欄位的轉換進行指定

// POJO定義
public class ViewPO implements Serializable {
    private Integer id;
    private String name;
    private Integer[] templates;
}

// Mapper方法
@Results({
    @Result(property = "templates", column = "template_ids", typeHandler = ArrayTypeHandler.class)
})
@Select("select * from test_view where id = #{id}")
ViewPO select(@Param("id") int id);

JSONB類型欄位的例子, 對ext欄位的轉換進行指定

// POJO定義
public class AccountDTO {
    private Long id;
    private String name;
    private int val;
    private BigDecimal amount;
    private Ext ext;

    public static class Ext {
        private Integer id;
        private String name;
        private BigDecimal level;
        // getters and setters
    }

    // getters and setters
}

// Mapper方法
@Results({
    @Result(property = "ext", column = "ext", typeHandler = JacksonTypeHandler.class)
})
@Select("select * from test_account where amount > #{amountGt}")
List<AccountDTO> list(@Param("amountGt") BigDecimal amountGt);

Insert和自增主鍵

@Insert("INSERT INTO role_item (role_id, item_id, create_time) VALUES (#{po.roleId}, #{po.itemId}, #{po.createTime})")
@Options(useGeneratedKeys=true, keyProperty="id")
int insert(@Param("po") RoleItemPO po);

Delete

@Delete("DELETE FROM role_item WHERE item_id = #{itemId}")
int deleteByItemId(@Param("itemId") int itemId);

Update

@Update("update widget set name=#{po.name}, manufacturer=#{po.manufacturer} where id=#{id}")
public void updateById(@Param("id") int id, @Param("po") Widget po);

與 Spring Boot 結合使用 MyBatis Plus

MyBatis Plus 相對於原生 MyBatis, 增加了對常用CRUD方法的包裝, 減小了手工編寫SQL的工作量. MyBatis Plus 對多主鍵的表支持不太好, 如果資料庫中的某個數據表使用了聯合主鍵, 建議使用原生的 MyBatis 註解對這個數據表進行操作

項目依賴

將 MyBatis 的依賴替換為MyBatis Plus, 參考上面的版本號, 本文使用的是3.5.1

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>

配置

1.增加 MyBatisPlus 的配置類

因為除了設置 MapperScan, 還要設置分頁插件, 因此放到了單獨的 @Configuration

@Configuration
@MapperScan(basePackages = "com.yourdomain.demo.common.mapper")
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
        return interceptor;
    }

}

2. 準備POJO(PO或者DTO)

如果使用 MyBatis Plus, 常用的三個註解為

  1. @TableName 表名, autoResultMap設置為true, 下麵 ext 欄位的 typeHandler 才會生效
  2. @TableId 唯一主鍵, 這個註解在當前類中只允許出現一次. 唯一主鍵修飾的id欄位需要使用包裝類, 例如 Integer或Long, 在Insert時這個欄位賦值NULL, 才能正確獲取生成的自增ID值
  3. @TableField 欄位映射關係, 當欄位名與變數名不能直接對應時使用, 當類型轉換需要特殊處理時也需要使用這個註解
@TableName(value = "test_users", autoResultMap = true)
public class UserPO implements Serializable {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    private String userName;
    private String password;
    @TableField("creat_time")
    private Date createdAt;
    @TableField("update_time")
    private Date updatedAt;
    @TableField(typeHandler = JacksonTypeHandler.class)
    private JsonNode ext;
}

3. 準備 Mapper

對於 MyBatis Plus, 最常見的 Mapper 初始化方式為擴展 BaseMapper, 這個介面已經實現了 selectById, selectList, insert, delete, deleteById, updateById 等常用方法

@Repository("userMapper")
public interface UserMapper extends BaseMapper<UserPO> {

}

註意

  1. 如果 BaseMapper 自帶方法不能滿足功能需求, 可以通過 @Select, @Update, @Insert, @Delete 等註解添加自定義介面, 實際上就是按原生 MyBatis 的方式處理.
  2. 如果添加了 @Select 查詢並且結果也是標準POJO, 結果中的 JSONB 類型欄位並不會自動賦值(輸出為null). 如果需要轉換, 需要按原生 MyBatis 一樣在方法上添加 @Results 註解.

4. 調用

調用方式與原生 MyBatis 一致

註解使用

MyBatis Plus 的常用註解主要有兩個, 一個是 Wrapper(有多個實現類), 一個是 Page, 分別用於查詢條件和分頁條件.

QueryWrapper 用於條件,排序和聚合

QueryWrapper 自帶了常用的邏輯方法 eq(相等), ne(不相等), lt/gt(小於/大於), lte/gte(小於等於/大於等於), like(兩邊%), likeLeft(左%), likeRiht(右%), exists(條件子查詢), in(數組中包含), 以及 groupBy, orderBy 方法

  1. 三參數格式中, 第一個參數為本條件是否加入查詢的條件判斷, 便於根據輸入進行條件組合
  2. 三參數的第二參數(或二參數的第一參數), 其值為資料庫的欄位名, 這點與 LambdaQueryWrapper 不同, 需要註意
List<FieldPO> fields = fieldMapper.selectList(new QueryWrapper<FieldPO>().eq("t_id", tId).orderByAsc("id"));

一個複雜一點的例子, 註意最後一個exists例子中的傳參方式

QueryWrapper<ItemPO> wrapper = new QueryWrapper<ItemPO>()
    .eq(name != null, "item_name", name)
    .like(nameLike != null, "item_name", nameLike)
    .like(descLike != null, "desc", descLike)
    .eq(type > 0, "item_type", type)
    .eq(state > 0, "item_state", state)
    .eq(creatorId > 0, "creator_id", creatorId)
    .in(idIn != null && idIn.size() > 0, "id", idIn)
    .exists(roleId > 0, "select 1 from role_item where role_item.item_id=item.id and role_item.role_id = {0}", roleId);

LambdaQueryWrapper POJO欄位方式

LambdaQueryWrapper 與 QueryWrapper 的區別在與條件參數為POJO的取值方法名而不是資料庫欄位, 從設計上看這樣可以完全隔離資料庫層的信息, 是更好的一種實現, 例子

LambdaQueryWrapper<AccountDTO> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
    userLambdaQueryWrapper.likeRight(AccountDTO::getName , "acc").lt(AccountDTO::getVal , 40).last("limit 5");

但是當前 MyBatis Plus 的實現在高版本JDK上會提示警告, 在 JDK9+ 上運行時會提示An illegal reflective access operation has occurred #issue. 建議還是繼續用 QueryWrapper, 待這部分的實現修複後再遷移.

Page 分頁

列表的排序和聚合都是通過Wrapper指定的, Page 用於列表中的分頁. 前提: 在 Configuration 中, 通過 MybatisPlusInterceptor 添加 PaginationInnerInterceptor, 參考前面的配置部分, 需要配置正確的 DbType.

通過頁數和每頁記錄數創建Page對象

Page page = new Page<>(vo.getPageNum(), vo.getPageSize());

結合Wrapper進行查詢

@Override
public List<ItemPO> listByArgs(
        Page<ItemPO> page,
        String name,
        int type) {
    QueryWrapper<ItemPO> wrapper = new QueryWrapper<ItemPO>()
            .eq(name != null, "item_name", name)
            .eq(type > 0, "item_type", type);
    return itemMapper.selectPage(page, wrapper).getRecords();
}

如果需要總數, 可以直接返回selectPage(page, wrapper)的結果.

混合使用 MyBatis 原生方式和 MyBatis Plus

因為 MyBatis Plus 是對 MyBatis 功能的包裝, 底層依然是 MyBatis, 所以在使用 MyBatis Plus 的過程中, MyBatis 的原生方法依然可用.

例如, 可以在原生方法中, 使用 MyBatis Plus 的 Wrapper

@Repository("userMapper")
public interface UserMapper extends BaseMapper<UserPO> {

    @Select("select * from u_users ${ew.customSqlSegment}")
    List<UserPO> selectAll(@Param(Constants.WRAPPER) Wrapper<UserPO> wrapper);
}

其它

批量執行

批量執行可以通過兩種方式,

MyBatis 原生環境

在 MyBatis 原生環境下, 可以通過獲取 sqlSession 後手工發起

@Resource(name = "sqlSessionFactory")
private SqlSessionFactory sqlSessionFactory;

public void addItems(List<ItemPO> items) {
    SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
    try {
        for (ItemPO item : items) {
            itemMapper.insert(item);
        }
        session.commit();
        session.clearCache();
    } catch (Exception e) {
        log.error("Exception in batch update", e);
        session.rollback();
    } finally {
        session.close();
    }
}

MyBatis Plus 的 saveBatch

在 MyBatis Plus 中, 可以通過擴展 ServiceImpl<Mapper, POJO> 類, 直接使用其中的 saveBatch() 方法

public class FieldServiceImpl
        extends ServiceImpl<FieldMapper, FieldPO>
        implements FieldService {

    @Override
    public Result saveSomething(List<FieldPO> pos) {
        // 在方法中使用 saveBatch
        saveBatch(pos);
    }
}

動態表名

如果需要在查詢中指定表名, 最簡單的方式是通過${}方式傳參, 例子如下

@Select("SELECT * FROM ${tableName} WHERE item_type=#{itemType} " +
        "AND item_name=#{itemName} AND item_id=#{itemId} AND label=#{label}")
LabelMapDTO selectFromTable(
        @Param("tableName") String tableName,
        @Param("itemType") int itemType,
        @Param("itemName") String itemName,
        @Param("itemId") String itemId,
        @Param("label") String label);

@Select("SELECT * FROM ${tableName} WHERE item_type=#{itemType} " +
        "AND item_name=#{itemName} AND item_id=#{itemId}")
List<LabelMapDTO> selectListFromTable(
        @Param("tableName") String tableName,
        @Param("itemType") int itemType,
        @Param("itemName") String itemName,
        @Param("itemId") String itemId);

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 工廠方法模式是什麼 工廠方法模式是一種創建型設計模式, 其在父類中提供一個創建對象的方法, 允許子類決定實例化對象的類型。 為什麼用工廠方法模式 在新增一個產品時,不用去修改已有的代碼。工廠方法將創建產品的代碼與實際使用產品的代碼分離,從而能在不影響其他代碼的情況下擴展產品創建部分代碼。例如,如果需 ...
  • 08-Functions, Parameters、 functions、 first class、 function signatures、 parameter、 pass by value、 p... ...
  • java是什麼? java是java面向對象程式設計語言和java平臺的總稱 java的開發平臺 javaSE:標準版 javaEE:企業版 javaME:嵌入式 JRE和JDK JRE:jre是java運行時環境,包含JVM和運行時所需要的核心類庫 JDK:jdk時java程式開發工具包,包含jr ...
  • 方法重載的規則,main方法也可以重載,JVM在調用main方法的時候傳入了一個空字元串數組 構成方法重載的規則: 1.首先必須是在同一個類中 2.函數名相同 3.形參列表不同(形參個數,類型,順序不同都可以構成方法重載) 只有返回值不同不能構成方法重載。 只有訪問修飾符不同也不能構成方法重載 ma ...
  • 有沒有小火伴是特別喜歡玩五子棋的,我記得我初中是特別喜歡的。於是,我今天就用Python給大家寫了一個黑白棋游戲。代碼放在下麵了。 01、繪製棋盤 Python學習交流Q群:906715085### import pygame, sys, random from pygame.locals impo ...
  • 多線程方式實現(1) 方式1:繼承Thread類。 * 步驟 * A:自定義類MyThread繼承Thread類。 * B:MyThread類裡面重寫run()? * 為什麼是run()方法呢? * C:創建對象 * D:啟動線程 */ package cn.itcast_02; /* * 該類要重 ...
  • Windows微信清理工具v.3.0.2 更新內容: 1、清理完成時可顯示刪除了哪些文件。 軟體截圖: 所有版本及源碼下載鏈接: 百度網盤:https://pan.baidu.com/s/1OSIpvZEOvd-lVZb_82BnKg 提取碼:ylzh (請下載v.3.0.2版本) 阿裡雲盤:htt ...
  • 假期就要好好利用,不然怎麼捲死同齡人,今天給大家分享替換字元串。 FlashText 演算法是由 Vikash Singh 於2017年發表的大規模關鍵詞替換演算法,這個演算法的時間複雜度僅由文本長度(N)決定,演算法時間複雜度為O(N) 而對於正則表達式的替換,演算法時間複雜度還需要考慮被替換的關鍵詞數量( ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...