9

Let's say I have a primary user table called UserProfile, and on it there's a display name.

I have various modules that you can be a member of, with a UserProfile and a ModuleId on it to signify your ModuleMembership. You then can have a profile for each different module to store data related to that module, for example if you're signed up for the PokerModule you'll get a PokerProfile.

I'd like to put the display name from UserProfile on the PokerProfile, but I'd like to do it in a normalized manner. I could do it via Hibernate or via SQL, either way works. The exact relationship would be PokerProfile.membership.userProfile.displayName - how can I get this into an @Column on the PokerProfile class?

Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
corsiKa
  • 81,495
  • 25
  • 153
  • 204
  • I must be missing something because it seems to me you can just copy the displayName from UserProfile to ModuleMembership and merge the update. – K.Nicholas Jan 05 '16 at 02:28
  • What about @ManyToOne? It may be an entity association. – crybird Jan 05 '16 at 03:27
  • It might be helpful if you could share the code of your entity definitions. Though from what you describe, I'm uncertain why you can't just add an `@Transient` method on the `PokerProfile` class (like, say `getUserName()`) that does something like `return this.getMembership().getUserProfile().getDisplayName();`. Assuming you've defined all of your relationships properly. There's no reason to use SQL or HQL for something like this, unless the SQL that the ORM layer generates is just plain too slow for your liking. – aroth Jan 05 '16 at 04:29
  • @aroth I'm not certain it would be helpful to do so - the question isn't about this poker example, it's about how to generically get a column that lives in another table into a class. You're right that a Transient method could work, and it's elegant, but it also suffers from the `n+1 problem` if not everything is lazy loaded. To continue the poker example, consider fetching the current state of the poker table - i don't want to fetch the membership and the userprofile (which is quite large!) just to get the display names. The userprofile can be looked up via the display name if necessary. – corsiKa Jan 05 '16 at 05:07
  • @corsiKa - Yes, there can always be optimization concerns. The best solution will depend upon how the data is being used. For instance, derived properties are populated at fetch-time, meaning the associated subquery is _always_ run. That may be inefficient if you're only occasionally interested in the value. But If you almost always want it, it's a different story. Though for the players in a game, I might use a dedicated query, like `select displayName from players where id in (select playerId from gamePlayers where gameId = ?)`. But like you said, it's a contrived example anyways. – aroth Jan 05 '16 at 05:15

3 Answers3

9

You can use a derived property to fetch the displayName in your PokerProfile class, as follows:

@Formula(
    "(SELECT up.displayName"
        + " FROM ModuleMembership mm"
        + " JOIN UserProfile up"
        + " ON (mm.userProfileId = up.userProfileId)"
        + " WHERE mm.moduleMembershipId = membershipId)")
String displayName;

Note that the derived property uses SQL only, not HQL. membershipId is defined as the @JoinColumn name of the membership property. Other column names are similarly defined.

However, although it's not what you asked for exactly, I usually implement my own shortcut properties in Java for things like this, as follows:

@Transient
public String getDisplayName() {
    if (membership == null) {
        return null;
    }
    UserProfile userProfile = membership.getUserProfile();
    return userProfile == null ? null : userProfile.getDisplayName();
}

For me, shortcut properties are easier to both read and write than the corresponding SQL.

Code samples were tested to work using Hibernate 5.0.6.Final against an H2 database version 1.4.190.

Community
  • 1
  • 1
heenenee
  • 19,914
  • 1
  • 60
  • 86
  • If `getDisplayName()` is meant to be placed inside of an entity definition, don't you want to throw an `@Transient` in front of it so that Hibernate won't complain when it sees there's no 'displayName' field in the backing table (or worse, try to _create_ that field if you happen to have auto schema updates turned on)? – aroth Jan 05 '16 at 04:31
  • @aroth You may be right, but I'm not sure if it's necessary in Hibernate 5. I'm not getting any warnings when I run my program, there's no extra column created, and I'm not seeing anything explicit in the Hibernate 5 docs. – heenenee Jan 05 '16 at 04:40
  • @aroth I added it because I felt it was more clear regardless. I still don't know if it's required or not. It could be worth another stackoverflow question. Thanks for the suggestion! – heenenee Jan 05 '16 at 04:52
  • 2
    I like the elegance of your transient method, but this requires the objects to be loaded, and those often won't be necessary (for example, in the contrived poker example, viewing all the people at your table shouldn't need to fetch all those records to get the display name...) Your first suggestion, while not as elegant, appears to do the job done. I'll see about testing this tomorrow to see if it performs as expected, and obviously an HQL example is *preferred* but not required. I greatly appreciate your answer and I'll let you know how it shook out! – corsiKa Jan 05 '16 at 05:03
  • @heenenee - Could be right about Hibernate 5. I haven't tried with anything past 4.3.6 (which definitely complains if the `@Transient` isn't there). – aroth Jan 05 '16 at 05:07
  • 1
    In hibernate 3 & 4, the annotations are taken from either the fields or the getters. You cannot mix them. That means that if you annotate fields, you can add a method without great effect. However, if you have annotated your getters and then add a transient getter (even without a field) then hibernate will expect a matching DB column. Short answer: if you have annotated the getters, you need a @Transient annotation. If you have annotated your fields, you don't. – Nathan Jan 10 '16 at 07:48
