MyBatis Dynamic SQL基本使用

5,514 阅读35分钟

MyBatis Dynamic SQL基本使用

1、简介

该库是用于生成动态 SQL 语句的框架。把它想象成一个类型安全的 SQL 模板库,额外支持 MyBatis3 和 Spring JDBC 模板。

该库将生成完整的 DELETE、INSERT、SELECT 和 UPDATE 语句,这些语句被格式化以供 MyBatis 或 Spring 使用。最常见的用例是生成语句和一组匹配的参数,MyBatis 可以直接使用它们。该库还将生成与 Spring JDBC 模板兼容的语句和参数对象。

该库通过实现一个类似 SQL 的 DSL 来工作,该 DSL 创建一个包含完整 SQL 语句和该语句所需的任何参数的对象。 SQL 语句对象可以直接被 MyBatis 用作映射器方法的参数。

该库将生成这些类型的 SQL 语句:

  • COUNT 语句 - 返回 Long 值的专用 SELECT 语句
  • 带有灵活 WHERE 子句的 DELETE 语句
  • 几种类型的 INSERT 语句:
    • 使用从相应对象提供的值插入单行的语句
    • 使用直接在语句中提供的值插入单行的语句
    • 使用多个 VALUES 子句插入多行的语句
    • 使用 JDBC 批处理插入多行的语句
    • 使用 SELECT 语句的结果插入表的语句
  • SELECT 语句具有灵活的列列表、灵活的 WHERE 子句,并支持 distinct、“group by”、join、union、“order by”等。
  • 带有灵活 WHERE 子句和灵活 SET 子句的 UPDATE 语句

该库主要目标是:

  1. 类型安全 - 在可能的范围内,库将确保参数类型与数据库列类型匹配
  2. 富有表现力 - 语句的构建方式清楚地传达了它们的含义(感谢 Hamcrest 的一些启发)
  3. 灵活 - 可以使用 and、or 和嵌套条件的任意组合来构建 where 子句
  4. 可扩展 - 该库将为 MyBatis3、Spring JDBC 模板或普通 JDBC 呈现语句。它也可以扩展到为其他框架生成子句。如果没有任何内置条件足以满足您的需求,则可以轻松添加自定义 where 条件。
  5. 小 - 库是一个需要添加的小依赖项。它没有传递依赖。

这个库源于创建一个实用程序的愿望,该实用程序可用于改进 MyBatis Generator 生成的代码,但该库可以单独使用,只需要很少的设置。

2、MyBatis 动态 SQL 快速入门

使用 MyBatis 动态 SQL 需要以下步骤:

  1. 创建表和列对象
  2. (对于 MyBatis3)创建mappers(基于 XML 或 Java)
  3. 编写和使用 SQL

出于讨论的目的,我们将展示如何使用该库在此表上执行 CRUD 操作:

create table Person (
    id int not null,
    first_name varchar(30) not null,
    last_name varchar(30) not null,
    birth_date date not null,
    employed varchar(3) not null,
    occupation varchar(30) null,
    address_id int not null,
    primary key(id)
);

我们还将创建一个简单的 Java 类来表示表中的一行:

package examples.simple;

import java.util.Date;

public class PersonRecord {
    private Integer id;
    private String firstName;
    private LastName lastName;
    private Date birthDate;
    private Boolean employed;
    private String occupation;
    private Integer addressId;
    
    // getters and setters omitted
}

2.1、定义表和列

org.mybatis.dynamic.sql.SqlTable 类用于定义一个表。表定义包括表的实际名称(包括架构或目录,如果合适)。如果需要,可以在 select 语句中应用表别名。您的表应该通过扩展 SqlTable 类来定义。

org.mybatis.dynamic.sql.SqlColumn 类用于定义在库中使用的列。 SqlColumns 应该使用 SqlTable 中的构建器方法创建。列定义包括:

  1. Java 类型
  2. 实际的列名(可以在 select 语句中应用别名)
  3. JDBC 类型
  4. 可选)如果不需要默认类型处理程序,则在 MyBatis 中使用的类型处理程序的名称

我们建议使用以下使用模式以提供最大的灵活性。这种模式将允许您以看起来像自然 SQL 的“qualified”或“un-qualified”方式使用表名和列名。例如,下面的列可以称为 firstName 或 person.firstName。

package examples.simple;

import java.sql.JDBCType;
import java.util.Date;

import org.mybatis.dynamic.sql.SqlColumn;
import org.mybatis.dynamic.sql.SqlTable;

public final class PersonDynamicSqlSupport {
    public static final Person person = new Person();
    public static final SqlColumn<Integer> id = person.id;
    public static final SqlColumn<String> firstName = person.firstName;
    public static final SqlColumn<LastName> lastName = person.lastName;
    public static final SqlColumn<Date> birthDate = person.birthDate;
    public static final SqlColumn<Boolean> employed = person.employed;
    public static final SqlColumn<String> occupation = person.occupation;
    public static final SqlColumn<Integer> addressId = person.addressId;

    public static final class Person extends SqlTable {
        public final SqlColumn<Integer> id = column("id", JDBCType.INTEGER);
        public final SqlColumn<String> firstName = column("first_name", JDBCType.VARCHAR);
        public final SqlColumn<LastName> lastName = column("last_name", JDBCType.VARCHAR, "examples.simple.LastNameTypeHandler");
        public final SqlColumn<Date> birthDate = column("birth_date", JDBCType.DATE);
        public final SqlColumn<Boolean> employed = column("employed", JDBCType.VARCHAR, "examples.simple.YesNoTypeHandler");
        public final SqlColumn<String> occupation = column("occupation", JDBCType.VARCHAR);
        public final SqlColumn<Integer> addressId = column("address_id", JDBCType.INTEGER);

        public Person() {
            super("Person");
        }
    }
}

2.2、创建 MyBatis3 Mappers

该库将创建将用作 MyBatis 映射器输入的类。这些类包括生成的 SQL,以及与生成的 SQL 匹配的参数集。两者都是 MyBatis 所必需的。这些对象旨在成为 MyBatis 映射器方法的唯一参数。

该库可以与 XML 和带注释的映射器一起使用,但我们建议在所有情况下都使用 MyBatis 的带注释的映射器支持。唯一需要 XML 的情况是当您编写 JOIN 语句时——在这种情况下,由于 MyBatis 注释在支持连接方面的限制,您需要在 XML 中定义结果映射。

例如,映射器可能如下所示:

package examples.simple;

import java.util.List;
import java.util.Optional;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.mybatis.dynamic.sql.util.SqlProviderAdapter;
import org.mybatis.dynamic.sql.util.mybatis3.CommonCountMapper;
import org.mybatis.dynamic.sql.util.mybatis3.CommonDeleteMapper;
import org.mybatis.dynamic.sql.util.mybatis3.CommonInsertMapper;
import org.mybatis.dynamic.sql.util.mybatis3.CommonUpdateMapper;

@Mapper
public interface PersonMapper extends CommonCountMapper, CommonDeleteMapper, CommonInsertMapper<PersonRecord>, CommonUpdateMapper {

    @SelectProvider(type = SqlProviderAdapter.class, method = "select")
    @Results(id = "PersonResult", value = {
            @Result(column = "A_ID", property = "id", jdbcType = JdbcType.INTEGER, id = true),
            @Result(column = "first_name", property = "firstName", jdbcType = JdbcType.VARCHAR),
            @Result(column = "last_name", property = "lastName", jdbcType = JdbcType.VARCHAR, typeHandler = LastNameTypeHandler.class),
            @Result(column = "birth_date", property = "birthDate", jdbcType = JdbcType.DATE),
            @Result(column = "employed", property = "employed", jdbcType = JdbcType.VARCHAR, typeHandler = YesNoTypeHandler.class),
            @Result(column = "occupation", property = "occupation", jdbcType = JdbcType.VARCHAR),
            @Result(column = "address_id", property = "addressId", jdbcType = JdbcType.INTEGER)
    })
    List<PersonRecord> selectMany(SelectStatementProvider selectStatement);

    @SelectProvider(type = SqlProviderAdapter.class, method = "select")
    @ResultMap("PersonResult")
    Optional<PersonRecord> selectOne(SelectStatementProvider selectStatement);
}

此映射器为表实现了完整的 CRUD 功能。基础接口 CommonCountMapper、CommonDeleteMapper 等提供插入、更新、删除和计数功能。由于自定义结果映射,只有选择方法必须编写。

请注意,如果插入生成的密钥,CommonInsertMapper 接口将不会正确返回生成的密钥。如果您需要生成的密钥支持,请参阅 INSERT 语句的文档页面以获取有关如何实现此类支持的详细信息。

2.3、用 MyBatis3 执行 SQL

在服务类中,您可以使用生成的语句作为映射器方法的输入。以下是 examples.simple.PersonMapperTest 中的一些示例:

@Test
void testGeneralSelect() {
    try (SqlSession session = sqlSessionFactory.openSession()) {
        PersonMapper mapper = session.getMapper(PersonMapper.class);

        SelectStatementProvider selectStatement = select(id.as("A_ID"), firstName, lastName, birthDate, employed,
            occupation, addressId)
        .from(person)
        .where(id, isEqualTo(1))
        .or(occupation, isNull())
        .build()
        .render(RenderingStrategies.MYBATIS3);

        List<PersonRecord> rows = mapper.selectMany(selectStatement);
        assertThat(rows).hasSize(3);
    }
}

@Test
void testGeneralDelete() {
    try (SqlSession session = sqlSessionFactory.openSession()) {
        PersonMapper mapper = session.getMapper(PersonMapper.class);

        DeleteStatementProvider deleteStatement = deleteFrom(person)
        .where(occupation, isNull())
        .build()
        .render(RenderingStrategies.MYBATIS3);

        int rows = mapper.delete(deleteStatement);
        assertThat(rows).isEqualTo(2);
    }
}

如果你使用 MyBatis 生成器,生成器将在这样的映射器中创建几个额外的实用方法,这将提高它的实用性。您可以通过查看 github.com/mybatis/myb… 的完整示例来查看 MyBatis 生成器创建的代码类型的完整示例简单的

3、数据库对象表示

MyBatis Dynamic SQL 使用代表关系表或视图的 Java 对象。

3.1、表或视图表示

org.mybatis.dynamic.sql.SqlTable 类用于表示数据库中的表或视图。 SqlTable 包含一个名称,以及代表表或视图中的列的 SqlColumn 对象的集合。

SQL 中的表或视图名称由三个部分组成:

  1. The catalog - 这是可选的,很少在 Microsoft SQL Server 之外使用。如果未指定,将使用默认目录 - 许多数据库只有一个目录
  2. The schema - 这是可选的,但经常被指定。如果未指定,将使用默认架构
  3. The table name - 这是必需的

名称的典型示例如下:

  • "dbo..bar" - 带有目录 (dbo) 和表名 (bar) 的名称。这对于 SQL Server 来说很典型
  • "foo.bar" - 带有schema (foo) 和table name (bar) 的名称。当您要访问不在默认模式中的表时,这在许多数据库中很常见
  • "bar" - 一个只有name (bar)的名称。这将访问连接的默认目录和架构中的表或视图

在 MyBatis Dynamic SQL 中,表名或视图名可以通过不同的方式指定:

  1. 名称可以是常量字符串
  2. 名称可以在运行时根据 catalog and/or schema supplier functions和常量表名称计算
  3. 可以在运行时使用name supplier function计算名称

3.2、常量名称

当您使用带有单个 String 参数的 SqlTable 构造函数时,将使用常量名称。例如:

public class MyTable extends SqlTable {
    public MyTable() {
        super("MyTable");
    }
}

要么

public class MyTable extends SqlTable {
    public MyTable() {
        super("MySchema.MyTable");
    }
}

3.3、动态Catalog and/or Schema名称

MyBatis Dynamic SQL 允许您动态指定目录和/或模式。这对于架构可能因不同用户或环境而改变的应用程序,或者如果您使用不同的架构来分片数据库的应用程序非常有用。当您使用接受一个或多个 java.util.function.Supplier 参数的 SqlTable 构造函数时,将使用动态名称。

例如,假设您想根据系统属性的值更改架构。你可以写一个这样的类:

public class SchemaSupplier {
    public static final String schema_property = "schemaToUse";

    public static Optional<String> schemaPropertyReader() {
        return Optional.ofNullable(System.getProperty(schema_property));
    }
}

此类有一个静态方法 schemaPropertyReader,它将返回一个 Optional 包含系统属性的值。然后,您可以在 SqlTable 的构造函数中引用此方法,如下所示:

