Spring certified China Education Management Center - Spring Data Couchbase tutorial II

Posted by MystedVeil on Mon, 10 Jan 2022 06:58:24 +0100

Original title: Spring certified China Education Management Center - Spring Data Couchbase tutorial II (spring China Education Management Center)

2.1.3 general recommendations

  • Try to stick with immutable objects -- immutable objects are easy to create because the implementation object only needs to call its constructor. In addition, this prevents your domain object from being littered by setter methods that allow client code to manipulate the state of the object. If you need these, it's best to package them so that they can only be called by a limited number of collocated types. Constructor only implementation is 30% faster than property padding.
  • Provide a full parameter constructor -- even if you can't or don't want to model your entity as an immutable value, providing a constructor that takes all the attributes of the entity as parameters (including variable attributes) is still valuable, because it allows object mapping to skip attribute filling for best performance.
  • Use factory methods instead of overloaded constructors to avoid @ PersistenceConstructor   - use the all parameter constructors required for best performance. We usually want to expose more application use case specific constructors that omit automatically generated identifiers and so on. This is an established pattern, rather than using static factory methods to expose variations of these all parameter constructors.
  • Ensure compliance with the constraints that allow the use of generated instantiator and property accessor classes   --  
  • For the identifier to be generated, still use the final field in combination with the all parameter persistence constructor (preferred) or with... Method 𔁑 -- 𔁑
  • Use Lombok to avoid template code   - since persistence operations usually require a constructor to obtain all parameters, their declaration becomes cumbersome repetition of template parameters assigned to fields. Using Lombok can best avoid this situation @ AllArgsConstructor.

Override properties

Java allows flexible design of domain classes, where subclasses can define an attribute that has been declared with the same name in its superclass. Consider the following examples:

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

Both classes define a fieldusing assignable type. SubType: supertype field. Depending on the class design, using a constructor may be the only default method for setting supertype field. Alternatively, call super Setfield (...) setter can set fieldin SuperType. All of these mechanisms conflict to some extent because attributes share the same name but may represent two different values. If the type is not assignable, Spring Data skips the supertype property. In other words, the type of the overridden attribute must be able to be assigned to its supertype attribute type before it can be registered as an override, otherwise the supertype attribute is considered transient. We usually recommend using different attribute names.

Spring Data modules usually support attributes that override different values. From the perspective of programming model, there are several points to consider:

  1. Which attribute should be retained (default to all declared attributes)? You can exclude the attribute @ Transient by annotating these attributes.
  2. How are attributes represented in the data store? Using the same field / column name for different values usually results in data corruption, so you should annotate at least one attribute with an explicit field / column name.
  3. @AccessType(PROPERTY) cannot use using because it is usually impossible to set the super property without any further assumptions about the setter implementation.

2.1.4. Kotlin support

Spring Data tweaks the details of Kotlin to allow object creation and mutation.

Kotlin object creation

Kotlin classes support instantiation. By default, all classes are immutable. Explicit attribute declarations are required to define variable attributes. Consider the following data class Person:

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

The above class is compiled into a typical class with an explicit constructor. We can customize this class by adding another constructor, and indicate the constructor preference with the annotation @ PersistenceConstructor:

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

    @PersistenceConstructor
    constructor(id: String) : this(id, "unknown")
}

Kotlin supports parameter selectability by allowing default values when no parameters are provided. When Spring Data detects a constructor with parameter default values, if the data store does not provide values (or simply returns null), it will make these parameters nonexistent, so kotlin can apply parameter default values. Consider the following class name that applies the default value of the parameter

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

Each time the name parameter is not part of the result or its value is null, name defaults to unknown.

Overall properties of Kotlin data class

In Kotlin, all classes are immutable by default, and explicit attribute declarations are required to define variable attributes. Consider the following data class Person:

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

This class is actually immutable. It allows the creation of new instances because Kotlin generates a copy(...) method to create new object instances, which copies all attribute values from existing objects and applies the attribute values provided as parameters to the method.

Kotlin override properties

Kotlin allows you to declare property overrides to change properties in subclasses.

open class SuperType(open var field: Int)

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

This arrangement presents two properties called field. Kotlin generates property accessors (getter s and setter s) for each property in each class. In fact, the code is as follows:

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 and setterSubType are only available on set Field instead of supertype field. In this arrangement, using the constructor is the only default method for set, supertype field. Add a method to set the SubType SuperType.fieldviathis.SuperType.field =... Is possible, but is not a supported convention. Property overrides can conflict to some extent because properties share the same name but may represent two different values. We usually recommend using different attribute names.

