MyBatis 通用 Mapper入门教程

TKMybatis-通用Mapper

TkMybatis,中文名称叫通用Mapper,是一款能够实现自定义Java处理的Mybatis第三方插件,在项目中使用通用Mapper能够轻松实现自定义的CURD等Mybatis操作。与它类似的插件还有MybatisPlus等。

通用Mapper的本质的实现是利用了 MyBatis 代码生成器 + JPA注释操作。

生成代码

<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>2.2.0</version>
</dependency>
<!-- TK-Mybatis -->
<dependency>
	<groupId>tk.mybatis</groupId>
	<artifactId>mapper</artifactId>
	<version>3.4.2</version>
</dependency>

使用通用Mapper时,可以和平常的Mybatis一样,先使用MyBatis 代码生成器生成Mapper,其中我们向其中的MyBatis 代码生成配置中配置通用Mapper插件。

<generatorConfiguration>
    <context id="sakila" targetRuntime="MyBatis3">
        ...
        <!-- 使用tkMapper插件 -->
        <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
            <!-- value可以为默认的,也可以为用户定义的tkMapper实体类 -->
            <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
            <!-- caseSensitive默认false,当数据库表名区分大小写时,可以将该属性设置为true -->
            <property name="caseSensitive" value="true"/>
        </plugin>
        ...
    </context>
</generatorConfiguration>

其中配置中tk.mybatis.mapper.common.Mapper是TkMapper中的一个通用Mapper类接口,我们可以查看具体的内容。

public interface Mapper<T> extends
        BaseMapper<T>,
        ExampleMapper<T>,
        RowBoundsMapper<T>,
        Marker {
}

为了实现更好的功能,推荐大家自己本地项目中配置一个通用Mapper类接口,这是我用的最习惯的配置:

public interface Mapper<T>
        extends
        BaseMapper<T>,
        ConditionMapper<T>,
        IdsMapper<T>,
        InsertListMapper<T> {
}

然后将其配置中tk.mybatis.mapper.common.Mapper更换为自己配置的这个类接口包名接口。

生成Mybatis代码完毕后,我们会发现,生成的mapper接口都继承上了这个通用Mapper接口,表示它就可以使用Tkmapper的功能了。

public interface XxxMapper extends Mapper<Xxx> {
}

配置文件配置

和Mybatis一样,我们在使用前必须配置相关配置,才能保证其插件正常启用。

由于这里面我们引进的是mapper-spring-boot-starter这个项目包,它内部拥有SpringBoot自动配置的配置,所以我们可以直接在SpringBoot配置文件中进行配置:

mapper:
  mappers:
    - tk.mybatis.mapper.common.Mapper
    - tk.mybatis.mapper.common.Mapper2
  notEmpty: true

注意:引入该 starter 时,和 MyBatis 官方的 starter 没有冲突,但是官方的自动配置不会生效!

我们也可以导入原始版本的Tk-Mybatis的依赖:

<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper</artifactId>
    <version>4.1.5</version>
</dependency>

它的配置复杂点,在SpringBoot中需要进行设置配置类:

@Configuration
public class MybatisConfigurer {
	...
	@Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
    	//下面配置通用Mapper,详情请查阅官方文档
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactoryBean");
        // 设置Mapper接口包
        mapperScannerConfigurer.setBasePackage(MAPPER_PACKAGE);

        Properties properties = new Properties();
        // 设置通用Mapper接口类包名
        properties.setProperty("mappers", MAPPER_INTERFACE_REFERENCE);
        properties.setProperty("notEmpty", "false");//insert、update是否判断字符串类型!='' 即 test="str != null"表达式内是否追加 and str != ''
        properties.setProperty("IDENTITY", "MYSQL");
        mapperScannerConfigurer.setProperties(properties);

        return mapperScannerConfigurer;
    }
}

上述的MapperScannerConfigurer 皆为tk.mybatis.spring.mapper包下,而不不是官方Mybatis包下的。

配置介绍

上面的配置文件配置中,我们配置了一些基础配置,一般情况下即可满足使用。下面我们简绍具体配置以及用途:

mappers

在 4.0 以前这是一个非常重要的参数,当时只有通过 自定义mappers 配置过的接口才能真正调用,由于很多人不注意看文档,通用 Mapper 90% 的问题都出在这个参数上。