public static final class User extends SqlTable {
    public User() {
        super(SchemaSupplier::schemaPropertyReader, "User");
    }
}

每当引用该表以呈现 SQL 时,将根据系统属性的当前值计算名称。

有两个构造函数可用于动态名称:

  1. 一个接受Supplier<Optional<String>> 作为schema, 和 String 作为名称的构造函数。此构造函数假定catalog始终为空或未使用
  2. 一个构造函数,接受 Supplier<Optional<String>> 作为catalog,Supplier<Optional<String>> 作为schema,和 String f作为名称

如果您使用的是 Microsoft SQL Server,并且想要使用动态catalog名称并忽略schema,那么您应该使用第二个构造函数,如下所示:

public static final class User extends SqlTable {
    public User() {
        super(CatalogSupplier::catalogPropertyReader, Optional::empty, "User");
    }
}

下表显示了如何在所有suppliers组合中计算名称:

Catalog Supplier ValueSchema Supplier ValueNameFully Qualified Name
“MyCatalog”“MySchema”“MyTable”“MyCatalog.MySchema.MyTable”
<empty>“MySchema”“MyTable”“MySchema.MyTable”
“MyCatalog”<empty>“MyTable”“MyCatalog..MyTable”
<empty><empty>“MyTable”“MyTable”

3.4、完全动态的名称

MyBatis Dynamic SQL 允许你动态指定一个完整的表名。这对于数据库与代表整体不同分片的不同表分片的应用程序很有用。当您使用接受单个 java.util.function.Supplier 参数的 SqlTable 构造函数时,将使用动态名称。

例如,假设您想根据系统属性的值更改名称。你可以写一个这样的类:

public class NameSupplier {
    public static final String name_property = "nameToUse";

    public static String namePropertyReader() {
        return System.getProperty(name_property);
    }
}

此类有一个静态方法 namePropertyReader,它将返回一个包含系统属性值的字符串。然后,您可以在 SqlTable 的构造函数中引用此方法,如下所示:

public static final class User extends SqlTable {
    public User() {
        super(NameSupplier::namePropertyReader);
    }
}

每当引用该表以呈现 SQL 时,将根据系统属性的当前值计算名称。

3.5、别名表

在连接查询中,指定表别名通常是一个好习惯。 select 语句支持以类似于自然 SQL 的方式在每个查询中指定表别名。例如:

SelectStatementProvider selectStatement = select(orderMaster.orderId, orderDate, orderLine.lineNumber, itemMaster.description, orderLine.quantity)
            .from(orderMaster, "om")
            .join(orderLine, "ol").on(orderMaster.orderId, equalTo(orderLine.orderId))
            .join(itemMaster, "im").on(orderLine.itemId, equalTo(itemMaster.itemId))
            .where(orderMaster.orderId, isEqualTo(2))
            .build()
            .render(RenderingStrategies.MYBATIS3);

在这样的查询中,库将在呈现查询时自动将表别名附加到列名。在内部,列的别名是通过在查询模型中维护的 HashMap 中查找关联表来确定的。如果您不指定表别名,库将自动在连接查询中附加表名。

不幸的是,这种策略对于自联接失败了。当有子查询时,它也会变得混乱。想象一下这样的查询:

    SelectStatementProvider selectStatement = select(user.userId, user.userName, user.parentId)
            .from(user, "u1")
            .join(user, "u2").on(user.userId, equalTo(user.parentId))
            .where(user.userId, isEqualTo(4))
            .build()
            .render(RenderingStrategies.MYBATIS3);

在此查询中,不清楚每列使用用户表的哪个实例,并且用户表的 HashMap 中只有一个条目 - 因此只有一个在 select 语句中指定的别名有效。有两种方法可以解决这个问题。

第一种是简单地创建 User SqlTable 对象的另一个实例。使用这种方法可以很清楚地知道哪一列属于表的哪个实例,并且库可以很容易地计算出别名:

User user1 = new User();
    User user2 = new User();
    SelectStatementProvider selectStatement = select(user1.userId, user1.userName, user1.parentId)
            .from(user1, "u1")
            .join(user2, "u2").on(user1.userId, equalTo(user2.parentId))
            .where(user2.userId, isEqualTo(4))
            .build()
            .render(RenderingStrategies.MYBATIS3);

从版本 1.3.1 开始,有一种新方法可以在表对象本身中指定别名。这允许您将别名移出 select 语句。

User user1 = user.withAlias("u1");
    User user2 = user.withAlias("u2");

    SelectStatementProvider selectStatement = select(user1.userId, user1.userName, user1.parentId)
            .from(user1)
            .join(user2).on(user1.userId, equalTo(user2.parentId))
            .where(user2.userId, isEqualTo(4))
            .build()
            .render(RenderingStrategies.MYBATIS3);

要启用此支持,您的表对象应扩展 org.mybatis.dynamic.sql.AliasableSqlTable 而不是 org.mybatis.dynamic.sql.SqlTable,如下所示:

public static final class User extends AliasableSqlTable<User> {
        public final SqlColumn<Integer> userId = column("user_id", JDBCType.INTEGER);
        public final SqlColumn<String> userName = column("user_name", JDBCType.VARCHAR);
        public final SqlColumn<Integer> parentId = column("parent_id", JDBCType.INTEGER);

        public User() {
            super("User", User::new);
        }
    }

如果您使用别名表对象,并且还在 select 语句中指定了别名,则 select 语句中的别名将覆盖表对象中的别名。

3.6、列表示

org.mybatis.dynamic.sql.SqlColumn 类用于表示表或视图中的列。 SqlColumn 始终与 SqlTable 相关联。在其最基本的形式中,SqlColumn 类包含一个名称和对与其关联的 SqlTable 的引用。表引用是必需的,以便可以在呈现阶段将表别名应用于列。

SqlColumn 将根据应用于 SQL 语句的 RenderingStrategy 在 SQL 中呈现。通常,呈现策略会生成一个字符串,该字符串表示您使用的任何 SQL 引擎中的参数标记。例如,MyBatis3 参数标记被格式化为“#{some_attribute}”。默认情况下,所有列都使用相同的策略呈现。该库提供了适用于多个 SQL 执行引擎的渲染策略,包括 MyBatis3 和 Spring JDBC 模板。

在某些情况下,需要覆盖特定列的呈现策略 - 因此 SqlColumn 类支持为列指定呈现策略,该策略将覆盖应用于语句的呈现策略。这个用例的一个很好的例子是 PostgreSQL。在该数据库中,当插入或更新 JSON 字段时,需要将字符串“::jsonb”添加到准备好的语句参数标记中,但对于其他字段则不需要。基于列的渲染策略可以实现这一点。

SqlColumn 类具有额外的可选属性,这些属性对于 SQL 呈现很有用——尤其是在 MyBatis3 中。这些包括:

  • 列的 java.sql.JDBCType。这将呈现到与 MyBatis3 兼容的参数标记中 - 这有助于选择类型处理程序以及插入或更新支持 null 的字段
  • 包含类型处理程序的字符串 - 类型处理程序别名或类型处理程序的完全限定类型。这将被渲染到 MyBatis3 兼容的参数标记中

如果您不使用 MyBatis3,则无需指定 JDBC 类型或类型处理程序,因为其他渲染策略会忽略这些属性。

最后,SqlColumn 类具有指定用于不同 SQL 语句的列别名或排序顺序的方法。

4、WHERE 子句支持

该库支持创建非常灵活的 WHERE 子句。 WHERE 子句可以添加到 DELETE、SELECT 和 UPDATE 语句中。 WHERE 子句也可以单独用于其他手工编码的 SQL。

4.1、简单的 WHERE 子句

最简单的 WHERE 子句是这种形式:

  SelectStatementProvider selectStatement = select(count())
                .from(simpleTable)
                .where(id, isEqualTo(3))
                .build()
                .render(RenderingStrategies.MYBATIS3);

该库附带了可以在 WHERE 子句中使用的各种条件,包括“in”、“like”、“between”、“isNull”、“isNotNull”和所有正常的比较运算符。例如:

 SelectStatementProvider selectStatement = select(count())
                .from(simpleTable)
                .where(id, isBetween(3).and(6))
                .build()
                .render(RenderingStrategies.MYBATIS3);
SelectStatementProvider selectStatement = select(count())
                .from(simpleTable)
                .where(id, isIn(3,4,5))
                .build()
                .render(RenderingStrategies.MYBATIS3);
SelectStatementProvider selectStatement = select(count())
                .from(simpleTable)
                .where(id, isNotNull())
                .build()
                .render(RenderingStrategies.MYBATIS3);

4.2、复杂的 WHERE 子句

条件几乎可以以任何组合“与”和“或”。例如:

        SelectStatementProvider selectStatement = select(count())
                .from(simpleTable, "a")
                .where(id, isGreaterThan(2))
                .or(occupation, isNull(), and(id, isLessThan(6)))
                .build()
                .render(RenderingStrategies.MYBATIS3);

4.3、子查询

大多数条件还支持子查询。例如:

 SelectStatementProvider selectStatement = select(column1.as("A_COLUMN1"), column2)
                .from(table, "a")
                .where(column2, isIn(select(column2).from(table).where(column2, isEqualTo(3))))
                .or(column1, isLessThan(d))
                .build()
                .render(RenderingStrategies.MYBATIS3);

4.4、独立Where 子句

虽然很少见,但如果您更愿意为语句的其余部分编写自己的 SQL,则可以单独使用 where 子句支持。这样做可能有几个原因 - 主要是如果库不支持您想要使用的某些 SQL 或 MyBatis 功能。一个很好的例子是,如果您想将其他 SQL 附加到库生成的 SQL 中。如果要使用独立的 where 子句,可以编写如下所示的映射器方法:

    @Select({
        "select id, animal_name, brain_weight, body_weight",
        "from AnimalData",
        "${whereClause}"
    })
    @ResultMap("AnimalDataResult")
    List<AnimalData> selectWithWhereClause(WhereClauseProvider whereClause);

您可以构建一个独立的 where 子句并像这样调用您的映射器:

   WhereClauseProvider whereClause = where(id, isNotBetween(10).and(60))
            .build()
            .render(RenderingStrategies.MYBATIS3);

    List<AnimalData> animals = mapper.selectWithWhereClause(whereClause);

当语句不需要其他参数并且不涉及表别名时,此方法效果很好。如果您还有其他需求,请参阅以下内容。

4.4.1、表别名

如果您需要在生成的 where 子句中使用表别名,您可以使用 TableAliasCalculator 类在 render 方法中提供它。例如,如果您有这样的映射器:

    @Select({
        "select a.id, a.animal_name, a.brain_weight, a.body_weight",
        "from AnimalData a",
        "${whereClause}"
    })
    @ResultMap("AnimalDataResult")
    List<AnimalData> selectWithWhereClauseAndAlias(WhereClauseProvider whereClause);

然后,您可以像这样在 render 方法上为生成的 WHERE 子句指定别名:

WhereClauseProvider whereClause = where(id, isEqualTo(1), or(bodyWeight, isGreaterThan(1.0)))
            .build()
            .render(RenderingStrategies.MYBATIS3, TableAliasCalculator.of(animalData, "a"));

    List<AnimalData> animals = mapper.selectWithWhereClauseAndAlias(whereClause);

您更有可能将表别名与手动编码连接一起使用,其中表别名不止于此。在这种情况下,您将 Map<SqlTable, String> 提供给 TableAliasCalculator,它为 WHERE 子句中涉及的每个表提供一个别名。

4.4.2、处理多个参数

默认情况下,WHERE 子句渲染器假定渲染的 WHERE 子句将是映射器方法的唯一参数。这并非总是如此。例如,假设您有一个这样的分页查询(这是 HSQLDB 语法):

@Select({
        "select id, animal_name, brain_weight, body_weight",
        "from AnimalData",
        "${whereClauseProvider.whereClause}",
        "order by id",
        "OFFSET #{offset,jdbcType=INTEGER} LIMIT #{limit,jdbcType=INTEGER}"
    })
    @ResultMap("AnimalDataResult")
    List<AnimalData> selectWithWhereClauseLimitAndOffset(@Param("whereClauseProvider") WhereClauseProvider whereClause,
            @Param("limit") int limit, @Param("offset") int offset);

在这个映射器方法中有三个参数。所以在这种情况下,有必要告诉 WHERE 渲染的参数名称来使用 for 渲染的 where 子句。该代码如下所示:

