Herramienta estática de interfaz de servicio SQL personalizada del constructor de condiciones de función principal MybatisPlus

Configuración de anotación común de inicio rápido de MybatisPlus_Blog de Soft Worker-Blog CSDN

2. Funciones principales

Los casos anteriores eran todos CRUD simples con id como condición. Algunas declaraciones SQL condicionales complejas requieren funciones más avanzadas.

2.1.Constructor condicional

Además de las nuevas incorporaciones, las declaraciones SQL para modificación, eliminación y consulta deben especificar las condiciones de dónde. idPor lo tanto, además de utilizar métodos relacionados como condiciones, los métodos relacionados proporcionados en BaseMapper wheretambién admiten wherecondiciones más complejas.

El parámetro Wrapperes una clase abstracta de construcción condicional . Tiene muchas implementaciones predeterminadas y la relación de herencia se muestra en la figura:

WrapperLas subclases de AbstractWrapperproporcionan todos los constructores condicionales contenidos en donde :

QueryWrapper extiende un método de selección basado en AbstractWrapper , permitiéndole especificar campos de consulta :

UpdateWrapper extiende un método set basado en AbstractWrapper , permitiéndole especificar la parte SET en SQL :

A continuación, echemos un vistazo a cómo implementar Wrapperconsultas complejas.

2.1.1.Contenedor de consultas

Ya sea que esté modificando, eliminando o consultando, puede usar QueryWrapper para crear condiciones de consulta. Veamos algunos ejemplos:

Consulta : Consultaa las personas cuyos nombres tengan odepósitos mayores o iguales a 1.000 yuanes . El código se muestra a continuación:

@Test
void testQueryWrapper() {
    // 1.构建查询条件 where name like "%o%" AND balance >= 1000
    QueryWrapper<User> wrapper = new QueryWrapper<User>()
            .select("id", "username", "info", "balance")
            .like("username", "o")
            .ge("balance", 1000);
    // 2.查询数据
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

Actualización : actualice el saldo del usuario llamado jack a 2000. El código es el siguiente:

@Test
void testUpdateByQueryWrapper() {
    // 1.构建查询条件 where name = "Jack"
    QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "Jack");
    // 2.更新数据,user中非null字段都会作为set语句
    User user = new User();
    user.setBalance(2000);
    userMapper.update(user, wrapper);
}

2.1.2.ActualizarWrapper

Al actualizar según el método de actualización en BaseMapper, solo puede asignar valores directamente, lo cual es difícil de implementar para algunos requisitos complejos .

Por ejemplo: para actualizar 1,2,4el saldo del usuario con id y descontar 200 , el SQL correspondiente debe ser:

UPDATE user SET balance = balance - 200 WHERE id in (1, 2, 4)

El resultado de la asignación de SET se basa en el valor existente del campo . En este momento, debe usar la función setSql en UpdateWrapper a -200:

@Test
void testUpdateWrapper() {
    List<Long> ids = List.of(1L, 2L, 4L);
    // 1.生成SQL
    UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
            .setSql("balance = balance - 200") // SET balance = balance - 200
            .in("id", ids); // WHERE id in (1, 2, 4)
	// 2.更新,注意第一个参数可以给null,也就是不填更新字段和数据,
    // 而是基于UpdateWrapper中的setSQL来更新
    userMapper.update(null, wrapper);
}

2.1.3.LambdaQueryWrapper

Tanto QueryWrapper como UpdateWrapper necesitan escribir los nombres de los campos al construir las condiciones , y aparecerán cadenas 魔法值. Esto claramente no se recomienda en los estándares de programación .

Entonces, ¿cómo podemos saber el nombre del campo sin escribir el nombre del campo?

Un enfoque es un gettterenfoque basado en variables combinado con técnicas de reflexión. Por lo tanto, solo necesitamos pasar el método del campo correspondiente a la condición gettera MybatisPlus , y éste podrá calcular el nombre de la variable correspondiente. 方法引用El método de transferencia puede utilizar la expresión de suma en JDK8 Lambda.

