Setting on a Set()

Maybe this post should be called ‘Setting on a Sunset()’ but Oracle now owns Java, and ‘Setting on an OracleSet()’ sounds programmatically correct but it just doesn’t have the same ring (or the feel of a nice drink in your hand while sitting on a beach….. writing java ;))

Anyway, there are many times when dealing with an ORM that you might want to call a setter on an object that exists in a 1-Many relationship and have that set() method do some other work.

For instance, say you have two objects (using JPA Annotations):

@Entity
@Table(name="USER")
public class User {
   @Id
   @Column(name="ID")
   private Long id;

   @Column(name="NAME")
   private String name;

   @ManyToOne(fetch=FetchType.LAZY)
   @JoinColumn(name = "COMPANY_ID", referencedColumnName = "ID", nullable = false)
   private Company company;
   
   public Long getId() { return id; }
   public void setId(Long id) { this.id = id; }

   public String getName() { return name; }
   public void setName(String name) { this.name = name; }

   public Company getCompany() { return company; }
   public void setCompany(Company company) { this.company = company }
}

@Entity
@Table(name="COMPANY")
public class Company {
   @Id
   @Column(name="ID")
   private Long id;

   @Column(name="NAME")
   private String name;

   @OneToMany(fetch = FetchType.LAZY, mappedBy = "company", 
          cascade = { CascadeType.MERGE, CascadeType.PERSIST })
   private Set<User> users = new HashSet<User>(0);

   public Long getId() { return id; }
   public void setId(Long id) { this.id = id; }

   public String getName() { return name; }
   public void setName(String name) { this.name = name; }

   public Set<User> getUsers() { return users; }
   public void setUsers(Set<User> users) { this.users = users; }
}

This is a pretty straightforward, standard way of making a 1-Many relationship in an ORM (my experience is with Hibernate mainly).

Side note:
My HashSet was initially set to a size of 0. If a Collection has the possibility of staying empty you should initialize it to 0 because otherwise some implementation will immediate make it 10 spaces and you will waste memory. However, initializing it to 0 also means that we take a small performance hit when we first put something into it since it has to allocate space. I’m just suggesting that you put some thought into it 😉

So lets start using our objects and set some things:

... some method definition...
Company company = new Company();
company.setName("World Wide Wicket");

User user = new User();
user.setName("Sloan");
user.setCompany(company);
company.addUser(user);
.... more code ...

Doesn’t that seem a bit annoying? You set the Company in the User but then you have to add the User to the Companies list of Users. Wouldn’t it be better to just call one method to accomplish this? Shouldn’t you just have to set the Company in the User and that be it (or just add the User to the list of Users in the Company)?

So I was thinking about this and I came up with a few different ways. I’ll go over them and also what is good/bad about them as well as any other pitfalls I have identified.

Custom Set implementation

The first thing I thought was: Well, I can create my own collection so that when it is called it will do things for me. That would look something like:

public class Company {
@Id
   @Column(name="ID")
   private Long id;

   @Column(name="NAME")
   private String name;

   @OneToMany(fetch = FetchType.LAZY, mappedBy = "company", 
          cascade = { CascadeType.MERGE, CascadeType.PERSIST })
   private Set<User> users = new UserHashSet<User>(this);

   public Long getId() { return id; }
   public void setId(Long id) { this.id = id; }

   public String getName() { return name; }
   public void setName(String name) { this.name = name; }

   public Set<User> getUsers() { return users; }
   public void setUsers(Set<User> users) { this.users = users; }

   public class UserHashSet extends HashSet<User> {
      private Company company;

      public UserHashSet(Company company) {
         this.company = company;
      }

      public void add(User user) {
         super.add(user);
         user.setCompany(company);
      }

      ... do the same for addAll ...
   }
}

Pros:

  • The code is pretty straight forward for the developers when adding:
company.getUsers().add(user);

Cons:

  • You have to code a whole other class
  • If someone calls company.setUsers() they can override your UserHashSet and add() will no longer work correctly
  • Developers, if playing it safe, would still probable do:
company.getUsers().add(user);
user.setCompany(company);

So, as slick as this may be in some ways, I don’t think it’s the way to go because it allows the developers to still do additional work as well as really screw things up.

Code in set() and an add() method

