MybatisPlus Advanced Guide(MybatisPlus高级功能指南)

本文深入探讨MyBatis Plus的高级应用技巧,包括部分实体字段的更新与查询、自定义SQL、链式CRUD操作、BaseMapper扩展、@SelectProvider注入复杂查询、函数式SQL编程、连表查询及乐观锁插件等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MybatisPlus Advanced Guide

前言

目前,市面大多数公司都会使用MybatisPlus开发,但是遇到一些比较复杂的sql,大家依然会选择xml的方式来书写sql,本文意挖掘MybatisPlus的高级功能,实现相对比较复杂的sql以及sql的自定义化。

附:MybatisPlus基础部分的使用方式可以参考官方文档 MybatisPlus文档



部分实体字段更新及查询
查询部分实体字段值
// 第一类
select(String... sqlSelect) 
// 第二类
select(Predicate<TableFieldInfo> predicate)
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate) 

第一类方法直接查询对应的字段

select("id", "name", "age") // 普通查询
select(PatientDO::getPatientName, PatientDO::getPinyin) // lambda查询

第二类方法为:过滤查询字段(主键除外),入参不包含 class 的调用前需要wrapper内的entity属性有值!这两类方法重复调用以最后一次为准

select(i -> i.getProperty().startsWith("marco"))
更新部分实体字段值
// 第一类
set(String column, Object val)
set(boolean condition, String column, Object val)
// 第二类
setSql(boolean condition, String sql)

第一类逐个设置更新实体字段

default void updatePatientName(PatientDO patientDO) {
    update(patientDO, new LambdaUpdateWrapper<PatientDO>()
    .set(PatientDO::getPatientName, patientDO.getPatientName()));
};

第二类类似于直接写sql,不过值需要写死,不太推荐

setSql(true, "patientName = 'marco'")

以上这两种方式适用于置空某一字段


自定义SQL

当有些场景需要自定义sql,供所有方法使用时,mybatis-plus提供了如下两种方式
譬如说我现在需要定义一个公用的BaseMapper方法,其他的Mapper去继承BaseMapper

baseMapper.getAll(Wrappers.<PatientDO>lambdaQuery().eq(PatientDO::isDeleted, 1));

注解方式

@Select("select * from patient ${ew.customSqlSegment}")
List<PatientDO> getAll(@Param(Constants.WRAPPER) Wrapper wrapper);

Xml方式

<select id="getAll" resultType="MysqlData">
	SELECT * FROM entity ${ew.customSqlSegment}
</select>

链式CURD(仅供了解

前面的查询都是创建一个条件构造器,再通过mapper或service以条件构造器为参数进行查询。例如LambdaQueryChainWrapper将前面的两句合二为一,一句来完成带条件的查询。

链式查询

// 链式查询 普通
QueryChainWrapper<T> query();
// 链式查询 lambda 式。注意:不支持 Kotlin
LambdaQueryChainWrapper<T> lambdaQuery(); 

// 示例:
query().eq("column", value).one();
lambdaQuery().eq(Entity::getId, value).list();

链式更新

// 链式更改 普通
UpdateChainWrapper<T> update();
// 链式更改 lambda 式。注意:不支持 Kotlin 
LambdaUpdateChainWrapper<T> lambdaUpdate();

// 示例:
update().eq("column", value).remove();
lambdaUpdate().eq(Entity::getId, value).update(entity);

注意:创建LambdaQueryChainWrapper对象需要传递mapper对象,还有个缺点链式查询只有list()和one()两个方法获取数据,返回的都是实体对象或实体对象的集合,没有对应的Map()查询方法。

@Autowired
private CustomerMapper customerMapper;
 
public void selectByCustomerName() {
    List<CustomerDO> list = new LambdaQueryChainWrapper<CustomerDO>(customerMapper)
            .select(CustomerDO::getId, Customer::getName)
            .like(CustomerDO::getName, "marco").list();

BaseMapper拓展通用Sql

第一步:定义通用sql语句的method

/**
 * 删除全部
 */
public class DeleteAll extends AbstractMethod {

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        /* 执行 SQL ,动态 SQL 参考类 SqlMethod */
        String sql = "delete from " + tableInfo.getTableName();
        /* mapper 接口方法名一致 */
        String method = "deleteAll";
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addDeleteMappedStatement(mapperClass, method, sqlSource);
    }
}

第二步:定义并注入MySqlInjector

/**
 * 自定义Sql注入
 *
 * @author nieqiurong 2018/8/11 20:23.
 */
public class MySqlInjector extends DefaultSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        //增加自定义方法
        methodList.add(new DeleteAll());
        /**
         * 以下 3 个为内置选装件
         * 头 2 个支持字段筛选函数
         */
        // 例: 不要指定了 update 填充的字段
        methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE));
        methodList.add(new AlwaysUpdateSomeColumnById());
        methodList.add(new LogicDeleteByIdWithFill());
        return methodList;
    }
}
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MySqlInjector sqlInjector() {
        return new MySqlInjector();
    }
}

第三步:自定义sql的方法接口

public interface MyBaseMapper<T> extends BaseMapper<T> {

    /**
     * 以下定义的 4个 method 其中 3 个是内置的选装件
     */
    int insertBatchSomeColumn(List<T> entityList);

