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

最近公司在用的通用mapper,自己感兴趣,然后就来搭建了一个springboot项目试验通用mapper

这个项目是国内的大神写的一个mybatis插件,里面有很多的增删改查方法

官方解释的是通用mapper支持3.2.4以及以上的版本

首先引入pom


 
 
  1. <!--Mybatis -->
  2. <dependency>
  3. <groupId>org.mybatis.spring.boot </groupId>
  4. <artifactId>mybatis-spring-boot-starter </artifactId>
  5. <version>1.1.1 </version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.mybatis.generator </groupId>
  9. <artifactId>mybatis-generator </artifactId>
  10. <version>1.3.5 </version>
  11. <type>pom </type>
  12. </dependency>
  13. <!--分页插件 -->
  14. <dependency>
  15. <groupId>com.github.pagehelper </groupId>
  16. <artifactId>pagehelper </artifactId>
  17. <version>4.2.1 </version>
  18. </dependency>
  19. <!--tkmybatis -->
  20. <dependency>
  21. <groupId>tk.mybatis </groupId>
  22. <artifactId>mapper-spring-boot-starter </artifactId>
  23. <version>1.1.4 </version>
  24. </dependency>

通用Mapper是tk.mybais中的

配置文件application.yml:


 
 
  1. server:
  2. port: 8081
  3. # 下面是配置undertow作为服务器的参数
  4. undertow:
  5. # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
  6. io-threads: 4
  7. # 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
  8. worker-threads: 20
  9. # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
  10. # 每块buffer的空间大小,越小的空间被利用越充分
  11. buffer-size: 1024
  12. # 是否分配的直接内存
  13. direct-buffers: true
  14. spring:
  15. datasource:
  16. type: com.alibaba.druid.pool.DruidDataSource
  17. driverClassName: com.mysql.jdbc.Driver
  18. driver-class-name: com.mysql.jdbc.Driver
  19. platform: mysql
  20. url: jdbc:mysql://xxx.xxx.xxx.xxx:5306/miniprogram?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false
  21. username: xxxxx
  22. password: xxxxx
  23. initialSize: 5
  24. minIdle: 5
  25. maxActive: 20
  26. maxWait: 60000
  27. timeBetweenEvictionRunsMillis: 60000
  28. minEvictableIdleTimeMillis: 300000
  29. validationQuery: SELECT1FROMDUAL
  30. testWhileIdle: true
  31. testOnBorrow: false
  32. testOnReturn: false
  33. filters: stat,wall
  34. logSlowSql: true
  35. redis:
  36. database: 1
  37. host: xxxxx
  38. port: xxxx
  39. password: xxxx
  40. timeout: 10000
  41. activemq:
  42. queueName: mvp.queue
  43. topicName: mvp.topic
  44. #账号密码
  45. user: user
  46. password: user
  47. #URL of the ActiveMQ broker.
  48. broker-url: tcp://localhost:61616
  49. in-memory: false
  50. #必须使用连接池
  51. pool:
  52. #启用连接池
  53. enabled: true
  54. #连接池最大连接数
  55. max-connections: 5
  56. #空闲的连接过期时间,默认为30秒
  57. idle-timeout: 30s
  58. # jedis: 有默认值,源码:RedisProperties
  59. # pool:
  60. # max-active:
  61. # max-idle:
  62. # max-wait:
  63. # min-idle:
  64. mybatis:
  65. typeAliasesPackage: com.pinyu.miniprogram.mysql.entity
  66. mapper-locations: classpath:mapper/**/*Mapper.xml
  67. mapper:
  68. mappers: com.pinyu.miniprogram.mysql.mappers.BaseMapper
  69. identity: mysql
  70. #logging.config:
  71. # classpath: test/log4j2_test.xml

xxxx请配置自己的数据库相关信息

mapper:
  mappers: com.pinyu.miniprogram.mysql.mappers.BaseMapper
  identity: mysql

