MyBatis-Plus碼之重器 lambda 表達式使用指南,開發效率瞬間提高80%

1、回顧

如今愈來愈流行基於 SpringBoot 開發 Web 應用,其中利用 Mybatis 做爲數據庫 CRUD 操做已成爲主流。樓主以 MySQL 爲例,總結了九大類使用 Mybatis 操做數據庫 SQL 小技巧分享給你們。html

  1. 分頁查詢
  2. 預置 sql 查詢字段
  3. 一對多級聯查詢
  4. 一對一級聯查詢
  5. foreach 搭配 in 查詢
  6. 利用if 標籤拼裝動態 where 條件
  7. 利用 choose 和 otherwise組合標籤拼裝查詢條件
  8. 動態綁定查詢參數:_parameter
  9. 利用 set 配合 if 標籤,動態設置數據庫字段更新值

01 分頁查詢

利用 limit 設置每頁 offset 偏移量和每頁 size 大小。java

select * from sys_user u
LEFT JOIN sys_user_site s ON u.user_id = s.user_id
LEFT JOIN sys_dept d ON d.dept_id = s.dept_id
LEFT JOIN sys_emailinfo e ON u.user_id = e.userid AND e.MAIN_FLAG = 'Y'
<where>
 <include refid="userCondition"/>
</where>
limit #{offset}, #{limit}

02 預置 sql 查詢字段

<sql id="columns">
  id,title,content,original_img,is_user_edit,province_id,status,porder
</sql>

查詢 select 語句引用 columns:mysql

<select id="selectById" resultMap="RM_MsShortcutPanel">
 seelct
 <include refid="columns"/>
 from cms_self_panel
 where
 id = #{_parameter}
</select>

03 一對多級聯查詢

利用 mybatis 的 collection 標籤,能夠在每次查詢文章主體同時經過 queryparaminstancelist 級聯查詢出關聯表數據。git

<resultMap id="BaseResultMap" type="com.unicom.portal.pcm.entity.ArticleEntity">
 <id column="id" jdbcType="BIGINT" property="id"/> 
 <collection property="paramList" column="id" select="queryparaminstancelist"/>
</resultMap>

queryparaminstancelist 的 sql 語句程序員

<select id="queryparaminstancelist" resultMap="ParamInstanceResultMap">
 select * from `cms_article_flow_param_instance` where article_id=#{id} 
</select>

04 一對一級聯查詢

利用 mybatis 的 association 標籤,一對一查詢關聯表數據。github

<resultMap id="BaseResultMap" type="com.unicom.portal.pcm.entity.ArticleEntity">
 <association property="articleCount" javaType="com.unicom.portal.pcm.entity.MsArticleCount"/>
</resultMap>

查詢sql語句:web

MsArticlecount 實體對象的屬性值能夠從 上面的 select 後的 sql 字段進行匹配映射獲取。spring

05 foreach 搭配 in 查詢

利用 foreach 遍歷 array 集合的參數,拼成 in 查詢條件sql

<foreach collection="array" index="index" item="item" open="(" separator="," close=")">
 #{item}
</foreach>

06 利用 if 標籤拼裝動態 where 條件

