天天看點

MyBatis這樣用,同僚直呼哇塞,堪稱最佳實踐

MyBatis是一款非常流行的ORM架構,相信很多小夥伴都在使用。我們經常會把它和MyBatis-Plus或者MBG一起使用,用多了之後對于其一些正常操作就不太熟悉了。最近總結了下MyBatis的實用用法和技巧,希望對大家有所幫助!

MyBatis簡介

MyBatis是一款優秀的開源持久層架構,支援自定義SQL查詢、存儲過程和進階映射,目前在Github上已有17k+Star。在MyBatis中,我們可以在XML中編寫SQL語句,然後綁定到Java方法中,通過參數和結果集的自動映射來實作複雜的查詢邏輯。MyBatis消除了幾乎所有JDBC操作和手動綁定參數操作,使用起來非常友善!

在SpringBoot中內建

下面我們來聊聊MyBatis在SpringBoot中的使用,首先我們需要內建它。
  • 在pom.xml中添加MyBatis提供的Spring Boot Starter;
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>
           
  • 然後在application.yml中配置好編寫SQL實作的xml檔案路徑,這裡我們存放在resources/dao目錄下;
mybatis:
  mapper-locations:
    - classpath:dao/*.xml
           
  • 然後添加Java配置,通過@MapperScan配置好Dao接口路徑,這樣就可以開始使用了。
/**
 * MyBatis配置類
 * Created by macro on 2019/4/8.
 */
@Configuration
@MapperScan("com.macro.mall.tiny.dao")
public class MyBatisConfig {
}
           

基本使用

下面我們來聊聊MyBatis的基本使用方法,涵蓋了基本的增删改查操作。

表結構說明

這裡将以mall項目中權限管理子產品相關表為例進行介紹,具體表結構如下。

MyBatis這樣用,同僚直呼哇塞,堪稱最佳實踐

項目結構說明

本文示例使用了mall-learning項目中的mall-tiny-mybatis子產品代碼,具體項目結構如下。

MyBatis這樣用,同僚直呼哇塞,堪稱最佳實踐

select

  • 首先是查詢操作,這裡我們以背景使用者表ums_admin為例,編寫一個根據ID查詢使用者的方法,先建立實體類UmsAdmin;
public class UmsAdmin implements Serializable {
    private Long id;

    private String username;

    private String password;

    @ApiModelProperty(value = "頭像")
    private String icon;

    @ApiModelProperty(value = "郵箱")
    private String email;

    @ApiModelProperty(value = "昵稱")
    private String nickName;

    @ApiModelProperty(value = "備注資訊")
    private String note;

    @ApiModelProperty(value = "建立時間")
    private Date createTime;

    @ApiModelProperty(value = "最後登入時間")
    private Date loginTime;

    @ApiModelProperty(value = "帳号啟用狀态:0->禁用;1->啟用")
    private Integer status;
}
           
  • 然後建立資料操作的接口UmsAdminDao,再添加對應的方法;
/**
 * 自定義UmsAdmin表查詢
 * Created by macro on 2022/10/20.
 */
@Repository
public interface UmsAdminDao {

    /**
     * 根據ID查詢使用者
     */
    UmsAdmin selectByIdSimple(Long id);
}
           
  • 再建立xml檔案UmsAdminDao.xml,添加查詢方法的SQL實作;
<select id="selectByIdSimple" resultType="com.macro.mall.tiny.model.UmsAdmin">
    select * from ums_admin where id = #{id}
</select>
           
  • 然後編寫測試類,注入Dao,調用Dao方法來進行測試;
/**
 * MyBatis基本操作測試
 * Created by macro on 2022/10/20.
 */
@SpringBootTest
public class MyBatisBaseTest {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyBatisBaseTest.class);

    @Autowired
    private UmsAdminDao umsAdminDao;

    @Test
    void testSelectByIdSimple(){
        UmsAdmin umsAdmin = umsAdminDao.selectByIdSimple(1L);
        LOGGER.info("testSelectByIdSimple result={}",umsAdmin);
    }
}
           
  • 此時你會發現,對于一些資料庫表中以下劃線分割的傳回字段無法自動映射,可以通過對字段取别名的方式來進行映射;
