Entity persister

dr3s edited this page Mar 3, 2013 · 28 revisions
Clone this wiki locally

Astyanax provides entity persister APIs that is similar to Java Persistence API (JPA). Please check out "com.netflix.astyanax.entitystore" package.

Quick notes

  • we uses annotations from javax.persistence package
  • entity class must be annotated with @Entity
  • field with @Id annotation will be written as rowKey in cassandra data model. If the same field can also have @Column annotation, then it will also be written as a column redundantly.
  • column name cannot contain dot (.) char, which is reserved separator for nested structure
  • duplication check for column name is done as case insensitive.

Basic put/get/delete operations

Here is sample code snippet from DefaultEntityManagerTest.java

final String id = "foo";
final SampleEntity origEntity = createRandomEntity(id);
final EntityManager<SampleEntity, String> entityManager = 
	new DefaultEntityManager.Builder<SampleEntity, String>()
	.withEntityType(SampleEntity.class)
	.withKeyspace(keyspace)
	.withColumnFamily(CF_SAMPLE_ENTITY)
	.build();

entityManager.put(origEntity);
SampleEntity getEntity = entityManager.get(id);
entityManager.delete(id);

TTL

Here are the ways to specify TTL (lower priority first)

  • @TTL annotation at entity class level
@Entity
@TTL(3600)  // 1 hour
public class FooEntity {
    // your class definition
}
  • DefaultEntityManager Builder
EntityManager<FooEntity, String> entityPersister = 
new DefaultEntityManager.Builder<FooEntity, String>()
	.withEntityType(FooEntity.class)
	.withKeyspace(keyspace)
	.withColumnFamily(columnFamily)
	.withTTL(3600)  // 1 hour
	.build();
  • @TTL annotation at entity method level. this the most flexible and dynamic way of deriving TTL value
@Entity
public class FooEntity {
    // it must be zero args and return Integer type
    @TTL
    public Integer getTTL() {
        // your TTL calculation logic
        // e.g. you may have entities that are time dependent 
        // (e.g. flight schedules, subscriptions to services, etc.). 
        // You can calculate the TTL value (in seconds) to basically 
        // expire it 1 week after the "end date" of that particular entity 
        // (which will vary per row/entity).
    }
}

Nested entity

  • We support nested structure. column name will be concatenated by "." char. That's why dot char is invalid in @Column name annotation.
  • Nested type must also be annotated with @Entity just like the root type. But the difference is that nested type doesn't need @Id field/annotation.
  • Nesting can be any arbitrary level deep.

Here is the code snippet from SampleEntity.java. When SampleEntity is written to cassandra. There will be two columns of "bar.i" and "bar.s".

@Entity
public class SampleEntity {
	@Entity
	public static class Bar {		
		@Column(name="i")
		public int i;
		@Column(name="s")
		public String s;
		...
	}
	...
	@Column(name="BAR")
	private Bar bar;
	...
}

Explicit @Serializer annotation

By default (without @Serializer annotation), astyanax will automatically infer the Serializer type based on entity field's java type. Here are the following inference types supported by entity persister.

  • basic java types (both primitive and wrapper): byte, short, int, long, boolean, float, double
  • String, byte[], Date, UUID

If you have a field that requires custom Serializer, you can specify it with the @Serializer annotation. Here is the code snippet from SampleEntity.java

public class SampleEntity {
	
	public static class Foo {
		
		public int i;
		public String s;
		
		public Foo(int i, String s) {
			this.i = i;
			this.s = s;
		}
		
		@Override
		public String toString() {		
			try {
				JSONObject jsonObj = new JSONObject();
				jsonObj.put("i", i);
				jsonObj.put("s", s);
				return jsonObj.toString();
			} catch (JSONException e) {
				throw new RuntimeException("failed to construct JSONObject for toString", e);
			}
		}
		
		public static Foo fromString(String str) {
			try {
				JSONObject jsonObj = new JSONObject(str);
				return new Foo(jsonObj.getInt("i"), jsonObj.getString("s"));
			} catch (JSONException e) {
				throw new RuntimeException("failed to construct JSONObject for toString", e);
			}			
		}

		...
	}
	
	public static class FooSerializer extends AbstractSerializer<Foo> {
		
		private static final String UTF_8 = "UTF-8";
		private static final Charset charset = Charset.forName(UTF_8);
		private static final FooSerializer instance = new FooSerializer();

		public static FooSerializer get() {
			return instance;
		}

		@Override
		public ByteBuffer toByteBuffer(Foo obj) {
			if (obj == null) {
				return null;
			}
			return ByteBuffer.wrap(obj.toString().getBytes(charset));
		}

		@Override
		public Foo fromByteBuffer(ByteBuffer byteBuffer) {
			if (byteBuffer == null) {
				return null;
			}
			return Foo.fromString(charset.decode(byteBuffer).toString());
		}

		...
	}

	@Column(name="FOO")
	@Serializer(FooSerializer.class)
	private Foo foo;
	...
}