DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Providing Enum Consistency Between Application and Data
  • Enterprise RIA With Spring 3, Flex 4 and GraniteDS
  • The First Annual Recap From JPA Buddy
  • Implement a Distributed Database to Your Java Application

Trending

  • Introduction to Tactical DDD With Java: Steps to Build Semantic Code
  • Why Good Models Fail After Deployment
  • Working With Cowork: Don’t Be Confused
  • Why We Chose Iceberg Over Delta After Evaluating Both at Scale
  1. DZone
  2. Data Engineering
  3. Data
  4. Projection Queries: A Way to Optimize Data Traffic

Projection Queries: A Way to Optimize Data Traffic

JPA provides several solutions for projection queries and DTO mapping. It is up to us to choose the most appropriate solution for each of our use cases.

By 
Sergiy Pylypets user avatar
Sergiy Pylypets
·
Updated Feb. 17, 21 · Tutorial
Likes (7)
Comment
Save
Tweet
Share
22.6K Views

Join the DZone community and get the full member experience.

Join For Free

In our modern, highly concurrent world, enterprise application developers have to deal with new challenges like huge data volumes, diversity of clients, and permanently changing business requirements. Now, it is a usual case when a microservice application has to serve various clients, and some of them are other microservices. These factors imply higher requirements for controlling data traffic. We cannot afford to send any excessive data and we need to respond to each request with data well-tailored for this particular client.

One option of customizing data traffic is the usage of projection queries; that is, queries that return a projection of domain objects. Almost all enterprise applications use some kind of ORM technology, and JPA is a standard way for its implementation. So, let's see how we can implement projection queries based on JPA 2.2 specification.

Suppose we are to implement a collection management online application. The domain system is the following.

Listing 1: CollectionItem.java 

Java
 




x
48


 
1
import javax.persistence.ElementCollection;
2
import javax.persistence.Entity;
3
import javax.persistence.GeneratedValue;
4
import javax.persistence.GenerationType;
5
import javax.persistence.Id;
6
import javax.persistence.JoinColumn;
7
import javax.persistence.Lob;
8
import javax.persistence.OneToOne;
9
import javax.persistence.SequenceGenerator;
10
import javax.persistence.Table;
11
 
          
12
@Entity
13
@Table(name="COLLECTION_ITEMS")
14
public class CollectionItem {
15
 
          
16
   @Id
17
   @SequenceGenerator(name = "CollectionItems_Generator", sequenceName = "CollectionItems_seq", allocationSize = 1)
18
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CollectionItems_Generator")
19
   long id;
20
 
          
21
   BigDecimal price;
22
 
          
23
   @OneToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval=true)
24
   @JoinColumn(name="small_image", unique=true)
25
   Image smallImage;
26
 
          
27
   @OneToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval=true)
28
   @JoinColumn(name="image", unique=true)
29
   Image image;
30
 
          
31
   String name;
32
 
          
33
   String summary;
34
 
          
35
   @Lob
36
   String description;
37
 
          
38
   Short year;
39
 
          
40
   String country;
41
 
          
42
   @ElementCollection
43
   @CollectionTable(name="ITEMS_TOPICS", joinColumns=@JoinColumn(name="ITEM_ID"))
44
   @Column(name="TOPIC")
45
   Set<String> topics;
46
 
          
47
Getters and setters
48
. . . 



Listing 2: Image.java

Java
 




xxxxxxxxxx
1
24


 
1
import javax.persistence.Entity;
2
import javax.persistence.GeneratedValue;
3
import javax.persistence.GenerationType;
4
import javax.persistence.Id;
5
import javax.persistence.Lob;
6
import javax.persistence.SequenceGenerator;
7
import javax.persistence.Table;
8
 
          
9
@Entity
10
@Table(name="IMAGES")
11
public class Image implements java.io.Serializable {
12
 
          
13
   private static final long serialVersionUID = 1L;
14
 
          
15
   @Id
16
   @SequenceGenerator(name = "Images_Generator", sequenceName = "Images_seq", allocationSize = 1)
17
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "Images_Generator")
18
   private long id;
19
 
          
20
   @Lob
21
   private String content;
22
 
          
23
Getters and setters
24
.  .  .



Note: In web applications, it is often more efficient to store images in the file system and store the paths in the database.

According to the specifications, we need to display collection items (probably filtered by some search criteria) in a table with the following attributes included: id, name, summary, year, price, country, smallimage. Also, we need to display the following data for each selected item: name, description, year, country, price, image. To address these requirements, we can implement data transfer objects (DTO).

