tkmybatis 是对底层 sql 进行了抽象封装,不需要考虑 sql 怎么写,只需要按照逻辑思维,遵循 tkmybatis 的语法即可实现数据库操作。
本文适合对springboot项目结构有一定了解的读者。
本文的项目基础是一个demo项目(多模块的)。
1. 配置
1、添加 tkmybatis 的依赖
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
2、创建 dao 层的 mapper 接口,每个接口都要继承 tk.mybatis.mapper.common.Mapper 接口。此接口的形式为 Mapper<T>,带了个泛型,此泛型一般指的是对应的 pojo 或者 domain。比如:
public interface HouseMapper extends Mapper<House> {
}
3、在 Application 启动类上添加 mapper 扫描注解,表示要扫描到 dao 层的 mapper 接口。
比如本项目的mapper接口就统一放在 com.dgh.dao 下面,用 @MapperScan(basePackages = "包名") 来指定。
附注:在项目启动时,springboot 会自动扫描 Application 启动类所在的当前目录以及下一级目录,一般 Application 启动类都放在根目录,所以在单一项目下,只要是在 Java 类上添加了注解,都能够默认被 springboot 扫描到,并被添加到 springboot 的容器中,一般不需要特别用 @ComponentScan 去指定 springboot 要扫描哪些目录。
本文之所以特地用 @ComponentScan 去指定了 service 层和 controller 层的目录,是因为本文的项目是一个多模块项目,service 层和 controller 层各自都是一个独立的模块,与 Application 启动类不在同一目录下。
@SpringBootApplication
@MapperScan(basePackages = "com.dgh.dao")
@ComponentScan(basePackages = "com.dgh.service")
@ComponentScan(basePackages = "com.dgh.controller")
public class ControllerApplication {
public static void main(String[] args) {
SpringApplication.run(ControllerApplication.class, args);
}
}
4、tkmybatis 具体的使用是在 service 层,service 层又分为接口和接口实现类,具体就在接口实现类里面。
5、其它的代码、配置与普通的springboot项目一样。
2. tkmybatis的结构
下图是 tkmybatis 的结构图,圈中的是 Mapper 接口,是最底层的,也就意味着它继承了所有的功能。因此才有了 “配置” 中第2步继承Mapper<T>接口。
它定义各种 sql 语句的基础,通过灵活的拼接,查询参数的设置,可以满足开发者各种的数据库查询要求。
既然如此,那我猜 tkmybatis 的底层是动态代理实现的。
3. 增删查改——基础方法
3.1 删除
DeleteByPrimaryKeyMapper接口有一个方法 deleteByPrimaryKey,顾名思义,以表的主键字段作为条件判断,进行删除。
delete from table where id = ?
int deleteByPrimaryKey(Object var1);
DeleteMapper接口有一个方法 delete,参数就是数据库表对应的Java实体类,参数实体中哪些字段不为null,就会被作为删除sql语句的条件字段,且条件关系是 and,而不是 or。
delete from table where 字段1 = ?and 字段2 = ?
int delete(T var1);
注意:在定义实体类时,每个成员变量的类型都应该是Java类,不能是基本类型,比如整型,应该用 Integer,而不是 int。如果用 int 的话,在没有给 int 成员变量赋值时,ava 会默认给它赋值为 0,由于 0 不是 null,所以会被 tkmybatis 当做是删除条件。
比如前段发送的删除请求,参数实体如下,height字段为 null,本来意思是不把 height 字段作为删除的条件字段,如果 Java 实体类的 height 字段类型定义为 Integer,那一切正常,但是如果定义为 int 类型,那么在实例化对象并赋值时,默认赋值 height = 0,sql语句将变成 delete from house where age = 69 and height = 0; 这样就违背了本意,容易造成错误的删除后果。
3.2 插入
InsertMapper 接口有一个方法 insert 方法,往数据库表插入一条记录,表有多少个字段,在 tkmybatis 生成的 insert sql 语句中就有多少个字段。
insert into table (所有字段) values (?,?...,?)
int insert(T var1);
InsertSelectiveMapper 接口有一个方法 insertSelective,实体类参数中不为 null 的字段就会被考虑,在 tkmybatis 生成的 insert sql 语句中只会包含这些不为 null 的字段。
insert into table (部分字段) values (?,..?)
int insertSelective(T var1);
3.3 查询
SelectMapper 接口有一个方法 select,参数实体类中哪些字段不为 null,就会被作为 select sql 语句中的条件字段,且字段之间的关系是 and。
select 所有字段 from table where 字段1 = ? and 字段2 = ?
List<T> select(T var1);
SelectOneMapper 接口有一个方法 selectOne,与 select 方法一样,只是返回结果只能为空或者一个,如果有多个,则抛出异常。
同上
T selectOne(T var1);
SelectCountMapper 接口有一个方法 selectCount,查询满足条件的记录有多少条。
select count(id) from table where 字段1 = ? and 字段2 = ?
int selectCount(T var1);
SelectAllMapper 接口有一个方法 selectAll,查询全表所有记录。
select 所有字段 from table;
List<T> selectAll();
SelectByPrimaryKeyMapper 接口有一个方法 selectByPrimaryKey,根据主键进行查询。
select 所有字段 from table where 主键字段 = ?
T selectByPrimaryKey(Object var1);
ExistsWithPrimaryKeyMapper 接口有一个方法 existsWithPrimaryKey,根据主键查询某条记录是否存在。
select case when count(主键字段) > 0 then 1 else 0 end as result from table where 主键字段 = ?
boolean existsWithPrimaryKey(Object var1);
3.4 修改
UpdateByPrimaryKeyMapper 接口有一个方法 updateByPrimaryKey,根据主键字段准确地修改某一条记录。
update table set 所有字段
int updateByPrimaryKey(T var1);
UpdateByPrimaryKeySelectiveMapper 接口有一个方法 updateByPrimaryKeySelective,根据主键字段准确地修改某一条记录的部分字段(实体类参数的不为 null 的字段)。
update table set 部分字段
int updateByPrimaryKeySelective(T var1);
4 批量增删查改——基础方法
4.1 批量插入
这两个功能有一个要求,那就是操作的数据库表必须有一个自增主键,因为它要求主键必须要有一个默认值,否则就抛出异常。
这两个接口是集成到 MySqlMapper 接口中了,所以 dao 层的 mapper 接口还要继承 MySqlMapper 接口才能使用批量插入功能。
public interface HouseMapper extends Mapper<House>, MySqlMapper<House> {
}
InsertListMapper 接口有一个方法 insertList,批量插入。
insert into table (所有字段,除了自增主键) values (?,..,?), ...,(?,...,?)
int insertList(List<? extends T> var1);
InsertUseGeneratedKeysMapper 接口有一个方法 insertUserGeneratedKeys,单个插入。
int insertUseGeneratedKeys(T var1);
4.2 批量查询与批量删除
SelectByIdsMapper 接口有一个方法 selectByIds,按照多个主键 id 值进行查询,但是方法的参数是 String,那么主键id之间用逗号隔开就行。
select 所有字段 from table where id in (id值1,id值2,...,id值n)
List<T> selectByIds(String var1);
DeleteByIdsMapper 接口有一个方法 deleteByIds,按照多个主键 id 值进行删除。
delete from table where id in (id值1,id值2,...,id值n)
int deleteByIds(String var1);
5 自定义查询条件
5.1 删改查方法
图中接口都有一个共同点,就是需要 Example 对象作为方法的参数,Example 对象包含了我们各种自定义的查询条件,相当于 sql 语句中 where 部分的条件。
每个接口都包含了一个方法,供我们调用。总结如下表:
方法 | 功能描述 |
int deleteByExample(Object var1); | 一般参数就是Example对象,按照条件进行删除,返回删除的记录数 |
List<T> selectByExample(Object var1); | 一般参数就是Example对象,按照条件进行查询,返回查询结果集 |
int selectCountByExample(Object var1); | 一般参数就是Example对象,按照条件进行查询,返回符合查询条件的记录数 |
T selectOneByExample(Object var1); | 一般参数就是Example对象,按照条件进行查询,结果只能为空或者一个,否则抛出异常 |
int updateByExample(@Param("record") T var1, @Param("example") Object var2); | 第一个参数是新记录,第二参数是example对象,用新记录替换掉符合条件的旧记录 |
int updateByExampleSelective(@Param("record") T var1, @Param("example") Object var2); | 功能同上,只是可以仅替换掉记录的部分字段 |
List<T> selectByRowBounds(T var1, RowBounds var2); | 第一个参数是查询条件,第二个参数是 RowBounds 对象(包含2个属性,offset 和 limit),offset 表示起始行,limit 表示需要的记录数;方法的功能是按照查询条件进行查询,再按照 offset 和 limit 在结果集中取相应数量的记录。 |
List<T> selectByExampleAndRowBounds(Object var1, RowBounds var2); | 第一个参数是 Example 对象,第二个参数是 RowBounds 对象,先根据 example 条件进行查询,再按照 offset 和 limit 取相应数量的记录。 |
List<T> selectByConditionAndRowBounds(Object var1, RowBounds var2); | 同上 |
5.2 Example 条件设置
先创建 Example 对象,再创建 Example.criteria 对象,借助这两个对象,可以灵活地设置各种条件。Example 对象可以理解为 sql 语句层次的设置, 而 Example.criteria 对象可以理解为 sql 语句中的一个单一的条件表达式设置。
原理上可以理解为:一个 example 包含了若干个 criteria ,每个 criteria 就是 sql 语句中条件部分的一个括号部分(没有嵌套),比如 (id = 5),criteria 包含了一个方法 void setAndOr(String andOr),它的意思相当于在括号前面加上 and 还是 or,比如执行了方法 setAndOr("and"),那么 criteria 相当于 and (id = 5),而 example 就把这些 criteria 拼凑起了,比如 example 包含了 2 个 criteria,分别是 (id = 5) 和 and (name = "张三"),那么此 example 的效果就是 (id = 5) and (name = "张三")。
Example example = new Example(House.class);
Example.Criteria criteria = example.createCriteria();
具体可以怎么设置呢?criteria 包含的方法总结如下表:
方法 | 功能描述 |
andAllEqualTo(Object param) | 所有字段都作为 where 后面的判断条件,判断值就是参数实体对象 |
andBetween(String property, Object value1, Object value2) | where property between value1 and value2 ,范围条件,包含两端 |
andEqualTo(Object param) | 实体对象中不为 null 的字段作为 where 后面的判断条件 |
andEqualTo(String property, Object value) | 某一个<字段,值>作为 where 后面的判等条件 |
andGreaterThan(String property, Object value) | 大于条件,某个字段大于某个值 |
andGreaterThanOrEqualTo(String property, Object value) | 大于等于条件,某个字段大于等于某个值 |
andIn(String property, Iterable values) | where property in (),范围条件 |
andIsNotNull(String property) | where property is not null,判空条件 |
andIsNull(String property) | where property is null,判空条件 |
andLessThan(String property, Object value) | 小于条件 |
andLessThanOrEqualTo(String property, Object value) | 小于等于条件 |
andLike(String property, String value) | where property like value,注意 value 应该是一个匹配表达式 |
andNotBetween(String property, Object value1, Object value2) | 范围条件,不包含两端 |
andNotEqualTo(String property, Object value) | 要求字段不等于某个值 |
andNotIn(String property, Iterable values) | 要求字段不在某个范围内 |
andNotLike(String property, String value) | 模糊查询,要求不 like。 |
void setAndOr(String andOr) | 上面已经介绍过了 |
上表的方法都是“与”关系,即 and。 同样的,有相应的 “或” 关系,即 or。比如 orAllEqualTo、orGreaterThan 等等,都是将方法名中的 “and” 换成 “or”。
那 criteria 能否嵌套呢?能否有更方便的使用方式呢?回答:能,有。如下表:
Example.Criteria orCondition(String condition, Object value) | condition参数是个sql字符串,可以拼接进 sql 语句的,value 是一个值,会拼接到 condition 后面的,此方法的最终结果为 or condition + value。 |
Example.Criteria orCondition(String condition) | 功能同上,只是更加直接,一个字符串搞定,只是字符串参数可以写成类似这种 “id = ”+getId(),"( id = "+getId()+")",一样灵活,此方法的最终结果为 or condition。 |
Example.Criteria andCondition(String condition) | 不再赘述 |
Example.Criteria andCondition(String condition, Object value) | 不再赘述 |
Example 类包含的方法总结如下表:
方法 | 功能描述 |
void setDistinct(boolean distinct) | 查询的结果是否要进行唯一性过滤,true表示过滤,false(默认)表示不过滤。 |
void setOrderByClause(String orderByClause) | 查询结果按照某个,或者某些字段进行升序,降序。比如参数是 “id asc” 就是按照 id 进行升序,“id asc,age desc” 就是按照 id 升序,在 id 相等的情况下,按照 age 降序。 |
Example selectProperties(String... properties) | 当利用 example 进行查询时,此方法可以设置想要查询的字段是哪些,比如我只需要查询一张表的部分字段。 |
Example.OrderBy orderBy(String property) | 排序,与 setOrderByClause 功能一样,只是用法不同,比如 orderBy("id").asc() 表示按照 id 升序, orderBy("id").asc().orderBy("age").desc() 表示按照 id 升序,再按照 age 降序 |
Example.Criteria or() | 创建一个 or 方式的、空的criteria,具体的 criteria 内容可以稍后设置。 |
void or(Example.Criteria criteria) | 直接以 or 的方式添加一个现有的 criteria |
Example.Criteria and() | 同上,不过是 and 方式 |
void and(Example.Criteria criteria) | 同上,and 方式 |
Example 类还有其它的一些方法,本人觉得都有些鸡肋或者重复,也就不再介绍了。