MybatisPlus Advanced Guide

MybatisPlus Advanced Guide

preface

At present, most companies in the market will use MybatisPlus to develop, but when encountering some complex sql, we will still choose xml to write sql. This paper intends to mine the advanced functions of MybatisPlus to realize relatively complex sql and customization of sql.

Attachment: please refer to the official documents for the use of the basic part of MybatisPlus MybatisPlus documentation

Update and query of some entity fields

Query some entity field values
// first kind
select(String... sqlSelect) 
// Class II
select(Predicate<TableFieldInfo> predicate)
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate) 

The first method directly queries the corresponding fields

select("id", "name", "age") // General query
select(PatientDO::getPatientName, PatientDO::getPinyin) // lambda query

The second method is to filter the query fields (except the primary key). If the input parameter does not contain class, the entity attribute in the wrapper needs to have a value before calling! The last repeated call of these two methods shall prevail

select(i -> i.getProperty().startsWith("marco"))
Update some entity field values
// first kind
set(String column, Object val)
set(boolean condition, String column, Object val)
// Class II
setSql(boolean condition, String sql)

The first type is to set and update entity fields one by one

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

The second type is similar to writing sql directly, but the value needs to be written dead, which is not recommended

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

The above two methods are applicable to blank a field

Custom SQL

When some scenarios require customized sql for all methods, mybatis plus provides the following two methods
For example, I now need to define a common BaseMapper method, and other mappers inherit BaseMapper

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

Annotation method

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

Xml mode

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

Chain CURD (for understanding only)

The previous queries are to create a condition constructor, and then query with the condition constructor as the parameter through mapper or service. For example, LambdaQueryChainWrapper combines the previous two sentences into one to complete the conditional query.

Chain query

// Chain query general
QueryChainWrapper<T> query();
// Chained query lambda. Note: Kotlin is not supported
LambdaQueryChainWrapper<T> lambdaQuery(); 

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

Chain update

// Chain change normal
UpdateChainWrapper<T> update();
// Chain change lambda. Note: Kotlin is not supported 
LambdaUpdateChainWrapper<T> lambdaUpdate();

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

Note: to create LambdaQueryChainWrapper object, you need to pass mapper object. Another disadvantage is that chained query only has list() and one() methods to obtain data. All returned are entity objects or collections of entity objects, and there is no corresponding Map() query method.

@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 extends general Sql

Step 1: define the method of general sql statement

/**
 * Delete all
 */
public class DeleteAll extends AbstractMethod {

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        /* Execute SQL, dynamic SQL reference class SqlMethod */
        String sql = "delete from " + tableInfo.getTableName();
        /* mapper Consistent interface method name */
        String method = "deleteAll";
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addDeleteMappedStatement(mapperClass, method, sqlSource);
    }
}

Step 2: define and inject MySQL injector

/**
 * Custom Sql injection
 *
 * @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);
        //Add custom method
        methodList.add(new DeleteAll());
        /**
         * The following 3 are built-in options
         * The first 2 support field filtering functions
         */
        // Example: do not specify the fields filled in by 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();
    }
}

Step 3: customize the method interface of sql

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

    /**
     * There are 4 method s defined below, of which 3 are built-in options
     */
    int insertBatchSomeColumn(List<T> entityList);

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

    int deleteByIdWithFill(T entity);

    /**
     * The following is self defined
     */
    int deleteAll();
}

Injecting complex queries using @ SelectProvider

Define query criteria PatientSelectProvider
Single parameter

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

Reference Provider in Mapper

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

Note: when the parameters passed in mapper are decorated with @ param annotation, the Map object must be used in the xxprovider class to receive parameters.

Multiple parameters

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();
}

Note: the sql writing method at this time does not need to use and to connect in the spliced sql. The where source code has been spliced into the where method as follows

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

Functional sql programming

func
func(Consumer<Children> consumer)
func(boolean condition, Consumer<Children> consumer)

func method (it is mainly convenient to use different methods to continuously chain when if... else is called)

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

Splice OR

eq("id",1).or().eq("name","marco")  
// --Equivalent to - > id = 1 or name = 'Marco'

Note: actively calling or means that the next method is not connected with and! (if or is not called, and connection is used by default.)

Nested OR

or(i -> i.eq("name", "marco").ne("status", "1"))
// --Equivalent to - > 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"))
// --Equivalent to - > and (name = 'Marco' and status < > '1')
apply
apply(String applySql, Object... params)
apply(boolean condition, String applySql, Object... params)

matters needing attention:
This method can be used for the params of the dynamic input parameters of the database function, which corresponds to the {index} part inside the previous applySql. In this way, there will be no sql injection risk. On the contrary, there will be ~ (the last method is recommended)

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

matters needing attention:
It can only be called once. The last call shall prevail. There is a risk of sql injection. Please use it with caution

// Common methods are as follows
last("limit 1")

FieldFill annotation

For the fields to be added and filled, add

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

For the fields to be added or filled, add

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

Linked table query


It is recommended to use xml to query linked tables. Of course, you can also use the following methods to query linked tables
Annotation method

//Multi table joint query by condition 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);
}

External extension method of SelectProvider
First, define the SelectProvider entity

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();
    }

Next, reference the Provider in Mapper

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

Optimistic lock plug-in

Implementation method of optimistic lock: when taking out the record, when obtaining the current version update, when carrying this version to execute the update,
set version = newVersion where version = oldVersion
If the version is wrong, the update fails. The optimistic lock configuration needs two steps. Remember two steps

1. Plug in configuration
First, inject the optimisticlocker interceptor

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

2. Annotation entity field @ Version must be!

@Version
private Integer version;

Special note:

Only int,Integer,long,Long,Date,Timestamp,LocalDateTime integer types are supported
newVersion = oldVersion + 1 newVersion will be written back to entity, only supported
updateById(id) and update(entity, wrapper) methods
Under the update(entity, wrapper) method, the wrapper cannot be reused!!!

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");
}
// --Equivalent to - > Update TBL_ user set name = 'update',version = 3 where id = 100 and version = 2

Tags: Mybatis

Posted by khujo56 on Wed, 11 May 2022 13:58:52 +0300