丰富的映射支持由 . 具有丰富的元数据模型,允许将域对象映射到数据行。 映射元数据模型是通过在域对象上使用注释来填充的。 但是,基础结构并不局限于使用注释作为元数据信息的唯一来源。 还允许您通过遵循一组约定将对象映射到行,而无需提供任何其他元数据。BasicJdbcConverterBasicJdbcConverterBasicJdbcConverterSpring中文文档

本节介绍 的功能,包括如何使用约定将对象映射到行,以及如何使用基于注释的映射元数据覆盖这些约定。BasicJdbcConverterSpring中文文档

在继续本章之前,请阅读有关对象映射基础知识的基础知识。Spring中文文档

基于约定的映射

BasicJdbcConverter有一些约定,用于在未提供其他映射元数据时将对象映射到行。 约定是:Spring中文文档

  • 短 Java 类名按以下方式映射到表名。 该类映射到表名。 将字段映射到列名时应用相同的名称映射。 例如,字段映射到列。 您可以通过提供自定义 . 有关详细信息,请参阅映射配置。 默认情况下,从属性或类名派生的表名和列名在 SQL 语句中使用,不带引号。 您可以通过设置 来控制此行为。com.bigbank.SavingsAccountSAVINGS_ACCOUNTfirstNameFIRST_NAMENamingStrategyRelationalMappingContext.setForceQuote(true)Spring中文文档

  • 转换器使用任何注册的 Spring Converter 来覆盖对象属性到行列和值的默认映射。CustomConversionsSpring中文文档

  • 对象的字段用于在行中的列之间相互转换。 不使用公共属性。JavaBeanSpring中文文档

  • 如果有一个非零参数构造函数,其构造函数参数名称与行的顶级列名称匹配,则使用该构造函数。 否则,将使用零参数构造函数。 如果存在多个非零参数构造函数,则会引发异常。 有关详细信息,请参阅对象创建Spring中文文档

实体中支持的类型

目前支持以下类型的属性:Spring中文文档

  • 所有基元类型及其盒装类型(、、、等)intfloatIntegerFloatSpring中文文档

  • 枚举映射到它们的名称。Spring中文文档

  • StringSpring中文文档

  • java.util.Datejava.time.LocalDatejava.time.LocalDateTimejava.time.LocalTimeSpring中文文档

  • 如果数据库支持,上述类型的数组和集合可以映射到数组类型的列。Spring中文文档

  • 数据库驱动程序接受的任何内容。Spring中文文档

  • 对其他实体的引用。 它们被视为一对一关系或嵌入式类型。 一对一关系实体具有属性是可选的。 引用实体的表应具有一个附加列,其名称基于引用实体,请参阅回溯引用。 嵌入式实体不需要 . 如果存在一个属性,它将被映射为没有任何特殊含义的正常属性。ididSpring中文文档

  • Set<some entity>被认为是一对多的关系。 引用实体的表应具有一个附加列,其名称基于引用实体,请参阅回溯引用Spring中文文档

  • Map<simple type, some entity>被认为是合格的一对多关系。 引用实体的表应具有两个附加列:一个基于外键的引用实体命名(请参阅反向引用),另一个具有相同名称和映射键的附加后缀。_keySpring中文文档

  • List<some entity>映射为 .需要相同的附加列,并且可以使用相同的方式自定义使用的名称。Map<Integer, some entity>Spring中文文档

对于 、 和 反向引用的命名,可以分别通过实现 和 来控制。 或者,您可以使用 来注释属性。 为 a 指定键列不起作用。ListSetMapNamingStrategy.getReverseColumnName(RelationalPersistentEntity<?> owner)NamingStrategy.getKeyColumn(RelationalPersistentProperty property)@MappedCollection(idColumn="your_column_name", keyColumn="your_key_column_name")SetSpring中文文档

映射注释概述