Por lo tanto, MybatisPlus proporciona otro conjunto de Wrapper basados ​​en Lambda , incluidos dos:

  • LambdaQueryWrapper
  • LambdaUpdateWrapper

Corresponde a QueryWrapper y UpdateWrapper respectivamente

Aquí se explica cómo usarlo:

@Test
void testLambdaQueryWrapper() {
    // 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.lambda()
            .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
            .like(User::getUsername, "o")
            .ge(User::getBalance, 1000);
    // 2.查询
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

2.2 SQL personalizado

En el caso de demostrar UpdateWrapper, escribimos la declaración SQL actualizada en el código:

Esta forma de escritura no está permitida en algunas empresas, porque es mejor mantener las declaraciones SQL en la capa de persistencia que en la capa empresarial. En el caso actual, dado que la condición es una declaración in, el SQL solo se puede escribir en el archivo Mapper.xml y usar foreach para generar SQL dinámico.

Esto es demasiado problema. Si las condiciones de consulta son más complejas, la escritura de SQL dinámico también lo será.

Por lo tanto, MybatisPlus proporciona una función SQL personalizada que nos permite usar Wrapper para generar condiciones de consulta y luego combinarlo con Mapper.xml para escribir SQL.

2.2.1.Uso básico

En el caso actual podemos escribir:

@Test
void testCustomWrapper() {
    // 1.准备自定义查询条件
    List<Long> ids = List.of(1L, 2L, 4L);
    QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);

    // 2.调用mapper的自定义方法,直接传递Wrapper
    userMapper.deductBalanceByIds(200, wrapper);
}

Luego personalice el SQL en UserMapper:

public interface UserMapper extends BaseMapper<User> {
    @Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}")
    void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper);
}

Esto le ahorra la molestia de escribir condiciones de consulta complejas.

2.2.2 Asociación de mesas múltiples

En teoría, MyBatisPlus no admite consultas de varias tablas, pero podemos usar condiciones personalizadas en Wrapper combinadas con SQL personalizado para lograr el efecto de consultas de varias tablas.

Por ejemplo, queremos consultar a todos los usuarios cuya dirección de entrega esté en Beijing y cuyo ID de usuario esté entre 1, 2 y 4.

Si implementa SQL basado en mybatis , probablemente se verá así:

  <select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">
      SELECT *
      FROM user u
      INNER JOIN address a ON u.id = a.user_id
      WHERE u.id
      <foreach collection="ids" separator="," item="id" open="IN (" close=")">
          #{id}
      </foreach>
      AND a.city = #{city}
  </select>

Se puede ver que la más complicada es la escritura de condiciones WHERE, si el negocio es más complicado, el SQL aquí será más anormal. Sin embargo, basándonos en SQL personalizado combinado con la jugabilidad de Wrapper, podemos usar Wrapper para crear condiciones de consulta y luego escribir a mano las partes SELECT y FROM para implementar consultas de múltiples tablas.

Las condiciones de consulta se construyen así:

@Test
void testCustomJoinWrapper() {
    // 1.准备自定义查询条件
    QueryWrapper<User> wrapper = new QueryWrapper<User>()
            .in("u.id", List.of(1L, 2L, 4L))
            .eq("a.city", "北京");

    // 2.调用mapper的自定义方法
    List<User> users = userMapper.queryUserByWrapper(wrapper);

    users.forEach(System.out::println);
}

Luego personalice el método en UserMapper:

@Select("SELECT u.* FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}")
List<User> queryUserByWrapper(@Param("ew")QueryWrapper<User> wrapper);

Por supuesto, también puedes UserMapper.xmlescribir SQL en:

<select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">
    SELECT * FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}
</select>

2.3.Interfaz de servicio

Análisis sobre Service y Mapper en mybatis-plus

MybatisPlus no solo proporciona BaseMapper, sino que también proporciona una interfaz de servicio común y una implementación predeterminada , que encapsula algunos métodos de plantilla de servicio de uso común.

La interfaz general es IService, la implementación predeterminada es ServiceImpl, y los métodos encapsulados se pueden dividir en varias categorías:

  • save: Nuevo
  • remove:borrar
  • update:renovar
  • get: Consultar un solo resultado
  • list: Resultados de la recopilación de consultas
  • count:contar
  • page: consulta de paginación