    int alwaysUpdateSomeColumnById(@Param(Constants.ENTITY) T entity);

    int deleteByIdWithFill(T entity);

    /**
     * 以下为自己自定义
     */
    int deleteAll();
}

使用@SelectProvider注入复杂查询

定义查询条件PatientSelectProvider
单个参数

public class PatientSelectProvider {
    public String selectById(String patientId) {
        return new SQL(){{
            SELECT("patient_id, patient_name");
            FROM("patient");
            WHERE("patient_id ="+ patientId);
        }}.toString();
    }
}

在Mapper中引用Provider

@SelectProvider(value = PatientSelectProvider.class, method = "selectById")
PatientDO selectByPatientId();

注意:当mapper中传入的参数是使用@param 注解修饰,在xxxProvider类中必须使用Map对象接收参数。

多个参数

public String selectUserById(Map<String, Object> para){
    return new SQL(){{
        SELECT("patient_id, patient_name");
        FROM("patient");
        WHERE("patient_id=" + para.get("patientId"));
        if(StringUtils.isNotBlank((String)para.get("patientName"))){
            WHERE("patientName=" + para.get("patientName"));
        }
    }}.toString();
}

注意:此时的sql写法在拼接sql中不需要在使用 and 进行连接 ,在where 方法中已拼入where 源码如下

private static final String AND = ") \nAND (";
private static final String OR = ") \nOR (";

函数式sql编程
func
func(Consumer<Children> consumer)
func(boolean condition, Consumer<Children> consumer)

func 方法(主要方便在出现if…else下调用不同方法能不断链)

func(i -> if(true) {i.eq("id", 1)} else {i.ne("id", 1)})
or
// 拼接
or()
or(boolean condition)
// 嵌套
or(Consumer<Param> consumer)
or(boolean condition, Consumer<Param> consumer)

拼接 OR

eq("id",1).or().eq("name","marco")  
// -- 等同于 ->  id = 1 or name = 'marco'

注意事项:主动调用or表示紧接着下一个方法不是用and连接!(不调用or则默认为使用and连接)

嵌套 OR

or(i -> i.eq("name", "marco").ne("status", "1"))
// -- 等同于 ->  or (name = 'marco' and status <> '1')
and
and(Consumer<Param> consumer)
and(boolean condition, Consumer<Param> consumer)
and(i -> i.eq("name", "marco").ne("status", "1"))
// -- 等同于 -> and (name = 'marco' and status <> '1')
apply
apply(String applySql, Object... params)
apply(boolean condition, String applySql, Object... params)

注意事项:
该方法可用于数据库函数 动态入参的params对应前面applySql内部的{index}部分,这样是不会有sql注入风险的,反之会有~(建议使用最后一种方式)

apply("id = 1")
// -- 等同于 -> id = 1
apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
// -- 等同于 -> date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08")
// -- 等同于 -> date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
last
last(String lastSql)
last(boolean condition, String lastSql)

注意事项:
只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用

// 常用方式如下
last("limit 1")

FieldFill注解

针对于需要新增填充的字段,在实体的字段上加上

@TableField(fill = FieldFill.INSERT)
private Date createTime

针对于需要新增或者填充的字段,在实体的字段上加上

@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime

连表查询

在这里插入图片描述
建议使用xml来连表查询,当然也可以使用以下几种方式进行连表查询
注解方式

//多表联合查询 按条件orderID
@Select("select t1.*,t2.user_name,t2.nick_name from orders t1 LEFT JOIN  users t2 ON t1.user_id =t2.id WHERE t1.user_id= #{id}")
	List<Map<String,Object>> orderUserList(Page<Map<String,Object>> page, String id);
}

SelectProvider外部拓展方式
首先还是定义SelectProvider实体

public String selectByPatientId(Long patientId) {
        return new SQL(){{
            SELECT("p.patient_id, p.patient_name, pc.mobile");
            FROM("patient p");
            LEFT_OUTER_JOIN("patient_contacts pc on p.patient_id = pc.patient_id");
            WHERE("p.patient_id = " + patientId);
        }}.toString();
    }

接着还是在Mapper中引用Provider

@SelectProvider(value = PatientSelectProvider.class, method = "selectByPatientId")
PatientDO selectByPatientId(Long patientId);

乐观锁插件

乐观锁实现方式: 取出记录时,获取当前version 更新时,带上这个version 执行更新时,
set version = newVersion where version = oldVersion
如果version不对,就更新失败 乐观锁配置需要2步 记得两步

1、插件配置
首先注入乐观所拦截器OptimisticLockerInterceptor

@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
    return new OptimisticLockerInterceptor();
}

2、注解实体字段 @Version 必须要!

@Version
private Integer version;

特别说明:

支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime 整数类型下
newVersion = oldVersion + 1 newVersion 会回写到 entity 中仅支持
updateById(id) 与 update(entity, wrapper) 方法
在 update(entity, wrapper)方法下, wrapper 不能复用!!!

int id = 100;
int version = 2;

User u = new User();
u.setId(id);
u.setVersion(version);
...
u.setXXX(xxx);

if(userService.updateById(u)){
    System.out.println("Update successfully");
}else{
    System.out.println("Update failed due to modified by others");
}
// -- 等同于 -> update tbl_user set name = 'update',version = 3 where id = 100 and version = 2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值