可以使用元数据来驱动对象到行的映射。 可以使用以下注释:RelationalConverterSpring中文文档

  • @Id:应用于字段级别以标记主键。Spring中文文档

  • @Table:在类级别应用,以指示此类是映射到数据库的候选项。 您可以指定存储数据库的表的名称。Spring中文文档

  • @Transient:默认情况下,所有字段都映射到该行。 此批注将应用它的字段排除在数据库中。 暂时性属性不能在持久性构造函数中使用,因为转换器无法实现构造函数参数的值。Spring中文文档

  • @PersistenceCreator:标记给定的构造函数或静态工厂方法(甚至是受包保护的方法)在从数据库实例化对象时使用。 构造函数参数按名称映射到检索到的行中的值。Spring中文文档

  • @Value:此注解是 Spring 框架的一部分。 在映射框架中,它可以应用于构造函数参数。 这允许您使用 Spring Expression Language 语句来转换在数据库中检索到的键的值,然后再将其用于构造域对象。 为了引用给定行的列,必须使用如下表达式: 其中 root 引用给定行的根。@Value("#root.myProperty")RowSpring中文文档

  • @Column:在字段级别应用,以描述列的名称,因为它在行中表示,使名称与类的字段名称不同。 在 SQL 语句中使用时,使用批注指定的名称始终加引号。 对于大多数数据库,这意味着这些名称区分大小写。 这也意味着您可以在这些名称中使用特殊字符。 但是,不建议这样做,因为它可能会导致其他工具出现问题。@ColumnSpring中文文档

  • @Version:应用于字段级别用于乐观锁定,并检查保存操作的修改。 该值 ( 对于基元类型) 被视为新实体的标记。 初始存储的值为 ( 对于基元类型)。 每次更新时,版本都会自动递增。nullzerozerooneSpring中文文档

映射元数据基础结构在与技术无关的单独项目中定义。 JDBC 支持中使用特定的子类来支持基于注释的元数据。 也可以制定其他策略(如果有需求)。spring-data-commonsSpring中文文档

引用的实体

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

如果您有 n-1 或 n-m 引用,则根据定义,您正在处理两个单独的聚合。 它们之间的引用可以编码为简单值,这些值与Spring Data JDBC正确映射。 对这些进行编码的更好方法是使它们成为 的实例。 An 是 id 值的包装器,它将该值标记为对不同聚合的引用。 此外,该聚合的类型在类型参数中进行编码。idAggregateReferenceAggregateReferenceSpring中文文档

返回参考资料

聚合中的所有引用都会在数据库中产生相反方向的外键关系。 默认情况下,外键列的名称是引用实体的表名。Spring中文文档

或者,您可以选择让它们以引用实体的实体名称命名,而忽略注释。 您可以通过调用 来激活此行为。@TablesetForeignKeyNaming(ForeignKeyNaming.IGNORE_RENAMING)RelationalMappingContextSpring中文文档

对于和引用,需要额外的列来保存列表索引或映射键。 它基于带有附加后缀的外键列。ListMap_KEYSpring中文文档

如果您想要一种完全不同的方式来命名这些反向引用,您可以以适合您需求的方式实现。NamingStrategy.getReverseColumnName(RelationalPersistentEntity<?> owner)Spring中文文档

声明和设置AggregateReference
class Person {
	@Id long id;
	AggregateReference<Person, Long> bestFriend;
}

// ...

Person p1, p2 = // some initialization

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

不应在实体中包含属性来保存反向引用的实际值,也不应包含地图或列表的键列的实际值。 如果希望这些值在域模型中可用,我们建议在 a 中执行此操作,并将这些值存储在瞬态值中。AfterConvertCallbackSpring中文文档

命名策略

按照惯例,Spring Data 应用 a 来确定默认为蛇形大小写的表、列和架构名称。 名为 的对象属性变为 。 您可以通过在应用程序上下文中提供 NamingStrategy 来调整它。NamingStrategyfirstNamefirst_nameSpring中文文档

覆盖表名称

当表命名策略与数据库表名称不匹配时,可以使用@Table注释覆盖表名称。 此批注的元素提供自定义表名。 下面的示例将类映射到数据库中的表:valueMyEntityCUSTOM_TABLE_NAMESpring中文文档

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

    String name;
}

覆盖列名称

当列命名策略与数据库表名称不匹配时,可以使用@Column注释覆盖表名称。 此批注的元素提供自定义列名。 下面的示例将类的属性映射到数据库中的列:valuenameMyEntityCUSTOM_COLUMN_NAMESpring中文文档

class MyEntity {
    @Id
    Integer id;

    @Column("CUSTOM_COLUMN_NAME")
    String name;
}

