原标题:Spring认证中国教育管理中心-Spring Data R2DBC框架教程六(Spring中国教育管理中心

16.1.4.Kotlin 支持

Spring Data 调整了 Kotlin 的细节以允许创建和更改对象。

Kotlin 对象创建

Kotlin 类支持实例化,默认情况下所有类都是不可变的,并且需要显式属性声明来定义可变属性。考虑以下data类Person:

data class Person(val id: String, val name: String)

上面的类编译为具有显式构造函数的典型类。我们可以通过添加另一个构造函数来自定义这个类,并使用注释@PersistenceConstructor来指示构造函数首选项:

data class Person(var id: String, val name: String) {      @PersistenceConstructor     constructor(id: String) : this(id, "unknown") }

Kotlin 通过允许在未提供参数时使用默认值来支持参数可选性。当 Spring Data 检测到具有参数默认值的构造函数时,如果数据存储不提供值(或简单地返回null),它就会使这些参数不存在,因此 Kotlin 可以应用参数默认值。考虑以下应用参数默认值的类name

data class Person(var id: String, val name: String = "unknown")

每次name参数不是结果的一部分或其值为 时null,则name默认为unknown。

Kotlin 数据类的属性填充

在 Kotlin 中,默认情况下所有类都是不可变的,并且需要明确的属性声明来定义可变属性。考虑以下data类Person:

data class Person(val id: String, val name: String)

这个类实际上是不可变的。它允许创建新实例,因为 Kotlin 生成copy(…)创建新对象实例的方法,该方法从现有对象复制所有属性值并将作为参数提供的属性值应用到该方法。

Kotlin 覆盖属性

Kotlin 允许声明属性覆盖来改变子类中的属性。

open class SuperType(open var field: Int)  class SubType(override var field: Int = 1) :   SuperType(field) { }

这样的安排呈现了两个名称为 的属性field。Kotlin 为每个类中的每个属性生成属性访问器(getter 和 setter)。实际上,代码如下所示:

public class SuperType {     private int field;     public SuperType(int field) {       this.field = field;    }     public int getField() {       return this.field;    }     public void setField(int field) {       this.field = field;    } }  public final class SubType extends SuperType {     private int field;     public SubType(int field) {       super(field);       this.field = field;    }     public int getField() {       return this.field;    }     public void setField(int field) {       this.field = field;    } }

#yyds干货盘点#Spring认证中国教育管理中心-Spring Data R2DBC框架教程六_数据库

getter 和 setterSubType只在set 上,SubType.field而不是SuperType.field. 在这种安排中,使用构造函数是设置的唯一默认方法SuperType.field。添加方法 to SubTypeset
SuperType.fieldviathis.SuperType.field = …是可能的,但不属于支持的约定。属性覆盖在某种程度上会产生冲突,因为属性共享相同的名称但可能代表两个不同的值。我们通常建议使用不同的属性名称。

Spring Data 模块通常支持包含不同值的覆盖属性。从编程模型的角度来看,需要考虑以下几点:

  1. 应该保留哪个属性(默认为所有声明的属性)?您可以通过使用 注释这些属性来排除属性@Transient。
  2. 如何表示数据存储中的属性?对不同的值使用相同的字段/列名称通常会导致数据损坏,因此您应该使用明确的字段/列名称来注释至少一个属性。
  3. using@AccessType(PROPERTY)不能使用,因为不能设置超级属性。

16.2.基于约定的映射

MappingR2dbcConverter当没有提供额外的映射元数据时,有一些将对象映射到行的约定。这些约定是:

  • 简短的 Java 类名以下列方式映射到表名。将com.bigbank.SavingsAccount类映射到SAVINGS_ACCOUNT表名。相同的名称映射应用于将字段映射到列名称。例如,firstName字段映射到FIRST_NAME列。您可以通过提供自定义NamingStrategy. 有关更多详细信息,请参阅映射配置。默认情况下,在 SQL 语句中使用从属性或类名派生的表名和列名,不带引号。您可以通过设置来控制这种行为R2dbcMappingContext.setForceQuote(true)。
  • 不支持嵌套对象。
  • 转换器使用任何注册的 Spring 转换器来覆盖对象属性到行列和值的默认映射。
  • 对象的字段用于在行中的列之间进行转换。JavaBean不使用公共属性。
  • 如果您有一个非零参数构造函数,其构造函数参数名称与行的顶级列名称匹配,则使用该构造函数。否则,将使用零参数构造函数。如果有多个非零参数构造函数,则会引发异常。

16.3.映射配置

默认情况下(除非明确配置)MappingR2dbcConverter在您创建DatabaseClient. 您可以创建自己的MappingR2dbcConverter. 通过创建您自己的实例,您可以注册 Spring 转换器以将特定类映射到数据库或从数据库映射。

您可以配置MappingR2dbcConverter以及DatabaseClient和ConnectionFactory使用基于Java的元数据。以下示例使用 Spring 的基于 Java 的配置:

如果设置setForceQuote为R2dbcMappingContext totrue,则从类和属性派生的表名和列名将与数据库特定的引号一起使用。这意味着可以在这些名称中使用保留的 SQL 字(例如 order)。您可以通过重写这样做r2dbcMappingContext(Optional<NamingStrategy>)的
AbstractR2dbcConfiguration。Spring Data 将此类名称的字母大小写转换为不使用引用时配置的数据库也使用的形式。因此,您可以在创建表时使用不带引号的名称,只要您的名称中不使用关键字或特殊字符即可。对于遵循 SQL 标准的数据库,这意味着名称被转换为大写。引用字符和名称大写的方式由 used 控制Dialect。有关如何配置自定义方言的信息,请参阅R2DBC 驱动程序。

例 87.@Configuration 类来配置 R2DBC 映射支持

@Configuration public class MyAppConfig extends AbstractR2dbcConfiguration {    public ConnectionFactory connectionFactory() {     return ConnectionFactories.get("r2dbc:…");   }    // the following are optional    @Override   protected List<Object> getCustomConverters() {      List<Converter<?, ?>> converterList = new ArrayList<Converter<?, ?>>();     converterList.add(new org.springframework.data.r2dbc.test.PersonReadConverter());     converterList.add(new org.springframework.data.r2dbc.test.PersonWriteConverter());     return converterList;   } }

AbstractR2dbcConfiguration要求您实现一个定义ConnectionFactory.

您可以通过覆盖该r2dbcCustomConversions方法向转换器添加其他转换器。

您可以NamingStrategy通过将自定义注册为 bean来配置它。该NamingStrategy控件类和属性的名称是如何地转化为表和列的名称。

AbstractR2dbcConfiguration创建一个DatabaseClient实例并将其注册到名为 的容器中databaseClient。

16.4.基于元数据的映射

要充分利用 Spring Data R2DBC 支持中的对象映射功能,您应该使用注释对映射的对象进行@Table注释。尽管映射框架没有必要具有此注释(您的 POJO 已正确映射,即使没有任何注释),但它允许类路径扫描器查找和预处理您的域对象以提取必要的元数据。如果你不使用这个注解,你的应用程序在你第一次存储域对象时会受到轻微的性能影响,因为映射框架需要建立它的内部元数据模型,以便它知道你的域对象的属性以及如何坚持他们。以下示例显示了一个域对象:

示例 88. 示例域对象

package com.mycompany.domain;  @Table public class Person {    @Id   private Long id;    private Integer ssn;    private String firstName;    private String lastName; }

该@Id注解告诉你想作为主键使用哪个属性映射器。

16.4.1.默认类型映射

下表解释了实体的属性类型如何影响映射:

#yyds干货盘点#Spring认证中国教育管理中心-Spring Data R2DBC框架教程六_构造函数_02

列的本机数据类型取决于 R2DBC 驱动程序类型映射。驱动程序可以提供额外的简单类型,例如几何类型。

16.4.2.映射注释概述

所述MappingR2dbcConverter可以使用元数据来驱动对象的映射的行。以下注释可用:

  • @Id: 在字段级别应用以标记主键。
  • @Table: 应用于类级别,表示该类是映射到数据库的候选。您可以指定存储数据库的表的名称。
  • @Transient: 默认情况下,所有字段都映射到行。此注释将应用它的字段排除在数据库中。瞬态属性不能在持久性构造函数中使用,因为转换器无法实现构造函数参数的值。
  • @PersistenceConstructor: 标记给定的构造函数——即使是受包保护的构造函数——在从数据库实例化对象时使用。构造函数参数按名称映射到检索行中的值。
  • @Value:这个注解是Spring框架的一部分。在映射框架内,它可以应用于构造函数参数。这使您可以使用 Spring 表达式语言语句来转换在数据库中检索到的键值,然后再使用它来构造域对象。为了引用给定行的列,必须使用以下表达式:@Value("#root.myProperty")其中 root 指的是给定的根Row。
  • @Column: 在字段级别应用,用于描述列在行中表示的名称,让名称与类的字段名称不同。用@Column注释指定的名称在 SQL 语句中使用时总是被引用。对于大多数数据库,这意味着这些名称区分大小写。这也意味着您可以在这些名称中使用特殊字符。但是,不建议这样做,因为它可能会导致其他工具出现问题。
  • @Version:应用于字段级别用于乐观锁定并检查保存操作的修改。值是null(zero对于原始类型)被视为新实体的标记。最初存储的值是zero(one对于原始类型)。每次更新时,版本都会自动增加。请参阅乐观锁定以获取更多参考。

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

16.4.3.自定义对象构建

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

  • 如果一个参数用注解进行@Value注解,则给定的表达式被求值,并将结果用作参数值。
  • 如果 Java 类型具有名称与输入行的给定字段匹配的属性,则其属性信息用于选择将输入字段值传递到的适当构造函数参数。这仅在 Java.class文件中存在参数名称信息时才有效,您可以通过使用调试信息编译源代码或使用Java 8 中的-parameters命令行开关来实现javac。
  • 否则,MappingException抛出 a 以指示无法绑定给定的构造函数参数。
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 ommitted }

16.4.4.使用显式转换器覆盖映射

在存储和查询对象时,拥有一个R2dbcConverter实例来处理所有 Java 类型到OutboundRow实例的映射通常很方便。但是,有时您可能希望R2dbcConverter实例完成大部分工作,但让您有选择地处理特定类型的转换——也许是为了优化性能。

要自己有选择地处理转换,请
org.springframework.core.convert.converter.Converter使用R2dbcConverter.

您可以使用 中的r2dbcCustomConversions方法
AbstractR2dbcConfiguration来配置转换器。本章开头的示例展示了如何使用 Java 执行配置。

自定义顶级实体转换需要非对称类型进行转换。入站数据是从 R2DBC 的Row. 出站数据(与INSERT/UPDATE语句一起使用)被表示为OutboundRow然后被组装成一个语句。

以下 Spring Converter 实现的示例从 aRow转换为PersonPOJO:

@ReadingConverter  public class PersonReadConverter implements Converter<Row, Person> {    public Person convert(Row source) {     Person p = new Person(source.get("id", String.class),source.get("name", String.class));     p.setAge(source.get("age", Integer.class));     return p;   } }

请注意,转换器适用于单一属性。集合属性(例如Collection<Person>)按元素进行迭代和转换。Converter<List<Person>>, OutboundRow不支持集合转换器(例如)。

R2DBC 使用装箱原语(Integer.class而不是int.class)来返回原语值。

以下示例从 a 转换Person为 a OutboundRow:

@WritingConverter public class PersonWriteConverter implements Converter<Person, OutboundRow> {    public OutboundRow convert(Person source) {     OutboundRow row = new OutboundRow();     row.put("id", SettableValue.from(source.getId()));     row.put("name", SettableValue.from(source.getFirstName()));     row.put("age", SettableValue.from(source.getAge()));     return row;   } }

使用显式转换器覆盖枚举映射

某些数据库(例如Postgres)可以使用其特定于数据库的枚举列类型本机写入枚举值。Spring DataEnum默认将String值转换为最大可移植性的值。要保留实际枚举值,请注册一个@Writing转换器,其源和目标类型使用实际枚举类型以避免使用Enum.name()转换。此外,您需要在驱动程序级别配置枚举类型,以便驱动程序知道如何表示枚举类型。

以下示例显示了Color本机读取和写入枚举值的相关组件:

enum Color {     Grey, Blue }  class ColorConverter extends EnumWriteSupport<Color> {  }   class Product {     @Id long id;     Color color;      // … }