SpringBoot整合mybatis通用Mapper+自定义通用Mapper方法解析

目录

  • 首先引入pom
  • 通用Mapper是tk.mybais中的
  • 也可以用代码进行配置
  • 数据库创建一张表member以及相关字段
  • 新建一个通用Mapper继承Mapper、MySqlMapper
  • 比较详细的一个入门示例
  • 举例我要写一个通用的单表分页
  • 在自己的BaseMapper写一个方法
  • 新建的BaseMapperProvider
  • 对上诉实现代码的描述
  • 返回后通用Mapper是怎么处理的
最近公司在用的通用mapper,自己感兴趣,然后就来搭建了一个springboot项目试验通用mapper
这个项目是国内的大神写的一个mybatis插件,里面有很多的增删改查方法
官方解释的是通用mapper支持3.2.4以及以上的版本

首先引入pom
org.mybatis.spring.bootmybatis-spring-boot-starter1.1.1org.mybatis.generatormybatis-generator1.3.5pomcom.github.pagehelperpagehelper4.2.1tk.mybatismapper-spring-boot-starter1.1.4


通用Mapper是tk.mybais中的 配置文件application.yml:
server: port: 8081# 下面是配置undertow作为服务器的参数undertow: # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程io-threads: 4# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载worker-threads: 20# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理# 每块buffer的空间大小,越小的空间被利用越充分buffer-size: 1024# 是否分配的直接内存direct-buffers: true spring: datasource: type: com.alibaba.druid.pool.DruidDataSourcedriverClassName: com.mysql.jdbc.Driverdriver-class-name: com.mysql.jdbc.Driverplatform: mysqlurl: jdbc:mysql://xxx.xxx.xxx.xxx:5306/miniprogram?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=falseusername: xxxxxpassword: xxxxxinitialSize: 5minIdle: 5maxActive: 20maxWait: 60000timeBetweenEvictionRunsMillis: 60000minEvictableIdleTimeMillis: 300000validationQuery: SELECT1FROMDUALtestWhileIdle: truetestOnBorrow: falsetestOnReturn: falsefilters: stat,walllogSlowSql: trueredis: database: 1host: xxxxxport: xxxxpassword: xxxxtimeout: 10000activemq: queueName: mvp.queuetopicName: mvp.topic#账号密码user: userpassword: user#URL of the ActiveMQ broker.broker-url: tcp://localhost:61616in-memory: false#必须使用连接池pool: #启用连接池enabled: true#连接池最大连接数max-connections: 5#空闲的连接过期时间,默认为30秒idle-timeout: 30s#jedis:有默认值,源码:RedisProperties#pool:#max-active:#max-idle:#max-wait:#min-idle: mybatis: typeAliasesPackage: com.pinyu.miniprogram.mysql.entitymapper-locations: classpath:mapper/**/*Mapper.xmlmapper:mappers: com.pinyu.miniprogram.mysql.mappers.BaseMapperidentity: mysql#logging.config: #classpath: test/log4j2_test.xml

xxxx请配置自己的数据库相关信息
mapper:mappers: com.pinyu.miniprogram.mysql.mappers.BaseMapperidentity: mysql

ctrl+鼠标点击com.pinyu.miniprogram.mysql.mappers.BaseMapper 进入源码可以看到 MapperProperties类
SpringBoot整合mybatis通用Mapper+自定义通用Mapper方法解析
文章图片

是一个集合,意思这里可以mappers配置多个通用Mapper,可以是直接继承它已有的通用Mapper,也可以是定义自己需要的通用Mapper,自定义通用Mapper(代替它的Mapper)继承实现的方式不一样,下面会讲到