2.3.1.CRUD

Primero echemos un vistazo a la interfaz CRUD básica.

Nuevo :

  • savees agregar un solo elemento
  • saveBatchSe agrega en lotes
  • saveOrUpdateSe juzga en función de la identificación . Si los datos existen, se actualizarán. Si no existen, se agregarán.
  • saveOrUpdateBatch¿Es una adición o modificación por lotes?

borrar:

  • removeById: Eliminar según la identificación
  • removeByIds: Eliminación por lotes según ID
  • removeByMap: Eliminar condicionalmente según los pares clave-valor en el mapa
  • remove(Wrapper<T>): Eliminar según las condiciones del Wrapper
  • ~~removeBatchByIds~~: Aún no compatible

Revisar:

  • updateById:Modificar según la identificación
  • update(Wrapper<T>): Sujeto a UpdateWrappermodificación, Wrappercontenido en setywhere parte de
  • update(T,Wrapper<T>): TModificar y Wrapperhacer coincidir los datos de acuerdo con los datos que contiene.
  • updateBatchById: Modificación por lotes basada en ID

Conseguir:

  • getById: Consulta 1 dato basado en la identificación
  • getOne(Wrapper<T>): Basado en Wrapperla consulta de 1 dato
  • getBaseMapper: Obtenga la implementación Serviceinterna BaseMapper. A veces, cuando necesita llamar directamente Mappera la personalización interna,SQL puede usar este método para obtenerla.Mapper

Lista:

  • listByIds: Consulta por lotes basada en ID
  • list(Wrapper<T>): Consulta múltiples datos según las condiciones del Wrapper
  • list(): Consultar todo

Conde :

  • count(): Cuente todas las cantidades
  • count(Wrapper<T>): Cuente Wrapperla cantidad de datos que cumplen las condiciones.

getBaseMapper :

Cuando queremos llamar al SQL personalizado en el Mapper del servicio, debemos obtener el Mapper correspondiente al servicio , podemos usar este método:

2.3.2.Uso básico

Dado que Servicea menudo necesitamos definir métodos personalizados relacionados con los negocios , no podemos usarlos directamente IService, sino que personalizamos Servicela interfaz y luego heredamos IServicepara extender el método. Al mismo tiempo, deje la Service实现类herencia personalizada ServiceImplpara que no necesite implementar IServicela interfaz usted mismo.

Primero, defina UserServiceherencia IService:

package com.itheima.mp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;

public interface UserService extends IService<User> {
    // 拓展自定义方法
}

Luego, escribe UserServiceImplla clase, hereda ServiceImple implementa UserService:

package com.itheima.mp.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.po.service.UserService;
import com.itheima.mp.mapper.UserMapper;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

La estructura del proyecto es la siguiente:

Finalmente, escriba una clase de prueba y pruébela:

package com.itheima.mp.service;

import com.itheima.mp.domain.po.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class UserServiceTest {

    @Autowired
    UserService userService;

    @Test
    void testService() {
        List<User> list = userService.list();
        list.forEach(System.out::println);
    }
}

2.3.3 Adición de lotes

La nueva función por lotes en IService es muy cómoda de usar, pero hay algunas cosas a tener en cuenta. Probémosla.

Primero probamos la inserción de datos uno por uno:

@Test
void testSaveOneByOne() {
    long b = System.currentTimeMillis();
    for (int i = 1; i <= 100000; i++) {
        userService.save(buildUser(i));
    }
    long e = System.currentTimeMillis();
    System.out.println("耗时:" + (e - b));
}

private User buildUser(int i) {
    User user = new User();
    user.setUsername("user_" + i);
    user.setPassword("123");
    user.setPhone("" + (18688190000L + i));
    user.setBalance(2000);
    user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
    user.setCreateTime(LocalDateTime.now());
    user.setUpdateTime(user.getCreateTime());
    return user;
}

Los resultados de la ejecución son los siguientes:

