mybatis 也想要類似 spring-data-jpa 那樣只需要寫接口就能查詢的功能怎麼辦?

mybatis 也想要類似 spring-data-jpa 那樣只需要寫接口就能查詢的功能怎麼辦?

前言

spring-data-jpa 的只寫接口便可以 CRUD 的能力真的是好爽,然而 mybatis 寫 sql 的靈活又讓我欲罷不能。

我總是希望二者能夠調和一下,爲此我曾想過在同一個項目中同時使用這兩個框架,當然跑起來是可行的,但是總會擔心會出現什麼問題,想要徹底整合卻沒那個實力。。。

在我的不懈努力搜索之下,終於讓我找到一個項目,它可以在 spring-data-jpa 中以 freemarker 模板的形式編寫動態 sql 去執行。看起來很完美的樣子,就是還需要去學習 freemarker 而已。

但是,受此啓發,既然他拓展了 spring-data-jpa,我爲什麼不能拓展一下 mybatis 呢?於是我就編寫了一個擴展包:mybatis-auto-mapper.jar

詳細功能介紹請移步我的個人博客(點擊這裏)查看,這裏就不重複寫一遍了,只介紹如何使用了。

下面說一下使用(創建項目就不貼圖了,直接貼關鍵代碼了):

首先是 pom.xml

在項目中添加以下依賴即可,無需新增任何配置(需要自己下載源碼安裝到本地倉庫。。。)

        <dependency>
            <groupId>com.kfyty</groupId>
            <artifactId>mybatis-auto-mapper</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

然後是實體類

package com.kfyty.mybatis.auto.mapper.entity;

import lombok.Data;

import java.util.Date;

/**
 * 功能描述: 實體類
 *
 * @author [email protected]
 * @date 2019/11/16 16:41
 * @since JDK 1.8
 */
@Data
public class TestUser {
    private Integer id;
    private String name;
    private Integer age;
    private Date createTime;
    private int sortIndex;

    public TestUser() {

    }

    public TestUser(String name, Integer age) {
        this.name = name;
        this.age = age;
        this.sortIndex = age;
    }
}

Mapper 接口

package com.kfyty.mybatis.auto.mapper.mapper;

import com.github.pagehelper.Page;
import com.kfyty.mybatis.auto.mapper.annotation.AutoMapper;
import com.kfyty.mybatis.auto.mapper.entity.TestUser;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 功能描述: mapper 接口
 *
 * @author [email protected]
 * @date 2019/11/16 16:39:41
 * @since JDK 1.8
 */
@Mapper
public interface TestUserMapper {
    @AutoMapper
    int insert(@Param("user") TestUser entity);

    @AutoMapper
    int insertAll(@Param("users") List<TestUser> entities);

    /**
     * 存在 @Pageable 時,以最後兩個整型參數自動分頁
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Pageable
    @AutoMapper
    Page<TestUser> pageByNameNotNull(int pageNum, int pageSize);

    /**
     * 自行配置 MybatisPageHelper 進行分頁
     * @param pageNum
     * @param pageSize
     * @return
     */
    @AutoMapper
    List<TestUser> findByAgeBetweenOrderByCreateTimeDesc(@Param("startAge") Integer startAge, @Param("endAge") Integer endAge, @Param("pageNum") int pageNum, @Param("pageSize") int pageSize);
}

需要提一下的是,裏面集成了 mybatis-page-helper 分頁插件(在此表示感謝),可以直接使用,當然如果想自己配置一個 Bean 的話也不會衝突。如果只想進行配置的話,可以添加如下代碼進行配置:

    @Bean("pageInterceptorProperties")
    public Properties pageProperties() {
        Properties properties = new Properties();
        properties.setProperty("supportMethodsArguments", "true");
        return properties;
    }

啓動類

package com.kfyty.mybatis.auto.mapper;

import com.github.pagehelper.PageInfo;
import com.kfyty.mybatis.auto.mapper.entity.TestUser;
import com.kfyty.mybatis.auto.mapper.mapper.TestUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * 功能描述: 啓動類
 *
 * @author [email protected]
 * @date 2019/11/16 16:35
 * @since JDK 1.8
 */
@RestController
@SpringBootApplication
public class MybatisAutoMapperDemoApplication {
    @Autowired
    private TestUserMapper testUserMapper;

    @RequestMapping("demo/insert/{name}/{age}")
    public int insert(@PathVariable("name") String name, @PathVariable("age") Integer age) {
        return testUserMapper.insert(new TestUser(name, age));
    }

