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

語言: CN / TW / HK

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

SpringBoot實戰電商項目mall(50k+star)地址:https://github.com/macrozheng/mall

MyBatis簡介

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

在SpringBoot中集成

下面我們來聊聊MyBatis在SpringBoot中的使用,首先我們需要集成它。

  • pom.xml中添加MyBatis提供的Spring Boot Starter;

xml <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目錄下;

yaml mybatis: mapper-locations: - classpath:dao/*.xml

  • 然後添加Java配置,通過@MapperScan配置好Dao接口路徑,這樣就可以開始使用了。

java /** * MyBatis配置類 * Created by macro on 2019/4/8. */ @Configuration @MapperScan("com.macro.mall.tiny.dao") public class MyBatisConfig { }

基本使用

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

表結構説明

這裏將以mall項目中權限管理模塊相關表為例進行介紹,具體表結構如下。

項目結構説明

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

select

  • 首先是查詢操作,這裏我們以後台用户表ums_admin為例,編寫一個根據ID查詢用户的方法,先創建實體類UmsAdmin

```java 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,再添加對應的方法;

```java /* * 自定義UmsAdmin表查詢 * Created by macro on 2022/10/20. / @Repository public interface UmsAdminDao {

/**
 * 根據ID查詢用户
 */
UmsAdmin selectByIdSimple(Long id);

} ```

  • 再創建xml文件UmsAdminDao.xml,添加查詢方法的SQL實現;

xml <select id="selectByIdSimple" resultType="com.macro.mall.tiny.model.UmsAdmin"> select * from ums_admin where id = #{id} </select>

  • 然後編寫測試類,注入Dao,調用Dao方法來進行測試;

```java /* * 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);
}

} ```

  • 此時你會發現,對於一些數據庫表中以下劃線分割的返回字段無法自動映射,可以通過對字段取別名的方式來進行映射;

xml <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中開啟全局下劃線自動轉駝峯功能來解決,個人習慣使用第一種。

yaml mybatis: configuration: # 下劃線自動轉駝峯 map-underscore-to-camel-case: true

insert

  • 接下來我們來編寫一個插入單個用户的方法;

```java /* * 自定義UmsAdmin表查詢 * Created by macro on 2022/10/20. / @Repository public interface UmsAdminDao {

/**
 * 插入用户
 */
int insert(UmsAdmin entity);

} ```

  • 然後在xml中編寫對應的SQL實現,這裏需要注意的是如果想返回插入後的自增ID的話,需要使用selectKey標籤進行配置。

xml <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修改用户信息的方法;

java /** * 自定義UmsAdmin表查詢 * Created by macro on 2022/10/20. */ @Repository public interface UmsAdminDao { /** * 根據ID修改用户信息 */ int updateById(UmsAdmin entity); }

  • 然後在xml中編寫對應的SQL實現。

xml <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刪除用户的方法;

java /** * 自定義UmsAdmin表查詢 * Created by macro on 2022/10/20. */ @Repository public interface UmsAdminDao { /** * 根據ID刪除用户 */ int deleteById(Long id); }

  • 然後在xml中編寫對應的SQL實現。

xml <delete id="deleteById"> delete from ums_admin where id = #{id} </delete>

動態SQL

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

if

  • if標籤可以實現判斷邏輯,這裏我們以根據用户名和Email模糊查詢用户為例,來聊聊它的使用;

java /** * 自定義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實現如下。

xml <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時,會查詢出全部用户,我們如果想不查詢出用户,可以使用它;

xml <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關鍵字。

xml <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選擇性修改用户信息的方法;

java /** * 自定義UmsAdmin表查詢 * Created by macro on 2022/10/20. */ @Repository public interface UmsAdminDao { /** * 根據ID選擇性修改用户信息 */ int updateByIdSelective(UmsAdmin entity); }

  • 方法對應的SQL實現如下,這裏既避免了使用set關鍵字,也會將多餘的逗號去除。

xml <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的邏輯,例如我們現在需要編寫一個批量插入用户的方法;

java /** * 自定義UmsAdmin表查詢 * Created by macro on 2022/10/20. */ @Repository public interface UmsAdminDao { /** * 批量插入用户 */ int insertBatch(@Param("entityList") List<UmsAdmin> adminList); }

  • 在xml中的對應SQL實現如下,在foreach標籤中的內容會根據傳入的集合參數進行循環拼接;

xml <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批量查詢的方法;

java /** * 自定義UmsAdmin表查詢 * Created by macro on 2022/10/20. */ @Repository public interface UmsAdminDao { /** * 根據用户ID批量查詢 */ List<UmsAdmin> selectByIds(@Param("ids") List<Long> ids); }

  • 在xml中的對應SQL實現如下,我們可以使用openclose屬性指定拼接語句的前後綴。

xml <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屬性;

```java /* * UmsResource擴展類 * Created by macro on 2022/10/20. / @Data public class UmsResourceExt extends UmsResource {

private UmsResourceCategory category;

} ```

  • 例如我們需要編寫一個根據資源ID獲取資源及分類信息的方法;

java /** * 自定義UmsResource表查詢 * Created by macro on 2022/10/20. */ @Repository public interface UmsResourceDao { /** * 根據資源ID獲取資源及分類信息 */ UmsResourceExt selectResourceWithCategory(Long id); }

  • 在xml中的具體SQL實現如下,我們可以通過給ums_resource_category表中字段取以category.xxx的別名來自動進行自動映射;

xml <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

<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>

```

  • UmsResourceDao.xml中我們可以這樣實現;

```xml

<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>

```

  • 編寫完成後,我們的一對一ResultMap實現就很簡單了,我們可以使用association標籤進行一對一管理,配置columnPrefix屬性將匹配到的字段直接映射到關聯對象中去;

xml <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_前綴以便於映射。

xml <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查詢時,一對多的情況也比較常見,例如這裏的分類和資源就是一對多的情況;

```java /* * UmsResourceCategory擴展類 * Created by macro on 2022/10/20. / @Data public class UmsResourceCategoryExt extends UmsResourceCategory {

private List<UmsResource> resourceList;

} ```

  • 例如我們現在需要編寫一個根據分類ID獲取分類及對應資源的方法;

```java /* * 自定義UmsResourceCategory表查詢 * Created by macro on 2022/10/20. / @Repository public interface UmsResourceCategoryDao {

/**
 * 根據分類ID獲取分類及對應資源
 */
UmsResourceCategoryExt selectCategoryWithResource(Long id);

} ```

  • 在實現具體SQL前,我們需要先在xml中配置一個ResultMap,通過collection標籤建立一對多關係;

xml <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。

xml <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;

```xml

com.github.pagehelper pagehelper-spring-boot-starter 1.4.2 ```

  • 然後在查詢方法之前使用它的startPage方法傳入分頁參數即可,分頁後的得到的數據可以在PageInfo中獲取到。

```java /* * 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

「其他文章」