Spring Boot 實踐 :Spring Boot + MyBatis

  • 2022 年 6 月 17 日
  • 筆記

Spring Boot 實踐系列,Spring Boot + MyBatis 。

目的

將 MyBatis 與 Spring Boot 應用程序一起使用來訪問數據庫。

本次使用的Library

  • spring-boot-starter:2.2.0.M4
  • mybatis-spring-boot-starter:2.0.1
  • mybatis-spring-boot-starter-test:2.0.1
  • h2:1.4.199
  • lombok:1.18.8

數據庫訪問

在實現 MyBatis 之前,首先進行 DB 訪問的設置。

數據庫訪問定義

DataSourceSpring Boot自動定義了訪問 DB等 bean。
默認情況下,它訪問內存中的 H2 數據庫,因此您需要將 h2 添加到依賴項中。

pom.xml

 
    <dependencies>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

注意
:您可以通過更改 Spring Boot 配置文件中的驅動程序和連接目標來訪問另一個 DB。(需要添加依賴對應的驅動)

src/main/resources/application.yml

 
spring:
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/testdb
    username: postgres
    password: postgres

如果直接在 src/main/resources 下創建如下 SQL 文件,也可以在應用啟動時自動初始化數據庫。
默認識別的 SQL 文件如下。

  • schema.sql
  • schema-${platform}.sql
  • data.sql
  • data-${platform}.sql

如您所見,我們在 schema.sql 中定義了 DDL,在 data.sql 中定義了 DML。


${platform}spring.datasource.platform在屬性中指定。
似乎您可以使用 H2 進行單元測試,使用 Postgresql 進行集成測試。

這一次,創建 schema.sql 並創建一個表。

src/main/resources/schema.sql

 
create table if not exists todo (
    todo_id identity,
    todo_title varchar(30),
    finished boolean,
    created_at timestamp
);

mybatis-spring-boot-starter

它是使用 MyBatis 和 Spring Boot 的入門者。
通過使用 Spring Boot 的 Auto Configuration 機制,自動完成在 Spring Boot 應用中使用 MyBatis 的 Bean 定義。開發者只需要將 mybatis-spring-boot-starter 添加到他們的依賴項中。

pom.xml

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

src/main/java/*/Application.java

 
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

主類沒有改變 Spring Boot 應用程序中的默認值。

領域類

映射 DB 數據的域類對於普通的 Java Bean 是可以的。
在下文中,Lombok 用於省略 Getter 和 Setter 的實現@Data

src/main/java/*/domain/Todo.java

 
@Data
public class Todo {
    private String todoId;
    private String todoTitle;
    private boolean finished;
    private LocalDateTime createdAt;
}

存儲庫接口

MyBatis 的 Mapper 對應 Spring 的 Repository 接口。
MyBatis 的 Mapper 可以通過以下任意一種方式實現。

  • 創建Repository接口對應的Mapper XML
  • @Mapper授予存儲庫接口

這次我將根據註解庫 Spring Boot 用 MyBatis 的 Mapper 來實現@Mapper

