Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
EclipseLink/Examples/JPA/NoSQL
Contents
NoSQL
NoSQL is a classification of database systems that do not conform to the relational database or SQL standard. They have various roots, from distributed internet databases, to object databases, XML databases and even legacy databases. They have become recently popular because of their use in large scale distributed databases in Google, Amazon, and Facebook.
There are various NoSQL databases including:
- Mongo DB
- Oracle NoSQL
- Cassandra
- Google BigTable
- Couch DB
As of EclipseLink 2.4, EclipseLink has added JPA support for NoSQL databases, initially with support for MongoDB and Oracle NoSQL.
EclipseLink's NoSQL support allows the JPA API and JPA annotations/xml to be used with NoSQL data. EclipseLink also supports several NoSQL specific annotations/xml including @NoSQL that defines a class to map NoSQL data.
EclipseLink's NoSQL support is based on previous EIS support offered since EclipseLink 1.0. EclipseLink's EIS support allowed persisting objects to legacy and non-relational databases. EclipseLink's EIS and NoSQL support uses the Java Connector Architecture (JCA) to access the data-source similar to how EclipseLink's relational support uses JDBC. EclipseLink's NoSQL support is extendable to other NoSQL databases, through the creation of an EclipseLink EISPlatform class and a JCA adapter.
Ordering Example
This example shows how to map and persist an Ordering system object model to an Oracle NoSQL Database (ONDB) and a MongoDB NoSQL database.
The source for the ONDB example can be found here.
The source for the MongoDB example can be found here, or from the EclipseLink SVN repository.
Ordering object model
The ordering system consists of four classes, Order, OrderLine, Address and Customer. The Order has a billing and shipping address, many order lines, and a customer.
public class Order implements Serializable { private String id; private String description; private double totalCost = 0; private Address billingAddress; private Address shippingAddress; private List<OrderLine> orderLines = new ArrayList<OrderLine>(); private Customer customer; ... } public class OrderLine implements Serializable { private int lineNumber; private String description; private double cost = 0; ... } public class Address implements Serializable { private String street; private String city; private String province; private String country; private String postalCode; .... } public class Customer implements Serializable { private String id; private String name; ... }
Step 1 : Decide how to store the data
ONDB stores data as key-value pairs where the value can be JSON or ByteArray and MongoDB stores data as BSON (binary JSON) documents. The first decision that must be made is how to store the objects. Normally each independent object would compose a single value or document, so a single value/document could contain Order, OrderLine and Address. Since customers can be shared amongst multiple orders, Customer would be its own value/document.
Step 2 : Map the data
The next step is to map the objects. Each root object in the value/document will be mapped as an @Entity in JPA. The objects that are stored by being embedded within their parent's value/document are mapped as @Embeddable. This is similar to how JPA maps relational data, but in NoSQL embedded data is much more common because of the hierarchical nature of the data format. In summary, Order and Customer are mapped as @Entity, OrderLine and Address are mapped as @Embeddable.
The @NoSQL annotation is used to map NoSQL data. This tags the classes as mapping to NoSQL data instead of traditional relational data. It is required in each persistence class, both entities and embeddables. The @NoSQL annotation allows the dataType and the dataFormat to be set.
The dataType is the equivalent of the table in relational data, its meaning can differ depending on the NoSQL data-source being used. With ONDB, the dataType is ignored. With MongoDB the dataType refers to the collection used to store the data. The dataType is defaulted to the entity name (as upper case), which is the simple class name.
The dataFormat depends on the type of data being stored. Three formats are supported by EclipseLink, XML, Mapped, and Indexed. XML is the default, but since ONDB key-value and MongoDB uses key-BSON, which is similar to a Map in structure, Mapped is used. In summary, each class requires the @NoSql(dataFormat=DataFormatType.MAPPED) annotation.
@Entity @NoSql(dataFormat=DataFormatType.MAPPED) public class Order
@Embeddable @NoSql(dataFormat=DataFormatType.MAPPED) public class OrderLine
Step 3 : Define the Id
JPA requires that each Entity define an Id. The Id can either be a natural id (application assign id) or a generated id (id is assign by EclipseLink). ONDB uses the id field in the Entity classes directly. MongoDB requires an _id field in every document. If no _id field is present, then Mongo will auto generate and assign the _id field using an OID (object identifier) which is similar to a UUID (universally unique identifier).
You are free to use any field or set of fields as your Id in EclipseLink with NoSQL, the same as a relational Entity. To use an application assigned id as the Mongo id, simply name its field as "_id". This can be done through the @Field annotation, which is similar to the @Column annotation (which will also work), but without all of the relational details, it has just a name. So, to define the field Mongo will use for the id include @Field(name="_id") in your mapping.
To use the EclipseLink generated id as your JPA Id, simply include @Id, @GeneratedValue, in your object's id field mapping. To use the generated Mongo OID as your JPA Id, simply include @Id, @GeneratedValue, and @Field(name="_id") in your object's id field mapping. The @GeneratedValue tells EclipseLink to use the Mongo OID to generate this id value. @SequenceGenerator and @TableGenerator are not supported in ONDB and MongoDB, so these cannot be used. Also the generation types of IDENTITY, TABLE and SEQUENCE are not supported. You can use the EclipseLink @UUIDGenerator (default @GeneratedValue for ONDB) if you wish to use a UUID instead of the Mongo OID. You can also use your own custom generator. The id value for a Mongo OID or a UUID is not a numeric value, it can only be mapped as String or byte[].
ONDB:
@Id @GeneratedValue private String id;
MongoDB:
@Id @GeneratedValue @Field(name="_id") private String id;
Step 4 : Define the mappings
Each attribute in your object has too be mapped. If no annotation/xml is defined for the attribute, then it mapping will be defaulted. Defaulting rules for NoSQL data, follow the JPA defaulting rules, so most simple mappings do not require any configuration if defaults are used. The field names used in the ONDB value and Mongo BSON document will mirror the object attribute names (as uppercase). To provide a different field name, the @Field annotation is used.
Any embedded value stored inside a value/document is persisted using the @Embedded JPA annotation. An embedded collection will use the JPA @ElementCollection annotation. The @CollectionTable of the @ElementCollection is not used or supported in NoSQL, as the data is stored within the value/document, no separate table is required. The @AttributeOverride is also not required nor supported with NoSQL, as the embedded objects are nested in the value/document, and do not require unique field names. The @Embedded annoation/xml is normally not required, as it is defaulted, the @ElementCollection is required, as defaulting does not currently work for @ElementCollection in EclipseLink.
The relationship annotations/xml @OneToOne, @ManyToOne, @OneToMany, and @ManyToMany are only to be used with external relationships in NoSQL. Relationships within the value/document use the embedded annotations/xml. External relationships are supported to other values/documents. To define an external relationship a foreign key is used. The id of the target object is stored in the source object's document. In the case of a collection, a collection of ids is stored. To define the name of the foreign key field in the value/document the @JoinField annotation/xml is used.
The mappedBy option on relationships is not supported for NoSQL data, for bi-directional relationships, the foreign keys would need to be stored on both sides. It is also possible to define a relationship mapping using a query, but this is not currently supported through annotations/xml, only through a DescriptorCustomizer.
ONDB and MongoDB:
@Basic private String description; @Basic private double totalCost = 0; @Embedded private Address billingAddress; @Embedded private Address shippingAddress; @ElementCollection private List<OrderLine> orderLines = new ArrayList<OrderLine>(); @ManyToOne(fetch=FetchType.LAZY) private Customer customer;
Step 5 : Optimistic locking
Optimistic locking is supported with ONDB and MongoDB. It is not required, but if locking is desired, the @Version annotation can be used.
MongoDB does not support transaction, so if a lock error occurs during a transaction, any objects that have been previously written will not be rolled back.
ONDB and MongoDB:
@Version private long version;
Step 6 : Querying
ONDB does not currently support secondary indexes, consequently its JPQL functionality if limited to basic id and range based lookups.
MongoDB has is own JSON based query by example language. It does not support SQL (i.e. NoSQL), so querying has limitations.
EclipseLink supports both JPQL and the Criteria API on MongoDB. As with ONDB, not all aspects of JPQL are supported. Most basic operations are supported, but joins are not supported, nor sub-selects, group bys, or certain database functions. Querying to embedded values, and element collections are supported, as well as ordering, like, and selecting attribute values.
Not all NoSQL databases support querying, so EclipseLink's NoSQL support only supports querying if the NoSQL platform supports it.
Query query = em.createQuery("Select o from Order o where o.totalCost > 1000"); List<Order> orders = query.getResultList(); Query query = em.createQuery("Select o from Order o where o.description like 'Pinball%'"); List<Order> orders = query.getResultList(); Query query = em.createQuery("Select o from Order o join o.orderLines l where l.description = :desc"); query.setParameter("desc", "shipping"); List<Order> orders = query.getResultList();
Native queries are also supported for MongoDB in EclipseLink NoSQL. For MongoDB the native query is in MongoDB's command language.
Query query = em.createNativeQuery("db.ORDER.findOne({\"_id\":\"" + oid + "\"})", Order.class); Order order = (Order)query.getSingleResult();
Step 7 : Connecting
The connection to a database is done through the JPA persistence.xml properties. The following properties must be set in the persistence unit for the respective NoSQL store: The platform: "eclipselink.target-database", Connection Spec: "eclipselink.nosql.connection-spec". Other NoSQL store specific properties such as DatabaseName, Host and Port should also be set. Note, for MongoDB, the host and port can accept a comma separated list of values to connect to a cluster of Mongo databases. For ONDB, all nodes in the distributed database are automatically connected so no special connection list is required.
ONDB:
<persistence-unit name="ondb" transaction-type="RESOURCE_LOCAL"> <class>model.Order</class> <class>model.OrderLine</class> <class>model.Address</class> <class>model.Customer</class> <properties> <property name="eclipselink.target-database" value="org.eclipse.persistence.nosql.adapters.nosql.OracleNoSQLPlatform"/> <property name="eclipselink.nosql.connection-spec" value="org.eclipse.persistence.nosql.adapters.nosql.OracleNoSQLConnectionSpec"/> <property name="eclipselink.nosql.property.nosql.host" value="Localhost:5000"/> <property name="eclipselink.nosql.property.nosql.store" value="kvstore"/> <property name="eclipselink.logging.level" value="FINEST"/> </properties> </persistence-unit>
MongoDB:
<persistence-unit name="mongo-example" transaction-type="RESOURCE_LOCAL"> <class>model.Order</class> <class>model.OrderLine</class> <class>model.Address</class> <class>model.Customer</class> <properties> <property name="eclipselink.target-database" value="org.eclipse.persistence.nosql.adapters.mongo.MongoPlatform"/> <property name="eclipselink.nosql.connection-spec" value="org.eclipse.persistence.nosql.adapters.mongo.MongoConnectionSpec"/> <property name="eclipselink.nosql.property.mongo.port" value="27017"/> <property name="eclipselink.nosql.property.mongo.host" value="localhost"/> <property name="eclipselink.nosql.property.mongo.db" value="mydb"/> <property name="eclipselink.logging.level" value="FINEST"/> </properties> </persistence-unit>
Summary
The full source code to this demo is available from:
ONDB: [1].
MongoDB: SVN.
To run the example you will need a database, which can be downloaded from:
ONDB: [2].
MongoDB: http://www.mongodb.org/downloads.
EclipseLink also support NoSQL access to other data-sources including:
- XML files
- JMS
- Oracle AQ
See Also
For more information, see "Using EclipseLink with NoSQL Databases" in the EclipseLink Solutions Guide.
The Polyglot Persistence example illustrates how to mix objects mapped to NoSQL and relational databases in a single application.