以前大家在用mybatis的时候比较喜欢用mapper(xml),确实写sql比较方便。但是开发过程中,在xml与代码之间跳来跳去实在太繁琐了,而且添加或者删除字段的成本也比较大。
后面mybatis plus出现了,单表操作确实比较方便,但是遇到多表联查的时候,就比较难受了。
下面看看mybatis官方提供的注解实现方式。
先写一个简单的查询,似乎比较轻松
@Mapper
public interface UserDao {
@Select("select id, name, age, address from tb_user_info")
List listUserInfo();
}
那么,如何实现动态sql呢,仅仅依靠@Select注解是无法实现的,还需要借助mybatis提供的另一个注解@SelectProvider
@Mapper
public interface UserDao {
@SelectProvider(type = UserInfoProvider.class, method = "listUserInfoByDto")
List listUserInfoByDto(@Param("userInfoDto") UserInfoDto userInfoDto);
}
创建一个辅助类, 方法名必须和注解中的method属性相对应, 字节码对象需要赋值给注解中的type属性,返回值就是要执行的sql。
public class UserInfoProvider {
public String listUserInfoByDto(UserInfoDto userInfoDto) {
String sql = "select id, name, age, address from tb_user_info";
if(userInfoDto.getAddress() != null) {
sql += " where address = #{userInfoDto.address}";
}
if(userInfoDto.getName() != null) {
if(userInfoDto.getAddress() != null) {
sql += " and name = #{userInfoDto.name}";
} else {
sql += " where name = #{userInfoDto.name}";
}
}
return sql;
}
}
虽然实现了功能,但是sql拼装太原始,而且一不小心就会出错。mybatis官方提供了一个工具类,我们可以借助他的工具类重新实现这个功能。
public String listUserInfoByDtoPro(UserInfoDto userInfoDto) {
return new SQL(){
{
SELECT("id, name, age, address");
FROM("tb_user_info");
if(userInfoDto.getAddress() != null) {
WHERE("address = #{userInfoDto.address}");
}
if(userInfoDto.getName() != null) {
WHERE("name = #{userInfoDto.name}");
}
}
}.toString();
}
巧妙地通过了双大括号的写法来初始化SQL工具类,并且屏蔽了sql拼接的过程。(双大括号写法,也是实现匿名类的一种方式,可以参考我的另一篇博客https://blog.csdn.net/qq_37855749/article/details/115913722)
@Mapper
public interface UserDao {
@SelectProvider(type = UserInfoProvider.class, method = "listUserWithRole")
List listUserWithRole(@Param("userInfoDto") UserInfoDto userInfoDto);
}
public class UserInfoProvider {
public String listUserWithRole(UserInfoDto userInfoDto) {
return new SQL(){
{
SELECT("tui.id, tui.name, tui.age, tui.address, tr.role_name roleName");
FROM("tb_user_info tui");
LEFT_OUTER_JOIN("tb_user_role tur on tui.id = tur.user_id");
LEFT_OUTER_JOIN("tb_role tr on tur.role_id = tr.id");
//也可以用下面这种写法
//LEFT_OUTER_JOIN("tb_user_role tur on tui.id = tur.user_id", "tb_role tr on tur.role_id = tr.id");
if(userInfoDto.getAddress() != null) {
WHERE("address = #{userInfoDto.address}");
}
if(userInfoDto.getName() != null) {
WHERE("name = #{userInfoDto.name}");
}
}
}.toString();
}
}
回过头来看注解,mybatis提供了四种Provider
@SelectProvider
@InsertProvider
@UpdateProvider
@DeleteProvider
但是我们在编码的过程中发现,其实主要是看UserInfoProvider 这个类中method的返回值,理论上和注解名没有太多关系,那为什么还要区分四个注解呢?
其实是和sql语句执行的返回值有关
//如果是SelectProvider也可以,但是返回值为null
@UpdateProvider(type = UserInfoProvider.class, method = "updateUser")
Integer updateUser(@Param("userInfo") UserInfo userInfo);
更新、删除、修改的返回值都是数值型(数据库中涉及的数据条数),而查询类sql执行的返回值可能是java对象,或者集合之类的,与其他类型不兼容。
比如上述的语句明显是个更新,我如果强行换成DeleteProvider,也能得到正确的结果,因为返回值都是数值类型的。
@DeleteProvider(type = listUserWithRole.class, method = "updateUser")
Integer updateUser(@Param("userInfo") UserInfo userInfo);
但肯定是不推荐的,语义化的注解让开发人员一看就知道当前的sql是什么类型的,直观且具体。
Provider需要的是原始的sql,我们一般会定义辅助类提供所需要的sql。
不一定要用官方的SQL拼装工具类,比如我们公司有人就是自己造了个轮子。
但我觉得SQL工具类还是很好用的,并且看了看里面的源码,明天再写一篇简单的源码分析。(已写:https://blog.csdn.net/qq_37855749/article/details/115932800)
另外要注意,Provider工具类(例子中的UserInfoProvider)可以有很多类,不一定要放在一个文件里。但是方法名不能相同,不要用重载,mybatis是根据方法名定位的。
如果需要测试的源码:
https://gitee.com/DayCloud/dayrain-demo/tree/master/mybatis-provider