Gyrex/Learning Material/Develop Data Persistence
|Mailing List • Forums • IRC • mattermost|
|Browse Source • Project Set File|
JAX-RS application developed with earlier tutorials only saved data temporarily in ZooKeeper at runtime of the application. With this tutorial a service can be provided to save data persistent. In order to do this we will use the EclipseLink JPA. It provides Object-Relational persistence solution. With the use of annotations the classes requiring persistence are defined and the persistent class details are specified to the JPA persistence provider. Any relational database that is compliant with SQL and has compliant JDBC drivers are supported, structure object-relational data-types and databases and NoSQL are supported, too. In this example we will use the NoSQL database MongoDB. The below explained classes are available as a zip-file here. The sample.console and sample.job bundles are also included.
OSGi Declarative Services require a configured target platform as well as a Java 6 runtime environment. Furthermore a database has to be installed and setted up (this sample uses MongoDB). Instructions how to install a MongoDb database are available on this page: Install MongoDB.
It is recommend that you have at least the packages from the tutorial Develop JAX-RS Application.
This tutorial was created using eclipse Indigo Service Release 2.
This new feature will be implemented in a new bundle you have to create. As every bundle in earlier tutorials this bundle has to be a plug-in project, too. The name will be something like "sample.jpa". Use no Activator and templates to create this bundle. Some packages have to be imported again. Add in MANIFEST.MF in register MANIFEST.MF following lines:
Import-Package: sample_imp.service;version="[1.0.0,2.0.0)", sample.service;version="[1.0.0,2.0.0)", javax.persistence;version="[2.0.0,3.0.0)", org.apache.commons.lang;version="[2.4.0,3.0.0)", org.apache.commons.lang.time;version="[2.4.0,3.0.0)", org.eclipse.gyrex.cloud.environment;version="[1.0.0,2.0.0)", org.eclipse.persistence.annotations;version="[2.4.0,3.0.0)", org.osgi.service.component;version="[1.2.0,2.0.0)", org.osgi.service.jpa;version="[1.0.0,2.0.0)", org.slf4j;version="[1.6.0,2.0.0)" Export-Package: sample.jpa;version="1.0.0"
In Dependencies, Automated Management of Dependencies, add this packages one-by-one:
A component definition with name "jpa-greeting-service.xml" is still required. Place it in a new folder OSGI-INF and set properties. The property "service.ranking" is required to give this implementation a higher ranking as the default implementation for GreetingService. Every component depending on a GreetingService will pick the one with the highest service ranking. The target where the EntityManagerFactoryBuilder is referenced has to be the persistence unit you will create a little bit later in this tutorial.
- parent folder: sample.jpa/OSGI-INF
- filename: jpa-greeting-service.xml
- name: sample.jpa.component
- class: sample.jpa.osgi.JpaGreetingServiceComponent
- activate: activate
- deactivate: deactivate
- provided service: sample.service.GreetingService
- property: name="service.description" type="String" value="GreetingService implementation which uses JPA tp persist Greetings"
- property: name="service.ranking" type="Integer" value="1000"
- <reference cardinality="1..1" interface="org.eclipse.gyrex.cloud.environment.INodeEnvironment" name="INodeEnvironment" policy="static"/>
- <reference cardinality="1..1" interface="org.osgi.service.jpa.EntityManagerFactoryBuilder" name="EntityManagerFactoryBuilder" policy="static" target="(osgi.unit.name=sample.persistence)"/>
Now it's time for implementing the persistence. The connection to a database will be generated via a JPA persistence.xml. In this xml file persistence units are defined. A unit is a logical group of user defined persistable classes and is required for the use of EntityManagerFactory. One persistence.xml file can contain one or more persistence units. Create this file in the folder META-INF, right-click on META-INF -> New -> Other.. . In menu XML you will find XML file. The parent folder has to be META-INF and the name persistence.xml. Than you can select the usage of a template (<?xml version="1.0" encoding="UTF-8"?>).
In this file create a new namespace for the element persistence. The namespaces are:
version="2.0" 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"
After that create a new persistence unit with a name like "sample.persistence". If you develop a JEE application it is recommend to set transaction-type="JTA" to be controlled by Java EE Application Server. If you are outside of an JEE container, e.g. JSE, or in a application managed mode, you have to use transaction-type="RESOURCE_LOCAL". This sample uses RESOURCE_LOCAL. Next you have to define the class, where your entity is. This class defines your model and how the data is mapped. The used sample.jpa.GreetingEntity in this sample will be implemented later. The next section is properties. In this section the MongoDB specific configuration will be defined and you can sepcify JDBC connection information as well. The properties target-platform and the connection-spec are mandatory. With the property:
<property name="eclipselink.nosql.property.mongo.db" value="mongodb1" />
The database is defined where to store the data. In this sample it will be mongodb1. If this database doesn't exist, the application will create it.
With finishing this the Entity can be implemented. The Entity should be located in the package sample.jpa of the bundle sample.jpa. In this sample we just have an entity. But in your implementation you can choose, whether you want to implement an entity or an embedded. Entitys are mapped as root objects and persisted directly. Embedded objects are persisted in context of its parents. That means you will need an entity to persist an embedded. As it was said above, this sample maps NoSQL data. That's why another annotation of the samples entity is the @NoSQL(dataFormat = DataFormatType.MAPPED) annotation. The data-format depends on the type of data being stored. Three formats are possible: XML, Mapped and Indexed. MongoDB uses BSON, this is in strucur similar to Mapped so we use this. Every entity class needs the annotation @NoSQL(dataFormat = DataFormatType.MAPPED).
Each entity has to define a ID, it could be a natural id or a generated (by EclipseLink). Actually a "_id" field is required, but if no special _id field is present MongoDB will auto generate and assign the _id field. In this sample with @Id and @GeneratedValue we define to use the generatedValue. With @Field annotations BSON field names are provided. To get and set the fields, setter and getter methods are implemented. That's all for the entity.
In sample.service bundle greetings are stored temporarily in the ZooKeeper. This new bundle will provide a service to persist them. Therefore implement a ServiceImpl again, and remembering osgi standards, a ServiceComponent is required, too. The JpaGreetingServiceImpl should be implemented in package sample.jpa, but the Component should be created in package sample.jpa.osgi as JpaGreetingServiceComponent.
Now let's deal with the JpaGreetingSeviceImpl first. This service does the same as the service from GreetingServiceImpl. But due to database access in this implementation some methods have to be implemented in a different way. The following methods are affected:
Every other method will be nearly the same, e.g. to show the greetings and get greetings. As you will see, the method add(final Greeting greeting, final List<Greeting> list) and the both lists unprocessed and processed we wont need. The other fields and methods may be copied from the GreetingServiceImpl.
But concentrate on the new methods, especially addprocessed. A entityManagerFactory was passed as a parameter. With this parameter a new Entitymanager is created. The EntityManagerFactory is constructed for specific databases and opens the database. The EntityManger represents the connection to the database server. Furthermore a GreetingEntity has to be created to set some parameters, defined as fields in the entity class. For example the most important one, the actual greeting. The EntityTransaction affects the content of the database, starting with .begin() and ending with .commit(). The transaction is a bundle of instructions, quite the same as you know from SQL and relational databases. Either the hole transaction was performed or no instruction was performed, accomplished by rolling back the whole transaction. With closing the EntityManager resources are released back to the EntityManagerFactory. The method addUnprocessed(final Greeting greeting) does the same with other parameters.
In loadAll() another function is used. With queries you can get data from the database. As the same as in SQL you wont get answer by answer, but get a hole set of data responding to your query. With TypedQuery a request is created. The response data you will get with .getResultList. To return a list of Greeting, now you still have to convert the GreetingEntity into Greeting using a for loop, remember you have a set of results presented as a List.
The method nextUnprocessed() creates a query, too. But the number of results now is restricted to one answer. Nevertheless you have to call getResultList() and create a list of GreetingEntity. Specified in the query your answer will be a unprocessed greeting and now you want to process him, whatever this means. With creating a new GreetingEntity this greeting can be removed from the database with unprocessed greetings by calling the EntityManager method remove(). Returning the greeting as Greeting it is accessible for further usage.
With finishing this implementation you are now able to start the server and mount the application to an URL. If you use all the bundles from earlier tutorials you should deactivate the bundle sample.service, to have a proper execution of the persistence. You can edit the MANIFEST.MF of the sample.service bundle to have this service always deactivated. Just delete the line
from the MANIFEST.MF.
Now start the server as it was explained in earlier tutorials. With typing in "ss sample" you should see the status of sample_imp.service as RESOLVED. After mounting the application to an URL you can open the application and again add greetings. As you noticed everything seems to be the same. But there is a big difference, every greeting is stored in a database. After closing the browser window and restarting the server you will see in a new browser when entering the application with the URL:
that every greeting is still present. Using the server.sample bundle you would see a empty list of greetings.
In the console you now can see some operations on the database are logged. It is logged, which user accesses which database, from which node, the greeting and whether the greeting is already processed (it's not, because in JpaGreetingServiceImpl we've defined to add greetings as unprocessed). There is one more possibility to check the content of the database. According you use a MongoDB, you can open a MongoDB shell in a terminal. With the command "db.GREETINGENTITY.find()" each entry will be shown. The content of an entry is an id, the text, the time when the greeting has been processed (still "0"), the node which has processed the greeting (still "null") and the node which has submitted the greeting. The GREETINGENTITY is something like a table in the database, where all the greetings are saved. How they are saved was defined in GreetingEntity, which is the name of the table. With another command you can delete all entries of your table. This command is "db.GREETINGENTITY.drop()". Refreshing the Greeting-Page in your browser you will see now an empty list.
If you still have the job bundle and the console command bundle implemented in your framework, you are able to process the greetings, too. Corresponding the tutorial Develop Background Processing with the command "samplecommand processGreetings" a new job is created, processing him all the greetings are shown again with the node who has added it. This modification you can see in the database as well. Now the entries of processing time and processing node have appropriate values.
This was how to implement a service to persist data simply with MongoDB. How to deal in JPA with other databases you will find on the website of EclipseLink JPA.