@MappedCollection注可用于参考类型(一对一关系)或集、列表和映射(一对多关系)。 注释元素为引用另一个表中的 ID 列的外键列提供自定义名称。 在以下示例中,类的相应表具有一列,并且出于关系原因,该列为 id:idColumnMySubEntityNAMECUSTOM_MY_ENTITY_ID_COLUMN_NAMEMyEntitySpring中文文档

class MyEntity {
    @Id
    Integer id;

    @MappedCollection(idColumn = "CUSTOM_MY_ENTITY_ID_COLUMN_NAME")
    Set<MySubEntity> subEntities;
}

class MySubEntity {
    String name;
}

使用 和 时,必须有一个附加列来表示数据集在 中的位置或实体的键值。 可以使用@MappedCollection注释的元素自定义此附加列名称:ListMapListMapkeyColumnSpring中文文档

class MyEntity {
    @Id
    Integer id;

    @MappedCollection(idColumn = "CUSTOM_COLUMN_NAME", keyColumn = "CUSTOM_KEY_COLUMN_NAME")
    List<MySubEntity> name;
}

class MySubEntity {
    String name;
}

嵌入式实体

嵌入式实体用于在 Java 数据模型中具有值对象,即使数据库中只有一个表也是如此。 在下面的示例中,您会看到,这是用注释映射的。 这样做的结果是,在数据库中,需要一个包含两列和(来自类)的表。MyEntity@Embeddedmy_entityidnameEmbeddedEntitySpring中文文档

但是,如果该列实际上位于结果集中,则当所有嵌套属性都为 时,整个属性将根据 的 设置为 null,其中 s 对象为 。
与此行为相反,尝试使用默认构造函数或接受结果集中的可为 null 参数值的构造函数创建新实例。
namenullembeddedEntityonEmpty@EmbeddednullnullUSE_EMPTYSpring中文文档

例 1.嵌入对象示例代码
class MyEntity {

    @Id
    Integer id;

    @Embedded(onEmpty = USE_NULL) (1)
    EmbeddedEntity embeddedEntity;
}

class EmbeddedEntity {
    String name;
}
1 Nulls 如果在 . 用于实例化属性的潜在值。embeddedEntitynamenullUSE_EMPTYembeddedEntitynullname

如果在实体中多次需要值对象,则可以使用注释的可选元素来实现。 此元素表示前缀,并且是嵌入对象中每个列名称的前缀。prefix@EmbeddedSpring中文文档

使用快捷键 & for 和 来减少冗长,并同时相应地设置 JSR-305。@Embedded.Nullable@Embedded.Empty@Embedded(onEmpty = USE_NULL)@Embedded(onEmpty = USE_EMPTY)@javax.annotation.NonnullSpring中文文档

class MyEntity {

    @Id
    Integer id;

    @Embedded.Nullable (1)
    EmbeddedEntity embeddedEntity;
}
1 的快捷方式。@Embedded(onEmpty = USE_NULL)

包含 a 或 a 的嵌入实体将始终被视为非空实体,因为它们至少包含空集合或映射。 因此,当使用 @Embedded(onEmpty = USE_NULL) 时,这样的实体永远不会是偶数。CollectionMapnullSpring中文文档

1 Nulls 如果在 . 用于实例化属性的潜在值。embeddedEntitynamenullUSE_EMPTYembeddedEntitynullname

使用快捷键 & for 和 来减少冗长,并同时相应地设置 JSR-305。@Embedded.Nullable@Embedded.Empty@Embedded(onEmpty = USE_NULL)@Embedded(onEmpty = USE_EMPTY)@javax.annotation.NonnullSpring中文文档

class MyEntity {

    @Id
    Integer id;

    @Embedded.Nullable (1)
    EmbeddedEntity embeddedEntity;
}
1 的快捷方式。@Embedded(onEmpty = USE_NULL)
1 的快捷方式。@Embedded(onEmpty = USE_NULL)

只读属性

Spring Data 不会将带有注释的属性写入数据库,但在加载实体时会读取它们。@ReadOnlyPropertySpring中文文档

Spring Data 在写入实体后不会自动重新加载实体。 因此,如果要查看数据库中为此类列生成的数据,则必须显式重新加载它。Spring中文文档

如果带批注的属性是实体或实体集合,则它由单独表中的一个或多个单独行表示。 Spring Data 不会对这些行执行任何插入、删除或更新。Spring中文文档

