mybatis-plus — это инструмент расширения Mybatis для упрощения разработки и повышения эффективности. Сокращения, используемые нижеmpупростить представлениеmybatis-plus, В этой статье в основном рассказывается об использовании mp с SpringBoot.
Примечание. В этой статье используется последняя версия mp 3.4.2. Различия в более ранних версиях см. в документации.
Официальный сайт:baomidou.com/
Быстрый старт
-
Создайте проект SpringBoot
-
импортировать зависимости
<!-- pom.xml --> <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>mybatis-plus</artifactId> <version>0.0.1-SNAPSHOT</version> <name>mybatis-plus</name> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> -
база данных конфигурации
# application.yml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/yogurt?serverTimezone=Asia/Shanghai username: root password: root mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启SQL语句打印 -
Создайте класс сущностей
package com.example.mp.po; import lombok.Data; import java.time.LocalDateTime; @Data public class User { private Long id; private String name; private Integer age; private String email; private Long managerId; private LocalDateTime createTime; } -
Создать интерфейс картографа
package com.example.mp.mappers; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.mp.po.User; public interface UserMapper extends BaseMapper<User> { } -
Настройте путь сканирования интерфейса сопоставления в классе запуска SpringBoot.
package com.example.mp; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.example.mp.mappers") public class MybatisPlusApplication { public static void main(String[] args) { SpringApplication.run(MybatisPlusApplication.class, args); } } -
Создать таблицу в базе
DROP TABLE IF EXISTS user; CREATE TABLE user ( id BIGINT(20) PRIMARY KEY NOT NULL COMMENT '主键', name VARCHAR(30) DEFAULT NULL COMMENT '姓名', age INT(11) DEFAULT NULL COMMENT '年龄', email VARCHAR(50) DEFAULT NULL COMMENT '邮箱', manager_id BIGINT(20) DEFAULT NULL COMMENT '直属上级id', create_time DATETIME DEFAULT NULL COMMENT '创建时间', CONSTRAINT manager_fk FOREIGN KEY(manager_id) REFERENCES user (id) ) ENGINE=INNODB CHARSET=UTF8; INSERT INTO user (id, name, age ,email, manager_id, create_time) VALUES (1, '大BOSS', 40, 'boss@baomidou.com', NULL, '2021-03-22 09:48:00'), (2, '李经理', 40, 'boss@baomidou.com', 1, '2021-01-22 09:48:00'), (3, '黄主管', 40, 'boss@baomidou.com', 2, '2021-01-22 09:48:00'), (4, '吴组长', 40, 'boss@baomidou.com', 2, '2021-02-22 09:48:00'), (5, '小菜', 40, 'boss@baomidou.com', 2, '2021-02-22 09:48:00') -
Напишите тестовый класс SpringBoot
package com.example.mp; import com.example.mp.mappers.UserMapper; import com.example.mp.po.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; import static org.junit.Assert.*; @RunWith(SpringRunner.class) @SpringBootTest public class SampleTest { @Autowired private UserMapper mapper; @Test public void testSelect() { List<User> list = mapper.selectList(null); assertEquals(5, list.size()); list.forEach(System.out::println); } }
подготовительная работа сделана
База данных выглядит следующим образом
Каталог проекта выглядит следующим образом
запустить тестовый класс
Как видите, для базовых операций CRUD с одной таблицей вам нужно только создать класс сущности и создать класс, наследуемый отBaseMapperИнтерфейс можно охарактеризовать как очень простой. И мы это замечаем,Userв классеmanagerId,createTimeсвойства, автоматические и в таблицах базы данныхmanager_id,create_timeСоответственно, это связано с тем, что mp автоматически преобразует имя подчеркивания базы данных в имя класса Java в стиле верблюда.
основная функция
аннотация
mp предоставляет в общей сложности 8 аннотаций, которые используются в классах сущностей Java.
-
@TableNameАннотация относится к классу, определяя отношение сопоставления между классом и таблицей базы данных.Когда имя класса класса сущности (после преобразования в нижний регистр) совпадает с именем таблицы базы данных, вы не можете указать эту аннотацию.
-
@TableIdАннотируется в поле класса сущности,Указывает, что это поле соответствует первичному ключу таблицы базы данных.. Когда первичный ключ имеет имя id (столбец в таблице имеет имя id, а поле в классе сущностей имеет имя id), нет необходимости явно указывать первичный ключ с этой аннотацией, и mp будет автоматически связан. Если имя поля класса не соответствует имени столбца таблицы, его можно использовать
valueАтрибут указывает имена столбцов таблицы. Кроме того, эта аннотация имеет важный атрибутtype, используемый для указания стратегии первичного ключа, см.Раздел политики первичного ключа -
@TableFieldВ поле укажите отношение сопоставления между полями класса сущностей Java и столбцами таблицы базы данных. Эта аннотация имеет следующие сценарии применения.
-
Исключить нетабличные поля
Если поле в классе сущностей Java не соответствует ни одному столбцу в таблице, оно используется только для сохранения некоторых дополнительных или собранных данных, вы можете установить
existсобственностьfalse, это поле будет игнорироваться при вставке в объект сущности. Исключение нетабличных полей также можно выполнить другими способами, например, с помощьюstaticилиtransientКлючевые слова, но я лично не думаю, что это очень разумно, поэтому не буду вдаваться в подробности. -
Стратегия проверки поля
пройти через
insertStrategy,updateStrategy,whereStrategyСвойства можно настроить для управления тем, как поля объекта собираются в инструкцию SQL, когда объект сущности вставляется, обновляется или используется в качестве условия WHERE. видетьРаздел конфигурации -
Политика заполнения полей
пройти через
fillСпецификация атрибута, когда поле пустое, оно будет заполнено автоматически
-
-
@Versionаннотацию оптимистичной блокировки, см.Секция оптимистичной блокировки
-
@EnumValueАннотация к полю enum
-
@TableLogicнадгробие см.Раздел надгробия
-
KeySequenceСтратегия первичного ключа последовательности (
oracle) -
InterceptorIgnoreПравила фильтрации плагинов
CRUD-интерфейс
mp инкапсулирует некоторые из самых основных методов CRUD, вам нужно только напрямую наследовать интерфейс, предоставляемый mp, без написания никакого SQL, вы можете его съесть. mp предоставляет два набора интерфейсов, а именно интерфейс Mapper CRUD и интерфейс Service CRUD. И mp также предоставляет условный конструкторWrapper, вы можете легко собрать условие WHERE в операторе SQL, см.Раздел условного конструктора
Интерфейс Mapper CRUD
Просто определите класс сущности, а затем создайте интерфейс, который наследуется от предоставленного mp.BaseMapper, готовы есть. mp автоматически разрешит отношения сопоставления между классами сущностей и таблицами при запуске mybatis и внедрит преобразователь с общими методами CRUD.BaseMapperНекоторые из представленных здесь методов перечислены ниже:
-
insert(T entity)вставить запись -
deleteById(Serializable id)Удалить запись на основе идентификатора первичного ключа -
delete(Wrapper<T> wrapper)Удалить в соответствии с оболочкой условного конструктора -
selectById(Serializable id)Поиск по идентификатору первичного ключа -
selectBatchIds(Collection idList)Пакетный поиск по идентификатору первичного ключа -
selectByMap(Map<String,Object> map)В соответствии с именем столбца и значением столбца, указанными на картеЭквивалентное совпадениенайти -
selectMaps(Wrapper<T> wrapper)В соответствии с условием оболочки записи запроса инкапсулируют результат запроса в виде карты, ключ карты — это столбец результата, а значение — это значение. -
selectList(Wrapper<T> wrapper)Условный конструкторwrapperсделать запрос -
update(T entity, Wrapper<T> wrapper)Условный конструкторwrapperсделать обновление updateById(T entity)- ...
Простой съедобный пример, как указано вышеРаздел быстрого старта, некоторые из более специальных методов объясняются ниже.
selectMaps
BaseMapperИнтерфейс также предоставляетselectMapsметод, этот метод будет инкапсулировать результат запроса в виде карты, ключ карты — это столбец результата, а значение — это значение
Сценарии использования этого метода следующие:
-
Проверить только некоторые столбцы
Когда в определенной таблице много столбцов, но при SELECT нужно выбирать только отдельные столбцы, а результаты запроса не нужно инкапсулировать в объекты класса сущностей Java (когда проверяется только часть столбцов, после инкапсуляции в сущности , свойство многих сущностных объектов будет нулевым), вы можете использовать
selectMaps, после получения указанного столбца вы можете его обработать самостоятельноНапример
@Test public void test3() { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.select("id","name","email").likeRight("name","黄"); List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); maps.forEach(System.out::println); } -
вести статистику
Например
// 按照直属上级进行分组,查询每组的平均年龄,最大年龄,最小年龄 /** select avg(age) avg_age ,min(age) min_age, max(age) max_age from user group by manager_id having sum(age) < 500; **/ @Test public void test3() { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.select("manager_id", "avg(age) avg_age", "min(age) min_age", "max(age) max_age") .groupBy("manager_id").having("sum(age) < {0}", 500); List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); maps.forEach(System.out::println); }
selectObjs
Будет возвращено только значение первого поля (первого столбца), остальные поля будут отброшены
Например
@Test
public void test3() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("id", "name").like("name", "黄");
List<Object> objects = userMapper.selectObjs(wrapper);
objects.forEach(System.out::println);
}
Результат получен, инкапсулирован только id первой колонки
selectCount
Запросите общее количество выполненных условий, обратите внимание, что с помощью этого метода вы не можете вызватьQueryWrapperизselectметод для установки столбца для запроса. Этот метод автоматически добавитselect count(1)
Например
@Test
public void test3() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("name", "黄");
Integer count = userMapper.selectCount(wrapper);
System.out.println(count);
}
Сервисный CRUD-интерфейс
Другой набор CRUD — это сервисный уровень, нужно только написать интерфейс, наследоватьIService, и создайте класс реализации интерфейса, готовый к употреблению. (Методы CRUD, предоставляемые этим интерфейсом, аналогичны функциям, предоставляемым интерфейсом Mapper.Очевидная разница в том, чтоIServiceПоддержка большего количества пакетных операций,какsaveBatch,saveOrUpdateBatchи другие методы.
Примеры потребления следующие
-
Сначала создайте новый интерфейс и наследуйте
IServicepackage com.example.mp.service; import com.baomidou.mybatisplus.extension.service.IService; import com.example.mp.po.User; public interface UserService extends IService<User> { } -
Создайте класс реализации этого интерфейса и наследуйте
ServiceImpl, и, наконец, нажмите@ServiceАннотация, зарегистрированная в контейнере Spring, готова к употреблениюpackage com.example.mp.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.mp.mappers.UserMapper; import com.example.mp.po.User; import com.example.mp.service.UserService; import org.springframework.stereotype.Service; @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { } -
тестовый код
package com.example.mp; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.example.mp.po.User; import com.example.mp.service.UserService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class ServiceTest { @Autowired private UserService userService; @Test public void testGetOne() { LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery(); wrapper.gt(User::getAge, 28); User one = userService.getOne(wrapper, false); // 第二参数指定为false,使得在查到了多行记录时,不抛出异常,而返回第一条记录 System.out.println(one); } } -
результат
Другой,IServiceОн также поддерживает цепочку вызовов, а код очень лаконичен.Пример запроса выглядит следующим образом.
@Test
public void testChain() {
List<User> list = userService.lambdaQuery()
.gt(User::getAge, 39)
.likeRight(User::getName, "王")
.list();
list.forEach(System.out::println);
}
Обновленный пример выглядит следующим образом
@Test
public void testChain() {
userService.lambdaUpdate()
.gt(User::getAge, 39)
.likeRight(User::getName, "王")
.set(User::getEmail, "w39@baomidou.com")
.update();
}
Пример удаления выглядит следующим образом
@Test
public void testChain() {
userService.lambdaUpdate()
.like(User::getName, "青蛙")
.remove();
}
Условный конструктор
Одна из вещей, которая делает mp чрезвычайно удобным для меня, заключается в том, что он предоставляет мощный условный конструктор.Wrapper, условие WHERE можно построить очень удобно. Условный конструктор в основном включает три класса:AbstractWrapper.QueryWrapper,UpdateWrapper, их классовые отношения следующие
существуетAbstractWrapperСуществует множество методов построения условий WHERE вQueryWrapperпротивSELECTзаявление, которое обеспечиваетselect()метод, вы можете настроить столбцы, которые необходимо запрашивать, иUpdateWrapperпротивUPDATEзаявление, которое обеспечиваетset()метод построенияsetутверждение. Условные конструкторы также поддерживают лямбда-выражения, которые очень удобно писать.
следующая параAbstractWrapperЧастичный список методов, используемых для создания условий WHERE в операторах SQL.
-
eq: равно, равно -
allEq: все равны, все равны -
ne: не равно -
gt: больше, больше, чем> -
ge: больше или равно≥ -
lt: меньше чем, меньше чем< -
le: меньше или равно≤ -
between: Эквивалент BETWEEN в SQL. notBetween-
like: Нечеткое совпадение.like("name","黄"), эквивалентно SQLname like '%黄%' -
likeRight: Нечеткое совпадение правой половины.likeRight("name","黄"), эквивалентно SQLname like '黄%' -
likeLeft: Нечеткое совпадение левой половины.likeLeft("name","黄"), эквивалентно SQLname like '%黄' -
notLike:notLike("name","黄"), эквивалентно SQLname not like '%黄%' isNullisNotNullin-
and: SQL-коннектор И -
or: соединитель SQL ИЛИ -
apply: используется для объединения SQL, этот метод может использоваться для функций базы данных и может динамически передавать параметры - .......
Пример использования
Давайте попрактикуемся в использовании условных конструкторов в некоторых конкретных случаях. (создан с использованием предыдущегоuserповерхность)
// 案例先展示需要完成的SQL语句,后展示Wrapper的写法
// 1. 名字中包含佳,且年龄小于25
// SELECT * FROM user WHERE name like '%佳%' AND age < 25
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("name", "佳").lt("age", 25);
List<User> users = userMapper.selectList(wrapper);
// 下面展示SQL时,仅展示WHERE条件;展示代码时, 仅展示Wrapper构建部分
// 2. 姓名为黄姓,且年龄大于等于20,小于等于40,且email字段不为空
// name like '黄%' AND age BETWEEN 20 AND 40 AND email is not null
wrapper.likeRight("name","黄").between("age", 20, 40).isNotNull("email");
// 3. 姓名为黄姓,或者年龄大于等于40,按照年龄降序排列,年龄相同则按照id升序排列
// name like '黄%' OR age >= 40 order by age desc, id asc
wrapper.likeRight("name","黄").or().ge("age",40).orderByDesc("age").orderByAsc("id");
// 4.创建日期为2021年3月22日,并且直属上级的名字为李姓
// date_format(create_time,'%Y-%m-%d') = '2021-03-22' AND manager_id IN (SELECT id FROM user WHERE name like '李%')
wrapper.apply("date_format(create_time, '%Y-%m-%d') = {0}", "2021-03-22") // 建议采用{index}这种方式动态传参, 可防止SQL注入
.inSql("manager_id", "SELECT id FROM user WHERE name like '李%'");
// 上面的apply, 也可以直接使用下面这种方式做字符串拼接,但当这个日期是一个外部参数时,这种方式有SQL注入的风险
wrapper.apply("date_format(create_time, '%Y-%m-%d') = '2021-03-22'");
// 5. 名字为王姓,并且(年龄小于40,或者邮箱不为空)
// name like '王%' AND (age < 40 OR email is not null)
wrapper.likeRight("name", "王").and(q -> q.lt("age", 40).or().isNotNull("email"));
// 6. 名字为王姓,或者(年龄小于40并且年龄大于20并且邮箱不为空)
// name like '王%' OR (age < 40 AND age > 20 AND email is not null)
wrapper.likeRight("name", "王").or(
q -> q.lt("age",40)
.gt("age",20)
.isNotNull("email")
);
// 7. (年龄小于40或者邮箱不为空) 并且名字为王姓
// (age < 40 OR email is not null) AND name like '王%'
wrapper.nested(q -> q.lt("age", 40).or().isNotNull("email"))
.likeRight("name", "王");
// 8. 年龄为30,31,34,35
// age IN (30,31,34,35)
wrapper.in("age", Arrays.asList(30,31,34,35));
// 或
wrapper.inSql("age","30,31,34,35");
// 9. 年龄为30,31,34,35, 返回满足条件的第一条记录
// age IN (30,31,34,35) LIMIT 1
wrapper.in("age", Arrays.asList(30,31,34,35)).last("LIMIT 1");
// 10. 只选出id, name 列 (QueryWrapper 特有)
// SELECT id, name FROM user;
wrapper.select("id", "name");
// 11. 选出id, name, age, email, 等同于排除 manager_id 和 create_time
// 当列特别多, 而只需要排除个别列时, 采用上面的方式可能需要写很多个列, 可以采用重载的select方法,指定需要排除的列
wrapper.select(User.class, info -> {
String columnName = info.getColumn();
return !"create_time".equals(columnName) && !"manager_id".equals(columnName);
});
Condition
Среди многих методов условного конструктора вы можете указатьbooleanтип параметраcondition, используется для определения того, добавляется ли условие в последнюю сгенерированную инструкцию WHERE, например
String name = "黄"; // 假设name变量是一个外部传入的参数
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like(StringUtils.hasText(name), "name", name);
// 仅当 StringUtils.hasText(name) 为 true 时, 会拼接这个like语句到WHERE中
// 其实就是对下面代码的简化
if (StringUtils.hasText(name)) {
wrapper.like("name", name);
}
Сущностный объект как условие
вызвать конструктор для созданияWrapperObject, вы можете передать объект сущности. использовать это позжеWrapperКогда условие WHERE создается с ненулевым атрибутом в объекте сущности (конструкция по умолчаниюЭквивалентное совпадениеУсловие WHERE, это поведение может быть передано для каждого поля в классе сущностей.@TableFieldв примечанияхconditionсвойства изменить)
Примеры следующие
@Test
public void test3() {
User user = new User();
user.setName("黄主管");
user.setAge(28);
QueryWrapper<User> wrapper = new QueryWrapper<>(user);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
Результат выполнения следующий. Можно видеть, что на основе ненулевых атрибутов в объекте сущности,Эквивалентный запрос соответствия.
Если вы хотите настроить таргетинг на определенные свойства, изменитеЭквивалентное совпадениеповедение, вы можете использовать его в классе сущностей@TableFieldАннотация для настройки, пример выглядит следующим образом
package com.example.mp.po;
import com.baomidou.mybatisplus.annotation.SqlCondition;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Long id;
@TableField(condition = SqlCondition.LIKE) // 配置该字段使用like进行拼接
private String name;
private Integer age;
private String email;
private Long managerId;
private LocalDateTime createTime;
}
Запустите тестовый код ниже
@Test
public void test3() {
User user = new User();
user.setName("黄");
QueryWrapper<User> wrapper = new QueryWrapper<>(user);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
Из результатов, полученных на следующем рисунке, для объекта сущности вnameполе, используяlikeсоединение
@TableFieldнастроен вconditionСвойство на самом деле является строкой,SqlConditionНекоторые строки предопределены в классе на выбор
package com.baomidou.mybatisplus.annotation;
public class SqlCondition {
//下面的字符串中, %s 是占位符, 第一个 %s 是列名, 第二个 %s 是列的值
public static final String EQUAL = "%s=#{%s}";
public static final String NOT_EQUAL = "%s<>#{%s}";
public static final String LIKE = "%s LIKE CONCAT('%%',#{%s},'%%')";
public static final String LIKE_LEFT = "%s LIKE CONCAT('%%',#{%s})";
public static final String LIKE_RIGHT = "%s LIKE CONCAT(#{%s},'%%')";
}
SqlConditionКонфигурация, представленная в, относительно ограничена, когда нам нужно<или>Для других методов сращивания вам необходимо определить его самостоятельно. Например
package com.example.mp.po;
import com.baomidou.mybatisplus.annotation.SqlCondition;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Long id;
@TableField(condition = SqlCondition.LIKE)
private String name;
@TableField(condition = "%s > #{%s}") // 这里相当于大于, 其中 > 是字符实体
private Integer age;
private String email;
private Long managerId;
private LocalDateTime createTime;
}
Тест выглядит следующим образом
@Test
public void test3() {
User user = new User();
user.setName("黄");
user.setAge(30);
QueryWrapper<User> wrapper = new QueryWrapper<>(user);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
Из результатов, полученных на рисунке ниже, видно, что,nameСвойство используетсяlikeсращен, в то время какageсвойства используются>Сращивание
метод allEq
Метод allEq проходит черезmap, который используется для эквивалентного сопоставления
@Test
public void test3() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
Map<String, Object> param = new HashMap<>();
param.put("age", 40);
param.put("name", "黄飞飞");
wrapper.allEq(param);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
Когда значение в Map, переданное методом allEq, равноnullэлемент, по умолчанию установлено значениеis null
@Test
public void test3() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
Map<String, Object> param = new HashMap<>();
param.put("age", 40);
param.put("name", null);
wrapper.allEq(param);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
Если вы хотите игнорировать значение на карте какnull, можно задать параметры при вызове allEqboolean null2IsNullзаfalse
@Test
public void test3() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
Map<String, Object> param = new HashMap<>();
param.put("age", 40);
param.put("name", null);
wrapper.allEq(param, false);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
Если вы хотите отфильтровать некоторые элементы на карте при выполнении allEq, вы можете вызвать перегруженный метод allEq.allEq(BiPredicate<R, V> filter, Map<R, V> params)
@Test
public void test3() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
Map<String, Object> param = new HashMap<>();
param.put("age", 40);
param.put("name", "黄飞飞");
wrapper.allEq((k,v) -> !"name".equals(k), param); // 过滤掉map中key为name的元素
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
условный конструктор лямбда
Условный конструктор лямбда поддерживает лямбда-выражения. Нет необходимости указывать имя столбца в виде строки, как в обычном условном конструкторе. Его можно напрямую использовать как класс сущности.ссылка на методуказать столбец. Примеры следующие
@Test
public void testLambda() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getName, "黄").lt(User::getAge, 30);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
Как и в обычном условном конструкторе, имя столбца указывается в виде строки, и правильность имени столбца нельзя проверить во время компиляции, что не так элегантно, как в условном конструкторе лямбда.
Кроме того, существуетСвязанные лямбда-условные конструкторы, пример использования следующий
@Test
public void testLambda() {
LambdaQueryChainWrapper<User> chainWrapper = new LambdaQueryChainWrapper<>(userMapper);
List<User> users = chainWrapper.like(User::getName, "黄").gt(User::getAge, 30).list();
users.forEach(System.out::println);
}
операция обновления
Выше перечислены все операции запроса, теперь давайте поговорим об операциях обновления и удаления.
BaseMapper2 метода обновления предоставлены в
-
updateById(T entity)Судя по вводу
entityизid(первичный ключ) для обновления, дляentityАтрибуты, которые не являются пустыми в операторе UPDATE, появятся после SET оператора UPDATE, т. е.entityАтрибуты, которые не являются пустыми, будут обновлены в базе данных, примеры приведены ниже.@RunWith(SpringRunner.class) @SpringBootTest public class UpdateTest { @Autowired private UserMapper userMapper; @Test public void testUpdate() { User user = new User(); user.setId(2L); user.setAge(18); userMapper.updateById(user); } } -
update(T entity, Wrapper<T> wrapper)Согласно сущности
entityи условный конструкторwrapperдля обновления, пример выглядит следующим образом@Test public void testUpdate2() { User user = new User(); user.setName("王三蛋"); LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>(); wrapper.between(User::getAge, 26,31).likeRight(User::getName,"吴"); userMapper.update(user, wrapper); }Для дополнительной демонстрации передайте объект сущности
Wrapper, то есть случай, когда условие WHERE строится с объектным объектом@Test public void testUpdate3() { User whereUser = new User(); whereUser.setAge(40); whereUser.setName("王"); LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>(whereUser); User user = new User(); user.setEmail("share@baomidou.com"); user.setManagerId(10L); userMapper.update(user, wrapper); }Обратите внимание, что в нашем классе User справа
nameсвойства иageСвойства установлены следующим образом@Data public class User { private Long id; @TableField(condition = SqlCondition.LIKE) private String name; @TableField(condition = "%s > #{%s}") private Integer age; private String email; private Long managerId; private LocalDateTime createTime; }Результаты
Чтобы продемонстрировать дополнительно, использование связанных лямбда-условных конструкторов
@Test public void testUpdate5() { LambdaUpdateChainWrapper<User> wrapper = new LambdaUpdateChainWrapper<>(userMapper); wrapper.likeRight(User::getEmail, "share") .like(User::getName, "飞飞") .set(User::getEmail, "ff@baomidou.com") .update(); }
отражение
так какBaseMapperОба предоставленных метода обновления передают объект сущности для выполнения обновления, котороеЛучше, когда много столбцов, которые нужно обновить, если вы хотите обновить только один или два столбца, создать объект сущности будет немного проблематично. Ввиду этой ситуации,UpdateWrapperобеспеченыsetметод, вы можете вручную соединить инструкцию SET в SQL, вам не нужно передавать объект сущности в это время, пример выглядит следующим образом
@Test
public void testUpdate4() {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.likeRight(User::getEmail, "share").set(User::getManagerId, 9L);
userMapper.update(null, wrapper);
}
удалить операцию
BaseMapperВсего для удаления предусмотрены следующие способы
-
deleteByIdудалить по идентификатору первичного ключа -
deleteBatchIdsПакетное удаление на основе идентификатора первичного ключа -
deleteByMapУдалить в соответствии с Картой (ключ в Карте — имя столбца, значение — значение, и эквивалентное сопоставление выполняется в соответствии с столбцом и значением) -
delete(Wrapper<T> wrapper)Условный конструкторWrapperудалить
Это похоже на предыдущие операции запроса и обновления, поэтому я не буду вдаваться в подробности.
пользовательский SQL
Если методы, предоставляемые mp, не соответствуют требованиям, вы можете настроить SQL.
родной мибатис
Примеры следующие
- Метод аннотации
package com.example.mp.mappers;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mp.po.User;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @Author yogurtzzz
* @Date 2021/3/18 11:21
**/
public interface UserMapper extends BaseMapper<User> {
@Select("select * from user")
List<User> selectRaw();
}
- XML-метод
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mp.mappers.UserMapper">
<select id="selectRaw" resultType="com.example.mp.po.User">
SELECT * FROM user
</select>
</mapper>
package com.example.mp.mappers;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mp.po.User;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface UserMapper extends BaseMapper<User> {
List<User> selectRaw();
}
При использовании xml,Если файл xml и файл интерфейса преобразователя находятся в разных каталогах, тебе надоapplication.ymlНастройте путь хранения mapper.xml в
mybatis-plus:
mapper-locations: /mappers/*
Если есть несколько мест для хранения маппера, настройте его в виде массива
mybatis-plus:
mapper-locations:
- /mappers/*
- /com/example/mp/*
Код теста выглядит следующим образом
@Test
public void testCustomRawSql() {
List<User> users = userMapper.selectRaw();
users.forEach(System.out::println);
}
результат
mybatis-plus
Вы также можете использовать условный конструктор Wrapper, предоставляемый mp, для настройки SQL.
Примеры следующие
- Метод аннотации
package com.example.mp.mappers;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.example.mp.po.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface UserMapper extends BaseMapper<User> {
// SQL中不写WHERE关键字,且固定使用${ew.customSqlSegment}
@Select("select * from user ${ew.customSqlSegment}")
List<User> findAll(@Param(Constants.WRAPPER)Wrapper<User> wrapper);
}
- XML-метод
package com.example.mp.mappers;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mp.po.User;
import java.util.List;
public interface UserMapper extends BaseMapper<User> {
List<User> findAll(Wrapper<User> wrapper);
}
<!-- UserMapper.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mp.mappers.UserMapper">
<select id="findAll" resultType="com.example.mp.po.User">
SELECT * FROM user ${ew.customSqlSegment}
</select>
</mapper>
Пейджинговый запрос
BaseMapperСуществует два метода запроса на поиск страниц:selectPageиselectMapsPage, первый инкапсулирует результат запроса в объект объекта Java, а второй инкапсулирует его какMap<String,Object>. Съедобный пример запроса на разбиение на страницы выглядит следующим образом.
-
Создайте перехватчик пейджинга для mp и зарегистрируйте его в контейнере Spring.
package com.example.mp.config; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MybatisPlusConfig { /** 新版mp **/ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } /** 旧版mp 用 PaginationInterceptor **/ } -
Выполнить запрос с разбивкой на страницы
@Test public void testPage() { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.ge(User::getAge, 28); // 设置分页信息, 查第3页, 每页2条数据 Page<User> page = new Page<>(3, 2); // 执行分页查询 Page<User> userPage = userMapper.selectPage(page, wrapper); System.out.println("总记录数 = " + userPage.getTotal()); System.out.println("总页数 = " + userPage.getPages()); System.out.println("当前页码 = " + userPage.getCurrent()); // 获取分页查询结果 List<User> records = userPage.getRecords(); records.forEach(System.out::println); } -
результат
-
разное
-
Обратите внимание, что запрос на подкачку выдал в общей сложности 2 SQL-запроса, один раз для проверки общего количества записей и один раз для проверки конкретных данных.Если вы не хотите проверять общее количество записей, проверьте только результаты разбиения на страницы.. в состоянии пройти
Pageперегруженный конструктор для , указывающийisSearchCountзаfalseПростоpublic Page(long current, long size, boolean isSearchCount) -
В реальной разработке вы можете столкнутьсяЗапрос соединения нескольких таблицсцена, в это время
BaseMapperПредставленный метод однотабличного страничного запроса не может удовлетворить потребности, необходимопользовательский SQL, пример выглядит следующим образом (используйте SQL запроса одной таблицы для демонстрации, когда вы фактически выполняете запрос соединения нескольких таблиц, вы можете изменить оператор SQL)-
Определите функцию в интерфейсе картографа, получите объект Page в качестве параметра и напишите собственный SQL
// 这里采用纯注解方式。当然,若SQL比较复杂,建议还是采用XML的方式 @Select("SELECT * FROM user ${ew.customSqlSegment}") Page<User> selectUserPage(Page<User> page, @Param(Constants.WRAPPER) Wrapper<User> wrapper); -
выполнить запрос
@Test public void testPage2() { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.ge(User::getAge, 28).likeRight(User::getName, "王"); Page<User> page = new Page<>(3,2); Page<User> userPage = userMapper.selectUserPage(page, wrapper); System.out.println("总记录数 = " + userPage.getTotal()); System.out.println("总页数 = " + userPage.getPages()); userPage.getRecords().forEach(System.out::println); } -
результат
-
-
режим дополненной реальности
Режим ActiveRecord, манипулируя сущностными объектами, напрямую манипулирует таблицами базы данных. Аналог ОРМ.
Примеры следующие
-
сделать класс сущности
Userунаследовано отModelpackage com.example.mp.po; import com.baomidou.mybatisplus.annotation.SqlCondition; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.extension.activerecord.Model; import lombok.Data; import lombok.EqualsAndHashCode; import java.time.LocalDateTime; @EqualsAndHashCode(callSuper = false) @Data public class User extends Model<User> { private Long id; @TableField(condition = SqlCondition.LIKE) private String name; @TableField(condition = "%s > #{%s}") private Integer age; private String email; private Long managerId; private LocalDateTime createTime; } -
напрямую вызывать метод объекта сущности
@Test public void insertAr() { User user = new User(); user.setId(15L); user.setName("我是AR猪"); user.setAge(1); user.setEmail("ar@baomidou.com"); user.setManagerId(1L); boolean success = user.insert(); // 插入 System.out.println(success); } -
результат
Другие примеры
// 查询
@Test
public void selectAr() {
User user = new User();
user.setId(15L);
User result = user.selectById();
System.out.println(result);
}
// 更新
@Test
public void updateAr() {
User user = new User();
user.setId(15L);
user.setName("王全蛋");
user.updateById();
}
//删除
@Test
public void deleteAr() {
User user = new User();
user.setId(15L);
user.deleteById();
}
стратегия первичного ключа
При определении класса сущности используйте@TableIdуказывает первичный ключ, а егоtypeможно указать стратегию первичного ключа.
mp поддерживает множество стратегий первичного ключа, стратегия по умолчанию основана на самоувеличенном идентификаторе алгоритма снежинки. Все стратегии первичного ключа определены в классе перечисленияIdTypeсередина,IdTypeимеет следующие значения
-
AUTOИдентификатор базы данных увеличивается автоматически,зависит от базы данных. Когда оператор SQL генерируется операцией вставки, столбец первичного ключа не будет вставлен.
-
NONEНе задан тип первичного ключа. Если первичный ключ не задан вручную в коде, он будетГлобальная политика для первичных ключейГенерируется автоматически (глобальная стратегия первичного ключа по умолчанию основана на самоувеличивающемся идентификаторе алгоритма снежинки)
-
INPUTНеобходимо вручную установить первичный ключ, если он не установлен. Когда операция вставки генерирует оператор SQL, значение столбца первичного ключа будет
null. Первичный ключ последовательности Oracle должен использоваться таким образом -
ASSIGN_IDКогда первичный ключ не задан вручную, то есть атрибут первичного ключа в классе сущностей пуст, он будет автоматически заполнен с использованием алгоритма снежинки.
-
ASSIGN_UUIDКогда свойство первичного ключа класса сущности пусто, оно будет автоматически заполнено с использованием UUID.
-
.... (есть несколько устаревших и не будут перечислены)
Для каждого класса сущностей используйте@TableIdАннотация определяет стратегию первичного ключа класса сущностей, которую можно понимать каклокальная стратегия. Если вы хотите использовать одну и ту же стратегию первичного ключа для всех классов сущностей и настраивать каждый класс сущностей один за другим, это будет слишком проблематично.В этом случае вы можете использовать первичный ключглобальная стратегия. просто нужноapplication.ymlПросто настройте его. Например, настроено глобальное использование стратегии автоматического увеличения первичного ключа.
# application.yml
mybatis-plus:
global-config:
db-config:
id-type: auto
Ниже показано поведение различных стратегий первичного ключа.
-
AUTOсуществует
UserсправаidАтрибуты аннотируются, а затем MYSQLuserТаблица изменяет свой первичный ключ на автоинкремент.@EqualsAndHashCode(callSuper = false) @Data public class User extends Model<User> { @TableId(type = IdType.AUTO) private Long id; @TableField(condition = SqlCondition.LIKE) private String name; @TableField(condition = "%s > #{%s}") private Integer age; private String email; private Long managerId; private LocalDateTime createTime; }контрольная работа
@Test public void testAuto() { User user = new User(); user.setName("我是青蛙呱呱"); user.setAge(99); user.setEmail("frog@baomidou.com"); user.setCreateTime(LocalDateTime.now()); userMapper.insert(user); System.out.println(user.getId()); }результат
Видно, что идентификатор первичного ключа не задан в коде, а идентификатор первичного ключа не установлен в выданном операторе SQL, и после вставки идентификатор первичного ключа будет записан обратно в объект сущности.
-
NONEв MySQL
userВ таблице удалите автоинкремент первичного ключа. затем изменитьUserкласс (если не настроен@TableIdОбратите внимание, что стратегия первичного ключа по умолчанию такжеNONE)@TableId(type = IdType.NONE) private Long id;При вставке, если идентификатор первичного ключа класса сущностей имеет значение, он используется; если идентификатор первичного ключа пуст, для создания идентификатора используется глобальная стратегия первичного ключа.
-
Остальные стратегии аналогичны и повторяться не будут.
резюме
AUTOОпираясь на самоинкрементный первичный ключ базы данных, при вставке объектному объекту не нужно устанавливать первичный ключ.После успешной вставки первичный ключ будет записан обратно в объектный объект.
INPUTПолностью зависит от пользовательского ввода. Какой идентификатор первичного ключа в объекте сущности, что задается при его вставке в базу данных. Если есть значение, установите значение, если оно естьnullзатем установитеnull
Остальные стратегии генерируются автоматически, когда идентификатор первичного ключа в объекте сущности пуст.
NONEбудет следовать глобальной стратегии,ASSIGN_IDИспользуя алгоритм снежинки,ASSIGN_UUIDПринять UUID
глобальная конфигурация, вapplication.ymlЭто можно сделать в; для локальной конфигурации одного класса сущностей используйте@TableIdВот и все. Для класса сущностей, если у него есть локальная стратегия первичного ключа, примите ее, в противном случае следуйте глобальной стратегии.
настроить
mybatis plus имеет множество настраиваемых элементов, которые можно найти вapplication.ymlНастройте в , например глобальную политику первичного ключа выше. Некоторые элементы конфигурации перечислены ниже
базовая конфигурация
-
configLocation: Если имеется отдельная конфигурация MyBatis, используйте эту аннотацию, чтобы указать файл конфигурации MYBATIS (глобальный профиль MyBatis). -
mapperLocations: расположение файла xml, соответствующего мапперу mybatis. -
typeAliasesPackage: Псевдоним пути сканирования пакета mybatis - .....
Расширенная конфигурация
-
mapUnderscoreToCamelCase: включить ли автоматическое сопоставление правил именования регистров верблюдов. (включено по умолчанию) -
dbTpe: Тип базы данных. Как правило, совпадение не требуется, оно будет автоматически идентифицировано в соответствии с URL-адресом подключения к базе данных. -
Существует несколько дополнительных конфигураций, как показано ниже.
-
IGNORED: игнорировать контрольную сумму. То есть проверка не производится. Все поля в объекте объекта, независимо от значения, точно собраны в оператор SQL (дляNULLПоля собраны в операторе SQL какNULL). -
NOT_NULL:НетNULLчек. будет толькоNULLПоля собираются в оператор SQL -
NOT_EMPTY: ненулевая проверка. Когда поле строкового типа, собираются только непустые строки; для других типов полей это эквивалентноNOT_NULL -
NEVER: Не присоединяйтесь к SQL. Не все поля добавляются в инструкцию SQL
Этот элемент конфигурации можно найти в
application.ymlв ходе выполненияГлобальная конфигурация, вы также можете использовать его для поля в классе сущностей@TableFieldАннотация сделаналокальная конфигурацияКакая польза от этой стратегии проверки полей? Это может быть отражено в операции UPDATE.
Userобъект для выполнения операции UPDATE, мы хотим выполнить операцию UPDATE только дляUserНепустые атрибуты в объекте обновляются в базе данных, а другие атрибуты не обновляются, тоNOT_NULLможет удовлетворить потребности. И еслиupdateStrategyнастроен какIGNORED, непустое суждение не будет выполнено, и все атрибуты в объекте сущности будут достоверно собраны в SQL.Таким образом, когда выполняется ОБНОВЛЕНИЕ, некоторые поля, которые не хотят обновляться, могут быть установлены наNULL. -
-
tablePrefix: добавить префикс имени таблицыНапример
mybatis-plus: global-config: db-config: table-prefix: xx_Затем внесите некоторые изменения в таблицу в MYSQL. Но класс сущности Java остается прежним (по-прежнему
User).контрольная работа
@Test public void test3() { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.like("name", "黄"); Integer count = userMapper.selectCount(wrapper); System.out.println(count); }Вы можете увидеть сплайсированный SQL с добавленным префиксом перед именем таблицы
Полную конфигурацию можете посмотреть на официальном сайте mp ==>портал
Генератор кода
mp предоставляет генератор, который может быстро генерировать полный набор кодов, таких как класс сущности Entity, интерфейс Mapper, Service, Controller и так далее.
Примеры следующие
public class GeneratorTest {
@Test
public void generate() {
AutoGenerator generator = new AutoGenerator();
// 全局配置
GlobalConfig config = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
// 设置输出到的目录
config.setOutputDir(projectPath + "/src/main/java");
config.setAuthor("yogurt");
// 生成结束后是否打开文件夹
config.setOpen(false);
// 全局配置添加到 generator 上
generator.setGlobalConfig(config);
// 数据源配置
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/yogurt?serverTimezone=Asia/Shanghai");
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("root");
// 数据源配置添加到 generator
generator.setDataSource(dataSourceConfig);
// 包配置, 生成的代码放在哪个包下
PackageConfig packageConfig = new PackageConfig();
packageConfig.setParent("com.example.mp.generator");
// 包配置添加到 generator
generator.setPackageInfo(packageConfig);
// 策略配置
StrategyConfig strategyConfig = new StrategyConfig();
// 下划线驼峰命名转换
strategyConfig.setNaming(NamingStrategy.underline_to_camel);
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
// 开启lombok
strategyConfig.setEntityLombokModel(true);
// 开启RestController
strategyConfig.setRestControllerStyle(true);
generator.setStrategy(strategyConfig);
generator.setTemplateEngine(new FreemarkerTemplateEngine());
// 开始生成
generator.execute();
}
}
После запуска вы можете увидеть, что сгенерирован полный набор кода, как показано на следующем рисунке.
Расширенные возможности
Демонстрация расширенных возможностей требует использования новой таблицыuser2
DROP TABLE IF EXISTS user2;
CREATE TABLE user2 (
id BIGINT(20) PRIMARY KEY NOT NULL COMMENT '主键id',
name VARCHAR(30) DEFAULT NULL COMMENT '姓名',
age INT(11) DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
manager_id BIGINT(20) DEFAULT NULL COMMENT '直属上级id',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
update_time DATETIME DEFAULT NULL COMMENT '修改时间',
version INT(11) DEFAULT '1' COMMENT '版本',
deleted INT(1) DEFAULT '0' COMMENT '逻辑删除标识,0-未删除,1-已删除',
CONSTRAINT manager_fk FOREIGN KEY(manager_id) REFERENCES user2(id)
) ENGINE = INNODB CHARSET=UTF8;
INSERT INTO user2(id, name, age, email, manager_id, create_time)
VALUES
(1, '老板', 40 ,'boss@baomidou.com' ,NULL, '2021-03-28 13:12:40'),
(2, '王狗蛋', 40 ,'gd@baomidou.com' ,1, '2021-03-28 13:12:40'),
(3, '王鸡蛋', 40 ,'jd@baomidou.com' ,2, '2021-03-28 13:12:40'),
(4, '王鸭蛋', 40 ,'yd@baomidou.com' ,2, '2021-03-28 13:12:40'),
(5, '王猪蛋', 40 ,'zd@baomidou.com' ,2, '2021-03-28 13:12:40'),
(6, '王软蛋', 40 ,'rd@baomidou.com' ,2, '2021-03-28 13:12:40'),
(7, '王铁蛋', 40 ,'td@baomidou.com' ,2, '2021-03-28 13:12:40')
и создайте соответствующий класс сущностиUser2
package com.example.mp.po;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User2 {
private Long id;
private String name;
private Integer age;
private String email;
private Long managerId;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Integer version;
private Integer deleted;
}
и интерфейс картографа
package com.example.mp.mappers;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mp.po.User2;
public interface User2Mapper extends BaseMapper<User2> { }
Надгробие
Во-первых, зачем надгробия? Нельзя просто удалить? Конечно можно, но если вы хотите восстановить его в будущем, или если вам нужно просмотреть данные, вы не сможете этого сделать.Надгробие — это схема, облегчающая восстановление данных и защищающая ценность самих данных..
В повседневной жизни, после того как мы удалили файл на компьютере, мы просто помещаем файл в корзину, и мы можем просмотреть или восстановить его при необходимости в будущем. Когда мы определяем, что файл больше не нужен, его можно полностью удалить из корзины. Это тоже похоже.
Надгробия, предоставляемые mp, очень просты в реализации.
просто нужноapplication.ymlСоответствующая конфигурация для логического удаления в
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名
logic-delete-value: 1 # 逻辑已删除值(默认为1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为0)
# 若逻辑已删除和未删除的值和默认值一样,则可以不配置这2项
тестовый код
package com.example.mp;
import com.example.mp.mappers.User2Mapper;
import com.example.mp.po.User2;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class LogicDeleteTest {
@Autowired
private User2Mapper mapper;
@Test
public void testLogicDel() {
int i = mapper.deleteById(6);
System.out.println("rowAffected = " + i);
}
}
результат
Как видите, выданный SQL больше неDELETE, ноUPDATE
В этот момент мы делаем это сноваSELECT
@Test
public void testSelect() {
List<User2> users = mapper.selectList(null);
}
Можно видеть, что выпущенный оператор SQL автоматически объединяет логически неудаленные условия после WHERE. В результатах запроса нет Wang Ruandan с id 6.
Если вам нужен столбец SELECT, исключая столбец надгробной плиты, вы можете передать его в классе сущностей.@TableFieldнастроить
@TableField(select = false)
private Integer deleted;
Вы можете видеть, что в результате выполнения на следующем рисунке удаленный столбец больше не включается в SELECT.
спередиapplication.ymlВыполненная конфигурация является глобальной. Вообще говоря, для нескольких таблиц мы также унифицируем имена полей захоронения и унифицируем логически удаленные и восстановленные значения, чтобы можно было выполнить глобальную настройку. Конечно, для индивидуальной настройки некоторых таблиц используйте соответствующие поля класса сущностей.@TableLogicПросто
@TableLogic(value = "0", delval = "1")
private Integer deleted;
резюме
После включения надгробия mp это окажет следующее влияние на SQL
- Оператор INSERT: без эффекта
- Оператор SELECT: добавить условие WHERE, отфильтровать удаленные данные
- Оператор UPDATE: добавьте условие WHERE, чтобы предотвратить обновление удаленных данных.
- Оператор DELETE: преобразован в оператор UPDATE.
**Обратите внимание, что вышеуказанные эффекты действуют только для SQL, автоматически внедряемого mp. ** Если это пользовательский SQL, добавленный вами вручную, он не вступит в силу. Например
public interface User2Mapper extends BaseMapper<User2> {
@Select("select * from user2")
List<User2> selectRaw();
}
назовите этоselectRaw, логическое удаление mp не вступит в силу.
Кроме того, надгробия можно найти вapplication.ymlГлобальная конфигурация в , также может использоваться в классах сущностей@TableLogicВыполните локальную настройку.
автозаполнение
В таблице часто есть такие поля, как «Время добавления», «Время изменения» и «Оператор». Более примитивный способ — устанавливать его вручную каждый раз при вставке или обновлении. mp можно настроить на автоматическое заполнение некоторых полей.Съедобный пример выглядит следующим образом
-
В некоторых полях класса сущностей через
@TableFieldНастроить автозаполнениеpublic class User2 { private Long id; private String name; private Integer age; private String email; private Long managerId; @TableField(fill = FieldFill.INSERT) // 插入时自动填充 private LocalDateTime createTime; @TableField(fill = FieldFill.UPDATE) // 更新时自动填充 private LocalDateTime updateTime; private Integer version; private Integer deleted; } -
Реализация обработчика автозаполнения
package com.example.mp.component; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.time.LocalDateTime; @Component //需要注册到Spring容器中 public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { // 插入时自动填充 // 注意第二个参数要填写实体类中的字段名称,而不是表的列名称 strictFillStrategy(metaObject, "createTime", LocalDateTime::now); } @Override public void updateFill(MetaObject metaObject) { // 更新时自动填充 strictFillStrategy(metaObject, "updateTime", LocalDateTime::now); } }
контрольная работа
@Test
public void test() {
User2 user = new User2();
user.setId(8L);
user.setName("王一蛋");
user.setAge(29);
user.setEmail("yd@baomidou.com");
user.setManagerId(2L);
mapper.insert(user);
}
По результатам следующего рисунка видно, что createTime заполняется автоматически.
Обратите внимание, что автозаполнение сработает, только если поле пусто.Если поле не пусто, существующее значение будет использоваться напрямую. следующее
@Test
public void test() {
User2 user = new User2();
user.setId(8L);
user.setName("王一蛋");
user.setAge(29);
user.setEmail("yd@baomidou.com");
user.setManagerId(2L);
user.setCreateTime(LocalDateTime.of(2000,1,1,8,0,0));
mapper.insert(user);
}
Автозаполнение при обновлении, тест выглядит следующим образом
@Test
public void test() {
User2 user = new User2();
user.setId(8L);
user.setName("王一蛋");
user.setAge(99);
mapper.updateById(user);
}
плагин оптимистичной блокировки
Когда происходят параллельные операции, необходимо убедиться, что операции каждого пользователя над данными не конфликтуют.В это время требуется метод управления параллелизмом. Метод пессимистической блокировки заключается в непосредственной блокировке (механизм блокировки базы данных) при изменении записи в базе данных, блокировке данных и последующем выполнении операции; в то время как оптимистическая блокировка, как следует из названия, сначала предполагает, что ее не существует Конфликтная ситуация , а затем проверьте, нет ли конфликта в фактической операции с данными. Распространенной реализацией оптимистической блокировки являетсяномер версии, существует также управление параллельными транзакциями на основе номера версии, называемое MVCC в MySQL.
В сценариях с большим количеством операций чтения и меньшим количеством операций записи больше подходит оптимистическая блокировка, которая может снизить нагрузку на производительность, вызванную операциями блокировки, и повысить пропускную способность системы.
В сценарии большего количества операций записи и меньшего количества операций чтения чаще используются пессимистичные блокировки, в противном случае оптимистическая блокировка будет продолжать сбоить и выполнять повторные попытки, что приведет к снижению производительности.
Реализация оптимистической блокировки выглядит следующим образом:
- При извлечении записи получить текущую версию
- При обновлении ставьте эту версию
- При выполнении обновления установите version = newVersion, где version = oldVersion.
- Если oldVersion не соответствует версии в базе данных, обновление завершается ошибкой.
Эта идея очень похожа на CAS (Compare And Swap).
Этапы реализации оптимистической блокировки следующие:
-
Настроить плагин оптимистичной блокировки
package com.example.mp.config; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MybatisPlusConfig { /** 3.4.0以后的mp版本,推荐用如下的配置方式 **/ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } /** 旧版mp可以采用如下方式。注意新旧版本中,新版的类,名称带有Inner, 旧版的不带, 不要配错了 **/ /* @Bean public OptimisticLockerInterceptor opLocker() { return new OptimisticLockerInterceptor(); } */ } -
Аннотировать поле, представляющее версию в классе сущностей
@Version@Data public class User2 { private Long id; private String name; private Integer age; private String email; private Long managerId; private LocalDateTime createTime; private LocalDateTime updateTime; @Version private Integer version; private Integer deleted; }
тестовый код
@Test
public void testOpLocker() {
int version = 1; // 假设这个version是先前查询时获得的
User2 user = new User2();
user.setId(8L);
user.setEmail("version@baomidou.com");
user.setVersion(version);
int i = mapper.updateById(user);
}
Взгляните на базу данных перед выполнением
По результату выполнения на следующем рисунке видно, что операции, связанные с версией, добавляются в оператор SQL.
Когда UPDATE возвращает 1, указывая, что количество затронутых строк равно 1, обновление выполнено успешно. Наоборот, поскольку версия после WHERE несовместима с базой данных и никакие записи не совпадают, количество затронутых строк равно 0, что указывает на сбой обновления. После успешного обновления новая версия будет инкапсулирована обратно в объект сущности.
Поле версии в классе сущности, тип поддерживает только int, long, Date, Timestamp, LocalDateTime.
Обратите внимание, что плагин оптимистичной блокировки поддерживает толькоupdateById(id)иupdate(entity, wrapper)метод
**Примечание: при использованииwrapper,ноwrapperНельзя использовать повторно! **Пример ниже
@Test
public void testOpLocker() {
User2 user = new User2();
user.setId(8L);
user.setVersion(1);
user.setAge(2);
// 第一次使用
LambdaQueryWrapper<User2> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User2::getName, "王一蛋");
mapper.update(user, wrapper);
// 第二次复用
user.setAge(3);
mapper.update(user, wrapper);
}
Видно, что при втором повторном использованииwrapperКогда в сплайсированном SQL есть 2 версии в следующем операторе WHERE, что проблематично.
плагин профилирования
Плагин будет выводить время выполнения операторов SQL для анализа производительности и настройки операторов SQL.
Примечание. После версии 3.2.0 подключаемый модуль анализа производительности, поставляемый с mp, был официально удален, и рекомендуется использовать подключаемый модуль анализа производительности стороннего производителя.
съедобные шаги
-
Внедрение зависимостей maven
<dependency> <groupId>p6spy</groupId> <artifactId>p6spy</artifactId> <version>3.9.1</version> </dependency> -
Исправлять
application.ymlspring: datasource: driver-class-name: com.p6spy.engine.spy.P6SpyDriver #换成p6spy的驱动 url: jdbc:p6spy:mysql://localhost:3306/yogurt?serverTimezone=Asia/Shanghai #url修改 username: root password: root -
существует
src/main/resourcesДобавить в каталог ресурсовspy.properties#spy.properties #3.2.1以上使用 modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory # 真实JDBC driver , 多个以逗号分割,默认为空。由于上面设置了modulelist, 这里可以不用设置driverlist #driverlist=com.mysql.cj.jdbc.Driver # 自定义日志打印 logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger #日志输出到控制台 appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger #若要日志输出到文件, 把上面的appnder注释掉, 或者采用下面的appender, 再添加logfile配置 #不配置appender时, 默认是往文件进行输出的 #appender=com.p6spy.engine.spy.appender.FileLogger #logfile=log.log # 设置 p6spy driver 代理 deregisterdrivers=true # 取消JDBC URL前缀 useprefix=true # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. excludecategories=info,debug,result,commit,resultset # 日期格式 dateformat=yyyy-MM-dd HH:mm:ss # 是否开启慢SQL记录 outagedetection=true # 慢SQL记录标准 2 秒 outagedetectioninterval=2 # 执行时间设置, 只有超过这个执行时间的才进行记录, 默认值0, 单位毫秒 executionThreshold=10
Просто запустите тестовый пример, вы увидите, что время выполнения SQL записывается
Мультитенантный анализатор SQL
Концепция мультиарендности: несколько пользователей совместно используют систему, но их данные должны быть относительно независимыми и поддерживать определенную степень изоляции.
Мультитенантная изоляция данных обычно имеет следующие методы:
-
Разные арендаторы используют разные серверы баз данных
Преимущества: разные арендаторы имеют разные независимые базы данных, что облегчает расширение и обеспечивает лучшую персонализацию для разных арендаторов, а также легче восстанавливать данные в случае сбоя.
Недостатки: увеличенное количество баз данных, стоимость приобретения, более высокая стоимость обслуживания.
-
Разные арендаторы используют один и тот же сервер базы данных, но используют разные базы данных (разные схемы).
Преимущество в том, что затраты на приобретение и обслуживание ниже, а недостаток в том, что восстановление данных сложнее, потому что данные разных арендаторов объединяются.
-
Разные арендаторы используют один и тот же сервер базы данных, используют одну и ту же базу данных, совместно используют таблицы данных и добавляют идентификатор арендатора в таблицу, чтобы различать
Преимущества — самые низкие затраты на приобретение и обслуживание, поддержка большинства пользователей, недостатки — самая низкая изоляция и самая низкая безопасность.
Примеры потребления следующие
Добавьте конфигурацию многопользовательского перехватчика. После добавления конфигурации при выполнении CRUD условие идентификатора арендатора будет автоматически склеено в конце оператора SQL.
package com.example.mp.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
@Override
public Expression getTenantId() {
// 返回租户id的值, 这里固定写死为1
// 一般是从当前上下文中取出一个 租户id
return new LongValue(1);
}
/**
** 通常会将表示租户id的列名,需要排除租户id的表等信息,封装到一个配置类中(如TenantConfig)
**/
@Override
public String getTenantIdColumn() {
// 返回表中的表示租户id的列名
return "manager_id";
}
@Override
public boolean ignoreTable(String tableName) {
// 表名不为 user2 的表, 不拼接多租户条件
return !"user2".equals(tableName);
}
}));
// 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor
// 用了分页插件必须设置 MybatisConfiguration#useDeprecatedExecutor = false
return interceptor;
}
}
тестовый код
@Test
public void testTenant() {
LambdaQueryWrapper<User2> wrapper = new LambdaQueryWrapper<>();
wrapper.likeRight(User2::getName, "王")
.select(User2::getName, User2::getAge, User2::getEmail, User2::getManagerId);
user2Mapper.selectList(wrapper);
}
Синтаксический анализатор SQL имени динамической таблицы
Когда объем данных особенно велик, мы обычно используем подбазу данных и подтаблицу. В настоящее время может существовать несколько таблиц с одинаковой структурой таблиц, но с разными именами таблиц. Напримерorder_1,order_2,order_3, При запросе нам может потребоваться динамически установить имя таблицы для проверки. mp предоставляет синтаксический анализатор SQL с динамическим именем таблицы, съедобный пример выглядит следующим образом
Сначала скопируйте его в mysqluser2поверхность
Настроить перехватчик имени динамической таблицы
package com.example.mp.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Random;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
HashMap<String, TableNameHandler> map = new HashMap<>();
// 对于user2表,进行动态表名设置
map.put("user2", (sql, tableName) -> {
String _ = "_";
int random = new Random().nextInt(2) + 1;
return tableName + _ + random; // 若返回null, 则不会进行动态表名替换, 还是会使用user2
});
dynamicTableNameInnerInterceptor.setTableNameHandlerMap(map);
interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
return interceptor;
}
}
контрольная работа
@Test
public void testDynamicTable() {
user2Mapper.selectList(null);
}
Суммировать
-
Условный конструктор
AbstractWrapperВ условии WHERE предусмотрено несколько методов для построения операторов SQL и его подклассов.QueryWrapperдополнительно предусмотреноselectметод, вы можете выбрать только определенные столбцы, подклассыUpdateWrapperдополнительно предусмотреноsetМетод установки оператора SET в SQL. помимо обычныхWrapperи на основе лямбдаWrapper,какLambdaQueryWrapper,LambdaUpdateWrapper, когда они создают условие WHERE, они непосредственно начинаются сссылка на методуказать столбцы в условии WHERE, чем обычноWrapperУказание строкой более элегантно. Кроме того, естьЦепная обертка,какLambdaQueryChainWrapper, который инкапсулируетBaseMapper, вы можете получить результаты более легко. -
Условный конструктор принимаетцепной вызовдля объединения нескольких условий по умолчанию между условиями
ANDсоединять -
когда
ANDилиORКогда следующие условия необходимо заключить в круглые скобки, условия в круглых скобках передаются в качестве параметра в виде лямбда-выражения.and()илиor()В частности, когда
()Когда вам нужно поместить его в самое начало оператора WHERE, вы можете использоватьnested()метод -
Доступно, когда в условном выражении необходимо передать пользовательский оператор SQL или вызвать функцию базы данных.
apply()метод сплайсинга SQL -
К каждому методу в условном конструкторе можно получить доступ через
booleanпеременная типаcondition, чтобы гибко соединять условия WHERE по мере необходимости (только еслиconditionзаtrueПри объединении операторов SQL) -
Используя лямбда-условный конструктор, вы можете напрямую использовать свойства класса сущностей для условного построения с помощью лямбда-выражений, что является более элегантным, чем обычный условный конструктор.
-
Если метода, предоставленного mp, недостаточно, вы можете пройтипользовательский SQL(родной мибатис) для развития расширения
-
При использовании mp для запроса подкачки вам необходимо создать перехватчик подкачки (Interceptor), зарегистрировать его в контейнере Spring, а затем выполнить запрос, передав объект подкачки (объект страницы). При запросе одной таблицы вы можете использовать
BaseMapperкоторый предоставилselectPageилиselectMapsPageметод. В сложных сценариях (таких как запрос на объединение нескольких таблиц) используйте пользовательский SQL. -
Режим AR может управлять базой данных напрямую, манипулируя классами сущностей. Пусть класс сущности наследуется от
ModelПросто
(Заканчивать)