现在越来越流行基于 SpringBoot
开发 Web
应用,其中利用 Mybatis
作为数据库 CRUD
操作已成为主流。楼主以 MySQL
为例,总结了九大类使用 Mybatis
操作数据库 SQL
小技巧分享给大家。
分页查询
预置 sql
查询字段
一对多级联查询
一对一级联查询
foreach
搭配 in
查询
利用if
标签拼装动态 where
条件
利用 choose
和 otherwise
组合标签拼装查询条件
动态绑定查询参数:_parameter
利用 set
配合 if
标签,动态设置数据库字段更新值
利用 limit
设置每页 offset
偏移量和每页 size
大小。
select * from sys_user u
LEFT JOIN sys_user_site s ON u.user_id = s.user_id
LEFT JOIN sys_dept d ON d.dept_id = s.dept_id
LEFT JOIN sys_emailinfo e ON u.user_id = e.userid AND e.MAIN_FLAG = 'Y'
limit #{offset}, #{limit}
id,title,content,original_img,is_user_edit,province_id,status,porder
查询 select
语句引用 columns:
利用 mybatis
的 collection
标签,可以在每次查询文章主体同时通过 queryparaminstancelist
级联查询出关联表数据。
queryparaminstancelist
的 sql
语句
利用 mybatis
的 association
标签,一对一查询关联表数据。
MsArticlecount
实体对象的属性值可以从 上面的 select
后的 sql
字段进行匹配映射获取。
利用 foreach
遍历 array
集合的参数,拼成 in
查询条件
#{item}
select r.*, (select d.org_name from sys_dept d where d.dept_id = r.dept_id) deptName from sys_role r
r.wid = #{wid}
and r.`role_name` like concat('%',#{roleName},'%')
and r.`status` = #{status}
order by r.${sidx} ${order}
order by r.role_id asc
_parameter
_parameter
参数的含义
“当
Mapper
、association
、collection
指定只有一个参数时进行查询时,可以使用_parameter
,它就代表了这个参数。
另外,当使用 Mapper
指定方法使用 @Param
的话,会使用指定的参数值代替。
SELECT id, grp_no grpNo, province_id provinceId, status FROM tj_group_province
...
and grp_no = #{_parameter}
UPDATE cms_label
label_group_id = #{labelGroupId},
dept_id = #{deptId},
is_recommend = #{recommend},
WHERE label_id = #{labelId}
如果 Mybatis-Plus
是扳手,那 Mybatis Generator
就是生产扳手的工厂。
MyBatis
是一种操作数据库的 ORM
框架,提供一种 Mapper
类,支持让你用 java
代码进行增删改查的数据库操作,省去了每次都要手写 sql
语句的麻烦。但是有一个前提,你得先在 xml
中写好 sql
语句,也是很麻烦的。
Mybatis
是一个半 ORM
框架;Hibernate
是一个全 ORM
框架。Mybatis
需要自己编写 sql
。
Mybatis
直接编写原生 sql
,灵活度高,可以严格控制 sql
执行性能;Hibernate
的自动生成 hql
,因为更好的封装型,开发效率提高的同时,sql
语句的调优比较麻烦。
Hibernate
的 hql
数据库移植性比 Mybatis
更好,Hibernate
的底层对 hql
进行了处理,对于数据库的兼容性更好,
Mybatis
直接写的原生 sql
都是与数据库相关,不同数据库 sql
不同,这时就需要多套 sql
映射文件。
Hibernate
在级联删除的时候效率低;数据量大, 表多的时候,基于关系操作会变得复杂。
Mybatis
和 Hibernate
都可以使用第三方缓存,而 Hibernate
相比 Mybatis
有更好的二级缓存机制。
Mybatis-Plus
的存在就是为了稍稍弥补 Mybatis
的不足。
在我们使用 Mybatis
时会发现,每当要写一个业务逻辑的时候都要在 DAO
层写一个方法,再对应一个 SQL
,即使是简单的条件查询、即使仅仅改变了一个条件都要在 DAO
层新增一个方法,针对这个问题,Mybatis-Plus
就提供了一个很好的解决方案:lambda
表达式,它可以让我们避免许多重复性的工作。
想想 Mybatis 官网提供的 CRUD
例子吧,基本上 xml
配置占据了绝大部分。而用 Lambda
表达式写的 CRUD
代码非常简洁,真正做到零配置,不需要在 xml
或用注解(@Select
)写大量原生 SQL
代码。
LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getSex, 0L)
.like(UserEntity::getUserName, "dun");
List userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like全包含关键字查询::" + u.getUserName()));
Java
中的 lambda
表达式实质上是一个匿名方法,但该方法并非独立执行,而是用于实现由函数式接口定义的唯一抽象方法。
使用 lambda
表达式时,会创建实现了函数式接口的一个匿名类实例,如 Java8
中的线程 Runnable
类实现了函数接口:@FunctionalInterface
。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
平常我们执行一个 Thread
线程:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("xxxx");
}
}).start();
如果用 lambda
会非常简洁,一行代码搞定。
new Thread(()-> System.out.println("xxx")).start();
所以在某些场景下使用 lambda
表达式真的能减少 java
中一些冗长的代码,增加代码的优雅性。
出现的第一个入参 boolean condition
表示该条件是否加入最后生成的 sql
中,例如:query.like(StringUtils.isNotBlank(name), Entity::getName, name) .eq(age!=null && age >= 0, Entity::getAge, age)
代码块内的多个方法均为从上往下补全个别 boolean
类型的入参,默认为 true
出现的泛型 Param
均为 Wrapper
的子类实例(均具有 AbstractWrapper
的所有方法)
方法在入参中出现的 R
为泛型,在普通 wrapper
中是 String
,在 LambdaWrapper
中是函数(例:Entity::getId
,Entity
为实体类,getId
为字段id
的getMethod
)
方法入参中的 R column
均表示数据库字段,当 R
具体类型为 String
时则为数据库字段名(字段名是数据库关键字的自己用转义符包裹!)!而不是实体类数据字段名!!!,另当 R
具体类型为 SFunction
时项目 runtime
不支持 eclipse
自家的编译器!
使用普通 wrapper
,入参为 Map
和 List
的均以 json
形式表现!
使用中如果入参的 Map
或者 List
为空,则不会加入最后生成的 sql
中!
警告:
不支持以及不赞成在 RPC
调用中把 Wrapper
进行传输。
“
Wrapper
很重 传输Wrapper
可以类比为你的controller
用map
接收值(开发一时爽,维护火葬场) 正确的RPC
调用姿势是写一个DTO
进行传输,被调用方再根据DTO
执行相应的操作 我们拒绝接受任何关于RPC
传输Wrapper
报错相关的issue
甚至pr
。
AbstractWrapper
内部结构从上图,我们了解到 AbstractWrapper
的实际上实现了五大接口:
SQL
片段函数接口:ISqlSegment
@FunctionalInterface
public interface ISqlSegment extends Serializable {
/**
* SQL 片段
*/
String getSqlSegment();
}
比较值接口 Compare
,如 等值 eq
、不等于:ne
、大于 gt
、大于等于:ge
、小于 lt
、小于等于 le
、between
、模糊查询:like
等等
嵌套接口 Nested
,如 and
、or
拼接接口 Join
,如 or
、exists
函数接口 Func
,如 in
查询、groupby
分组、having
、order by
排序等
常用的 where
条件表达式 eq、like、in、ne、gt、ge、lt、le
。
@Override
public Children in(boolean condition, R column, Collection> coll) {
return doIt(condition, () -> columnToString(column), IN, inExpression(coll));
}
public Children notIn(boolean condition, R column, Collection> coll)
public Children inSql(boolean condition, R column, String inValue)
public Children notInSql(boolean condition, R column, String inValue)
public Children groupBy(boolean condition, R... columns)
public Children orderBy(boolean condition, boolean isAsc, R... columns)
public Children eq(boolean condition, R column, Object val)
public Children ne(boolean condition, R column, Object val)
public Children gt(boolean condition, R column, Object val)
public Children ge(boolean condition, R column, Object val)
public Children lt(boolean condition, R column, Object val)
public Children le(boolean condition, R column, Object val)
...
/**
* 普通查询条件
*
* @param condition 是否执行
* @param column 属性
* @param sqlKeyword SQL 关键词
* @param val 条件值
*/
protected Children addCondition(boolean condition, R column, SqlKeyword sqlKeyword, Object val) {
return doIt(condition, () -> columnToString(column), sqlKeyword, () -> formatSql("{0}", val));
}
lambda
这么好用的秘诀在于 SQL 片段函数接口:ISqlSegment,我们在 doIt
方法找到 ISqlSegment
对象参数,翻开 ISqlSegment
源码,发现它真实的庐山真面目,原来是基于 Java 8
的函数接口 @FunctionalInterface
实现!
ISqlSegment
就是对 where
中的每个条件片段进行组装。
/**
* 对sql片段进行组装
*
* @param condition 是否执行
* @param sqlSegments sql片段数组
* @return children
*/
protected Children doIt(boolean condition, ISqlSegment... sqlSegments) {
if (condition) {
expression.add(sqlSegments);
}
return typedThis;
}
@FunctionalInterface
public interface ISqlSegment extends Serializable {
/**
* SQL 片段
*/
String getSqlSegment();
}
从 MergeSegments
类中,我们找到 getSqlSegment
方法,其中代码片段
sqlSegment = normal.getSqlSegment() + groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment()
这段代码表明,一条完整的 where
条件 SQL
语句,最终由 normal SQL
片段,groupBy SQL
片段,having SQL
片段,orderBy SQL
片段拼接而成。
@Getter
@SuppressWarnings("serial")
public class MergeSegments implements ISqlSegment {
private final NormalSegmentList normal = new NormalSegmentList();
private final GroupBySegmentList groupBy = new GroupBySegmentList();
private final HavingSegmentList having = new HavingSegmentList();
private final OrderBySegmentList orderBy = new OrderBySegmentList();
@Getter(AccessLevel.NONE)
private String sqlSegment = StringPool.EMPTY;
@Getter(AccessLevel.NONE)
private boolean cacheSqlSegment = true;
public void add(ISqlSegment... iSqlSegments) {
List list = Arrays.asList(iSqlSegments);
ISqlSegment firstSqlSegment = list.get(0);
if (MatchSegment.ORDER_BY.match(firstSqlSegment)) {
orderBy.addAll(list);
} else if (MatchSegment.GROUP_BY.match(firstSqlSegment)) {
groupBy.addAll(list);
} else if (MatchSegment.HAVING.match(firstSqlSegment)) {
having.addAll(list);
} else {
normal.addAll(list);
}
cacheSqlSegment = false;
}
@Override
public String getSqlSegment() {
if (cacheSqlSegment) {
return sqlSegment;
}
cacheSqlSegment = true;
if (normal.isEmpty()) {
if (!groupBy.isEmpty() || !orderBy.isEmpty()) {
sqlSegment = groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment();
}
} else {
sqlSegment = normal.getSqlSegment() + groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment();
}
return sqlSegment;
}
}
org.springframework.boot
spring-boot-starter-web
com.github.pagehelper
pagehelper-spring-boot-starter
org.springframework.boot
spring-boot-starter
com.baomidou
mybatis-plus-boot-starter
org.projectlombok
lombok
mysql
mysql-connector-java
org.springframework.boot
spring-boot-starter-test
test
junit
junit
test
org.apache.commons
commons-lang3
com.google.code.gson
gson
Base 实体
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@SuperBuilder(toBuilder = true)
@Data
public class BaseEntity {
@TableField(value = "created_tm", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdTm;
@TableField(value = "created_by", fill = FieldFill.INSERT)
private String createdBy;
@TableField(value = "modified_tm", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime modifiedTm;
@TableField(value = "modified_by", fill = FieldFill.INSERT_UPDATE)
private String modifiedBy;
}
用户账号实体:UserEntity
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@SuperBuilder(toBuilder = true)
@Data
@TableName("sys_user")
public class UserEntity extends BaseEntity{
private Long userId;
private String userName;
private Integer sex;
private Integer age;
private String mobile;
}
Mapper 操作类
List selectUsers();
UserEntity selectByIdOnXml(long userId);
@Results(id = "userResult", value = {
@Result(property = "user_id", column = "userId", id = true),
@Result(property = "userName", column = "user_name"),
@Result(property = "sex", column = "sex"),
@Result(property = "mobile", column = "mobile"),
@Result(property = "age", column = "age")
})
@Select("select * from sys_user where user_id = #{id}")
UserEntity selectByIdOnSelectAnnotation(@Param("id") long id);
@SelectProvider(type = UserSqlProvider.class, method = "selectById")
@ResultMap("BaseResultMap")
UserEntity selectByIdOnSelectProviderAnnotation(long id);
@Select("select * from sys_user where user_id = #{id} and user_name=#{userName}")
@ResultMap("BaseResultMap")
UserEntity selectByIdOnParamAnnotation(@Param("id") long id, @Param("userName") String uerName);
Mapper 表映射文件
订单实体:OrderEntity
@Data
@TableName("sys_user_card")
public class CardEntity {
private Long cardId;
private String cardCode;
private Long userId;
}
Mapper 操作类
@Mapper
public interface OrderMapper extends BaseMapper {
}
Mapper 表映射文件
身份证实体:CardEntity
@Data
@TableName("biz_order")
public class OrderEntity {
private Long orderId;
private String orderName;
private Integer userId;
private Date createdTm;
private Integer price;
}
Mapper 操作类
@Mapper
public interface CardMapper extends BaseMapper {
}
Mapper 表映射文件
LambdaQueryWrapper 四种不同的 lambda 构造方法
方式一 使用 QueryWrapper
的成员方法方法 lambda
构建 LambdaQueryWrapper
LambdaQueryWrapper lambda = new QueryWrapper().lambda();
方式二 直接 new
出 LambdaQueryWrapper
LambdaQueryWrapper lambda = new LambdaQueryWrapper<>();
方式三 使用 Wrappers
的静态方法 lambdaQuery
构建 LambdaQueryWrapper
推荐
LambdaQueryWrapper lambda = Wrappers.lambdaQuery();
方式四:链式查询
List users = new LambdaQueryChainWrapper(userMapper)
.like(User::getName, "雨").ge(User::getAge, 20).list();
笔者推荐使用 Wrappers
的静态方法 lambdaQuery
构建 LambdaQueryWrapper
条件构造器。
Debug 调试
为了 Debug
调试方便,需要在 application.yml
启动文件开启 Mybatis-Plus SQL
执行语句全栈打印:
#mybatis
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
1 等值查询:eq
@Test
public void testLambdaQueryOfEq() {
//eq查询
//相当于 select * from sys_user where user_id = 1
LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getUserId, 1L);
UserEntity user = userMapper.selectOne(lqw);
System.out.println("eq查询::" + user.getUserName());
}
eq
查询等价于原生 sql
的等值查询。
select * from sys_user where user_id = 1
2 范围查询 :in
@Test
public void testLambdaQueryOfIn() {
List ids = Arrays.asList(1L, 2L);
LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
lqw.in(UserEntity::getUserId, ids);
List userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("in查询::" + u.getUserName()));
}
in
查询等价于原生 sql
的 in
查询
select * from sys_user where user_id in (1,2)
3 通配符模糊查询:like
@Test
public void testLambdaQueryOfLikeAll() {
LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getSex, 0L)
.like(UserEntity::getUserName, "dun");
List userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like全包含关键字查询::" + u.getUserName()));
}
like
查询等价于原生 sql
的 like
全通配符模糊查询。
select * from sys_user where sex = 0 and user_name like '%dun%'
4 右通配符模糊查询:likeRight
@Test
public void testLambdaQueryOfLikeRight() {
LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getSex, 0L)
.likeRight(UserEntity::getUserName, "dun");
List userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like Right含关键字查询::" + u.getUserName()));
}
likeRight
查询相当于原生 sql
的 like
右通配符模糊查询。
select * from sys_user where sex = 0 and user_name like 'dun%'
5 左通配符模糊查询:likeLeft
@Test
public void testLambdaQueryOfLikeLeft() {
LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getSex, 0L)
.likeLeft(UserEntity::getUserName, "zung");
List userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like Left含关键字查询::" + u.getUserName()));
}
likeLeft
查询相当于原生 sql
的 like
左通配符模糊查询。
select * from sys_user where sex = 0 and user_name like '%zung'
6 条件判断查询
条件判断查询类似于 Mybatis
的 if
标签,第一个入参 boolean condition
表示该条件是否加入最后生成的 sql
中。
@Test
public void testLambdaQueryOfBoolCondition() {
UserEntity condition = UserEntity.builder()
.sex(1)
.build();
//eq 或 like 条件判断查询
LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
lqw.eq(condition.getSex() != null, UserEntity::getSex, 0L)
// 满足 bool 判断,是否进查询按字段 userName 查询
.like(condition.getUserName() != null, UserEntity::getUserName, "dun");
List userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like查询::" + u.getUserName()));
}
7 利用 or 和 and 构建复杂的查询条件
@Test
public void testLambdaQueryOfOr_And() {
LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getSex, 0L)
.and(wrapper->wrapper.eq(UserEntity::getUserName,"dunzung")
.or().ge(UserEntity::getAge, 50));
List userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like查询::" + u.getUserName()));
}
上面实例查询等价于原生 sql
查询:
select * from sys_user where sex = 0 and (use_name = 'dunzung' or age >=50)
8 善于利用分页利器 PageHelpler
@Test
public void testLambdaPage() {
//PageHelper分页查询
//相当于 select * from sys_user limit 0,2
int pageNumber = 0;
int pageSize = 2;
PageHelper.startPage(pageNumber + 1, pageSize);
LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(UserEntity::getAge)
.orderByDesc(UserEntity::getMobile);
List userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("page分页查询::" + u.getUserName()));
}
上面实例查询等价于原生 sql
分页查询:
select * from sys_user order by age desc,mobile desc limit 0,2
另外,Mybatis-Plus
自带分页组件,BaseMapper
接口提供两种分页方法来实现物理分页。
第一个返回实体对象允许 null
第二个人返回 map
对象多用于在指定放回字段时使用,避免为指定字段 null
值出现
IPage selectPage(IPage page, @Param("ew") Wrapper queryWrapper);
IPage
注意,Mybatis-Plus
自带分页组件时,需要配置 PaginationInterceptor
分页插件。
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
@Test
public void testLambdaUpdate() {
LambdaUpdateWrapper luw = Wrappers.lambdaUpdate();
luw.set(UserEntity::getUserName, "dunzung01")
.set(UserEntity::getSex, 1);
luw.eq(UserEntity::getUserId, 1);
userMapper.update(null, luw);
}
Association
标签适用于表和表之间存在一对一的关联关系,如用户和身份证存在一个人只会有一个身份证号,反过来也成立。
@Test
public void testOnAssociationTag() {
List userList = userMapper.selectUsers();
userList.forEach(u -> System.out.println(u.getUserName()));
}
XML配置
Collection
标签适用于表和表之间存在一对多的关联关系,如用户和订单存在一个人可以购买多个物品,产生多个购物订单。
@Test
public void testOnCollectionTag() {
List userList = userMapper.selectUsers();
userList.forEach(u -> System.out.println(u.getUserName()));
}
XML配置
注意 Association
和 Collection
先后关系,在编写 ResultMap
时,association
在前,collection
标签在后。
如果二者颠倒顺序会提示错误。
MetaObjectHandler
元对象字段填充器的填充原理是直接给 entity
的属性设置值,提供默认方法的策略均为:
“如果属性有值则不覆盖,如果填充值为
null
则不填充,字段必须声明TableField
注解,属性fill
选择对应策略,该声明告知Mybatis-Plus
需要预留注入SQL
字段。TableField
注解则是指定该属性在对应情况下必有值,如果无值则入库会是null
。
自定义填充处理器 MyMetaObjectHandler
在 Spring Boot
中需要声明 @Component
或 @Bean
注入,要想根据注解 FieldFill.xxx
,如:
@TableField(value = "created_tm", fill = FieldFill.INSERT)
private LocalDateTime createdTm;
@TableField(value = "modified_tm", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime modifiedTm;
和字段名以及字段类型来区分必须使用父类的 setInsertFieldValByName
或者 setUpdateFieldValByName
方法,不需要根据任何来区分可以使用父类的 setFieldValByName
方法 。
/**
* 属性值填充 Handler
*
* @author 猿芯
* @since 2021/3/30
*/
@Component
public class FillMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.setInsertFieldValByName("createdTm", LocalDateTime.now(), metaObject);
this.setInsertFieldValByName("createdBy", MvcContextHolder.getUserName(), metaObject);
this.setFieldValByName("modifiedTm", LocalDateTime.now(), metaObject);
this.setFieldValByName("modifiedBy", MvcContextHolder.getUserName(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setUpdateFieldValByName("modifiedTm", LocalDateTime.now(), metaObject);
this.setUpdateFieldValByName("modifiedBy", MvcContextHolder.getUserName(), metaObject);
}
}
一般 FieldFill.INSERT
用父类的 setInsertFieldValByName
方法更新创建属性(创建人、创建时间)值;FieldFill.INSERT_UPDATE
用父类的 setUpdateFieldValByName
方法更新修改属性(修改人、修改时间)值;如果想让诸如 FieldFill.INSERT
或 FieldFill.INSERT_UPDATE
任何时候不起作用,用父类的 setFieldValByName
设置属性(创建人、创建时间、修改人、修改时间)值即可。
使用 Wrapper
自定义 SQL
需要 mybatis-plus
版本 >= 3.0.7
,param
参数名要么叫 ew
,要么加上注解 @Param(Constants.WRAPPER)
,使用 ${ew.customSqlSegment}
不支持 Wrapper
内的 entity
生成 where
语句。
注解方式
@Select("select * from mysql_data ${ew.customSqlSegment}")
List getAll(@Param(Constants.WRAPPER) Wrapper wrapper);
XML配置
List getAll(Wrapper ew);
通过上面丰富的举例详解以及剖析 lambda
底层实现原理,想必大家会问:” lambda
表达式似乎只支持单表操作?”
据我对 Mybatis-Plus
官网的了解,目前确实是这样。依笔者实际运用经验来看,其实程序员大部分开发的功能基本上都是针对单表操作的,Lambda
表达式的优势在于帮助开发者减少在 XML
编写大量重复的 CRUD
代码,这点是非常的 nice
的。很显然,Lambda
表达式对于提高程序员的开发效率是不言而喻的,我想这点也是我作为程序员非常喜欢 Mybatis-Plus
的一个重要原因。
但是,如果涉及对于多表之间的关联查询,lambda
表达式就显得力不从心了,因为 Mybatis-Plus
并没有提供类似于 join
查询的条件构造器。
lambda
表达式优点:
单表操作,代码非常简洁,真正做到零配置,如不需要在 xml
或用注解(@Select
)写大量原生 SQL
代码
并行计算
预测代表未来的编程趋势
lambda
表达式缺点:
单表操作,对于多表关联查询支持不好
调试困难
底层逻辑复杂
Mybatis-Plus
推出的 lambda
表达式致力于构建复杂的 where
查询构造器式并不是银弹,它可以解决你实际项目中 80%
的开发效率问题,但是针对一些复杂的大 SQL
查询条件支持的并不好,例如一些复杂的 SQL
报表统计查询。
所以,笔者推荐单表操作用 lambda
表达式,查询推荐用 LambdaQueryWrapper
,更新用 LambdaUpdateWrapper
;多表操作还是老老实实写一些原生 SQL
,至于原生 SQL
写在哪里?Mapper
文件或者基于注解,如 @Select
都是可以的。
https://mp.baomidou.com/guide/wrapper.html
https://www.jianshu.com/p/613a6118e2e0
https://blog.csdn.net/Solitude_w/article/details/108235236
https://blog.csdn.net/weixin_44472810/article/details/105649901
https://blog.csdn.net/weixin_44495678/article/details/106748214
作者简介:猿芯,一枚简单的北漂程序员。喜欢用简单的文字记录工作与生活中的点点滴滴,愿与你一起分享程序员灵魂深处真正的内心独白。我的微信号:WooolaDunzung,公众号【猿芯】输入 1024 ,有份面试惊喜送给你哦。
< END >
【猿芯】
微信扫描二维码,关注我的公众号。
原创不易,莫要干想,如果觉得有点用的话,动动你的发财之手,一键三连击:分享、点赞、在看,你们的鼓励是我写作更多优质文章的最强动力 ^_^