ctrl+鼠标点击com.pinyu.miniprogram.mysql.mappers.BaseMapper 进入源码可以看到 MapperProperties类

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

也可以用代码进行配置:


 
 
  1. /**
  2. * 通用mapper与分页插件的一些配置
  3. */
  4. @Configuration
  5. public class MyBatisMapperScannerConfig {
  6. /**
  7. * 使用通用Mapper之前需要初始化的一些信息
  8. * 使用通用Mapper插件时请勿使用热加载,否则报错,插件作者后续应该会修复
  9. */
  10. @Bean
  11. public MapperScannerConfigurer mapperScannerConfigurer() {
  12. MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
  13. mapperScannerConfigurer.setSqlSessionFactoryBeanName( "sqlSessionFactory");
  14. mapperScannerConfigurer.setBasePackage( "com.xx.xx.xx.mapper"); //普通mapper的位置
  15. Properties properties = new Properties();
  16. properties.setProperty( "mappers", BaseMapper.class.getName());//通用mapper的全名
  17. properties.setProperty("notEmpty", " false");
  18. properties.setProperty("IDENTITY", "MYSQL");//配置数据库方言
  19. mapperScannerConfigurer.setProperties(properties);
  20. return mapperScannerConfigurer;
  21. }
  22. /**
  23. * 配置mybatis的分页插件pageHelper
  24. */
  25. @Bean
  26. public PageHelper pageHelper(){
  27. PageHelper pageHelper = new PageHelper();
  28. Properties properties = new Properties();
  29. //设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用
  30. properties.setProperty( "offsetAsPageNum", "true");
  31. //置为true时,使用RowBounds分页会进行count查询
  32. properties.setProperty( "rowBoundsWithCount", "true");
  33. //合理化查询,启用合理化时,
  34. //如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页
  35. //未开启时如果pageNum<1或pageNum>pages会返回空数据
  36. properties.setProperty( "reasonable", "true");
  37. //配置mysql数据库的方言
  38. properties.setProperty( "dialect", "mysql");
  39. pageHelper.setProperties(properties);
  40. return pageHelper;
  41. }
  42. }

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

数据库创建一张表member以及相关字段


 
 
  1. /*
  2. Navicat MySQL Data Transfer
  3. Source Server : 120.79.81.103-5306-master
  4. Source Server Version : 50719
  5. Source Host : 120.79.81.103:5306
  6. Source Database : miniprogram
  7. Target Server Type : MYSQL
  8. Target Server Version : 50719
  9. File Encoding : 65001
  10. Date: 2019-04-03 23:09:51
  11. */
  12. SET FOREIGN_KEY_CHECKS= 0;
  13. -- ----------------------------
  14. -- Table structure for member
  15. -- ----------------------------
  16. DROP TABLE IF EXISTS `member`;
  17. CREATE TABLE `member` (
  18. `id` bigint( 20) NOT NULL AUTO_INCREMENT,
  19. `member_name` varchar( 255) NOT NULL COMMENT '会员用户名',
  20. `tel` varchar( 255) DEFAULT NULL COMMENT '电话',
  21. `nick_name` varchar( 255) NOT NULL COMMENT '昵称',
  22. `head_img` varchar( 255) DEFAULT NULL COMMENT '头像地址',
  23. `status` int( 1) NOT NULL COMMENT '状态 1启用 2禁用',
  24. `create_date` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  25. `update_date` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  26. `pwd` varchar( 255) NOT NULL COMMENT '密码',
  27. `signature` varchar( 255) DEFAULT NULL COMMENT '个性签名',
  28. `creat_id` bigint( 20) DEFAULT NULL,
  29. `delete_state` int( 1) NOT NULL DEFAULT '1',
  30. PRIMARY KEY ( `id`)
  31. ) ENGINE= InnoDB AUTO_INCREMENT= 3 DEFAULT CHARSET=utf8;
  32. -- ----------------------------
  33. -- Records of member
  34. -- ----------------------------
  35. 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');
  36. INSERT INTO `member` VALUES ( '2', 'ypp', '15928878888', '6666', null, '1', '2019-03-11 19:35:39', null, 'EDGM@MAMABDACFDLLG', null, null, '1');

