最近公司在用的通用mapper,自己感兴趣,然后就来搭建了一个springboot项目试验通用mapper
这个项目是国内的大神写的一个mybatis插件,里面有很多的增删改查方法
官方解释的是通用mapper支持3.2.4以及以上的版本
首先引入pom
-
<!--Mybatis -->
-
<dependency>
-
<groupId>org.mybatis.spring.boot
</groupId>
-
<artifactId>mybatis-spring-boot-starter
</artifactId>
-
<version>1.1.1
</version>
-
</dependency>
-
<dependency>
-
<groupId>org.mybatis.generator
</groupId>
-
<artifactId>mybatis-generator
</artifactId>
-
<version>1.3.5
</version>
-
<type>pom
</type>
-
</dependency>
-
<!--分页插件 -->
-
<dependency>
-
<groupId>com.github.pagehelper
</groupId>
-
<artifactId>pagehelper
</artifactId>
-
<version>4.2.1
</version>
-
</dependency>
-
<!--tkmybatis -->
-
<dependency>
-
<groupId>tk.mybatis
</groupId>
-
<artifactId>mapper-spring-boot-starter
</artifactId>
-
<version>1.1.4
</version>
-
</dependency>
通用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.DruidDataSource
-
driverClassName:
com.mysql.jdbc.Driver
-
driver-class-name:
com.mysql.jdbc.Driver
-
platform:
mysql
-
url:
jdbc:mysql://xxx.xxx.xxx.xxx:5306/miniprogram?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false
-
username:
xxxxx
-
password:
xxxxx
-
initialSize:
5
-
minIdle:
5
-
maxActive:
20
-
maxWait:
60000
-
timeBetweenEvictionRunsMillis:
60000
-
minEvictableIdleTimeMillis:
300000
-
validationQuery:
SELECT1FROMDUAL
-
testWhileIdle:
true
-
testOnBorrow:
false
-
testOnReturn:
false
-
filters:
stat,wall
-
logSlowSql:
true
-
redis:
-
database:
1
-
host:
xxxxx
-
port:
xxxx
-
password:
xxxx
-
timeout:
10000
-
activemq:
-
queueName:
mvp.queue
-
topicName:
mvp.topic
-
#账号密码
-
user:
user
-
password:
user
-
#URL of the ActiveMQ broker.
-
broker-url:
tcp://localhost:61616
-
in-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.entity
-
mapper-locations:
classpath:mapper/**/*Mapper.xml
-
mapper:
-
mappers:
com.pinyu.miniprogram.mysql.mappers.BaseMapper
-
identity:
mysql
-
-
#logging.config:
-
# 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)继承实现的方式不一样,下面会讲到
也可以用代码进行配置:
-
/**
-
* 通用mapper与分页插件的一些配置
-
*/
-
@Configuration
-
public
class MyBatisMapperScannerConfig {
-
-
/**
-
* 使用通用Mapper之前需要初始化的一些信息
-
* 使用通用Mapper插件时请勿使用热加载,否则报错,插件作者后续应该会修复
-
*/
-
@Bean
-
public 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
-
*/
-
@Bean
-
public 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 Transfer
-
-
Source Server : 120.79.81.103-5306-master
-
Source Server Version : 50719
-
Source Host : 120.79.81.103:5306
-
Source Database : miniprogram
-
-
Target Server Type : MYSQL
-
Target Server Version : 50719
-
File Encoding : 65001
-
-
Date: 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<
T
extends
BaseEntity>
extends
Mapper<
T>,
MySqlMapper<
T>{
-
-
}
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:
-
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<T extends BaseEntity> extends Mapper<T>, MySqlMapper<T> {
-
-
/**
-
* * 单表分页查询 * * @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中不同的节点,如<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接口中的方法名一致
-
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<SqlNode> sqlNodes =
new ArrayList<SqlNode>();
-
// 静态的sql部分:select column ... from table
-
sqlNodes.add(
new StaticTextSqlNode(
-
"SELECT " + EntityHelper.getSelectColumns(entityClass) +
" FROM " + tableName(entityClass)));
-
// 获取全部列
-
Set<EntityColumn> columns = EntityHelper.getColumns(entityClass);
-
-
List<SqlNode> ifNodes =
new ArrayList<SqlNode>();
-
boolean first =
true;
-
// 对所有列循环,生成<if test="property!=null">[AND] column = #{property}</if>
-
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添加到<where>
-
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<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配置文件进行配置和注入