Mapping

BasicJdbcConverter 是一款强大的工具,它可以通过注释或约定从域对象映射到数据库行。它支持各种类型,包括基本类型、枚举、日期、列表和映射,以及对其他实体的引用。通过提供自定义转换器,可以覆盖默认映射并支持特定的数据类型转换。提及对聚合根的引用以及管理反向引用的最佳实践。

丰富的映射支持是由 BasicJdbcConverter 提供的。BasicJdbcConverter 有一个丰富的元数据模型,允许将域对象映射到数据行。映射元数据模型通过使用注释填充到你的域对象上。然而,基础架构并不局限于使用注释作为元数据信息的唯一来源。通过遵循一组约定,BasicJdbcConverter 还允许你在不提供任何其他元数据的情况下将对象映射到行。

Rich mapping support is provided by the BasicJdbcConverter. BasicJdbcConverter has a rich metadata model that allows mapping domain objects to a data row. The mapping metadata model is populated by using annotations on your domain objects. However, the infrastructure is not limited to using annotations as the only source of metadata information. The BasicJdbcConverter also lets you map objects to rows without providing any additional metadata, by following a set of conventions.

本部分描述了 BasicJdbcConverter 的功能,包括如何使用约定将对象映射到行以及如何用基于注释的映射元数据覆盖这些约定。

This section describes the features of the BasicJdbcConverter, including how to use conventions for mapping objects to rows and how to override those conventions with annotation-based mapping metadata.

在继续本章之前,阅读有关 object-mapping.adoc的基础知识。

Read on the basics about object-mapping.adoc before continuing with this chapter.

Convention-based Mapping

当没有提供其他映射元数据时,BasicJdbcConverter 有几个约定可将对象映射到行。这些约定是:

BasicJdbcConverter has a few conventions for mapping objects to rows when no additional mapping metadata is provided. The conventions are:

  • The short Java class name is mapped to the table name in the following manner. The com.bigbank.SavingsAccount class maps to the SAVINGS_ACCOUNT table name. The same name mapping is applied for mapping fields to column names. For example, the firstName field maps to the FIRST_NAME column. You can control this mapping by providing a custom NamingStrategy. See mapping.configuration for more detail. Table and column names that are derived from property or class names are used in SQL statements without quotes by default. You can control this behavior by setting RelationalMappingContext.setForceQuote(true).

  • The converter uses any Spring Converters registered with CustomConversions to override the default mapping of object properties to row columns and values.

  • The fields of an object are used to convert to and from columns in the row. Public JavaBean properties are not used.

  • If you have a single non-zero-argument constructor whose constructor argument names match top-level column names of the row, that constructor is used. Otherwise, the zero-argument constructor is used. If there is more than one non-zero-argument constructor, an exception is thrown. Refer to Object Creation for further details.

Supported Types in Your Entity

目前支持以下类型的属性:

The properties of the following types are currently supported:

  • All primitive types and their boxed types (int, float, Integer, Float, and so on)

  • Enums get mapped to their name.

  • String

  • java.util.Date, java.time.LocalDate, java.time.LocalDateTime, and java.time.LocalTime

  • Arrays and Collections of the types mentioned above can be mapped to columns of array type if your database supports that.

  • Anything your database driver accepts.

  • References to other entities. They are considered a one-to-one relationship, or an embedded type. It is optional for one-to-one relationship entities to have an id attribute. The table of the referenced entity is expected to have an additional column with a name based on the referencing entity see Back References. Embedded entities do not need an id. If one is present it gets mapped as a normal attribute without any special meaning.

  • Set<some entity> is considered a one-to-many relationship. The table of the referenced entity is expected to have an additional column with a name based on the referencing entity see Back References.

  • Map<simple type, some entity> is considered a qualified one-to-many relationship. The table of the referenced entity is expected to have two additional columns: One named based on the referencing entity for the foreign key (see Back References) and one with the same name and an additional _key suffix for the map key.

  • List<some entity> is mapped as a Map<Integer, some entity>. The same additional columns are expected and the names used can be customized in the same way.

