Введение в Fluent Mybatis II

Java

Часть 1Введение в FluentMybatis, представляет методы Mapper, предоставляемые платформой, из которых несколько важных методов используют объекты IQuery и IUpdate. Эти два объекта являются строительными классами FluentMybatis для реализации сложного и динамического sql.Благодаря этим двум объектам Fluent Mybatis может создавать более сложные бизнес-операторы sql непосредственно через java API без написания определенных файлов xml, чтобы достичь логики кода и логического единства sql. .

Сравнение функций Fluent Mybatis, родного Mybatis и Mybatis Plus

Свободная документация и примеры Mybatis

В следующей статье рассказывается, как определить мощные динамические операторы SQL с помощью IQuery и IUpdate.

Сначала создайте бизнес-сценарий

  • Структура таблицы

Если есть структура таблицы отчета студента следующим образом:

create table `student_score`
(
    id           bigint auto_increment comment '主键ID' primary key,
    student_id   bigint            not null comment '学号',
    gender_man   tinyint default 0 not null comment '性别, 0:女; 1:男',
    school_term  int               null comment '学期',
    subject      varchar(30)       null comment '学科',
    score        int               null comment '成绩',
    gmt_create   datetime          not null comment '记录创建时间',
    gmt_modified datetime          not null comment '记录最后修改时间',
    is_deleted   tinyint default 0 not null comment '逻辑删除标识'
) engine = InnoDB default charset=utf8;
  • необходимость

Сейчас нужны:Статистика с 2000 по 2019 год, баллы по трем предметам («английский язык», «математика», «китайский язык») по семестрам, минимальный балл предметной статистики, наивысший балл и средний балл, статистические результаты, отсортированные по семестрам и предметам.Оператор SQL для достижения этого требования выглядит следующим образом.

select school_term, subject, count(score), min(score), max(score), avg(score)
from student_score
where school_term between 2000 and 2019
  and subject in ('英语', '数学', '语文')
  and is_deleted = 0
group by school_term, subject
order by school_term, subject

Теперь реализуем через FluentMybatis

  1. Определите интерфейс в классе StudentScoreDao.
@Data
public class ScoreStatistics {
    private int schoolTerm;
    private String subject;
    private long count;
    private Integer minScore;
    private Integer maxScore;
    private BigDecimal avgScore;
}
public interface StudentScoreDao extends IBaseDao<StudentScoreEntity> {
    /**
     * 统计从fromYear到endYear年间学科subjects的统计数据
     *
     * @param fromYear 统计年份区间开始
     * @param endYear  统计年份区间结尾
     * @param subjects 统计的学科列表
     * @return 统计数据
     */
    List<ScoreStatistics> statistics(int fromYear, int endYear, String[] subjects);
}
  1. Реализовать бизнес-логику на StudentScoreDaoImpl
@Repository
public class StudentScoreDaoImpl extends StudentScoreBaseDao implements StudentScoreDao {
    @Override
    public List<ScoreStatistics> statistics(int fromSchoolTerm, int endSchoolTerm, String[] subjects) {
        return super.listPoJos(ScoreStatistics.class, super.query()
            .select.schoolTerm().subject()
            .count("count")
            .min.score("min_score")
            .max.score("max_score")
            .avg.score("avg_score")
            .end()
            .where.isDeleted().isFalse()
            .and.schoolTerm().between(fromSchoolTerm, endSchoolTerm)
            .and.subject().in(subjects)
            .end()
            .groupBy.schoolTerm().subject().end()
            .orderBy.schoolTerm().asc().subject().asc().end()
        );
    }
}

В реализации DaoImpl помимо возврата статистических результатов в соответствии с условиями, результаты также автоматически преобразуются в объекты ScoreStatistics по правилу подчеркивания для горба.

  1. Напишите тест для проверки
@RunWith(SpringRunner.class)
@SpringBootTest(classes = QuickStartApplication.class)
public class StudentScoreDaoImplTest {
    @Autowired
    private StudentScoreDao dao;

    @Test
    public void statistics() {
        List<ScoreStatistics> list = dao.statistics(2000, 2019, new String[]{"语文", "数学", "英语"});
        System.out.println(list);
    }
}
  1. Просмотр вывода консоли
