Skip to content

Polyglot Persistence

xamry edited this page Nov 8, 2012 · 5 revisions

Cross datastore persistence using Kundera

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.

Kundera doesn't support JoinByPrimaryKeyColumn as of now, It's a proposed feature that we are planning to take up in next releases.

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)

Configuration

persistence.xml

<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>

Unidirectional Association

OneToOne Relationship

Database Table Structure:

PERSON (PERSON_ID, PERSON_NAME, p_website, p_email, p_yahoo_id, ADDRESS_ID)
ADDRESS(ADDRESS_ID, STREET)

Entity Definitions:

Person Entity:

 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
}

PersonalData Embedded object:

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

}

Address Entity:

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
}

DB Operation using Kundera:

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", "[email protected]", "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();
}

OneToMany Relationship

Database Table Structure:

PERSON (PERSON_ID, PERSON_NAME, p_website, p_email, p_yahoo_id)
ADDRESS(ADDRESS_ID, STREET, PERSON_ID)

Entity Definitions:

Person Entity:

//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
}

PersonalData Embedded object:

Same as above.

Address Entity:

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

DB Operation using Kundera:

 Person person = new Person();
 person.setPersonId("1");
 person.setPersonName("John Smith");
 person.setPersonalData(new PersonalData("www.johnsmith.com", "[email protected]", "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();
}

ManyToOne Relationship

Database Table Structure:

PERSON (PERSON_ID, PERSON_NAME, p_website, p_email, p_yahoo_id, ADDRESS_ID)
ADDRESS(ADDRESS_ID, STREET)

Entity Definitions:

Person Entity:

//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
}

PersonalData Embedded object:

Same as above.

Address Entity:

//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", "[email protected]", "jsmith"));

 Person person2 = new Person();
 person2.setPersonId("2");
 person2.setPersonName("Patrick Wilson");
 person2.setPersonalData(new PersonalData("www.patrickwilson.com", "[email protected]", "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();
}

ManyToMany Relationship

Database Table Structure:

PERSON (PERSON_ID, PERSON_NAME, p_website, p_email, p_yahoo_id)
ADDRESS(ADDRESS_ID, STREET)
PERSON_ADDRESS(PERSON_ID, ADDRESS_ID)

Entity Definitions:

Person Entity:

//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
}

PersonalData Embedded object:

Same as above.

Address Entity:

//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", "[email protected]", "jsmith"));

        Person person2 = new Person();
        person2.setPersonId("2");
        person2.setPersonName("Patrick Wilson");
        person2.setPersonalData(new PersonalData("www.patrickwilson.com", "[email protected]", "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(); 

Bidirectional Association

OneToOne Relationship

Database Table Structure:

PERSON (PERSON_ID, PERSON_NAME, p_website, p_email, p_yahoo_id, ADDRESS_ID)
ADDRESS(ADDRESS_ID, STREET)

Entity Definitions:

Person Entity:

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

PersonalData Embedded object:

Same as above.

Address Entity:

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

DB Operation using Kundera:

 Person person = new Person();
 person.setPersonId("1");
 person.setPersonName("John Smith");
 person.setPersonalData(new PersonalData("www.johnsmith.com", "[email protected]", "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();

OneToMany Relationship

Database Table Structure:

PERSON (PERSON_ID, PERSON_NAME, p_website, p_email, p_yahoo_id)
ADDRESS(ADDRESS_ID, STREET, PERSON_ID)

Entity Definitions:

Person Entity:

//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
}

PersonalData Embedded object:

Same as above.

Address Entity:

//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
}

DB Operation using Kundera:

 Person person = new Person();
 person.setPersonId("1");
 person.setPersonName("John Smith");
 person.setPersonalData(new PersonalData("www.johnsmith.com", "[email protected]", "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();
}

ManyToOne Relationship

Database Table Structure:

PERSON (PERSON_ID, PERSON_NAME, p_website, p_email, p_yahoo_id, ADDRESS_ID)
ADDRESS(ADDRESS_ID, STREET)

Entity Definitions:

Person Entity:

//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
}

PersonalData Embedded object:

Same as above.

Address Entity:

//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
}

DB Operation using Kundera:

 Person person1 = new Person();
 person1.setPersonId("1");
 person1.setPersonName("John Smith");
 person1.setPersonalData(new PersonalData("www.johnsmith.com", "[email protected]", "jsmith"));

 Person person2 = new Person();
 person2.setPersonId("2");
 person2.setPersonName("Patrick Wilson");
 person2.setPersonalData(new PersonalData("www.patrickwilson.com", "[email protected]", "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();
}

ManyToMany Relationship

Database Table Structure:

PERSON (PERSON_ID, PERSON_NAME, p_website, p_email, p_yahoo_id)
ADDRESS (ADDRESS_ID, STREET)
PERSON_ADDRESS (PERSON_ID, ADDRESS_ID)

Entity Definitions:

Person Entity:

//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
}

PersonalData Embedded object:

Same as above.

Address Entity:

//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
}

DB Operation using Kundera:

 Person person1 = new Person();
 person1.setPersonId("1");
 person1.setPersonName("John Smith");
 person1.setPersonalData(new PersonalData("www.johnsmith.com", "[email protected]", "jsmith"));

 Person person2 = new Person();
 person2.setPersonId("2");
 person2.setPersonName("Patrick Wilson");
 person2.setPersonalData(new PersonalData("www.patrickwilson.com", "[email protected]", "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();
}

Conclusion

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".

Home


Home

Clone this wiki locally