views:

38

answers:

1

I'm trying to create a domain object that contains objects in a read-only fashion but be able to modify which objects it points to.

So in the example below I would like Hibernate to handle populating the Account and Category objects but I don't want to ever update those objects. However I may wish to change which Account or which Category I want Expense to point at.

In other words I may wish to update the account_id or category_id on the expense table but I never want to update the account or category table with changes I've made on the Account or Category objects contained in the Expense object.

@Entity
@Table(name = "expense")
public class Expense {

  @Id
  @GeneratedValue
  private long id;

  private String name;

  private BigDecimal amount;

  @OneToOne
  @JoinColumn(name = "id", referencedColumnName = "category_id")
  //From table called category
  private Category category;

  @OneToOne
  @JoinColumn(name = "id", referencedColumnName = "account_id")
  //From table called account
  private Account account;

Am I totally misusing Hibernate or is it just not the right tool? Should I define my own sql with @SQLUpdate and @SQLInsert?

The general flow of the application is standard web app stuff.

  1. Load data from database
  2. Display on form
  3. Gather changes made by user
  4. Persist those changes

Thanks

+1  A: 

It is a bit of a departure from the intent of hibernate. Changes to persistent objects, are supposed to be persistent. Don't change them if you don't want their state to be saved! Explaining and thus better understanding for yourself why you want that kind of behavior might help you arrive at a solution (or help us help you.)

One option you have is to detach/evict the mapped objects that you don't want to be changed inside your data access layer before returning the Expense object. Then as long as no cascading is on, changes made to them won't be saved. IMO this leads to "surprising" behavior for other programmers.

Another option is to use hibernate in a Sessionless manner. Each call is its own atomic database operation and all objects are detached after returning. Then only entities that you explicitly call saveOrUpdate on will get saved to the database. You can learn how to configure Hibernate to work this way in the Hibernate reference docs, although again, I don't know why you'd want to if you're not trying to emulate the behavior of some legacy system you're uplifting.

One final point to understand, withing a Session, the Category that you get by calling expense.getCategory() will usually be the same actual object that you get by calling session.get(Category.class, 123). So if both ways of accessing it are available inside the same Session, it will be easy to lose track of its entity state if you're trying to manually manage it.


edit:

Ah-ha! Now it makes more sense.

If you're doing purely CRUDy web form screens, you don't have much to worry about. As long as you don't have any Cascading on, when you merge back the detached Expense object, changes on the Category and Account aren't going to end up in the database. You could null out every other field, and as long as the id is still there, you're ok. (Assuming you don't have other things like cascading validation that would cry about it.)

There are two basic patterns for handling this a little bit better.

One is to put the Expense object on the user's web session. This way you have the entire thing, and your web data binding framework only needs to bind back onto it the fields that are actually changed by the form. The original Category and Account are still there (or proxies of them) and the binder doesn't need to munge them. Downside is of course adding server side state, as well as needing to clean it up.

Two is to register data binders that actually go into the database and get the mapped entity during web binding. So all that would actually appear in the form is the ID of the mapped field, and the binder will go fetch and put the full object there for you. Downside is possibly unneeded round trips to the database, but aggressive L2 caching can fix that (assuming Categories almost never change and Accounts rarely change.)

You may also want to look into the OpenSessionInView/OpenEntityManagerInView patterns if you're presently using hibernate in an essentially sessionless manner by creating and disposing sessions within the DAO.

Affe
I am working in a servlet environment so my hibernate sessions last only as long as I am in the dao layer of the application. The flow is a standard web app`Load data from Hibernate -> Display stuff on form -> Get data from Form -> Load data back into database.`After I get back from the form I don't necessarily have the full objects Hibernate originally gave me, just bits and pieces. So I don't want to overwrite the full object in the db with those pieces.
Josh
ah-ha! answer expanded
Affe
This is exactly what I was looking for. Sorry it took so long to get back to it. Thanks!
Josh