DEBUG - ==>  Preparing: SELECT school_term, subject, count(*) AS count, MIN(score) AS min_score, MAX(score) AS max_score, AVG(score) AS avg_score 
    FROM student_score 
    WHERE is_deleted = ? 
    AND school_term BETWEEN ? AND ? 
    AND subject IN (?, ?, ?) 
    GROUP BY school_term, subject 
    ORDER BY school_term ASC, subject ASC  
DEBUG - ==> Parameters: false(Boolean), 2000(Integer), 2019(Integer), 语文(String), 数学(String), 英语(String) 
DEBUG - <==      Total: 30 
[ScoreStatistics(schoolTerm=2000, subject=数学, count=17, minScore=1, maxScore=93, avgScore=36.0588), 
 ...
 ScoreStatistics(schoolTerm=2009, subject=语文, count=24, minScore=3, maxScore=100, avgScore=51.2500)]

В приведенном выше примере примерно показана логика динамического построения запроса FluentMybatis. Давайте объясним это подробно.

где условная конструкция

Общее, где условная конструкция

Синтаксис настройки условия where следующий: он начинается с where и заканчивается методом end(). После встроенной переменной where поля таблицы (методы), которые могут быть установлены, могут быть автоматически перечислены, а за полем может следовать компаратор и значение сравнения.

    .where
    .字段().条件(条件参数)
    .end()

Уравнение: равный =?

  • Пример установки условия равенства
@Test
public void column_eq_value() {
    UserQuery query = new UserQuery()
        .where.userName().eq("darui.wu")
        .and.eMail().eq("darui.wu@163.com").end();
    mapper.findOne(query);
}
  • Используйте метод условного суждения: eq(значение объекта, предикат когда)

Когда прогноз когда верен, будет выполнено суждение eq(value)

Пример:

    /**
     * 根据条件查询用户列表
     * 如果设置了积分, 加上积分条件
     * 如果设置了状态, 加上状态条件
     *
     * @return
     */
    @Override
    public List<UserEntity> findByBirthdayAndBonusPoints(Date birthday, Long points, String status) {
        UserQuery query = super.query()
            .where.birthday().eq(birthday)
            .and.bonusPoints().ge(points, If::notNull)
            .and.status().eq(status, If::notBlank).end();
        return mapper.listEntity(query);
    }

Приведенный выше пример эквивалентен следующим общим условным настройкам Java.

    public List<UserEntity> findByBirthdayAndBonusPoints(Date birthday, Long points, String status) {
        UserQuery query = super.query();
        query.where.birthday().eq(birthday);
        if (points != null) {
            query.where.bonusPoints().ge(points);
        }
        if (status != null && !status.trim().isEmpty()) {
            query.where.status().eq(status).end();
        }
        return mapper.listEntity(query);
    }

В качестве альтернативы используйте условные настройки mybatis

<select id="findByBirthdayAndBonusPoints" parameterType="java.util.Map" resultMap="UserEntity">
    SELECT ... FROM user
    WHERE birthday = #{birthday}
    <if test="bonus_points != null ">
        AND bonus_points = #{bonusPoints}
    </if>
    <if test="status != null and status != '' ">
        AND status = #{status}
    </if>
</select>

Использование условного оператора fluent mybatis может достичь того же эффекта, что и вышеприведенное суждение java и суждение xml.В то же время, если пустая строка передана для дня рождения, она также будет считаться пустой, и будет выдано исключение. Предотвратите непреднамеренные условные запросы, вызванные неправильной передачей параметров.

ne: не равно ?

@Test
public void ne() {
    UserQuery query = new UserQuery()
        .where
        .age().ne(34)
        .end();
    mapper.count(query);
}

gt: больше > ?

@Test
public void gt() {
    UserQuery query = new UserQuery()
        .where.age().gt(34).end();
    mapper.count(query);
}

ge: больше или равно >= ?

@Test
public void ge() {
    UserQuery query = new UserQuery()
        .where.age().ge(34).end();
    mapper.count(query);
}