    @RequestMapping("demo/insert/all/{startIndex}/{count}")
    public int insertAll(@PathVariable("startIndex") Integer startIndex, @PathVariable("count") int count) {
        List<TestUser> list = new ArrayList<>();
        for (int i = 0; i < count; i++, startIndex++) {
            list.add(new TestUser("test-" + startIndex, startIndex));
        }
        return testUserMapper.insertAll(list);
    }

    @RequestMapping("demo/page/{pageNum}/{pageSize}")
    public PageInfo page(@PathVariable("pageNum") int pageNum, @PathVariable("pageSize") int pageSize) {
        return PageInfo.of(testUserMapper.pageByNameNotNull(pageNum, pageSize));
    }

    @RequestMapping("demo/find/age-between/{start}/{end}/{pageNum}/{pageSize}")
    public List<TestUser> findByAgeBetweenOrderByCreateTimeDesc(@PathVariable("start") int start, @PathVariable("end") int end, @PathVariable("pageNum") int pageNum, @PathVariable("pageSize") int pageSize) {
        return testUserMapper.findByAgeBetweenOrderByCreateTimeDesc(start, end, pageNum, pageSize);
    }

    @Bean("pageInterceptorProperties")
    public Properties pageProperties() {
        Properties properties = new Properties();
        properties.setProperty("supportMethodsArguments", "true");
        return properties;
    }

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

由於這裏只涉及到了單表操作,所以不需要 Mapper.xml 文件

下面就可以啓動測試了:

-- 測試之前
mysql> select * from test_user;
Empty set (0.00 sec)

-- 測試接口: http://localhost:8080/demo/insert/1/1
mysql> select * from test_user;
+----+------+------+---------------------+------------+
| id | name | age  | create_time         | sort_index |
+----+------+------+---------------------+------------+
| 15 | 1    |    1 | 2019-12-08 14:38:01 |          1 |
+----+------+------+---------------------+------------+
1 row in set (0.00 sec)

-- 測試接口: http://localhost:8080/demo/insert/all/2/10
mysql> select * from test_user;
+----+---------+------+---------------------+------------+
| id | name    | age  | create_time         | sort_index |
+----+---------+------+---------------------+------------+
| 15 | 1       |    1 | 2019-12-08 14:38:01 |          1 |
| 16 | test-2  |    2 | 2019-12-08 14:39:08 |          2 |
| 17 | test-3  |    3 | 2019-12-08 14:39:08 |          3 |
| 18 | test-4  |    4 | 2019-12-08 14:39:08 |          4 |
| 19 | test-5  |    5 | 2019-12-08 14:39:08 |          5 |
| 20 | test-6  |    6 | 2019-12-08 14:39:08 |          6 |
| 21 | test-7  |    7 | 2019-12-08 14:39:08 |          7 |
| 22 | test-8  |    8 | 2019-12-08 14:39:08 |          8 |
| 23 | test-9  |    9 | 2019-12-08 14:39:08 |          9 |
| 24 | test-10 |   10 | 2019-12-08 14:39:08 |         10 |
| 25 | test-11 |   11 | 2019-12-08 14:39:08 |         11 |
+----+---------+------+---------------------+------------+
11 rows in set (0.00 sec)

下面測試分頁接口:

--接口: http://localhost:8080/demo/find/name-not-null/1/3
-- 日誌如下:
2019-12-08 14:42:01.418 DEBUG 6524 --- [nio-8080-exec-2] c.k.m.j.s.m.T.findByNameNotNull_COUNT    : ==>  Preparing: SELECT count(0) FROM test_user WHERE (name IS NOT NULL) 
2019-12-08 14:42:01.418 DEBUG 6524 --- [nio-8080-exec-2] c.k.m.j.s.m.T.findByNameNotNull_COUNT    : ==> Parameters: 
2019-12-08 14:42:01.419 DEBUG 6524 --- [nio-8080-exec-2] c.k.m.j.s.m.T.findByNameNotNull_COUNT    : <==      Total: 1
2019-12-08 14:42:01.421 DEBUG 6524 --- [nio-8080-exec-2] c.k.m.j.s.m.T.findByNameNotNull          : ==>  Preparing: select * from test_user where ( name is not null ) LIMIT ? 
2019-12-08 14:42:01.421 DEBUG 6524 --- [nio-8080-exec-2] c.k.m.j.s.m.T.findByNameNotNull          : ==> Parameters: 3(Integer)
2019-12-08 14:42:01.426 DEBUG 6524 --- [nio-8080-exec-2] c.k.m.j.s.m.T.findByNameNotNull          : <==      Total: 3

--接口: http://localhost:8080/demo/find/age-between/5/10/1/10
-- 日誌如下:
2019-12-08 15:07:41.874 DEBUG 5104 --- [nio-8080-exec-3] dByAgeBetweenOrderByCreateTimeDesc_COUNT : ==>  Preparing: SELECT count(0) FROM test_user WHERE age BETWEEN ? AND ? 
2019-12-08 15:07:41.874 DEBUG 5104 --- [nio-8080-exec-3] dByAgeBetweenOrderByCreateTimeDesc_COUNT : ==> Parameters: 5(Integer), 10(Integer)
2019-12-08 15:07:41.876 DEBUG 5104 --- [nio-8080-exec-3] dByAgeBetweenOrderByCreateTimeDesc_COUNT : <==      Total: 1
2019-12-08 15:07:41.876 DEBUG 5104 --- [nio-8080-exec-3] .T.findByAgeBetweenOrderByCreateTimeDesc : ==>  Preparing: select * from test_user where age between ? and ? order by create_time desc LIMIT ? 
2019-12-08 15:07:41.877 DEBUG 5104 --- [nio-8080-exec-3] .T.findByAgeBetweenOrderByCreateTimeDesc : ==> Parameters: 5(Integer), 10(Integer), 10(Integer)
2019-12-08 15:07:41.882 DEBUG 5104 --- [nio-8080-exec-3] .T.findByAgeBetweenOrderByCreateTimeDesc : <==      Total: 6

最後,插入單個數據時,如果需要返回主鍵值的話,可以使用 @SelectKey 註解,SelectKey.java 文件如下:

package com.kfyty.mybatis.auto.mapper.annotation;

import com.kfyty.mybatis.auto.mapper.enums.SelectKeyOrder;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 功能描述: 用於 mapper 接口或方法,插入數據時生成 <selectKey/> 標籤
 * 方法註解優先級高於類註解
 * insertAll 方法無效
 *
 * @author [email protected]
 * @date 2019/12/20 19:37
 * @since JDK 1.8
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface SelectKey {
    /**
     * 查詢主鍵值 SQL 語句
     * @return 默認值爲 MySQL 查詢自增主鍵
     */
    String value() default "select last_insert_id()";

    /**
     * <selectKey/> 標籤執行順序
     * @return 默認值爲 SelectKeyOrder.AFTER
     */
    SelectKeyOrder order() default SelectKeyOrder.AFTER;
}

最後放上 AutoMapper.java 文件,大致可以瞭解支持哪些功能:

package com.kfyty.mybatis.auto.mapper.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 功能描述: 用於 mapper 接口或方法,即可自動映射方法
 *
 * @author [email protected]
 * @date 2019/11/6 13:37
 * @since JDK 1.8
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface AutoMapper {
    /**
     * 更新時指定主鍵屬性,可用於接口配置
     * @return 默認值爲 id
     */
    String[] primaryKey() default "";

    /**
     * 實體類後綴,可用於接口配置
     * @return 默認值爲 Pojo
     */
    String suffix() default "";

    /**
     * 實體類/Mapper 接口命名不規範時需指定表名,可用於接口配置
     * @return 默認值爲 ""
     */
    String table() default "";

    /**
     * 查詢時添加額外的條件,可用於接口配置
     * @return 默認值爲 ""
     */
    String where() default "";

    /**
     * where 條件分隔符,可用於接口配置
     * @return 默認值爲 "and"
     */
    String separator() default "and";

    /**
     * 指定需要查詢的列,僅用於方法配置
     * @return 默認值爲 "*"
     */
    String columns() default "*";

    /**
     * 符合 find*By** 風格命名時,是否從方法解析需查詢的列,僅用於方法配置
     * @return 默認值爲 true
     */
    boolean parseColumn() default true;

    /**
     * 插入/更新對象時,遇到 null 是否轉換爲插入數據庫默認值,僅用於方法配置
     * @return 默認值爲 false
     */
    boolean useDefault() default false;

    /**
     * 更新對象時,是否允許更新爲 null 值,僅用於方法配置
     * @return 默認值爲 false
     */
    boolean allowNull() default false;

    /**
     * 是否繼承類註解 where 配置,僅用於方法配置
     * @return 默認值爲 true
     */
    boolean extend() default true;
}

看起來是不是挺像那麼回事的呢?

最後的最後,如果需要查看生成的 mapper 標籤的話,在 application.yml 中添加如下配置即可:

logging:
  level:
    com.kfyty.mybatis.auto.mapper.handle: debug

感興趣的可以查看 github:https://github.com/kfyty/mybatis-auto-mapper

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章