Hibernate: Difference between revisions

From Elvanör's Technical Wiki
Jump to navigation Jump to search
No edit summary
Line 43: Line 43:
== Managing objects and their lifecycles ==
== Managing objects and their lifecycles ==


=== Detached instances ==
=== Detached instances ===


* To reattach a detached instance to the session, you can use either update(), which will schedule an SQL UPDATE statement when the session is flushed, or merge(). update() will only work if the session does not contains already the object, while merge() will always work regardless of the session state.
* To reattach a detached instance to the session, you can use either update(), which will schedule an SQL UPDATE statement when the session is flushed, or merge(). update() will only work if the session does not contains already the object, while merge() will always work regardless of the session state.

Revision as of 12:13, 19 May 2008

Hibernate

Some nice Hibernate documentation links:

Useful books:

  • Java Persistence with Hibernate (Manning)
  • Pro Hibernate 3 (Apress)

Mappings

  • Mappings and especially associations can be hard to understand at first.

Concepts

  • The concept of bidirectionality is *very* important. A bidirectional association means that links exist at both sides - the link can be navigated in both directions. Note that sometimes things that are possible with unidirectional associations are hard or impossible to do with bidirectional associations (usually because actually, in the Java underlying model the association is not truely "bidirectional", eg. there is some missing information on one side of the link).
  • The owning side in the case of a bidirectional association is the side that will update the relationship when saved to the DB. If I understand it correctly, it is the side controlling the columns managing the relationship in the SQL tables.
  • Note that the concept of bidirectionality usually implies (for a many-to-one mapping) than one side has a reference (simple link) to another object, and this object has a collection of objects. The bidirectionality is optional in the sense that using collections on an object is not necessarily. You could always write queries to find all the objects that are the child of a given entities. However, using a collection explicitly states at the Java object model the bidirectional relationship.
  • This is also the reason for which many-to-one unidirectional associations are frequent, but one-to-many (eg a collection with no inverse links) are less used.
  • Hibernate differentiates between entities and components. Entities have their own lifecycle, and can support shared references, while components are similar to value types and their lifespan is equal to the lifespan of their containing parent. Components can thus be useful when the object is clearly bound to another object. Relatively speaking, components are of lesser importance than full domain entities.

Limitations

  • Note that you cannot have two one-to-one bidirectional mappings to the same class (this is also true for many-to-one). This is because there is no true bidirectionality in the relationship, in the sense that the target object cannot know to which property it belongs in the object that has two links. At the database level, the table of one object will hold two join columns. From this object you can correctly find the target objects by looking at the join columns which will hold the target objects ids. But from a sample target object, you cannot go back to the other object as there is no way for you to know which one of the join columns you must query.

Remarks

  • If you specify a one-to-many or many-to-many mapping, be sure to use the same mapping as the Java collection interface. A Set mapping won't work with a List collection, for example.

Collections

  • Never try to associate a collection reference to more than one object. This will cause lots of errors. If you need to, you can copy all the objects from a collection to another one. This applies even if you deleted the object containing the collection reference first.

Managing objects and their lifecycles

Detached instances

  • To reattach a detached instance to the session, you can use either update(), which will schedule an SQL UPDATE statement when the session is flushed, or merge(). update() will only work if the session does not contains already the object, while merge() will always work regardless of the session state.
    • Be careful, as the object may in fact already be loaded via other objects into the session! Thus it's hard to be always sure that your object is not yet in the session.
  • If you don't want to issue an UPDATE statement, you can use lock(object, LockMode.NONE). In this case, you must be sure however that the detached instance has not been modified.

Casting an object to one of its subclass

  • To understand why this is not possible directly in Hibernate, consider the fact that this is not possible in the underlying layer (the JVM). In Java you cannot change dynamically the class of an object at runtime. The same holds for Hibernate: there is no easy way to change the class of a persistent object (even to one of its subclasses).
  • So, in order to actually achieve this, proceed as you would with a Java object: create a new object and copy the properties from the previous object to this new one. In Hibernate you also need to delete the old object as you accomplish that (generally flushing the session before saving the new one, because of unique constraints). Wrap this in a transaction as you want to revert and avoid any change to the DB should an exception occurs.
  • This kind of situation should be avoided (or rarely used). Once set, an object should not change its class. If need be, redesign your data model.

Older remarks

  • If you have an object and a many-to-one mapping on this object (such as Item that belongs to a Category), and you happen to have the ID of the Category but not the associated Java object, in order to store the item on the database the fastest way is to create a new virtual Category and call setID(known_id) on this newly created object. Then, once you save the object, the virtual Category will not be saved (updated) as it already exists. For this to happen (eg, for the object not to be overriden), you must use the ID of the Object as the equals() method. This is not recommended. Note that by default Hibernate distinguishes between objects by using their addresses in memory.
  • Update: The previous remark would work, but is not the recommended way. The correct way is to use session.load(Category.class, category_id) and use a proxy. This means the object won't actually be fetched from the DB, but you'll be able to use it as a Java object. This is nice. By default, a mapping uses a proxy.

Cascade

  • The concept of cascade implies somehow that one object "belongs" to another one. When saving, updating, or deleting the parent object, the child will also be saved, updated or deleted.
  • Apparently, it is not possible to cascade in the other direction, that is, from the child to the parent. Saving a child when the parent is not yet saved won't persist the parent.

Transactions

  • Real database transactions are only possible if the underlying DB supports them. This means that with MySQL MyISAM tables, transactions are very limited. In an Hibernate context, nothing is written to the DB unless the session is flushed. When it is, however, everything is written and cannot be rolled back later.
  • Bottom-line: use InnoDB if you need transactions (and transactions are almost mandatory when using an ORM I would say).

Performance Tuning

  • In Hibernate, lazy loading is on by default for collections. This means the collections are fetched from the database only when they are used (never if they are not used). Warning: if the loading appears when the Hibernate session is already closed, it will fail.

Locking

  • If you get StaleObjectExceptions in your code, this means that you have a concurrency problem. Typically, you access one object in a transaction, try to update it, while in another transaction the same object was already modified. In Grails, since there is a session per request model, this typically means that two HTTP requests were fired at the same time. Thus it is most probably a JavaScript error.
  • This can also happens if you have a detached instance that you try to reattach, but the SQL data row was deleted in the meantime.

Referential Integrity

  • The InnoDB engine supports referential integrity (database consistency) by the use of foreign keys constraints. When a FK constraint is in place, the database will prevent you from deleting some rows (or will prevent wrong UPDATEs etc). Normally you can specify Hibernate which FK constraint to use.

Hibernate & EJB3 Annotations

  • The EJB specification indicates whether the property should be accessed by field or by getter method. This depends on where you put the annotation. You should put it before the field if you want field access, and before the getter method to get property access.
  • The best documentation for Hibernate annotations is the reference Hibernate documentation, but also the javax.persistence API which documents all possible official EJB annotations.
  • Here is an example of a (Groovy) annotated class:
@Entity()
@Table(name="shop_category")
class ShopCategory
{
	@Id
	Long id
	
	@ManyToOne
	ShopCategory parentCategory
	
	String name
	
	@OneToMany(targetEntity = ShopItem.class, mappedBy="category")	
	Set items
	
	@OneToMany(cascade = [javax.persistence.CascadeType.ALL], targetEntity = ShopCategory.class, mappedBy="parentCategory")
	Set subCategories
}