views:

71

answers:

2

I need some help with a design problem. I'm having trouble wrapping my head around how to design an object (or maybe multiple objects) so that I can query the way I would like to.

Let me start by showing a sql query that returns exactly the information I want:

select distinct rights_table.receiver_guid, user_info.name
from rights_table 
inner join user_info
on rights_table.receiver_guid = user_info.guid
where rights_table.giver_guid = '_this_is_the_argument_'

So user_info has one entry for each user. rights_table has info about rights that giver_guid has delegated to receiver_guid. I don't care what the rights are right now - I just want to be able to take a giver_guid and get back a list of all the guids and names of anyone he has given rights to. I'm having trouble conceptualizing how to translate these tables to an object model in such a way that I can perform that query.

If it helps to know, receiver_guid and giver_guid are both (could both be) many-to-one mappings to user_info.guid. user_info.guid is the pk on that table; the pk on rights_table is another column not mentioned here.

Edit: My solution:

As Tim suggested, I put many-to-one mappings on the rights table "object" that linked the rights table to the User table. It took me a while to understand that Hibernate does joins implicity for you when you do mappings like this, so here is what I basically ended up with for HQL:

String hqlQuery =           
    "select right.recipient_guid, user.name " +
    "from rightsTable right, userInfo user " +
    "where right.recipient_guid = user.guid and right.giver_guid like :giver_guid";

Honestly, I don't even know if there are joins happening behind the scene there, but I know that this gives me the information I originally wanted. If anyone has suggestions for how to do this query more efficiently please let me know.

+1  A: 

If it helps to know, receiver_guid and giver_guid are both (could both be) many-to-one mappings to user_info.guid. user_info.guid is the pk on that table; the pk on rights_table is another column not mentioned here.

I would try out the following:

  • A user has a one-to-many relationship "rightsGiven" to the right entity.
  • A user has a one-to-many relationship "rightsReceived" to the right entity.
  • A right has a one-to-one relationship "givenBy" to a user.
  • A right has a one-to-one relationship "givenTo" to a user.

To get the list of users who obtained some rights by user X, then

  • load user X
  • traverse the collection "righstGiven" of rights
  • for each right get the "givenTo" user

Once you have on object loaded, when you traverse the graph, hibernate will load the object lazily for you. This can result in many queries being executed under the hood by hibernate, especially the Select N+1 problem. Use then eager fetching, so that hibernate uses then a join. E.g. @OneToMany(fetch = FetchType.EAGER). Set eager fetching on "rightsGiven" and "givenTo".

You can then control the so-called max fetch depth to ensure hibernate won't load the whole graph eagerly. In this case we have 2 relationship that need to be fetched eagerly (rightsGiven + givenTo), so set it to 2. This should result in query with two outer join.

ewernli
To be specific: say I have User X - in Java the field "rightsGiven" would be a Set that I mapped to the other table, right? So if I traverse across that Set, how is each individual value linked to the "givenTo" user? Or are you saying I would do another query at that point?
Nathan Spears
Hibernate loads the object lazily for you by default. But I hope you can configure hibernate with eager fetching in this case and have all the information you want loaded with 1 query.
ewernli
ewernli is saying the same thing as Tim's answer, but I didn't grok that until I understood mappings better. I assume the answer to my question in the comment is that as I traverse across the Set, the one-to-one you suggested would make it possible for me to use .givenTo() on each object. Both of these answers would have benefited from a little pseudocode but they got me where I wanted to be eventually.
Nathan Spears
+1  A: 

Trying to map queries and tables to objects is hard because the conceptual models differ so much. Putting your SQL statement aside and just working from the data you want to extrapolate, I arrive at the following Objects:

  • User
    (id, name, Set< RightsAssignment> rights_given, Set< RightsAssignment> rights_recieved, info, ...)
  • Right
    (id, name, level, Set< RightsAssignment> assignments, info, ...)
  • RightsAssingment
    (id, giver_id, reciever_id, right_id, date, info...)

This way RightsAssignment (pardon the name, change it to something that fits) is an assignment of a right by a user to another user, with optional additional information like the assignment.date field above (you might not need it now, but it makes your models more extendable).
The fields in RightsAssignment would have a ManyToOne's on giver, reciever and right, where as User and Right will have OneToMany's mapped by the fields in RightsAssignment.

To get a list of rights assigned by any one user, you just have to query the RightsAssignment table for entries where the giver_id matches the user.id.

Hope this helps!

Tim
+1 for the idea to introduce another table.
ewernli
I have used a many-to-one in the ways that Tim suggested. I haven't needed to use any one-to-many mappings yet, and I haven't used Sets at all, but this suggestion was good enough to deserve the answer.
Nathan Spears
Tim
I got your suggestion implemented. The thing that it lacks compared to sql and hql queries is I can't specify the distinct constraint. So if I call .getRightsGiven() and then loop through the results, if I have given the same rights to multiple users then I have dupes. I have an improved model vs. hql, but now I have to sort the results. Any comments? I guess I should do some timing tests to see which is faster.
Nathan Spears
For the duplicates: Have a look at the full Criteria API, there's a lot you can accomplish with it, and even using only annotations (@Where for instance).As for the sorting you might want to implement your givenRights collection as a SortedList/SortedSet.. Hibernate will then sort them before returning them. (If Comparable<T> is implemented correctly)
Tim