select r.*, (select d.org_name from sys_dept d where d.dept_id = r.dept_id) deptName from sys_role r
<where>
r.wid = #{wid}
<if test="roleName != null and roleName.trim() != ''">
and r.`role_name` like concat('%',#{roleName},'%')
</if>
<if test="status != null and status.trim() != ''">
and r.`status` = #{status}
</if>
</where>

07 利用 choose 和 otherwise 組合標籤拼裝查詢條件

<choose>
 <when test="sidx != null and sidx.trim() != ''">
 order by r.${sidx} ${order}
 </when>
 <otherwise>
 order by r.role_id asc
 </otherwise>
</choose>

08 隱形綁定參數:_parameter

_parameter 參數的含義數據庫

當 Mapper、association、collection 指定只有一個參數時進行查詢時,可使用 _parameter,它就表明了這個參數。

另外,當使用 Mapper指定方法使用 @Param 的話,會使用指定的參數值代替。

SELECT id, grp_no grpNo, province_id provinceId, status FROM tj_group_province
<where> 
 ...
 <if test="_parameter!=null">
 and grp_no = #{_parameter}
 </if>
</where>

09 利用 set 配合 if 標籤,動態設置數據庫字段更新值

<update id="updateById">
 UPDATE cms_label
 <set>
   <if test="labelGroupId != null">
     label_group_id = #{labelGroupId},
   </if>
   dept_id = #{deptId},
   <if test="recommend != null">
     is_recommend = #{recommend},
   </if>
 </set>
 WHERE label_id = #{labelId} 
</update

2、Mybatis-Plus Lambda 表達式理論篇

背景

若是 Mybatis-Plus 是扳手,那 Mybatis Generator 就是生產扳手的工廠。

MyBatis 是一種操做數據庫的 ORM 框架,提供一種 Mapper 類,支持讓你用 java 代碼進行增刪改查的數據庫操做,省去了每次都要手寫 sql 語句的麻煩。可是有一個前提,你得先在 xml 中寫好 sql 語句,也是很麻煩的。

題外話:Mybatis 和 Hibernate 的比較

  • Mybatis 是一個半 ORM 框架;Hibernate 是一個全 ORM 框架。Mybatis 須要本身編寫 sql 。
  • Mybatis 直接編寫原生 sql,靈活度高,能夠嚴格控制 sql 執行性能;Hibernate的自動生成 hql,由於更好的封裝型,開發效率提升的同時,sql 語句的調優比較麻煩。
  • Hibernate的 hql 數據庫移植性比 Mybatis 更好,Hibernate 的底層對 hql 進行了處理,對於數據庫的兼容性更好,
  • Mybatis 直接寫的原生 sql 都是與數據庫相關,不一樣數據庫 sql 不一樣,這時就須要多套 sql 映射文件。
  • Hibernate 在級聯刪除的時候效率低;數據量大, 表多的時候,基於關係操做會變得複雜。
  • Mybatis 和 Hibernate 均可以使用第三方緩存,而 Hibernate 相比 Mybatis 有更好的二級緩存機制。

爲何要選擇 Lambda 表達式?

Mybatis-Plus 的存在就是爲了稍稍彌補 Mybatis 的不足。

在咱們使用 Mybatis 時會發現,每當要寫一個業務邏輯的時候都要在 DAO 層寫一個方法,再對應一個 SQL,即便是簡單的條件查詢、即便僅僅改變了一個條件都要在 DAO層新增一個方法,針對這個問題,Mybatis-Plus 就提供了一個很好的解決方案:lambda 表達式,它可讓咱們避免許多重複性的工做。

想一想 Mybatis 官網提供的 CRUD 例子吧,基本上 xml 配置佔據了絕大部分。而用 Lambda 表達式寫的 CRUD 代碼很是簡潔,真正作到零配置,不須要在 xml 或用註解(@Select)寫大量原生 SQL 代碼。

LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getSex, 0L)
        .like(UserEntity::getUserName, "dun");
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like全包含關鍵字查詢::" + u.getUserName()));

lambda 表達式的理論基礎

Java中的 lambda 表達式實質上是一個匿名方法,但該方法並不是獨立執行,而是用於實現由函數式接口定義的惟一抽象方法。

使用 lambda 表達式時,會建立實現了函數式接口的一個匿名類實例,如 Java8 中的線程 Runnable 類實現了函數接口:@FunctionalInterface。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

日常咱們執行一個 Thread 線程:

new Thread(new Runnable() {
  @Override
  public void run() {
      System.out.println("xxxx");
  }
}).start();

若是用 lambda 會很是簡潔,一行代碼搞定。

new Thread(()-> System.out.println("xxx")).start();

因此在某些場景下使用 lambda 表達式真的能減小 java 中一些冗長的代碼,增長代碼的優雅性。