2

If we think of the given domain in terms of object-oriented/relational way, we can conclude the following relationship:

PokerProfile inherits UserProfile

You can achieve the same relationship with JPA and DBMS with the help of "One Table Per Subclass Inheritance" and Foreign Key relationships respectively.

So, you can define something like below:

UserProfile.java

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;


@Entity
@Table(name = "TBL_USER_PROFILE")
@Inheritance(strategy=InheritanceType.JOINED)
public class UserProfile {

    @Id
    @GeneratedValue
    @Column(name = "ID")
    private Long id;

    @Column(name = "DISPLAY_NAME")
    private String displayName;

    public Person() {

    }

    public Person(String displayName) {
        this.displayName = displayName;
    }

    // Getter and Setter methods
}

PokerProfile.java

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@Entity
@Table(name="TBL_POKER_PROFILE")
@PrimaryKeyJoinColumn(name="ID")
public class PokerProfile extends UserProfile {

    // Properties & Getters & Setters here

}

The advantage of this model is that it clearly expresses the desired functionality and of course clearly synced with the parent profile. There is no workaround or tweak required. And under the hood, it uses foreign key relationship between PokerProfile & UserProfile and so no redundancy of data.

James
  • 2,756
  • 17
  • 19
  • Poker profile does not inherit from user profile in any way. If it did inherit, I wouldn't be able to have a userprofile that wanted to play both poker and blackjack without having a different id. I like the outside-the-box thinking, but unfortunately I don't think it applies here. – corsiKa Jan 10 '16 at 02:04
  • So I started thinking about this and I realized I do want PokerProfile to inherit from something, but not UserProfile. That class exists as a global representation of a real person, while their WhateverProfiles represents them choosing to join a specific module. There is no shared behavior between a PokerProfile and a UserProfile, but there would be between a PokerProfile and a BlackjackProfile. So I made an AbstractProfile (as a @MappedSuperclass) for Poker and Blackjack to inherit from, and put the annotation above in it. Again, though, I like the thinking here, it just doesn't fit for me. – corsiKa Jan 11 '16 at 07:42
1

Hibernate Formulas are elegant solution for this, however they may be difficult to get right if they are complex (Hibernate has limited ability to parse native SQL). Also, even if you get them to work properly, the performance may suffer because formulas may involve subqueries which are always embedded into the final SQL.

The approach I use often is to create a database view which does the necessary joins for me and then I map my entity to the view. The database is able to optimize the view much better then nested subqueries, and Java code is a bit cleaner because there is no native SQL in it.

So, you could create a db view Membership_Profile and a reusable entity mapped to it, say MembershipProfile.

@Entity
@Table(name = Membership_Profile)
class MembershipProfile {
   Long moduleMembershipId;
   String displayName;
   // Other properties if needed
}

Then, you can associate this entity with specific profiles, for example:

@Entity
class PokerProfile {
   @OneToOne(fetch=FetchType.LAZY)
   @JoinColumn(name = "membershipId")
   MembershipProfile membershipProfile;
   ...
}

The benefit of this approach is that you load the displayName (and any other MembershipProfile properties) lazily when you need them (thus avoiding the cost of executing the view or subqueries).

An alternative to creating database views are Hibernate Subselects which are a kind of Hibernate (read-only) views, but I expect real db views to perform much better.

Community
  • 1
  • 1
Dragan Bozanovic
  • 23,102
  • 5
  • 43
  • 110
  • This would have the benefit of being very low level - Hibernate wouldn't know the difference, and I could leverage full optimization on the SQL side for whatever flavor I'm running. In fact, I don't even need to put it on the membership profile - it could stay on the user profile like it is now, have a poker profile without the display name, and have a view that wraps the two. – corsiKa Jan 05 '16 at 15:57
  • @corsiKa True, you can wrap in the view anything that best suits your use cases and your performance requirements. The disadvantages of defining the view on `PokerProfile` is that it is always going to execute when you load `PokerProfile` instances and that you would have to define a new view for each other `*Profile` use cases (for example `BlackjackProfile`, etc). – Dragan Bozanovic Jan 05 '16 at 16:10