关于通用mapper的使用
最近有看到通用mapper的一些新用法,记录一下通用mapper的相关使用
官方文档:
https://gitee.com/free/Mapper/wikis/Home
1 通用Mapper简介
通用 Mapper是一个可以实现任意 MyBatis 通用方法的框架,项目提供了常规的增删改查操作以及Example
相关的单表操作。通用 Mapper 是为了解决 MyBatis 使用中 90% 的基本操作,使用它可以很方便的进行开发,可以节省开发人员大量的时间。
通用Mapper可以通过Mybatis的拦截器原理,动态的帮我们实现单表的增删改查功能.
2 通用Mapper基本原理
先从通用Mapper中提供的通用方法,来查看其原理
/**
* 通用Mapper接口,查询
*
* @param <T> 不能为空
* @author liuzh
*/
@RegisterMapper
public interface SelectMapper<T> {
/**
* 根据实体中的属性值进行查询,查询条件使用等号
*
* @param record
* @return
*/
@SelectProvider(type = BaseSelectProvider.class, method = "dynamicSQL")
List<T> select(T record);
}
SelectMapper接口和方法都使用的是泛型, 使用时需要指定泛型类型. 通过反射很容易得到泛型接口的类型信息
Type[] types = mapperClass.getGenericInterfaces();
Class<?> entityClass = null;
for (Type type : types) {
if (type instanceof ParameterizedType) {
ParameterizedType t = (ParameterizedType) type;
//判断父接口是否为 SelectMapper.class
if (t.getRawType() == SelectMapper.class) {
//得到泛型类型
entityClass = (Class<?>) t.getActualTypeArguments()[0];
break;
}
}
}
实体类中添加的 JPA 注解只是一种映射实体和数据库表关系的手段,通过一些默认规则或者自定义注解也很容易设置这种关系,获取实体和表的对应关系后,就可以根据通用接口方法定义的功能来生成和 XML 中一样的 SQL 代码
看到注解SelectProvider上type为BaseSelectProvider.class类, 查看其类代码:
/**
* BaseSelectProvider实现类,基础方法实现类
*
* @author liuzh
*/
public class BaseSelectProvider extends MapperTemplate {
public BaseSelectProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
super(mapperClass, mapperHelper);
}
/**
* 查询
*
* @param ms
* @return
*/
public String selectOne(MappedStatement ms) {
Class<?> entityClass = getEntityClass(ms);
//修改返回值类型为实体类型
setResultType(ms, entityClass);
StringBuilder sql = new StringBuilder();
sql.append(SqlHelper.selectAllColumns(entityClass));
sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
sql.append(SqlHelper.whereAllIfColumns(entityClass, isNotEmpty()));
return sql.toString();
}
/**
* 查询
*
* @param ms
* @return
*/
public String select(MappedStatement ms) {
Class<?> entityClass = getEntityClass(ms);
//修改返回值类型为实体类型
setResultType(ms, entityClass);
StringBuilder sql = new StringBuilder();
sql.append(SqlHelper.selectAllColumns(entityClass));
sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
sql.append(SqlHelper.whereAllIfColumns(entityClass, isNotEmpty()));
sql.append(SqlHelper.orderByDefault(entityClass));
return sql.toString();
}
/**
* 查询
*
* @param ms
* @return
*/
public String selectByRowBounds(MappedStatement ms) {
return select(ms);
}
/**
* 根据主键进行查询
*
* @param ms
*/
public String selectByPrimaryKey(MappedStatement ms) {
final Class<?> entityClass = getEntityClass(ms);
//将返回值修改为实体类型
setResultType(ms, entityClass);
StringBuilder sql = new StringBuilder();
sql.append(SqlHelper.selectAllColumns(entityClass));
sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
sql.append(SqlHelper.wherePKColumns(entityClass));
return sql.toString();
}
/**
* 查询总数
*
* @param ms
* @return
*/
public String selectCount(MappedStatement ms) {
Class<?> entityClass = getEntityClass(ms);
StringBuilder sql = new StringBuilder();
sql.append(SqlHelper.selectCount(entityClass));
sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
sql.append(SqlHelper.whereAllIfColumns(entityClass, isNotEmpty()));
return sql.toString();
}
/**
* 根据主键查询总数
*
* @param ms
* @return
*/
public String existsWithPrimaryKey(MappedStatement ms) {
Class<?> entityClass = getEntityClass(ms);
StringBuilder sql = new StringBuilder();
sql.append(SqlHelper.selectCountExists(entityClass));
sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
sql.append(SqlHelper.wherePKColumns(entityClass));
return sql.toString();
}
/**
* 查询全部结果
*
* @param ms
* @return
*/
public String selectAll(MappedStatement ms) {
final Class<?> entityClass = getEntityClass(ms);
//修改返回值类型为实体类型
setResultType(ms, entityClass);
StringBuilder sql = new StringBuilder();
sql.append(SqlHelper.selectAllColumns(entityClass));
sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
// 逻辑删除的未删除查询条件
sql.append("<where>");
sql.append(SqlHelper.whereLogicDelete(entityClass, false));
sql.append("</where>");
sql.append(SqlHelper.orderByDefault(entityClass));
return sql.toString();
}
}
在 mybatis 中,每一个方法(注解或 XML )经过处理后,最终会构造成 MappedStatement
对象.
使用@SelectProvider 这种定义,会构造成 ProviderSqlSource,ProviderSqlSource 是一种处于中间的 SqlSource,它本身不能作为最终执行时使用的 SqlSource,但是他会根据指定方法返回的 SQL 去构造一个可用于最后执行的 StaticSqlSource,StaticSqlSource的特点就是静态 SQL,支持在 SQL 中使用#{param} 方式的参数,但是不支持 <if>,<where>
等标签
通用 Mapper 从这里入手,利用ProviderSqlSource
可以生成正常的 MappedStatement
,在生成 MappedStatement
后,就把 ProviderSqlSource
替换掉了.
接口方法中根据ms 的 id(规范情况下是 接口名.方法名
)得到接口,通过接口的泛型可以获取实体类(entityClass
),根据实体和表的关系我们可以拼出 XML 方式的动态 SQL
/**
* 查询全部结果
*
* @param ms
* @return
*/
public String selectAll(MappedStatement ms) {
final Class<?> entityClass = getEntityClass(ms);
//修改返回值类型为实体类型
setResultType(ms, entityClass);
StringBuilder sql = new StringBuilder();
sql.append(SqlHelper.selectAllColumns(entityClass));
sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
// 逻辑删除的未删除查询条件
sql.append("<where>");
sql.append(SqlHelper.whereLogicDelete(entityClass, false));
sql.append("</where>");
sql.append(SqlHelper.orderByDefault(entityClass));
return sql.toString();
}
拼出的 XML 形式的动态 SQL,使用 mybatis 的 XMLLanguageDriver
中的 createSqlSource
方法可以生成 SqlSource
。然后使用反射用新的 SqlSource
替换ProviderSqlSource
.
tk.mybatis.mapper.mapperhelper.MapperTemplate通用Mapper模板类中
/**
* 重新设置SqlSource
*
* @param ms
* @param sqlSource
*/
protected void setSqlSource(MappedStatement ms, SqlSource sqlSource) {
MetaObject msObject = MetaObjectUtil.forObject(ms);
msObject.setValue("sqlSource", sqlSource);
}
3 通用Mapper使用
通用mapper提供了一些基础的增删改查的方法:
// 1 新增
// Mapper接口只提供基础的增删改查 批量新增的需要使用 MySqlMapper 接口,该接口继承自 InsertListMapper 接口
userMapper.insert(new User());
userMapper.insertSelective(new User());
userMapper.insertList(new ArrayList<User>());
// 2 修改
userMapper.updateByPrimaryKey(new User());
userMapper.updateByPrimaryKeySelective(new User());
userMapper.updateByExample(new User(), new Example(User.class));
userMapper.updateByExampleSelective(new User(), new Example(User.class));
// 3 删除
userMapper.delete(new User());
userMapper.deleteByPrimaryKey("id");
userMapper.deleteByExample(new Example(User.class));
// 4 查询
userMapper.selectAll();
userMapper.select(new User());
userMapper.selectOne(new User());
userMapper.selectByExample(new Example(User.class));
0 数据库准备
-- 建表语句
CREATE TABLE `user` (
`id` int NOT NULL COMMENT '主键',
`username` varchar(64) DEFAULT NULL COMMENT '名称',
`phone` varchar(64) DEFAULT NULL COMMENT '电话',
`icon` varchar(255) DEFAULT NULL COMMENT '二进制',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
-- 添加数据
INSERT INTO `test`.`user`(`id`, `username`, `phone`, `icon`) VALUES (1, '李子柒', '77777', '李子柒的头像');
INSERT INTO `test`.`user`(`id`, `username`, `phone`, `icon`) VALUES (2, '2', '2', 'world');
INSERT INTO `test`.`user`(`id`, `username`, `phone`, `icon`) VALUES (3, '3', '3', '3333');
INSERT INTO `test`.`user`(`id`, `username`, `phone`, `icon`) VALUES (5, '5', '5', '123adb');
INSERT INTO `test`.`user`(`id`, `username`, `phone`, `icon`) VALUES (7, '5', '5', '汉字你好');
1 新建一个SpringBoot环境
2 添加maven依赖
<!-- https://mvnrepository.com/artifact/tk.mybatis/mapper -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>4.1.5</version>
</dependency>
<!--mybatis和spring整合-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<!--MySQL数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
3 添加配置文件
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
4 添加实体类
@Data
@Table(name = "user")
public class User {
@Id
private String id;
@Column
private String username;
@Column
private String phone;
@Column
private String icon;
@Transient
private Date queryTime = new Date();
}
5 添加mapper接口
// MySqlMapper接口主要是使用批量新增功能
public interface UserMapper extends Mapper<User>, MySqlMapper<User> {
}
6 添加controller控制器
@Controller
@RequestMapping("/helloworld")
public class HelloWorld {
@Autowired
private UserMapper userMapper;
@GetMapping("/queryByCondition")
@ResponseBody
public String queryByCondition(@RequestParam("keyword") String keyword) {
// 使用example来实现复杂关系的查询
// 1 example可以查询自己需要的字段,并设置排序规则 (可选,默认是查询所有,无排序条件)
Example example = new Example(User.class);
example.selectProperties("id", "phone", "icon")
.orderBy("id")
.desc();
// 2 模糊查询需要自己拼百分号% (mybatis-plus不需要)
example.createCriteria()
.andLike("username", "%" + keyword + "%")
.orEqualTo("phone", keyword);
List<User> users = userMapper.selectByExample(example);
System.out.println(users);
return "<h1>Hello Wrold</h1>";
}
}
7 添加启动类
@MapperScan("com.cf.demo.mapper")
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
8 测试
在本地浏览器访问
http://localhost:8080/helloworld/queryByCondition?keyword=子
浏览器页面展示:
Hello Wrold
控制台展示:
[User(id=1, username=null, phone=77777, icon=李子柒的头像, queryTime=Mon Oct 25 19:25:44 CST 2021)]