创建实体MemberEntity,一般创建实体我都会抽一些相同的字段出来

MemberEntity:


 
 
  1. package com.pinyu.miniprogram.mysql.entity.member;
  2. import javax.persistence.Table;
  3. import com.pinyu.miniprogram.mysql.entity.BaseEntity;
  4. import lombok.AllArgsConstructor;
  5. import lombok.Data;
  6. import lombok.NoArgsConstructor;
  7. import lombok.experimental.Accessors;
  8. @Data // getter、setter
  9. @AllArgsConstructor // 全参构造方法
  10. @NoArgsConstructor // 无参构造方法
  11. @Accessors(chain = true) // 链式编程写法
  12. @Table(name="member")
  13. public class MemberEntity extends BaseEntity {
  14. /**
  15. *
  16. */
  17. private static final long serialVersionUID = - 2601234073734313278L;
  18. private String memberName; // 会员登录用户名
  19. private String nickName; // 昵称
  20. private String tel; // 电话
  21. private String pwd; // 密码
  22. private String headImg; // 头像图片
  23. private String signature; // 个性签名
  24. private Integer status; //状态 1禁用 2启用
  25. }

BaseEntity:


 
 
  1. package com.pinyu.miniprogram.mysql.entity;
  2. import java.util.Date;
  3. import javax.persistence.Column;
  4. import com.pinyu.miniprogram.mysql.entity.member.MemberEntity;
  5. import lombok.AllArgsConstructor;
  6. import lombok.Data;
  7. import lombok.NoArgsConstructor;
  8. import lombok.experimental.Accessors;
  9. /**
  10. * @author ypp
  11. * @Description: TODO(用一句话描述该文件做什么)
  12. */
  13. @Data // getter、setter
  14. @AllArgsConstructor // 全参构造方法
  15. @NoArgsConstructor // 无参构造方法
  16. @Accessors(chain = true) // 链式编程写法
  17. public class BaseEntity extends IdEntity {
  18. /**
  19. *
  20. */
  21. private static final long serialVersionUID = 8575696766261326260L;
  22. @Column(name="creat_id")
  23. private Integer creatId;
  24. @Column(name="create_date")
  25. private Date createDate;
  26. @Column(name="delete_state")
  27. private Integer deleteState; // 删除状态 1正常 2已删除
  28. }

IdEntity:


 
 
  1. package com.pinyu.miniprogram.mysql.entity;
  2. import java.io.Serializable;
  3. import javax.persistence.Column;
  4. import javax.persistence.GeneratedValue;
  5. import javax.persistence.GenerationType;
  6. import javax.persistence.Id;
  7. import com.pinyu.miniprogram.mysql.entity.member.MemberEntity;
  8. import lombok.AllArgsConstructor;
  9. import lombok.Data;
  10. import lombok.NoArgsConstructor;
  11. import lombok.experimental.Accessors;
  12. /**
  13. * @author ypp
  14. * @Description: TODO(用一句话描述该文件做什么)
  15. */
  16. @Data // getter、setter
  17. @AllArgsConstructor // 全参构造方法
  18. @NoArgsConstructor // 无参构造方法
  19. @Accessors(chain = true) // 链式编程写法
  20. public class IdEntity implements Serializable {
  21. /**
  22. *
  23. */
  24. private static final long serialVersionUID = - 9089706482760436909L;
  25. @Id
  26. @Column(name = "id")
  27. @GeneratedValue(strategy = GenerationType.IDENTITY)
  28. private Long id;
  29. }

@Table 对应数据库的表,如果不写默认是类名首字母小写作为表名,比Member,不写数据库表默认指向member

@Column的属性name对应数据库表的字段,如果不写默认是驼峰下划线匹配,比如private Long myId,如果不写得话,就是对应数据库表字段my_id