4.0 之后,增加了一个 @RegisterMapper 注解,通用 Mapper 中提供的所有接口都有这个注解,有了该注解后,通用 Mapper 会自动解析所有的接口,如果父接口(递归向上找到的最顶层)存在标记该注解的接口,就会自动注册上。因此 4.0 后使用通用 Mapper 提供的方法时,不需要在配置这个参数。

当你自己自定义通用接口时,建议加上@RegisterMapper注解,否则就要配置 mappers 参数。

IDENTITY:

取回主键的方式,可以配置的值如下中所列的数据库类型:

  • DB2:
  • MYSQL
  • SQLSERVER
  • CLOUDSCAPE
  • DERBY
  • HSQLDB
  • SYBASE
  • DB2_MF
  • INFORMIX

只有定义这个后再实体类中,主键属性上的@GeneratedValue(strategy = GenerationType.IDENTITY) 定义才有效。

catalog、schema

数据库的catalogschema,如果设置该值,查询的时候表名会带catalog或者schema设置的前缀。其中catalog优先级高于schema

notEmpty

接受一个布朗值,insertSelectiveupdateByPrimaryKeySelective 中,是否判断字符串类型 !=''

…通用Mapper中还有很多配置,但是用处不大,这里不做过多简绍了。

使用

我们完成上述操作后我们可以在Service中注入Mapper,就可以使用TkMapper的快速功能了。

常规使用

select(T record): 根据实体中的属性值进行精准查询,它接受一个相关类型的实体类,返回相关类型实体集合。

注意: 查询操作不建议大伙使用这种方式,查询推荐使用条件查询方式,后面简绍。

@Resource
private AdminMapper adminMapper;
...
Admin admin = new Admin();
// 查询 sex = 0
admin.setSex = 0;
List<Admin> admins = adminMapper.select(admin);

selectByPrimaryKey(Object key): 根据实体中的主键进行精准查询,它接受一个主键相关类型的值,返回一个实体类。

@Resource
private AdminMapper adminMapper;
...
Admin admin = adminMapper.selectByPrimaryKey(12);

selectAll() :查询出所有的实体类 数据。

@Resource
private AdminMapper adminMapper;
...
List<Admin> admins =  adminMapper.selectAll();

insert(T record) : 向实体类数据库中插入一条数据。它接受一个相关类型的实体类,返回一个int,表示成功插入个数。

@Resource
private AdminMapper adminMapper;
...
Admin admin = new Admin();
adminMapper.insert(admin)

insertList(List<T> recordList) : 向实体类数据库中插多条数据。它接受一个相关类型的List集合,返回一个int,表示成功插入个数。

@Resource
private AdminMapper adminMapper;
...
List<Admin> admins = Arrays.asList(new Admin(),new Admin(),new Admin())
adminMapper.insert(admins)

insertSelective(T record): 向实体类数据库中插入一条数据,其中null的属性不会保存,会使用数据库默认值。它接受一个相关类型的实体类,返回一个int,表示成功插入个数。

@Resource
private AdminMapper adminMapper;
...
Admin admin = new Admin();
adminMapper.insertSelective(admin)

updateByPrimaryKey(T record) : 根据主键更新指定实体数据库实体,null值也会被更新。

@Resource
private AdminMapper adminMapper;
...
Admin admin = new Admin();
// 更新id为12的实体
admin.setId = 12;
admin.setName= Zssaer;
adminMapper.updateByPrimaryKey(admin)

delete(T record) : 根据相关属性,删除一个具体的实体类。返回一个int,表示成功删除个数。

注意:其中的删除的实体类属性必须完全一致。大部分时间我们不会常用这个方法来删除,我们使用条件查询方式删除,这个后面简绍。

@Resource
private AdminMapper adminMapper;
...
Admin admin = new Admin();

admin.setId = 12;
admin.setName= Zssaer;
adminMapper.delete(admin)

deleteByPrimaryKey(Object key):根据实体类主键删除相关实体类。

@Resource
private AdminMapper adminMapper;
...
adminMapper.deleteByPrimaryKey(12);

existsWithPrimaryKey(Object key) : 通过实体类主键来判断是否存在该主键实体,返回一个boolean值。

@Resource
private AdminMapper adminMapper;
...
if(adminMapper.existsWithPrimaryKey(12)){
    ...
}

条件查询使用

上面介绍了部分常规使用的方法,它们包含了部分简易的CRUD操作,使用它们可以快速进行一些CRUD操作。但是对于一些复杂的查询、删除等操作,我们通常会使用条件查询使用的方法。