“仅插入”属性

注释的属性只会在插入操作期间由 Spring Data 写入数据库。 对于更新,这些属性将被忽略。@InsertOnlyPropertySpring中文文档

@InsertOnlyProperty仅聚合根支持。Spring中文文档

客制化对象构造

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

  • 如果使用注释对参数进行注释,则对给定的表达式进行计算,并将结果用作参数值。@ValueSpring中文文档

  • 如果 Java 类型的属性的名称与输入行的给定字段匹配,则其属性信息将用于选择要将输入字段值传递到的相应构造函数参数。 仅当 Java 文件中存在参数名称信息时,这才有效,您可以通过使用调试信息编译源代码或使用 Java 8 中的命令行开关来实现。.class-parametersjavacSpring中文文档

  • 否则,将抛出 a 以指示无法绑定给定的构造函数参数。MappingExceptionSpring中文文档

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
}

使用显式转换器重写映射

Spring Data 允许注册自定义转换器,以影响值在数据库中的映射方式。 目前,转换器仅应用于媒体资源级别,即您只能将域中的单个值转换为数据库中的单个值并返回。 不支持复杂对象和多列之间的转换。Spring中文文档

使用已注册的 Spring 转换器编写属性

下面的示例演示从对象转换为值的实现:ConverterBooleanStringSpring中文文档

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";
    }
}

这里有几件事需要注意:并且都是简单的类型,因此Spring Data需要提示此转换器应应用的方向(读取或写入)。 通过注释此转换器,您可以指示 Spring Data 将每个属性写入数据库中。BooleanString@WritingConverterBooleanStringSpring中文文档

使用弹簧转换器读取

下面的示例演示从 a 转换为值的 a 的实现:ConverterStringBooleanSpring中文文档

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

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

这里有几件事需要注意:并且都是简单的类型,因此Spring Data需要提示此转换器应应用的方向(读取或写入)。 通过注释此转换器,您可以指示 Spring Data 转换数据库中应分配给属性的每个值。StringBoolean@ReadingConverterStringBooleanSpring中文文档

将弹簧转换器注册到JdbcConverter

class MyJdbcConfiguration extends AbstractJdbcConfiguration {

    // …

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

}
在以前的Spring Data JDBC版本中,建议直接覆盖。 这不再是必需的,甚至不再是推荐的,因为该方法汇集了用于所有数据库的转换、使用者注册的转换和用户注册的转换。 如果您从旧版本的 Spring Data JDBC 迁移并覆盖了转换,则不会注册。AbstractJdbcConfiguration.jdbcCustomConversions()DialectAbstractJdbcConfiguration.jdbcCustomConversions()Dialect

如果要依靠 Spring Boot 来引导 Spring Data JDBC,但仍希望覆盖配置的某些方面,则可能需要公开该类型的 bean。 对于自定义转换,您可以选择注册一个 Bean 类型,该 Bean 类型将由 Boot 基础设施选取。 要了解更多信息,请务必阅读 Spring Boot 参考文档JdbcCustomConversionsSpring中文文档

Jdbc值

值转换用于使用类型扩充传播到 JDBC 操作的值。 如果需要指定特定于 JDBC 的类型而不是使用类型派生,请注册自定义写入转换器。 此转换器应将具有值和实际字段的值转换为该值。JdbcValuejava.sql.TypesJdbcValueJDBCTypeSpring中文文档

在以前的Spring Data JDBC版本中,建议直接覆盖。 这不再是必需的,甚至不再是推荐的,因为该方法汇集了用于所有数据库的转换、使用者注册的转换和用户注册的转换。 如果您从旧版本的 Spring Data JDBC 迁移并覆盖了转换,则不会注册。AbstractJdbcConfiguration.jdbcCustomConversions()DialectAbstractJdbcConfiguration.jdbcCustomConversions()Dialect

如果要依靠 Spring Boot 来引导 Spring Data JDBC,但仍希望覆盖配置的某些方面,则可能需要公开该类型的 bean。 对于自定义转换,您可以选择注册一个 Bean 类型,该 Bean 类型将由 Boot 基础设施选取。 要了解更多信息,请务必阅读 Spring Boot 参考文档JdbcCustomConversionsSpring中文文档