天天看點

MyBatis-Plus入門

什麼是MyBatis-Plus

從名字便知它是MyBatis的增強工具,對MyBatis隻做擴充增強不做改變,為簡單開發,提高效率而生。

特性

  • 無侵入:隻做增強不做改變,引入它不會對現有工程産生影響,如絲般順滑
  • 損耗小:啟動即會自動注入基本 CURD,性能基本無損耗,直接面向對象操作
  • 強大的 CRUD 操作:内置通用 Mapper、通用 Service,僅僅通過少量配置即可實作單表大部分 CRUD 操作,更有強大的條件構造器,滿足各類使用需求
  • 支援 Lambda 形式調用:通過 Lambda 表達式,友善的編寫各類查詢條件,無需再擔心字段寫錯
  • 支援主鍵自動生成:支援多達 4 種主鍵政策(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解決主鍵問題
  • 支援 ActiveRecord 模式:支援 ActiveRecord 形式調用,實體類隻需繼承 Model 類即可進行強大的 CRUD 操作
  • 支援自定義全局通用操作:支援全局通用方法注入( Write once, use anywhere )
  • 内置代碼生成器:采用代碼或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 層代碼,支援模闆引擎,更有超多自定義配置等您來使用
  • 内置分頁插件:基于 MyBatis 實體分頁,開發者無需關心具體操作,配置好插件之後,寫分頁等同于普通 List 查詢
  • 分頁插件支援多種資料庫:支援 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多種資料庫
  • 内置性能分析插件:可輸出 Sql 語句以及其執行時間,建議開發測試時啟用該功能,能快速揪出慢查詢
  • 内置全局攔截插件:提供全表 delete 、 update 操作智能分析阻斷,也可自定義攔截規則,預防誤操作

內建方法示例

  1. 引入依賴,使用myabtis-plus的依賴替換原有的mybatis依賴

mybatis-plus的依賴會傳遞依賴mybatis,是以不必再單獨聲明,在使用SpringBoot的項目中引入如下依賴即可。

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>the-last-version</version>
</dependency>           
  1. 聲明Sql-Mapping掃描,下面以将Sql-Mapping的接口聲明放在“com.alsc.databus.order.dao”包中為例

聲明掃描範圍,利用MyBatis原有的@MapperScan注解

@Configuration
@MapperScan("com.alsc.databus.order.dao")
public class ModuleConfiguration {
   // ... 您的Bean定義代碼
  
  // 要使用架構支援的自動分頁查詢,需要聲明如下Bean
  @Bean
  public PaginationInterceptor paginationInterceptor() {
      PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
      // 設定請求的頁面大于最大頁後操作, true調回到首頁,false 繼續請求  預設false
      // paginationInterceptor.setOverflow(false);
      // 設定最大單頁限制數量,預設 500 條,-1 不受限制
      // paginationInterceptor.setLimit(500);
      return paginationInterceptor;
  }
}           

聲明一個Mapper接口,這裡需要注意該Mapper繼承了MyBatis-Plus提供的BaseMapper,且泛型參數指定為要儲存到資料庫的實體模型類。

@Repository
public interface OrderDao extends BaseMapper<UserOrder> {
   // 無須聲明任何方法
}           

實體模型類定義:

@Data
public class UserOrder extends Model<UserOrder> {
    private Long id;

    /**  外部訂單id */
    @TableField(updateStrategy = NEVER)
    private String orderId;
    /**  使用者id */
    private String userId;
    /**  商戶id */
    private String merchantId;
    /**  saas門店id */
    private Long saasStoreId;
    /**  訂單實付金額(包含支付級平台優惠),機關為分 */
    private Long payAmount;
      /** 訂單來源 */
      private Source source;

    @TableField(fill = INSERT)
    private Date gmtCreate;
    @TableField(fill = INSERT_UPDATE)
    private Date gmtModified;
}           
  1. 使用Mapper接口進行資料庫増删查改操作
    UserOrder order = new UserOrder();
    orderDao.insert(order); // 儲存實體到資料表user_order,預設使用資料庫自增id
    orderDao.delete(order.getId()); // 根據ID删除對應的記錄 delete from user_order where id=1
    
    // 更新資料實體
    // update user_order set pay_amount=100, saas_store_id=534543423 where id=1
    order.setPayAmount(100);
    order.setSaasStoreId(534543423);
    orderDao.updateById()
    
    // 查詢清單 
    // select id,order_id,user_id,... from user_order where user_id='hadix' and order_id='4325542342'
    UserOrder query = new UserOrder();
    query.setUserId("hadix");
    query.setOrderId("4325542342");
    List<UserOrder>  orderDao.selectList(Wrappers.query(query));
    
    // 分頁查詢,會自動執行以下兩個sql語句
    // select id,order_id,... from user_order where user_id='hadix' and order_id='4325542342' limit 0,50
    // select count(*) from user_order where user_id='hadix' and order_id='4325542342'
    IPage<UserOrder> p = orderDao.selectPage(new Page<>(1, 50), Wrappers.query(query));
    
    // 如果僅僅想分頁查詢,不關心總頁數,隻需要執行一個sql語句
    // select id,order_id,... from user_order where user_id='hadix' and order_id='4325542342' limit 0,50
    IPage<UserOrder> p = orderDao.selectPage(new Page<>(1, 50, false), Wrappers.query(query));           

預設情況下生成的sql語句遵循如下約定:

  • 表名 = 實體類名由駝峰式轉換為下劃線分隔,例如UserOrder => user_order,可以通過@TableName(name=XXX)自定義
  • 列名 = 實體字段名有駝峰式轉換為下劃線分隔,例如userId => user_id,可以通過@TableField(name=XXX)自定義
  • 主鍵 = 預設屬性名id對應列名id為主鍵,可以通過@TableId(value=xxx,idType=xxx)來自定義屬性對應的主鍵列名。idType用來自定義主鍵生成方式,預設為AUTO:使用資料庫自增,其他可選值為INPUT:由使用者輸入,UUID:使用全局唯一ID等。

小結:

由上述代碼示例可見,與原始MyBatis-Plus不同的地方僅有Mapper的聲明需要繼承BaseMapper而已,預設情況無須編寫任何配置,XML檔案以及SQL語句,即可獲得對單個實體的基本増删查改操作,且查詢語句自動支援分頁。

BaseMapper聲明了大量的増删查改方法,可以滿足基本需求,清單如下:

/** 插入一條記錄 */
int insert(T entity);
/** 根據 ID 删除  */
int deleteById(Serializable id);
/** 根據 columnMap 條件,删除記錄 */
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/** 根據 entity 條件,删除記錄 */
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
/** 删除(根據ID 批量删除) */
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
/** 根據 ID 修改 */
int updateById(@Param(Constants.ENTITY) T entity);
/** 根據 whereEntity 條件,更新記錄 */
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
/** 根據 ID 查詢 */
T selectById(Serializable id);
/** 查詢(根據ID 批量查詢) */
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
/** 查詢(根據 columnMap 條件) */
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/** 根據 entity 條件,查詢一條記錄 */
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/** 根據 Wrapper 條件,查詢總記錄數 */
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/** 根據 entity 條件,查詢全部記錄 */
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/** 根據 Wrapper 條件,查詢全部記錄 */
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/** 根據 Wrapper 條件,查詢全部記錄 */
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/** 根據 entity 條件,查詢全部記錄(并翻頁) */
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/** 根據 Wrapper 條件,查詢全部記錄(并翻頁) */
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);           

