Web Engineering II CS 313

JPA EXAMPLE (STUDENTS AND MAJORS)

Overview

The idea behind an Object-Relational Mapping (ORM) is that often, we have classes (objects) that represent entities in our program, and then we also have a representation of these entities in a relational database, and we then have to write many very generic methods to create, retrieve, update, and delete these objects. With an ORM, we simply specify how the objects map to the relational database tables, and then it can take care of the rest.

In this example, we will set up a simple database with students and majors, and use an ORM to map our Student and Major Java classes to corresponding tables in our relational database.

Beware that since this query is not dynamic Eclipselink will cache this query using the entityManagerFactory's shared cache. See https://wiki.eclipse.org/EclipseLink/Examples/JPA/Caching. This gives the warning: "The limitation of the shared cache, is that if the database is changed directly through JDBC, or by another application or server, the objects in the shared cache will be stale." and provides methods to avoid caching.

Create the Database

We will begin by creating a new MySql database and putting in some majors and students.

First create a new DB with a new user:

CREATE DATABASE Majors; GRANT ALL PRIVILEGES on Majors.* TO 'majorAdmin'@'localhost' IDENTIFIED BY 'majorPass'; FLUSH PRIVILEGES;

Next create two tables:

USE Majors; CREATE TABLE major (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL); CREATE TABLE student (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, major_id INT NOT NULL, FOREIGN KEY (major_id) REFERENCES major(id));

Now add some majors and students:

INSERT INTO major (name) VALUES ("Web Design and Development"), ("Computer Science"), ("Computer Information Technology"); INSERT INTO student (name, major_id) VALUES ("Josh", 1), ("Heather", 2), ("Steven", 2), ("Emily", 3);

We can verify the data in our database with the following query:

SELECT * FROM major AS m INNER JOIN student AS s ON m.id = s.major_id; +----+---------------------------------+----+---------+----------+ | id | name | major_id | +----+---------------------------------+----+---------+----------+ | 1 | Web Design and Development | 1 | Josh | 1 | | 2 | Computer Science | 2 | Heather | 2 | | 2 | Computer Science | 3 | Steven | 2 | | 3 | Computer Information Technology | 4 | Emily | 3 | +----+---------------------------------+----+---------+----------+ 4 rows in set (0.09 sec)

Setup the Java Project

Now that we have a database in place, we can set up a Java project to map to it. In order to use JPA, we need a library that implements it. Download and install EclipseLink from: http://www.eclipse.org/eclipselink/downloads/

Alternatively, if you are using Maven, you can have it automatically download dependent library by adding the following to your pom.xml:

<dependency> <groupId>org.eclipse.persistence</groupId> <artifactId>eclipselink</artifactId> <version>2.5.0</version> </dependency>

Next we can create a regular Java Project in NetBeans. If you are not using Maven, you will need to go add an external link to the .jar files you downloaded.

Next create a Java class for a Major, named "Major" in the "majors" package.

In order to tell Java that this class is going to map to a relational database table, we annotate it with the Java "@Entity" annotation. This requires importing javax.persistence.Entity as shown:

package majors; import javax.persistence.Entity; @Entity public class Major { }

Now, we can add the fields that this class will have (int id and String name) along with their Getter and Setters. To specify that "id" is an identity field, that should be auto generated, we give in the annotations "@Id" and "@GeneratedValue(strategy = GenerationType.IDENTITY)" (which also require imports). The resulting class looks as follows:

package majors; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Major { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }

We will come back and implement the Student class shortly.

Add the Database Information

Now that we have a properly annotated class, we need to specify the database connection information for it. This is done in a persistence.xml file placed in the META-INF directory.

In NetBeans, this is created by selecting File -> New, choosing the Persistence type, and selecting a new Persistence Unit. You can then specify a name for the persistence Unit, and select a database connection. This will generate the XML file for you.

After creating the Persistence Unit, make sure you click the Add Class button and select your Entity class (in this example, "Major").

Letting the IDE create this file for you is the easiest way to go about this. However, if necessary, the XML file can also be created manually, by adding a subfolder beneath the "src" folder named "META-INF". Make sure you select "New -> Other" and then find "Folder" under the General category, as opposed to created a new "Source Folder".

Then right-click that folder and create a file named, "persistence.xml" with the following content:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="majorJpa" transaction-type="RESOURCE_LOCAL">
<class>majors.Major</class>

<properties>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/Majors"/>
<property name="javax.persistence.jdbc.user" value="majorAdmin"/>
<property name="javax.persistence.jdbc.password" value="majorPass"/>
</properties>

</persistence-unit>
</persistence>

This defines a persistence unit (named "majorJpa"), the classes that are included in the mapping (in this case, simply the "Major" class in the "majors" namespace), and then the properties of the connection string, including the DB location and credentials to use.

Use JPA to Display the Database Data

Now that we have all the configuration information in place, we can use JPA to retrieve and display our data from the database.

First, create a new class with a main method (e.g., MajorTester).

