What is the connection between ORM, EclipseLink and JPA? I hope you have read previous article: EclipseLink – getting started so you have some basics. In this text I want to explain these terms and show you an example – how to use EclipseLink in real applications.
ORM – Object-Relational Mapping – the way of converting objects from object-oriented languages into relational databases. It answers questions like “How to save our Customer object in SQL-database and – how to read it back?”.
JPA – Java Persistence API – framework for managing data. Defines API, JPQL (Java Persistence Query Language) and metadata.
EclipseLink – Java persistence solution supporting Object-Relational Mapping
(JPA), Object-XML Binding (JAXB and SDO) and Database Web Services (JAX-WS and JAX-RS).
————–
Let’s see the example.
Suppose you have to create simple application for managing customers of DVD rental. What you need is:
- search users by name,
- add and remove existing DVDs to/from users,
- check who has particular DVD.
So you need 2 classes: User that represents your customer and Movie that represents discs you have in DVD rental. Consider used JPA annotations (getters and setters are removed):
User:
package com.codesmuggler.model; import java.util.HashSet; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; // JPA annotations: // this class is an entity: @Entity // SQL-table name should be "users" @Table(name = "users") public class User { public static final String FIELD_ID = "id"; public static final String FIELD_NAME = "name"; // JPA annotations: // this field is the primary key of this entity: @Id // id should be generated using this GenerationType @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; // JPA annotation: this field in db have to be unique @Column(unique = true) private String name; // JPA annotation: this field is in one to many relation // and it is mapped by user field from Movie class @OneToMany(mappedBy = Movie.FIELD_USER) private SetborrowedMovies = new HashSet (); @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("NAME/ID: "); builder.append(getName()); builder.append("/"); builder.append(getId()); builder.append("\nborrowed movies: "); builder.append(getBorrowedMovies().size()); builder.append("\n"); for (Movie movie : getBorrowedMovies()) { builder.append(movie.toString()); builder.append("\n"); } return builder.toString(); } }
Movie:
package com.codesmuggler.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.ManyToOne; @Entity public class Movie { public static final String FIELD_ID = "id"; public static final String FIELD_TITLE = "title"; public static final String FIELD_USER = "user"; // no-arg constructor must be defined public Movie() { } public Movie(String title) { this.title = title; } @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; // JPA annotation: this field is in many to one relation @ManyToOne private User user; private String title; @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(getTitle()); builder.append("/"); builder.append(getId()); builder.append(" is borrowed by: "); builder.append(getUser() == null ? "---" : getUser().getName()); return builder.toString(); } }
So, how the database diagram looks like?
EclipseLink generated the following schema (in this case I have used PostgreSQL):
TABLE USERS: integer - id character varying(255) - name CONSTRAINTS: ALTER TABLE users ADD CONSTRAINT users_pkey PRIMARY KEY(id); ALTER TABLE users ADD CONSTRAINT users_name_key UNIQUE(name); -------------------- TABLE MOVIE: integer - id character varying(255) - title integer - user_id CONSTRAINTS: ALTER TABLE movie ADD CONSTRAINT movie_pkey PRIMARY KEY(id); ALTER TABLE movie ADD CONSTRAINT fk_movie_user_id FOREIGN KEY (user_id) REFERENCES users (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION;
As you see, I have done nothing more than setting up database connection in xml (check out EclipseLink – getting started) and some annotations in domain model. JPA and EclipseLink have done everything for me :)
Schema is generated, so try to write some testing code:
package com.codesmuggler; import java.util.Collection; import java.util.LinkedList; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import org.eclipse.persistence.expressions.Expression; import org.eclipse.persistence.expressions.ExpressionBuilder; import org.eclipse.persistence.jpa.JpaEntityManager; import org.eclipse.persistence.queries.ReadAllQuery; import org.eclipse.persistence.queries.ReadObjectQuery; import org.eclipse.persistence.sessions.UnitOfWork; import org.eclipse.persistence.sessions.server.ClientSession; import org.eclipse.persistence.sessions.server.ServerSession; import com.codesmuggler.model.Movie; import com.codesmuggler.model.User; public class Main { private static final String PERSISTENCE_UNIT_NAME = "eclipselinktest"; private final EntityManagerFactory entityManagerFactory; private final EntityManager entityManager; private final JpaEntityManager jpa; private final ServerSession serverSession; public Main() { entityManagerFactory = Persistence .createEntityManagerFactory(PERSISTENCE_UNIT_NAME); entityManager = entityManagerFactory.createEntityManager(); jpa = (JpaEntityManager) entityManager.getDelegate(); serverSession = jpa.getServerSession(); initData(); } public UnitOfWork acquireUnitOfWork() { return serverSession.acquireClientSession().acquireUnitOfWork(); } public ClientSession acquireClientSession() { return serverSession.acquireClientSession(); } // create some data :) private void initData() { UnitOfWork uow = acquireUnitOfWork(); User codesmuggler = new User(); codesmuggler.setName("codesmuggler"); User helloKitty = new User(); helloKitty.setName("helloKitty"); uow.registerNewObject(codesmuggler); uow.registerNewObject(helloKitty); // create some movies Collectionmovies = new LinkedList (); movies.add(new Movie("The Shawshank Redemption")); movies.add(new Movie("The Godfather")); movies.add(new Movie("The Godfather: Part II")); movies.add(new Movie("Pulp Fiction")); uow.registerAllObjects(movies); uow.commit(); } // EclipseLink: ReadAllQuery public List fetchAllUsers() { UnitOfWork uow = acquireUnitOfWork(); ReadAllQuery raq = new ReadAllQuery(User.class); @SuppressWarnings("unchecked") List users = (List ) uow.executeQuery(raq); return users; } // EclipseLink: ReadAllQuery public List fetchAllMovies() { UnitOfWork uow = acquireUnitOfWork(); ReadAllQuery raq = new ReadAllQuery(Movie.class); @SuppressWarnings("unchecked") List movies = (List ) uow.executeQuery(raq); return movies; } public void printAllMoviesAndUsers() { System.out.println("\n------------------\nMOVIES:"); for (Movie m : fetchAllMovies()) { System.out.println(m); } System.out.println("\n------------------\nUSERS:"); for (User u : fetchAllUsers()) { System.out.println(u); } } // EclipseLink: query by example + ReadObjectQuery public User fetchUserByName(String name) { User example = new User(); example.setName(name); ReadObjectQuery roq = new ReadObjectQuery(User.class); roq.setExampleObject(example); return (User) acquireClientSession().executeQuery(roq); } // fetch movies that are free using Expressions, // "and" operator and ReadAllQuery public List fetchFreeMoviesByTitle(String title) { ExpressionBuilder expressionBuilder = new ExpressionBuilder(Movie.class); Expression exp1 = expressionBuilder.get(Movie.FIELD_TITLE) .containsSubstring(title); Expression exp2 = expressionBuilder.get(Movie.FIELD_USER).isNull(); ReadAllQuery raq = new ReadAllQuery(Movie.class); raq.setSelectionCriteria(exp1.and(exp2)); @SuppressWarnings("unchecked") List movies = (List ) acquireClientSession().executeQuery( raq); return movies; } public static void main(String[] args) { Main main = new Main(); // here we have DB inited with data System.out.println("BEFORE ANY OPERATIONS........"); main.printAllMoviesAndUsers(); User user = main.fetchUserByName("codesmuggler"); assert (user != null); List movies = main.fetchFreeMoviesByTitle("Fiction"); assert (movies.size() == 1); Movie movie = movies.get(0); UnitOfWork uow = main.acquireUnitOfWork(); uow.registerExistingObject(user); uow.registerExistingObject(movie); user.getBorrowedMovies().add(movie); movie.setUser(user); uow.commit(); System.out.println("AFTER LENDING THE MOVIE........"); main.printAllMoviesAndUsers(); movies = main.fetchFreeMoviesByTitle("Fiction"); assert (movies.size() == 0); } }
The results:
BEFORE ANY OPERATIONS........ ------------------ MOVIES: The Godfather: Part II/1 is borrowed by: --- The Godfather/2 is borrowed by: --- The Shawshank Redemption/3 is borrowed by: --- Pulp Fiction/4 is borrowed by: --- ------------------ USERS: NAME/ID: helloKitty/1 borrowed movies: 0 NAME/ID: codesmuggler/2 borrowed movies: 0 AFTER LENDING THE MOVIE........ ------------------ MOVIES: The Godfather: Part II/1 is borrowed by: --- The Godfather/2 is borrowed by: --- The Shawshank Redemption/3 is borrowed by: --- Pulp Fiction/4 is borrowed by: codesmuggler ------------------ USERS: NAME/ID: helloKitty/1 borrowed movies: 0 NAME/ID: codesmuggler/2 borrowed movies: 1 Pulp Fiction/4 is borrowed by: codesmuggler
To sum up, we don’t need to know which database engine we use and don’t have to write any SQL codes. We need to know how the object domain model looks like and ask queries in the object flavour. Using ORMs simplifies lots of things. However, you have to keep in mind that writing applications in this way has also drawbacks – object-relational impedance mismatch and subtle errors that it may cause – it will be discuss in the future.
PS EclipseLink 2.3.0 and JPA 2.0.3 are used.