WhereClauseProvider whereClause = where(id, isLessThan(60))
            .build()
            .render(RenderingStrategies.MYBATIS3, "whereClauseProvider");
            
    List<AnimalData> animals = mapper.selectWithWhereClauseLimitAndOffset(whereClause, 5, 15);

请注意,字符串 whereClauseProvider 既用作映射器 @Param 注释中的参数名称,也用作渲染方法中的参数名称。

render 方法还有一个重写,它接受一个 TableAliasCalculator 和一个参数名称。

5、Where条件

MyBatis 动态 SQL 支持多种 where 子句条件。所有条件都可以与“and”和“or”运算符组合以创建任意复杂的 where 子句。

在以下示例中:

  • “x”和“y”是将呈现为准备好的语句参数的值。生成的 SQL 以与目标运行时(MyBatis 或 Spring)兼容的格式呈现,但为了简单起见,我们将显示标准的准备语句参数标记。
  • “foo” 和 “bar” 是 SqlColumn 的实例。

5.1、简单条件

简单条件是最常见的——它们呈现基本的 SQL 运算符。

ConditionExampleResult
Betweenwhere(foo, isBetween(x).and(y))where foo between ? and ?
Equalswhere(foo, isEqualTo(x))where foo = ?
Greater Thanwhere(foo, isGreaterThan(x))where foo > ?
Greater Than or Equalswhere(foo, isGreaterThanOrEqualTo(x))where foo >= ?
Inwhere(foo, isIn(x, y))where foo in (?,?)
In (case insensitive)where(foo, isInCaseInsensitive(x, y))where upper(foo) in (?,?) (the framework will transform the values for x and y to upper case)
Less Thanwhere(foo, isLessThan(x))where foo < ?
Less Than or Equalswhere(foo, isLessThanOrEqualTo(x))where foo <= ?
Likewhere(foo, isLike(x))where foo like ? (the framework DOES NOT add the SQL wild cards to the value - you will need to do that yourself)
Like (case insensitive)where(foo, isLikeCaseInsensitive(x))where upper(foo) like ? (the framework DOES NOT add the SQL wild cards to the value - you will need to do that yourself, the framework will transform the value of x to upper case)
Not Betweenwhere(foo, isNotBetween(x).and(y))where foo not between ? and ?
Not Equalswhere(foo, isNotEqualTo(x))where foo <> ?
Not Inwhere(foo, isNotIn(x, y))where foo not in (?,?)
Not In (case insensitive)where(foo, isNotInCaseInsensitive(x, y))where upper(foo) not in (?,?) (the framework will transform the values for x and y to upper case)
Not Likewhere(foo, isLike(x))where foo not like ? (the framework DOES NOT add the SQL wild cards to the value - you will need to do that yourself)
Not Like (case insensitive)where(foo, isNotLikeCaseInsensitive(x))where upper(foo) not like ? (the framework DOES NOT add the SQL wild cards to the value - you will need to do that yourself, the framework will transform the value of x to upper case)
Not Nullwhere(foo, isNotNull())where foo is not null
Nullwhere(foo, isNull())where foo is null

5.2、子查询

许多条件可以用子查询来呈现。

