Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 53 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
53
Dung lượng
223,29 KB
Nội dung
Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es> Executing queries 249 setProperties() matches the names of JavaBean properties to named parameters in the query string, using setParameter() to guess the Hibernate type and bind the value. In practice, this turns out to be less useful than it sounds, since some com- mon Hibernate types aren’t guessable ( date , in particular). The parameter-binding methods of Query are null-safe, making this code legal: session.createQuery("from User as u where u.username = :name") .setString("name", null) .list(); However, the result of this code is almost certainly not what we intended. The resulting SQL will contain a comparison like username = null , which always evalu- ates to null in SQL ternary logic. Instead, we must use the is null operator: session.createQuery("from User as u where u.email is null").list(); So far, the HQL code examples we’ve shown all use embedded HQL query string literals. This isn’t unreasonable for simple queries, but once we start considering complex queries that must be split over multiple lines, it starts to get a bit unwieldy. 7.1.3 Using named queries We don’t like to see HQL string literals scattered all over the Java code unless they’re necessary. Hibernate lets you externalize query strings to the mapping metadata, a technique that is called named queries. This allows you to store all que- ries related to a particular persistent class (or a set of classes) encapsulated with the other metadata of that class in an XML mapping file. The name of the query is used to call it from the application. The getNamedQuery() method obtains a Query instance for a named query: session.getNamedQuery("findItemsByDescription") .setString("description", description) .list(); In this example, we execute the named query findItemsByDescription after bind- ing a string argument to a named parameter. The named query is defined in map- ping metadata, e.g. in Item.hbm.xml , using the <query> element: <query name="findItemsByDescription"><![CDATA[ from Item item where item.description like :description ]]></query> Named queries don’t have to be HQL strings; they might even be native SQL que- ries—and your Java code doesn’t need to know the difference: Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es> 250 CHAPTER 7 Retrieving objects efficiently <sql-query name="findItemsByDescription"><![CDATA[ select {i.*} from ITEM {i} where DESCRIPTION like :description ]]> <return alias="i" class="Item"/> </sql-query> This is useful if you think you might want to optimize your queries later by fine-tun- ing the SQL. It’s also a good solution if you have to port a legacy application to Hibernate, where SQL code was isolated from the handcoded JDBC routines. With named queries, you can easily port the queries one by one to mapping files. We come back to native SQL queries later in this chapter, but now let’s continue with basic HQL and criteria queries. 7.2 Basic queries for objects Let’s start with simple queries to become familiar with the HQL syntax and seman- tics. Although we show the criteria alternative for most HQL queries, keep in mind that HQL is the preferred approach for complex queries. Usually, the crite- ria can be derived if you know the HQL equivalent, it’s much more difficult the other way around. NOTE Testing Hibernate queries—You can use two tools to execute Hibernate que- ries ad hoc: Hibern8IDE, a Java Swing application; and an Eclipse plugin called Hibernator. Both tools let you select Hibernate mapping docu- ments, connect to the database, and then view the result of HQL queries you type interactively. Hibern8IDE even lets you prototype criteria que- ries by providing a Java BeanShell. You can find links to both tools on the Hibernate project web site. 7.2.1 The simplest query The simplest query retrieves all instances of a particular persistent class. In HQL, it looks like this: from Bid Using a criteria query, it looks like this: session.createCriteria(Bid.class); Both these queries generate the following SQL: select B.BID_ID, B.AMOUNT, B.ITEM_ID, B.CREATED from BID B Even for this simple case, you can see that HQL is less verbose than SQL. Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es> 251 Basic queries for objects 7.2.2 Using aliases Usually, when you query a class using HQL, you need to assign an alias to the que- ried class to use as reference in other parts of the query: from Bid as bid The as keyword is always optional. The following is equivalent: from Bid bid Think of this as being a bit like the temporary variable declaration in the following Java code: for ( Iterator i = allQueriedBids.iterator(); i.hasNext(); ) { Bid bid = (Bid) i.next(); } We assign the alias bid to queried instances of the Bid class, allowing us to refer to their property values later in the code (or query). To remind yourself of the simi- larity, we recommend that you use the same naming convention for aliases that you use for temporary variables (camelCase, usually). However, we use shorter aliases in some of the examples in this book (for example, i instead of item ) to keep the printed code readable. NOTE We never write HQL keywords in uppercase; we never write SQL keywords in uppercase either. It looks ugly and antiquated—most modern termi- nals can display both uppercase and lowercase characters. However, HQL isn’t case-sensitive for keywords, so you can write FROM Bid AS bid if you like shouting. By contrast, a criteria query defines an implicit alias. The root entity in a criteria query is always assigned the alias this . We discuss this topic in more detail later, when we’re joining associations with criteria queries. You don’t have to think much about aliases when using the Criteria API. 7.2.3 Polymorphic queries We described HQL as an object-oriented query language, so it should support poly- morphic queries—that is, queries for instances of a class and all instances of its subclasses, respectively. You already know enough HQL that we can demonstrate this. Consider the following query: from BillingDetails Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es> 252 CHAPTER 7 Retrieving objects efficiently This query returns objects of the type BillingDetails , which is an abstract class. So, in this case, the concrete objects are of the subtypes of BillingDetails : Cred- itCard and BankAccount . If we only want instances of a particular subclass, we may use from CreditCard The class named in the from clause doesn’t need to be a mapped persistent class; any class will do. The following query returns all persistent objects: from java.lang.Object Of course, this also works for interfaces—this query returns all serializable persis- tent objects: from java.io.Serializable Criteria queries also support polymorphism: session.createCriteria(BillingDetails.class).list(); This query returns instances of BillingDetails and its subclasses. Likewise, the fol- lowing criteria query returns all persistent objects: session.createCriteria(java.lang.Object.class).list(); Polymorphism applies not only to classes named explicitly in the from clause, but also to polymorphic associations, as you’ll see later. We’ve discussed the from clause; now let’s move on to the other parts of HQL. 7.2.4 Restriction Usually, you don’t want to retrieve all instances of a class. You must be able to express constraints on the property values of objects returned by the query. Doing so is called restriction. The where clause is used to express a restriction in both SQL and HQL; these expressions may be of arbitrary complexity. Let’s start simple, using HQL: from User u where u.email = 'foo@hibernate.org' Notice that the constraint is expressed in terms of a property, email , of the User class, and that we use an object-oriented notion: Just as in Java, u.email may not be abbreviated to plain email . For a criteria query, we must construct a Criterion object to express the con- straint. The Expression class provides factory methods for built-in Criterion types. Let’s create the same query using criteria and immediately execute it: Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es> Basic queries for objects 253 Criterion emailEq = Expression.eq("email", "foo@hibernate.org"); Criteria crit = session.createCriteria(User.class); crit.add(emailEq); User user = (User) crit.uniqueResult(); We create a Criterion holding the simple Expression for an equality comparison and add it to the Criteria . The uniqueResult() method executes the query and returns exactly one object as a result. Usually, we would write this a bit less verbosely, using method chaining: User user = (User) session.createCriteria(User.class) .add( Expression.eq("email", "foo@hibernate.org") ) .uniqueResult(); A new feature of JDK 1.5 is static imports. Hibernate has some use cases for static imports, so we’re looking forward to the new version. For example, by adding static import net.sf.hibernate.Expression.*; we’ll be able to abbreviate the criteria query restriction code to User user = (User) session.createCriteria(User.class) .add( eq("email", "foo@hibernate.org") ) .uniqueResult(); The SQL generated by these queries is select U.USER_ID, U.FIRSTNAME, U.LASTNAME, U.USERNAME, U.EMAIL from USER U where U.EMAIL = 'foo@hibernate.org' You can of course use various other comparison operators in HQL. 7.2.5 Comparison operators A restriction is expressed using ternary logic. The where clause is a logical expres- sion that evaluates to true, false, or null for each tuple of objects. You construct log- ical expressions by comparing properties of objects to other properties or literal values using HQL’s built-in comparison operators. FAQ What is ternary logic? A row is included in an SQL result set if and only if the where clause evaluates to true. In Java, notNullObject==null evaluates to false and null==null evaluates to true. In SQL, NOT_NULL_COLUMN=null and null=null both evaluate to null, not true. Thus, SQL needs a special operator, IS NULL , to test whether a value is null. This ternary logic is a way of handling expressions that may be applied to null column values. It is a (debatable) SQL extension to the familiar binary logic of the relational model and of typical programming languages such as Java. Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es> 254 CHAPTER 7 Retrieving objects efficiently HQL supports the same basic operators as SQL: = , <> , < , > , >= , <= , between , not between , in , and not in . For example: from Bid bid where bid.amount between 1 and 10 from Bid bid where bid.amount > 100 from User u where u.email in ( "foo@hibernate.org", "bar@hibernate.org" ) In the case of criteria queries, all the same operators are available via the Expres- sion class: session.createCriteria(Bid.class) .add( Expression.between("amount", new BigDecimal(1), new BigDecimal(10)) ).list(); session.createCriteria(Bid.class) .add( Expression.gt("amount", new BigDecimal(100) ) ) .list(); String[] emails = { "foo@hibernate.org", "bar@hibernate.org" }; session.createCriteria(User.class) .add( Expression.in("email", emails) ) .list(); Because the underlying database implements ternary logic, testing for null values requires some care. Remember that null = null doesn’t evaluate to true in the database, but to null. All comparisons that use the null operator in fact evaluate to null. Both HQL and the Criteria API provide an SQL-style is null operator: from User u where u.email is null This query returns all users with no email address. The same semantic is available in the Criteria API: session.createCriteria(User.class) .add( Expression.isNull("email") ) .list(); We also need to be able to find users who do have an email address: from User u where u.email is not null session.createCriteria(User.class) .add( Expression.isNotNull("email") ) .list(); Finally, the HQL where clause supports arithmetic expressions (but the Criteria API doesn’t): Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es> Basic queries for objects 255 from Bid bid where ( bid.amount / 0.71 ) - 100.0 > 0.0 For string-based searches, you need to be able to perform case-insensitive match- ing and matches on fragments of strings in restriction expressions. 7.2.6 String matching The like operator allows wildcard searches, where the wildcard symbols are % and _, just as in SQL: from User u where u.firstname like "G%" This expression restricts the result to users with a first name starting with a capi- tal G. You can also negate the like operator, for example using a substring match expression: from User u where u.firstname not like "%Foo B%" For criteria queries, wildcard searches may use either the same wildcard symbols or specify a MatchMode . Hibernate provides the MatchMode as part of the Criteria query API; we use it for writing string match expressions without string manipula- tion. These two queries are equivalent: session.createCriteria(User.class) .add( Expression.like("firstname", "G%") ) .list(); session.createCriteria(User.class) .add( Expression.like("firstname", "G", MatchMode.START) ) .list(); The allowed MatchModes are START , END , ANYWHERE , and EXACT . An extremely powerful feature of HQL is the ability to call arbitrary SQL func- tions in the where clause. If your database supports user-defined functions (most do), you can put this functionality to all sorts of uses, good or evil. For the moment, let’s consider the usefulness of the standard ANSI SQL functions upper() and lower() . They can be used for case-insensitive searching: from User u where lower(u.email) = 'foo@hibernate.org' The Criteria API doesn’t currently support SQL function calls. It does, however, provide a special facility for case-insensitive searching: session.createCriteria(User.class) .add( Expression.eq("email", "foo@hibernate.org").ignoreCase() ) .list(); Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es> 256 CHAPTER 7 Retrieving objects efficiently Unfortunately, HQL doesn’t provide a standard string-concatenation operator; instead, it supports whatever syntax your database provides. Most databases will allow the following: from User user where ( user.firstname || ' ' || user.lastname ) like 'G% K%' We’ll return to some more exotic features of the HQL where clause later in this chapter. We only used single expressions for restrictions in this section; let’s com- bine several with logical operators. 7.2.7 Logical operators Logical operators (and parentheses for grouping) are used to combine expressions: from User user where user.firstname like "G%" and user.lastname like "K%" from User user where ( user.firstname like "G%" and user.lastname like "K%" ) or user.email in ( "foo@hibernate.org", "bar@hibernate.org" ) If you add multiple Criterion instances to the one Criteria instance, they’re applied conjunctively (that is, using and ): session.createCriteria(User.class) .add( Expression.like("firstname", "G%") ) .add( Expression.like("lastname", "K%") ) If you need disjunction ( or ), you have two options. The first is to use Expres- sion.or() together with Expression.and() : Criteria crit = session.createCriteria(User.class) .add( Expression.or( Expression.and( Expression.like("firstname", "G%"), Expression.like("lastname", "K%") ), Expression.in("email", emails) ) ); The second option is to use Expression.disjunction() together with Expres- sion.conjunction() : Criteria crit = session.createCriteria(User.class) .add( Expression.disjunction() .add( Expression.conjunction() Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es> Basic queries for objects 257 .add( Expression.like("firstname", "G%") ) .add( Expression.like("lastname", "K%") ) ) .add( Expression.in("email", emails) ) ); We think both options are ugly, even after spending five minutes trying to format them for maximum readability. JDK 1.5 static imports would help improve readabil- ity considerably; but even so, unless you’re constructing a query on the fly, the HQL string is much easier to understand. Complex criteria queries are useful only when they’re created programmatically; for example, in the case of a complex search screen with several optional search criteria, we might have a CriteriaBuilder that translates user restrictions to Criteria instances. 7.2.8 Ordering query results All query languages provide a mechanism for ordering query results. HQL provides an order by clause, similar to SQL. This query returns all users, ordered by username: from User u order by u.username You specify ascending and descending order using asc from User u order by u.username desc Finally, you can order by multiple properties: or desc : from User u order by u.lastname asc, u.firstname asc The Criteria API provides a similar facility: List results = session.createCriteria(User.class) .addOrder( Order.asc("lastname") ) .addOrder( Order.asc("firstname") ) .list(); Thus far, we’ve only discussed the basic concepts of HQL and criteria queries. You’ve learned how to write a simple from clause and use aliases for classes. We’ve combined various restriction expressions with logical operators. However, we’ve focused on single persistent classes—that is, we’ve only referenced a single class in the from clause. An important query technique we haven’t discussed yet is the join- ing of associations at runtime. Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es> 258 CHAPTER 7 Retrieving objects efficiently 7.3 Joining associations You use a join to combine data in two (or more) relations. For example, we might join the data in the ITEM and BID tables, as shown in figure 7.1. (Note that not all columns and possible rows are shown; hence the dotted lines.) ITEM ITEM_ID NAME 1 2 Foo Bar ITEM_ID AMOUNT 1 1 2 BID_ID 1 2 3 INITIAL_PRICE 2.00 50.00 10.00 20.00 55.50 3 Baz 1.00 BID Figure 7.1 The and BIDITEM tables are obvious candidates for a join operation. What most people think of when they hear the word join in the context of SQL databases is an inner join. An inner join is one of several types of joins, and it’s the easiest to understand. Consider the SQL statement and result in figure 7.2. This SQL statement is an ANSI-style join. If we join tables ITEM and BID with an inner join, using their common attributes (the ITEM_ID column), we get all items and their bids in a new result table. Note that the result of this operation contains only items that have bids. If we want all items, and null values instead of bid data when there is no corresponding bid, we use a (left) outer join, as shown in figure 7.3. You can think of a table join as working as follows. First, you get a Cartesian prod- uct of the two tables by taking all possible combinations of ITEM rows with BID rows. Second, you filter these joined rows using a join condition. Note that the database has much more sophisticated algorithms to evaluate a join; it usually doesn’t build a memory-consuming product and then filter all rows. The join condition is just a boolean expression that evaluates to true if the joined row is to be included in the result. In the case of the left outer join, each row in the (left) ITEM table that never from ITEM I inner join BID B on I.ITEM_ID = B.ITEM_ID ITEM_ID NAME 1 2 Foo Bar ITEM_ID AMOUNT 1 1 2 BID_ID 1 2 3 INITIAL_PRICE 2.00 50.00 10.00 20.00 55.50 1 Foo 2.00 Figure 7.2 The result table of an ANSI-style inner join of two tables [...]... table join expression This helps make queries less verbose and more readable HQL provides four ways of expressing (inner and outer) joins: ■ An ordinary join in the from clause ■ A fetch join in the from clause ■ A theta-style join in the where clause ■ An implicit association join Later, we’ll show you how to write a join between two classes that don’t have an asso ciation defined (a theta-style join)... couple of things to consider and remember: ■ If you’ve mapped some associations to be fetched by outer join (by setting outer-join="true" on the association mapping), any HQL query will ignore this preference You must use an explicit fetch join if you want eager fetch ing in HQL On the other hand, the criteria query will not ignore the map ping! If you specify outer-join="true" in the mapping file,... performance in ORM Let’s continue with the other join operations 7.3.3 Using aliases with joins We’ve already discussed the role of the where clause in expressing restriction Often, you’ll need to apply restriction criteria to multiple associated classes (joined tables) If we want to do this using an HQL from clause join, we need to assign an alias to the joined class: from Item item join item.bids... of BigDecimals (two instances of BigDecimal in an Object[] array) The special count(distinct) function ignores duplicates: select count(distinct item.description) from Item item When you call an aggregate function in the select clause without specifying any grouping in a group by clause, you collapse the result down to a single row contain ing your aggregated value(s) This means (in the absence of a... BID (A right outer join would retrieve all bids and null if a bid has no item—certainly not a sensible query in our situation.) In SQL, the join condition is usually specified explicitly (Unfortunately, it isn’t possible to use the name of a foreign key constraint to specify how two tables are to be joined.) We specify the join condition in the on clause for an ANSI-style join or in the where clause... bid join bid.item item where item.category.name like 'Laptop%' and item.successfulBid.amount > 100 We can even be more verbose: Licensed to Jose Carlos Romero Figueroa Joining associations from join join join 267 Bid as bid bid.item as item item.category as cat item.successfulBid as winningBid where cat.name like 'Laptop%' and winningBid.amount > 100 Let’s continue with... so-called theta-style join, where I.ITEM_ID = B.ITEM_ID 7.3.1 Hibernate join options In Hibernate queries, you don’t usually specify a join condition explicitly Rather, you specify the name of a mapped Java class association For example, the Item class has an association named bids with the Bid class If we name this association in our query, Hibernate has enough information in the mapping document to then... restriction, since fetching more than one collection in a single query would be a Cartesian product result This restriction might be relaxed in a future version of Hibernate, but we encourage you to think about the size of the result set if more than one collection is fetched in an outer join The amount of data that would have to be transported between database and application can easily grow into the megabyte... queried Items in the same single select, something we called eager fetching earlier Remember that we prefer to map all associations lazy by default, so an eager, outerjoin fetch query is used to override the default fetching strategy at runtime Let’s discuss this last case first 7.3.2 Fetching associations In HQL, you can specify that an association should be eagerly fetched by an outer join using the fetch... Item i join i.bids b"); Iterator items = q.list().iterator(); while ( items.hasNext() ) { Item item = (Item) items.next(); } As you can see, using aliases in HQL is the same for both direct classes and joined associations We assign aliases in the from clause and use them in the where and in the optional select clause The select clause in HQL is much more powerful; we discuss it in detail later in this . expressing (inner and outer) joins: ■ An ordinary join in the from clause ■ A fetch join in the from clause ■ A theta-style join in the where clause ■ An implicit association join Later,. the join condition in the on clause for an ANSI-style join or in the where clause for a so-called theta-style join, where I.ITEM_ID = B.ITEM_ID . 7.3.1 Hibernate join options In Hibernate. candidates for a join operation. What most people think of when they hear the word join in the context of SQL databases is an inner join. An inner join is one of several types of joins, and it’s