<select id="selectById" resultType="com.macro.mall.tiny.model.UmsAdmin">
    select username,
           password,
           icon,
           email,
           nick_name   as nickName,
           note,
           create_time as createTime,
           login_time  as loginTime,
           status
    from ums_admin
    where id = #{id}
</select>
           
  • 如果你覺得這種方式比較麻煩,也可以通過在application.yml中開啟全局下劃線自動轉駝峰功能來解決,個人習慣使用第一種。
mybatis:
  configuration:
    # 下劃線自動轉駝峰
    map-underscore-to-camel-case: true
           

insert

  • 接下來我們來編寫一個插入單個使用者的方法;
/**
 * 自定義UmsAdmin表查詢
 * Created by macro on 2022/10/20.
 */
@Repository
public interface UmsAdminDao {

    /**
     * 插入使用者
     */
    int insert(UmsAdmin entity);
}
           
  • 然後在xml中編寫對應的SQL實作,這裡需要注意的是如果想傳回插入後的自增ID的話,需要使用selectKey标簽進行配置。
<insert id="insert">
    insert into ums_admin(username, password, icon, email, nick_name, note, create_time, login_time)
    values (#{username}, #{password}, #{icon}, #{email}, #{nickName}, #{note}, #{createTime}, #{loginTime})
    <selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
</insert>
           

update

  • 接下來我們來編寫一個根據ID修改使用者資訊的方法;
/**
 * 自定義UmsAdmin表查詢
 * Created by macro on 2022/10/20.
 */
@Repository
public interface UmsAdminDao {
    /**
     * 根據ID修改使用者資訊
     */
    int updateById(UmsAdmin entity);
}
           
  • 然後在xml中編寫對應的SQL實作。
<update id="updateById">
    update ums_admin
    set username = #{username},
        password = #{password},
        icon = #{icon},
        email = #{email},
        nick_name = #{nickName},
        note = #{note},
        create_time = #{createTime},
        login_time = #{loginTime}
    where id = #{id}
</update>
           

delete

  • 接下來我們來編寫一個根據ID删除使用者的方法;
/**
 * 自定義UmsAdmin表查詢
 * Created by macro on 2022/10/20.
 */
@Repository
public interface UmsAdminDao {
    /**
     * 根據ID删除使用者
     */
    int deleteById(Long id);
}
           
  • 然後在xml中編寫對應的SQL實作。
<delete id="deleteById">
    delete from  ums_admin where id = #{id}
</delete>
           

動态SQL

通過MyBatis的動态SQL功能,我們可以靈活地在xml中實作各種複雜的操作,動态SQL功能需要依賴MyBatis的各種标簽,下面我們就來學習下。

if

  • if标簽可以實作判斷邏輯,這裡我們以根據使用者名和Email模糊查詢使用者為例,來聊聊它的使用;
/**
 * 自定義UmsAdmin表查詢
 * Created by macro on 2022/10/20.
 */
@Repository
public interface UmsAdminDao {
    /**
     * 根據使用者名和Email模糊查詢使用者
     * 不輸入查詢所有
     */
    List<UmsAdmin> selectByUsernameAndEmailLike(@Param("username") String username, @Param("email") String email);
}
           
  • xml中添加對應的SQL實作如下。
<select id="selectByUsernameAndEmailLike" resultType="com.macro.mall.tiny.model.UmsAdmin">
    select username,
           password,
           icon,
           email,
           nick_name   as nickName,
           note,
           create_time as createTime,
           login_time  as loginTime,
           status
    from ums_admin
    where 1=1
    <if test="username!=null and username!=''">
        and username like concat('%',#{username},'%')
    </if>
    <if test="email!=null and email!=''">
        and email like concat('%',#{email},'%')
    </if>
</select>
           

choose

  • choose标簽也可以實作判斷邏輯,上面的例子中當我們不輸入使用者名和Email時,會查詢出全部使用者,我們如果想不查詢出使用者,可以使用它;
<select id="selectByUsernameAndEmailLike2" resultType="com.macro.mall.tiny.model.UmsAdmin">
    select username,
    password,
    icon,
    email,
    nick_name as nickName,
    note,
    create_time as createTime,
    login_time as loginTime,
    status
    from ums_admin
    where 1=1
    <choose>
        <when test="username!=null and username!=''">
            and username like concat('%',#{username},'%')
        </when>
        <when test="email!=null and email!=''">
            and email like concat('%',#{email},'%')
        </when>
        <otherwise>
            and 1=2
        </otherwise>
    </choose>
</select>
           

where

  • 上面的例子中我們為了SQL拼接不出錯,添加了where 1=1這樣的語句,其實可以使用where标簽來實作查詢條件,當标簽内沒有内容時會自動去除where關鍵字,同時還會去除開頭多餘的and關鍵字。
<select id="selectByUsernameAndEmailLike3" resultType="com.macro.mall.tiny.model.UmsAdmin">
    select username,
    password,
    icon,
    email,
    nick_name as nickName,
    note,
    create_time as createTime,
    login_time as loginTime,
    status
    from ums_admin
    <where>
        <if test="username!=null and username!=''">
            and username like concat('%',#{username},'%')
        </if>
        <if test="email!=null and email!=''">
            and email like concat('%',#{email},'%')
        </if>
    </where>
</select>
           

set

  • 當我們拼接更新字段的語句時,也會面臨同樣的問題,此時可以使用set标簽來解決,比如我們現在想寫一個根據ID選擇性修改使用者資訊的方法;
/**
 * 自定義UmsAdmin表查詢
 * Created by macro on 2022/10/20.
 */
@Repository
public interface UmsAdminDao {
    /**
     * 根據ID選擇性修改使用者資訊
     */
    int updateByIdSelective(UmsAdmin entity);
}
           
  • 方法對應的SQL實作如下,這裡既避免了使用set關鍵字,也會将多餘的逗号去除。
<update id="updateByIdSelective">
    update ums_admin
    <set>
        <if test="username!=null and username!=''">
            username = #{username},
        </if>
        <if test="password!=null and password!=''">
            password = #{password},
        </if>
        <if test="icon!=null and icon!=''">
            icon = #{icon},
        </if>
        <if test="email!=null and email!=''">
            email = #{email},
        </if>
        <if test="nickName!=null and nickName!=''">
            nick_name = #{nickName},
        </if>
        <if test="note!=null and note!=''">
            note = #{note},
        </if>
        <if test="createTime!=null">
            create_time = #{createTime},
        </if>
        <if test="loginTime!=null">
            login_time = #{loginTime},
        </if>
    </set>
    where id = #{id}
</update>
           

foreach

  • 通過foreach我們可以實作一些循環拼接SQL的邏輯,例如我們現在需要編寫一個批量插入使用者的方法;
/**
 * 自定義UmsAdmin表查詢
 * Created by macro on 2022/10/20.
 */
@Repository
public interface UmsAdminDao {
    /**
     * 批量插入使用者
     */
    int insertBatch(@Param("entityList") List<UmsAdmin> adminList);
}
           
  • 在xml中的對應SQL實作如下,在foreach标簽中的内容會根據傳入的集合參數進行循環拼接;
<insert id="insertBatch">
    insert into ums_admin(username, password, icon, email, nick_name, note, create_time, login_time) values
    <foreach collection="entityList" separator="," item="item">
        (#{item.username}, #{item.password}, #{item.icon}, #{item.email}, #{item.nickName}, #{item.note}, #{item.createTime}, #{item.loginTime})
    </foreach>
</insert>
           
  • 再例如我們現在需要編寫一個根據使用者ID批量查詢的方法;
/**
 * 自定義UmsAdmin表查詢
 * Created by macro on 2022/10/20.
 */
@Repository
public interface UmsAdminDao {
    /**
     * 根據使用者ID批量查詢
     */
    List<UmsAdmin> selectByIds(@Param("ids") List<Long> ids);
}
           
  • 在xml中的對應SQL實作如下,我們可以使用open、close屬性指定拼接語句的前字尾。
<select id="selectByIds" resultType="com.macro.mall.tiny.model.UmsAdmin">
    select username,
           password,
           icon,
           email,
           nick_name   as nickName,
           note,
           create_time as createTime,
           login_time  as loginTime,
           status
    from ums_admin
    where id in
    <foreach collection="ids" item="item" open="(" close=")" separator=",">
        #{item}
    </foreach>
</select>
           

進階查詢

介紹完MyBatis的基本操作後,我們再來介紹下MyBatis的進階查詢功能。

一對一映射

  • 在我們平時進行SQL查詢時,往往會有一對一的情況,比如說我們這裡有資源分類ums_resource_category和資源ums_resource兩張表,資源和分類就是一對一的關系,如果你不想改動原實體類的話,可以編寫一個擴充類繼承UmsResource,并包含UmsResourceCategory屬性;
/**
 * UmsResource擴充類
 * Created by macro on 2022/10/20.
 */
@Data
public class UmsResourceExt extends UmsResource {

    private UmsResourceCategory category;
}
           
  • 例如我們需要編寫一個根據資源ID擷取資源及分類資訊的方法;
/**
 * 自定義UmsResource表查詢
 * Created by macro on 2022/10/20.
 */
@Repository
public interface UmsResourceDao {
    /**
     * 根據資源ID擷取資源及分類資訊
     */
    UmsResourceExt selectResourceWithCategory(Long id);
}
           
  • 在xml中的具體SQL實作如下,我們可以通過給ums_resource_category表中字段取以category.xxx的别名來自動進行自動映射;
<select id="selectResourceWithCategory" resultType="com.macro.mall.tiny.domain.UmsResourceExt">
    select ur.id,
           ur.create_time  as createTime,
           ur.name,
           ur.url,
           ur.description,
           ur.category_id  as categoryId,
           urc.id          as "category.id",
           urc.name        as "category.name",
           urc.sort        as "category.sort",
           urc.create_time as "category.createTime"
    from ums_resource ur
             left join ums_resource_category urc on ur.category_id = urc.id
    where ur.id = #{id}
</select>
           
  • 當然除了這種方式以外,我們還可以通過ResultMap+association标簽來實作,不過在此之前我們在編寫xml檔案的時候,一般習慣于先給目前檔案寫一個BaseResultMap,用于對目前表的字段和對象屬性進行直接映射,例如在UmsResourceCategoryDao.xml中這樣實作;
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.macro.mall.tiny.dao.UmsResourceCategoryDao">

    <resultMap id="BaseResultMap" type="com.macro.mall.tiny.model.UmsResourceCategory">
        <id property="id" column="id"/>
        <result property="createTime" column="create_time"/>
        <result property="name" column="name"/>
        <result property="sort" column="sort"/>
    </resultMap>
</mapper>
           
  • 在UmsResourceDao.xml中我們可以這樣實作;
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.macro.mall.tiny.dao.UmsResourceDao">

    <resultMap id="BaseResultMap" type="com.macro.mall.tiny.model.UmsResource">
        <id property="id" column="id"/>
        <result property="createTime" column="create_time"/>
        <result property="name" column="name"/>
        <result property="url" column="url"/>
        <result property="description" column="description"/>
        <result property="categoryId" column="category_id"/>
    </resultMap>
</mapper>
           
  • 編寫完成後,我們的一對一ResultMap實作就很簡單了,我們可以使用association标簽進行一對一管理,配置columnPrefix屬性将比對到的字段直接映射到關聯對象中去;
<resultMap id="ResourceWithCategoryMap" type="com.macro.mall.tiny.domain.UmsResourceExt" extends="BaseResultMap">
    <association property="category" resultMap="com.macro.mall.tiny.dao.UmsResourceCategoryDao.BaseResultMap" columnPrefix="category_"/>
</resultMap>
           
  • 然後再編寫下Dao中方法對應SQL實作即可,這裡直接使用上面的ResultMap,同時給ums_resource_category表中的字段指定了category_字首以便于映射。
<select id="selectResourceWithCategory2" resultMap="ResourceWithCategoryMap">
    select ur.id,
           ur.create_time,
           ur.name,
           ur.url,
           ur.description,
           ur.category_id,
           urc.id          as category_id,
           urc.name        as category_name,
           urc.sort        as category_sort,
           urc.create_time as category_create_time
    from ums_resource ur
             left join ums_resource_category urc on ur.category_id = urc.id
    where ur.id = #{id}
</select>
           

一對多映射

  • 在編寫SQL查詢時,一對多的情況也比較常見,例如這裡的分類和資源就是一對多的情況;
/**
 * UmsResourceCategory擴充類
 * Created by macro on 2022/10/20.
 */
@Data
public class UmsResourceCategoryExt extends UmsResourceCategory {

    private List<UmsResource> resourceList;
}
           
  • 例如我們現在需要編寫一個根據分類ID擷取分類及對應資源的方法;
/**
 * 自定義UmsResourceCategory表查詢
 * Created by macro on 2022/10/20.
 */
@Repository
public interface UmsResourceCategoryDao {

    /**
     * 根據分類ID擷取分類及對應資源
     */
    UmsResourceCategoryExt selectCategoryWithResource(Long id);
}
           
  • 在實作具體SQL前,我們需要先在xml中配置一個ResultMap,通過collection标簽建立一對多關系;
<resultMap id="selectCategoryWithResourceMap" type="com.macro.mall.tiny.domain.UmsResourceCategoryExt" extends="BaseResultMap">
    <collection property="resourceList" columnPrefix="resource_" resultMap="com.macro.mall.tiny.dao.UmsResourceDao.BaseResultMap"/>
</resultMap>
           
  • 然後在xml中編寫具體的SQL實作,使用該ResultMap。
<select id="selectCategoryWithResource" resultMap="selectCategoryWithResourceMap">
    select urc.id,
           urc.create_time,
           urc.name,
           urc.sort,
           ur.id resource_id,
           ur.create_time resource_create_time,
           ur.name resource_name,
           ur.url resource_url,
           ur.description resource_description,
           ur.category_id resource_category_id
    from ums_resource_category urc
    left join ums_resource ur on urc.id = ur.category_id
    where urc.id = #{id}
</select>
           

分頁插件

  • 我們平時實作查詢邏輯時,往往還會遇到分頁查詢的需求,直接使用開源的PageHelper插件即可,首先在pom.xml中添加它的Starter;
<!--MyBatis分頁插件-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.2</version>
</dependency>
           
  • 然後在查詢方法之前使用它的startPage方法傳入分頁參數即可,分頁後的得到的資料可以在PageInfo中擷取到。
/**
 * UmsResource的Service接口實作類
 * Created by macro on 2022/10/20.
 */
@Service
public class UmsResourceServiceImpl implements UmsResourceService {

    @Autowired
    private UmsResourceDao umsResourceDao;

    @Override
    public PageInfo<UmsResource> page(Integer pageNum, Integer pageSize,Long categoryId) {
        PageHelper.startPage(pageNum,pageSize);
        List<UmsResource> resourceList = umsResourceDao.selectListByCategoryId(categoryId);
        PageInfo<UmsResource> pageInfo = new PageInfo<>(resourceList);
        return pageInfo;
    }
}

           

總結

本文主要介紹了MyBatis中一些比較正常的用法,涵蓋了SpringBoot內建、基本查詢、動态SQL和進階查詢,建議大家收藏起來,在對MyBatis的用法有所遺忘的時候拿出來看看。

項目源碼位址

https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-mybatis

來源:https://mp.weixin.qq.com/s/X11OCZSEh2k5K6uYG768yQ