自定義查詢、更新

BaseMapper中提供的selectByXXX系列方法接受一個queryWrapper參數,利用LambdaQueryWrapper可以類型安全的方式編寫查詢,避免直接編寫SQL。

例如:增加一個根據訂單ID查詢,在上文的OrderDao接口中增加一個預設方法即可。

@Repository
public interface OrderDao extends BaseMapper<UserOrder> {

    /**
     * 根據訂單号查詢指定訂單
     * select id,user_id,... from user_order where order_id=? and source=?
     * @param orderId 訂單号
     * @param source  來源
     * @return 查詢結果
     */
    default UserOrder getByOrderId(String orderId, Source source) {
        LambdaQueryWrapper<UserOrder> queryWrapper = Wrappers.lambdaQuery();
        return selectOne(
            queryWrapper
                .eq(UserOrder::getOrderId, orderId)
                .eq(UserOrder::getSource, source)
        );
    }
}           

LambdaQueryWrapper還支援編寫動态查詢,例如:

@Data
public class UserRequest {
      String userId;
    String orderId;
}

@Repository
public interface OrderDao extends BaseMapper<UserOrder> {

    /**
     * 根據訂單号查詢指定訂單
     *
     * <select id="find" parameterType="UserRequest" resultType="UserOrder">
     *  select id,user_id,... from user_order 
     *  <where>
     *    <if test="userId != null">
     *      and user_id = #{userId}
     *    </if>
     *     <if test="orderId != null">
     *      and order_id = #{orderId}
     *    </if>
     *  </where>
     * </select>
     *
     * @param orderId 訂單号
     * @param source  來源
     * @return 查詢結果
     */
    default List<UserOrder> find(UserRequest req) {
        LambdaQueryWrapper<UserOrder> queryWrapper = Wrappers.lambdaQuery();
        return selectList(
            queryWrapper
                .eq(req.getUserId()!=null, UserOrder::getUserId, req.getUserId())
                .eq(req.getOrderId()!=null, UserOrder::getOrderId, req.getOrderId())
        );
    }
    