对于 ListSetMap,可以通过实现 NamingStrategy.getReverseColumnName(RelationalPersistentEntity<?> owner)NamingStrategy.getKeyColumn(RelationalPersistentProperty property) 来控制反向引用的命名。或者,您可以使用 @MappedCollection(idColumn="your_column_name", keyColumn="your_key_column_name") 对属性加上注释。为 Set 指定一个键列没有任何作用。

For List, Set, and Map naming of the back reference can be controlled by implementing NamingStrategy.getReverseColumnName(RelationalPersistentEntity<?> owner) and NamingStrategy.getKeyColumn(RelationalPersistentProperty property), respectively. Alternatively you may annotate the attribute with @MappedCollection(idColumn="your_column_name", keyColumn="your_key_column_name"). Specifying a key column for a Set has no effect.

Mapping Annotation Overview

`RelationalConverter`可以使用元数据来驱动对象到行的映射。下列注解是可用的:

The RelationalConverter can use metadata to drive the mapping of objects to rows. The following annotations are available:

  • @Id: Applied at the field level to mark the primary key.

  • @Table: Applied at the class level to indicate this class is a candidate for mapping to the database. You can specify the name of the table where the database is stored.

  • @Transient: By default, all fields are mapped to the row. This annotation excludes the field where it is applied from being stored in the database. Transient properties cannot be used within a persistence constructor as the converter cannot materialize a value for the constructor argument.

  • @PersistenceCreator: Marks a given constructor or static factory method — even a package protected one — to use when instantiating the object from the database. Constructor arguments are mapped by name to the values in the retrieved row.

  • @Value: This annotation is part of the Spring Framework. Within the mapping framework it can be applied to constructor arguments. This lets you use a Spring Expression Language statement to transform a key’s value retrieved in the database before it is used to construct a domain object. In order to reference a column of a given row one has to use expressions like: @Value("#root.myProperty") where root refers to the root of the given Row.

  • @Column: Applied at the field level to describe the name of the column as it is represented in the row, letting the name be different from the field name of the class. Names specified with a @Column annotation are always quoted when used in SQL statements. For most databases, this means that these names are case-sensitive. It also means that you can use special characters in these names. However, this is not recommended, since it may cause problems with other tools.

  • @Version: Applied at field level is used for optimistic locking and checked for modification on save operations. The value is null (zero for primitive types) is considered as marker for entities to be new. The initially stored value is zero (one for primitive types). The version gets incremented automatically on every update.

有关更多参考信息,请参见 Optimistic Locking

See Optimistic Locking for further reference.

映射元数据基础设施在独立且与技术无关的 spring-data-commons 项目中定义。在 JDBC 支持中使用特定子类来支持基于注释的元数据。也可以实施其他策略(如果有需求)。

The mapping metadata infrastructure is defined in the separate spring-data-commons project that is technology-agnostic. Specific subclasses are used in the JDBC support to support annotation based metadata. Other strategies can also be put in place (if there is demand).

Referenced Entities

对引用的实体的处理是有限的。这是基于上述聚合根的思想。如果您引用另一个实体,则该实体根据定义是聚合的一部分。因此,如果您删除引用,则先前引用的实体将被删除。这也意味着引用是一对一或一对多,但不是多对一或多对多。

The handling of referenced entities is limited. This is based on the idea of aggregate roots as described above. If you reference another entity, that entity is, by definition, part of your aggregate. So, if you remove the reference, the previously referenced entity gets deleted. This also means references are 1-1 or 1-n, but not n-1 or n-m.

如果您有 n 对 1 或 n 对多引用,则根据定义,您正在处理两个单独的聚合。它们之间的引用可以编码为简单的 id 值,这些值可以通过 Spring Data JDBC 适当地映射。对这些引用进行编码的更好方法是将其设为 AggregateReference 的实例。AggregateReference 是对 id 值的包装,用于将该值标记为对其他聚合的引用。此外,将该聚合的类型编码在类型参数中。

