Cassandra 的Custom Codecs

mapper能夠利用自定義編解碼器將自定義轉換映射到 columns和fields.html

Declaring codecs

假定有一張表,含有timestamp column:java

create table user(id int primary key, birth timestamp);

還create了一個custom class, 一個codec 來 vonvert it:git

public class MyCustomDate { ... }

public class MyCustomDateCodec extends TypeCodec<MyCustomDate> {
    public MyCustomDate() {
        super(DataType.timestamp(), MyCustomDate.class);
    }
    ...
}

 

Pre-registered codecs

若是你register codec with the mapper's underlying Cluster, it will be automatically available to the mapper:github

Cluster cluster = Cluster.builder()
    .addContactPoint("127.0.0.1")
    .withCodecRegistry(
        new CodecRegistry().register(new MyCustomDateCodec())
    ).build();

MappingManager mappingManager = new MappingManager(cluster.connect());

你能夠正常的使用自定義的type來  create mapped類, 而不須要額外的配置:json

@Table(name = "user")
public class User {
  @PartitionKey
  private int id;
  private MyCustomDate birth;

  ... // getters and setters
}

 

This also works in accessors:session

@Accessor
interface UserAccessor {
  @Query("update user set birth = :b where id = :i")
  void updateBirth(@Param("i") int id,
                   @Param("b") MyCustomDate birth);
}

 

One-time declaration

有時只須要在指定的column/field上使用codec, Cluster初始化時不須要register:app

Cluster cluster = Cluster.builder()
    .addContactPoint("127.0.0.1")
    .build();

MappingManager mappingManager = new MappingManager(cluster.connect());

 

@Column註解就起到做用了ide

@Table(name = "user")
public class User {
  @PartitionKey
  private int id;
  @Column(codec = MyCustomDateCodec.class)
  private MyCustomDate birth;

  ... // getters and setters
}

The class must have a no-arg constructor. The mapper will create an instance (one per column) and cache it for future use.ui

This also works with @Field and @Param annotations.spa

 

Implicit UDT codecs

隱式codec

@UDT(name = "address")
public class Address { ... }

@Entity(name = "user")
public class User {
  ...
  private Address address;
  ...
}

Mapper<User> userMapper = mappingManager.mapper(User.class);

// Codec is now registered for Address <-> CQL address
Row row = session.execute("select address from user where id = 1").one();
Address address = row.get("address", Address.class);

If you don't use entity mappers but still want the convenience of the UDT codec for core driver methods, the mapper provides a way to create it independently:

mappingManager.udtCodec(Address.class);

// Codec is now registered

 

 

 

 

 

 

 

 

 

////////////////////////////////////分界線, 上面的是使用,下面的是介紹//////////////////////////////////////////////////////////

Custom codecs支持 transparent, user-configurable mapping of CQL types to arbitrary Java objects.

這種特徵的實際用例不少:

  • Ability to map CQL timestamp, date and time columns to Java 8 or Joda Time classes (rather than the built-in mappings for Date, LocalDate and Long);
  • Ability to map CQL varchar columns directly to JSON or XML mapped objects (i.e. using frameworks such as Jackson);
  • Ability to map CQL user-defined types to Java objects;
  • Ability to map CQL lists directly to Java arrays;
  • Ability to map CQL collections directly to Scala collections;
  • etc.

Overview of the serialization mechanism 序列化機制概述

序列化機制的中心部分是TypeCodec。

每個TypeCodec支持 Java type和CQL type 雙向映射.  所以TypeCodec可以進行4項基本操做

  • Serialize a Java object into a CQL value;
  • Deserialize a CQL value into a Java object;
  • Format a Java object into a CQL literal;
  • Parse a CQL literal into a Java object.

 

Implementing and using custom codecs

有這樣一種場景: user有JSON文檔store在varchar column, 他但願driver使用Jackson library自動映射此column 爲Java對象,而不是返回一個原生的JSON string.

簡單的table結構以下

CREATE TABLE t (id int PRIMARY KEY, json VARCHAR);

首先實現一個合適的codec. 使用Jackson, 一個可能的Json codec 以下:

/**
 * A simple Json codec.
 */
public class JsonCodec<T> extends TypeCodec<T> {

    private final ObjectMapper objectMapper = new ObjectMapper();