    /**
     * 同時支援Mybatis原來使用xml定義查詢的方式,另外第一個參數為IPage對象時自動支援分頁 
     */
    List<UserOrder> findByPage(Page<UserOrder> page, @Param("req") UserRequest req);
}           
<mapper namespace="OrderDao">
  <select id="findByPage" parameterType="UserRequest" returnType="UserOrder">
    select id,user_id,... from user_order 
    <where>
      <if test="req.userId != null">
        and user_id = #{req.userId}
      </if>
       <if test="req.orderId != null">
        and order_id = #{req.orderId}
      </if>
    </where>
  </select>
</mapper>           

更多有關LambdaQueryWrapper的用法可以查閱

官方文檔

自定義更新語句:

@Repository
public interface OrderDao extends BaseMapper<UserOrder> {

    /**
     * 根據訂單id更新訂單
     * <mapper namespace="OrderDao">
     *   <update id="updateByOrderId" parameterType="UserOrder">
     *     update user_order
     *     <set>
     *       <if test="userOrder.userId != null">user_id = #{userOrder.userId},</if>
     *       <if test="userOrder.payAmount != null">pay_amount = #{userOrder.payAmount},</if>
     *       <if test="userOrder.saasStoreId != null">saas_store_id = #{userOrder.getSaasStoreId}</if>
     *       <!-- .... 其他語句 -->
     *     </set>
     *     where order_id = #{userOrder.orderId}
     *   </update>
     * </mapper>
     *
     * @param userOrder 使用者訂單
     */
    default void updateByOrderId(UserOrder userOrder) {
        LambdaUpdateWrapper<UserOrder> updateWrapper = Wrappers.lambdaUpdate();
        update(userOrder, updateWrapper.eq(UserOrder::getOrderId, userOrder.getOrderId()));
    }
}           

更多有關LambdaUpdateWrapper的用法可以查閱

自動填充

技術團隊通常對資料表結構有些規範要求,阿裡的mysql規約中要求資料表必須有主鍵id,建立時間gmtCreated,更新時間gmtModified三個字段。業務代碼中填充三個字段的代碼無疑會成為樣闆代碼,利用MyBatis-Plus提供的自動填充功能可以有效解決這個問題。

@TableField注解有個fill屬性,表示被标注的字段需要自動填充,取值如下:

  • INSERT:在插入時填充
  • INSERT_UPDATE:在插入和更新時都更新
上面代碼中對gmtCreated和gmtModified上加了@TableField(fill=INSERT)

在您的應用中添加一個類型為MetaObjectHandler類型的SpringBean

@Component
public class OrderMetaObjectHandler implements MetaObjectHandler {

    private static final String FIELD_GMT_CREATE = "gmtCreate";
    private static final String FIELD_GMT_MODIFIED = "gmtModified";
    