Spring Data modules usually support attributes that override different values. From the perspective of programming model, there are several points to consider:

  1. Which attribute should be retained (default to all declared attributes)? You can exclude the attribute @ Transient by annotating these attributes.
  2. How are attributes represented in the data store? Using the same field / column name for different values usually results in data corruption, so you should annotate at least one attribute with an explicit field / column name.
  3. @AccessType(PROPERTY) cannot use using because the super property cannot be set.

2.2. Documents and fields

All entities should be @ Document annotated with annotations, but this is not required.

In addition, each Field in the entity should be annotated with a @ Field annotation. Although this is - strictly speaking - optional, it helps to reduce edge conditions and clearly show the intent and design of the entity. It can also be used to store fields with different names.

There is also a special @ id annotation that needs to always be in place. The best practice is to name the attribute id at the same time.

This is a very simple User entity:

Example 6 Simple document with fields

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 supports automatic document expiration. The library supports it through @ document annotations. You can set an expiration value, which is converted to the number of seconds before the document is automatically deleted. If you want it to expire within 10 seconds after the mutation, please set it to @ document (expiration = 10) Alternatively, you can configure expiration using Spring's property support and the expryexpression parameter to allow the expiration value to be changed dynamically. For example: @ document (expryexpression = "${valid. Document. Express}"). The property must be resolvable to an int value, and you cannot mix the two methods.

If you want a different representation of the Field name in the document from the Field name used in the entity, you can set a different name on the @ Field annotation. For example, if you want to keep the document small, you can set the firstname Field to @ Field("fname"). In the JSON file, you will see {"fname": ".."}, Instead of {"firstname": ".."}.

It needs to exist in the @ Id annotation because Couchbase needs a unique key for each file. The key must be any string with a length of no more than 250 characters. Feel free to use whatever is appropriate for your use case, whether it's UUID, email address, or anything else.

2.3. Data types and converters

The selected storage format is JSON. This is great, but like many data representations, it allows fewer data types than you can express directly in Java. Therefore, for all non primitive types, some form of conversion from supported types is required.

There is no need to add special treatment for the following entity field types:

Because JSON supports objects ("maps") and lists, Map and List types can be converted naturally. If they contain only the original field types in the last paragraph, you do not need to add special processing. Here is an example:

Example 7 Documents with maps and lists

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

}

With some sample data storage, the user may look like this JSON representation:

Example 8 Documents with maps and lists - JSON

{
    "_class": "foo.User",
    "childrenAges": {
        "Alice": 10,
        "Bob": 5
    },
    "firstnames": [
        "Foo",
        "Bar",
        "Baz"
    ]
}

You don't need to always break everything down into original types and lists / mappings. Of course, you can also combine other objects with these original values. Let's modify the last example so that we want to store a Listof Children:

Example 9 Documents containing composite objects

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

    }

}

Filled objects may look like this:

Example 10 Document containing composite objects - JSON

{
  "_class": "foo.User",
  "children": [
    {
      "age": 4,
      "name": "Alice"
    },
    {
      "age": 3,
      "name": "Bob"
    }
  ],
  "firstnames": [
    "Foo",
    "Bar",
    "Baz"
  ]
}

In most cases, you also need to store a time value, such as a Date. Since it cannot be stored directly in JSON, it needs to be transformed. The library implements the default converters Date, Calendar and JodaTime types (if in classpath). All of these are represented as a unix timestamp (number) by default in the document. You can always override the default behavior with a custom converter, as shown below. Here is an example:

Example 11 Document with date and calendar

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

}

Filled objects may look like this:

Example 12 Document with date and calendar - JSON

{
  "title": "a blog post title",
  "_class": "foo.BlogPost",
  "updated": 1394610843,
  "created": 1394610843897
}

Alternatively, you can set the system properties to org.springframework.data.couchbase.useISOStringConverterForDate is true to convert the date to an ISO-8601 compliant string. This is also possible if you want to override the converter or implement your own converter. The library implements the general Spring Converter pattern. You can insert a custom converter at the bean creation time in the configuration. This is how you can configure it (in your override, AbstractCouchbaseConfiguration):

Example 13 Custom converter

@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 */;
    }

}

The following points should be noted for custom conversion:

  • For clarity, always use @ WritingConverter and @ ReadingConverter annotations on the converter. Especially if you are dealing with raw type conversions, this will help reduce possible erroneous conversions.
  • If you implement a write converter, make sure to decode only the original types, mappings, and lists. If you need more complex object types, use CouchbaseDocument and CouchbaseList types, which can also be understood by the underlying translation engine. Your best choice is to keep the conversion as simple as possible.
  • Always place more special converters before the general converter to avoid executing the wrong converter.
  • For dates, the read converter should be able to read from any Number (not just Long). This is required for N1QL support.