原标题:Spring认证中国教育管理中心-Spring Data Couchbase教程二(Spring中国教育管理中心)
2.1.3一般建议
- 尝试坚持使用不可变对象 ——不可变对象很容易创建,因为实现对象只需调用其构造函数即可。此外,这可以避免您的域对象被允许客户端代码操纵对象状态的 setter 方法乱扔垃圾。如果您需要这些,最好将它们包保护起来,以便它们只能被有限数量的并置类型调用。仅构造函数实现比属性填充快 30%。
- 提供一个全参数的构造函数 ——即使你不能或不想将你的实体建模为不可变值,提供一个将实体的所有属性作为参数(包括可变属性)的构造函数仍然有价值,因为这允许对象映射以跳过属性填充以获得最佳性能。
- 使用工厂方法而不是重载的构造函数来避免@PersistenceConstructor - 使用最佳性能所需的全参数构造函数,我们通常希望公开更多特定于应用程序用例的构造函数,这些构造函数省略自动生成的标识符等内容。这是一种既定的模式,而不是使用静态工厂方法来公开这些全参数构造函数的变体。
- 确保遵守允许使用生成的实例化器和属性访问器类的约束 ——
- 对于要生成的标识符,仍将 final 字段与全参数持久性构造函数(首选)或with…方法结合使用 ——
- 使用 Lombok 避免样板代码 - 由于持久性操作通常需要一个构造函数来获取所有参数,因此它们的声明变成了对字段分配的样板参数的繁琐重复,使用 Lombok 可以最好地避免这种情况@AllArgsConstructor。
覆盖属性
Java 允许灵活设计域类,其中子类可以定义一个已在其超类中以相同名称声明的属性。考虑以下示例:
代码语言:javascript复制public class SuperType {
private CharSequence field;
public SuperType(CharSequence field) {
this.field = field;
}
public CharSequence getField() {
return this.field;
}
public void setField(CharSequence field) {
this.field = field;
}
}
public class SubType extends SuperType {
private String field;
public SubType(String field) {
super(field);
this.field = field;
}
@Override
public String getField() {
return this.field;
}
public void setField(String field) {
this.field = field;
// optional
super.setField(field);
}
}
这两个类都定义了一个fieldusing 可分配类型。SubType然而阴影SuperType.field。根据类设计,使用构造函数可能是设置的唯一默认方法SuperType.field。或者,调用super.setField(…)setter 可以设置fieldin SuperType。所有这些机制都会在某种程度上产生冲突,因为属性共享相同的名称但可能代表两个不同的值。如果类型不可分配,Spring Data 会跳过超类型属性。也就是说,被覆盖的属性的类型必须可以分配给它的超类型属性类型才能注册为覆盖,否则超类型属性被认为是瞬态的。我们通常建议使用不同的属性名称。
Spring Data 模块通常支持覆盖不同值的属性。从编程模型的角度来看,有几点需要考虑:
- 应该保留哪个属性(默认为所有声明的属性)?您可以通过使用 注释这些属性来排除属性@Transient。
- 如何在数据存储中表示属性?对不同的值使用相同的字段/列名称通常会导致数据损坏,因此您应该使用明确的字段/列名称注释至少一个属性。
- @AccessType(PROPERTY)不能使用 using ,因为如果不对 setter 实现进行任何进一步的假设,通常无法设置超属性。
2.1.4。Kotlin 支持
Spring Data 调整了 Kotlin 的细节以允许对象创建和变异。
Kotlin 对象创建
Kotlin 类支持实例化,默认情况下所有类都是不可变的,需要明确的属性声明来定义可变属性。考虑以下data类Person:
代码语言:javascript复制data class Person(val id: String, val name: String)
上面的类编译成一个带有显式构造函数的典型类。我们可以通过添加另一个构造函数来自定义这个类,并用注释@PersistenceConstructor来指示构造函数的偏好:
代码语言:javascript复制data class Person(var id: String, val name: String) {
@PersistenceConstructor
constructor(id: String) : this(id, "unknown")
}
Kotlin 通过在未提供参数时允许使用默认值来支持参数可选性。当 Spring Data 检测到具有参数默认值的构造函数时,如果数据存储不提供值(或简单地返回null),它将使这些参数不存在,因此 Kotlin 可以应用参数默认值。考虑以下应用参数默认值的类name
代码语言:javascript复制data class Person(var id: String, val name: String = "unknown")
每次name参数不是结果的一部分或其值为 时null,name默认为unknown。
Kotlin 数据类的属性总体
在 Kotlin 中,默认情况下所有类都是不可变的,并且需要显式的属性声明来定义可变属性。考虑以下data类Person:
代码语言:javascript复制data class Person(val id: String, val name: String)
这个类实际上是不可变的。它允许创建新实例,因为 Kotlin 生成一个copy(…)创建新对象实例的方法,该方法从现有对象复制所有属性值并将作为参数提供的属性值应用到该方法。
Kotlin 覆盖属性
Kotlin 允许声明属性覆盖以更改子类中的属性。
代码语言:javascript复制open class SuperType(open var field: Int)
class SubType(override var field: Int = 1) :
SuperType(field) {
}
这样的安排呈现了两个名为 的属性field。Kotlin 为每个类中的每个属性生成属性访问器(getter 和 setter)。实际上,代码如下所示:
代码语言:javascript复制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;
}
}
getter 和 setterSubType仅在set 上SubType.field,而不在SuperType.field. 在这种安排中,使用构造函数是 set 的唯一默认方法SuperType.field。添加一个方法来SubType设置 SuperType.fieldviathis.SuperType.field = …是可能的,但不属于支持的约定。属性覆盖在某种程度上会产生冲突,因为属性共享相同的名称但可能代表两个不同的值。我们通常建议使用不同的属性名称。
Spring Data 模块通常支持覆盖不同值的属性。从编程模型的角度来看,有几点需要考虑:
- 应该保留哪个属性(默认为所有声明的属性)?您可以通过使用 注释这些属性来排除属性@Transient。
- 如何在数据存储中表示属性?对不同的值使用相同的字段/列名称通常会导致数据损坏,因此您应该使用明确的字段/列名称注释至少一个属性。
- @AccessType(PROPERTY)由于无法设置超属性,因此无法使用 using 。
2.2.文档和字段
所有实体都应使用注释进行@Document注释,但这不是必需的。
此外,实体中的每个字段都应使用注释进行@Field注释。虽然这是 - 严格来说 - 可选的,但它有助于减少边缘情况并清楚地显示实体的意图和设计。它还可以用于以不同的名称存储字段。
还有一个特殊的@Id注释需要始终到位。最佳做法是同时命名属性 id。
这是一个非常简单的User实体:
示例 6. 带有字段的简单文档
代码语言:javascript复制import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.mapping.Field;
import org.springframework.data.couchbase.core.mapping.Document;
@Document
public class User {
@Id
private String id;
@Field
private String firstname;
@Field
private String lastname;
public User(String id, String firstname, String lastname) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
}
public String getId() {
return id;
}
public String getFirstname() {
return firstname;
}
public String getLastname() {
return lastname;
}
}
Couchbase Server 支持文档自动过期。该库通过@Document注释实现对它的支持。您可以设置一个expiry值,该值转换为文档被自动删除之前的秒数。如果你想让它在突变后 10 秒内过期,请将其设置为@Document(expiry = 10). 或者,您可以使用 Spring 的属性支持和expiryExpression参数配置到期,以允许动态更改到期值。例如:@Document(expiryExpression = "${valid.document.expiry}")。该属性必须可解析为 int 值,并且不能混合使用这两种方法。
如果您想要文档中的字段名称与实体中使用的字段名称不同的表示形式,您可以在@Field注释上设置不同的名称。例如,如果您想保持文档较小,您可以将 firstname 字段设置为@Field("fname")。在JSON文件,你会看到{"fname": ".."},而不是{"firstname": ".."}。
在@Id注释中需要存在,因为Couchbase每个文件需要一个唯一的密钥。该键必须是长度不超过 250 个字符的任意字符串。随意使用适合您用例的任何内容,无论是 UUID、电子邮件地址还是其他任何内容。
2.3.数据类型和转换器
选择的存储格式是 JSON。这很棒,但与许多数据表示一样,它允许的数据类型比您直接用 Java 表达的要少。因此,对于所有非原始类型,需要进行某种形式的与支持类型之间的转换。
对于以下实体字段类型,无需添加特殊处理:
由于JSON支持对象(“映射”)和列表,Map和List类型可以自然被转换。如果它们只包含最后一段中的原始字段类型,则您也不需要添加特殊处理。这是一个例子:
示例 7. 带有地图和列表的文档
代码语言:javascript复制@Document
public class User {
@Id
private String id;
@Field
private List<String> firstnames;
@Field
private Map<String, Integer> childrenAges;
public User(String id, List<String> firstnames, Map<String, Integer> childrenAges) {
this.id = id;
this.firstnames = firstnames;
this.childrenAges = childrenAges;
}
}
用一些示例数据存储用户可能看起来像这样的 JSON 表示:
示例 8. 带有地图和列表的文档 - JSON
代码语言:javascript复制{
"_class": "foo.User",
"childrenAges": {
"Alice": 10,
"Bob": 5
},
"firstnames": [
"Foo",
"Bar",
"Baz"
]
}
您不需要一直将所有内容分解为原始类型和列表/映射。当然,您也可以用这些原始值组合其他对象。让我们修改最后一个示例,以便我们要存储 a Listof Children:
示例 9. 包含组合对象的文档
代码语言:javascript复制@Document
public class User {
@Id
private String id;
@Field
private List<String> firstnames;
@Field
private List<Child> children;
public User(String id, List<String> firstnames, List<Child> children) {
this.id = id;
this.firstnames = firstnames;
this.children = children;
}
static class Child {
private String name;
private int age;
Child(String name, int age) {
this.name = name;
this.age = age;
}
}
}
填充的对象可能如下所示:
示例 10. 包含组合对象的文档 - JSON
代码语言:javascript复制{
"_class": "foo.User",
"children": [
{
"age": 4,
"name": "Alice"
},
{
"age": 3,
"name": "Bob"
}
],
"firstnames": [
"Foo",
"Bar",
"Baz"
]
}
大多数情况下,您还需要存储一个时间值,例如 a Date。由于它不能直接存储在 JSON 中,因此需要进行转换。该库实现默认的转换器Date,Calendar以及JodaTime类型(如果在classpath)。所有这些在文档中默认表示为一个 unix 时间戳(数字)。您始终可以使用自定义转换器覆盖默认行为,如下所示。这是一个例子:
示例 11. 带有日期和日历的文档
代码语言:javascript复制@Document
public class BlogPost {
@Id
private String id;
@Field
private Date created;
@Field
private Calendar updated;
@Field
private String title;
public BlogPost(String id, Date created, Calendar updated, String title) {
this.id = id;
this.created = created;
this.updated = updated;
this.title = title;
}
}
填充的对象可能如下所示:
示例 12. 带有日期和日历的文档 - JSON
代码语言:javascript复制{
"title": "a blog post title",
"_class": "foo.BlogPost",
"updated": 1394610843,
"created": 1394610843897
}
可选地,可以通过将系统属性设置 org.springframework.data.couchbase.useISOStringConverterForDate为 true来将日期转换为符合 ISO-8601 的字符串。如果您想覆盖转换器或实现自己的转换器,这也是可能的。该库实现了一般的 Spring Converter 模式。您可以在配置中的 bean 创建时间插入自定义转换器。这是您可以配置它的方法(在您的覆盖中AbstractCouchbaseConfiguration):
示例 13. 自定义转换器
代码语言:javascript复制@Override
public CustomConversions customConversions() {
return new CustomConversions(Arrays.asList(FooToBarConverter.INSTANCE, BarToFooConverter.INSTANCE));
}
@WritingConverter
public static enum FooToBarConverter implements Converter<Foo, Bar> {
INSTANCE;
@Override
public Bar convert(Foo source) {
return /* do your conversion here */;
}
}
@ReadingConverter
public static enum BarToFooConverter implements Converter<Bar, Foo> {
INSTANCE;
@Override
public Foo convert(Bar source) {
return /* do your conversion here */;
}
}
自定义转换需要注意以下几点:
- 为了明确起见,请始终在转换器上使用@WritingConverter和@ReadingConverter注释。特别是如果您正在处理原始类型转换,这将有助于减少可能的错误转换。
- 如果你实现了一个写入转换器,请确保只解码为原始类型、映射和列表。如果您需要更复杂的对象类型,请使用CouchbaseDocument和CouchbaseList类型,底层翻译引擎也可以理解这些类型。您最好的选择是坚持尽可能简单的转换。
- 始终在通用转换器之前放置更多特殊转换器,以避免执行错误转换器的情况。
- 对于日期,读取转换器应该能够从任何Number(不仅仅是Long)读取。这是 N1QL 支持所必需的。