src/main/java/*/repository/TodoRepository.java

 
@Mapper // (1)
public interface TodoRepository {

    // (2)
    @Select("SELECT todo_id, todo_title, finished, created_at FROM todo WHERE todo_id = #{todoId}")
    Optional<Todo> findById(String todoId);

    @Select("SELECT todo_id, todo_title, finished, created_at FROM todo")
    Collection<Todo> findAll();

    @Insert("INSERT INTO todo (todo_title, finished, created_at) VALUES (#{todoTitle}, #{finished}, #{createdAt})")
    @Options(useGeneratedKeys = true, keyProperty = "todoId") // (3)
    void create(Todo todo);

    @Update("UPDATE todo SET todo_title = #{todoTitle}, finished = #{finished}, created_at = #{createdAt} WHERE todo_id = #{todoId}")
    boolean updateById(Todo todo);

    @Delete("DELETE FROM todo WHERE todo_id = #{todoId}")
    void deleteById(Todo todo);

    @Select("SELECT COUNT(*) FROM todo WHERE finished = #{finished}")
    long countByFinished(boolean finished);
}

(1)如果添加到Repository界面@Mapper,MyBatis會自動掃描並註冊到Mapper中。讓我們將Repository接口放在主類下的包中。

(2)在給方法的…中實現要執行的SQL 。由於參數在 SQL 中使用,因此比 XML 更容易理解,因為它在同一個文件中進行了描述。@Select@Insert@Update@Delete#{}

(3) @Options在需要執行異常設置的 SQL 時給出。這裡,由於表的關鍵項是IDENTITY列,所以在DB端是自動編號的,但是@Options你可以通過using來使用自動編號的ID。

多行 SQL

雖然上面的 SQL 是用一行描述的,但還是建議多行描述,因為它不可讀。
它比 XML 更難看,因為它是一種像這樣的字符串連接形式。

 
    @Select("SELECT"
            + " todo_id,"
            + " todo_title,"
            + " finished,"
            + " created_at"
            + " FROM todo"
            + " WHERE"
            + " todo_id = #{todoId}")
    Optional<Todo> findById(String todoId);

選擇結果的自動和手動映射

Select 結果映射失敗,因為圖示的 Select 語句的結果列名稱和 Todo 類的屬性名稱不同。(todo_title等等todoTitle

有以下方法之一可以解決此問題。

  • MyBatis 命名規則自動映射
  • @Results@ResultMap手動映射和

就個人而言,為屬性命名以便自動映射可用,並且僅在它偏離規則時才進行手動映射是一個好主意。

自動映射

如果下劃線分隔的列名與駝峰式的屬性名匹配,則可以通過 MyBatis 的命名規則進行自動映射。

將以下設置添加到 Spring Boot 配置文件中。

src/main/resources/application.yml

 
mybatis:
  configuration:
    map-underscore-to-camel-case: true

注意
mybatis.configuration.*:您可以在屬性中更改 MyBatis 設置。

手動映射

如果列名和屬性名不匹配,則@Results需要@ResultMap為每個 SQL 定義手動映射。

  • @Results在中定義手動映射規則
  • @Results如果要使用中定義的規則@ResultMap@Results請指定中的 ID。
 
    @Select("SELECT todo_id, todo_title, finished, created_at FROM todo WHERE todo_id = #{todoId}")
    @Results(id = "todo", value = {
            @Result(column = "todo_id", property = "todoId"),
            @Result(column = "todo_title", property = "todoTitle"),
            @Result(column = "finished", property = "finished"),
            @Result(column = "created_at", property = "createdAt") })
    Optional<Todo> findById(String todoId);

    @Select("SELECT todo_id, todo_title, finished, created_at FROM todo")
    @ResultMap("todo")
    Collection<Todo> findAll();

與動態 SQL 通用

使用 Mapper XML 可能實現的動態 SQL ( <if>) 和通用性()<sql>現在通過 SqlProvider 實現。

 
    // (2)
    @SelectProvider(type = TodoSqlProvider.class, method = "find")
    Optional<Todo> findById(String todoId);

    @SelectProvider(type = TodoSqlProvider.class, method = "find")
    Collection<Todo> findAll();

    // (1)
    public class TodoSqlProvider {
        public String find(String todoId) {
            return new SQL() {{
                SELECT("todo_id", "todo_title", "finished", "created_at");
                FROM("todo");
                if (todoId != null) {
                    WHERE("todo_id = #{todoId}");
                }
            }}.toString();
        }
    }

(1) 實現SqlProvider類,並new SQL()在方法中使用組裝SQL。例子中使用了實例初始化器( new SQL() {{ココ}}),當然你也可以正常寫在方法鏈中。

注意
:您可以直接在 WHERE 子句中嵌入參數,但一定#{}要使用。
#{}防止 SQL 注入。

(2)通過賦值@Select而不是@SelectProvider方法來指定實現的SqlProvider類和方法。

注意
:接口默認方法不能是 SqlProvider 方法。你需要上課。
這是因為當您使用 SqlProvider 方法時,會創建一個 SqlProvider 類的實例。

mybatis-spring-boot-starter-test

使用 Spring Boot 測試 MyBatis 的入門者。

通過使用 Spring Boot Auto Configuration 機制,Spring Boot 應用會自動定義一個 bean 用於測試 MyBatis Mapper。開發者只需要在自己的依賴中加入 mybatis-spring-boot-starter-test 並做一些設置即可。

pom.xml

 
    <dependencies>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter-test</artifactId>
            <version>2.0.1</version>
        </dependency>
    </dependencies>

JUnit 測試用例

在 JUnit 測試用例中,您所要做的就是提供類並@MyBatisTest製作要測試的存儲庫@Autowired

src/test/java/*/repository/TodoRepositoryTest.java

 
@MybatisTest
class TodoRepositoryTest {

    @Autowired
    TodoRepository todoRepository;

    @Test
    @Sql(statements = "INSERT INTO todo (todo_title, finished, created_at) VALUES ('sample todo', false, '2019-01-01')")
    void testFindAll() {
        // execute
        Collection<Todo> todos = todoRepository.findAll();

        // assert
        assertThat(todos)
                .hasSize(1)
                .extracting(Todo::getTodoTitle, Todo::isFinished, Todo::getCreatedAt)
                .containsExactly(tuple("sample todo", false, LocalDate.of(2019, 1, 1).atStartOfDay()));
    }

@MyBatisTestDataSource會自動定義使用 MyBatis、訪問 DB 等的 bean ,但@Transactional也會添加更多。

@Transactional@Sql測試完成後會回滾測試中執行的SQL。這是安全的,因為即使在訪問和測試不在內存中的實際數據庫時,也可以保持測試的獨立性。