lambda 條件構造器基礎類:包裝器模式(裝飾模式)之 AbstractWrapper AbstractWrapper 條件構造器說明

  1. 出現的第一個入參 boolean condition 表示該條件是否加入最後生成的 sql 中,例如:query.like(StringUtils.isNotBlank(name), Entity::getName, name) .eq(age!=null && age >= 0, Entity::getAge, age)
  2. 代碼塊內的多個方法均爲從上往下補全個別 boolean 類型的入參,默認爲 true
  3. 出現的泛型 Param 均爲 Wrapper 的子類實例(均具備 AbstractWrapper 的全部方法)
  4. 方法在入參中出現的 R 爲泛型,在普通 wrapper 中是 String ,在 LambdaWrapper 中是函數(例:Entity::getId,Entity 爲實體類,getId爲字段id的getMethod)
  5. 方法入參中的 R column 均表示數據庫字段,當 R 具體類型爲 String 時則爲數據庫字段名(字段名是數據庫關鍵字的本身用轉義符包裹!)!而不是實體類數據字段名!!!,另當 R 具體類型爲 SFunction 時項目 runtime 不支持 eclipse 自家的編譯器!
  6. 使用普通 wrapper,入參爲 Map 和 List 的均以 json 形式表現!
  7. 使用中若是入參的 Map 或者 List爲空,則不會加入最後生成的 sql 中!

警告:

不支持以及不同意在 RPC 調用中把 Wrapper 進行傳輸。

Wrapper 很重 傳輸 Wrapper 能夠類比爲你的 controller 用 map 接收值(開發一時爽,維護火葬場) 正確的 RPC 調用姿式是寫一個 DTO 進行傳輸,被調用方再根據 DTO 執行相應的操做 咱們拒絕接受任何關於 RPC 傳輸 Wrapper 報錯相關的 issue 甚至 pr。

AbstractWrapper 內部結構

從上圖,咱們瞭解到 AbstractWrapper 的實際上實現了五大接口:

  • SQL 片斷函數接口:ISqlSegment
@FunctionalInterface
public interface ISqlSegment extends Serializable {
    /**
     * SQL 片斷
     */
    String getSqlSegment();
}
  • 比較值接口 Compare<Children, R>,如 等值 eq、不等於:ne、大於 gt、大於等於:ge、小於 lt、小於等於 le、between、模糊查詢:like 等等
  • 嵌套接口 Nested<Param, Children> ,如 and、or
  • 拼接接口 Join<Children>,如 or 、exists
  • 函數接口 Func<Children, R>,如 in 查詢、groupby 分組、having、order by排序等

經常使用的 where 條件表達式 eq、like、in、ne、gt、ge、lt、le。

@Override
public Children in(boolean condition, R column, Collection<?> coll) {
    return doIt(condition, () -> columnToString(column), IN, inExpression(coll));
}

public Children notIn(boolean condition, R column, Collection<?> coll)

public Children inSql(boolean condition, R column, String inValue)

public Children notInSql(boolean condition, R column, String inValue)

public Children groupBy(boolean condition, R... columns)

public Children orderBy(boolean condition, boolean isAsc, R... columns)
    
public Children eq(boolean condition, R column, Object val)

public Children ne(boolean condition, R column, Object val)

public Children gt(boolean condition, R column, Object val)

public Children ge(boolean condition, R column, Object val)

public Children lt(boolean condition, R column, Object val)

public Children le(boolean condition, R column, Object val)

...

/**
 * 普通查詢條件
 *
 * @param condition  是否執行
 * @param column     屬性
 * @param sqlKeyword SQL 關鍵詞
 * @param val        條件值
 */
protected Children addCondition(boolean condition, R column, SqlKeyword sqlKeyword, Object val) {
    return doIt(condition, () -> columnToString(column), sqlKeyword, () -> formatSql("{0}", val));
}

SQL 片斷函數接口

lambda 這麼好用的祕訣在於 SQL 片斷函數接口:ISqlSegment,咱們在 doIt 方法找到 ISqlSegment 對象參數,翻開 ISqlSegment 源碼,發現它真實的廬山真面目,原來是基於 Java 8 的函數接口 @FunctionalInterface 實現!

ISqlSegment 就是對 where 中的每一個條件片斷進行組裝。

/**
 * 對sql片斷進行組裝
 *
 * @param condition   是否執行
 * @param sqlSegments sql片斷數組
 * @return children
 */
protected Children doIt(boolean condition, ISqlSegment... sqlSegments) {
    if (condition) {
        expression.add(sqlSegments);
    }
    return typedThis;
}
   