Example

通用 Mapper 中的 Example 方法有两大类定义,一个参数和两个参数的,例如下面两个:

List<T> selectByExample(Object example);

int updateByExampleSelective(@Param("record") T record, @Param("example") Object example);

Example 类 ,它接受一个实体类的类,表示该Example 为 该实体类的Example 类。

Example  exa = new Example (Admin.class);

有了对于实体类的Example 类后就可以进行相关的条件查询了。

下面是一些例子:

Example example = new Example(Country.class);
// 设置为 for update
example.setForUpdate(true);
// 创建 条件查询为 id > 100 并且id < 151
example.createCriteria().andGreaterThan("id", 100).andLessThan("id",151);
// 或者 id <41
example.or().andLessThan("id", 41);

其中createCriteria方法相当于where,上述Example 类相当于拼接出了一段Sql语句:

SELECT id,countryname,countrycode FROM country WHERE ( id > 100 and id < 151 ) or ( id < 41 ) ORDER BY id desc FOR UPDATE 

如果未指定排序方式的话,自动为 主键 id 倒序。

最后将其创建好的Example类放置到selectByExample中即可实现自定义查询 :

List<Admin> admins =  adminMapper.selectByExample(example);

我们也可以将其创建好的Example类放置到deleteByCondition中即可实现自定义查询后删除 :

adminMapper.deleteByCondition(example);

Condition

Condition是通用Mapper中的另一个条件查询类,它的使用方法和Example一样,它的存在只是为了避免Example带来的歧义。它的自定义查询实现为selectByCondition,自定义删除实现为deleteByCondition

动态SQl查询

动态SQL查询,在实际项目中非常常见,比如实现聚合查询功能。它根据其请求来进行动态拼接SQL查询:

public Result list(ArticleReq req) {
        PageHelper.startPage(req.getPage(), req.getSize());

        Integer id = req.getId();
        Integer type = req.getArticleType();
        String title = req.getArticleTitle();
        Integer status = req.getStatus();
        String lang = req.getLang();

        Condition condition = new Condition(Article.class);
        Example.Criteria criteria = condition.createCriteria();

        if (id != null) {
            criteria.andEqualTo("id", id);
        }
        if (type != null) {
            criteria.andEqualTo("articleType", type);
        }
        if (!StringUtils.isEmpty(title)) {
            criteria.andLike("articleTitle", '%' + title + '%');
        }
        if (status != null) {
            criteria.andEqualTo("status", status);
        }
        if (!StringUtils.isEmpty(lang)) {
            criteria.andEqualTo("lang", lang);
        }
    	List<Article> articleList = articleService.findByCondition(condition);
    	...
}

排序

Example example = new Example(Country.class);
example.orderBy("id").desc().orderBy("countryname").orderBy("countrycode").asc();
List<Country> countries = mapper.selectByExample(example);

结果SQL为:

SELECT id,countryname,countrycode FROM country order by id DESC,countryname,countrycode ASC 

当然还可以手动写排序:

example.setOrderByClause("is_top desc,news_time desc");

去重

Example example = new Example(Country.class);
//设置 distinct
example.setDistinct(true);

条件方法总结

总结一下其中不管是Example还是Condition,他们的主要使用方法:

方法 说明
example.setOrderByClause(“字段名 ASC”) 添加升序排列条件,DESC为降序
example.setDistinct(false) 去除重复,boolean型,true为选择不重复的记录。
criteria.andXxxIsNull 添加字段xxx为null的条件
criteria.andXxxIsNotNull 添加字段xxx不为null的条件
criteria.andXxxEqualTo(value) 添加xxx字段等于value条件
criteria.andXxxNotEqualTo(value) 添加xxx字段不等于value条件
criteria.andXxxGreaterThan(value) 添加xxx字段大于value条件
criteria.andXxxGreaterThanOrEqualTo(value) 添加xxx字段大于等于value条件
criteria.andXxxLessThan(value) 添加xxx字段小于value条件
criteria.andXxxLessThanOrEqualTo(value) 添加xxx字段小于等于value条件
criteria.andXxxIn(List<?>) 添加xxx字段值在List<?>条件
criteria.andXxxNotIn(List<?>) 添加xxx字段值不在List<?>条件
criteria.andXxxNotIn(List<?>) 添加xxx字段值不在List<?>条件