My Journey With Implementing JPA
There comes a time in pretty much every programming project when you need to communicate with a database of some sort. When doing this in Java, one choice is to manipulate DriverManagers or DataSources directly. However, if you want to take advantage of POJOs to represent your data from the database, manipulating data as objects, you might be staring down the task of writing all sorts of ugly reflection code or a really long constructor to make this happen. There is an alternative: the Java Persistence API.
Old news, buddy!
JPA is old-school, clocking in at over 10 years old! (Jeez, I might as well bust out my IBM 5150.) However, I did not feel like writing reflection like what was done on the last project, so I searched for an alternative. JPA provides you a mechanism to add in details pertaining to object/relational metadata (ORM), and thus run all sorts of CRUD operations with minimal code, by offering convenience functions for these operations. The objects you make persist the data in memory, just like a database would. The Java Persistence Query Language is offered in order to help translate your objects into SQL, or you can use regular SQL.
JPA in and of itself is simply a framework, and there are many different providers of the advertised functionality and benefits. Some of these choices include Hibernate, EclipseLink, and OpenJPA. There’s also Spring’s JPA provider but since Spring is not part of my current project (much to my chagrin), it might not be so easy to just go and get it. Also, OpenJPA doesn’t seem to be available in my available Maven repository, and I’m a Maven snob now; if I can’t get the dependency through a dependency manager, then forget it. This pretty much leaves me the choice between Hibernate and EclipseLink.
Now, some people have given themselves fits with JPQL and how it is implemented by the various persistence provider they chose. If you don’t want to use JPQL and/or are really good at SQL, then you can always write straight SQL to manipulate your data. However, you’ll have to weigh using regular SQL with the possible drawbacks of not getting to use your nice POJOs. Due to that article I just linked to, I ended up choosing EclipseLink and performing all my operations with SQL simply because I’m only doing SELECT operations.
The Gist Of a JPA Implementation
I use IntelliJ by JetBrains as my Java IDE. In here, it is really easy to create a Java Maven project. I am going to eschew detailing the basic steps of setting up a project and get straight into the code.
For starters, you need to get the correct Maven dependencies. Then, build more or less a POJO to describe the contents of the table. Open up the table description in your favorite SQL editor and copy it in or start writing it into your new class. Normally, you should name the class the same as the table you wish to access. Use the IDE to automatically generate the getters and setters once you have written in the variables. Import the persistence library into your POJO class file in order to describe certain properties, such as the table name, which instance variable is the ID/primary key, and so on.
Build a persistence.xml file describing the means by which you will connect to the database, and which Java class will define which persistence unit.
Finally, make your EntityManagerFactory and EntityManager that will manage all the persisted data across all the instances of the POJOs you create by means of performing CRUD operations on your databases using functions from the EntityManager.
The Structure Of Everything
Here are the Maven dependencies you will need to import (if you wish to use EclipseLink, like I did):
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.5.2</version>
</dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.5.2</version>
</dependency>
You will also want to provide the maven-compiler-plugin as a <plugin> in your POM file with <source> and <target> configurations set to your JDK version if you’re having trouble using advanced syntax.
Here is a summary of my class:
package com.myproject.tests;
import javax.persistence.*;
import java.util.Date;
@Entity(name = "the_table_name_in_the_DB")
public class MyPojo {
@Id
private Long id;
private String someOtherString;
@Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated;
}
import javax.persistence.*;
import java.util.Date;
@Entity(name = "the_table_name_in_the_DB")
public class MyPojo {
@Id
private Long id;
private String someOtherString;
@Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated;
}
The @Entity annotation's name attribute is used when your table name is different than your class name. It’s usually better to name them the same unless your table name is awful. It happens…
Notice the @Id and @Temporal annotations too. These are important; you always need a primary key (even if the underlying table doesn’t have one), and @Temporal is required for any Date or Calendar objects.
The next thing is the persistence.xml file, belonging at resources/META-INF/persistence.xml (either src/ or test/ is OK, depending on your context):
<?xml version="1.0"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1">
<persistence-unit name="testJPA" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>com.myproject.MyPojo</class>
<properties>
<property name="javax.persistence.jdbc.url"
value="jdbc:mysql://mysqlOnRDS.rds.amazonaws.com/my_db?zeroDateTimeBehavior=convertToNull"/>
<property name="javax.persistence.jdbc.driver"
value="java.sql.DriverManager"/>
</properties>
</persistence-unit>
</persistence>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1">
<persistence-unit name="testJPA" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>com.myproject.MyPojo</class>
<properties>
<property name="javax.persistence.jdbc.url"
value="jdbc:mysql://mysqlOnRDS.rds.amazonaws.com/my_db?zeroDateTimeBehavior=convertToNull"/>
<property name="javax.persistence.jdbc.driver"
value="java.sql.DriverManager"/>
</properties>
</persistence-unit>
</persistence>
In here, you specify the persistence-unit name (referred to by the EntityManagerFactory creator function earlier), the PersistenceProvider you are using (the PersistenceProvider class should always be implemented by the provider you chose), and your POJO class. Then, specify other properties such as your JDBC URL, the driver you use for reading the database of your choice (here this is the MySQL driver), and possibly a username and password if you don’t care about keeping those in plaintext.
Note that the <property> names might change based on the particular persistence provider you are pursuing. In this case, the name of javax.persistence.jdbc.url works for EclipseLink, but in OpenJDK, it would be openjpa.ConnectionURL .
Finally, I set the remaining important parameters and instantiate my entityManager with the following snippet. The entityManager manages all the instances of my POJOs (objects) representing the (relational) data in my database table, and is responsible for giving us the ability to run queries. Note that below, I'm running this as a JUnit test, thus the use of before() rather than main().
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
public class StepDefinitions {
public EntityManager entityManager;
@Before
public void before() {
Map<String, Object> configOverrides = new HashMap<>();
configOverrides.put("javax.persistence.jdbc.user",
configOverrides.put("javax.persistence.jdbc.user",
System.getProperty("db.user"));
configOverrides.put("javax.persistence.jdbc.password",
configOverrides.put("javax.persistence.jdbc.password",
System.getProperty("db.password"));
EntityManagerFactory emfactory = Persistence.createEntityManagerFactory(
EntityManagerFactory emfactory = Persistence.createEntityManagerFactory(
"testJPA",
configOverrides);
entityManager = emfactory.createEntityManager();
Query query = entityManager.createNativeQuery(
entityManager = emfactory.createEntityManager();
Query query = entityManager.createNativeQuery(
"SELECT * FROM the_table_name_in_the_DB;",
MyPojo.class);
MyPojo firstRow = (MyPojo) query.getSingleResult();
MyPojo firstRow = (MyPojo) query.getSingleResult();
}
}
With System.getProperty(string), I can add the -D arguments (such as -Ddb.user=user) to build the application with the desired parameters on the command line, saving me from saving them in plaintext and/or checking them in anywhere.
You should also look at the documentation for entityManager to find out all the ways you can construct queries. The one above takes a standard SQL string as an argument, but you can find ones that will work with JPQL. The find(Class<T> entityClass, Object primaryKey) call is the simplest of them all, allowing you to find an object just by providing its primary key and your POJO class.
You should also look at the documentation for entityManager to find out all the ways you can construct queries. The one above takes a standard SQL string as an argument, but you can find ones that will work with JPQL. The find(Class<T> entityClass, Object primaryKey) call is the simplest of them all, allowing you to find an object just by providing its primary key and your POJO class.
Battles I Fought
The persistence.xml file belongs in a specific place. Since I am not running this application as a WAR (Web application) at all, and in fact this is simply a project that runs JUnit tests on existing servers, I learned it needs to go in src/test/resources/META-INF/persistence.xml .
The first time I tried to run my JPA code, it complained about
PersistenceException: No resource files named META-INF/services/javax.persistence.spi.PersistenceProvider were found. Please make sure that the persistence provider jar file is in your classpath.
Ultimately, I realized that while I had included the Persistence APIs themselves (javax.persistence.*), I omitted the actual persistence provider. I resolved this by doing some quick research to pick between Hibernate, EclipseLink, and OpenJPA (though I would have used Spring’s one if I were making a Spring project). Ultimately I added EclipseLink to my POM file. It turns out I also needed to add the <provider> tag to my persistence.xml file with the name of the PersistenceProvider class: org.eclipse.persistence.jpa.PersistenceProvider This one pertains to EclipseLink in particular. Whichever JPA service you use, the provider class should always be named PersistenceProvider.
In IntelliJ, I also had to make liberal use of the “Reimport All Maven Projects” button. It exists on the far right, folded up in the “Maven Projects” panel. Unfold this panel, and then see the button that resembles a Refresh button.
Once I had this in place, Java gave me the following error:
javax.persistence.PersistenceException: No Persistence provider for EntityManager named testJPA: The following providers:
org.eclipse.persistence.jpa.PersistenceProvider
Returned null to createEntityManagerFactory.
Since I was developing in IntelliJ with Maven, it would be surprising to me if the usual suggestions of fixing your CLASSPATH would actually help my problem. In fact, by digging down, it was suggested that the root tag in persistence.xml (<persistence> itself) needed some attributes set. To fix the error, I used this (as noted above):
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1">
Now, I received a similar error upon adding these attributes in persistence.xml, but noticed I was making progress when closely scrutinizing the error message. I saw that my class “does not specify a temporal type”, which “must be specified for persistent fields or properties of type java.util.Date and java.util.Calendar”. Most tables will have timestamps of some sort in them as one or more fields, so for this, you will want to use the @Temporal annotation on any of your Date or Calendar objects in your POJO. (Just be mindful to consider such things as daylight saving time when utilizing objects that do not specify a time zone.)
My next problem:
java.sql.SQLException: Zero date value prohibited
There are a couple ways to fix the problem where you have a timestamp consisting of all zeros. Assuming you don’t have the ability (or care) to modify the data to NULL the all-zero timestamps inside the table itself, just add this to the end of your JDBC string:
jdbc:service://host:port/dbName?zeroDateTimeBehavior=convertToNull
After solving this problem, I ran across one more issue:
Exception Description: Entity class has no primary key specified. It should define either an @Id, @EmbeddedId or an @IdClass. If you have defined PK using any of these annotations then make sure that you do not have mixed access-type (both fields and properties annotated) in your entity class hierarchy.
In my case, the database table I inherited had no primary key. It is not my concern to go in there and define a primary key at this time, so I was hoping to get away without defining it in the POJO. If your database table does not have a primary key defined, it’s OK. JPA does not care if you specify a primary key in your POJO that isn’t truly a primary key in your table, so just pick an instance variable representing a column that always has a unique value in it and add the @Id annotation to it.
Epilogue
After this was all said and done, I realized I would need more than just what one single table would give me in order to run my tests. Data will have to come from joined tables, and that will be explored another day…
Sources
A JPA Tutorial - https://www.tutorialspoint.com/jpa/jpa_entity_managers.htm
Another JPA Tutorial - http://www.javaworld.com/article/2077817/java-se/understanding-jpa-part-1-the-object-oriented-paradigm-of-data-persistence.html
You need an actual persistence provider - http://stackoverflow.com/questions/18815133/eclipselinkeno-resource-files-named-meta-inf-services-javax-persistence-spino
Override Configurations Loaded from XML (i.e. DON’T STORE PASSWORDS IN PLAINTEXT) - http://stackoverflow.com/questions/8836834/read-environment-variables-in-persistence-xml-file
Handling timestamps in JDBC - http://stackoverflow.com/questions/3283871/jpa-and-gregoriancalendar
Handling ZERO timestamps in JDBC - http://www.oodlestechnologies.com/blogs/How-to-handle-DATETIME-values-with--zero-Timestamp-in-JDBC
EclipseLink API Documentation - http://www.eclipse.org/eclipselink/api/2.4/org/eclipse/persistence/config/PersistenceUnitProperties.html
Comments
Post a Comment