Enterprise Java Development@TOPIC@

Chapter 43. Creating JPA Queries

43.1. Setup
43.2. Create/Execute Query
43.2.1. Multiple Results
43.2.2. Single Result
43.2.3. Single Result - NoResultException
43.2.4. Single Result - NonUniqueResultException
43.3. Query Parameters
43.4. Paging Query Results
43.5. Named Query
43.6. Value Queries
43.6.1. Retrieve Value
43.6.2. Retrieve Function Result Value
43.6.3. Retrieve Multiple Values
43.6.4. Encapsulate Row Values with ResultClass
43.7. Summary

In this chapter you will create simple JPAQL queries and work mostly with the outer JPA query framework.

In this section you will setup a JUnit test case to do work within this chapter.

In this section you will execute a query that produces different types of results.

Create a query that will return multiple results.

In this section you will pass in parameters to the JPAQL query.

  1. Add the following test method to your existing test case to execute a query with a provided parameter argument.

    
    
    @Test
    public void testParameters() {
        log.info("*** testParameters ***");
        
        List<Movie> movies = em.createQuery(
                "select m from Movie m " +
                "where m.rating=:rating", Movie.class)
                .setParameter("rating", MovieRating.R)
                .getResultList();
        log.debug("result=" + movies);
        assertEquals("unexpected number of movies", 4, movies.size());
    }

    Notice the parameter name is passed in the setParameter() call and is prefaced within the query with the ":" character. A single parameter can appear in the query multiple times.

  2. $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.queryex.QueryTest#testParameters
    ...
     -*** testParameters ***
    Hibernate: 
        select
            movie0_.ID as ID3_,
            movie0_.DIRECTOR_ID as DIRECTOR6_3_,
            movie0_.MINUTES as MINUTES3_,
            movie0_.RATING as RATING3_,
            movie0_.RELEASE_DATE as RELEASE4_3_,
            movie0_.TITLE as TITLE3_ 
        from
            QUERYEX_MOVIE movie0_ 
        where
            movie0_.RATING=?
     -result=[Animal House (1978), Sleepers (1996), Wag The Dog (1997), Diner (1982)]
    ...
    [INFO] BUILD SUCCESS
    
  3. Updated the query specification to include items that match a date comparison. Be sure to update the expected count.

    
    
    List<Movie> movies = em.createQuery(
            "select m from Movie m " +
            "where m.rating=:rating " +
            "and m.releaseDate > :date", Movie.class)
            .setParameter("rating", MovieRating.R)
            .setParameter("date", new GregorianCalendar(1980, 0, 0).getTime(), TemporalType.DATE)
            .getResultList();
    log.debug("result=" + movies);
    assertEquals("unexpected number of movies", 3, movies.size());
  4. Re-run the test method. Notice the extra statement within the WHERE clause and fewer matches as a result of the updated query.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.queryex.QueryTest#testParameters
    ...
     -*** testParameters ***
    Hibernate: 
        select
            movie0_.ID as ID3_,
            movie0_.DIRECTOR_ID as DIRECTOR6_3_,
            movie0_.MINUTES as MINUTES3_,
            movie0_.RATING as RATING3_,
            movie0_.RELEASE_DATE as RELEASE4_3_,
            movie0_.TITLE as TITLE3_ 
        from
            QUERYEX_MOVIE movie0_ 
        where
            movie0_.RATING=? 
            and movie0_.RELEASE_DATE>?
     -result=[Sleepers (1996), Wag The Dog (1997), Diner (1982)]
    ...
    [INFO] BUILD SUCCESS
    
  5. Update the query spec to include a LIKE search for text supplied by a parameter. Concatenate JPAQL wildcards to the beginning and ending of the supplied parameter.

    
    
    List<Movie> movies = em.createQuery(
            "select m from Movie m " +
            "where m.rating=:rating " +
            "and m.releaseDate > :date " +
            "and m.title like concat(concat('%',:title),'%')", Movie.class)
            .setParameter("rating", MovieRating.R)
            .setParameter("date", new GregorianCalendar(1980, 0, 0).getTime(), TemporalType.DATE)
            .setParameter("title", "The")
            .getResultList();
    log.debug("result=" + movies);
    assertEquals("unexpected number of movies", 1, movies.size());
  6. Re-run test the test method with the additional search string.

    
    
    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.queryex.QueryTest#testParameters
    ...
     -*** testParameters ***
    Hibernate: 
        select
            movie0_.ID as ID3_,
            movie0_.DIRECTOR_ID as DIRECTOR6_3_,
            movie0_.MINUTES as MINUTES3_,
            movie0_.RATING as RATING3_,
            movie0_.RELEASE_DATE as RELEASE4_3_,
            movie0_.TITLE as TITLE3_ 
        from
            QUERYEX_MOVIE movie0_ 
        where
            movie0_.RATING=? 
            and movie0_.RELEASE_DATE>? 
            and (
                movie0_.TITLE like (('%'||?)||'%')
            )
     -result=[Wag The Dog (1997)]
    ...
    [INFO] BUILD SUCCESS
  7. Update the query spec to make the text search case-insensitive

    
    
    List<Movie> movies = em.createQuery(
            "select m from Movie m " +
            "where m.rating=:rating " +
            "and m.releaseDate > :date " +
            "and lower(m.title) like concat(concat('%',lower(:title)),'%')", Movie.class)
            .setParameter("rating", MovieRating.R)
            .setParameter("date", new GregorianCalendar(1980, 0, 0).getTime(), TemporalType.DATE)
            .setParameter("title", "wag")
            .getResultList();
    log.debug("result=" + movies);
    assertEquals("unexpected number of movies", 1, movies.size());
  8. Re-run test the test method with the case-insensitive search.

    
    
    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.queryex.QueryTest#testParameters
    ...
     -*** testParameters ***
    Hibernate: 
        select
            movie0_.ID as ID3_,
            movie0_.DIRECTOR_ID as DIRECTOR6_3_,
            movie0_.MINUTES as MINUTES3_,
            movie0_.RATING as RATING3_,
            movie0_.RELEASE_DATE as RELEASE4_3_,
            movie0_.TITLE as TITLE3_ 
        from
            QUERYEX_MOVIE movie0_ 
        where
            movie0_.RATING=? 
            and movie0_.RELEASE_DATE>? 
            and (
                lower(movie0_.TITLE) like (('%'||lower(?))||'%')
            )
     -result=[Wag The Dog (1997)]
    ...
    [INFO] BUILD SUCCESS