      // 插入操作時執行自動填充方法
    @Override
    public void insertFill(MetaObject metaObject) {
        Date now = new Date();
        setInsertFieldValByName(FIELD_GMT_CREATE, now, metaObject);
        setInsertFieldValByName(FIELD_GMT_MODIFIED, now, metaObject);
    }

    // 更新時執行自動填充方法
    @Override
    public void updateFill(MetaObject metaObject) {
        setUpdateFieldValByName(FIELD_GMT_MODIFIED, new Date(), metaObject);
    }
}           

如此聲明後,上述自動填充代碼就會在所有的插入和更新操作時執行。

對于主鍵ID的自動生成,由于預設情況下主鍵有自己的生成政策,如果要使用自動填充,需要将主鍵ID的生成政策改為INPUT,可以使用@TableId(idType=INPUT)标注具體實體類的id字段或者

mybatis-plus.global-config.db-config.id-type=input

進行全局配置。

下面以使用自定義序列生成主鍵ID為例:

@Component
public class OrderMetaObjectHandler implements MetaObjectHandler {

    private static final String DEFAULT_SEQUENCE_NAME = "defaultSequence";

    @Autowired
    private GroupSequence seq;

    @Override
    public void insertFill(MetaObject metaObject) {
        Class<?> tableClass = metaObject.getOriginalObject().getClass();
        TableInfo tableInfo = TableInfoHelper.getTableInfo(tableClass);
        
        setFieldValByName(tableInfo.getKeyProperty(), seq.nextValue(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {}
}           

枚舉類型序列化支援

預設情況下,枚舉類型将直接使用枚舉值的名稱儲存到資料庫的文本類型(char,varchar)字段中。

我們經常會使用code模式為枚舉指定數字類型的編碼,希望儲存編碼到資料庫的數值類型(tinyint,int)字段中

上文中UserOrder的source字段是個枚舉類型,聲明如下:

public enum Source{
    ELEME(1),ALIPAY(2);
    
    @EnumValue // 表示使用該值儲存到資料庫
    private final int code;
  
  Source(int code){
    this.code = code;
  }
}           

Mybatis-Plus需要通過掃描發現需要處理的枚舉類,為了縮小掃描範圍需要如下配置

mybatis-plus.type-enums-package=com.alsc.databus.order.model.enums           

這樣既可直接儲存UserOrder的實體到資料庫,儲存和反查枚舉值均能由架構自行轉換,無須人工編寫代碼。

針對枚舉的更多支援可以查閱

邏輯删除

邏輯删除是資料庫操作的常用模式,通常我們會在實體類中聲明一個

int deleted

字段,删除時将該值設定為1。

MyBatis-Plus為我們提供了自動實作該模式的方式:

@Data
public class UserOrder{
  private Long id;
  
  @TableLogic
  private Integer deleted
}           

使用@TableLogic注解标注在表示邏輯删除的字段上。

然後再使用Mapper中的delete方法,實際執行的語句就是

update user_order set deleted=1 where id=1

而使用Mapper的selectXXX方法,實際執行的語句是

select * from user_order where id=1 and deleted =0

如果要改變表示删除狀态的邏輯值,可以使用如下配置

mybatis-plus:
  global-config:
    db-config:
      logic-delete-value: 1 # 邏輯已删除值(預設為 1)
      logic-not-delete-value: 0 # 邏輯未删除值(預設為 0)           

自動代碼生成

MyBatis-Plus提供了從資料庫表生成Entity,Mapper Interface,Mapper XML,Service,Controller的生成器,預設支援Velocity,Freemarker,Beetl三種模闆引擎,也可以通過自行擴充支援其他的模闆引擎。

感興趣的讀者可以自行查詢

不鼓勵使用代碼生成能力,原因如下:

  • 能通過模闆生成的代碼無疑都是樣闆代碼
  • 代碼生成很難細粒度按需生成,備援的代碼會為代碼重構帶來不必要的負擔

特别是在模型設計階段,可能需要經常調整模型或資料表結構,這時已經生成的代碼需要人工同步修改。

  • 樣闆代碼會引入大量的噪音,代碼讀者需要過濾噪音代碼才能關注重要資訊。
  • 代碼模闆的好壞決定代碼生成的品質,擴散安全風險

如果要使用代碼模闆,盡可能隻用于生成Entity類。