@Id:把当前字段作为数据库主键使用,匹配数据库主键。如果不贴此注解,在某些查询语句的时候会把表字段一起作为联合主键查询

@GeneratedValue  让通用mapper在执行insert操作之后将自动生成的主键值回写到当前实体对象对应的属性当中

新建一个通用Mapper继承Mapper、MySqlMapper,点击进去看


 
 
  1. package com .pinyu .miniprogram .mysql .mappers;
  2. import com .pinyu .miniprogram .mysql .entity .BaseEntity;
  3. import tk .mybatis .mapper .common .Mapper;
  4. import tk .mybatis .mapper .common .MySqlMapper;
  5. /**
  6. * @author ypp
  7. * 创建时间:2018年12月27日 下午1:29:03
  8. * @Description: TODO(用一句话描述该文件做什么)
  9. */
  10. public interface BaseMapper< T extends BaseEntity> extends Mapper< T>, MySqlMapper< T>{
  11. }

 

BaseMapper是我自己定义的通用Mapper,注意在被继承Mapper上面也有BaseMapper,注意区分,我这里名字取一样了而已。

被Mapper继承的BaseMapper里面有还有很多增删改查的方法

 这是源码

看了下,里面有大概20个左右方法,都是比较基础的增删改查

测试:

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

比较详细的一个入门示例。希望能帮助到用到的小伙伴

 

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

举例我要写一个通用的单表分页:

1、自己定义的通用Mapper必须包含泛型,例如MysqlMapper<T>。这一点在这里可以忽略,这里并没有自定义自己的通用Mapper,而是使用了它自带的通用Mapper,我们继承的它

2、自定义的通用Mapper接口中的方法需要有合适的注解。具体可以参考Mapper

3、需要继承MapperTemplate来实现具体的操作方法。必须要新建一个类继承MapperTemplate,必须继承MapperTemplate,必须继承MapperTemplate

4、通用Mapper中的Provider一类的注解只能使用相同的type类型(这个类型就是第三个要实现的类。)。实际上method也都写的一样。

 

在自己的BaseMapper写一个方法,改造后的BaseMapper:


 
 
  1. package com.pinyu.miniprogram.mysql.mappers;
  2. import java.util.List;
  3. import org.apache.ibatis.annotations.Param;
  4. import org.apache.ibatis.annotations.SelectProvider;
  5. import com.pinyu.miniprogram.mysql.entity.BaseEntity;
  6. import tk.mybatis.mapper.common.Mapper;
  7. import tk.mybatis.mapper.common.MySqlMapper;
  8. /**
  9. * @author ypp 创建时间:2018年12月27日 下午1:29:03
  10. * @Description: TODO(用一句话描述该文件做什么)
  11. */
  12. public interface BaseMapper<T extends BaseEntity> extends Mapper<T>, MySqlMapper<T> {
  13. /**
  14. * * 单表分页查询 * * @param object * @param offset * @param limit * @return 
  15. */
  16. @SelectProvider(type = BaseMapperProvider.class, method = "dynamicSQL")
  17. List selectPage(@Param("entity") T object, @Param("offset") int offset, @Param("limit") int limit);
  18. }

 

返回结果为List,入参分别为查询条件和分页参数。在Mapper的接口方法中,当有多个入参的时候建议增加@Param注解,否则就得用param1,param2...来引用参数。

同时必须在方法上添加注解。查询使用SelectProvider,插入使用@InsertProvider,更新使用UpdateProvider,删除使用DeleteProvider。不同的Provider就相当于xml中不同的节点,如<select>,<insert>,<update>,<delete>