ConditionExampleResult
Equalswhere(foo, isEqualTo(select(bar).from(table2).where(bar, isEqualTo(x)))where foo = (select bar from table2 where bar = ?)
Greater Thanwhere(foo, isGreaterThan(select(bar).from(table2).where(bar, isEqualTo(x)))where foo > (select bar from table2 where bar = ?)
Greater Than or Equalswhere(foo, isGreaterThanOrEqualTo(select(bar).from(table2).where(bar, isEqualTo(x)))where foo >= (select bar from table2 where bar = ?)
Inwhere(foo, isIn(select(bar).from(table2).where(bar, isLessThan(x)))where foo in (select bar from table2 where bar < ?)
Less Thanwhere(foo, isLessThan(select(bar).from(table2).where(bar, isEqualTo(x)))where foo < (select bar from table2 where bar = ?)
Less Than or Equalswhere(foo, isLessThanOrEqualTo(select(bar).from(table2).where(bar, isEqualTo(x)))where foo <= (select bar from table2 where bar = ?)
Not Equalswhere(foo, isNotEqualTo(select(bar).from(table2).where(bar, isEqualTo(x)))where foo <> (select bar from table2 where bar = ?)
Not Inwhere(foo, isNotIn(select(bar).from(table2).where(bar, isLessThan(x)))where foo not in (select bar from table2 where bar < ?)

5.3、Column比较条件

列比较条件可用于编写比较表中列值的 where 子句。

ConditionExampleResult
Equalswhere(foo, isEqualTo(bar))where foo = bar
Greater Thanwhere(foo, isGreaterThan(bar))where foo > bar
Greater Than or Equalswhere(foo, isGreaterThanOrEqualTo(bar))where foo >= bar
Less Thanwhere(foo, isLessThan(bar))where foo < bar
Less Than or Equalswhere(foo, isLessThanOrEqualTo(bar))where foo <= bar
Not Equalswhere(foo, isNotEqualTo(bar))where foo <> bar

5.4、值转换

所有条件(isNull 和 isNotNull 除外)都支持映射函数,该函数允许您在呈现语句之前转换与条件关联的值。 map 函数的功能类似于 Streams 和 Optionals 上的 JDK 标准 map 函数——它允许您转换值并更改数据类型。

例如,假设您想使用 SQL like 运算符编写通配符搜索。要完成这项工作,您需要将 SQL 通配符附加到搜索值。这可以使用 map 方法直接在条件下完成,如下所示:

List<Animal> search(String searchName) {
        SelectStatementProvider selectStatement=select(id,animalName,bodyWeight,brainWeight)
        .from(animalData)
        .where(animalName,isLike(searchName).map(s->"%"+s+"%"))
        .orderBy(id)
        .build()
        .render(RenderingStrategies.MYBATIS3);
        
        ...
}

您可以看到 map 方法接受一个将 SQL 通配符添加到 searchName 字段的 lambda。如果您使用方法引用,这会更简洁:

List<Animal> search(String searchName){
        SelectStatementProvider selectStatement=select(id,animalName,bodyWeight,brainWeight)
        .from(animalData)
        .where(animalName,isLike(searchName).map(this::appendWildCards))
        .orderBy(id)
        .build()
        .render(RenderingStrategies.MYBATIS3);
}
        
String appendWildCards(String in) {
    return "%" + in + "%";
}

每个条件的映射都接受一个 lambda 表达式,该表达式可用于转换与条件关联的值。 lambda 是标准的 JDK 类型 Function<T,R> ,其中 T 是条件的类型,R 是输出类型。对于大多数情况,这应该很容易理解。异常情况详述如下:

  1. Between 和 NotBetween 条件具有接受一个或两个映射函数的映射方法。如果您传递一个函数,它将应用于条件中的两个值。如果您提供两个函数,那么它们将分别应用于第一个和第二个值。
  2. In 和 NotIn 条件接受单个映射函数,它将应用于条件中的值集合中的所有值。

5.5、可选条件

所有条件都支持可选性——这意味着如果配置的测试通过,它们可以被配置为呈现到最终的 SQL 中。可选性是通过标准的“filter”和“map”方法实现的——它们的行为与 java.util.Optional 中的“filter”和“map”方法非常相似。一般来说,如果一个条件的“过滤”方法不满足,那么这个条件就不会被渲染。 “map”方法可用于在条件呈现之前更改条件中的值。

例如,您可以编写如下搜索代码:

    public List<AnimalData> searchPeople(String animalName_, Double bodyWeight_, Double brainWeight_) {
        ...
        SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight)
                .from(animalData)
                .where(animalName, isEqualTo(animalName_).filter(Objects::nonNull))
                .and(bodyWeight, isEqualTo(bodyWeight_).filter(Objects::nonNull))
                .and(brainWeight, isEqualTo(brainWeight_).filter(Objects::nonNull))
                .build()
                .render(RenderingStrategies.MYBATIS3);
        ...
    }

在此示例中,仅当传递给它们的值不为空时才会呈现三个条件。如果所有三个值都为 null,则不会生成 where 子句。

每个条件都接受一个 lambda 表达式,该表达式可用于确定条件是否应呈现。 lambda 都将是标准 JDK 类型(java.util.function.BooleanSupplier、java.util.function.Predicate 或 java.util.function.BiPredicate,具体取决于条件类型)。下表列出了可选条件并显示了如何使用它们:

ConditionExampleRendering Rules
Betweenwhere(foo, isBetween(x).and(y).filter(BiPredicate))库会将 x 和 y 传递给 BiPredicate 的测试方法。如果 BiPredicate.test(x, y) 返回 true,则条件将呈现
Betweenwhere(foo, isBetween(x).and(y).filter(Predicate))该库将调用 Predicate 的测试方法两次 - 一次使用 x,一次使用 y。如果两个函数调用都返回 true,则条件将呈现
Equalswhere(foo, isEqualTo(x).filter(Predicate))库会将 x 传递给 Predicate 的测试方法。如果 Predicate.test(x) 返回 true,条件将呈现
Greater Thanwhere(id, isGreaterThan(x).filter(Predicate))库会将 x 传递给 Predicate 的测试方法。如果 Predicate.test(x) 返回 true,条件将呈现
Greater Than or Equalswhere(id, isGreaterThanOrEqualTo(x).filter(Predicate))库会将 x 传递给 Predicate 的测试方法。如果 Predicate.test(x) 返回 true,条件将呈现
Less Thanwhere(id, isLessThan(x).filter(Predicate))库会将 x 传递给 Predicate 的测试方法。如果 Predicate.test(x) 返回 true,条件将呈现
Less Than or Equalswhere(id, isLessThanOrEqualTo(x).filter(Predicate))库会将 x 传递给 Predicate 的测试方法。如果 Predicate.test(x) 返回 true,条件将呈现
Likewhere(id, isLike(x).filter(Predicate))库会将 x 传递给 Predicate 的测试方法。如果 Predicate.test(x) 返回 true,条件将呈现
Like Case Insensitivewhere(id, isLikeCaseInsensitive(x).filter(Predicate<String>))库会将 x 传递给 Predicate 的测试方法。如果 Predicate.test(x) 返回 true,条件将呈现
Not Betweenwhere(id, isNotBetween(x).and(y).filter(BiPredicate))库会将 x 和 y 传递给 BiPredicate 的测试方法。如果 BiPredicate.test(x, y) 返回 true,则条件将呈现
Not Betweenwhere(foo, isNotBetween(x).and(y).filter(Predicate))该库将调用 Predicate 的测试方法两次 - 一次使用 x,一次使用 y。如果两个函数调用都返回 true,则条件将呈现
Not Equalswhere(id, isNotEqualTo(x).filter(Predicate))库会将 x 传递给 Predicate 的测试方法。如果 Predicate.test(x) 返回 true,条件将呈现
Not Likewhere(id, isNotLike(x).filter(Predicate))库会将 x 传递给 Predicate 的测试方法。如果 Predicate.test(x) 返回 true,条件将呈现
Not Like Case Insensitivewhere(id, isNotLikeCaseInsensitive(x).filter(Predicate<String>))库会将 x 传递给 Predicate 的测试方法。如果 Predicate.test(x) 返回 true,条件将呈现
Not Nullwhere(id, isNotNull().filter(BooleanSupplier)如果 BooleanSupplier.getAsBoolean() 返回 true,则条件将呈现
Nullwhere(id, isNull().filter(BooleanSupplier)如果 BooleanSupplier.getAsBoolean() 返回 true,则条件将呈现

5.5.1、“When Present”条件构建器

该库提供了几种方法,这些方法提供了在检查空值的常见情况下使用的条件。下表列出了每个“当存在”条件构建器方法的呈现规则。

ConditionExampleRendering Rules
Betweenwhere(foo, isBetweenWhenPresent(x).and(y))如果 x 和 y 值均非空,则条件将呈现
Equalswhere(foo, isEqualToWhenPresent(x))如果 x 不为空,则条件将呈现
Greater Thanwhere(id, isGreaterThanWhenPresent(x))如果 x 不为空,则条件将呈现
Greater Than or Equalswhere(id, isGreaterThanOrEqualToWhenPresent(x))如果 x 不为空,则条件将呈现
Less Thanwhere(id, isLessThanWhenPresent(x))如果 x 不为空,则条件将呈现
Less Than orEqualswhere(id, isLessThanOrEqualToWhenPresent(x))如果 x 不为空,则条件将呈现
Likewhere(id, isLikeWhenPresent(x))如果 x 不为空,则条件将呈现
Like Case Insensitivewhere(id, isLikeCaseInsensitiveWhenPresent(x))如果 x 不为空,则条件将呈现
Not Betweenwhere(id, isNotBetweenWhenPresent(x).and(y))如果 x 和 y 值均非空,则条件将呈现
Not Equalswhere(id, isNotEqualToWhenPresent(x))如果 x 不为空,则条件将呈现
Not Likewhere(id, isNotLikeWhenPresent(x))如果 x 不为空,则条件将呈现
Not Like Case Insensitivewhere(id, isNotLikeCaseInsensitiveWhenPresent(x))如果 x 不为空,则条件将呈现

请注意,这些方法只是将“NotNull”过滤器应用于条件。例如:

// the following two lines are functionally equivalent
... where (id, isEqualToWhenPresent(x)) ...
... where (id, isEqualTo(x).filter(Objects::nonNull)) ...

5.5.2、“in”条件下的可选性

“in”和“not in”条件的可选性比其他类型的条件要复杂一些。首先要知道的是,如果值列表为空,则不会呈现“in”或“not in”条件。例如,永远不会呈现像 where name in () 这样的 SQL。因此,“in”条件的可选性更多是关于条件值的可选性。该库附带的函数将过滤掉空值,并将大写字符串值以启用不区分大小写的查询。如果您愿意,可以使用扩展点添加额外的过滤和映射。

我们认为库不会呈现无效 SQL 是一件好事。如果值列表为空,则始终会从渲染中删除“in”条件。但这种立场存在一些危险。因为可以从呈现的 SQL 中删除条件,所以如果列表因任何原因最终为空,则可能会影响比预期更多的行。我们推荐的解决方案是确保您验证列表值 - 特别是如果它们来自直接用户输入。另一种选择是在列表为空时采取一些措施。当您将过滤器应用于值列表或使用内置的“存在时”条件之一时,这可能特别有用。在这种情况下,值列表可能在验证检查后最终为空。

“In”条件支持一个回调,当值列表为空时且就在呈现语句之前将调用该回调。您可以使用回调创建一个条件,当列表为空时将引发异常。例如:

private static <T> IsIn<T> isInRequired(Collection<T> values) {
        return IsIn.of(values).withListEmptyCallback(() -> { throw new RuntimeException("In values cannot be empty"); } );
    }

    // Alternatively, there is a convenience method in the Callback interface
    private static <T> IsIn<T> isInRequired(Collection<T> values) {
        return IsIn.of(values).withListEmptyCallback(Callback.exceptionThrowingCallback("In values cannot be empty"));
    }

下表显示了提供的不同 In 条件以及它们将如何呈现不同的输入集。该表假定以下类型的输入:

  • 示例 1 假设输入列表为 (“foo”, null, “bar”) - 比如 where (name, isIn ("foo", null, "bar"))
  • 示例 2 假设输入列表为 (null) - 例如 where(name, isIn((String)null))
ConditionNulls FilteredStrings Mapped to UppercaseExample 1 RenderingExample 2 Rendering
IsInNoNoname in (‘foo’, null, ‘bar’)name in (null)
IsInWhenPresentYesNoname in (‘foo’, ‘bar’)No Render
IsInCaseInsensitiveNoYesupper(name) in (‘FOO’, null, ‘BAR’)upper(name) in (null)
IsInCaseInsensitiveWhenPresentYesYesupper(name) in (‘FOO’, ‘BAR’)No Render
IsNotInNoNoname not in (‘foo’, null, ‘bar’)name not in (null)
IsNotInWhenPresentYesNoname not in (‘foo’, ‘bar’)No render
IsNotInCaseInsensitiveNoYesupper(name) not in (‘FOO’, null, ‘BAR’)upper(name) not in (null)
IsNotInCaseInsensitiveWhenPresentYesYesupper(name) not in (‘FOO’, ‘BAR’)No Render

如果这些选项都不能满足您的需求,“In”条件还支持值的“map”和“filter”方法。这为您提供了极大的灵活性,可以在呈现条件之前更改或过滤值列表。

例如,假设您想要编写一个接受字符串列表的“in”条件,但您想要过滤掉任何空字符串或空白字符串,并且想要修剪所有字符串。这可以通过如下代码来完成:

SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight)
            .from(animalData)
            .where(animalName, isIn("  Mouse", "  ", null, "", "Musk shrew  ")
                    .filter(Objects::nonNull)
                    .map(String::trim)
                    .filter(st -> !st.isEmpty()))
            .orderBy(id)
            .build()
            .render(RenderingStrategies.MYBATIS3);

这段代码有点麻烦,所以如果这是一个常见的用例,你可以构建一个 IsIn 条件的特化,如下所示:

import org.mybatis.dynamic.sql.SqlBuilder;

public class MyInCondition {
    public static IsIn<String> isIn(String... values) {
        return SqlBuilder.isIn(values)
               .filter(Objects::nonNull)
               .map(String::trim)
               .filter(st -> !st.isEmpty());
    }
}

然后可以在查询中使用该条件,如下所示:

    SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight)
            .from(animalData)
            .where(animalName, MyInCondition.isIn("  Mouse", "  ", null, "", "Musk shrew  "))
            .orderBy(id)
            .build()
            .render(RenderingStrategies.MYBATIS3);

6、select语句

Select 语句是最复杂的 SQL 语句。这个库复制了最常见的 select 语句的语法,但故意不涵盖所有可能性。

一般来说,支持以下内容:

  1. select语句的典型部分包括 SELECT、DISTINCT、FROM、JOIN、WHERE、GROUP BY、UNION、UNION ALL、ORDER BY
  2. 表可以为每个 select 语句起别名
  3. 每个 select 语句可以为列设置别名
  4. 对聚合的一些支持(avg、min、max、sum)
  5. INNER、LEFT OUTER、RIGHT OUTER、FULL OUTER 类型的等值连接
  6. where 子句中的子查询。例如 where foo in (select foo from foos where id < 36)

目前,该库不支持以下内容:

  1. WITH 表达式
  2. HAVING 表达式
  3. 从另一个选择中选择。例如 select count(*) from (select foo from foos where id < 36)
  4. INTERSECT, EXCEPT, 等.

WHERE 子句的用户指南页面显示了许多不同类型的 SELECT 语句的示例,这些语句具有不同的 WHERE 子句复杂性,包括对子查询的支持。我们将在这里只展示一个示例,包括一个 ORDER BY 子句:

    SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight)
            .from(animalData)
            .where(id, isIn(1, 5, 7))
            .and(bodyWeight, isBetween(1.0).and(3.0))
            .orderBy(id.descending(), bodyWeight)
            .build()
            .render(RenderingStrategies.MYBATIS3);

    List<AnimalData> animals = mapper.selectMany(selectStatement);

WHERE 和 ORDER BY 子句是可选的。

6.1、Joins

该库支持生成 equijoin 语句 - 由列匹配定义的连接。例如:

SelectStatementProvider selectStatement = select(orderMaster.orderId, orderDate, orderDetail.lineNumber, orderDetail.description, orderDetail.quantity)
            .from(orderMaster, "om")
            .join(orderDetail, "od").on(orderMaster.orderId, equalTo(orderDetail.orderId))
            .build()
            .render(RenderingStrategies.MYBATIS3);

请注意,如果需要,您可以为表指定别名。如果不指定别名,则在生成的 SQL 中将使用完整的表名。

可以在单个语句中连接多个表。例如:

   SelectStatementProvider selectStatement = select(orderMaster.orderId, orderDate, orderLine.lineNumber, itemMaster.description, orderLine.quantity)
            .from(orderMaster, "om")
            .join(orderLine, "ol").on(orderMaster.orderId, equalTo(orderLine.orderId))
            .join(itemMaster, "im").on(orderLine.itemId, equalTo(itemMaster.itemId))
            .where(orderMaster.orderId, isEqualTo(2))
            .build()
            .render(RenderingStrategies.MYBATIS3);

连接查询可能需要您在 XML 中定义一个 MyBatis 结果映射。这是唯一需要 XML 的实例。这是由于映射集合时 MyBatis 注释的限制。

该库支持四种连接类型:

  1. .join(...) is an INNER join
  2. .leftJoin(...) is a LEFT OUTER join
  3. .rightJoin(...) is a RIGHT OUTER join
  4. .fullJoin(...) is a FULL OUTER join

6.2、Union Queries

该库支持生成 UNION 和 UNION ALL 查询。例如:

SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight)
            .from(animalData)
            .union()
            .selectDistinct(id, animalName, bodyWeight, brainWeight)
            .from(animalData)
            .orderBy(id)
            .build()
            .render(RenderingStrategies.MYBATIS3);

可以将任意数量的 SELECT 语句添加到 UNION 查询中。只允许使用一个 ORDER BY 短语。

6.3、select语句的 MyBatis 映射器

SelectStatementProvider 对象可以直接用作 MyBatis 映射器方法的参数。如果您使用带注释的映射器,则 select 方法应如下所示(请注意,我们建议使用共享结果映射编写“selectMany”和“selectOne”方法):

import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.SelectProvider;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.mybatis.dynamic.sql.util.SqlProviderAdapter;

...
    @SelectProvider(type=SqlProviderAdapter.class, method="select")
    @Results(id="AnimalDataResult", value={
        @Result(column="id", property="id", id=true),
        @Result(column="animal_name", property="animalName"),
        @Result(column="brain_weight", property="brainWeight"),
        @Result(column="body_weight", property="bodyWeight")
    })
    List<AnimalData> selectMany(SelectStatementProvider selectStatement);

    @SelectProvider(type=SqlProviderAdapter.class, method="select")
    @ResultMap("AnimalDataResult")
    AnimalData selectOne(SelectStatementProvider selectStatement);
...

6.4、用于join语句的 XML 映射器

如果您正在编写连接代码,则可能需要编写 XML 映射器来定义结果映射。这是由于 MyBatis 的限制——注解不能定义集合映射。

如果必须这样做,Java 代码如下所示:

    @SelectProvider(type=SqlProviderAdapter.class, method="select")
    @ResultMap("SimpleJoinResult")
    List<OrderMaster> selectMany(SelectStatementProvider selectStatement);

对应的 XML 如下所示:

<mapper namespace="examples.joins.JoinMapper">
  <resultMap id="SimpleJoinResult" type="examples.joins.OrderMaster">
    <id column="order_id" jdbcType="INTEGER" property="id" />
    <result column="order_date" jdbcType="DATE" property="orderDate" />
    <collection property="details" ofType="examples.joins.OrderDetail">
      <id column="order_id" jdbcType="INTEGER" property="orderId"/>
      <id column="line_number" jdbcType="INTEGER" property="lineNumber"/>
      <result column="description" jdbcType="VARCHAR" property="description"/>
      <result column="quantity" jdbcType="INTEGER" property="quantity"/>
    </collection>
  </resultMap>
</mapper>

请注意,resultMap 是 XML 映射器中的唯一元素。这是我们推荐的做法。

6.5、select语句的 XML 映射器

我们不建议对 select 语句使用 XML 映射器,但如果您想这样做,可以将 SelectStatementProvider 对象直接用作 MyBatis 映射器方法的参数。

如果您使用的是 XML 映射器,那么 Java 界面中的 select 方法应如下所示:

import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;

...
    List<AnimalData> selectMany(SelectStatementProvider selectStatement);
...

XML 元素应如下所示:

  <resultMap id="animalResult" type="examples.animal.data.AnimalData">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="animal_name" jdbcType="VARCHAR" property="animalName" />
    <result column="brain_weight" jdbcType="DOUBLE" property="brainWeight" />
    <result column="body_weight" jdbcType="DOUBLE" property="bodyWeight" />
  </resultMap>

  <select id="selectMany" resultMap="animalResult">
    ${selectStatement}
  </select>

6.6、Order By注意事项

当存在别名列、别名表、联合和连接时,按短语排序可能很难计算。这个库采取了一种相对简单的方法:

  1. 在 ORDER BY 短语中指定 SqlColumn 时,库会将列别名或列名写入 ORDER BY 短语。对于 ORDER BY 短语,表别名(如果有的话)将被忽略。当 ORDER BY 列是选择列表的成员时使用此模式。例如 orderBy(foo)。如果列有别名,那么很容易使用列别名的“任意字符串”方法,如下所示。
  2. 也可以为 ORDER BY 短语中的列显式指定表别名。当存在连接,并且 ORDER BY 列在两个或多个表中,并且 ORDER BY 列不在选择列表中时,请使用此模式。例如 orderBy(sortColumn("t1", foo))。
  3. 如果上述用例都不能满足您的需求,那么您可以指定一个任意字符串来写入呈现的 ORDER BY 短语(参见下面的示例)。

在我们的测试中,这仅在一种情况下导致了问题。当存在外连接并且选择列表包含左右连接列时。在这种情况下,解决方法是为两列提供列别名。

使用列函数(lower、upper 等)时,习惯上为计算列指定别名,这样您将获得可预测的结果集。在这种情况下,将没有用于别名的列。该库支持 ORDER BY 表达式中的任意值,如下所示:

    SelectStatementProvider selectStatement = select(substring(gender, 1, 1).as("ShortGender"), avg(age).as("AverageAge"))
            .from(person, "a")
            .groupBy(substring(gender, 1, 1))
            .orderBy(sortColumn("ShortGender").descending())
            .build()
            .render(RenderingStrategies.MYBATIS3);

在此示例中,子字符串函数用于选择列表和 GROUP BY 表达式。在 ORDER BY 表达式中,我们使用 sortColumn 函数复制为选择列表中的列指定的别名。

6.7、Limit 和 Offset 支持

从 1.1.1 版本开始,select 语句支持分页(或切片)查询的限制和偏移量。您可以指定:

  • Limit only
  • Offset only
  • Both limit and offset

需要注意的是,选择渲染器按原样将限制和偏移子句写入生成的选择语句中。该库不会尝试为不直接支持限制和偏移的数据库规范化这些值。因此,了解目标数据库是否支持limit和offset对于用户来说非常重要。如果目标数据库不支持限制和偏移量,那么使用这种支持很可能会创建具有运行时错误的 SQL。

一个例子如下:

    SelectStatementProvider selectStatement = select(animalData.allColumns())
            .from(animalData)
            .orderBy(id)
            .limit(3)
            .offset(22)
            .build()
            .render(RenderingStrategies.MYBATIS3);

6.8、Fetch First支持

从 1.1.2 版本开始,select 语句支持分页(或切片)查询的优先获取。您可以指定:

  • Fetch first only
  • Offset only
  • Both offset and fetch first

Fetch first 是一种 SQL 标准,大多数数据库都支持它。

一个例子如下:

    SelectStatementProvider selectStatement = select(animalData.allColumns())
            .from(animalData)
            .orderBy(id)
            .offset(22)
            .fetchFirst(3).rowsOnly()
            .build()
            .render(RenderingStrategies.MYBATIS3);

7、复杂查询

1.1.2 版中的增强功能使编写复杂查询的代码变得更加容易。 Select DSL 被实现为一组相关的对象。随着 select 语句的构建,各种类型的中间对象从实现 DSL 的各种方法中返回。 select语句可以通过调用很多中间对象的build()方法来完成。在 1.1.2 版本之前,必须在最后一个中间对象上调用 build()。此限制已被删除,现在可以在任何中间对象上调用 build()。这与其他几项增强功能一起,简化了复杂查询的编码。

例如,假设您要编写对 Person 表的复杂搜索。搜索参数是 id、名字和姓氏。规则是:

  1. 如果输入了 id,则使用 id 并忽略其他搜索参数
  2. 如果没有输入id,则根据其他参数进行模糊搜索

这可以使用如下代码来实现……

public SelectStatementProvider search(Integer targetId, String fName, String lName) {
    var builder = select(id, firstName, lastName)    // (1)
            .from(person)
            .where();    // (2)
        
    if (targetId != null) {    // (3)
        builder
            .and(id, isEqualTo(targetId));
    } else {
        builder
            .and(firstName, isLike(fName).filter(Objects::nonNull).map(s -> "%" + s + "%"))    // (4) (5)
            .and(lastName, isLikeWhenPresent(lName).map(this::addWildcards));    // (6)
    }

    builder
        .orderBy(lastName, firstName)
        .fetchFirst(50).rowsOnly();    // (7)
        
    return builder.build().render(RenderingStrategies.MYBATIS3);    // (8)
}
    
public String addWildcards(String s) {
    return "%" + s + "%";
}

Notes:

  1. 请注意此处使用 var 关键字。如果您使用的是较旧版本的 Java,则实际类型为 QueryExpressionDSL<SelectModel>.QueryExpressionWhereBuilder
  2. 在这里,我们在没有参数的情况下调用 where()。这将设置构建器以在代码中进一步接受条件。如果不添加条件,则不会渲染 where 子句
  3. 这个 if 语句实现了搜索的规则。如果输入了 ID,请使用它。否则,根据名字和姓氏进行模糊搜索。
  4. 如果不满足过滤器(filter),则此行的过滤器方法会将条件标记为不可渲染。
  5. 此行上的 map 语句允许您在将参数值放入参数 Map 之前更改参数值。在这种情况下,我们将 SQL 通配符添加到搜索字符串的开头和结尾——但前提是搜索字符串不为空。如果搜索字符串为空,则不会调用 lambda,也不会渲染条件
  6. 这一行显示了在地图上使用方法引用而不是 lambda。方法引用使您可以更清楚地表达意图。还要注意 isLikeWhenPresent 函数的使用,它是一个应用非空过滤器的内置函数
  7. 限制从搜索返回的行数是个好主意。该库现在支持 fetch first 语法来限制行
  8. 请注意,我们正在从步骤 1 中检索到的中间对象调用 build 方法。不再需要对从选择构建器返回的最后一个对象调用 build

8、Delete语句

Delete 语句由一个表和一个可选的 where 子句组成。构建删除语句的结果是一个 DeleteStatementProvider 对象。例如

    DeleteStatementProvider deleteStatement = deleteFrom(simpleTable)
            .where(occupation, isNull())
            .build()
            .render(RenderingStrategies.MYBATIS3);

您还可以构建不带 where 子句的删除语句。这将删除表中的每一行。例如:

    DeleteStatementProvider deleteStatement = deleteFrom(foo)
            .build()
            .render(RenderingStrategies.MYBATIS3);

8.1、Delete语句的注释映射器

DeleteStatementProvider 对象可以直接用作 MyBatis 映射器方法的参数。如果您使用的是带注释的映射器,则删除方法应如下所示:

import org.apache.ibatis.annotations.DeleteProvider;
import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider;
import org.mybatis.dynamic.sql.util.SqlProviderAdapter;

...
    @DeleteProvider(type=SqlProviderAdapter.class, method="delete")
    int delete(DeleteStatementProvider deleteStatement);
...

8.2、XML Mapper for Delete 语句

我们不建议对删除语句使用 XML 映射器,但如果您想这样做,可以将 DeleteStatementProvider 对象直接用作 MyBatis 映射器方法的参数。

如果您使用的是 XML 映射器,那么 Java 界面中的 delete 方法应如下所示:

import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider;

...
    int delete(DeleteStatementProvider deleteStatement);
...

XML 元素应如下所示:

  <delete id="delete">
    ${deleteStatement}
  </delete>

9、Insert语句

该库将生成各种 INSERT 语句:

  1. 单行插入
  2. 使用单个语句插入多行
  3. 使用 JDBC 批处理插入多行
  4. 一般插入语句
  5. 带有 select 语句的插入

9.1、单行插入

单条记录插入是将单条记录插入表中的语句。该语句的配置与库中的其他语句不同,以便 MyBatis 对生成的密钥的支持能够正常工作。要使用该语句,您必须首先创建一个将映射到数据库行的对象,然后将对象属性映射到数据库中的字段。例如:

...
    SimpleTableRecord row = new SimpleTableRecord();
    row.setId(100);
    row.setFirstName("Joe");
    row.setLastName("Jones");
    row.setBirthDate(new Date());
    row.setEmployed(true);
    row.setOccupation("Developer");

    InsertStatementProvider<SimpleTableRecord> insertStatement = insert(row)
            .into(simpleTable)
            .map(id).toProperty("id")
            .map(firstName).toProperty("firstName")
            .map(lastName).toProperty("lastName")
            .map(birthDate).toProperty("birthDate")
            .map(employed).toProperty("employed")
            .map(occupation).toProperty("occupation")
            .build()
            .render(RenderingStrategies.MYBATIS3);

    int rows = mapper.insert(insertStatement);
...

注意 map 方法。它用于将数据库列映射到要插入的记录的属性。有几种不同的映射可用:

  1. map(column).toNull() 将在列中插入一个空值
  2. map(column).toConstant(constant_value) 将在列中插入一个常量。常量值将完全按照输入的内容写入生成的插入语句
  3. map(column).toStringConstant(constant_value) w将在列中插入一个常量。 constant_value 将写入生成的插入语句中,用单引号括起来(作为 SQL 字符串)
  4. map(column).toProperty(property) 将从记录中插入一个值到列中。该属性的值将作为准备好的语句参数绑定到 SQL 语句
  5. map(column).toPropertyWhenPresent(property, Supplier<?> valueSupplier) 如果值非空,则将记录中的值插入列中。该属性的值将作为准备好的语句参数绑定到 SQL 语句。这用于生成 MyBatis Generator 中定义的“选择性”插入。

9.2、单行插入语句的带注释映射器

InsertStatementProvider 对象可以直接用作 MyBatis 映射器方法的参数。如果您使用的是带注释的映射器,则插入方法应如下所示(必要时为生成的值添加 @Options):

import org.apache.ibatis.annotations.InsertProvider;
import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider;
import org.mybatis.dynamic.sql.util.SqlProviderAdapter;

...
    @InsertProvider(type=SqlProviderAdapter.class, method="insert")
    int insert(InsertStatementProvider<SimpleTableRecord> insertStatement);
...

9.3、单行插入语句的 XML 映射器

我们不建议对插入语句使用 XML 映射器,但如果您想这样做,可以将 InsertStatementProvider 对象直接用作 MyBatis 映射器方法的参数。

如果您使用的是 XML 映射器,则插入方法在 Java 界面中应如下所示:

import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider;

...
    int insert(InsertStatementProvider<SimpleTableRecord> insertStatement);
...

XML 元素应如下所示(必要时为生成的值添加属性):

  <insert id="insert">
    ${insertStatement}
  </insert>

9.4、生成值

MyBatis 支持从单行插入或批量插入返回生成的值。在任何一种情况下,只需适当地配置插入映射器方法即可。例如,要检索计算列的值,请配置您的映射器方法,如下所示:

...
    @InsertProvider(type=SqlProviderAdapter.class, method="insert")
    @Options(useGeneratedKeys=true, keyProperty="row.fullName")
    int insert(InsertStatementProvider<GeneratedAlwaysRecord> insertStatement);
...

重要的是 keyProperty 设置正确。它应始终位于表单 row. 中,其中 是记录类的属性,应使用生成的值进行更新。

9.5、多行插入支持

多行插入是将多行插入到表中的单个插入语句。这可能是向表中插入几行的便捷方式,但它有一些限制:

  1. 由于它是一条 SQL 语句,因此您可以生成相当多的预处理语句参数。例如,假设您想在一个表中插入 1000 条记录,每条记录有 5 个字段。使用多行插入,您将生成一个包含 5000 个参数的 SQL 语句。 JDBC 预处理语句中允许的参数数量是有限制的——这种插入很容易超过这些限制。如果你想插入很多记录,你可能应该使用 JDBC 批量插入来代替(见下文)
  2. 巨大的插入语句的性能可能比您预期的要低。如果要插入许多记录,使用 JDBC 批量插入几乎总是更有效(见下文)。使用批量插入,JDBC 驱动程序可以进行一些使用单个大语句无法实现的优化
  3. 使用多行插入检索生成的值可能是一个挑战。 MyBatis 目前有一些限制,与在需要特殊考虑的多行插入中检索生成的键有关(见下文)

尽管如此,多行插入还是有一些用例——尤其是当您只想在表中插入几条记录而不需要检索生成的键时。在这些情况下,多行插入将是一个简单的解决方案。

多行插入语句如下所示:

    try (SqlSession session = sqlSessionFactory.openSession()) {
        GeneratedAlwaysAnnotatedMapper mapper = session.getMapper(GeneratedAlwaysAnnotatedMapper.class);
        List<GeneratedAlwaysRecord> records = getRecordsToInsert(); // not shown
            
        MultiRowInsertStatementProvider<GeneratedAlwaysRecord> multiRowInsert = insertMultiple(records)
                .into(generatedAlways)
                .map(id).toProperty("id")
                .map(firstName).toProperty("firstName")
                .map(lastName).toProperty("lastName")
                .build()
                .render(RenderingStrategies.MYBATIS3);
            
        int rows = mapper.insertMultiple(multiRowInsert);
    }

9.6、多行插入语句的带注释映射器

MultiRowInsertStatementProvider 对象可以直接用作 MyBatis 映射器方法的参数。如果您使用带注释的映射器,则插入方法应如下所示:

import org.apache.ibatis.annotations.InsertProvider;
import org.mybatis.dynamic.sql.insert.render.MultiRowInsertStatementProvider;
import org.mybatis.dynamic.sql.util.SqlProviderAdapter;

...
    @InsertProvider(type=SqlProviderAdapter.class, method="insertMultiple")
    int insertMultiple(MultiRowInsertStatementProvider<SimpleTableRecord> insertStatement);
...

9.7、多行插入语句的 XML 映射器

我们不建议对插入语句使用 XML 映射器,但如果您想这样做,可以将 MultiRowInsertStatementProvider 对象直接用作 MyBatis 映射器方法的参数。

如果您使用的是 XML 映射器,则插入方法在 Java 界面中应如下所示:

import org.mybatis.dynamic.sql.insert.render.MultiInsertStatementProvider;

...
    int insertMultiple(MultiRowInsertStatementProvider<SimpleTableRecord> insertStatement);
...

XML 元素应如下所示:

  <insert id="insertMultiple">
    ${insertStatement}
  </insert>

9.8、生成值

MyBatis 支持从多行插入语句返回生成的值,但有一些限制。主要限制是 MyBatis 不支持参数对象中的嵌套列表。不幸的是,MultiRowInsertStatementProvider 依赖于嵌套列表。 MyBatis 中的这个限制可能会在未来的某个时候被取消,敬请期待。

不过,您可以配置一个映射器,该映射器将与此库创建的 MultiRowInsertStatementProvider 一起使用。主要思想是从参数映射中分解语句,并将它们作为单独的参数发送到 MyBatis 映射器。例如:

...
    @InsertProvider(type=SqlProviderAdapter.class, method="insertMultipleWithGeneratedKeys")
    @Options(useGeneratedKeys=true, keyProperty="records.fullName")
    int insertMultipleWithGeneratedKeys(String insertStatement, @Param("records") List<GeneratedAlwaysRecord> records);

    default int insertMultipleWithGeneratedKeys(MultiRowInsertStatementProvider<GeneratedAlwaysRecord> multiInsert) {
        return insertMultipleWithGeneratedKeys(multiInsert.getInsertStatement(), multiInsert.getRecords());
    }
...

上面的第一个方法显示了实际的 MyBatis 映射器方法。请注意使用 @Options 注释来指定我们期望生成的值。此外,请注意 keyProperty 设置为 records.fullName - 在这种情况下,fullName 是记录列表中对象的属性。库提供的适配器方法将简单地返回方法调用中提供的 insertStatement。适配器方法要求方法调用中只有一个String参数,并且假定这个String参数是SQL插入语句。参数可以有任何名称,并且可以在方法的参数列表中的任何位置指定。插入语句不需要@Param 注释。但是,如果您愿意,可以指定它。

上面的第二个方法分解 MultiRowInsertStatementProvider 并调用第一个方法。

9.9、批量插入支持

批处理插入是可用于执行 JDBC 批处理的语句的集合。批处理是使用 JDBC 进行批量插入的首选方法。基本思想是您为批量插入配置连接,然后多次执行相同的语句,为每个插入的记录使用不同的值。 MyBatis 有一个很好的 JDBC 批处理抽象,可以很好地处理从这个库生成的语句。批量插入如下所示:

...
    try(SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
        SimpleTableMapper mapper = session.getMapper(SimpleTableMapper.class);
        List<SimpleTableRecord> records = getRecordsToInsert(); // not shown

        BatchInsert<SimpleTableRecord> batchInsert = insert(records)
                .into(simpleTable)
                .map(id).toProperty("id")
                .map(firstName).toProperty("firstName")
                .map(lastName).toProperty("lastName")
                .map(birthDate).toProperty("birthDate")
                .map(employed).toProperty("employed")
                .map(occupation).toProperty("occupation")
                .build()
                .render(RenderingStrategies.MYBATIS3);

        batchInsert.insertStatements().forEach(mapper::insert);

        session.commit();
    }
...

通过将执行器类型设置为 BATCH 来打开 MyBatis 会话很重要。记录在提交时插入。如果要进行中间提交,可以多次调用 commit。

请注意,用于插入单个记录的相同映射器方法现在被执行多次。除了批量插入不支持 toPropertyWhenPresent 映射外,映射方法相同。

9.10、一般插入语句

通用插入用于构建任意插入语句。一般插入不需要单独的记录对象来保存语句的值 - 任何值都可以传递到语句中。这个版本的插入不方便使用 MyBatis 检索生成的密钥——对于那个用例,我们推荐“单记录插入”。然而,对于不返回生成键的 Spring JDBC 模板或 MyBatis 插入,一般插入是完全可以接受的。例如

    GeneralInsertStatementProvider insertStatement = insertInto(animalData)
            .set(id).toValue(101)
            .set(animalName).toStringConstant("Fred")
            .set(brainWeight).toConstant("2.2")
            .set(bodyWeight).toValue(4.5)
            .build()
            .render(RenderingStrategies.MYBATIS3);

注意 set 方法。它用于设置数据库列的值。有几种不同的可能性:

  1. set(column).toNull() 将在列中插入一个空值
  2. set(column).toConstant(constant_value) 将在列中插入一个常量。常量值将完全按照输入的内容写入生成的插入语句
  3. set(column).toStringConstant(constant_value) 将在列中插入一个常量。 constant_value 将写入生成的插入语句中,用单引号括起来(作为 SQL 字符串)
  4. set(column).toValue(value) 将在列中插入一个值。该属性的值将作为准备好的语句参数绑定到 SQL 语句
  5. set(column).toValueWhenPresent(property, Supplier<?> valueSupplier) 如果值为非空,则将值插入列中。该属性的值将作为准备好的语句参数绑定到 SQL 语句。

9.11、通用插入语句的带注释映射器

GeneralInsertStatementProvider 对象可以直接用作 MyBatis 映射器方法的参数。如果您使用带注释的映射器,则插入方法应如下所示:

import org.apache.ibatis.annotations.InsertProvider;
import org.mybatis.dynamic.sql.insert.render.GeneralInsertStatementProvider;
import org.mybatis.dynamic.sql.util.SqlProviderAdapter;

...
    @InsertProvider(type=SqlProviderAdapter.class, method="generalInsert")
    int generalInsert(GeneralInsertStatementProvider insertStatement);
...

9.12、通用插入语句的 XML 映射器

我们不建议对插入语句使用 XML 映射器,但如果您想这样做,可以将 GeneralInsertStatementProvider 对象直接用作 MyBatis 映射器方法的参数。

如果您使用的是 XML 映射器,则插入方法在 Java 界面中应如下所示:

import org.mybatis.dynamic.sql.insert.render.GeneralInsertStatementProvider;

...
    int generalInsert(GeneralInsertStatementProvider insertStatement);
...

XML 元素应如下所示:

  <insert id="generalInsert">
    ${insertStatement}
  </insert>

9.13、Insert with Select

插入选择是插入选择语句结果的 SQL 插入语句。例如:

    InsertSelectStatementProvider insertSelectStatement = insertInto(animalDataCopy)
            .withColumnList(id, animalName, bodyWeight, brainWeight)
            .withSelectStatement(
                select(id, animalName, bodyWeight, brainWeight)
                .from(animalData)
                .where(id, isLessThan(22)))
            .build()
            .render(RenderingStrategies.MYBATIS3);

    int rows = mapper.insertSelect(insertSelectStatement);

列列表是可选的,如果选定的列与表格的布局匹配,则可以将其删除。

9.14、插入选择语句的带注释映射器

InsertSelectStatementProvider 对象可以直接用作 MyBatis 映射器方法的参数。如果您使用带注释的映射器,则插入方法应如下所示:

import org.apache.ibatis.annotations.InsertProvider;
import org.mybatis.dynamic.sql.insert.render.InsertSelectStatementProvider;
import org.mybatis.dynamic.sql.util.SqlProviderAdapter;

...
    @InsertProvider(type=SqlProviderAdapter.class, method="insertSelect")
    int insertSelect(InsertSelectStatementProvider insertSelectStatement);
...

注意MyBatis 不支持重载的mapper 方法名,所以方法名应该和mapper 中的单条记录插入不同。

9.15、插入选择语句的 XML 映射器

我们不建议对插入语句使用 XML 映射器,但如果您想这样做,可以将 InsertSelectStatementProvider 对象直接用作 MyBatis 映射器方法的参数。

如果您使用的是 XML 映射器,则插入方法在 Java 界面中应如下所示:

import org.mybatis.dynamic.sql.insert.render.InsertSelectStatementProvider;

...
    int insertSelect(InsertSelectStatementProvider insertSelectStatement);
...

XML 元素应如下所示:

  <insert id="insertSelect">
    ${insertStatement}
  </insert>

10、Update语句

更新语句由指定要更新的表和列以及可选的 where 子句组成。例如:

    UpdateStatementProvider updateStatement = update(animalData)
            .set(bodyWeight).equalTo(row.getBodyWeight())
            .set(animalName).equalToNull()
            .where(id, isIn(1, 5, 7))
            .or(id, isIn(2, 6, 8), and(animalName, isLike("%bat")))
            .or(id, isGreaterThan(60))
            .and(bodyWeight, isBetween(1.0).and(3.0))
            .build()
            .render(RenderingStrategies.MYBATIS3);

    int rows = mapper.update(updateStatement);

注意 set 方法。它用于设置数据库列的值。有几种设置值的选项:

  1. set(column).equalToNull() 将 null 设置为列
  2. set(column).equalToConstant(String constant) 将一个常量设置为一列。常量值将完全按照输入的内容写入生成的更新语句
  3. set(column).equalToStringConstant(String constant) 将一个常量设置为一列。 constant_value 将写入生成的更新语句中,用单引号括起来(作为 SQL 字符串)
  4. set(column).equalTo(T value) 将在列中设置一个值。该值将作为准备好的语句参数绑定到 SQL 语句
  5. set(column).equalTo(Supplier<T> valueSupplier) 将在列中设置一个值。该值将作为准备好的语句参数绑定到 SQL 语句
  6. set(column).equalToWhenPresent(T value) 如果值为非空,则将值设置到列中。该属性的值将作为准备好的语句参数绑定到 SQL 语句。这用于生成 MyBatis Generator 中定义的“选择性”更新。
  7. set(column).equalToWhenPresent(Supplier<T> valueSupplier) 如果值为非空,则将值设置到列中。该属性的值将作为准备好的语句参数绑定到 SQL 语句。这用于生成 MyBatis Generator 中定义的“选择性”更新。
  8. set(column).equalTo(Buildable<SelectModel> selectModelBuilder) 将子查询的结果设置为列。查询应该只有一列,并且返回的列的类型必须能够被数据库转换,如果它不是相同的类型。这些约束未经库验证。
  9. set(column).equalTo(BasicColumn rightColumn) 将一列的值设置为另一列的值。这对于指定诸如加法、减法等函数也很有用。

您还可以构建没有 where 子句的更新语句。这将更新表中的每一行。例如:

    UpdateStatementProvider updateStatement = update(animalData)
            .set(bodyWeight).equalTo(row.getBodyWeight())
            .set(animalName).equalToNull()
            .build()
            .render(RenderingStrategies.MYBATIS3);

10.1、更新语句的注释映射器

UpdateStatementProvider 对象可以直接用作 MyBatis 映射器方法的参数。如果您使用的是带注释的映射器,则更新方法应如下所示:

import org.apache.ibatis.annotations.UpdateProvider;
import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider;
import org.mybatis.dynamic.sql.util.SqlProviderAdapter;

...
    @UpdateProvider(type=SqlProviderAdapter.class, method="update")
    int update(UpdateStatementProvider updateStatement);
...

10.2、更新语句的 XML 映射器

我们不建议对更新语句使用 XML 映射器,但如果您想这样做,可以将 UpdateStatementProvider 对象直接用作 MyBatis 映射器方法的参数。

如果您使用的是 XML 映射器,则更新方法在 Java 界面中应如下所示:

import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider;

...
    int update(UpdateStatementProvider updateStatement);
...

XML 元素应如下所示:

  <update id="update">
    ${updateStatement}
  </update>

11、子查询支持

该库目前支持以下领域的子查询:

  1. 在 where 子句中 - 使用“exists”运算符和基于列的条件
  2. 在某些插入语句中
  3. 在更新语句中
  4. 在 select 语句的“from”子句中
  5. 在 select 语句的连接子句中

在展示子查询示例之前,了解库如何在 select 语句中生成和应用表限定符非常重要。我们将首先介绍。

11.1、Select 语句中的表限定符

该库尝试自动计算表限定符。如果指定了表限定符,则库将自动在与表关联的所有列上呈现表限定符。例如,使用以下查询:

SelectStatementProvider selectStatement =
    select(id, animalName)
    .from(animalData, "ad")
    .build()
    .render(RenderingStrategies.MYBATIS3);

该库会将 SQL 呈现为:

select ad.id, ad.animal_name
from AnimalData ad

请注意,表格限定符 ad 会自动应用于选择列表中的列。

在连接查询的情况下,指定的表限定符,或者如果未指定表名本身,将用作表限定符。但是,对于子查询的连接,此功能被禁用。

对于子查询,了解自动表限定符的限制很重要。规则如下:

  1. 自动表限定符的范围仅限于单个 select 语句。对于子查询,外部查询的范围与子查询不同。
  2. 限定符可以应用于子查询,但该限定符不会自动应用于任何列

例如,考虑以下查询:

DerivedColumn<Integer> rowNum = DerivedColumn.of("rownum()");

SelectStatementProvider selectStatement =
    select(animalName, rowNum)
    .from(
        select(id, animalName)
        .from(animalData, "a")
        .where(id, isLessThan(22))
        .orderBy(animalName.descending()),
        "b"
    )
    .where(rowNum, isLessThan(5))
    .and(animalName, isLike("%a%"))
    .build()
    .render(RenderingStrategies.MYBATIS3);

呈现的 SQL 将如下所示:

select animal_name, rownum()
from (select a.id, a.animal_name
      from AnimalDate a
      where id < #{parameters.p1}
      order by animal_name desc) b
where rownum() < #{parameters.p2}
  and animal_name like  #{parameters.p3} 

请注意,限定符 a 会自动应用于子查询中的列,而限定符 b 不会应用于任何地方。

如果您的查询需要将子查询限定符应用于外部选择列表中的列,您可以手动将限定符应用于列,如下所示:

DerivedColumn<Integer> rowNum = DerivedColumn.of("rownum()");

SelectStatementProvider selectStatement =
    select(animalName.qualifiedWith("b"), rowNum)
    .from(
        select(id, animalName)
        .from(animalData, "a")
        .where(id, isLessThan(22))
        .orderBy(animalName.descending()),
        "b"
    )
    .where(rowNum, isLessThan(5))
    .and(animalName.qualifiedWith("b"), isLike("%a%"))
    .build()
    .render(RenderingStrategies.MYBATIS3);

在这种情况下,我们手动将限定符 b 应用于外部查询中的列。呈现的 SQL 如下所示:

select b.animal_name, rownum()
from (select a.id, a.animal_name
      from AnimalDate a
      where id < #{parameters.p1}
      order by animal_name desc) b
where rownum() < #{parameters.p2}
  and b.animal_name like  #{parameters.p3} 

11.2、Where 条件中的子查询

库支持以下 where 条件下的子查询:

  • exists
  • notExists
  • isEqualTo
  • isNotEqualTo
  • isIn
  • isNotIn
  • isGreaterThan
  • isGreaterThanOrEqualTo
  • isLessThan
  • isLessThanOrEqualTo

一个存在子查询的例子如下:

SelectStatementProvider selectStatement = select(itemMaster.allColumns())
        .from(itemMaster, "im")
        .where(exists(
                select(orderLine.allColumns())
                .from(orderLine, "ol")
                .where(orderLine.itemId, isEqualTo(itemMaster.itemId))
        ))
        .orderBy(itemMaster.itemId)
        .build()
        .render(RenderingStrategies.MYBATIS3);

请注意,外部查询的限定符(“im”)会自动应用于内部查询,以及内部查询的限定符(“ol”)。仅存在或不存在子查询支持将别名从外部查询传递到内部查询。

基于列的子查询示例如下:

SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight)
        .from(animalData)
        .where(brainWeight, isEqualTo(
                select(min(brainWeight))
                .from(animalData)
            )
        )
        .orderBy(animalName)
        .build()
        .render(RenderingStrategies.MYBATIS3);

11.3、插入语句中的子查询

该库支持从 SELECT 语句中检索值的 INSERT 语句。例如:

InsertSelectStatementProvider insertSelectStatement = insertInto(animalDataCopy)
        .withColumnList(id, animalName, bodyWeight, brainWeight)
        .withSelectStatement(
            select(id, animalName, bodyWeight, brainWeight)
            .from(animalData)
            .where(id, isLessThan(22))
        )
        .build()
        .render(RenderingStrategies.MYBATIS3);

11.4、更新语句中的子查询

该库支持根据子查询的结果设置更新值。例如:

UpdateStatementProvider updateStatement = update(animalData)
        .set(brainWeight).equalTo(
            select(avg(brainWeight))
            .from(animalData)
            .where(brainWeight, isGreaterThan(22.0))
        )
        .where(brainWeight, isLessThan(1.0))
        .build()
        .render(RenderingStrategies.MYBATIS3);

11.5、From 子句中的子查询

该库支持 from 子句中的子查询,语法是 select DSL 的自然扩展。一个例子如下:

DerivedColumn<Integer> rowNum = DerivedColumn.of("rownum()");

SelectStatementProvider selectStatement =
    select(animalName, rowNum)
    .from(
        select(id, animalName)
        .from(animalData)
        .where(id, isLessThan(22))
        .orderBy(animalName.descending())
    )
    .where(rowNum, isLessThan(5))
    .and(animalName, isLike("%a%"))
    .build()
    .render(RenderingStrategies.MYBATIS3);

请注意,使用 DerivedColumn 可以轻松指定类似 rownum() 的函数,该函数既可用于选择列表,也可用于 where 条件。

11.6、join子句中的子查询

该库支持“join”子句中的子查询,类似于“from”子句中的子查询。例如:

SelectStatementProvider selectStatement = select(orderMaster.orderId, orderMaster.orderDate,
        orderDetail.lineNumber, orderDetail.description, orderDetail.quantity)
    .from(orderMaster, "om")
    .join(
        select(orderDetail.orderId, orderDetail.lineNumber, orderDetail.description, orderDetail.quantity)
        .from(orderDetail),
        "od")
    .on(orderMaster.orderId, equalTo(orderDetail.orderId.qualifiedWith("od")))
    .build()
    .render(RenderingStrategies.MYBATIS3);

这呈现为:

select om.order_id, om.order_date, line_number, description, quantity
from OrderMaster om
join (select order_id, line_number, description, quantity from OrderDetail) od
on om.order_id = od.order_id

请注意,子查询使用“od”作为别名,但该别名不会自动应用,因此必须在需要时指定它。如果有疑问,请使用qualifiedBy 方法指定别名。

12、数据库函数

该库为几个常见的数据库函数提供了实现。我们不会尝试实现一大堆功能。相反,我们提供了一些常用函数作为示例,并且可以相对容易地编写您自己可能想要使用的函数的实现,这些函数不是由库提供的。有关如何编写自己的函数的信息,请参见“扩展库”页面。

提供的函数都在 org.mybatis.dynamic.sql.select.function 包中。此外,SqlBuilder 中有静态方法可以访问所有函数。

在下表中…

  • “Function Class”是org.mybatis.dynamic.sql.select.function包中的实现类
  • “示例”显示了来自 SqlBuilder 类的静态方法
  • “Rendered Result”显示了函数生成的渲染SQL片段
Function ClassExampleRendered Result
Addadd(column1, column2, constant(55))column1 + column2 + 55
Concatenateconcatenate(stringConstant("Name: ", column1)'Name: ' || column1
Dividedivide(column1, column2, constant(55))column1 / column2 / 55
Lowerlower(column1)lower(column1)
Multiplymultiply(column1, column2, constant(55))column1 * column2 * 55
OperatorFunctionapplyOperator(“^”, column1, column2)column1 ^ column2
Substringsubstring(column1, 5, 7)substring(column1, 5, 7)
Subtractsubtract(column1, column2, constant(55))column1 - column2 - 55
Upperupper(column1)upper(column1)

特别注意 OperatorFunction - 您可以使用此函数轻松实现数据库支持的运算符。例如,MySQL 支持许多可以用这个函数轻松实现的位运算符。

13、对 MyBatis3 的专业支持

13.1、Common Mapper 支持

该库包括几个常见的 MyBatis 映射器,可以按原样注入到 MyBatis 配置中,也可以扩展。这些映射器可用于消除多个操作的重复样板代码 - 即计数查询、删除、插入和更新。此外,还有一个通用的选择映射器,可用于避免为每个查询编写自定义结果映射。常见的 select mapper 提供了一个与 Spring JDBC 模板非常相似的 row mapper 功能。

13.1.1、Common Count, Delete, Insert, and Update Mappers

这些映射器提供执行简单查询的实用功能。它们可以按原样使用,也可以扩展。他们提供如下方法:

MapperMethods(s)
org.mybatis.dynamic.sql.util.mybatis3.CommonCountMapperlong count(SelectStatementProvider)
org.mybatis.dynamic.sql.util.mybatis3.CommonDeleteMapperint delete(DeleteStatementProvider)
org.mybatis.dynamic.sql.util.mybatis3.CommonInsertMapper<T>int insert(InsertStatementProvider<T>) int generalInsert(GeneralInsertStatementProvider) int insertSelect(InsertSelectStatementProvider) int insertMultiple(MultiRowInsertStatementProvider<T>)
org.mybatis.dynamic.sql.util.mybatis3.CommonUpdateMapperint update(UpdateStatementProvider)

这些映射器以及常见的 selectmapper 可用于创建通用 CRUD 映射器,如下所示:

import org.apache.ibatis.annotations.Mapper;
import org.mybatis.dynamic.sql.util.mybatis3.CommonCountMapper;
import org.mybatis.dynamic.sql.util.mybatis3.CommonDeleteMapper;
import org.mybatis.dynamic.sql.util.mybatis3.CommonInsertMapper;
import org.mybatis.dynamic.sql.util.mybatis3.CommonSelectMapper;
import org.mybatis.dynamic.sql.util.mybatis3.CommonUpdateMapper;

@Mapper
public interface FooMapper extends CommonCountMapper, CommonDeleteMapper, CommonInsertMapper<Foo>, CommonSelectMapper,
        CommonUpdateMapper {
}

可以使用默认方法扩展此映射器,如下所示。

13.1.2、Common Select Mapper

MyBatis 非常擅长将结果集映射到对象——这是它的主要区别之一。 MyBatis 还要求您为每种可能性预定义映射。如果您想在查询中使用非常动态的列列表,这将带来挑战。这个库提供了一个通用的 MyBatis 映射器,可以帮助解决这个问题。

通用的映射器是 org.mybatis.dynamic.sql.util.mybatis3.CommonSelectMapper。这个映射器可以按原样注入到 MyBatis 配置中,也可以由现有的映射器扩展。

映射器包含三种类型的方法:

  1. selectOneMappedRow 和 selectManyMappedRows 方法允许您对任意数量的列使用 select 语句。 MyBatis 将处理这些行并返回一个值的 Map 或多行的 Map 列表。
  2. selectOne 和 selectMany 方法还允许您对任意数量的列使用 select 语句。这些方法还允许您指定将行值映射转换为特定对象的函数。
  3. 其他方法适用于具有单列的结果集。有许多数据类型(整数、长整型、字符串等)的函数。还有返回单个值、可选值或值列表的函数。

使用映射行方法的示例如下:

package foo.service;
import static org.mybatis.dynamic.sql.SqlBuilder.*;

import java.util.List;
import java.util.Map;
import org.mybatis.dynamic.sql.render.RenderingStrategies;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.mybatis.dynamic.sql.util.mybatis3.CommonSelectMapper;

public class MyService {
    public List<Map<String,Object>> generalSearch() {
        CommonSelectMapper mapper = getGeneralMapper(); // not shown

        SelectStatementProvider selectStatement = select(id, description)
            .from(foo)
            .where(description. isLike("%bar%"))
            .build()
            .render(RenderingStrategies.MYBATIS3);
        return  mapper.selectManyMappedRows(selectStatement);
    }
}

如您所见,该方法返回一个包含行值的 Map 列表。 Map 键将是从数据库返回的列名(通常为大写),以及从 ResultSet.getObject() 返回的列值。有关如何将 SQL 类型映射到 Java 类型以确定特定数据库的数据类型的详细信息,请参阅 JDBC 驱动程序的文档。

这种方法效果很好,但通常最好将结果集编组为实际对象。这可以通过以下方式完成:

package foo.service;
import static org.mybatis.dynamic.sql.SqlBuilder.*;

import java.util.List;
import org.mybatis.dynamic.sql.render.RenderingStrategies;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.mybatis.dynamic.sql.util.mybatis3.CommonSelectMapper;

public class MyService {
    public List<TableCode> generalSearch() {
        CommonSelectMapper mapper = getGeneralMapper(); // not shown

        SelectStatementProvider selectStatement = select(id, description)
            .from(foo)
            .where(description. isLike("%bar%"))
            .build()
            .render(RenderingStrategies.MYBATIS3);
        return  mapper.selectMany(selectStatement, m -> {
            TableCode tc = new TableCode();
            tc.setId((Integer) m.get("ID"));
            tc.setDescription((String) m.get("DESCRIPTION"));
            return tc;
        });
    }
}

使用此方法,您可以将所有数据库特定操作集中在一个方法中。

如果结果集中只有一列,通用映射器提供了直接检索值的方法。例如:

package foo.service;
import static org.mybatis.dynamic.sql.SqlBuilder.*;

import org.mybatis.dynamic.sql.render.RenderingStrategies;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.mybatis.dynamic.sql.util.mybatis3.CommonSelectMapper;

public class MyService {
    public Long getAverageAge() {
        CommonSelectMapper mapper = getGeneralMapper(); // not shown

        SelectStatementProvider selectStatement = select(avg(age))
            .from(foo)
            .where(description. isLike("%bar%"))
            .build()
            .render(RenderingStrategies.MYBATIS3);
        return  mapper.selectOneLong(selectStatement);
    }
}

13.2、Count Method 支持

count 方法支持的目标是允许创建执行 count 查询的方法,允许用户在运行时指定 where 子句,但抽象出所有其他细节。

为了使用这种支持,我们设想在 MyBatis 映射器接口上创建几个方法。第一种方法是标准的 MyBatis 动态 SQL 方法,它将执行一个选择:

@SelectProvider(type=SqlProviderAdapter.class, method="select")
long count(SelectStatementProvider selectStatement);

这是 MyBatis 动态 SQL 执行查询并返回 long 的标准方法。其他方法将重用此方法并提供构建 select 语句所需的一切,除了 where 子句。代替编写此方法,您可以扩展 org.mybatis.dynamic.sql.util.mybatis3.CommonCountMapper。有几种可能有用的计数查询变体:

  1. count(*) - 计算匹配 where 子句的行数
  2. count(column) - 计算匹配 where 子句的非空列值的数量
  3. count(distinct column) - 计算与 where 子句匹配的唯一列值的数量

对应的mapper方法如下:

default long count(CountDSLCompleter completer) {  // count(*)
    return MyBatis3Utils.countFrom(this::count, person, completer);
}

default long count(BasicColumn column, CountDSLCompleter completer) { // count(column)
    return MyBatis3Utils.count(this::count, column, person, completer);
}

default long countDistinct(BasicColumn column, CountDSLCompleter completer) { // count(distinct column)
    return MyBatis3Utils.countDistinct(this::count, column, person, completer);
}

这些方法展示了 CountDSLCompleter 的使用,它是 java.util.Function 的一个特化,允许用户提供 where 子句。客户可以使用以下方法:

long rows = mapper.count(c ->
        c.where(occupation, isNull()));

有一种实用方法可用于计算表中的所有行数:

long rows = mapper.count(CountDSLCompleter.allRows());

13.3、Delete Method 支持

删除方法支持的目标是允许创建执行删除语句的方法,允许用户在运行时指定 where 子句,但抽象出所有其他细节。

为了使用这种支持,我们设想在 MyBatis 映射器接口上创建两个方法。第一种方法是标准的 MyBatis 动态 SQL 方法,它将执行删除:

@DeleteProvider(type=SqlProviderAdapter.class, method="delete")
int delete(DeleteStatementProvider deleteStatement);

这是 MyBatis Dynamic SQL 的标准方法,它执行删除并返回一个 int - 删除的行数。代替编写此方法,您可以扩展 org.mybatis.dynamic.sql.util.mybatis3.CommonDeleteMapper。第二种方法将重用此方法并提供构建删除语句所需的一切,除了 where 子句:

default int delete(DeleteDSLCompleter completer) {
    return MyBatis3Utils.deleteFrom(this::delete, person, completer);
}

此方法显示了 DeleteDSLCompleter 的使用,它是 java.util.Function 的一个特化,允许用户提供 where 子句。客户可以使用以下方法:

int rows = mapper.delete(c ->
        c.where(occupation, isNull()));

有一个实用方法可用于删除表中的所有行:

int rows = mapper.delete(DeleteDSLCompleter.allRows());

13.4、Insert Method 支持

插入方法支持的目标是从映射器接口中的插入方法中删除一些样板代码。

为了使用这种支持,我们设想在 MyBatis 映射器接口上创建几个方法。第一个方法是执行插入的标准 MyBatis 方法:

@InsertProvider(type=SqlProviderAdapter.class, method="insert")
int insert(InsertStatementProvider<PersonRecord> insertStatement);

@InsertProvider(type=SqlProviderAdapter.class, method="generalInsert")
int generalInsert(GeneralInsertStatementProvider insertStatement);

@InsertProvider(type=SqlProviderAdapter.class, method="insertMultiple")
int insertMultiple(MultiRowInsertStatementProvider<PersonRecord> insertStatement);

这些方法是 MyBatis Dynamic SQL 的标准方法。它们执行单行插入、一般插入和多行插入。代替编写这些方法,您可以扩展 org.mybatis.dynamic.sql.util.mybatis3.CommonInsertMapper。

这些方法可用于实现简化的插入方法:

default int insert(UnaryOperator<GeneralInsertDSL> completer) {
    return MyBatis3Utils.insert(this::generalInsert, person, completer);
}

default int insert(PersonRecord record) {
    return MyBatis3Utils.insert(this::insert, record, person, c -> 
        c.map(id).toProperty("id")
        .map(firstName).toProperty("firstName")
        .map(lastName).toProperty("lastName")
        .map(birthDate).toProperty("birthDate")
        .map(employed).toProperty("employed")
        .map(occupation).toProperty("occupation")
        .map(addressId).toProperty("addressId")
    );
}

default int insertMultiple(PersonRecord...records) {
    return insertMultiple(Arrays.asList(records));
}

default int insertMultiple(Collection<PersonRecord> records) {
    return MyBatis3Utils.insertMultiple(this::insertMultiple, records, person, c ->
        c.map(id).toProperty("id")
        .map(firstName).toProperty("firstName")
        .map(lastName).toProperty("lastName")
        .map(birthDate).toProperty("birthDate")
        .map(employed).toProperty("employed")
        .map(occupation).toProperty("occupation")
        .map(addressId).toProperty("addressId")
    );
}

第一种插入方法是通用插入,可用于创建具有指定列的不同组合的任意插入。其他方法将插入语句映射到 POJO“记录”类,该类保存插入语句的值。

13.5、Select Method 支持

选择方法支持的目标是允许创建执行选择语句的方法,允许用户在运行时指定 where 子句和/或 order by 子句,但抽象出所有其他细节。

为了使用这种支持,我们设想在 MyBatis 映射器接口上创建几个方法。前两个方法是标准的 MyBatis Dynamic SQL 方法,它将执行一个选择:

@SelectProvider(type=SqlProviderAdapter.class, method="select")
@Results(id="PersonResult", value= {
        @Result(column="A_ID", property="id", jdbcType=JdbcType.INTEGER, id=true),
        @Result(column="first_name", property="firstName", jdbcType=JdbcType.VARCHAR),
        @Result(column="last_name", property="lastName", jdbcType=JdbcType.VARCHAR),
        @Result(column="birth_date", property="birthDate", jdbcType=JdbcType.DATE),
        @Result(column="employed", property="employed", jdbcType=JdbcType.VARCHAR),
        @Result(column="occupation", property="occupation", jdbcType=JdbcType.VARCHAR)
})
List<PersonRecord> selectMany(SelectStatementProvider selectStatement);
    
@SelectProvider(type=SqlProviderAdapter.class, method="select")
@ResultMap("PersonResult")
Optional<PersonRecord> selectOne(SelectStatementProvider selectStatement);

这两种方法是 MyBatis Dynamic SQL 的标准方法。他们执行选择并返回记录列表或单个记录。

我们还设想为 select 语句的可重用列列表创建一个静态字段:

BasicColumn[] selectList =
    BasicColumn.columnList(id.as("A_ID"), firstName, lastName, birthDate, employed, occupation, addressId);

selectOne 方法可用于实现广义的选择一方法:

default Optional<PersonRecord> selectOne(SelectDSLCompleter completer) {
    return MyBatis3Utils.selectOne(this::selectOne, selectList, person, completer);
}

此方法显示了 SelectDSLCompleter 的使用,它是 java.util.Function 的一个特化,允许用户提供 where 子句。

通用的 selectOne 方法可以用来实现一个 selectByPrimaryKey 方法:

default Optional<PersonRecord> selectByPrimaryKey(Integer id_) {
    return selectOne(c ->
        c.where(id, isEqualTo(id_))
    );
}

selectMany 方法可用于实现通用选择方法,其中用户可以指定 where 子句和/或 order by 子句。通常,我们推荐其中两种方法 - for select 和 select distinct:

default List<PersonRecord> select(SelectDSLCompleter completer) {
    return MyBatis3Utils.selectList(this::selectMany, selectList, person, completer);
}
    
default List<PersonRecord> selectDistinct(SelectDSLCompleter completer) {
    return MyBatis3Utils.selectDistinct(this::selectMany, selectList, person, completer);
}

这些方法展示了 SelectDSLCompleter 的使用,它是 java.util.Function 的一个特化,允许用户提供 where 子句和/或 order by 子句。

客户可以使用以下方法:

List<PersonRecord> rows = mapper.select(c ->
        c.where(id, isEqualTo(1))
        .or(occupation, isNull()));

有一些实用方法可以选择表中的所有行:

List<PersonRecord> rows =
    mapper.select(SelectDSLCompleter.allRows());

以下查询将按指定顺序选择所有行:

List<PersonRecord> rows =
    mapper.select(SelectDSLCompleter.allRowsOrderedBy(lastName, firstName));

13.6、Update Method 支持

更新方法支持的目标是允许创建执行更新语句的方法,允许用户在运行时指定要设置的值和 where 子句,但抽象出所有其他细节。

为了使用这种支持,我们设想在 MyBatis 映射器接口上创建几个方法。第一种方法是标准的 MyBatis 动态 SQL 方法,它将执行更新:

@UpdateProvider(type=SqlProviderAdapter.class, method="update")
int update(UpdateStatementProvider updateStatement);

这是 MyBatis Dynamic SQL 的标准方法,它执行查询并返回一个 int——更新的行数。代替编写此方法,您可以扩展 org.mybatis.dynamic.sql.util.mybatis3.CommonUpdateMapper。第二种方法将重用此方法并提供构建更新语句所需的所有内容,但值和 where 子句除外:

default int update(UpdateDSLCompleter completer) {
    return MyBatis3Utils.update(this::update, person, completer);
}

此方法显示了 UpdateDSLCompleter 的使用,它是 java.util.Function 的一个特化,允许用户提供值和 where 子句。客户可以使用以下方法:

int rows = mapper.update(c ->
    c.set(occupation).equalTo("Programmer")
    .where(id, isEqualTo(100)));

只需省略 where 子句即可更新表中的所有行:

int rows = mapper.update(c ->
    c.set(occupation).equalTo("Programmer"));

也可以编写一个设置值的实用程序方法。例如:

static UpdateDSL<UpdateModel> updateSelectiveColumns(PersonRecord row,
        UpdateDSL<UpdateModel> dsl) {
    return dsl.set(id).equalToWhenPresent(row::getId)
            .set(firstName).equalToWhenPresent(row::getFirstName)
            .set(lastName).equalToWhenPresent(row::getLastName)
            .set(birthDate).equalToWhenPresent(row::getBirthDate)
            .set(employed).equalToWhenPresent(row::getEmployed)
            .set(occupation).equalToWhenPresent(row::getOccupation);
}

如果记录中的相应字段不为空,此方法将选择性地设置值。此方法可按如下方式使用:

rows = mapper.update(h ->
    updateSelectiveColumns(updateRecord, h)
    .where(id, isEqualTo(100)));