Another option is to put some code in the User.setCompany() and create a method in the Company called addUser(). Something like:

@Entity
@Table(name="USER")
public class User {
   @Id
   @Column(name="ID")
   private Long id;

   @Column(name="NAME")
   private String name;

   @ManyToOne(fetch=FetchType.LAZY)
   @JoinColumn(name = "COMPANY_ID", referencedColumnName = "ID", nullable = false)
   private Company company;
   
   public Long getId() { return id; }
   public void setId(Long id) { this.id = id; }

   public String getName() { return name; }
   public void setName(String name) { this.name = name; }

   public Company getCompany() { return company; }
   public void setCompany(Company company) { 
      this.company = company 
      company.getUsers().add(this);
   }
}

@Entity
@Table(name="COMPANY")
public class Company {
   @Id
   @Column(name="ID")
   private Long id;

   @Column(name="NAME")
   private String name;

   @OneToMany(fetch = FetchType.LAZY, mappedBy = "company", 
          cascade = { CascadeType.MERGE, CascadeType.PERSIST })
   private Set<User> users = new HashSet<User>(0);

   public Long getId() { return id; }
   public void setId(Long id) { this.id = id; }

   public String getName() { return name; }
   public void setName(String name) { this.name = name; }

   public Set<User> getUsers() { return users; }
   public void setUsers(Set<User> users) { this.users = users; }

   public void addUser(User user) {
      user.setCompany(user);
   }
}

Pros:

  • Less code than the previous example
  • A little more direct in having the developer use it. They can do it either way and it will work

Cons:

  • Still allows developers to go around the addUser() method when adding a user. They can still call setUsers() and getUsers().add()

We could try solving it by doing:

   ... company object code ...
   public Set<User> getUsers() {
      return Collections.unmodifiableSet(users);
   }

   void setUsers(Set<User> users) {
      this.users = users;
   }

   public void addUser(User user) {
      user.setCompany(user);
   }

This would restrict the developer form using company.getUsers().add() and also hide the setUsers() from them.
The issue though is now User.setCompany() cannot call company.getUsers().add() so we have broken the User code.

The Best Solution?

Let me state that I don’t know if I have the best solution for this. This is what I came up with.

@Entity
@Table(name="USER")
public class User {
   @Id
   @Column(name="ID")
   private Long id;

   @Column(name="NAME")
   private String name;

   @ManyToOne(fetch=FetchType.LAZY)
   @JoinColumn(name = "COMPANY_ID", referencedColumnName = "ID", nullable = false)
   private Company company;
   
   public Long getId() { return id; }
   public void setId(Long id) { this.id = id; }

   public String getName() { return name; }
   public void setName(String name) { this.name = name; }

   public Company getCompany() { return company; }
   public void setCompany(Company company) { 
      this.company = company 
      company.addUser(this);
   }
}

@Entity
@Table(name="COMPANY")
public class Company {
   @Id
   @Column(name="ID")
   private Long id;

   @Column(name="NAME")
   private String name;

   @OneToMany(fetch = FetchType.LAZY, mappedBy = "company", 
          cascade = { CascadeType.MERGE, CascadeType.PERSIST })
   private Set<User> users = new HashSet<User>(0);

   public Long getId() { return id; }
   public void setId(Long id) { this.id = id; }

   public String getName() { return name; }
   public void setName(String name) { this.name = name; }

   public Set<User> getUsers() { 
      return Collections.unmodifiableSet(users); 
   }
   void setUsers(Set<User> users) { this.users = users; }

   void addUser(User user) {
      users.add(user);
   }

Pros:

  • This limits the developer to setting the Company/User relationship in only one way that we can control and set the relationship with
  • Less code than the first example, more controlled than the second

Cons:

  • Developers may be confused that they can only set the Company on the user object

So, it sounds like a simple thing but really I had a put a bit of thought into it. The main issue is that you need to control how the developer will do things and keep it constricted so that they are forced down a specific path so that you can manipulate the objects correctly.

If you think, or know, there is a better solution than this, please post it! No one said I was always going to get it right 🙂

About sseaman

Connect with me on Google+
This entry was posted in Java, Programming and tagged . Bookmark the permalink.

5 Responses to Setting on a Set()

Leave a Reply

Your email address will not be published. Required fields are marked *