    public JsonCodec(Class<T> javaType) {
        super(DataType.varchar(), javaType);
    }

    @Override
    public ByteBuffer serialize(T value, ProtocolVersion protocolVersion) throws InvalidTypeException {
        if (value == null)
            return null;
        try {
            return ByteBuffer.wrap(objectMapper.writeValueAsBytes(value));
        } catch (JsonProcessingException e) {
            throw new InvalidTypeException(e.getMessage(), e);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public T deserialize(ByteBuffer bytes, ProtocolVersion protocolVersion) throws InvalidTypeException {
        if (bytes == null)
            return null;
        try {
            byte[] b = new byte[bytes.remaining()];
            // always duplicate the ByteBuffer instance before consuming it!
            bytes.duplicate().get(b);
            return (T) objectMapper.readValue(b, toJacksonJavaType());
        } catch (IOException e) {
            throw new InvalidTypeException(e.getMessage(), e);
        }
    }

    @Override
    public String format(T value) throws InvalidTypeException {
        if (value == null)
            return "NULL";
        String json;
        try {
            json = objectMapper.writeValueAsString(value);
        } catch (IOException e) {
            throw new InvalidTypeException(e.getMessage(), e);
        }
        return '\'' + json.replace("\'", "''") + '\'';
    }

    @Override
    @SuppressWarnings("unchecked")
    public T parse(String value) throws InvalidTypeException {
        if (value == null || value.isEmpty() || value.equalsIgnoreCase("NULL"))
            return null;
        if (value.charAt(0) != '\'' || value.charAt(value.length() - 1) != '\'')
            throw new InvalidTypeException("JSON strings must be enclosed by single quotes");
        String json = value.substring(1, value.length() - 1).replace("''", "'");
        try {
            return (T) objectMapper.readValue(json, toJacksonJavaType());
        } catch (IOException e) {
            throw new InvalidTypeException(e.getMessage(), e);
        }
    }

    protected JavaType toJacksonJavaType() {
        return TypeFactory.defaultInstance().constructType(getJavaType().getType());
    }

}

 

幾個實施指南:

  • Your codecs should be thread-safe, or better yet, immutable;
  • Your codecs should be fast: do not forget that codecs are executed often and are usually very "hot" pieces of code;
  • Your codecs should never block.

接下來register你的codec with CodecRegistry實例

JsonCodec<MyPojo> myJsonCodec = new JsonCodec<MyPojo>(MyPojo.class);
CodecRegistry myCodecRegistry = cluster.getConfiguration().getCodecRegistry();
myCodecRegistry.register(myJsonCodec);

Cluster's CodecRegistry 是最簡單的方式, Cluster實例默認使用CodecRegistry.DEFAULT_INSTANCE,對大多數用來講已經夠用.

然而,咱們仍是能夠create一個使用不一樣CodecRegistry的cluster

CodecRegistry myCodecRegistry = new CodecRegistry();
Cluster cluster = new Cluster.builder().withCodecRegistry(myCodecRegistry).build();

Note:新的CodecRegistry,會自動register全部默認的codecs.

 

Cluster cluster = ...
Session session = ...
MyPojo myPojo = ...
// Using SimpleStatement
Statement stmt = new SimpleStatement("INSERT INTO t (id, json) VALUES (?, ?)", 42, myPojo));
// Using the Query Builder
BuiltStatement insertStmt = QueryBuilder.insertInto("t")
    .value("id", 42)
    .value("json", myPojo);
// Using BoundStatements
PreparedStatement ps = session.prepare("INSERT INTO t (id, json) VALUES (?, ?)");
BoundStatement bs1 = ps.bind(42, myPojo); // or alternatively...
BoundStatement bs2 = ps.bind()
    .setInt(0, 42)
    .set(1, myPojo, MyPojo.class);

 

And here is how to retrieve a MyPojo object converted from a JSON document:

ResultSet rs = session.execute(...);
Row row = rs.one();
// Let the driver convert the string for you...
MyPojo myPojo = row.get(1, MyPojo.class);
// ... or retrieve the raw string if you need it
String json = row.get(1, String.class); // row.getString(1) would have worked too

 

....

https://github.com/datastax/java-driver/tree/3.x/manual/custom_codecs

相關文章
相關標籤/搜索