18

I have two classes, Foo and Bar, as follows:

public class Foo {
     private Long fooId;

     private Bar bar;
     //Yes, this doesn't actually make any sense,
     //having both a list and a single object here, its an example.
     private List<Bar> bars;
}

public class Bar {
    private Long barId;

    private Foo foo;
}

How do I implement a (uni-directional/bi-directional) one-to-many, many-to-one or many-to-many relationship using annotations for Hibernate 4 for these classes?

Also how do I configure my one-to-many for orphan removal, lazy loading and what causes a LazyInitialiaizationException when dealing with collections and how to solve the problem?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
JamesENL
  • 6,400
  • 6
  • 39
  • 64
  • True, until 20k, which makes sense. This 'question' might get deleted, but most likely it will be closed. Good luck and thanks for the effort anyway. – Sully Jun 17 '14 at 07:13
  • wikipedia/wikibooks is actually the place for this, and there already are books there for such JPA relationships, so why not just "improve" those if not complete – Neil Stockton Jun 17 '14 at 07:37
  • I did consider that, then I considered how likely it is that the people who this might benefit would come across it there. I mainly wanted a SO resource that I could point to when answering people's questions because I got sick of typing out the same object structure time after time after time – JamesENL Jun 17 '14 at 07:39
  • Most information you mentioned here also applies to JPA. I will suggest leaving JPA in the tag/question. – Adrian Shum Jun 18 '14 at 05:06

1 Answers1

37

Creating Relationships with Annotations

Assume all classes annotated with @Entity and @Table

Uni-directional One to One Relationship

public class Foo{
    private UUID fooId;

    @OneToOne
    private Bar bar;
}

public class Bar{
    private UUID barId;
    //No corresponding mapping to Foo.class
}

Bi-Directional One to One Relationship managed by Foo.class

public class Foo{
    private UUID fooId;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "barId")
    private Bar bar;
}

public class Bar{
    private UUID barId;

    @OneToOne(mappedBy = "bar")
    private Foo foo;
}

Uni-Directional One to Many Relationship using user managed join table

public class Foo{
    private UUID fooId;

    @OneToMany
    @JoinTable(name="FOO_BAR",
        joinColumns = @JoinColumn(name="fooId"),
        inverseJoinColumns = @JoinColumn(name="barId"))
    private List<Bar> bars;
}

public class Bar{
    private UUID barId;

    //No Mapping specified here.
}

@Entity
@Table(name="FOO_BAR")
public class FooBar{
    private UUID fooBarId;

    @ManyToOne
    @JoinColumn(name = "fooId")
    private Foo foo;

    @ManyToOne
    @JoinColumn(name = "barId")
    private Bar bar;

    //You can store other objects/fields on this table here.
}

Very commonly used with Spring Security when setting up a User object who has a list of Role's that they can perform. You can add and remove roles to a user without having to worry about cascades deleting Role's.

Bi-directional One to Many Relationship using foreign key mapping

public class Foo{
    private UUID fooId;

    @OneToMany(mappedBy = "bar")
    private List<Bar> bars;
}

public class Bar{
    private UUID barId;

    @ManyToOne
    @JoinColumn(name = "fooId")
    private Foo foo;
}

Bi-Directional Many to Many using Hibernate managed join table

public class Foo{
    private UUID fooId;

    @OneToMany
    @JoinTable(name="FOO_BAR",
        joinColumns = @JoinColumn(name="fooId"),
        inverseJoinColumns = @JoinColumn(name="barId"))
    private List<Bar> bars;
}

public class Bar{
    private UUID barId;

    @OneToMany
    @JoinTable(name="FOO_BAR",
        joinColumns = @JoinColumn(name="barId"),
        inverseJoinColumns = @JoinColumn(name="fooId"))
    private List<Foo> foos;
}

Bi-Directional Many to Many using user managed join table object

Commonly used when you want to store extra information on the join object such as the date the relationship was created.

public class Foo{
    private UUID fooId;

    @OneToMany(mappedBy = "bar")
    private List<FooBar> bars;
}

public class Bar{
    private UUID barId;

    @OneToMany(mappedBy = "foo")
    private List<FooBar> foos;
}

@Entity
@Table(name="FOO_BAR")
public class FooBar{
    private UUID fooBarId;

    @ManyToOne
    @JoinColumn(name = "fooId")
    private Foo foo;

    @ManyToOne
    @JoinColumn(name = "barId")
    private Bar bar;

    //You can store other objects/fields on this table here.
}

Determining which side of the bi-directional relationship 'owns' the relationship:

This is one of the trickier aspects of working out Hibernate relationships because Hibernate will operate correctly no matter which way to set up the relationship. The only thing that will change is which table the foreign key is stored on. Generally the object that you have a collection of will own the relationship.