In this section you will control the amount of rows returned by using paging parameters.

  1. Add the following test method to the existing test case to demonstrate paging capabilities.

    
    
    @Test
    public void testPaging() {
        log.info("*** testPaging ***");
        List<Movie> movies = new LinkedList<Movie>();

    Setup to constant portion of the query up front.

    
    
        TypedQuery<Movie> query = em.createQuery(
                "select m from Movie m " +
                "order by title", Movie.class)
                .setMaxResults(2);

    Loop thru each page until an empty page is returned

    
    
        List<Movie> page=null;
        int offset=0;
        do {
            page = query.setFirstResult(offset).getResultList();
            log.debug("page=" + page);
            movies.addAll(page);
            offset += page.size();
            log.debug("page.size=" + page.size() + ", offset=" + offset);
        } while (page.size() > 0);

    Evaluate the count of rows returned.

        
    
        log.debug("result=" + movies);
        assertEquals("unexpected number of movies", 7, movies.size());
    }
  2. Run the new test method to demonstrate paging.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.queryex.QueryTest#testPaging
    ...
     -*** testPaging ***
    Hibernate: 
        select
            movie0_.ID as ID3_,
            movie0_.DIRECTOR_ID as DIRECTOR6_3_,
            movie0_.MINUTES as MINUTES3_,
            movie0_.RATING as RATING3_,
            movie0_.RELEASE_DATE as RELEASE4_3_,
            movie0_.TITLE as TITLE3_ 
        from
            QUERYEX_MOVIE movie0_ 
        order by
            movie0_.TITLE limit ?
     -page=[Animal House (1978), Apollo 13 (1995), Diner (1982)]
     -page.size=3, offset=3
    

    The first page finishes with 3 rows. The row count is added to the offset for the next query.

    Hibernate: 
        select
            movie0_.ID as ID3_,
            movie0_.DIRECTOR_ID as DIRECTOR6_3_,
            movie0_.MINUTES as MINUTES3_,
            movie0_.RATING as RATING3_,
            movie0_.RELEASE_DATE as RELEASE4_3_,
            movie0_.TITLE as TITLE3_ 
        from
            QUERYEX_MOVIE movie0_ 
        order by
            movie0_.TITLE limit ? offset ?
     -page=[Footloose (1984), Sleepers (1996), Tremors (1990)]
     -page.size=3, offset=6
    

    The second page finishes with 3 rows. The row count is added to the offset for the next query.

    Hibernate: 
        select
            movie0_.ID as ID3_,
            movie0_.DIRECTOR_ID as DIRECTOR6_3_,
            movie0_.MINUTES as MINUTES3_,
            movie0_.RATING as RATING3_,
            movie0_.RELEASE_DATE as RELEASE4_3_,
            movie0_.TITLE as TITLE3_ 
        from
            QUERYEX_MOVIE movie0_ 
        order by
            movie0_.TITLE limit ? offset ?
     -page=[Wag The Dog (1997)]
     -page.size=1, offset=7
    

    The third page finishes with 1 row. The row count is added to the offset for the next query.

    Hibernate: 
        select
            movie0_.ID as ID3_,
            movie0_.DIRECTOR_ID as DIRECTOR6_3_,
            movie0_.MINUTES as MINUTES3_,
            movie0_.RATING as RATING3_,
            movie0_.RELEASE_DATE as RELEASE4_3_,
            movie0_.TITLE as TITLE3_ 
        from
            QUERYEX_MOVIE movie0_ 
        order by
            movie0_.TITLE limit ? offset ?
     -page=[]
     -page.size=0, offset=7
    

    The fourth page finishes with 0 rows. This signals the loop to complete. The result is printed.

     -result=[Animal House (1978), Apollo 13 (1995), Diner (1982), Footloose (1984), Sleepers (1996), Tremors (1990), Wag The Dog (1997)]
    ...
    [INFO] BUILD SUCCESS
    