Puedes ver que la velocidad es muy lenta.

Luego pruebe el procesamiento por lotes de MybatisPlus :

@Test
void testSaveBatch() {
    // 准备10万条数据
    List<User> list = new ArrayList<>(1000);
    long b = System.currentTimeMillis();
    for (int i = 1; i <= 100000; i++) {
        list.add(buildUser(i));
        // 每1000条批量插入一次
        if (i % 1000 == 0) {
            userService.saveBatch(list);
            list.clear();
        }
    }
    long e = System.currentTimeMillis();
    System.out.println("耗时:" + (e - b));
}

El tiempo de ejecución final es el siguiente:

Se puede ver que después de usar el procesamiento por lotes, la eficiencia mejora aproximadamente 10 veces en comparación con agregar elementos uno por uno , y el rendimiento sigue siendo bueno.

Sin embargo, veamos brevemente MybatisPlusel código fuente:

@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveBatch(Collection<T> entityList, int batchSize) {
    String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
    return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
}
// ...SqlHelper
public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
    Assert.isFalse(batchSize < 1, "batchSize must not be less than one");
    return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, sqlSession -> {
        int size = list.size();
        int idxLimit = Math.min(batchSize, size);
        int i = 1;
        for (E element : list) {
            consumer.accept(sqlSession, element);
            if (i == idxLimit) {
                sqlSession.flushStatements();
                idxLimit = Math.min(idxLimit + batchSize, size);
            }
            i++;
        }
    });
}

Se puede encontrar que el MybatisPlusprocesamiento por lotes real se basa en PrepareStatementel modo precompilado y luego se envía en lotes . Al final, cuando se ejecuta la base de datos , todavía hay varias declaraciones de inserción y los datos se insertan uno por uno. SQL es similar a este:

Preparing: INSERT INTO user ( username, password, phone, info, balance, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? )
Parameters: user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01
Parameters: user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01
Parameters: user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01

Y si desea obtener el mejor rendimiento, lo mejor es fusionar varias declaraciones SQL en una , como esta:

INSERT INTO user ( username, password, phone, info, balance, create_time, update_time )
VALUES 
(user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01),
(user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01),
(user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01),
(user_4, 123, 18688190004, "", 2000, 2023-07-01, 2023-07-01);

¿Cómo debería hacerlo?

Existe un parámetro de este tipo en los parámetros de conexión del cliente de MySQL: rewriteBatchedStatements. Como sugiere el nombre, se trata de reescribir lasstatement declaraciones de procesamiento por lotes. Documentación de referencia:

cj-conn-prop_rewriteBatchedStatements

El valor predeterminado de este parámetro es falso, necesitamos modificar los parámetros de conexión y configurarlo en verdadero.

Modifique el archivo application.yml en el proyecto y agregue parámetros después de la URL jdbc&rewriteBatchedStatements=true :

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: MySQL123

Pruebe nuevamente para insertar 100.000 datos y podrá ver que la velocidad ha mejorado significativamente:

En ClientPreparedStatement, executeBatchInternalhay una función para determinar rewriteBatchedStatementssi el valor es verdadero y reescribir el SQL :

Finalmente, se reescribió el SQL:

2.3.4.Lambda

LambdaQueryWrapperEl uso de y en el Servicio LambdaUpdateWrapperse ha simplificado aún más. No necesitamos crearlo nosotros mismos , sino llamar directamente al método and :newWrapperlambdaQuerylambdaUpdate

Basado en la consulta Lambda:

@Test
void testLambdaQuery() {
    // 1.查询1个
    User rose = userService.lambdaQuery()
            .eq(User::getUsername, "Rose")
            .one(); // .one()查询1个
    System.out.println("rose = " + rose);

    // 2.查询多个
    List<User> users = userService.lambdaQuery()
            .like(User::getUsername, "o")
            .list(); // .list()查询集合
    users.forEach(System.out::println);

    // 3.count统计
    Long count = userService.lambdaQuery()
            .like(User::getUsername, "o")
            .count(); // .count()则计数
    System.out.println("count = " + count);
}