The main object we will be using to handling our transactions is an EntityManager. To get one, we first create an EntityManagerFactory:

EntityManagerFactory emf = Persistence.createEntityManagerFactory("majorJpa");
EntityManager em = emf.createEntityManager();

Then, using the EntityManager object, we do various things, such as loading a major by id:

Major major = em.find(Major.class, 1);
System.out.println("Major: " + major.getName());

Finally, don't forget to close the EntityManager:

em.close();

Now, we should be able to run this class, and see it output the name of our first major, "Web Design and Development".

The complete class looks as follows:

package majors;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class MajorTester {

public static void main(String[] args) {

EntityManagerFactory emf = Persistence.createEntityManagerFactory("majorJpa");
EntityManager em = emf.createEntityManager();

Major major = em.find(Major.class, 1);
System.out.println("Major: " + major.getName());

em.close();
}
}

At this point, it is possible to have errors from missing JAR files, from a mistake in the Java files, the persistence.xml, or issues with the database connection. So there is a lot that could potentially go wrong. That is why it is good to test it here to make sure this much is working.

Retrieving a List of Majors

In addition to using the em.find() method, with an EntityManager object, we can iterate through all majors in the database:

Query query = em.createQuery("SELECT m FROM Major m");
List<Major> majors = query.getResultList();

for (Major major : majors) {
System.out.println("Major: " + major.getName());
}

Notice that the query looks a lot like SQL, but it is not. It is querying over the abstract entities, and will later be converted to an appropriate SQL statement in the database. There are many other things you can do with the EntityManager, and with entity classes such as the Major class. The beauty of the ORM is that you can work with these entities directly in Java and let JPA take care of all the mapping to the database.

Creating a New Entity

Similar to retrieving entities, we can also create one using the EntityManager. We do this inside a transaction, by creating a new object and then telling the EntityManager to persist it:

em.getTransaction().begin();

Major newMajor = new Major();
newMajor.setName("Computer Engineering");

em.persist(newMajor);

em.getTransaction().commit();

Handling Relationships

Just like our Major class, we can create a Student class. Students have a name and an id, just like Majors, but students also have a major. This is shown by creating a member variable of type "Major" and annotating it with the "@ManyToOne" annotation. The complete class can be seen as follows:

package majors;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

@Entity
public class Student {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;

@ManyToOne
private Major major;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Major getMajor() {
return major;
}

public void setMajor(Major major) {
this.major = major;
}
}

After we have created this class and annotated it as an Entity, we also need to add it to our persistence.xml file, by adding the following:

<class>majors.Student</class>

Finally, we can add code to our main class to list the students along with their majors:

Query studentQuery = em.createQuery("SELECT s FROM Student s");
List<Student> students = studentQuery.getResultList();

for (Student student : students) {
System.out.println("Student: " + student.getName() + " has major: " + student.getMajor().getName());
}

Notice that we did all of this without having to specify an SQL join! This was handled automatically by the @ManyToOne annotation.

It should be noted that because we had a standard naming convention for our foreign key table, this was implied, but the name of the column to do the join on can be explicitly specified in the annotation if needed.

Finding Students in a Major

We can also add the ability to follow the relationship in the other direction. In the Major class we could add:

@OneToMany(mappedBy = "major")
private List<Student> students;

public List<Student> getStudents() {
return students;
}

public void setStudents(List<Student> students) {
this.students = students;
}

Notice that we need to specify the member field from the Student class that shows the mapping. Then, we can list the students in a major using the EntityManager in our main class:

Query query = em.createQuery("SELECT m FROM Major m");
List<Major> majors = query.getResultList();

for (Major major : majors) {
System.out.println("Major: " + major.getName());

for (Student student : major.getStudents()) {
System.out.println("\tStudent: " + student.getName());
}
}

Adding New Students

As a final example, we add a new student to the database and connect them to a major using Java:

em.getTransaction().begin();

Student newStudent = new Student();
newStudent.setName("Lance");

Major firstMajor = em.find(Major.class, 1);
newStudent.setMajor(firstMajor);

em.persist(newStudent);

em.getTransaction().commit();

Just as before, we use the persist method on the EntityManger. The only difference here is that we also use the "setMajor" method to set a major, and then the entity manager takes care of connecting the foreign key.

Auto-generating Tables

In closing, we will briefly mention one other feature. Using our entity relationships defined in Java, the JPA process can actually have it generate new database tables for us. In other words, we could have created the Major and Student classes without having created the corresponding tables in the DB, and the ORM solution could automatically generate them for us. This is done by adding the following to the persistence-unit section of the persistence.xml file:

<property name="eclipselink.ddl-generation" value="create-tables" />
<property name="eclipselink.ddl-generation.output-mode" value="database" />

Note that this may not always create the most optimal tables the way you would like them. Also, there are a number of other options here including the ability to drop and create tables, or to create scripts to create the tables.

There are many other options and capabilities of JPA that were not mentioned in this example. Our purpose here was just to scratch the surface and give an example of how to connect the pieces.