If you have n-1 or n-m references, you are, by definition, dealing with two separate aggregates. References between those may be encoded as simple id values, which map properly with Spring Data JDBC. A better way to encode these, is to make them instances of AggregateReference. An AggregateReference is a wrapper around an id value which marks that value as a reference to a different aggregate. Also, the type of that aggregate is encoded in a type parameter.

Back References

聚合中的所有引用会导致数据库中相反方向的外键关系。默认情况下,外键列的名称是引用实体的表名。

All references in an aggregate result in a foreign key relationship in the opposite direction in the database. By default, the name of the foreign key column is the table name of the referencing entity.

或者,您可以选择使它们由引用实体的实体名称命名,而忽略 @Table 注释。通过在 RelationalMappingContext 上调用 setForeignKeyNaming(ForeignKeyNaming.IGNORE_RENAMING) 来激活此行为。

Alternatively you may choose to have them named by the entity name of the referencing entity ignoreing @Table annotations. You activate this behaviour by calling setForeignKeyNaming(ForeignKeyNaming.IGNORE_RENAMING) on the RelationalMappingContext.

对于 ListMap 引用,需要一个附加列来保存列表索引或映射键。它基于外键列并附加 _KEY 后缀。

For List and Map references an additional column is required for holding the list index or map key. It is based on the foreign key column with an additional _KEY suffix.

如果您希望完全不同的方式来命名这些反向引用,则可以根据需要实现 NamingStrategy.getReverseColumnName(RelationalPersistentEntity<?> owner)

If you want a completely different way of naming these back references you may implement NamingStrategy.getReverseColumnName(RelationalPersistentEntity<?> owner) in a way that fits your needs.

Declaring and setting an AggregateReference
class Person {
	@Id long id;
	AggregateReference<Person, Long> bestFriend;
}

// ...

Person p1, p2 = // some initialization

p1.bestFriend = AggregateReference.to(p2.id);

不应在实体中包含属性来保存反向引用的实际值,也不应包含映射或列表的键列。如果您希望在域模型中提供这些值,我们建议在 AfterConvertCallback 中完成此操作,并将值存储在暂态值中。

You should not include attributes in your entities to hold the actual value of a back reference, nor of the key column of maps or lists. If you want these value to be available in your domain model we recommend to do this in a AfterConvertCallback and store the values in transient values.

  • Types for which you registered suitable [id="jdbc.custom-converters"].

Naming Strategy

Naming Strategy

根据习惯,Spring Data 应用了一个 NamingStrategy,以确定表、列和模式名称,其默认为 snake case。名为 firstName`的对象属性会成为 `first_name。您可以通过在应用程序上下文中提供 {spring-data-jdbc-javadoc}org/springframework/data/relational/core/mapping/NamingStrategy.html[NamingStrategy] 来调整它。

By convention, Spring Data applies a NamingStrategy to determine table, column, and schema names defaulting to snake case. An object property named firstName becomes first_name. You can tweak that by providing a {spring-data-jdbc-javadoc}org/springframework/data/relational/core/mapping/NamingStrategy.html[NamingStrategy] in your application context.

Override table names

当表命名策略不匹配您的数据库表名称时,您可以使用 {spring-data-jdbc-javadoc}org/springframework/data/relational/core/mapping/Table.html[@Table] 注解覆盖表名称。此注解的 `value`元素提供了自定义表名称。以下示例将 `MyEntity`类映射到数据库中的 `CUSTOM_TABLE_NAME`表:

When the table naming strategy does not match your database table names, you can override the table name with the {spring-data-jdbc-javadoc}org/springframework/data/relational/core/mapping/Table.html[@Table] annotation. The element value of this annotation provides the custom table name. The following example maps the MyEntity class to the CUSTOM_TABLE_NAME table in the database:

@Table("CUSTOM_TABLE_NAME")
class MyEntity {
    @Id
    Integer id;

    String name;
}

Override column names