因为这里是查询,所以要设置为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接口中的方法名一致


 
 
  1. package com.pinyu.miniprogram.mysql.mappers;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.Set;
  5. import org.apache.ibatis.mapping.MappedStatement;
  6. import org.apache.ibatis.scripting.xmltags.IfSqlNode;
  7. import org.apache.ibatis.scripting.xmltags.MixedSqlNode;
  8. import org.apache.ibatis.scripting.xmltags.SqlNode;
  9. import org.apache.ibatis.scripting.xmltags.StaticTextSqlNode;
  10. import org.apache.ibatis.scripting.xmltags.WhereSqlNode;
  11. import tk.mybatis.mapper.entity.EntityColumn;
  12. import tk.mybatis.mapper.mapperhelper.EntityHelper;
  13. import tk.mybatis.mapper.mapperhelper.MapperHelper;
  14. import tk.mybatis.mapper.mapperhelper.MapperTemplate;
  15. public class BaseMapperProvider extends MapperTemplate {
  16. public BaseMapperProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
  17. super(mapperClass, mapperHelper);
  18. }
  19. public SqlNode selectPage(MappedStatement ms) {
  20. Class<?> entityClass = getEntityClass(ms);
  21. // 修改返回值类型为实体类型
  22. setResultType(ms, entityClass);
  23. List<SqlNode> sqlNodes = new ArrayList<SqlNode>();
  24. // 静态的sql部分:select column ... from table
  25. sqlNodes.add( new StaticTextSqlNode(
  26. "SELECT " + EntityHelper.getSelectColumns(entityClass) + " FROM " + tableName(entityClass)));
  27. // 获取全部列
  28. Set<EntityColumn> columns = EntityHelper.getColumns(entityClass);
  29. List<SqlNode> ifNodes = new ArrayList<SqlNode>();
  30. boolean first = true;
  31. // 对所有列循环,生成<if test="property!=null">[AND] column = #{property}</if>
  32. for (EntityColumn column : columns) {
  33. StaticTextSqlNode columnNode = new StaticTextSqlNode(
  34. (first ? "" : " AND ") + column.getColumn() + " = #{entity." + column.getProperty() + "} ");
  35. if (column.getJavaType().equals(String.class)) {
  36. ifNodes.add( new IfSqlNode(columnNode, "entity." + column.getProperty() + " != null and " + "entity."
  37. + column.getProperty() + " != '' "));
  38. } else {
  39. ifNodes.add( new IfSqlNode(columnNode, "entity." + column.getProperty() + " != null "));
  40. }
  41. first = false;
  42. }
  43. // 将if添加到<where>
  44. sqlNodes.add( new WhereSqlNode(ms.getConfiguration(), new MixedSqlNode(ifNodes)));
  45. // 处理分页
  46. sqlNodes.add( new IfSqlNode( new StaticTextSqlNode( " LIMIT #{limit}"), "offset==0"));
  47. sqlNodes.add( new IfSqlNode( new StaticTextSqlNode( " LIMIT #{limit} OFFSET #{offset} "), "offset>0"));
  48. return new MixedSqlNode(sqlNodes);
  49. }
  50. }

在这里有一点要求,那就是BaseMapperProvider处理BaseMapper<T>中的方法时,方法名必须一样,因为这里需要通过反射来获取对应的方法,方法名一致一方面是为了减少开发人员的配置,另一方面和接口对应看起来更清晰。

除了方法名必须一样外,入参必须是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<SqlNode> sqlNodes = new ArrayList<SqlNode>();代码开始拼写SQL,首先是SELECT查询头,在EntityHelper.getSelectColumns(entityClass)中还处理了别名的情况。

然后获取所有的列,对列循环创建<if entity.property!=null>column = #{entity.property}</if>节点。最后把这些if节点组成的List放到一个<where>节点中。

这一段使用属性时用的是 entity. + 属性名,entity来自哪儿?来自我们前面接口定义处的Param("entity")注解,后面的两个分页参数也是。如果你用过Mybatis,相信你能明白。

之后在<where>节点后添加分页参数,当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。

测试:

这里分页参数就随便用了RESTFUL风格随便写了下

数据库只有2条数据。测试成功

其实有了Mybatis自动生成插件,通用Mapper优势并不是太突出。

非SpringBoot项目的话,原理都是一样的,只是某些类需要.xml配置文件进行配置和注入

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值