Example: A User object has a list of Roles declared on it. In most applications, the system will be manipulating instances of the User object more often than instances of the Roles object. Hence I would make the Role object the owning side of the relationship and manipulate the Role objects through the list of Role's on a User by cascade. For a practical example see the bi-directional One to Many example. Typically you will cascade all changes in this scenario unless you have a specific requirement to do otherwise.

Determining your fetchType

Lazily fetched collections have resulted in more issues on SO than I care to look at because by default Hibernate will load related objects lazily. It doesn't matter if the relationship is a one-to-one or many-to-many as per the Hibernate docs:

By default, Hibernate uses lazy select fetching for collections and lazy proxy fetching for single-valued associations. These defaults make sense for most associations in the majority of applications.

Consider this my two cents on when to use fetchType.LAZY vs fetchType.EAGER on your objects. If you know that 50% of the time you won't need to access the collection on your parent object, I'd be using fetchType.LAZY.

The performance benefits are of this are huge and only grow as you add more objects to your collection. This is because for an eagerly loaded collection, Hibernate does a ton of behind the scenes checking to ensure that none of your data is out of date. While I do advocate using Hibernate for collections, be aware that there is a performance penalty** for using fetchType.EAGER. However, take our Person object example. Its fairly likely that when we load a Person we will want to know what Roles they perform. I will usually mark this collection as fetchType.EAGER. DON'T REFLEXIVELY MARK YOUR COLLECTION AS fetchType.EAGER SIMPLY TO GET AROUND A LazyInitializationException. Not only is it bad for performance reasons, it generally indicates that you have a design issue. Ask yourself, should this collection actually be an eagerly loaded collection, or am I doing this just to access the collection in this one method. Hibernate has ways around this, that doesn't impact the performance of your operations quite as much. You can use the following code in your Service layer if you want to initialize a lazily loaded collection just for this one call.

//Service Class
@Override
@Transactional
public Person getPersonWithRoles(UUID personId){
    Person person = personDAO.find(personId);

    Hibernate.initialize(person.getRoles());

    return person;
}

The call to Hibernate.initialize forces the creation and loading of the collection object. However, be careful, if you only pass it the Person instance, you will get a proxy of your Person back. See the documentation for more information. The only downside to this method is that you have no control over how Hibernate will actually fetch your collection of objects. If you want to control this, then you can do so in your DAO.