@FunctionalInterface
public interface ISqlSegment extends Serializable {

    /**
     * SQL 片斷
     */
    String getSqlSegment();
}

從 MergeSegments 類中,咱們找到 getSqlSegment 方法,其中代碼片斷

sqlSegment = normal.getSqlSegment() + groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment()

這段代碼代表,一條完整的 where 條件 SQL 語句,最終由 normal SQL 片斷,groupBy SQL 片斷,having SQL 片斷,orderBy SQL 片斷拼接而成。

@Getter
@SuppressWarnings("serial")
public class MergeSegments implements ISqlSegment {

    private final NormalSegmentList normal = new NormalSegmentList();
    private final GroupBySegmentList groupBy = new GroupBySegmentList();
    private final HavingSegmentList having = new HavingSegmentList();
    private final OrderBySegmentList orderBy = new OrderBySegmentList();

    @Getter(AccessLevel.NONE)
    private String sqlSegment = StringPool.EMPTY;
    @Getter(AccessLevel.NONE)
    private boolean cacheSqlSegment = true;

    public void add(ISqlSegment... iSqlSegments) {
        List<ISqlSegment> list = Arrays.asList(iSqlSegments);
        ISqlSegment firstSqlSegment = list.get(0);
        if (MatchSegment.ORDER_BY.match(firstSqlSegment)) {
            orderBy.addAll(list);
        } else if (MatchSegment.GROUP_BY.match(firstSqlSegment)) {
            groupBy.addAll(list);
        } else if (MatchSegment.HAVING.match(firstSqlSegment)) {
            having.addAll(list);
        } else {
            normal.addAll(list);
        }
        cacheSqlSegment = false;
    }

    @Override
    public String getSqlSegment() {
        if (cacheSqlSegment) {
            return sqlSegment;
        }
        cacheSqlSegment = true;
        if (normal.isEmpty()) {
            if (!groupBy.isEmpty() || !orderBy.isEmpty()) {
                sqlSegment = groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment();
            }
        } else {
            sqlSegment = normal.getSqlSegment() + groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment();
        }
        return sqlSegment;
    }
}

3、Mybatis-Plus Lambda 表達式實戰

01 環境準備

1. Maven 依賴

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
</dependency>

2. 實體(表)以及 Mapper 表映射文件

  • Base 實體
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@SuperBuilder(toBuilder = true)
@Data
public class BaseEntity {