Listing 3: CollectionItemDto.java

Java
 




xxxxxxxxxx
1
36


 
1
import java.math.BigDecimal;
2
 
          
3
public class CollectionItemDto {
4
 
          
5
   long id;
6
   String name;
7
   String summary;
8
   Short year;
9
   String country;
10
   String smallImage;
11
   BigDecimal price;
12
 
          
13
   public CollectionItemDto() {}
14
 
          
15
   public CollectionItemDto(long id, String name, String summary, Short year, String country, BigDecimal price, String smallImage
16
) {
17
      this.id = id;
18
      this.name = name;
19
      this.summary = summary;
20
      this.year = year;
21
      this.country = country;
22
      this.price = price;
23
      this.smallImage = smallImage;
24
   }
25
 
          
26
   public long getId() {
27
      return id;
28
   }
29
 
          
30
   public String getName() {
31
      return name;
32
   }
33
 
          
34
Other getters
35
. . . .
36
}



Listing 4: CollectionItemDetailsDto.java

Java
 




xxxxxxxxxx
1
33


 
1
import java.math.BigDecimal;
2
 
          
3
public class CollectionItemDetailsDto {
4
 
          
5
   String name;
6
   String description;
7
   Short year;
8
   String country;
9
   String image;
10
   BigDecimal price;
11
 
          
12
   public CollectionItemDetailsDto() {}
13
 
          
14
   public CollectionItemDetailsDto(String name, String description, Short year, String country, BigDecimal price, String image) {
15
      this.name = name;
16
      this.description = description;
17
      this.year = year;
18
      this.country = country;
19
      this.price = price;
20
      this.image = image;
21
   }
22
 
          
23
   public String getName() {
24
      return name;
25
   }
26
 
          
27
   public String getDescription() {
28
      return description;
29
   }
30
 
          
31
Other getters
32
. . . .
33
}



Now, we should find a way to populate these DTOs with data from the database. The modern JPA version provides support for mapping table data onto custom objects in JPQL, Criteria API, and for native queries.

1. Projection Queries in JPQL

In JPQL, we can use the new keyword to define the DTO in the body of the query. So, we create the following named query for filtering collection items by country:

Listing 5: Named JPQL projection query for retrieving collection item data

Java
 




