Spring Boot with Mybatis

Vivi Wang
9 min readMay 31, 2021

紀錄Spring Boot整合Mybatis筆記以及遇到要注意的事項。
Mybatis有annotation以及xml兩種方式,此處為套用 xml方式。

1. 在pom.xml中新增dependency

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>

2. 配置 Mapper.java

範例:有個Table名稱為test_table,其中PK是DB有設定Auto increment,因此新增時不用給PK。

CREATE TABLE `test_table` (
`id` bigint(20) PRIMARY KEY AUTO_INCREMENT
`test_amount` decimal(10,2) NOT NULL
);

Mapper.java interface中可以宣告需要用到的方法 (方法名稱會對應到之後 xml 的設定)

package com.demo.repository.mapper;public interface TestTableMapper {
/**
* 新增 Test 資料
*
*
@param testTable 新增資料
*
@return 新增資料影響的行數
*/
long insertTestData(TestTable testTable);

/**
* 查詢特定區間紀錄
*
*
@param id Table PK
*
@param amount 金額
*
@return 查詢結果
*/
List<TestTable> findTestDataByCondition(
@Param("id") long id,
@Param("test_amount") long amount;
}
  • 備註1:要注意的是Mybatis這邊insertData所返回的值並不是新增後的 PK,返回的值為此次「新增資料所影響的行數」,至於要怎麼取得generate的PK稍後解說。
  • 備註2:很多文章會在此處每個interface上加上@Mapper宣告此類別為Mapper,但其實效果等同於下方配置3的設定,所以若已經加上MapperScan便不需要再綴上此annotation。

3. 配置設定在 Application 中 (啟動類別)

新增 @MapperScan 在class上,其中basePackages宣告為步驟2中配置,讓Spring Boot 啟動時可以知道Mapper放置在哪。

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

3. 配置 Mapper.xml

基本的CRUD網路上資源很多
此處範例是將Mapper.xml配置在 src/resources/ 下方,其中SQL說明:
- 新增一筆資料
- 查詢多筆資料(其中查詢條件非必填),如果有傳id則id=傳入id,如果有傳amount則條件查詢test_amount > 傳入的 amount

<?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="package com.demo.repository.mapper.TestTableMapper">
<insert id="insertTestData" parameterType="com.demo.repository.model.TestTable"
useGeneratedKeys="true" keyProperty="id" keyColumn="id">
insert into test_table(id, test_amount)
values(#{id}, #{amount})
</insert>
<resultMap id="testResultMap" type="com.demo.repository.model.TestTable">
<id column="id" property="id"/>
<result column="test_amount" property="amount"/>
</resultMap>
<select id="findTestDataByCondition" resultMap="testResultMap">
select * from transaction_log
<where>
<if test="id != 0">
id = #{id}
</if>
<if test="amount != null">
test_amount &gt; #{amount}
</if>
</where>
</select>
</mapper>

<mapper namespace=”XXX”>

此處XXX為對應的Mapper.java路徑

<insert id=”insertTestData” parameterType=”com.demo.repository.model.TestTable”
useGeneratedKeys=”true” keyProperty=”id” keyColumn=”id”>

  • id: 為對應的為Mapper.java中的方法名稱
  • parameterType: 宣告傳入的parameter類型
  • useGeneratedKeys=”true”: 此設定代表PK為Auto Generate
  • keyProperty: 對應Model中的PK名稱
  • keyColumn: 對應Table中的PK名稱

<resultMap>

若資料可能會返回多筆 (例如: List<Object>形式)
需要宣告resultMap這段來定義返回值,其中用id當作辨別給select使用。

<select id=”findTestDataByCondition” resultMap=”testResultMap”>

  • id: 為對應的為Mapper.java中的方法名稱
  • resultMap: 資料返回時對應的resultMap id
  • 若想要動態配置查詢的where條件,可使用tag <where> / <if>
  • 可以看到大於或小於等含有角括號的SQL,要用 &gt; &lt; 等字元替代

4. 配置 application.properties (or yml)

這邊值得注意的是,當xml檔案所在的package名稱和interface對應的package名稱沒有對應時,會出現如下錯誤org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)

因此在application.properties中,我們需要宣告mybatis.mapper-locations來表示實際上xml位置,範例如下:

mybatis.mapper-locations=classpath*:*Mapper.xml

取得 Auto Increment Key 新增的值

差點忘記補充要如何取得insert資料後新Generate的key值
其實會直接更新在原先傳入的Model中,因此程式中取得方式如下

TestTable testTable = new TestTable();
testTable.setAmount(5000);
mapper.insertTestData(testTable); //此時呼叫Mapper.java中方法
long id = testTable.getId(); //再次取得的就會是新增的id

大致上Mybatis使用時xml部分設定就這樣:)
- Mapper.xml 更詳細tag使用說明也可以參考官方說明: https://mybatis.org/mybatis-3/sqlmap-xml.html

Auto Increment Key 單測時如何撰寫

假設有一User table有三個欄位(id, name, age) ,其中id是自動遞增的key,今天service中有一個方法會需要取得insert後的id做其他事。

public ResultVO testMethod() {
User user = this.insertData(name, age);
long id = user.getId();
// do something...
}
private User insertData(String name, int age) {
User user = new User();
user.setName(name);
user.setAge(age);
mapper.insertTestData(user);
return user;
}

此時在撰寫Unit Test可使用Answer去mock自動產生的id

Mockito.when(mapper.insert(Mockito.any(User.class)))
.thenAnswer((Answer<Integer>) invocation -> {
User record = invocation.getArgument(0, User.class);
record.setId(5);
return 1;
});

這樣就不會發生NPE的狀況囉

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Vivi Wang
Vivi Wang

Written by Vivi Wang

一些簡單基礎小筆記

No responses yet

Write a response