mapper可以利用自定义编解码器将自定义转换映射到 columns和fields.
Declaring codecs
假定有一张表,含有timestamp column:
create table user(id int primary key, birth timestamp);
还create了一个custom class, 一个codec 来 vonvert it:
public class MyCustomDate { ... }public class MyCustomDateCodec extends TypeCodec{ 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:
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类, 而不需要额外的配置:
@Table(name = "user")public class User { @PartitionKey private int id; private MyCustomDate birth; ... // getters and setters}
This also works in accessors:
@Accessorinterface 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:
Cluster cluster = Cluster.builder() .addContactPoint("127.0.0.1") .build();MappingManager mappingManager = new MappingManager(cluster.connect());
@Column注解就起到作用了
@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.
This also works with and annotations.
Implicit UDT codecs
隐式codec
@UDT(name = "address")public class Address { ... }@Entity(name = "user")public class User { ... private Address address; ...}MapperuserMapper = 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, 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项基本操作
- a Java object into a CQL value;
- a CQL value into a Java object;
- a Java object into a CQL literal;
- a CQL literal into a Java object.
Implementing and using custom codecs
有这样一种场景: user有JSON文档store在varchar column, 他希望driver使用 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 JsonCodecextends TypeCodec { private final ObjectMapper objectMapper = new ObjectMapper(); public JsonCodec(Class 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实例
JsonCodecmyJsonCodec = new JsonCodec (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 SimpleStatementStatement stmt = new SimpleStatement("INSERT INTO t (id, json) VALUES (?, ?)", 42, myPojo));// Using the Query BuilderBuiltStatement insertStmt = QueryBuilder.insertInto("t") .value("id", 42) .value("json", myPojo);// Using BoundStatementsPreparedStatement 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 itString 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