06 Mybatis
resultMap
resultMap 中的标签如下:
result
字段映射
constructor
实例化类时,注入结果到构造方法中
association
关联一个对象
collection
关联多个对象
result
result 用于字段映射,给定一个实体类:
@Data
public class User {
private String id;
private String username;
private String password;
private String address;
private String email;
}假若数据库别名为 uid,则查询语句:
<resultMap id="getUserByIdMap" type="User">
<result property="id" column="uid"></result>
</resultMap>
<select id="getUsers" resultType="User">
SELECT
u.id as uid,
u.username,
u.password,
u.address,
u.email
FROM USER u
WHERE u.id=#{id}
</select>constructor
使用 constructor 元素将结果注入构造方法里,先给 User 添加构造方法:
其中 column 代表数据库字段名称或者别名;name 则是构造方法中的参数名称;javaType 指定了参数的类型。
这样之后,结果集中的 id 和 username 属性都会发生变化:
association
给 User 类添加角色:
查询用户时查询角色:
最后展示信息:
collection
嵌套结果映射
如果用户有多个角色,可以使用 collection 来返回角色列表:
修改 resultMap 返回结果:
最后展示信息:
嵌套 Select 查询
假设这么一张表:
1
系统管理
0
1001
用户管理
/user
1
1002
角色管理
/role
1
1003
单位管理
/employer
1
2
系统管理
0
2001
系统管理
/system/monitor
2
2002
数据管理
/data/monitor
2
需要将表的数据分成两级菜单返回,而不是平铺展示,新建 Menu 实体类:
这里使用 childMenu 来展示二级菜单。然后来写 SQL 语句,如果没有传入 parent_id 字段,则查询一级菜单:
然后定义返回结果:
collection 元素含义:
property="childMenu"对应的是菜单中的子级菜单列表ofType="Menu"对应返回数据的类型select="getMenus"指定了 SELECT 语句的 idcolumn="{parent_id=id}"则是参数的表达式
整体含义为:通过 getMenus 这个 SELECT 语句来获取一级菜单中的 childMenu 属性结果;在上面的 SELECT 语句中,需要传递一个 parent_id 参数;这个参数的值就是一级菜单中的 id。
最终结果展示信息:
自动填充关联对象
在 Mybatis 解析返回值的时候,第一步是获取返回值类型,拿到 Class 对象,然后获取构造器,设置可访问并返回实例,然后又把它包装成 MetaObject 对象。
从数据库 rs 中拿到结果之后,会调用 MetaObject.setValue(String name, Object value) 来填充对象。在这过程中,它会以 . 来分隔这个 name 属性。如果 name 属性中包含 . 符号,就找到 . 符号之前的属性名称,把它当做一个实体对象来。
还是以上面获取用户角色为例,在该 SQL 语句:
join
left join(左联接):返回包括左表中的所有记录和右表中联结字段相等的记录
right join(右联接):返回包括右表中的所有记录和左表中联结字段相等的记录
inner join(内连接):只返回两个表中联结字段相等的行
outer join(全连接):只要左表和右表其中一个表中存在匹配,则返回
假若有两张表 A 和 B,分别如下:
1
a20050111
2
a20050112
3
a20050113
4
a20050114
5
a20050115
1
2006032401
2
2006032402
3
2006032403
4
2006032404
8
2006032408
left join
结果如下:
1
a20050111
1
2006032401
2
a20050112
2
2006032402
3
a20050113
3
2006032403
4
a20050114
4
2006032404
5
a20050115
NULL
NULL
left join 是以 A 表的记录为基础的,A 可以看成左表,B 可以看成右表,left join 是以左表为准的。换句话说,左表 A 的记录将会全部表示出来,而右表 B 只会显示符合搜索条件的记录(例子中为: A.aID = B.bID),B 表记录不足的地方均为 NULL。
right join
结果如下:
1
a20050111
1
2006032401
2
a20050112
2
2006032402
3
a20050113
3
2006032403
4
a20050114
4
2006032404
NULL
NULL
8
2006032408
right join 和 left join 的结果刚好相反,这次是以右表 B 为基础的,A 表不足的地方用 NULL 填充。
inner join
结果如下:
1
a20050111
1
2006032401
2
a20050112
2
2006032402
3
a20050113
3
2006032403
4
a20050114
4
2006032404
inner join 产生左表(A)和右表(B)的交集。
outer join
结果如下:
1
a20050111
1
2006032401
2
a20050112
2
2006032402
3
a20050113
3
2006032403
4
a20050114
4
2006032404
5
a20050115
NULL
NULL
NULL
NULL
8
2006032408
outer join 产生左表(A)和右表(B)的并集。
实际用例
已之前写过的 Netty 聊天系统为例,用户查询好友添加请求和自己的好友列表,查询语句如下:
通用 Mapper
通用 Mapper 就是为了解决单表增删改查,基于 Mybatis 的插件。开发人员不需要编写 SQL,不需要在 DAO 中增加方法,只要写好实体类,就能支持相应的增删改查方法。
Select
方法:List<T> select(T record);
说明:根据实体中的属性值进行查询,查询条件使用等号。
方法:T selectByPrimaryKey(Object key);
说明:根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号。
方法:List<T> selectAll();
说明:查询全部结果,select(null) 方法能达到同样的效果。
方法:T selectOne(T record);
说明:根据实体中的属性进行查询,只能有一个返回值,有多个结果是抛出异常,查询条件使用等号。
方法:int selectCount(T record);
说明:根据实体中的属性查询总数,查询条件使用等号。
Insert
方法:int insert(T record);
说明:保存一个实体,null 的属性也会保存,不会使用数据库默认值。
方法:int insertSelective(T record);
说明:保存一个实体,null 的属性不会保存,会使用数据库默认值。
Update
方法:int updateByPrimaryKey(T record);
说明:根据主键更新实体全部字段,null 值会被更新。
方法:int updateByPrimaryKeySelective(T record);
说明:根据主键更新属性不为 null 的值。
Delete
方法:int delete(T record); 说明:根据实体属性作为条件进行删除,查询条件使用等号。
方法:int deleteByPrimaryKey(Object key); 说明:根据主键字段进行删除,方法参数必须包含完整的主键属性。
Example
方法:List<T> selectByExample(Object example); 说明:根据 Example 条件进行查询 重点:这个查询支持通过 Example 类指定查询列,通过 selectProperties 方法指定查询列。
方法:int selectCountByExample(Object example); 说明:根据 Example 条件进行查询总数。
方法:int updateByExample(@Param("record") T record, @Param("example") Object example); 说明:根据 Example 条件更新实体 record 包含的全部属性,null 值会被更新。
方法:int updateByExampleSelective(@Param("record") T record, @Param("example") Object example); 说明:根据 Example 条件更新实体 record 包含的不是 null 的属性值。
方法:int deleteByExample(Object example); 说明:根据 Example 条件删除数据。
Example 的使用
查询
动态 SQL
排序
去重
设置查询列
Example.builder 方式
Weekend
使用 6.2 和 6.3 中的 Example 时,需要自己输入属性名,例如 "countryname",假设输入错误,或者数据库有变化,这里很可能就会出错,因此基于 Java 8 的方法引用是一种更安全的用法,如果你使用 Java 8,你可以试试 Weekend。
在代码中的 Country::getCountryname 就是方法引用,通过该方法可以自动转换对应的列名。
Tips
打印 SQL 日志
格式:logging.level.Mapper 类的包=debug。
插入数据并返回自增 ID
修改 Mapper 文件:
示例:
in() 查询排序
查询结果按 in() 中的 ID 排序:
示例:
find_in_set()
举例:有个文章表里面有个 type 字段,它存储的是文章类型,有 1 头条、2 推荐、3 热点、4 图文等等。现在有篇文章他既是头条,又是热点,还是图文,type 中以 1,3,4 的格式存储。那我们如何用 sql 查找所有 type 中有 4 的图文类型的文章呢?
这里就需要用到 mysql 的 Find_IN_SET() 函数:
FIND_IN_SET(str,strlist) 参数说明:
str 要查询的字符串
strlist 字段名,参数以
,分隔,如 (1,2,6,8)
查询字段 strlist 中包含 str 的结果,返回结果为 null 或记录:
示例:
Find_IN_SET 和 like 的区别:like 是广泛的模糊匹配,字符串中没有分隔符,Find_IN_SET 是精确匹配,字段值以英文,分隔,Find_IN_SET 查询的结果要小于 like 查询的结果。
批量更新多个不同值的字段
参考文章: MyBatis 通用 Mapper4
字符串替换
通过任何一列从表中 select 数据时:
多行插入
批量查询
CONCAT('%', #{tag}, '%') 可以用 bind 实现:
时间比较
批量更新
更新单条记录:
更新多条记录的同一个字段为同一个值:
更新多条记录为多个字段为不同的值:
比较普通的写法,是通过循环,依次执行 update 语句:
这样做一条记录 update 一次,性能比较差,容易造成阻塞。
MySQL 没有提供直接的方法来实现批量更新,但可以使用 case when 语法来实现这个功能:
这条 sql 的意思是,如果 id 为 1,则 name 的值为 name1,title 的值为 New Title1;依此类推。
查询结果排序
单字段排序:
多个字段排序:
Mybatis Plus
apply sql 注入风险
condition 用法
分组查询
最后更新于
这有帮助吗?