One common question that you may encounter one day when using object-relational-mapping in your application is how to slim down data that you’re retrieving from the persistence layer down to a specific subset for your use-case in an efficient manner and without using complex additional mapping frameworks. In some situations you might declare lazy loaded fields but another approach that I’d like to share with you here are JPA2 constructor expressions.
Constructor expressions allow us to create plain old java objects from the result of an JPA query. The advantage is that we may use different projections for different scenarios and without being managed, the POJOs might help us save some resources here.
In the following tutorial, we’re going to persist 100 book entities with multiple properties to an embedded database and we’re using constructor expressions afterwards to create smaller POJOs using a subset of the information available from each persisted book.
Dependencies / Project Setup
For JPA we need a persistence provider .. often used providers are Hibernate or EclipseLink/Toplink. I’ll be using eclipselink here and Javadb/Derby as embedded database.
The last dependencies are just tools – java-sizeof helps us in estimating the size of an object tree and xstream is a framework to simply serialize/deserialize pojos to/from xml.
Simply add the following dependencies to your pom.xml:
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.9.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyclient</artifactId>
<version>10.9.1.0</version>
</dependency>
<dependency>
<groupId>com.carrotsearch</groupId>
<artifactId>java-sizeof</artifactId>
<version>0.0.3</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.4</version>
</dependency>
</dependencies>
Setting up the Persistence Layer
Only a few steps are needed at this point .. adding an entity, configuring the persistence unit and finally creating an entitymanager to get a hand on the persistence context….
Book Entity
We’re creating a simple entity here that uses the minimal set of declarations needed to make it a full-fledged JPA entity: @Entity at class level and @Id and @GeneratedValue to declare our primary key.
Please take note of the named queries added to the entity:
-
The first one returns all persisted books
-
The second query returns the total amount of saved books
-
The third one creates a collection of SimpleBook objects .. this is the interesting one and the topic of this tutorial ;)
The third query takes id, title and author from each book and creates a slim SimpleBook POJO for each book entity found. Since SimpleBook is a normal java object and is not managed in the JPA persistence context, we might save some ressources here:
SELECT NEW com.hascode.tutorial.dto.SimpleBook(b.id, b.title, b.author) FROM Book b
As you can see, it is necessary to enter the full qualified class name in the SELECT NEW statement without any quotes.
package com.hascode.tutorial.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
@Entity
@NamedQueries({
@NamedQuery(name = "Book.findAll", query = "SELECT b FROM Book b"),
@NamedQuery(name = "Book.countAll", query = "SELECT COUNT(b) FROM Book b"),
@NamedQuery(name = "Book.findAll.toSimpleBook", query = "SELECT NEW com.hascode.tutorial.dto.SimpleBook(b.id, b.title, b.author) FROM Book b") })
public class Book {
@Id
@GeneratedValue
private Long id;
private String title;
private String author;
private String isbn;
private String description;
private String excerpt;
private String someOtherProp;
public final Long getId() {
return id;
}
public final void setId(final Long id) {
this.id = id;
}
public final String getTitle() {
return title;
}
public final void setTitle(final String title) {
this.title = title;
}
public final String getAuthor() {
return author;
}
public final void setAuthor(final String author) {
this.author = author;
}
public final String getIsbn() {
return isbn;
}
public final void setIsbn(final String isbn) {
this.isbn = isbn;
}
public final String getDescription() {
return description;
}
public final void setDescription(final String description) {
this.description = description;
}
public final String getExcerpt() {
return excerpt;
}
public final void setExcerpt(final String excerpt) {
this.excerpt = excerpt;
}
public final String getSomeOtherProp() {
return someOtherProp;
}
public final void setSomeOtherProp(final String someOtherProp) {
this.someOtherProp = someOtherProp;
}
}
Afterwards we’re configuring our persistence context by adding the following persistence.xml file to src/main/resources/META-INF:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
version="1.0">
<persistence-unit name="hascode-manual"
transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>com.hascode.tutorial.entity.Book</class>
<properties>
<property name="javax.persistence.target-database" value="DERBY" />
<property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver" />
<property name="javax.persistence.jdbc.url" value="jdbc:derby:test2db;create=true" />
<property name="javax.persistence.jdbc.user" value="APPT" />
<property name="javax.persistence.jdbc.password" value="APPT" />
<property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
<property name="eclipselink.logging.level" value="FINE" />
</properties>
</persistence-unit>
</persistence>
If you’re interested in some more detailed information about this configuration, please feel free to take a look at my JPA Tutorial: Object-relational Mapping using JPA2.
The SimpleBook DTO
This is just a POJO that takes its content via constructor – that’s also nice because our object is immutable ;)
It is using a small subset of the data that is available in a book entity, namely the id, the title and the author.
package com.hascode.tutorial.dto;
public class SimpleBook {
private final Long id;
private final String title;
private final String author;
public SimpleBook(final Long id, final String title, final String author) {
this.id = id;
this.title = title;
this.author = author;
}
public final Long getId() {
return id;
}
public final String getTitle() {
return title;
}
public final String getAuthor() {
return author;
}
}
Running an Measuring the Memory Footprint
Now it’s time to pull everything together.What we’re doing here is…
-
..creating a new persistence manager connected to the embedded derby database that’s configured in the persistence.xml
-
..creating exactly 100 Book entities and persisting them to the database
-
..running a named query to verify that there are 100 book entities in the database
-
.. using a named query to search for- and return all available books from the database as a list of book entities
-
.. using a RamUsageEstimator to estimate the size of this list
-
.. running a nemd query to create a list of SimpleBook objects using partial data from the books saved in the database
-
..using the estimator again to print out the size of the list of SimpleBooks
-
..finally printing out the data returned in both lists as an xml structure as a proof of concept..
package com.hascode.tutorial.app;
import static com.carrotsearch.sizeof.RamUsageEstimator.sizeOf;
import java.util.List;
import java.util.Vector;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.xml.bind.JAXBException;
import com.hascode.tutorial.dto.SimpleBook;
import com.hascode.tutorial.entity.Book;
import com.thoughtworks.xstream.XStream;
public class ExamplesUsingJPQL {
private EntityManagerFactory emf;
private EntityManager em;
private EntityTransaction tx;
public static void main(final String[] args) throws JAXBException {
new ExamplesUsingJPQL().run();
}
private void run() throws JAXBException {
emf = Persistence.createEntityManagerFactory("hascode-manual");
em = emf.createEntityManager();
tx = em.getTransaction();
tx.begin();
for (int i = 0; i < 100; i++) {
Book book = new Book();
book.setTitle("title" + i);
book.setAuthor("author" + i);
book.setDescription("description" + i);
book.setExcerpt("excerpt" + i);
book.setIsbn("isbn" + i);
book.setSomeOtherProp("prop" + i);
em.persist(book);
}
long amount = em.createNamedQuery("Book.countAll", Long.class)
.getSingleResult();
System.out.println(amount + " books in the store..");
tx.commit();
List<Book> books = em.createNamedQuery("Book.findAll", Book.class)
.getResultList(); // list of attached entities .. expensive ..
System.out.println(books.size() + " Book entities' guessed size is: "
+ sizeOf(books));
List<SimpleBook> bookDtos = em.createNamedQuery(
"Book.findAll.toSimpleBook", SimpleBook.class).getResultList();
System.out.println(bookDtos.size()
+ " SimpleBook entities' guessed size is: " + sizeOf(bookDtos));
printAsXml(books);
printAsXml(bookDtos);
em.close();
emf.close();
}
private <T> void printAsXml(final List<T> list) throws JAXBException {
XStream xs = new XStream();
xs.alias("books", Vector.class);
xs.alias("book", Book.class);
xs.alias("book", SimpleBook.class);
System.out.println("serialized: " + xs.toXML(list));
}
}
Running the code above should produce the following output (trimmed down to the relevant parts).
There shouldn’t be much surprise .. the first structure is – if course bigger than the second one (and we don’t know what additional memory the management of the attached, managed entities cost in the persistence context is) and finally we’re getting some nice rendered xml.
[EL Fine]: sql: 2013-04-14 21:12:48.811--ClientSession(2108622142)--Connection(140952840)--Thread(Thread[main,5,main])--SELECT COUNT(ID) FROM BOOK
100 books in the store..
[EL Fine]: sql: 2013-04-14 21:12:48.846--ServerSession(1698116199)--Connection(140952840)--Thread(Thread[main,5,main])--SELECT ID, AUTHOR, DESCRIPTION, EXCERPT, ISBN, SOMEOTHERPROP, TITLE FROM BOOK
100 Book entities' guessed size is: 42688
[EL Fine]: sql: 2013-04-14 21:12:48.887--ServerSession(1698116199)--Connection(140952840)--Thread(Thread[main,5,main])--SELECT ID, TITLE, AUTHOR FROM BOOK
100 SimpleBook entities' guessed size is: 16448
serialized: <books>
<book>
<id>1</id>
<title>title0</title>
<author>author0</author>
<isbn>isbn0</isbn>
<description>description0</description>
<excerpt>excerpt0</excerpt>
<someOtherProp>prop0</someOtherProp>
</book>
<book>
<id>82</id>
<title>title81</title>
<author>author81</author>
<isbn>isbn81</isbn>
<description>description81</description>
<excerpt>excerpt81</excerpt>
<someOtherProp>prop81</someOtherProp>
</book>
[..]
</books>
serialized: <books>
<book>
<id>1</id>
<title>title0</title>
<author>author0</author>
</book>
<book>
<id>82</id>
<title>title81</title>
<author>author81</author>
</book>
<book>
<id>9</id>
<title>title8</title>
<author>author8</author>
</book>
[..]
</books>
If you take a look in the derby database there should be 100 books as shown in the following screenshot.
Using the Criteria API instead of JPQL
If you prefer to construct your queries programmatically using JPA’s criteria builder and the criteria API, the following code reflects the operations from the examples above but makes use of the criteria API.
package com.hascode.tutorial.app;
import static com.carrotsearch.sizeof.RamUsageEstimator.sizeOf;
import java.util.List;
import java.util.Vector;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.xml.bind.JAXBException;
import com.hascode.tutorial.dto.SimpleBook;
import com.hascode.tutorial.entity.Book;
import com.thoughtworks.xstream.XStream;
public class ExamplesUsingCriteriaApi {
private EntityManagerFactory emf;
private EntityManager em;
private EntityTransaction tx;
public static void main(final String[] args) throws JAXBException {
new ExamplesUsingCriteriaApi().run();
}
private void run() throws JAXBException {
emf = Persistence.createEntityManagerFactory("hascode-manual");
em = emf.createEntityManager();
tx = em.getTransaction();
tx.begin();
for (int i = 0; i < 100; i++) {
Book book = new Book();
book.setTitle("title" + i);
book.setAuthor("author" + i);
book.setDescription("description" + i);
book.setExcerpt("excerpt" + i);
book.setIsbn("isbn" + i);
book.setSomeOtherProp("prop" + i);
em.persist(book);
}
tx.commit();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Long> amountQuery = cb.createQuery(Long.class);
Root<Book> b = amountQuery.from(Book.class);
amountQuery.select(cb.count(b));
long amount = em.createQuery(amountQuery).getSingleResult();
System.out.println(amount + " books in the store..");
CriteriaQuery<Book> bookQuery = cb.createQuery(Book.class);
Root<Book> bk = bookQuery.from(Book.class);
bookQuery.select(bk);
List<Book> books = em.createQuery(bookQuery).getResultList();
System.out.println(books.size() + " Book entities' guessed size is: "
+ sizeOf(books));
CriteriaQuery<SimpleBook> dtoQuery = cb.createQuery(SimpleBook.class);
Root<Book> book = dtoQuery.from(Book.class);
dtoQuery.multiselect(book.get("id"), book.get("title"),
book.get("author"));
List<SimpleBook> bookDtos = em.createQuery(dtoQuery).getResultList();
System.out.println(bookDtos.size()
+ " SimpleBook entities' guessed size is: " + sizeOf(bookDtos));
printAsXml(books);
printAsXml(bookDtos);
em.close();
emf.close();
}
private <T> void printAsXml(final List<T> list) throws JAXBException {
XStream xs = new XStream();
xs.alias("books", Vector.class);
xs.alias("book", Book.class);
xs.alias("book", SimpleBook.class);
System.out.println("serialized: " + xs.toXML(list));
}
}
Another option here is to use explicit constructs – there are use cases where you need to use this syntax – especially when the constructor expression forms only one part of the result..
CriteriaQuery<SimpleBook> dtoQuery = cb.createQuery(SimpleBook.class);
Root<Book> book = dtoQuery.from(Book.class);
dtoQuery.select(cb.construct(SimpleBook.class, book.get("id"),
book.get("title"), book.get("author")));
Tutorial Sources
Please feel free to download the tutorial sources from my GitHub repository, fork it there or clone it using Git:
git clone https://github.com/hascode/jpa2-projection-tutorial.git
Other JPA Articles
Please feel free to have a look at my other articles about the Java Persistence API:
Article Updates
-
2015-03-03: Links to my other JPA articles added.