Kundera is a powerful JPA based object-datastore mapping library (ORM equivalent) for NoSQL databases. It significantly reduced programming effort required for performing CRUD operations in NoSQL databases. Kundera currently supports Cassandra, HBase, MongoDB and relational databases.
Polyglot Persistence (or Cross-datastore persistence as many call it) is the latest additions to its feather. If your business objects are distributed across multiple databases, all you have to do is create entity classes, their relationship and specify which database you want them to be stored into. You perform CRUD operations on them using JPA API and rest is taken care of by Kundera. It automatically stores/ searches different entities into/ from their intended datastores.
You can get started with Kundera in 5 minutes if you are new. You can download latest release of Kundera from here.
In this post, I will take you through this exciting journey. I am taking a simple example of two entities, namely PERSON and ADDRESS, to be stored into MySQL and Cassandra respectively. You can however choose any combination (Cassandra + HBase, MongoDB + MySQL, HBase + MongoDB etc.
Kundera supports both Unidirectional and Bidirectional associations. I’ll take following associations in this post:
Unidirectional (OneToOne, OneToMany, ManyToOne, ManyToMany)
Bidirectional (OneToOne, OneToMany, ManyToOne, ManyToMany)
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0">
<persistence-unit name="mysqlPU">
<provider>com.impetus.kundera.KunderaPersistence</provider>
<class>com.impetus.kundera.examples.entities.Person</class>
<class>com.impetus.kundera.examples.entities.Address</class>
<properties>
<property name="kundera.client.lookup.class" value="com.impetus.client.rdbms.RDBMSClientFactory" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" />
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/hibernatepoc" />
<property name="hibernate.connection.username" value="root" />
<property name="hibernate.connection.password" value="impetus" />
<property name="hibernate.current_session_context_class" value="org.hibernate.context.ThreadLocalSessionContext" />
</properties>
</persistence-unit>
<persistence-unit name="cassandraPU">
<provider>com.impetus.kundera.KunderaPersistence</provider>
<properties>
<property name="kundera.nodes" value="localhost" />
<property name="kundera.port" value="9160" />
<property name="kundera.keyspace" value="KunderaKeyspace" />
<property name="kundera.dialect" value="cassandra" />
<property name="kundera.client.lookup.class" value="com.impetus.client.cassandra.pelops.PelopsClientFactory" />
<property name="kundera.cache.provider.class" value="com.impetus.kundera.cache.ehcache.EhCacheProvider" />
<property name="kundera.cache.config.resource" value="/ehcache-test.xml" />
</properties>
</persistence-unit>
</persistence>
<pre>
PERSON (PERSON_ID, PERSON_NAME, p_website, p_email, p_yahoo_id, ADDRESS_ID)
ADDRESS(ADDRESS_ID, STREET)
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
@Entity
@Table(name="PERSON", schema="mysqlschema")
public class Person {
@Id
@Column(name="PERSON_ID")
private String personId;
@Column(name="PERSON_NAME")
private String personName;
@Embedded
PersonalData personalData;
@OneToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
@JoinColumn(name="ADDRESS_ID")
private Address address;
//Constructors and getters/ setters omitted
}
import javax.persistence.Column;
import javax.persistence.Embeddable;
@Embeddable
public class PersonalData
{
@Column(name = "p_website")
private String website;
@Column(name = "p_email")
private String email;
@Column(name = "p_yahoo_id")
private String yahooId;
//Constructors and getters/ setters ommitted
}
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="ADDRESS", schema="KunderaKeyspace@CassandraPU")
public class Address
{
@Id
@Column(name = "ADDRESS_ID")
private String addressId;
@Column(name = "STREET")
private String street;
//Constructors and getters/ setters omitted
}
import javax.persistence.Persistence;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;
import javax.persistence.Query;
//Persist Person entity
Person person = new Person();
person.setPersonId("1");
person.setPersonName("John Smith");
person.setPersonalData(new PersonalData("www.johnsmith.com", "john.smith@gmail.com", "jsmith"));
Address address = new Address();
address.setAddressId("111");
address.setStreet("123, New street");
person.setAddress(address);
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mysqlPU,CassandraPU");
EntityManager em = emf.createEntityManager();
em.persist(person);
//Find Person Entity
Person p = em.find(Person.class, "1");
//Run JPA Query
Query q = em.createQuery("select p from Person p");
List<?> persons = q.getResultList();
em.close();
emf.close();
}
PERSON (PERSON_ID, PERSON_NAME, p_website, p_email, p_yahoo_id)
ADDRESS(ADDRESS_ID, STREET, PERSON_ID)
//Imports here
@Entity
@Table(name = "PERSON", schema = "mysqlschema")
public class Person {
@Id
@Column(name = "PERSON_ID")
private String personId;
@Column(name = "PERSON_NAME")
private String personName;
@Embedded
PersonalData personalData;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name="PERSON_ID")
private Set<Address> addresses;
//Constructors, getters and setters omitted
}
Same as above.
//Imports here
@Entity
@Table(name="ADDRESS", schema="KunderaKeyspace@CassandraPU")
public class Address
{
@Id
@Column(name = "ADDRESS_ID")
private String addressId;
@Column(name = "STREET")
private String street;
}
Person person = new Person();
person.setPersonId("1");
person.setPersonName("John Smith");
person.setPersonalData(new PersonalData("www.johnsmith.com", "john.smith@gmail.com", "jsmith"));
Set<Address> addresses = new HashSet<Address>();
Address address1 = new Address();
address1.setAddressId("111");
address1.setStreet("123, Old street");
Address address2 = new Address();
address2.setAddressId("222");
address2.setStreet("456, New street");
addresses.add(address1);
addresses.add(address2);
person.setAddresses(addresses);
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mysqlPU,CassandraPU");
EntityManager em = emf.createEntityManager();
//Save Person entity
em.persist(person);
//Find Person Entity
Person p = em.find(Person.class, "1");
//Run JPA Query
Query q = em.createQuery("select p from Person p");
List<?> persons = q.getResultList();
em.close();
emf.close();
}
PERSON (PERSON_ID, PERSON_NAME, p_website, p_email, p_yahoo_id, ADDRESS_ID)
ADDRESS(ADDRESS_ID, STREET)
//Imports here
@Entity
@Table(name = "PERSON", schema = "mysqlschema")
public class Person {
@Id
@Column(name = "PERSON_ID")
private String personId;
@Column(name = "PERSON_NAME")
private String personName;
@Embedded
private PersonalData personalData;
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name="ADDRESS_ID")
private Address address;
//Constructor, getters, setters here
}
Same as above.
//Imports here
@Entity
@Table(name="ADDRESS", schema="KunderaKeyspace@CassandraPU")
public class Address {
@Id
@Column(name = "ADDRESS_ID")
private String addressId;
@Column(name = "STREET")
private String street;
//Constructors, getters, setters here
}
Person person1 = new Person();
person1.setPersonId("1");
person1.setPersonName("John Smith");
person1.setPersonalData(new PersonalData("www.johnsmith.com", "john.smith@gmail.com", "jsmith"));
Person person2 = new Person();
person2.setPersonId("2");
person2.setPersonName("Patrick Wilson");
person2.setPersonalData(new PersonalData("www.patrickwilson.com", "patrick.wilson@gmail.com", "pwilson"));
Address address = new Address();
address.setAddressId("111");
address.setStreet("123, Old street");
person1.setAddress(address);
person2.setAddress(address);
Set<Person> persons = new HashSet<Person>();
persons.add(person1);
persons.add(person2);
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mysqlPU,CassandraPU");
EntityManager em = emf.createEntityManager();
//Save Person entities
for(Person person : persons) {
em.persist(person);
}
//Find Person Entities
Person p1 = em.find(Person.class, "1");
Address add1 = p1.getAddress();
Person p2 = em.find(Person.class, "2");
Address add2 = p2.getAddress();
//Run JPA Query
Query q = em.createQuery("select p from Person p");
List<?> ps = q.getResultList();
em.close();
emf.close();
}
PERSON (PERSON_ID, PERSON_NAME, p_website, p_email, p_yahoo_id)
ADDRESS(ADDRESS_ID, STREET)
PERSON_ADDRESS(PERSON_ID, ADDRESS_ID)
//Imports here
@Entity
@Table(name = "PERSON", schema = "mysqlschema")
public class Person {
@Id
@Column(name = "PERSON_ID")
private String personId;
@Column(name = "PERSON_NAME")
private String personName;
@Embedded
private PersonalData personalData;
@ManyToMany
@JoinTable(name = "PERSON_ADDRESS",
joinColumns = {
@JoinColumn(name="PERSON_ID")
},
inverseJoinColumns = {
@JoinColumn(name="ADDRESS_ID")
}
)
private Set<Address> addresses;
//Constructor, getters, setters here
}
Same as above.
//Imports here
@Entity
@Table(name="ADDRESS", schema="KunderaKeyspace@CassandraPU")
public class Address {
@Id
@Column(name = "ADDRESS_ID")
private String addressId;
@Column(name = "STREET")
private String street;
//Constructors, getters, setters here
}
DB Operation using Kundera:
Person person1 = new Person();
person1.setPersonId("1");
person1.setPersonName("John Smith");
person1.setPersonalData(new PersonalData("www.johnsmith.com", "john.smith@gmail.com", "jsmith"));
Person person2 = new Person();
person2.setPersonId("2");
person2.setPersonName("Patrick Wilson");
person2.setPersonalData(new PersonalData("www.patrickwilson.com", "patrick.wilson@gmail.com", "pwilson"));
Address address1 = new Address();
address1.setAddressId("111");
address1.setStreet("123, Old street");
Address address2 = new Address();
address2.setAddressId("222");
address2.setStreet("456, New street");
Address address3 = new Address();
address3.setAddressId("333");
address3.setStreet("789, Forbidden street");
person1.addAddress(address1);
person1.addAddress(address2);
person2.addAddress(address2);
person3.addAddress(address3);
Set<Person> persons = new HashSet<Person>();
persons.add(person1);
persons.add(person2);
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mysqlPU,CassandraPU");
EntityManager em = emf.createEntityManager();
//Save Person entities
for(Person person : persons) {
em.persist(person);
}
//Find Person Entities
Person p1 = em.find(Person.class, "1");
Set<Address> adds1 = p1.getAddresses();
Address address11 = adds1.get(0);
Set<Person> people1 = address1.getPeople();
Person p2 = em.find(Person.class, "2");
Set<Address> adds2 = p2.getAddresses();
Address address21 = adds2.get(0);
Set<Person> people2 = address21.getPeople();
//Run JPA Query
Query q = em.createQuery("select p from Person p");
List<?> ps = q.getResultList();
em.close();
emf.close();
PERSON (PERSON_ID, PERSON_NAME, p_website, p_email, p_yahoo_id, ADDRESS_ID)
ADDRESS(ADDRESS_ID, STREET)
@Entity
@Table(name = "PERSON", schema = "mysqlschema")
public class Person {
@Id
@Column(name = "PERSON_ID")
private String personId;
@Column(name = "PERSON_NAME")
private String personName;
@Embedded
private PersonalData personalData;
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name="ADDRESS_ID")
private Address address;
//Constructors, getters, setters here
}
Same as above.
@Entity
@Table(name="ADDRESS", schema="KunderaKeyspace@CassandraPU")
public class Address
{
@Id
@Column(name = "ADDRESS_ID")
private String addressId;
@Column(name = "STREET")
private String street;
@OneToOne(mappedBy="address")
private Person person;
//Constructors, getters, setters here
}
Person person = new Person();
person.setPersonId("1");
person.setPersonName("John Smith");
person.setPersonalData(new PersonalData("www.johnsmith.com", "john.smith@gmail.com", "jsmith"));
Address address = new Address();
address.setAddressId("111");
address.setStreet("123, old street");
person.setAddress(address);
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mysqlPU,CassandraPU");
EntityManager em = emf.createEntityManager();
//Save Person entity
em.persist(person);
//Find Person Entity
Person p = em.find(Person.class, "1");
Address address = p.getAddress();
//Run JPA Query
Query q = em.createQuery("select p from Person p");
List<?> persons = q.getResultList();
em.close();
emf.close();
PERSON (PERSON_ID, PERSON_NAME, p_website, p_email, p_yahoo_id)
ADDRESS(ADDRESS_ID, STREET, PERSON_ID)
//Imports here
@Entity
@Table(name = "PERSON", schema = "mysqlschema")
public class Person {
@Id
@Column(name = "PERSON_ID")
private String personId;
@Column(name = "PERSON_NAME")
private String personName;
@Embedded
private PersonalData personalData;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy="person")
private Set<Address> addresses;
//Constructors, Getters, setters here
}
Same as above.
//Imports here
@Entity
@Table(name="ADDRESS", schema="KunderaKeyspace@CassandraPU")
public class Address
{
@Id
@Column(name = "ADDRESS_ID")
private String addressId;
@Column(name = "STREET")
private String street;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="PERSON_ID")
private Person person;
//Constructors, getters, setters here
}
Person person = new Person();
person.setPersonId("1");
person.setPersonName("John Smith");
person.setPersonalData(new PersonalData("www.johnsmith.com", "john.smith@gmail.com", "jsmith"));
Set<Address> addresses = new HashSet<Address>();
Address address1 = new Address();
address1.setAddressId("111");
address1.setStreet("123, Old Street");
Address address2 = new Address();
address2.setAddressId("222");
address2.setStreet("456, New Street");
addresses.add(address1);
addresses.add(address2);
person.setAddresses(addresses);
//Save Person entity
em.persist(person);
//Find Person Entity
Person p = em.find(Person.class, "1");
Set<Address> adds = p.getAddresses();
//Run JPA Query
Query q = em.createQuery("select p from Person p");
List<?> persons = q.getResultList();
em.close();
emf.close();
}
PERSON (PERSON_ID, PERSON_NAME, p_website, p_email, p_yahoo_id, ADDRESS_ID)
ADDRESS(ADDRESS_ID, STREET)
//Imports here
@Entity
@Table(name = "PERSON", schema = "mysqlschema")
public class Person {
@Id
@Column(name = "PERSON_ID")
private String personId;
@Column(name = "PERSON_NAME")
private String personName;
@Embedded
private PersonalData personalData;
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name="ADDRESS_ID")
private Address address;
//Constructors, getters, setters here
}
Same as above.
//Imports here
@Entity
@Table(name="ADDRESS", schema="KunderaKeyspace@CassandraPU")
public class Address
{
@Id
@Column(name = "ADDRESS_ID")
private String addressId;
@Column(name = "STREET")
private String street;
@OneToMany(mappedBy="address", fetch = FetchType.LAZY)
private Set<Person> people;
//Constructors, getters, setters here
}
Person person1 = new Person();
person1.setPersonId("1");
person1.setPersonName("John Smith");
person1.setPersonalData(new PersonalData("www.johnsmith.com", "john.smith@gmail.com", "jsmith"));
Person person2 = new Person();
person2.setPersonId("2");
person2.setPersonName("Patrick Wilson");
person2.setPersonalData(new PersonalData("www.patrickwilson.com", "patrick.wilson@gmail.com", "pwilson"));
Address address = new Address();
address.setAddressId("111");
address.setStreet("123, Old street");
person1.setAddress(address);
person2.setAddress(address);
Set<Person> persons = new HashSet<Person>();
persons.add(person1);
persons.add(person2);
//Save Person entities
for(Person person : persons) {
em.persist(person);
}
//Find Person Entities
Person p1 = em.find(Person.class, "1");
Address add1 = p1.getAddress();
Set people1 = add1.getPeople();
Person p2 = em.find(Person.class, "2");
Address add2 = p2.getAddress();
Set people2 = add2.getPeople();
//Run JPA Query
Query q = em.createQuery("select p from Person p");
List<?> ps = q.getResultList();
em.close();
emf.close();
}
PERSON (PERSON_ID, PERSON_NAME, p_website, p_email, p_yahoo_id)
ADDRESS (ADDRESS_ID, STREET)
PERSON_ADDRESS (PERSON_ID, ADDRESS_ID)
//Imports here
@Entity
@Table(name = "PERSON", schema = "mysqlschema")
public class Person {
@Id
@Column(name = "PERSON_ID")
private String personId;
@Column(name = "PERSON_NAME")
private String personName;
@Embedded
private PersonalData personalData;
@ManyToMany
@JoinTable(name = "PERSON_ADDRESS",
joinColumns = {
@JoinColumn(name="PERSON_ID")
},
inverseJoinColumns = {
@JoinColumn(name="ADDRESS_ID")
}
)
private Set<Address> addresses;
//Constructors, getters, setters here
}
Same as above.
//Imports here
@Entity
@Table(name="ADDRESS", schema="KunderaKeyspace@CassandraPU")
public class Address
{
@Id
@Column(name = "ADDRESS_ID")
private String addressId;
@Column(name = "STREET")
private String street;
@ManyToMany(mappedBy = "addresses", fetch = FetchType.LAZY)
private Set<Person> people;
//Constructors, getters, setters here
}
Person person1 = new Person();
person1.setPersonId("1");
person1.setPersonName("John Smith");
person1.setPersonalData(new PersonalData("www.johnsmith.com", "john.smith@gmail.com", "jsmith"));
Person person2 = new Person();
person2.setPersonId("2");
person2.setPersonName("Patrick Wilson");
person2.setPersonalData(new PersonalData("www.patrickwilson.com", "patrick.wilson@gmail.com", "pwilson"));
Address address = new Address();
address.setAddressId("111");
address.setStreet("123, Old street");
person1.setAddress(address);
person2.setAddress(address);
Set<Person> persons = new HashSet<Person>();
persons.add(person1);
persons.add(person2);
//Save Person entities
for(Person person : persons) {
em.persist(person);
}
//Find Person Entities
Person p1 = em.find(Person.class, "1");
Address add1 = p1.getAddress();
Set people1 = add1.getPeople();
Person p2 = em.find(Person.class, "2");
Address add2 = p2.getAddress();
Set people2 = add2.getPeople();
//Run JPA Query
Query q = em.createQuery("select p from Person p");
List<?> ps = q.getResultList();
em.close();
emf.close();
}
Applications, at times require data persistence in multiple databases (occasionally a combination of RDBMS and NoSQL).
Using low level database drivers and APIs, it requires considerable effort in persisting, retrieving and querying your related data into/ from multiple stores. Kundera solves this important problem by providing a simple, easy to use and cleaner interface. It hides complexities and maintains those relationships behind the scene. This is consistent with basic philosophy behind Kundera - “Working with NoSQL should be as easy and fun as RDBMS”.