Hibernate Tools
Total Page:16
File Type:pdf, Size:1020Kb
APPENDIX A ■ ■ ■ More Advanced Features In this appendix, we discuss some of the features that, strictly speaking, lie outside the scope of this book, but that you should be aware of if you go on to use Hibernate in more depth. Managed Versioning and Optimistic Locking While we have saved versioning for this appendix’s discussion of advanced features, it is actually quite straightforward to understand and apply. Consider the following scenario: • Client A loads and edits a record. • Client B loads and edits the same record. • Client A commits its edited record data. • Client B commits its differently edited record data. While the scenario is simple, the problems it presents are not. If Client A establishes a transaction, then Client B may not be able to load and edit the same record. Yet in a web environment, it is not unlikely that Client A will close a browser window on the open record, never committing or canceling the transaction, so that the record remains locked until the session times out. Clearly this is not a satisfactory solution. Usually, you will not want to permit the alternative scenario, in which no locking is used, and the last person to save a record wins! The solution, versioning, is essentially a type of optimistic locking (see Chapter 8). When any changes to an entity are stored, a version column is updated to reflect the fact that the entity has changed. When a subsequent user tries to commit changes to the same entity, the original version number will be compared against the current value—if they differ, the commit will be rejected. The Hibernate/JPA 2 annotation mappings and the Hibernate XML-based mappings both provide a simple syntax for indicating which field should be used for storing the managed version information. The annotation for this field is shown in Listing A-1. Listing A-1. Marking the Version Attribute Using Annotations @Version protected int getVersionNum() { return versionNum; } 263 APPENDIX A ■ MORE ADVANCED FEATURES The default optimistic locking strategy for Hibernate is versioning, so if you provide a <version> element in your XML configuration, this will be used as long as you have enabled dynamic updates (as shown in Listing A-2). Listing A-2. Marking the Version Attribute Using XML Mappings <class dynamic-update="version" optimistic-lock="version" ... > ... <version name="versionNum"/> </class> The version attribute is defined in a very similar way to the normal property attribute configuration. The version can be of type long, integer, short, timestamp, or calendar (note that using the <timestamp ... /> element is an equivalent alternative to the use of the <version type="timestamp" ... /> element syntax). The <class> element’s optimistic-lock attribute can be used to override the default versioning- based optimistic locking strategy. You can disable it entirely (despite the presence of a version field) using a value of none. You can explicitly state that versioning should be used with a value of version. You can elect to use dirty checking, with the dirty and all options. If you elect not to use versioning, dirty checking offers an alternative form of optimistic locking. Here, the values of the entities are themselves checked to see if they have changed since the entity was originally obtained. As with versioning-based optimistic locking, the check against the database is carried out when the changes are committed. If an optimistic lock type of dirty is selected, then only those fields that have changed since the persistent entity was obtained will be checked (the Session keeps track of the appropriate state information). If an optimistic lock type of all is selected, then all the fields comprising the entity will be checked for changes. If the fields being checked have changed prior to the commit, then the commit will fail. Versioning is generally a simpler and more reliable approach, so we suggest that you use this whenever you need optimistic locking features. Maps In addition to the default mode (POJO) and the XML mode (Dom4J) described previously, the Hibernate session can be accessed in one more way: as a map of name/value pairs. This mode is accessed by calling the getSession() method with a parameter of EntityMode.MAP (see Listing A-3). Listing A-3. Accessing a Hibernate Session in Map Mode package sample.map; import java.util.*; import org.hibernate.EntityMode; import org.hibernate.*; import org.hibernate.cfg.Configuration; public class AccessAsMap { private static final SessionFactory sessionFactory = new Configuration() .configure().buildSessionFactory(); 264 APPENDIX A ■ MORE ADVANCED FEATURES public static void main(String[] args) throws Exception { System.out.println("Preparing the Session objects"); Session session = sessionFactory.openSession(); Session mapSession = session.getSession(EntityMode.MAP); System.out.println("Reading the map entries for XXX"); session.beginTransaction(); Map entity = (Map)mapSession.get("sample.entity.Category",new Long(2)); System.out.println("Category Title: " + entity.get("title")); System.out.println("Contains Adverts:"); Set adverts = (Set)entity.get("adverts"); Iterator adIt = adverts.iterator(); while(adIt.hasNext()) { Map advert = (Map)adIt.next(); System.out.println(advert.get("title")); } session.getTransaction().commit(); session.close(); System.out.println("Done."); } } Changes written to the Map objects will be persisted exactly as if a normal persistent POJO object had been updated. Note that only the entities themselves will be represented as Maps—not any of their attributes having a value type, or associations using Collection types. For example, in Listing A-3, the Category entity is represented as a Map, but its title attribute is represented as a String and its adverts attribute is represented as a Set—however, the Set itself contains Advert entities represented as Maps. ■ Note You may find problems in edge cases where Hibernate expects certain objects to be POJOs and not hashmaps, such as composite primary key id classes. Limitations of Hibernate First and foremost, Hibernate wants every entity to be identifiable with a primary key. Ideally, it would like this to be a surrogate key (a single column distinct from the fields of the table). Hibernate will accept a primary key that is not a surrogate key. For example, the username column might be used to uniquely identify an entry in the user table. Hibernate will also accept a composite key as its primary key, so that the username and hostname might be used to form the primary key if the username alone does not serve to identify the row. In the real world, things do not really work like that. Any database that has been around the block a few times is likely to have at least one table for which the primary key has been omitted. For instance, 265 APPENDIX A ■ MORE ADVANCED FEATURES the contents of the table may not have needed to be involved in any relations with other tables. While this is still bad database design, the error is only exposed when Hibernate tries to map objects to data. It may be that adding a suitable surrogate key column is an option—when this is the case, we urge you to do so. In practice, however, the fundamental schema may not be under the developer’s control, or other applications may break if the schema is radically changed. In most scenarios, a developer will be able to arrange the creation of views or stored procedures. It may be possible to create the appearance of a suitable primary key using these if no other options present themselves, but you should consult with your database administrators, since a table for which no true primary key can be obtained is likely to cause long-term corruption of your data. Finally, if you can neither change a broken schema nor add views or stored procedures to ameliorate its effects, you have the option of obtaining a pure JDBC connection (see Listing A-4) from the session to the database, and carrying out traditional connected database access. This is the option of last resort, and is only truly of value when you anticipate being able to correct the faulty schema at some future time. Listing A-4. Obtaining a JDBC Connection from Hibernate SessionFactory factory = new Configuration().configure().buildSessionFactory(); Session session = factory.openSession(); Connection connection = session.getConnection(); Hand-Rolled SQL While Hibernate cannot operate upon entities that lack primary keys, it is also extremely awkward to use Hibernate when there is a poor correspondence between the tables and the classes of your object model. Using a Direct Mapping Figure A-1 presents a fairly typical example of a valid database model that may be painful to represent in our mapping files. Figure A-1. A problematic but legal schema Here, the product table represents a product (for example, a flashlight). The color table represents the colors in which it is sold. The link table named product_color then allows us to identify a product by stock keeping unit (SKU), and identify the colors in which it is available. 266 APPENDIX A ■ MORE ADVANCED FEATURES If we do not mind the Product object retaining a set of colors (representing the colors in which it can be sold), then we have no problem; but if we want to distinguish between a red flashlight and a green one, things become more difficult (see Listing A-5). Listing A-5. A Fairly Direct Representation of the Product as an XML mapping <class name="com.hibernatebook.legacy.Product" table="product_color"> <composite-id class="com.hibernatebook.legacy.ProductKey" name="key"> <key-property type="int" name="id" column="product_id"/> <key-property type="int" name="colorId" column="color_id"/> </composite-id> <many-to-one name="color" class="com.hibernatebook.legacy.Color" column="color_id" insert="false" update="false"/> <many-to-one name="data" class="com.hibernatebook.legacy.ProductData" column="product_id" insert="false" update="false"/> </class> There are several dissatisfying aspects to the mapping in Listing A-5.