Use a named query to register re-usable queries.

  1. Observe the @NamedQuery defined within the Movie entity.

    
    
    @Entity
    @Table(name="QUERYEX_MOVIE")
    @NamedQueries(value = { 
        @NamedQuery(name="Movie.findByTitle", query=
            "select m from Movie m " +
            "where lower(m.title) like concat(concat('%',lower(:title)),'%')")
    })
    public class Movie implements Comparable<Movie>{
    })
  2. Add the following test method to your existing test case to demonstrate calling a named query.

    
    
    @Test
    public void testNamedQuery() {
        log.info("*** testNamedQuery ***");
        
        Movie movie = em.createNamedQuery("Movie.findByTitle", Movie.class)
                .setParameter("title", "wag")
                .getSingleResult();
        log.debug("result=" + movie);
        assertNotNull("no movie", movie);
    }
  3. Re-run the new test method to show the execution of the named query with a parameter supplied at runtime.

    
    
    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.queryex.QueryTest#testNamedQuery
    ...
    -*** testNamedQuery ***
    Hibernate: 
        select
            movie0_.ID as ID3_,
            movie0_.DIRECTOR_ID as DIRECTOR6_3_,
            movie0_.MINUTES as MINUTES3_,
            movie0_.RATING as RATING3_,
            movie0_.RELEASE_DATE as RELEASE4_3_,
            movie0_.TITLE as TITLE3_ 
        from
            QUERYEX_MOVIE movie0_ 
        where
            lower(movie0_.TITLE) like (('%'||lower(?))||'%') limit ?
     -result=Wag The Dog (1997)
    ...

Retrieving an entity by its property values and having that entity be managed is a powerful capability provided by JPAQL queries. However, there are times when retrieving a simple value -- rather than the complete entity -- is a better solution.

