views:

397

answers:

3

I have a Message object associated with a User object (user_from and user_to). I persist the Message instances into a database using Hibernate (with JPA annotations) and the user_id is persisted.

The User object is also stored to the database, but when fetching messages I would like the User to be fetched from a Map in memory instead of from the database.

The reason is because I have some transient attributes that I can't persist to the database (Facebook data), and when the Facebook data is already loaded in memory, I don't want to re-query Facebook for the data.

Is this possible or should be done by creating a UserType? Which class needs to be defined as the UserType, the Message, the User or a custom mapper? If a custom mapper, how do I associate the mapper using JPA annotations (I have seen an example that uses configurations and sets meta-type="com.example.hibernate.customtype.CustomerTypeMapper")?

Thank you very much for your help!

User class:

@Entity(name="com.company.model.user")
@Table(name = "user")
public class User {
    private Long id;
    private Long fbId;
    private String firstName;
    private URL picThumbnailUrl;
    //...

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Long getFbId() {
        return fbId;
    }
    public void setFbId(Long fbId) {
        this.fbId = fbId;
    }
    @Transient @FacebookField
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    @Transient @FacebookField
    public URL getPicThumbnailUrl() {
        return picThumbnailUrl;
    }
    public void setPicThumbnailUrl(URL picThumbnailUrl) {
        this.picThumbnailUrl = picThumbnailUrl;
    }
    //....
}

Message class:

@Entity(name="com.company.model.message")
@Table(name = "message")
public class Message {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @ManyToOne
    @JoinColumn(name="user_id_from")
    private User from;
    @ManyToOne
    @JoinColumn(name="user_id_to")
    private User to;
    private String text;
    //...

    public Long getId() {
     return id;
    }
    public void setId(Long id) {
     this.id = id;
    }
    public User getFrom() {
     return from;
    }
    public void setFrom(User from) {
     this.from = from;
    }
    public User getTo() {
     return to;
    }
    public void setTo(User to) {
     this.to = to;
    }
    public String getText() {
     return text;
    }
    public void setText(String text) {
     this.text = text;
    }
    //....
}
A: 

I don't think user type would help in this case. A User Type is more for specifying how data gets saved in the database not if.

Probably the easiest solution would be to mark your field as transient, and let the getter fetch this data from some service which in turn would check an internal cache and in case of a cache miss actually query facebook.

I you really want to handle it like a persisted field you should check out the hibernate event model, to find the events that you need to implement in order to plug your logic in. But this would be more use full if you want to implement a more or less complete persistence solution that persists in something different then DBs or XML files.

Jens Schauder
A: 

Using the principle of simplicity, I would start with suggesting the following:

While persisting Users as needed, you can certainly query for Messages without loading Users entities. What I would do would have UserId fields ("to" and "from" if I understand correctly), that are typed Long. So the information saying what user is related is there.

Now, to obtain the actual User entity:

  • You could make an alternate getter (transient, non mapped for Hibernate) that would provide the User instance by looking into your Map.

It is typical to restrict domain model objects (a persistent entity here) from accessing to services and other codes. Therefore, either you make the map part of your model (it could be statically stored in a model class).

  • Or, you let other codes (Services etc) use the UserId to obtain the User instance via the UserId, by calling the appropriate Service (that encapsulates the Map).
KLE
+1  A: 

Use an EntityListener or a @PostLoad callback method:

@PostLoad
public void updateFacebookFields() {
  // get stuff from HashMap
  setFirstName("whatever");
  setPicThumbnailUrl(myUrl);
}

Update (based on comments below):

I can understand you wanting to load instance from memory; I don't understand why you'd want Hibernate to do it :-) It all really comes down to a simple choice - either your User entity has at least some fields you'd want to always persist to / load from the database or it doesn't. I take it we're talking about the latter scenario because the former is best served by the solution I've proposed above. Your choices then boil down to:

  1. Not persisting User at all. Any entity referencing it would persist only user identifier; you will retrieve the actual user instance from your cache in appropriate getter method.

  2. You can write a custom type that would automate the above for you. That may make sense if you have lots of entities referencing users (you'll avoid repeated code). You'll need to implement UserType interface; you'll retrieve your User instance from your cache from within nullSafeGet() method based on identifier you get from passed in ResultSet; you'll do the opposite in nullSageSet() and persist it back.

ChssPly76
Thank you. I store a map of all User objects. Your solution will get the attributes, but will still create another instance of User. I prefer to to use the same instance of User that's already in memory.
Perhaps I've misunderstood your question. If you have transient **attributes** that you want to set on User instance when it's loaded, the above solution is the best. If you're trying to replace loaded instance with **transient** instance already in memory, why are you loading it in the first place? Why is it even mapped? Change your Message to map user_id_from and user_id_to as strings (or whatever they are) and change getFrom() / getTo() accessors to get User instances from the map.
ChssPly76
Hi! Thanks for your response. I do want Hibernate to load the whole INSTANCE from memory instead of from database. The Users are loaded in a map because they're placed on a spatial map (i.e. GEO data)
I've updated my answer - too much to fit in here
ChssPly76
Thanks very much. If I implement UserType, do you think the User class should implement it, the Message class, or a custom mapper class and if a mapper, how do I associate objects with it?
A separate class. `User` is what that custom type going to persist / load. `Message` (or any other class that needs `User` reference) can use `@Type` annotation (http://docs.jboss.org/hibernate/stable/annotations/reference/en/html/entity.html#d0e2282) to specify that custom type (note that this is a HIbernate extension; I'm not sure if there's a way to do this in pure JPA), e.g. `@Type(type="my.custom.UserType) private User from`
ChssPly76