Hibernate object states

hibernate mock objects and difference between get and load in hibernate
GregDeamons Profile Pic
GregDeamons,New Zealand,Professional
Published Date:03-08-2017
Your Website URL(Optional)
Comment
Part 3 Conversational object processing In this part of the book, we explain how to work with persistent objects. Chapter 9 shows you how to load and store objects with the Hibernate and Java Persistence programming interfaces. Transactions and concurrency control are another important topic, discussed in detail in chapter 10. We then implement conversations in chapter 11 and show you how this con- cept can improve the design of your system. Chapters 12 and 13 focus on efficiency and how Hibernate features can make your life easier when you have to load and modify large and complex datasets. Querying, query lan- guages, and APIs are examined in detail in chapters 14 and 15. In chapter 16, we bring it all together by designing and testing a layered appli- cation with ORM persistence. After reading this part, you’ll know how to work with Hibernate and Java Persistence programming interfaces and how to load, modify, and store objects efficiently. You’ll understand how transactions work and why conver- sational processing can open up new ways for application design. You’ll be ready to optimize any object modification scenario, write complex queries, and apply the best fetching and caching strategy to increase performance and scalability.384 CHAPTER 9 Working with objects You now have an understanding of how Hibernate and ORM solve the static aspects of the object/relational mismatch. With what you know so far, it’s possible to solve the structural mismatch problem, but an efficient solution to the problem requires something more. You must investigate strategies for runtime data access, because they’re crucial to the performance of your applications. You basically have learn how to control the state of objects. This and the following chapters cover the behavioral aspect of the object/rela- tional mismatch. We consider these problems to be at least as important as the structural problems discussed in previous chapters. In our experience, many developers are only really aware of the structural mismatch and rarely pay atten- tion to the more dynamic behavioral aspects of the mismatch. In this chapter, we discuss the lifecycle of objects—how an object becomes per- sistent, and how it stops being considered persistent—and the method calls and other actions that trigger these transitions. The Hibernate persistence manager, Session, is responsible for managing object state, so we discuss how to use this the important API. The main Java Persistence interface in EJB 3.0 is called EntityMan- ager, and thanks to its close resemblance with Hibernate APIs, it will be easy to learn alongside. Of course, you can skip quickly through this material if you aren’t working with Java Persistence or EJB 3.0—we encourage you to read about both options and then decide what is better for your application. Let’s start with persistent objects, their lifecycle, and the events which trigger a change of persistent state. Although some of the material may be formal, a solid understanding of the persistence lifecycle is essential. 9.1 The persistence lifecycle Because Hibernate is a transparent persistence mechanism—classes are unaware of their own persistence capability—it’s possible to write application logic that is unaware whether the objects it operates on represent persistent state or tempo- rary state that exists only in memory. The application shouldn’t necessarily need to care that an object is persistent when invoking its methods. You can, for exam- ple, invoke the calculateTotalPrice() business method on an instance of the Item class without having to consider persistence at all; e.g., in a unit test. Any application with persistent state must interact with the persistence service whenever it needs to propagate state held in memory to the database (or vice versa). In other words, you have to call Hibernate (or the Java Persistence) inter- faces to store and load objects. The persistence lifecycle 385 When interacting with the persistence mechanism in that way, it’s necessary for the application to concern itself with the state and lifecycle of an object with respect to persistence. We refer to this as the persistence lifecycle: the states an object goes through during its life. We also use the term unit of work: a set of operations you consider one (usually atomic) group. Another piece of the puzzle is the persis- tence context provided by the persistence service. Think of the persistence context as a cache that remembers all the modifications and state changes you made to objects in a particular unit of work (this is somewhat simplified, but it’s a good starting point). We now dissect all these terms: object and entity states, persistence contexts, and managed scope. You’re probably more accustomed to thinking about what statements you have to manage to get stuff in and out of the database (via JDBC and SQL). However, one of the key factors of your success with Hibernate (and Java Persistence) is your understanding of state management, so stick with us through this section. 9.1.1 Object states Different ORM solutions use different terminology and define different states and state transitions for the persistence lifecycle. Moreover, the object states used internally may be different from those exposed to the client application. Hiber- nate defines only four states, hiding the complexity of its internal implementation from the client code. The object states defined by Hibernate and their transitions in a state chart are shown in figure 9.1. You can also see the method calls to the persistence manager API that trigger transitions. This API in Hibernate is the Session. We discuss this chart in this chapter; refer to it whenever you need an overview. We’ve also included the states of Java Persistence entity instances in figure 9.1. As you can see, they’re almost equivalent to Hibernate’s, and most methods of the Session have a counterpart on the EntityManager API (shown in italics). We say that Hibernate is a superset of the functionality provided by the subset standardized in Java Persistence. Some methods are available on both APIs; for example, the Session has a per- sist() operation with the same semantics as the EntityManager’s counterpart. Others, like load() and getReference(), also share semantics, with a different method name. During its life, an object can transition from a transient object to a persistent object to a detached object. Let’s explore the states and transitions in more detail.386 CHAPTER 9 Working with objects Figure 9.1 Object states and their transitions as triggered by persistence manager operations Transient objects Objects instantiated using the new operator aren’t immediately persistent. Their state is transient, which means they aren’t associated with any database table row and so their state is lost as soon as they’re no longer referenced by any other object. These objects have a lifespan that effectively ends at that time, and they become inaccessible and available for garbage collection. Java Persistence doesn’t include a term for this state; entity objects you just instantiated are new. We’ll con- tinue to refer to them as transient to emphasize the potential for these instances to become managed by a persistence service. Hibernate and Java Persistence consider all transient instances to be nontrans- actional; any modification of a transient instance isn’t known to a persistence con- text. This means that Hibernate doesn’t provide any roll-back functionality for transient objects. Objects that are referenced only by other transient instances are, by default, also transient. For an instance to transition from transient to persistent state, to become managed, requires either a call to the persistence manager or the cre- ation of a reference from an already persistent instance. Persistent objects A persistent instance is an entity instance with a database identity, as defined in chap- ter 4, section 4.2, “Mapping entities with identity.” That means a persistent andThe persistence lifecycle 387 managed instance has a primary key value set as its database identifier. (There are some variations to when this identifier is assigned to a persistent instance.) Persistent instances may be objects instantiated by the application and then made persistent by calling one of the methods on the persistence manager. They may even be objects that became persistent when a reference was created from another persistent object that is already managed. Alternatively, a persistent instance may be an instance retrieved from the database by execution of a query, by an identifier lookup, or by navigating the object graph starting from another persistent instance. Persistent instances are always associated with a persistence context. Hibernate caches them and can detect whether they have been modified by the application. There is much more to be said about this state and how an instance is man- aged in a persistence context. We’ll get back to this later in this chapter. Removed objects You can delete an entity instance in several ways: For example, you can remove it with an explicit operation of the persistence manager. It may also become avail- able for deletion if you remove all references to it, a feature available only in Hibernate or in Java Persistence with a Hibernate extension setting (orphan dele- tion for entities). An object is in the removed state if it has been scheduled for deletion at the end of a unit of work, but it’s still managed by the persistence context until the unit of work completes. In other words, a removed object shouldn’t be reused because it will be deleted from the database as soon as the unit of work completes. You should also discard any references you may hold to it in the application (of course, after you finish working with it—for example, after you’ve rendered the removal-confirmation screen your users see). Detached objects To understand detached objects, you need to consider a typical transition of an instance: First it’s transient, because it just has been created in the application. Now you make it persistent by calling an operation on the persistence manager. All of this happens in a single unit of work, and the persistence context for this unit of work is synchronized with the database at some point (when an SQL INSERT occurs). The unit of work is now completed, and the persistence context is closed. But the application still has a handle: a reference to the instance that was saved. As long as the persistence context is active, the state of this instance is persistent. At388 CHAPTER 9 Working with objects the end of a unit of work, the persistence context closes. What is the state of the object you’re holding a reference to now, and what can you do with it? We refer to these objects as detached, indicating that their state is no longer guaranteed to be synchronized with database state; they’re no longer attached to a persistence context. They still contain persistent data (which may soon be stale). You can continue working with a detached object and modify it. However, at some point you probably want to make those changes persistent—in other words, bring the detached instance back into persistent state. Hibernate offers two operations, reattachment and merging, to deal with this situ- ation. Java Persistence only standardizes merging. These features have a deep impact on how multitiered applications may be designed. The ability to return objects from one persistence context to the presentation layer and later reuse them in a new persistence context is a main selling point of Hibernate and Java Persistence. It enables you to create long units of work that span user think-time. We call this kind of long-running unit of work a conversation. We’ll get back to detached objects and conversations soon. You should now have a basic understanding of object states and how transi- tions occur. Our next topic is the persistence context and the management of objects it provides. 9.1.2 The persistence context You may consider the persistence context to be a cache of managed entity instances. The persistence context isn’t something you see in your application; it isn’t an API you can call. In a Hibernate application, we say that one Session has one internal persistence context. In a Java Persistence application, an EntityMan- ager has a persistence context. All entities in persistent state and managed in a unit of work are cached in this context. We walk through the Session and EntityManager APIs later in this chapter. Now you need to know what this (inter- nal) persistence context is buying you. The persistence context is useful for several reasons: ■ Hibernate can do automatic dirty checking and transactional write-behind. ■ Hibernate can use the persistence context as a first-level cache. ■ Hibernate can guarantee a scope of Java object identity. ■ Hibernate can extend the persistence context to span a whole conversation. All these points are also valid for Java Persistence providers. Let’s look at each feature. The persistence lifecycle 389 Automatic dirty checking Persistent instances are managed in a persistence context—their state is synchro- nized with the database at the end of the unit of work. When a unit of work com- pletes, state held in memory is propagated to the database by the execution of SQL INSERT, UPDATE, and DELETE statements (DML). This procedure may also occur at other times. For example, Hibernate may synchronize with the database before execution of a query. This ensures that queries are aware of changes made earlier during the unit of work. Hibernate doesn’t update the database row of every single persistent object in memory at the end of the unit of work. ORM software must have a strategy for detecting which persistent objects have been modified by the application. We call this automatic dirty checking. An object with modifications that have not yet been propagated to the database is considered dirty. Again, this state isn’t visible to the application. With transparent transaction-level write-behind, Hibernate propagates state changes to the database as late as possible but hides this detail from the application. By executing DML as late as possible (toward the end of the database transaction), Hibernate tries to keep lock-times in the database as short as possi- ble. (DML usually creates locks in the database that are held until the transac- tion completes.) Hibernate is able to detect exactly which properties have been modified so that SQL UPDATE it’s possible to include only the columns that need updating in the statement. This may bring some performance gains. However, it’s usually not a sig- nificant difference and, in theory, could harm performance in some environ- ments. By default, Hibernate includes all columns of a mapped table in the SQL UPDATE statement (hence, Hibernate can generate this basic SQL at startup, not at runtime). If you want to update only modified columns, you can enable dynamic SQL generation by setting dynamic-update="true" in a class mapping. The same mechanism is implemented for insertion of new records, and you can enable INSERT statements with dynamic-insert="true". We rec- runtime generation of ommend you consider this setting when you have an extraordinarily large num- ber of columns in a table (say, more than 50); at some point, the overhead network traffic for unchanged fields will be noticeable. In rare cases, you may also want to supply your own dirty checking algorithm to Hibernate. By default, Hibernate compares an old snapshot of an object with the snapshot at synchronization time, and it detects any modifications that require an update of the database state. You can implement your own routine by supplying a findDirty() method with an org.hibernate.Interceptor for a Ses- custom sion. We’ll show you an implementation of an interceptor later in the book. 390 CHAPTER 9 Working with objects We’ll also get back to the synchronization process (known as flushing) and when it occurs later in this chapter. The persistence context cache A persistence context is a cache of persistent entity instances. This means it remembers all persistent entity instances you’ve handled in a particular unit of work. Automatic dirty checking is one of the benefits of this caching. Another benefit is repeatable read for entities and the performance advantage of a unit of work-scoped cache. For example, if Hibernate is told to load an object by primary key (a lookup by identifier), it can first check the persistence context for the current unit of work. If the entity is found there, no database hit occurs—this is a repeatable read for an application. The same is true if a query is executed through one of the Hiber- nate (or Java Persistence) interfaces. Hibernate reads the result set of the query and marshals entity objects that are then returned to the application. During this process, Hibernate interacts with the current persistence context. It tries to resolve every entity instance in this cache (by identifier); only if the instance can’t be found in the current persistence context does Hibernate read the rest of the data from the result set. The persistence context cache offers significant performance benefits and improves the isolation guarantees in a unit of work (you get repeatable read of entity instances for free). Because this cache only has the scope of a unit of work, it has no real disadvantages, such as lock management for concurrent access—a unit of work is processed in a single thread at a time. The persistence context cache sometimes helps avoid unnecessary database traffic; but, more important, it ensures that: ■ The persistence layer isn’t vulnerable to stack overflows in the case of circu- lar references in a graph of objects. ■ There can never be conflicting representations of the same database row at the end of a unit of work. In the persistence context, at most a single object represents any database row. All changes made to that object may be safely written to the database. ■ Likewise, changes made in a particular persistence context are always imme- diately visible to all other code executed inside that persistence context and its unit of work (the repeatable read for entities guarantee). You don’t have to do anything special to enable the persistence context cache. It’s always on and, for the reasons shown, can’t be turned off. Object identity and equality 391 Later in this chapter, we’ll show you how objects are added to this cache (basi- cally, whenever they become persistent) and how you can manage this cache (by detaching objects manually from the persistence context, or by clearing the per- sistence context). The last two items on our list of benefits of a persistence context, the guaran- teed scope of identity and the possibility to extend the persistence context to span a conversation, are closely related concepts. To understand them, you need to take a step back and consider objects in detached state from a different perspective. 9.2 Object identity and equality A basic Hibernate client/server application may be designed with server-side units of work that span a single client request. When a request from the application user requires data access, a new unit of work is started. The unit of work ends when processing is complete and the response for the user is ready. This is also called the session-per-request strategy (you can replace the word session with persis- tence context whenever you read something like this, but it doesn’t roll off the tongue as well). We already mentioned that Hibernate can support an implementation of a possibly long-running unit of work, called a conversation. We introduce the con- cept of conversations in the following sections as well as the fundamentals of object identity and when objects are considered equal—which can impact how you think about and design conversations. Why is the concept of a conversation useful? 9.2.1 Introducing conversations For example, in web applications, you don’t usually maintain a database transac- tion across a user interaction. Users take a long time to think about modifica- tions, but, for scalability reasons, you must keep database transactions short and release database resources as soon as possible. You’ll likely face this issue when- ever you need to guide the user through several screens to complete a unit of work (from the user’s perspective)—for example, to fill an online form. In this common scenario, it’s extremely useful to have the support of the persistence ser- vice, so you can implement such a conversation with a minimum of coding and best scalability. Two strategies are available to implement a conversation in a Hibernate or Java Persistence application: with detached objects or by extending a persistence con- text. Both have strength and weaknesses. 392 CHAPTER 9 Working with objects Figure 9.2 Conversation implementation with detached object state The detached object state and the already mentioned features of reattachment or merging are ways to implement a conversation. Objects are held in detached state during user think-time, and any modification of these objects is made persistent manually through reattachment or merging. This strategy is also called session-per- request-with-detached-objects. You can see a graphical illustration of this conversation pattern in figure 9.2. A persistence context only spans the processing of a particular request, and the application manually reattaches and merges (and sometimes detaches) entity instances during the conversation. The alternative approach doesn’t require manual reattachment or merging: With the session-per-conversation pattern, you extend a persistence context to span the whole unit of work (see figure 9.3). First we have a closer look at detached objects and the problem of identity you’ll face when you implement a conversation with this strategy. Figure 9.3 Conversation implementation with an extended persistence contextObject identity and equality 393 9.2.2 The scope of object identity As application developers, we identify an object using Java object identity (a==b). If an object changes state, is the Java identity guaranteed to be the same in the new state? In a layered application, that may not be the case. In order to explore this, it’s extremely important to understand the relation- a==b, and database identity, x.getId().equals( ship between Java identity, y.getId() ). Sometimes they’re equivalent; sometimes they aren’t. We refer to the conditions under which Java identity is equivalent to database identity as the scope of object identity. For this scope, there are three common choices: ■ A primitive persistence layer with no identity scope makes no guarantees that if a row is accessed twice the same Java object instance will be returned to the application. This becomes problematic if the application modifies two different instances that both represent the same row in a sin- gle unit of work. (How should we decide which state should be propa- gated to the database?) ■ A persistence layer using persistence context-scoped identity guarantees that, in the scope of a single persistence context, only one object instance repre- sents a particular database row. This avoids the previous problem and also allows for some caching at the context level. ■ Process-scoped identity goes one step further and guarantees that only one object instance represents the row in the whole process (JVM). For a typical web or enterprise application, persistence context-scoped identity is preferred. Process-scoped identity does offer some potential advantages in terms of cache utilization and the programming model for reuse of instances across multiple units of work. However, in a pervasively multithreaded application, the cost of always synchronizing shared access to persistent objects in the global iden- tity map is too high a price to pay. It’s simpler, and more scalable, to have each thread work with a distinct set of persistent instances in each persistence context. We would say that Hibernate implements persistence context-scoped iden- tity. So, by nature, Hibernate is best suited for highly concurrent data access in multiuser applications. However, we already mentioned some issues you’ll face when objects aren’t associated with a persistence context. Let’s discuss this with an example. The Hibernate identity scope is the scope of a persistence context. Let’s see how this works in code with Hibernate APIs—the Java Persistence code is the394 CHAPTER 9 Working with objects equivalent with EntityManager instead of Session. Even though we haven’t shown you much about these interfaces, the following examples are simple, and you should have no problems understanding the methods we call on the Session. If you request two objects using the same database identifier value in the same Session, the result is two references to the same in-memory instance. Listing 9.1 demonstrates this with several get() operations in two Sessions. Listing 9.1 The guaranteed scope of object identity in Hibernate Session session1 = sessionFactory.openSession(); Transaction tx1 = session1.beginTransaction(); // Load Item with identifier value "1234" Object a = session1.get(Item.class, new Long(1234) ); Object b = session1.get(Item.class, new Long(1234) ); ( a==b ) // True, persistent a and b are identical tx1.commit(); session1.close(); // References a and b are now to an object in detached state Session session2 = sessionFactory.openSession(); Transaction tx2 = session2.beginTransaction(); Object c = session2.get(Item.class, new Long(1234) ); ( a==c ) // False, detached a and persistent c are not identical tx2.commit(); session2.close(); Object references a and b have not only the same database identity, but also the same Java identity, because they’re obtained in the same Session. They reference the same persistent instance known to the persistence context for that unit of work. Once you’re outside this boundary, however, Hibernate doesn’t guarantee Java identity, so a and c aren’t identical. Of course, a test for database identity, a.getId().equals( c.getId() ), will still return true. If you work with objects in detached state, you’re dealing with objects that are living outside of a guaranteed scope of object identity. 9.2.3 The identity of detached objects If an object reference leaves the scope of guaranteed identity, we call it a reference to a detached object. In listing 9.1, all three object references, a, b, and c, are equal if we only consider database identity—their primary key value. However, they aren’tObject identity and equality 395 identical in-memory object instances. This can lead to problems if you treat them as equal in detached state. For example, consider the following extension of the code, after session2 has ended: ... session2.close(); Set allObjects = new HashSet(); allObjects.add(a); allObjects.add(b); allObjects.add(c); All three references have been added to a Set. All are references to detached objects. Now, if you check the size of the collection, the number of elements, what result do you expect? First you have to realize the contract of a Java Set: No duplicate elements are allowed in such a collection. Duplicates are detected by the Set; whenever you add an object, its equals() method is called automatically. The added object is checked against all other elements already in the collection. If equals() returns true for any object already in the collection, the addition doesn’t occur. If you know the implementation of equals() for the objects, you can find out the number of elements you can expect in the Set. By default, all Java classes inherit the equals() method of java.lang.Object. This implementation uses a double-equals (==) comparison; it checks whether two references refer to the same in-memory instance on the Java heap. You may guess that the number of elements in the collection is two. After all, a and b are references to the same in-memory instance; they have been loaded in the same persistence context. Reference c is obtained in a second Session; it refers to a different instance on the heap. You have three references to two instances. However, you know this only because you’ve seen the code that loaded the objects. In a real application, you may not know that a and b are loaded in the same Session and c in another. Furthermore, you obviously expect that the collection has exactly one ele- ment, because a, b, and c represent the same database row. Whenever you work with objects in detached state, and especially if you test them for equality (usually in hash-based collections), you need to supply your own implementation of the equals() and hashCode() methods for your persis- tent classes. 396 CHAPTER 9 Working with objects Understanding equals() and hashCode() Before we show you how to implement your own equality routine. we have to bring two important points to your attention. First, in our experience, many Java developers never had to override the equals() and hashCode() methods before using Hibernate (or Java Persistence). Traditionally, Java developers seem to be unaware of the intricate details of such an implementation. The longest discus- sion threads on the public Hibernate forum are about this equality problem, and the “blame” is often put on Hibernate. You should be aware of the fundamental issue: Every object-oriented programming language with hash-based collections requires a custom equality routine if the default contract doesn’t offer the desired semantics. The detached object state in a Hibernate application exposes you to this problem, maybe for the first time. equals() and hashCode(). On the other hand, you may not have to override The identity scope guarantee provided by Hibernate is sufficient if you never com- pare detached instances—that is, if you never put detached instances into the same Set. You may decide to design an application that doesn’t use detached objects. You can apply an extended persistence context strategy for your conversa- tion implementation and eliminate the detached state from your application com- pletely. This strategy also extends the scope of guaranteed object identity to span the whole conversation. (Note that you still need the discipline to not compare detached instances obtained in two conversations) Let’s assume that you want to use detached objects and that you have to test equals() and hash- them for equality with your own routine. You can implement Code() several ways. Keep in mind that when you override equals(), you always hashCode() so the two methods are consistent. If two objects need to also override are equal, they must have the same hashcode. equals() to compare just the database A clever approach is to implement identifier property (often a surrogate primary key) value: public class User ... public boolean equals(Object other) if (this==other) return true; if (id==null) return false; if ( (other instanceof User) ) return false; final User that = (User) other; return this.id.equals( that.getId() ); public int hashCode() return id==null ?Object identity and equality 397 System.identityHashCode(this) : id.hashCode(); Notice how this equals() method falls back to Java identity for transient instances (if id==null) that don’t have a database identifier value assigned yet. This is rea- sonable, because they can’t possibly be equal to a detached instance, which has an identifier value. Unfortunately, this solution has one huge problem: Identifier values aren’t assigned by Hibernate until an object becomes persistent. If a transient object is added to a Set before being saved, its hash value may change while it’s contained by the Set, contrary to the contract of java.util.Set. In particular, this problem makes cascade save (discussed later in the book) useless for sets. We strongly dis- courage this solution (database identifier equality). A better way is to include all persistent properties of the persistent class, apart from any database identifier property, in the equals() comparison. This is how most people perceive the meaning of equals(); we call it by value equality. When we say all properties, we don’t mean to include collections. Collection state is associated with a different table, so it seems wrong to include it. More important, you don’t want to force the entire object graph to be retrieved just to perform equals(). In the case of User, this means you shouldn’t include the boughtItems collection in the comparison. This is the implementation you can write: public class User ... public boolean equals(Object other) if (this==other) return true; if ( (other instanceof User) ) return false; final User that = (User) other; if ( this.getUsername().equals( that.getUsername() ) ) return false; if ( this.getPassword().equals( that.getPassword() ) ) return false; return true; public int hashCode() int result = 14; result = 29 result + getUsername().hashCode(); result = 29 result + getPassword().hashCode(); return result; 398 CHAPTER 9 Working with objects However, there are again two problems with this approach. First, instances from different Sessions are no longer equal if one is modified (for example, if the user changes the password). Second, instances with different database identity (instances that represent different rows of the database table) can be considered equal unless some combination of properties is guaranteed to be unique (the database columns have a unique constraint). In the case of user, there is a unique username. property: This leads us to the preferred (and semantically correct) implementation of an equality check. You need a business key. Implementing equality with a business key To get to the solution that we recommend, you need to understand the notion of a business key. A business key is a property, or some combination of properties, that is unique for each instance with the same database identity. Essentially, it’s the nat- ural key that you would use if you weren’t using a surrogate primary key instead. Unlike a natural primary key, it isn’t an absolute requirement that the business key never changes—as long as it changes rarely, that’s enough. We argue that essentially every entity class should have some business key, even if it includes all properties of the class (this would be appropriate for some immutable classes). The business key is what the user thinks of as uniquely identi- fying a particular record, whereas the surrogate key is what the application and database use. Business key equality means that the equals() method compares only the prop- erties that form the business key. This is a perfect solution that avoids all the prob- lems described earlier. The only downside is that it requires extra thought to identify the correct business key in the first place. This effort is required anyway; it’s important to identify any unique keys if your database must ensure data integ- rity via constraint checking. For the User class, username is a great candidate business key. It’s never null, it’s unique with a database constraint, and it changes rarely, if ever: public class User ... public boolean equals(Object other) if (this==other) return true; if ( (other instanceof User) ) return false; final User that = (User) other; return this.username.equals( that.getUsername() ); public int hashCode() Object identity and equality 399 return username.hashCode(); For some other classes, the business key may be more complex, consisting of a combination of properties. Here are some hints that should help you identify a business key in your classes: ■ Consider what attributes users of your application will refer to when they have to identify an object (in the real world). How do users tell the differ- ence between one object and another if they’re displayed on the screen? This is probably the business key you’re looking for. ■ Every attribute that is immutable is probably a good candidate for the busi- ness key. Mutable attributes may be good candidates, if they’re updated rarely or if you can control the situation when they’re updated. ■ Every attribute that has a UNIQUE database constraint is a good candidate for the business key. Remember that the precision of the business key has to be good enough to avoid overlaps. ■ Any date or time-based attribute, such as the creation time of the record, is usually a good component of a business key. However, the accuracy of Sys- tem.currentTimeMillis() depends on the virtual machine and operating system. Our recommended safety buffer is 50 milliseconds, which may not be accurate enough if the time-based property is the single attribute of a business key. ■ You can use database identifiers as part of the business key. This seems to contradict our previous statements, but we aren’t talking about the database identifier of the given class. You may be able to use the database identifier of an associated object. For example, a candidate business key for the Bid class is the identifier of the Item it was made for together with the bid amount. You may even have a unique constraint that represents this com- posite business key in the database schema. You can use the identifier value of the associated Item because it never changes during the lifecycle of a Bid—setting an already persistent Item is required by the Bid constructor. If you follow our advice, you shouldn’t have much difficulty finding a good busi- ness key for all your business classes. If you have a difficult case, try to solve it with- out considering Hibernate—after all, it’s purely an object-oriented problem. Notice that it’s almost never correct to override equals() on a subclass and include another property in the comparison. It’s a little tricky to satisfy the400 CHAPTER 9 Working with objects requirements that equality be both symmetric and transitive in this case; and, more important, the business key may not correspond to any well-defined candi- date natural key in the database (subclass properties may be mapped to a differ- ent table). equals() and hashCode() methods always You may have also noticed that the access the properties of the “other” object via the getter methods. This is extremely important, because the object instance passed as other may be a proxy object, not the actual instance that holds the persistent state. To initialize this proxy to get the property value, you need to access it with a getter method. This is one point where Hibernate isn’t completely transparent. However, it’s a good prac- tice to use getter methods instead of direct instance variable access anyway. Let’s switch perspective now and consider an implementation strategy for con- versations that doesn’t require detached objects and doesn’t expose you to any of the problems of detached object equality. If the identity scope issues you’ll possi- bly be exposed to when you work with detached objects seem too much of a bur- den, the second conversation-implementation strategy may be what you’re looking for. Hibernate and Java Persistence support the implementation of con- versations with an extended persistence context: the session-per-conversation strategy. 9.2.4 Extending a persistence context A particular conversation reuses the same persistence context for all interactions. All request processing during a conversation is managed by the same persistence context. The persistence context isn’t closed after a request from the user has been processed. It’s disconnected from the database and held in this state during user think-time. When the user continues in the conversation, the persistence context is reconnected to the database, and the next request can be processed. At the end of the conversation, the persistence context is synchronized with the database and closed. The next conversation starts with a fresh persistence context and doesn’t reuse any entity instances from the previous conversation; the pat- tern is repeated. Note that this eliminates the detached object state All instances are either tran- sient (not known to a persistence context) or persistent (attached to a particular persistence context). This also eliminates the need for manual reattachment or merging of object state between contexts, which is one of the advantages of this strategy. (You still may have detached objects between conversations, but we con- sider this a special case that you should try to avoid.) In Hibernate terms, this strategy uses a single Session for the duration of the conversation. Java Persistence has built-in support for extended persistenceThe Hibernate interfaces 401 contexts and can even automatically store the disconnected context for you (in a stateful EJB session bean) between requests. We’ll get back to conversations later in the book and show you all the details about the two implementation strategies. You don’t have to choose one right now, but you should be aware of the consequences these strategies have on object state and object identity, and you should understand the necessary transitions in each case. We now explore the persistence manager APIs and how you make the theory behind object states work in practice. 9.3 The Hibernate interfaces Any transparent persistence tool includes a persistence manager API. This persis- tence manager usually provides services for the following: ■ Basic CRUD (create, retrieve, update, delete) operations ■ Query execution ■ Control of transactions ■ Management of the persistence context The persistence manager may be exposed by several different interfaces. In the case of Hibernate, these are Session, Query, Criteria, and Transaction. Under the covers, the implementations of these interfaces are coupled tightly together. In Java Persistence, the main interface you interact with is the EntityManager; it has the same role as the Hibernate Session. Other Java Persistence interfaces are Query and EntityTransaction (you can probably guess what their counter- part in native Hibernate is). We’ll now show you how to load and store objects with Hibernate and Java Per- sistence. Sometimes both have exactly the same semantics and API, and even the method names are the same. It’s therefore much more important to keep your eyes open for little differences. To make this part of the book easier to under- stand, we decided to use a different strategy than usual and explain Hibernate first and then Java Persistence. Let’s start with Hibernate, assuming that you write an application that relies on the native API. 402 CHAPTER 9 Working with objects 9.3.1 Storing and loading objects In a Hibernate application, you store and load objects by essentially changing their state. You do this in units of work. A single unit of work is a set of operations considered an atomic group. If you’re guessing now that this is closely related to transactions, you’re right. But, it isn’t necessarily the same thing. We have to approach this step by step; for now, consider a unit of work a particular sequence of state changes to your objects that you’d group together. First you have to begin a unit of work. Beginning a unit of work At the beginning of a unit of work, an application obtains an instance of Session from the application’s SessionFactory: Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); At this point, a new persistence context is also initialized for you, and it will man- age all the objects you work with in that Session. The application may have multi- ple SessionFactorys if it accesses several databases. How the SessionFactory is created and how you get access to it in your application code depends on your deployment environment and configuration—you should have the simple Hiber- nateUtil startup helper class ready if you followed the setup in “Handling the SessionFactory” in chapter 2, section 2.1.3. You should never create a new SessionFactory just to service a particular request. Creation of a SessionFactory is extremely expensive. On the other hand, Session creation is extremely inexpensive. The Session doesn’t even obtain a JDBC Connection until a connection is required. The second line in the previous code begins a Transaction on another Hibernate interface. All operations you execute inside a unit of work occur inside a transaction, no matter if you read or write data. However, the Hibernate API is optional, and you may begin a transaction in any way you like—we’ll explore these options in the next chapter. If you use the Hibernate Transaction API, your code works in all environments, so you’ll do this for all examples in the following sections. After opening a new Session and persistence context, you use it to load and save objects. Making an object persistent The first thing you want to do with a Session is make a new transient object per- save() method (listing 9.2). sistent with the