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 kfyty725@hotmail.com
 * @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.BaseMapper;
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 kfyty725@hotmail.com
 * @date 2019/11/16 16:39:41
 * @since JDK 1.8
 */
@Mapper
@AutoMapper(entity = TestUserMapper.class)
public interface TestUserMapper extends BaseMapper<Integer, TestUserMapper> {
}

需要提一下的是,里面集成了 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 kfyty725@hotmail.com
 * @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

这里介绍一下预设接口 BaseMapper<PrimaryKey, T>,该接口提供了一些基本的方法,但是如果继承它的话,需要在子接口上使用 @AutoMapper 注解并指定实体类型,之所以需要这个配置,是因为这个包不是在运行时起作用,而是在启动时起作用,因此需要提前配置实体类型,其代码如下:

package com.kfyty.mybatis.auto.mapper;

import com.kfyty.mybatis.auto.mapper.annotation.AutoMapper;
import com.kfyty.mybatis.auto.mapper.struct.TableFieldStruct;
import com.kfyty.mybatis.auto.mapper.struct.TableStruct;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface BaseMapper<PrimaryKey, T> {
    /**
     * 插入单条数据
     * @param entity 实体数据
     * @return 受影响的行数
     */
    @AutoMapper
    int insert(@Param("entity") T entity);

    /**
     * 批量插入数据
     * @param entities 实体数据
     * @return 受影响的行数
     */
    @AutoMapper
    int insertAll(@Param("entities") List<T> entities);

    /**
     * 更新单条数据,只更新值不为空的字段
     * @param entity 实体数据
     * @return 受影响的行数
     */
    @AutoMapper
    int update(@Param("entity") T entity);

    /**
     * 更新单条数据,允许更新值为空的字段
     * @param entity 实体数据
     * @return 受影响的行数
     */
    @AutoMapper(allowNull = true)
    int updateDeep(@Param("entity") T entity);

    /**
     * 批量更新数据,只更新值不为空的字段
     * @param entities 实体数据
     * @return 受影响的行数
     */
    @AutoMapper
    int updateAll(@Param("entities") List<T> entities);

    /**
     * 批量更新数据,允许更新值为空的字段
     * @param entities 实体数据
     * @return 受影响的行数
     */
    @AutoMapper(allowNull = true)
    int updateAllDeep(@Param("entities") List<T> entities);

    /**
     * 查询所有的数据
     * @return 所有数据
     */
    @AutoMapper
    List<T> findAll();

    /**
     * 查询记录总条数
     * @return 记录总条数
     */
    @AutoMapper
    int countAll();

    /**
     * 分页查询数据
     * @param pageNum 起始页码
     * @param pageSize 每页大小
     * @return 分页数据
     */
    @AutoMapper
    List<T> pageAll(@Param("pageNum") int pageNum, @Param("pageSize") int pageSize);

    /**
     * 删除所有数据
     * @return 受影响的行数
     */
    @AutoMapper
    int deleteAll();

    /**
     * 根据主键查询数据,支持复合主键
     * @param pk 主键数据
     * @return 实体
     */
    @AutoMapper
    T findByPk(@Param("pk") PrimaryKey ... pk);

    /**
     * 根据主键查询数据,支持复合主键
     * @param pk 主键数据
     * @return 直接以 Map 形式返回
     */
    @AutoMapper(parseColumn = false)
    Map<String, Object> findMapByPk(@Param("pk") PrimaryKey ... pk);

    /**
     * 根据主键删除数据,支持复合主键
     * @param pk 主键数据
     * @return 受影响的行数
     */
    @AutoMapper
    int deleteByPk(@Param("pk") PrimaryKey ... pk);

    /**
     * 查询当前表结构
     * @param database 表所在的数据库名称
     * @return 表结构
     */
    @AutoMapper
    TableStruct findTableStruct(@Param("database") String database);

    /**
     * 查询当前表的字段结构
     * @param database 表所在的数据库名称
     * @return 表字段结构
     */
    @AutoMapper
    List<TableFieldStruct> findTableFieldStruct(@Param("database") String database);
}

最后,插入单个数据时,如果需要返回主键值的话,可以使用 @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 kfyty725@hotmail.com
 * @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 kfyty725@hotmail.com
 * @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 "";

    /**
     * 继承自 BaseMapper 的方法需在子接口上声明实体类型
     * @return 默认值为 Object.class
     */
    Class<?> entity() default Object.class;

    /**
     * 查询时添加额外的条件,可用于接口配置
     * @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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值