    @TableField(value = "created_tm", fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createdTm;

    @TableField(value = "created_by", fill = FieldFill.INSERT)
    private String createdBy;

    @TableField(value = "modified_tm", fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime modifiedTm;

    @TableField(value = "modified_by", fill = FieldFill.INSERT_UPDATE)
    private String modifiedBy;
}
  • 用戶帳號實體:UserEntity
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@SuperBuilder(toBuilder = true)
@Data
@TableName("sys_user")
public class UserEntity extends BaseEntity{
    private Long userId;
    private String userName;
    private Integer sex;
    private Integer age;
    private String mobile;
}

Mapper 操做類

List<UserDTO> selectUsers();

UserEntity selectByIdOnXml(long userId);

@Results(id = "userResult", value = {
        @Result(property = "user_id", column = "userId", id = true),
        @Result(property = "userName", column = "user_name"),
        @Result(property = "sex", column = "sex"),
        @Result(property = "mobile", column = "mobile"),
        @Result(property = "age", column = "age")
})
@Select("select * from sys_user where user_id = #{id}")
UserEntity selectByIdOnSelectAnnotation(@Param("id") long id);

@SelectProvider(type = UserSqlProvider.class, method = "selectById")
@ResultMap("BaseResultMap")
UserEntity selectByIdOnSelectProviderAnnotation(long id);

@Select("select * from sys_user where user_id = #{id} and user_name=#{userName}")
@ResultMap("BaseResultMap")
UserEntity selectByIdOnParamAnnotation(@Param("id") long id, @Param("userName") String uerName);

Mapper 表映射文件

<mapper namespace="com.dunzung.mybatisplus.query.mapper.UserMapper">
    <resultMap id="BaseResultMap" type="com.dunzung.mybatisplus.query.entity.UserEntity">
        <id column="user_id" property="userId"/>
        <result column="user_name" property="userName"/>
        <result column="sex" property="sex"/>
        <result column="age" property="age"/>
        <result column="mobile" property="mobile"/>
    </resultMap>
    
    <resultMap id="RelationResultMap" type="com.dunzung.mybatisplus.query.entity.UserDTO" extends="BaseResultMap">
        <association property="card" column="{userId,user_id}"
                     select="com.dunzung.mybatisplus.query.mapper.CardMapper.selectCardByUserId"/>
        <collection property="orders" column="{userId,user_id}"
                    select="com.dunzung.mybatisplus.query.mapper.OrderMapper.selectOrders"/>
    </resultMap>
    
    <select id="selectUsers" resultMap="RelationResultMap">
        select * from sys_user
    </select>
    
    <select id="selectByIdOnXml" resultMap="BaseResultMap">
        select * from sys_user where user_id = #{userId}
   </select>
    
</mapper>
  • 訂單實體:OrderEntity
@Data
@TableName("sys_user_card")
public class CardEntity {
    private Long cardId;
    private String cardCode;
    private Long userId;
}

Mapper 操做類

@Mapper
public interface OrderMapper extends BaseMapper<OrderEntity> {
}

Mapper 表映射文件

<mapper namespace="com.dunzung.mybatisplus.query.mapper.OrderMapper">
    <resultMap id="BaseResultMap" type="com.dunzung.mybatisplus.query.entity.OrderEntity">
        <id column="order_id" property="orderId"/>
        <result column="order_name" property="orderName"/>
        <result column="user_id" property="userId"/>
        <result column="price" property="price"/>
        <result column="created_tm" property="createdTm"/>
    </resultMap>

    <select id="selectOrders" resultMap="BaseResultMap">
         select * from biz_order where user_id = #{userId}
    </select>

</mapper>
  • 身份證明體:CardEntity
@Data
@TableName("biz_order")
public class OrderEntity {
    private Long orderId;
    private String orderName;
    private Integer userId;
    private Date createdTm;
    private Integer price;
}

Mapper 操做類

@Mapper
public interface CardMapper extends BaseMapper<CardEntity> {
}

Mapper 表映射文件

<mapper namespace="com.dunzung.mybatisplus.query.mapper.CardMapper">
    <resultMap id="BaseResultMap" type="com.dunzung.mybatisplus.query.entity.CardEntity">
        <id column="card_id" property="cardId"/>
        <result column="card_code" property="cardCode"/>
        <result column="user_id" property="userId"/>
    </resultMap>
    <select id="selectCardByUserId" resultMap="BaseResultMap">
        select * from sys_user_card where user_id = #{userId}
    </select>
</mapper>

02 Lambda 基礎篇

lambda 構建複雜的查詢條件構造器:LambdaQueryWrapper

LambdaQueryWrapper 四種不一樣的 lambda 構造方法

  • 方式一 使用 QueryWrapper 的成員方法方法 lambda 構建 LambdaQueryWrapper
LambdaQueryWrapper<UserEntity> lambda = new QueryWrapper<UserEntity>().lambda();
  • 方式二 直接 new 出 LambdaQueryWrapper
LambdaQueryWrapper<UserEntity> lambda = new  LambdaQueryWrapper<>();
  • 方式三 使用 Wrappers 的靜態方法 lambdaQuery 構建 LambdaQueryWrapper 推薦
LambdaQueryWrapper<UserEntity> lambda = Wrappers.lambdaQuery();
  • 方式四:鏈式查詢
List<UserEntity> users = new LambdaQueryChainWrapper<UserEntity>(userMapper)
            .like(User::getName, "雨").ge(User::getAge, 20).list();

筆者推薦使用 Wrappers 的靜態方法 lambdaQuery 構建 LambdaQueryWrapper 條件構造器。

Debug 調試

爲了 Debug 調試方便,須要在 application.yml 啓動文件開啓 Mybatis-Plus SQL 執行語句全棧打印:

#mybatis
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

執行效果以下:

1 等值查詢:eq

@Test
public void testLambdaQueryOfEq() {
    //eq查詢
    //至關於 select * from sys_user where user_id = 1
    LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
    lqw.eq(UserEntity::getUserId, 1L);
    UserEntity user = userMapper.selectOne(lqw);
    System.out.println("eq查詢::" + user.getUserName());
}

eq 查詢等價於原生 sql 的等值查詢。

select * from sys_user where user_id = 1

2 範圍查詢 :in

@Test
public void testLambdaQueryOfIn() {  
    List<Long> ids = Arrays.asList(1L, 2L);
    LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();  
    lqw.in(UserEntity::getUserId, ids);
    List<UserEntity> userList = userMapper.selectList(lqw);
    userList.forEach(u -> System.out.println("in查詢::" + u.getUserName()));
}

in 查詢等價於原生 sql 的 in 查詢

select * from sys_user where user_id in (1,2)

3 通配符模糊查詢:like

@Test
public void testLambdaQueryOfLikeAll() {
    LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
    lqw.eq(UserEntity::getSex, 0L)
            .like(UserEntity::getUserName, "dun");
    List<UserEntity> userList = userMapper.selectList(lqw);
    userList.forEach(u -> System.out.println("like全包含關鍵字查詢::" + u.getUserName()));
}

like 查詢等價於原生 sql 的 like 全通配符模糊查詢。

select * from sys_user where sex = 0 and user_name like '%dun%'

4 右通配符模糊查詢:likeRight

@Test
public void testLambdaQueryOfLikeRight() { 
    LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
    lqw.eq(UserEntity::getSex, 0L)
            .likeRight(UserEntity::getUserName, "dun");
    List<UserEntity> userList = userMapper.selectList(lqw);
    userList.forEach(u -> System.out.println("like Right含關鍵字查詢::" + u.getUserName()));
}

likeRight 查詢至關於原生 sql 的 like 右通配符模糊查詢。

select * from sys_user where sex = 0 and user_name like 'dun%'

5 左通配符模糊查詢:likeLeft

@Test
public void testLambdaQueryOfLikeLeft() {  
    LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
    lqw.eq(UserEntity::getSex, 0L)
            .likeLeft(UserEntity::getUserName, "zung");
    List<UserEntity> userList = userMapper.selectList(lqw);
    userList.forEach(u -> System.out.println("like Left含關鍵字查詢::" + u.getUserName()));
}

likeLeft 查詢至關於原生 sql 的 like 左通配符模糊查詢。

select * from sys_user where sex = 0 and user_name like '%zung'

6 條件判斷查詢

條件判斷查詢相似於 Mybatis 的 if 標籤,第一個入參 boolean condition 表示該條件是否加入最後生成的 sql 中。

@Test
public void testLambdaQueryOfBoolCondition() {
    UserEntity condition = UserEntity.builder()
            .sex(1)
            .build();
    //eq 或 like 條件判斷查詢
    LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
    lqw.eq(condition.getSex() != null, UserEntity::getSex, 0L)
            // 知足 bool 判斷,是否進查詢按字段 userName 查詢
            .like(condition.getUserName() != null, UserEntity::getUserName, "dun");
    List<UserEntity> userList = userMapper.selectList(lqw);
    userList.forEach(u -> System.out.println("like查詢::" + u.getUserName()));
}

7 利用 or 和 and 構建複雜的查詢條件

@Test
public void testLambdaQueryOfOr_And() {  
    LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
    lqw.eq(UserEntity::getSex, 0L)
            .and(wrapper->wrapper.eq(UserEntity::getUserName,"dunzung")
                    .or().ge(UserEntity::getAge, 50));
    List<UserEntity> userList = userMapper.selectList(lqw);
    userList.forEach(u -> System.out.println("like查詢::" + u.getUserName()));
}

上面實例查詢等價於原生 sql 查詢:

select * from sys_user where sex = 0 and (use_name = 'dunzung' or age >=50)

8 善於利用分頁利器 PageHelpler

@Test
public void testLambdaPage() {
    //PageHelper分頁查詢
    //至關於 select * from sys_user limit 0,2
    int pageNumber = 0;
    int pageSize = 2;
    PageHelper.startPage(pageNumber + 1, pageSize);
    LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
    lqw.orderByAsc(UserEntity::getAge)
          .orderByDesc(UserEntity::getMobile);
    List<UserEntity> userList = userMapper.selectList(lqw); 
    userList.forEach(u -> System.out.println("page分頁查詢::" + u.getUserName()));
}

上面實例查詢等價於原生 sql 分頁查詢:

select * from sys_user order by age desc,mobile desc limit 0,2

另外,Mybatis-Plus 自帶分頁組件,BaseMapper 接口提供兩種分頁方法來實現物理分頁。

  • 第一個返回實體對象容許 null
  • 第二我的返回 map 對象多用於在指定放回字段時使用,避免爲指定字段 null 值出現
IPage<T> selectPage(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper);
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper);

注意,Mybatis-Plus 自帶分頁組件時,須要配置 PaginationInterceptor 分頁插件。

@Bean
public PaginationInterceptor paginationInterceptor() {
    return new PaginationInterceptor();
}

9 更新條件構造器:LambdaUpdateWrapper

@Test
public void testLambdaUpdate() {
    LambdaUpdateWrapper<UserEntity> luw = Wrappers.lambdaUpdate();
    luw.set(UserEntity::getUserName, "dunzung01")
            .set(UserEntity::getSex, 1);
    luw.eq(UserEntity::getUserId, 1);
    userMapper.update(null, luw);
}

03 進階篇

1. Association

Association 標籤適用於表和表之間存在一對一的關聯關係,如用戶和身份證存在一我的只會有一個身份證號,反過來也成立。

@Test
public void testOnAssociationTag() {
    List<UserDTO> userList = userMapper.selectUsers();
    userList.forEach(u -> System.out.println(u.getUserName()));
}

XML配置

<resultMap id="RelationResultMap" type="com.dunzung.mybatisplus.query.entity.UserDTO" extends="BaseResultMap">
  <association property="card" column="{userId,user_id}"
               select="com.dunzung.mybatisplus.query.mapper.CardMapper.selectCardByUserId"/>
</resultMap>

2. Collection

Collection 標籤適用於表和表之間存在一對多的關聯關係,如用戶和訂單存在一我的能夠購買多個物品,產生多個購物訂單。

@Test
public void testOnCollectionTag() {
    List<UserDTO> userList = userMapper.selectUsers();
    userList.forEach(u -> System.out.println(u.getUserName()));
}

XML配置

<resultMap id="RelationResultMap" type="com.dunzung.mybatisplus.query.entity.UserDTO" extends="BaseResultMap">
  <collection property="orders" 
      column="{userId,user_id}" 
      select="com.dunzung.mybatisplus.query.mapper.OrderMapper.selectOrders"/>
  </resultMap>

注意 Association 和 Collection 前後關係,在編寫 ResultMap 時,association 在前,collection 標籤在後。

<resultMap id="RelationResultMap" type="com.dunzung.mybatisplus.query.entity.UserDTO" extends="BaseResultMap">
        <association property="card" column="{userId,user_id}"
                     select="com.dunzung.mybatisplus.query.mapper.CardMapper.selectCardByUserId"/>
        <collection property="orders" column="{userId,user_id}"
                    select="com.dunzung.mybatisplus.query.mapper.OrderMapper.selectOrders"/>
    </resultMap>

若是兩者顛倒順序會提示錯誤。

3. 元對象字段填充屬性值:MetaObjectHandler

MetaObjectHandler元對象字段填充器的填充原理是直接給 entity 的屬性設置值,提供默認方法的策略均爲:

若是屬性有值則不覆蓋,若是填充值爲 null 則不填充,字段必須聲明 TableField 註解,屬性 fill 選擇對應策略,該聲明告知 Mybatis-Plus 須要預留注入 SQL字段。 TableField 註解則是指定該屬性在對應狀況下必有值,若是無值則入庫會是 null。

自定義填充處理器 MyMetaObjectHandler 在 Spring Boot 中須要聲明 @Component 或 @Bean 注入,要想根據註解 FieldFill.xxx,如:

@TableField(value = "created_tm", fill = FieldFill.INSERT)
private LocalDateTime createdTm;

@TableField(value = "modified_tm", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime modifiedTm;

和字段名以及字段類型來區分必須使用父類的 setInsertFieldValByName 或者 setUpdateFieldValByName 方法,不須要根據任何來區分可使用父類的 setFieldValByName 方法 。

/**
 * 屬性值填充 Handler
 *
 * @author 猿芯
 * @since 2021/3/30
 */
@Component
public class FillMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        this.setInsertFieldValByName("createdTm", LocalDateTime.now(), metaObject);
        this.setInsertFieldValByName("createdBy", MvcContextHolder.getUserName(), metaObject);
        this.setFieldValByName("modifiedTm", LocalDateTime.now(), metaObject);
        this.setFieldValByName("modifiedBy", MvcContextHolder.getUserName(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setUpdateFieldValByName("modifiedTm", LocalDateTime.now(), metaObject);
        this.setUpdateFieldValByName("modifiedBy", MvcContextHolder.getUserName(), metaObject);
    }
}

通常 FieldFill.INSERT 用父類的 setInsertFieldValByName 方法更新建立屬性(建立人、建立時間)值;FieldFill.INSERT_UPDATE 用父類的 setUpdateFieldValByName 方法更新修改屬性(修改人、修改時間)值;若是想讓諸如 FieldFill.INSERT 或 FieldFill.INSERT_UPDATE 任什麼時候候不起做用,用父類的 setFieldValByName 設置屬性(建立人、建立時間、修改人、修改時間)值便可。

4. 自定義SQL

使用 Wrapper 自定義 SQL 須要 mybatis-plus 版本 >= 3.0.7 ,param 參數名要麼叫 ew,要麼加上註解 @Param(Constants.WRAPPER) ,使用 ${ew.customSqlSegment} 不支持 Wrapper 內的 entity生成 where 語句。

註解方式

@Select("select * from mysql_data ${ew.customSqlSegment}")
List<MysqlData> getAll(@Param(Constants.WRAPPER) Wrapper wrapper);

XML配置

List<MysqlData> getAll(Wrapper ew);
<select id="getAll" resultType="MysqlData">
 SELECT * FROM mysql_data ${ew.customSqlSegment}
</select>

4、Mybatis-Plus lambda 表達式的優點與劣勢

經過上面豐富的舉例詳解以及剖析 lambda 底層實現原理,想必你們會問:」 lambda 表達式彷佛只支持單表操做?」

據我對 Mybatis-Plus 官網的瞭解,目前確實是這樣。依筆者實際運用經驗來看,其實程序員大部分開發的功能基本上都是針對單表操做的,Lambda 表達式的優點在於幫助開發者減小在 XML 編寫大量重複的 CRUD 代碼,這點是很是重要的 nice 的。很顯然,Lambda 表達式對於提升程序員的開發效率是不言而喻的,我想這點也是我做爲程序員很是喜歡 Mybatis-Plus 的一個重要緣由。

可是,若是涉及對於多表之間的關聯查詢,lambda 表達式就顯得力不從心了,由於 Mybatis-Plus 並無提供相似於 join 查詢的條件構造器。

lambda 表達式優勢:

  1. 單表操做,代碼很是簡潔,真正作到零配置,如不須要在 xml 或用註解(@Select)寫大量原生 SQL 代碼
  2. 並行計算
  3. 預測表明將來的編程趨勢

lambda 表達式缺點:

  1. 單表操做,對於多表關聯查詢支持很差
  2. 調試困難
  3. 底層邏輯複雜

5、總結

Mybatis-Plus 推出的 lambda 表達式致力於構建複雜的 where 查詢構造器式並非銀彈,它能夠解決你實際項目中 80% 的開發效率問題,可是針對一些複雜的大 SQL 查詢條件支持地並很差,例如一些複雜的 SQL 報表統計查詢。

因此,筆者推薦單表操做用 lambda 表達式,查詢推薦用 LambdaQueryWrapper,更新用 LambdaUpdateWrapper;多表操做仍是老老實實寫一些原生 SQL ,至於原生 SQL 寫在哪裏? Mapper 文件或者基於註解,如 @Select 都是能夠的。

參考

做者:猿芯
來源: https://www.toutiao.com/i6951...
相關文章
相關標籤/搜索