也可以用代码进行配置
/** * 通用mapper与分页插件的一些配置 */@Configurationpublic class MyBatisMapperScannerConfig { /*** 使用通用Mapper之前需要初始化的一些信息* 使用通用Mapper插件时请勿使用热加载,否则报错,插件作者后续应该会修复*/@Beanpublic MapperScannerConfigurer mapperScannerConfigurer() {MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory"); mapperScannerConfigurer.setBasePackage("com.xx.xx.xx.mapper"); //普通mapper的位置Properties properties = new Properties(); properties.setProperty("mappers", BaseMapper.class.getName()); //通用mapper的全名properties.setProperty("notEmpty", "false"); properties.setProperty("IDENTITY", "MYSQL"); //配置数据库方言mapperScannerConfigurer.setProperties(properties); return mapperScannerConfigurer; } /*** 配置mybatis的分页插件pageHelper*/@Beanpublic PageHelper pageHelper(){PageHelper pageHelper = new PageHelper(); Properties properties = new Properties(); //设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用properties.setProperty("offsetAsPageNum","true"); //置为true时,使用RowBounds分页会进行count查询properties.setProperty("rowBoundsWithCount","true"); //合理化查询,启用合理化时,//如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页//未开启时如果pageNum<1或pageNum>pages会返回空数据properties.setProperty("reasonable","true"); //配置mysql数据库的方言properties.setProperty("dialect","mysql"); pageHelper.setProperties(properties); return pageHelper; }}

mappers对应的通用mapper类,不要和自己其他业务的mapper放在一起,不要被@MapperScan扫描到,不然会报错,因为继承了通用mapper,会有很多相应的方法,被扫描到以后,mybatis发现没有一个xml配置文件或者相应方法没有进行实现,这时候就会报错。但是继承自己的BaseMapper相关mapper肯定是要被扫描到的

数据库创建一张表member以及相关字段
/*Navicat MySQL Data TransferSource Server: 120.79.81.103-5306-masterSource Server Version : 50719Source Host: 120.79.81.103:5306Source Database: miniprogramTarget Server Type: MYSQLTarget Server Version : 50719File Encoding: 65001Date: 2019-04-03 23:09:51*/ SET FOREIGN_KEY_CHECKS=0; -- ------------------------------ Table structure for member-- ----------------------------DROP TABLE IF EXISTS `member`; CREATE TABLE `member` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`member_name` varchar(255) NOT NULL COMMENT '会员用户名',`tel` varchar(255) DEFAULT NULL COMMENT '电话',`nick_name` varchar(255) NOT NULL COMMENT '昵称',`head_img` varchar(255) DEFAULT NULL COMMENT '头像地址',`status` int(1) NOT NULL COMMENT '状态 1启用 2禁用',`create_date` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',`update_date` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',`pwd` varchar(255) NOT NULL COMMENT '密码',`signature` varchar(255) DEFAULT NULL COMMENT '个性签名',`creat_id` bigint(20) DEFAULT NULL,`delete_state` int(1) NOT NULL DEFAULT '1',PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; -- ------------------------------ Records of member-- ----------------------------INSERT INTO `member` VALUES ('1', 'dsada', '15928878433', 'dasdas', null, '1', '2019-03-11 18:47:53', '2019-03-11 18:47:53', '123456', null, null, '1'); INSERT INTO `member` VALUES ('2', 'ypp', '15928878888', '6666', null, '1', '2019-03-11 19:35:39', null, 'EDGM@MAMABDACFDLLG', null, null, '1');

创建实体MemberEntity,一般创建实体我都会抽一些相同的字段出来
MemberEntity:
package com.pinyu.miniprogram.mysql.entity.member; import javax.persistence.Table; import com.pinyu.miniprogram.mysql.entity.BaseEntity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; @Data // getter、setter@AllArgsConstructor // 全参构造方法@NoArgsConstructor // 无参构造方法@Accessors(chain = true) // 链式编程写法@Table(name="member")public class MemberEntity extends BaseEntity { /*** */ private static final long serialVersionUID = -2601234073734313278L; private String memberName; // 会员登录用户名 private String nickName; // 昵称 private String tel; // 电话 private String pwd; // 密码 private String headImg; // 头像图片 private String signature; // 个性签名 private Integer status; //状态 1禁用 2启用}

BaseEntity:
package com.pinyu.miniprogram.mysql.entity; import java.util.Date; import javax.persistence.Column; import com.pinyu.miniprogram.mysql.entity.member.MemberEntity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; /** * @author ypp * @Description: TODO(用一句话描述该文件做什么) */@Data // getter、setter@AllArgsConstructor // 全参构造方法@NoArgsConstructor // 无参构造方法@Accessors(chain = true) // 链式编程写法public class BaseEntity extends IdEntity { /*** */ private static final long serialVersionUID = 8575696766261326260L; @Column(name="creat_id") private Integer creatId; @Column(name="create_date") private Date createDate; @Column(name="delete_state") private Integer deleteState; // 删除状态 1正常 2已删除 }

IdEntity:
package com.pinyu.miniprogram.mysql.entity; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import com.pinyu.miniprogram.mysql.entity.member.MemberEntity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; /** * @author ypp * @Description: TODO(用一句话描述该文件做什么) */@Data // getter、setter@AllArgsConstructor // 全参构造方法@NoArgsConstructor // 无参构造方法@Accessors(chain = true) // 链式编程写法public class IdEntity implements Serializable { /*** */ private static final long serialVersionUID = -9089706482760436909L; @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; }

  • @Table:对应数据库的表,如果不写默认是类名首字母小写作为表名,比Member,不写数据库表默认指向member
  • @Column:的属性name对应数据库表的字段,如果不写默认是驼峰下划线匹配,比如private Long myId,如果不写得话,就是对应数据库表字段my_id
  • @Id:把当前字段作为数据库主键使用,匹配数据库主键。如果不贴此注解,在某些查询语句的时候会把表字段一起作为联合主键查询
  • @GeneratedValue:让通用mapper在执行insert操作之后将自动生成的主键值回写到当前实体对象对应的属性当中

新建一个通用Mapper继承Mapper、MySqlMapper 点击进去看
package com.pinyu.miniprogram.mysql.mappers; import com.pinyu.miniprogram.mysql.entity.BaseEntity; import tk.mybatis.mapper.common.Mapper; import tk.mybatis.mapper.common.MySqlMapper; /** * @author ypp* 创建时间:2018年12月27日 下午1:29:03 * @Description: TODO(用一句话描述该文件做什么) */public interface BaseMapper extends Mapper,MySqlMapper{ }

BaseMapper是我自己定义的通用Mapper,注意在被继承Mapper上面也有BaseMapper,注意区分,我这里名字取一样了而已。
被Mapper继承的BaseMapper里面有还有很多增删改查的方法
SpringBoot整合mybatis通用Mapper+自定义通用Mapper方法解析
文章图片

这是源码
看了下,里面有大概20个左右方法,都是比较基础的增删改查
SpringBoot整合mybatis通用Mapper+自定义通用Mapper方法解析
文章图片

测试:
SpringBoot整合mybatis通用Mapper+自定义通用Mapper方法解析
文章图片

SpringBoot整合mybatis通用Mapper+自定义通用Mapper方法解析
文章图片

SpringBoot整合mybatis通用Mapper+自定义通用Mapper方法解析
文章图片

MemberMapper并没有selectAll方法,沿用的继承的selectAll方法

比较详细的一个入门示例 希望能帮助到用到的小伙伴
SpringBoot整合mybatis通用Mapper+自定义通用Mapper方法解析
文章图片

自定义通用Mapper,也有可能在实际工作中通用Mapper并不能满足工作,需要额外的一些通用方法,但是这种的情况很少,通用Mapper提供的方法基本都能满足单表操作需求了

举例我要写一个通用的单表分页 1、自己定义的通用Mapper必须包含泛型,例如MysqlMapper。这一点在这里可以忽略,这里并没有自定义自己的通用Mapper,而是使用了它自带的通用Mapper,我们继承的它
2、自定义的通用Mapper接口中的方法需要有合适的注解。具体可以参考Mapper
3、需要继承MapperTemplate来实现具体的操作方法。必须要新建一个类继承MapperTemplate,必须继承MapperTemplate,必须继承MapperTemplate
4、通用Mapper中的Provider一类的注解只能使用相同的type类型(这个类型就是第三个要实现的类。)。实际上method也都写的一样。

在自己的BaseMapper写一个方法 改造后的BaseMapper
package com.pinyu.miniprogram.mysql.mappers; import java.util.List; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.SelectProvider; import com.pinyu.miniprogram.mysql.entity.BaseEntity; import tk.mybatis.mapper.common.Mapper; import tk.mybatis.mapper.common.MySqlMapper; /** * @author ypp 创建时间:2018年12月27日 下午1:29:03 * @Description: TODO(用一句话描述该文件做什么) */public interface BaseMapper extends Mapper, MySqlMapper { /*** * 单表分页查询 * * @param object * @param offset * @param limit * @return */ @SelectProvider(type = BaseMapperProvider.class, method = "dynamicSQL") List selectPage(@Param("entity") T object, @Param("offset") int offset, @Param("limit") int limit); }

返回结果为List,入参分别为查询条件和分页参数。在Mapper的接口方法中,当有多个入参的时候建议增加@Param注解,否则就得用param1,param2...来引用参数。
同时必须在方法上添加注解。查询使用SelectProvider,插入使用@InsertProvider,更新使用UpdateProvider,删除使用DeleteProvider。不同的Provider就相当于xml中不同的节点,如,,,
因为这里是查询,所以要设置为SelectProvider,这4个Provider中的参数都一样,只有type和method。
type必须设置为实际执行方法的BaseMapperProvider.class(此类在下面),method必须设置为"dynamicSQL"。
通用Mapper处理的时候会根据type反射BaseMapperProvider查找方法,而Mybatis的处理机制要求method必须是type类中只有一个入参,且返回值为String的方法。"dynamicSQL"方法定义在MapperTemplate中,该方法如下:
public String dynamicSQL(Object record) {return "dynamicSQL"; }


新建的BaseMapperProvider 上面第三点说到了需要一个类继承MapperTemplate,这是必须的。继承了它然后再进行相应的实现,方法名请和Mapper接口中的方法名一致
package com.pinyu.miniprogram.mysql.mappers; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.scripting.xmltags.IfSqlNode; import org.apache.ibatis.scripting.xmltags.MixedSqlNode; import org.apache.ibatis.scripting.xmltags.SqlNode; import org.apache.ibatis.scripting.xmltags.StaticTextSqlNode; import org.apache.ibatis.scripting.xmltags.WhereSqlNode; import tk.mybatis.mapper.entity.EntityColumn; import tk.mybatis.mapper.mapperhelper.EntityHelper; import tk.mybatis.mapper.mapperhelper.MapperHelper; import tk.mybatis.mapper.mapperhelper.MapperTemplate; public class BaseMapperProvider extends MapperTemplate { public BaseMapperProvider(Class mapperClass, MapperHelper mapperHelper) {super(mapperClass, mapperHelper); } public SqlNode selectPage(MappedStatement ms) {Class entityClass = getEntityClass(ms); // 修改返回值类型为实体类型setResultType(ms, entityClass); List sqlNodes = new ArrayList(); // 静态的sql部分:select column ... from tablesqlNodes.add(new StaticTextSqlNode("SELECT " + EntityHelper.getSelectColumns(entityClass) + " FROM " + tableName(entityClass))); // 获取全部列Set columns = EntityHelper.getColumns(entityClass); List ifNodes = new ArrayList(); boolean first = true; // 对所有列循环,生成[AND] column = #{property}for (EntityColumn column : columns) {StaticTextSqlNode columnNode = new StaticTextSqlNode((first ? "" : " AND ") + column.getColumn() + " = #{entity." + column.getProperty() + "} "); if (column.getJavaType().equals(String.class)) {ifNodes.add(new IfSqlNode(columnNode, "entity." + column.getProperty() + " != null and " + "entity."+ column.getProperty() + " != '' ")); } else {ifNodes.add(new IfSqlNode(columnNode, "entity." + column.getProperty() + " != null ")); }first = false; }// 将if添加到sqlNodes.add(new WhereSqlNode(ms.getConfiguration(), new MixedSqlNode(ifNodes))); // 处理分页sqlNodes.add(new IfSqlNode(new StaticTextSqlNode(" LIMIT #{limit}"), "offset==0")); sqlNodes.add(new IfSqlNode(new StaticTextSqlNode(" LIMIT #{limit} OFFSET #{offset} "), "offset>0")); return new MixedSqlNode(sqlNodes); } }

在这里有一点要求,那就是BaseMapperProvider处理BaseMapper中的方法时,方法名必须一样,因为这里需要通过反射来获取对应的方法,方法名一致一方面是为了减少开发人员的配置,另一方面和接口对应看起来更清晰。
除了方法名必须一样外,入参必须是MappedStatement ms,除此之外返回值可以是void或者SqlNode之一。
这里先讲一下通用Mapper的实现原理。通用Mapper目前是通过拦截器在通用方法第一次执行的时候去修改MappedStatement对象的SqlSource属性。而且只会执行一次,以后就和正常的方法没有任何区别。
使用Provider注解的这个Mapper方法,Mybatis本身会处理成ProviderSqlSource(一个SqlSource的实现类),由于之前的配置,这个ProviderSqlSource种的SQL是上面代码中返回的"dynamicSQL"。这个SQL没有任何作用,如果不做任何修改,执行这个代码肯定会出错。所以在拦截器中拦截符合要求的接口方法,遇到ProviderSqlSource就通过反射调用如BaseMapperProvider中的具体代码去修改原有的SqlSource。
最简单的处理Mybatis SQL的方法是什么?就是创建SqlNode,使用DynamicSqlSource,这种情况下我们不需要处理入参,不需要处理代码中的各种类型的参数映射。比执行SQL的方式容易很多。
有关这部分的内容建议查看通用Mapper的源码和Mybatis源码了解,如果不了解在这儿说多了反而会乱。

对上诉实现代码的描述 首先获取了实体类型,然后通过setResultType将返回值类型改为entityClass,就相当于resultType=entityClass。
这里为什么要修改呢?因为默认返回值是T,Java并不会自动处理成我们的实体类,默认情况下是Object,对于所有的查询来说,我们都需要手动设置返回值类型。
对于insert,update,delete来说,这些操作的返回值都是int,所以不需要修改返回结果类型。
之后从List sqlNodes = new ArrayList(); 代码开始拼写SQL,首先是SELECT查询头,在EntityHelper.getSelectColumns(entityClass)中还处理了别名的情况。
然后获取所有的列,对列循环创建column = #{entity.property}节点。最后把这些if节点组成的List放到一个节点中。
这一段使用属性时用的是 entity. + 属性名,entity来自哪儿?来自我们前面接口定义处的Param("entity")注解,后面的两个分页参数也是。如果你用过Mybatis,相信你能明白。
之后在节点后添加分页参数,当offset==0时和offset>0时的分页代码不同。
最后封装成一个MixedSqlNode返回。

返回后通用Mapper是怎么处理的 这里贴下源码:
SqlNode sqlNode = (SqlNode) method.invoke(this, ms); DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(ms.getConfiguration(), sqlNode); setSqlSource(ms, dynamicSqlSource);

返回SqlNode后创建了DynamicSqlSource,然后修改了ms原来的SqlSource。
【SpringBoot整合mybatis通用Mapper+自定义通用Mapper方法解析】测试:
SpringBoot整合mybatis通用Mapper+自定义通用Mapper方法解析
文章图片

SpringBoot整合mybatis通用Mapper+自定义通用Mapper方法解析
文章图片

这里分页参数就随便用了RESTFUL风格随便写了下
SpringBoot整合mybatis通用Mapper+自定义通用Mapper方法解析
文章图片

SpringBoot整合mybatis通用Mapper+自定义通用Mapper方法解析
文章图片

数据库只有2条数据。测试成功
其实有了Mybatis自动生成插件,通用Mapper优势并不是太突出。
非SpringBoot项目的话,原理都是一样的,只是某些类需要.xml配置文件进行配置和注入
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

    推荐阅读