lt: меньше, чем
@Test
public void lt() {
    UserQuery query = new UserQuery()
        .where.age().lt(34).end();
    mapper.count(query);
}

le: меньше или равно
@Test
public void le() {
    UserQuery query = new UserQuery()
        .where.age().le(34).end();
    mapper.count(query);
}

between: BETWEEN ? AND ?

@Test
public void between() {
    UserQuery query = new UserQuery()
        .where.age().between(23, 40).end();
    mapper.count(query);
}

notBetween: NOT BETWEEN ? AND ?

@Test
public void not_between() {
    UserQuery query = new UserQuery()
        .where.age().notBetween(23, 40).end();
    mapper.count(query);
}

например: НРАВИТСЯ ?, ? это "%value%"

@Test
void like() {
    mapper.listEntity(new UserQuery()
        .where
        .userName().like("abc") // like '%abc%'
        .end()
    );
}

notLike: НЕ НРАВИТСЯ ?, ? равно "%value%"

@Test
void not_like() {
    mapper.listEntity(new UserQuery()
        .where
        .userName().notLike("abc") // not like '%abc%'
        .end()
    );
}

likeLeft: НРАВИТСЯ?,? это "значение%"

likeRight: НРАВИТСЯ ?, ? равно "%value"

isNull: column IS NULL

@Test
void isNull() {
    mapper.listEntity(new UserQuery()
        .where.age().isNull().end()
    );
}

isNotNull: column IS NOT NULL

@Test
void isNotNull() {
    mapper.listEntity(new UserQuery()
        .where.age().isNotNull().end()
    );
}

в: в IN (?, ..., ?)

@Test
public void in_collection() {
    UserQuery query = new UserQuery()
        .where
        .age().in(Arrays.asList(34, 35))
        .end();
    mapper.count(query);
}

@Test
public void in_array() {
    UserQuery query = new UserQuery()
        .where
        .age().in(new int[]{34, 35})
        .end();
    mapper.count(query);
}

notIn: не в... НЕ В (?, ..., ?)

Вложенные условные конструкции

FluentMybatis также поддерживает построение вложенных условий.

в (выберите подзапрос)

  • Вложенная таблица запросов аналогична основной таблице запросов.

.column().in(запрос-> {установить условия для запроса})

Вам нужно только обратиться к лямбда-выражению в in, а входным параметром лямбда-выражения является запрос с тем же именем. Параметр where может быть установлен для этого входного параметра.

@DisplayName("嵌套查询和主查询的表是同一个")
@Test
void test_in_same_table_query() {
    UserQuery query = new UserQuery()
        .where.id().in(q -> q.selectId()
            .where.id().eq(3L).end())
        .and.userName().like("user")
        .and.age().gt(23).end();

    List list = mapper.listEntity(query);
}

Приведенная выше логика выполняет следующую инструкцию SQL

SELECT id, gmt_create, gmt_modified, is_deleted, account, age, avatar, birthday, bonus_points, e_mail, password, phone, status, user_name 
FROM user WHERE id IN (SELECT id FROM user WHERE id = ?) 
AND user_name LIKE ? 
AND age > ?
  • Вложенная таблица запросов — это сцена другой таблицы

.column().in(new XyzQuery(){установить условия для запроса})

Если вложенный запрос не является одной и той же таблицей и одной и той же таблицей, вам необходимо явно объявить тип класса объекта Query в методе in, и следующее использование будет таким же, как в методе 1.

@DisplayName("嵌套查询和主查询的表是不同")
@Test
void test_in_difference_table_query() {
    UserQuery query = new UserQuery()
        .selectId()
        .where.addressId().in(new ReceivingAddressQuery().selectId()
            .where.id().in(new int[]{1, 2}).end())
        .end();
    mapper.listEntity(query);
}

выполнить sql-оператор

SELECT id FROM user 
WHERE address_id IN (SELECT id 
    FROM receiving_address WHERE id IN (?, ?)
)
  • не во вложенном запросе: используйте тот же метод, что и во вложенном запросе

существует (выберите подзапрос)

  • Вложенная таблица запросов аналогична основной таблице запросов.

В запросе Exists не нужно указывать поля, а на метод exists можно ссылаться непосредственно в запросе where.

  1. существует (запрос-> {установить условия для запроса})