当列命名策略不匹配您的数据库表名称时,您可以使用 {spring-data-jdbc-javadoc}org/springframework/data/relational/core/mapping/Column.html[@Column] 注解覆盖表名称。此注解的 `value`元素提供了自定义列名称。以下示例将 `MyEntity`类的 `name`属性映射到数据库中的 `CUSTOM_COLUMN_NAME`列:

When the column naming strategy does not match your database table names, you can override the table name with the {spring-data-jdbc-javadoc}org/springframework/data/relational/core/mapping/Column.html[@Column] annotation. The element value of this annotation provides the custom column name. The following example maps the name property of the MyEntity class to the CUSTOM_COLUMN_NAME column in the database:

class MyEntity {
    @Id
    Integer id;

    @Column("CUSTOM_COLUMN_NAME")
    String name;
}

Read Only Properties

使用 @ReadOnlyProperty 注解的属性不会被 Spring Data 写入到数据库中,但当加载实体时,这些属性将会被读取。

Attributes annotated with @ReadOnlyProperty will not be written to the database by Spring Data, but they will be read when an entity gets loaded.

Spring Data 不会自动在写入实体后重新加载此实体。因此,如果你想要查看在数据库中为这些列生成的数据,那么就必须显式地重新加载此实体。

Spring Data will not automatically reload an entity after writing it. Therefore, you have to reload it explicitly if you want to see data that was generated in the database for such columns.

如果带注解的属性是实体或实体集合,则它将由各单独表中的一个或者若干单独行来表示。Spring Data 不会针对这些行执行任何插入、删除或者更新。

If the annotated attribute is an entity or collection of entities, it is represented by one or more separate rows in separate tables. Spring Data will not perform any insert, delete or update for these rows.

Insert Only Properties

使用 @InsertOnlyProperty 注解的属性仅在插入操作期间由 Spring Data 写入到数据库中。对于更新,将忽略这些属性。

Attributes annotated with @InsertOnlyProperty will only be written to the database by Spring Data during insert operations. For updates these properties will be ignored.

@InsertOnlyProperty 仅受支持于聚合根。

@InsertOnlyProperty is only supported for the aggregate root.

Customized Object Construction

映射子系统允许通过使用 @PersistenceConstructor 注解注解构造函数来自定义对象构造。用于构造函数参数的值将按照以下方式解析:

The mapping subsystem allows the customization of the object construction by annotating a constructor with the @PersistenceConstructor annotation.The values to be used for the constructor parameters are resolved in the following way:

  • If a parameter is annotated with the @Value annotation, the given expression is evaluated, and the result is used as the parameter value.

  • If the Java type has a property whose name matches the given field of the input row, then its property information is used to select the appropriate constructor parameter to which to pass the input field value. This works only if the parameter name information is present in the Java .class files, which you can achieve by compiling the source with debug information or using the -parameters command-line switch for javac in Java 8.

  • Otherwise, a MappingException is thrown to indicate that the given constructor parameter could not be bound.

class OrderItem {

  private @Id final String id;
  private final int quantity;
  private final double unitPrice;

  OrderItem(String id, int quantity, double unitPrice) {
    this.id = id;
    this.quantity = quantity;
    this.unitPrice = unitPrice;
  }

  // getters/setters omitted
}

Overriding Mapping with Explicit Converters

Spring Data 允许注册自定义转换器,以影响值在数据库中的映射方式。目前,转换器仅应用于属性级别,即您只能将域中的单个值转换为数据库中的单个值,反之亦然。不支持在复杂对象和多列之间进行转换。

Spring Data allows registration of custom converters to influence how values are mapped in the database. Currently, converters are only applied on property-level, i.e. you can only convert single values in your domain to single values in the database and back. Conversion between complex objects and multiple columns isn’t supported.

Writing a Property by Using a Registered Spring Converter

以下示例显示了将 Boolean 对象转换为 String 值的 Converter 的实现:

The following example shows an implementation of a Converter that converts from a Boolean object to a String value:

import org.springframework.core.convert.converter.Converter;

@WritingConverter
public class BooleanToStringConverter implements Converter<Boolean, String> {

    @Override
    public String convert(Boolean source) {
        return source != null && source ? "T" : "F";
    }
}