//DAO
@Override
public Person findPersonWithRoles(UUID personId){
    Criteria criteria = sessionFactory.getCurrentSession().createCritiera(Person.class);

    criteria.add(Restrictions.idEq(personId);
    criteria.setFetchMode("roles", FetchMode.SUBSELECT);
}

The performance here depends on what FetchMode you specify. I've read answers that say to use FetchMode.SUBSELECT for performance reasons. The linked answer goes into more detail if you are really interested.

If you want to read me as I repeat myself, feel free to check out my other answer here

Determining Cascade Direction

Hibernate can cascade operations either or both ways in a bi-directional relationship. So if you have a List of Role's on a User you can cascade changes to Role's in both directions. If you change the name of a particular Role on a User Hibernate can automatically update the associated Role on the Role Table.

However this is not always desired behaviour. If you think about it, in this case, making changes to Role's based on changes to User doesn't make any sense. However it makes sense going in the opposite direction. Change a Role's name on the Role object itself, and that change can be cascaded to all User objects that have that Role on it.

In terms of efficiency, it makes sense to create/update Role objects by saving the User object that they belong to. This means you would mark your @OneToMany annotation as the cascading one. I'll give an example:

public User saveOrUpdate(User user){
    getCurrentSession.saveOrUpdate(user);
    return user;
}

In the above example, Hibernate will generate a INSERT query for the User object, and then cascade the creation of the Role's once the User has been inserted into the database. These insert statements will then be able to use the PK of the User as their foreign key, so you would end up with N + 1 insert statements, where N is the number of Role objects in the list of users.

Conversely if you wanted to save the individual Role objects cascading back to the User object, could be done:

//Assume that user has no roles in the list, but has been saved to the
//database at a cost of 1 insert.
public void saveOrUpdateRoles(User user, List<Roles> listOfRoles){
    for(Role role : listOfRoles){
        role.setUser(user);
        getCurrentSession.saveOrUpdate(role);
    }
}

This results in N + 1 inserts where N is the number of Role's in the listOfRoles, but also N update statements being generated as Hibernate cascades the addition of each Role to the User table. This DAO method has a higher time complexity than our previous method, O(n) as opposed to O(1) because you have to iterate through the list of roles. Avoid this if at all possible.

In practice however, usually the owning side of the relationship will be where you mark your cascades, and you will usually cascade everything.

Orphan Removal

Hibernate can work out for you if you remove all associations to an object. Suppose you have a User who has a list of Role's and in this list are links to 5 different roles. Lets say you remove a Role called ROLE_EXAMPLE and it happens that the ROLE_EXAMPLE doesn't exist on any other User object. If you have orphanRemoval = true set on the @OneToMany annotation, Hibernate will delete the now 'orphaned' Role object from the database by cascade.

Orphan removal should not be enabled in every case. In fact, having orphanRemoval in our example above makes no sense. Just because no User can perform whatever action the ROLE_EXAMPLE object is representing, that doesn't mean that any future User will never be able to perform the action.

This Q&A is intended to complement the official Hibernate documentation, which has a large amount of XML configuration for these relationships.

These examples are not meant to be copy-pasted into production code. They are generic examples of how to create and manage various objects and their relationships using JPA annotations to configure Hibernate 4 within the Spring Framework. The examples assume that all classes have ID fields declared in the following format: fooId. The type of this ID field is not relevant.

** We recently had to abandon using Hibernate for an insert job where we were inserting <80,000+ objects into the database through a collection. Hibernate ate up all the heap memory doing checking on the collection and crashed the system.

DISCLAIMER: I DO NOT KNOW IF THESE EXAMPLES WILL WORK WITH STANDALONE HIBERNATE

I am not in any way affiliated with Hibernate or the Hibernate dev team. I'm providing these examples so I have a reference to point to when I'm answering questions on the Hibernate tag. These examples and discussions are my based on my own opinion as well as how I develop my applications using Hibernate. These examples are in no way comprehensive. I'm basing them on the common situations I've used Hibernate for in the past.

If you encounter issues trying to implement these examples, do not comment and expect me to fix your problem. Part of learning Hibernate is learning the in's and out 's of its API. If there is a mistake with the examples, please feel free to edit them.

Community
  • 1
  • 1
JamesENL
  • 6,400
  • 6
  • 39
  • 64
  • My suggestion on usage of LAZY fetch is different from you. Although I do agree to use LAZY fetch generally, I don't agree on the approach of using hacky code to trigger lazy loading in order to to avoid lazy initialization exception. Instead, it is more reasonable to have the DAO perform appropriate join fetching etc so that attributes needed is fetched all together. The reason behind is as you have mentioned in the quoted article: to better utilize DB resources. – Adrian Shum Jun 18 '14 at 04:58
  • Have you got an example of the types of joins that you would have to do? The only reason I didn't include it, is that I've never had to manually join using Criteria, but if you can give me an example, I'll happily update my answer. – JamesENL Jun 18 '14 at 04:59
  • And, I believe it is also worth mentioning the difference of relationship-owning side for bi-directional One-To-Many, on the aspect of resulting SQLs. If ManyToOne side is owning the relationship, it is going to cause 1 + n inserts and n updates. While having ManyToOne owning the relationship, the FK can be determined when inserting which will result in 1+n inserts only. – Adrian Shum Jun 18 '14 at 05:01
  • I usually use HQL which join fetching is easy. I do expect it is not more difficult using Criteria :) As for better future reference, I may simply raise the alternative as a new answer in the quoted article. – Adrian Shum Jun 18 '14 at 05:03
  • By all means, please do. I've just had a quick look and found a method called `Hibernate.initialize(Object object)` that forces the initialisation of a proxy or collection. That sounds like a much better solution too me. Also, if you want to suggest an edit for my answer regarding the different SQL generated out, feel free. I freely admit I haven't studied the differences in the SQL myself. – JamesENL Jun 18 '14 at 05:06
  • forcing the proxy to load is not really a good idea (and that's what I tried to avoid in my comment). That only solve the lazy init error, but it is giving same extra workload to DB. Am going to add an new answer to that question and hopefully it make the point clearer :) – Adrian Shum Jun 18 '14 at 09:01
  • posted an answer in that lazy-loading question: http://stackoverflow.com/a/24281634/395202 see if you want to incorporate that in your answer here – Adrian Shum Jun 18 '14 at 09:17
  • @AdrianShum, reading your earlier comment about the relationship-owning side and 1+n inserts has confused me, which side results in 1+n inserts and n updates? I would have thought it wouldn't matter which side owned the relationship, but the n+1 problem would be caused by which object you use to cascade saves through. If you save the child and expect the update to cascade to the parent then you would get n+1 inserts and n updates, but if you save the parent with the collection, you'd only get n+1 inserts. – JamesENL Jun 19 '14 at 05:07
  • it is not related to cascading direction. Assume you have User and UserName which have a one to many relationship (a user can have multiple name records). If the `User` side is owning the relationship, when you are saving a new User with multiple names, it will result in 1 insert for user, 1 insert per name, and 1 update per name. However if the `Name` side is owning the relationship, when you save a new user, it will result in 1 insert for user, and 1 insert per name. This is particular important if in DB, `user` column in `Name` table is set to not null – Adrian Shum Jun 19 '14 at 06:20
  • So does Hibernate auto-cascade the parent's changes to the child if you don't override it with a specific `CascadeType`? If Hibernate has already saved the parent, why does it need to update the parent when it saves the child? I can easily see that happening if you are saving the child though. – JamesENL Jun 19 '14 at 06:23