Se puede encontrar que además de construir condiciones en el método lambdaQuery, el resultado final devuelto también se puede juzgar en función del último método de programación en cadena. Los métodos opcionales son:

  • .one():Máximo 1 resultado
  • .list():Devuelve el resultado de la colección
  • .count():Devuelve el resultado del recuento

lambdaQuery también admite consultas condicionales dinámicas . Por ejemplo, el siguiente requisito:

Defina un método que reciba parámetros como nombre de usuario, estado, minBalance y maxBalance. Los parámetros pueden estar vacíos.
Si el parámetro de nombre de usuario no está vacío, se utiliza la consulta difusa;
si el parámetro de estado no está vacío, se utiliza la coincidencia exacta;
si el parámetro minBalance no está vacío, el saldo debe ser mayor que minBalance;
si el parámetro maxBalance no está vacío, el saldo debe ser menor que maxBalance

Este requisito es una consulta dinámica típica , que a menudo se encuentra en el desarrollo empresarial, y su implementación es la siguiente:

@Test
void testQueryUser() {
    List<User> users = queryUser("o", 1, null, null);
    users.forEach(System.out::println);
}

public List<User> queryUser(String username, Integer status, Integer minBalance, Integer maxBalance) {
    return userService.lambdaQuery()
            .like(username != null , User::getUsername, username)
            .eq(status != null, User::getStatus, status)
            .ge(minBalance != null, User::getBalance, minBalance)
            .le(maxBalance != null, User::getBalance, maxBalance)
            .list();
}

Actualización basada en Lambda:

@Test
void testLambdaUpdate() {
    userService.lambdaUpdate()
            .set(User::getBalance, 800) // set balance = 800
            .eq(User::getUsername, "Jack") // where username = "Jack"
            .update(); // 执行Update
}

lambdaUpdate()Según la programación en cadena, se pueden agregar setcondiciones y condiciones después del método where. Pero debes seguir el ritmo hasta el final update(), de lo contrario la declaración no se ejecutará.

lambdaUpdate() también admite condiciones dinámicas, como los siguientes requisitos:

Implemente un método de actualización basado en el método lambdaUpdate() en IService para cumplir con los siguientes requisitos:
1 Los parámetros son balance, id, nombre de usuario
2 Al menos uno de id o nombre de usuario no está vacío y el usuario coincide con precisión según id o nombre de usuario
3 Modificar el saldo del usuario coincidente Para el saldo
4, si el saldo es 0, modifique el estado del usuario al estado congelado (2)

La implementación es la siguiente:

@Test
void testUpdateBalance() {
    updateBalance(0L, 1L, null);
}

public void updateBalance(Long balance, Long id, String username){
    userService.lambdaUpdate()
            .set(User::getBalance, balance)
            .set(balance == 0, User::getStatus, 2)
            .eq(id != null, User::getId, id)
            .eq(username != null, User::getId, username)
            .update();
}

2.4.Herramientas estáticas

A veces, los servicios se llamarán entre sí. Para evitar problemas de dependencia circular , puede llamar al asignador de Service o MybatisPlus para proporcionar una clase de herramienta estática:Db algunos de los cuales tienen básicamente las mismas firmas que el métodoIService en , y también pueden ayúdanos a implementar CRUD.Función:

DbLa diferencia entre el método estático y el métodoIService chino : además de guardar y actualizar, otros parámetros tienen clases , y luego sabes qué tabla operar.

Ejemplo:

@Test
void testDbGet() {
    User user = Db.getById(1L, User.class);
    System.out.println(user);
}

@Test
void testDbList() {
    // 利用Db实现复杂条件查询
    List<User> list = Db.lambdaQuery(User.class)
            .like(User::getUsername, "o")
            .ge(User::getBalance, 1000)
            .list();
    list.forEach(System.out::println);
}

@Test
void testDbUpdate() {
    Db.lambdaUpdate(User.class)
            .set(User::getBalance, 2000)
            .eq(User::getUsername, "Rose");
}

Supongo que te gusta

Origin blog.csdn.net/m0_67184231/article/details/132676911
Recomendado
Clasificación