这里有几件事需要注意:BooleanString 都是简单类型,因此 Spring Data 需要提示此转换器应该应用的方向(读取或写入)。通过为该转换器添加 @WritingConverter 注释,您指示 Spring Data 在数据库中将每个 Boolean 属性写入为 String

There are a couple of things to notice here: Boolean and String are both simple types hence Spring Data requires a hint in which direction this converter should apply (reading or writing). By annotating this converter with @WritingConverter you instruct Spring Data to write every Boolean property as String in the database.

Reading by Using a Spring Converter

以下示例显示了将 String 转换为 Boolean 值的 Converter 的实现:

The following example shows an implementation of a Converter that converts from a String to a Boolean value:

@ReadingConverter
public class StringToBooleanConverter implements Converter<String, Boolean> {

    @Override
    public Boolean convert(String source) {
        return source != null && source.equalsIgnoreCase("T") ? Boolean.TRUE : Boolean.FALSE;
    }
}

这里有几件事需要注意:StringBoolean 都是简单类型,因此 Spring Data 需要提示此转换器应该应用的方向(读取或写入)。通过为该转换器添加 @ReadingConverter 注释,您指示 Spring Data 从数据库中转换应分配给 Boolean 属性的每个 String 值。

There are a couple of things to notice here: String and Boolean are both simple types hence Spring Data requires a hint in which direction this converter should apply (reading or writing). By annotating this converter with @ReadingConverter you instruct Spring Data to convert every String value from the database that should be assigned to a Boolean property.

Registering Spring Converters with the JdbcConverter

class MyJdbcConfiguration extends AbstractJdbcConfiguration {

    // …

    @Override
    protected List<?> userConverters() {
	return Arrays.asList(new BooleanToStringConverter(), new StringToBooleanConverter());
    }

}

在 Spring Data JDBC 的早期版本中,建议直接覆盖 AbstractJdbcConfiguration.jdbcCustomConversions()。这不再必要,甚至也不再推荐,因为该方法组装了针对所有数据库的转换、由所用 Dialect 注册的转换和由用户注册的转换。如果您正在从 Spring Data JDBC 的旧版本迁移,并且 AbstractJdbcConfiguration.jdbcCustomConversions() 覆盖了 Dialect 的转换,则不会注册这些转换。

In previous versions of Spring Data JDBC it was recommended to directly overwrite AbstractJdbcConfiguration.jdbcCustomConversions(). This is no longer necessary or even recommended, since that method assembles conversions intended for all databases, conversions registered by the Dialect used and conversions registered by the user. If you are migrating from an older version of Spring Data JDBC and have AbstractJdbcConfiguration.jdbcCustomConversions() overwritten conversions from your Dialect will not get registered.

如果您想依靠 Spring Boot 自举 Spring Data JDBC,但仍想覆盖配置的特定方面,您可能想要公开此类 Bean。对于自定义转换,您可能会选择注册一个 JdbcCustomConversions 类型的 Bean,基础设施将拾取它。要了解有关它的更多信息,请务必阅读 Spring Boot Reference Documentation

If you want to rely on Spring Boot to bootstrap Spring Data JDBC, but still want to override certain aspects of the configuration, you may want to expose beans of that type. For custom conversions you may e.g. choose to register a bean of type JdbcCustomConversions that will be picked up the by the Boot infrastructure. To learn more about this please make sure to read the Spring Boot Reference Documentation.

JdbcValue

值转换使用 JdbcValue 来使用 java.sql.Types 类型扩充传播到 JDBC 操作的值。如果您需要指定 JDBC 特定类型而不是使用类型派生,请注册一个自定义写入转换器。该转换器应将值转换为 JdbcValue,它有一个用于值和实际 JDBCType 的字段。

Value conversion uses JdbcValue to enrich values propagated to JDBC operations with a java.sql.Types type. Register a custom write converter if you need to specify a JDBC-specific type instead of using type derivation. This converter should convert the value to JdbcValue which has a field for the value and for the actual JDBCType.