In this section you will query for multiple values for the entity. Results for multiple values are returned in a Java Array[]. In the case of the properties being multiple types, the array is an Object[] array.

  1. Add the following test method to demonstrate querying an entity model and returning multiple values of different types.

    
    
    @Test
    public void testMultiValueQuery() {
        log.info("*** testMultiValueQuery ***");
        List<Object[]> results = em.createQuery(
                "select m.title, m.releaseDate from Movie m " +
                "order by title ASC", Object[].class)
                .getResultList();
        for (Object[] result : results) {
            String title = (String)result[0];
            Date releaseDate = (Date)result[1];
            log.debug(String.format("%s (%s)", title, releaseDate));
        }
        assertEquals("unexpected number of results", 7, results.size());
    }

    Notice...

  2. Run the new test method and observe the database query produced.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.queryex.QueryTest#testMultiValueQuery
    ...
     -*** testMultiValueQuery ***
    Hibernate: 
        select
            movie0_.TITLE as col_0_0_,
            movie0_.RELEASE_DATE as col_1_0_ 
        from
            QUERYEX_MOVIE movie0_ 
        order by
            movie0_.TITLE ASC
     -Animal House (1978-06-01)
     -Apollo 13 (1995-06-30)
     -Diner (1982-04-02)
     -Footloose (1984-02-17)
     -Sleepers (1996-10-18)
     -Tremors (1990-01-19)
     -Wag The Dog (1997-12-25)
    ...
    [INFO] BUILD SUCCESS

    Notice how the database query resulted in a request for multiple values of different type and the provider made these values available to the application using their assigned type.

Using Object[] arrays is functional but it can lead to some errors or less than desired query result handling. In this section you will encapsulate each row of values returned from the query with an instance of a result class. The result class will provide type-safe access to the returned values as well as any additional functionality we wish to assign.

  1. Add the following result class as a static nested class within your test case. Notice it contains an attribute for each value we expect in the query response and contains a constructor that will process them in a specific order.

    
    
    private static class MovieRelease {
        public final String title;
        public final Date releaseDate;
        @SuppressWarnings("unused")
        public MovieRelease(String title, Date releaseDate) {
            this.title = title;
            this.releaseDate = releaseDate;
        }
    }
  2. Add the following test method to your test case. This test method will issue a similar query as before -- except this time will supply a result class expression for the values to be handled by.

    
    
    @Test
    public void testResultClass() {
        log.info("*** testResultClass ***");
        String query = String.format("select new %s(m.title, m.releaseDate) " +
                "from Movie m order by title ASC", 
                MovieRelease.class.getName());
        List<MovieRelease> results = em.createQuery(query, MovieRelease.class)
                .getResultList();
        for (MovieRelease movie: results) {
            log.debug(String.format("%s (%s)", movie.title, movie.releaseDate));
        }
        assertEquals("unexpected number of results", 7, results.size());
    }

    Notice each row of the query result creates an instance of our result class, passing the values into the constructor in a particular order. Our result class is not an entity and will not be managed within the persistence context.

  3. Run the new test method and observe the JPAQL issued to the entity manager contains a constructor specification for the result class and query values desired. The resultant database query is identical to the one produced in the Object[] array case. The only difference is the provider handles the Object[] array processing for us.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.queryex.QueryTest#testResultClass
    ...
     -select new myorg.queryex.QueryTest$MovieRelease(m.title, m.releaseDate) from Movie m order by title ASC
    Hibernate: 
        select
            movie0_.TITLE as col_0_0_,
            movie0_.RELEASE_DATE as col_1_0_ 
        from
            QUERYEX_MOVIE movie0_ 
        order by
            movie0_.TITLE ASC
     -Animal House (1978-06-01)
     -Apollo 13 (1995-06-30)
     -Diner (1982-04-02)
     -Footloose (1984-02-17)
     -Sleepers (1996-10-18)
     -Tremors (1990-01-19)
     -Wag The Dog (1997-12-25)
    ...
    [INFO] BUILD SUCCESS

In this chapter you formed different types of queries that accepted JPAQL. We didn't get into specific query capabilities of JPAQL -- just how to interface with it. In in the next section we will look at implementing queries within JPA using straight SQL.