Если таблица, запрашиваемая с помощью exists, совпадает с основным запросом, вы можете напрямую использовать параметр запроса того же типа в лямбда-выражении, а использование параметра такое же, как и в методе in.

  1. существует (новый XyzQuery {установить условия для запроса})

Если таблица, запрошенная с помощью exists, не соответствует основному запросу, укажите тип запроса в первом параметре метода exists, а второй параметр такой же, как в методе 1.

конкретный пример

@DisplayName("EXISTS查询")
@Test
void test_exists_query() {
    UserQuery query = new UserQuery()
        .where.exists(new ReceivingAddressQuery()
            .where.detailAddress().like("杭州")
            .and.id().apply(" = user.address_id").end())
        .end();
    mapper.listEntity(query);
}

выполнить sql-оператор

SELECT id, gmt_create, gmt_modified, is_deleted, account, address_id, age, avatar, birthday, bonus_points, e_mail, password, phone, status, user_name 
FROM user 
WHERE EXISTS (SELECT * FROM receiving_address
    WHERE detail_address LIKE ?
    AND id = user.address_id
)

группировать по условной конструкции

Синтаксис установки условия groupBy следующий, начиная с groupBy и заканчивая методом end().

    .groupBy.column1().column2()
    .end()

Пример 1:

@Test
public void test_groupBy() throws Exception {
    UserQuery query = new UserQuery()
        .selectId()
        .where.id().eq(24L).end()
        .groupBy.userName().age().end();
    mapper.listEntity(query);
}

выполнить sql-оператор

SELECT id FROM t_user WHERE id = ? GROUP BY user_name, age

состояние

    .groupBy.column1().column2().end().end()
    .having
    .聚合函数1.column1().条件(条件值)
    .聚合函数2.column2().条件(条件值)
    .end()

Условия здесь аналогичны части where, поддерживающей eq, ge, gt и т. д., пример

    .where.id().eq(24L).end()
    .groupBy.id().end()
    .having.sum.age().between(2, 10)
    .and.count.id().gt(2)
    .and.avg.age().in(new int[]{2, 3})
    .and.min.age().gt(10)
    .and.max.age().lt(20)
    .end();

Соответствующий оператор sql

GROUP BY id 
HAVING SUM(age) BETWEEN ? AND ? 
AND COUNT(id) > ? 
AND AVG(age) IN (?, ?) 
AND MIN(age) > ? 
AND MAX(age) < ?

порядок по условной конструкции

Синтаксис установки условия orderBy следующий, начиная с orderBy и заканчивая методом end().

    .orderBy
    .字段().asc()
    .字段().desc()
    .end()

Пример кода 1

@Test
public void test_orderBy() throws Exception {
    UserQuery query = new UserQuery()
        .selectId()
        .where.id().eq(24L).end()
        .orderBy.id().asc().age().desc().end();
    mapper.listEntity(query);
}

Соответствующий оператор sql

SELECT id FROM t_user WHERE id = ? ORDER BY id ASC, age DESC

установить значение обновления

Синтаксис: начать с set, обновить значение соответствующего поля, закончить с помощью end()

    .set.字段1().is(设置值)
    .set.字段2().is(设置值)
    .set.字段3().is(设置值)
    .end()
    .where.条件设置.end()  

образец кода

@Test
void test_update() {
    mapper.updateBy(new UserUpdate()
        .set.age().is(34).end()
        .where.id().eq(2).end()
    );
}

Соответствующий оператор sql

UPDATE t_user SET gmt_modified = now(), age = ? WHERE id = ?

инструкция: В примере явно указано значение обновления поля age, но поле gmt_modified устанавливает значение обновления по умолчанию now(), Таким образом, поле gmt_modified обновляется одновременно с выполнением инструкции. вывод журнала консоли

...UserMapper.updateBy - ==>  Preparing: UPDATE t_user SET gmt_modified = now(), age = ? WHERE id = ? 
...UserMapper.updateBy - ==> Parameters: 34(Integer), 2(Integer)

Свободная документация и примеры Mybatis

Последовательность статей FluentMybatis