xxxxxxxxxx
1


 
1
@NamedQuery(name="findByCountry", query="SELECT new com.collections.entity.dto.CollectionItemDto(st.id, st.name, st.summary, st.year, st.country, st.price, st.smallImage.content) FROM CollectionItem st WHERE st.country = :country")
2
@Entity
3
@Table(name="COLLECTION_ITEMS")
4
public class CollectionItem {
5
 
          
6
   public static final String FIND_BY_COUNTRY = "findByCountry";
7
. . .



We use this query in the corresponding method of CollectionItemDao.

Listing 6: CollectionItemDao.findByCountry() method with the usage of named JPQL projection query

Java
 




xxxxxxxxxx
1


 
1
public Stream<CollectionItemDto> findByCountry(String country, int from, int rows) {
2
return em.createNamedQuery(CollectionItem.FIND_BY_COUNTRY, CollectionItemDto.class)
3
 .setParameter("country", country)
4
           .setFirstResult(from)
5
           .setMaxResults(rows)
6
           .getResultStream();
7
}



For retrieving details of the selected collection item, we define the following named query.

Listing 7: Named JPQL projection query for retrieving details of the selected collection item

Java
 




xxxxxxxxxx
1


 
1
@NamedQuery(name="getDetailsById", query="SELECT new com.collections.entity.dto.CollectionItemDetailsDto(st.name, st.description, st.year, st.country, st.price, st.image.content) FROM CollectionItem st WHERE st.id = :id")



And use it in CollectionItemDao the same way as the findByCountry query (see Listing 6):

Listing 8: CollectionItemDao.getDetails() method with the usage of named JPQL projection query

Java
 




xxxxxxxxxx
1


 
1
public Stream<CollectionItemDetailsDto> getDetails(long stampId) {
2
return em.createNamedQuery(CollectionItem.GET_DETAILS_BY_ID, CollectionItemDetailsDto.class)
3
 .setParameter("id", stampId)
4
 .getResultStream();
5
}



For advanced search, when customers can choose parameters by which to filter collection items, we can create query dynamically, with the usage of Criteria API.

2. Projection Queries in Criteria API

In the Criteria API, we can use the construct() method of CriteriaBuilder. So, we implement the following dynamic search method in CollectionItemDao:

Listing 9: Dynamically created query

Java
 




xxxxxxxxxx
1
23


 
1
public Stream<CollectionItemDto> search(String country, short year, String ... topics) {
2
   CriteriaBuilder cb = em.getCriteriaBuilder();
3
   CriteriaQuery<CollectionItemDto> cq = cb.createQuery(CollectionItemDto.class);
4
   Root<CollectionItem> stamp = cq.from(CollectionItem.class);
5
   Join<CollectionItem, Image> smallImage = stamp.join(CollectionItem_.smallImage);
6
   Predicate topicFilter = cb.conjunction();
7
   for(String topic : topics) {
8
      topicFilter = cb.and(topicFilter, cb.isMember(topic, stamp.get(CollectionItem_.topics)));
9
   }
10
   cq.where(cb.equal(stamp.get(CollectionItem_.country), country),
11
      cb.equal(stamp.get(CollectionItem_.year), year),topicFilter
12
   );
13
   cq.select(cb.construct(CollectionItemDto.class,
14
      stamp.get(CollectionItem_.id),
15
      stamp.get(CollectionItem_.name),
16
      stamp.get(CollectionItem_.summary),
17
      stamp.get(CollectionItem_.year),
18
      stamp.get(CollectionItem_.country),
19
      stamp.get(CollectionItem_.price),
20
      smallImage.get(Image_.content))).distinct(true);
21
   TypedQuery<CollectionItemDto> tq = em.createQuery(cq);
22
   return tq.getResultStream();
23
}  



This code assumes usage of metamodel objects, which are typically generated while building the application, e.g. with the usage of a Maven processor plugin.

Sometimes, we want to use native SQL queries instead of JPQL or criteria queries, e.g. for performance tuning related to a specific database.

3. Native SQL Projection Queries

To customize data mapping in native SQL queries, we use the @SqlResultSetMapping annotation. Suppose, for some reason, we want to use a native query for implementing the CollectionItemDao.findByCountry() method. In this case, we can create the SQL query and the SqlResultSetMapping definition as follows.

Listing 10: CollectionItemDao.findByCountry() method with the usage of native SQL projection query

Java
 




xxxxxxxxxx
1
16


 
1
@SqlResultSetMapping(name="findByCountryResultSet", classes={@ConstructorResult(targetClass=com.collections.entity.dto.CollectionItemDto.class, columns={
2
@ColumnResult(name="id", type=Long.class),
3
@ColumnResult(name="name"),
4
@ColumnResult(name="summary"),
5
@ColumnResult(name="year"),
6
@ColumnResult(name="country"),
7
@ColumnResult(name="price"),
8
@ColumnResult(name="content", type=String.class)
9
})})
10
@Entity
11
@Table(name="COLLECTION_ITEMS")
12
public class CollectionItem {
13
 
          
14
   public static final String FIND_BY_COUNTRY_SQL = "SELECT ci.id, ci.name, ci.summary, ci.year, ci.country, ci.price, img.content FROM collection_items ci JOIN images img WHERE ci.small_image=img.id AND ci.country = ?";
15
   public static final String FIND_BY_COUNTRY_RESULTSET = "findByCountryResultSet";
16
. . . .



Then, we use this stuff in the DAO method.

Listing 11: Implementation of CollectionItemDao.findByCountry() method with the usage of native SQL projection query

Java
 




xxxxxxxxxx
1


 
1
public Stream<CollectionItemDto> findByCountry(String country, int from, int rows) {
2
return em.createNativeQuery(CollectionItem.FIND_BY_COUNTRY_SQL, CollectionItem.FIND_BY_COUNTRY_RESULTSET)
3
   .setParameter(1, country)
4
   .setFirstResult(from)
5
   .setMaxResults(rows)
6
   .getResultStream();
7
}



As we can see, JPA provides several solutions for projection queries and DTO mapping. It is up to us to choose the most appropriate solution for each of our use cases.

Database Data (computing) Listing (computer) Java (programming language) application

Opinions expressed by DZone contributors are their own.

Related

  • Providing Enum Consistency Between Application and Data
  • Enterprise RIA With Spring 3, Flex 4 and GraniteDS
  • The First Annual Recap From JPA Buddy
  • Implement a Distributed Database to Your Java Application

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook