Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate saveOrUpdate() tries to save when it should update

Tags:

java

hibernate

I have a Hibernate entity called IssueParticipant. It basically describes the relationship between a user and an issue (which is like a JIRA or Bugzilla issue). It represents a sort of many-to-many linking table in the database, linking a user ID to an issue ID, but also includes other information related to notification settings, and so it is treated as its own entity.

I was having huge problems with using the userId and issueId as a composite key, so I created a synthethic key which is a String (and a varchar in the postgres database), which is formed as: _.

Now, I have a screen where a user can edit all of the users associated with an issue, while also editing the notification settings. In a controller class I create a List of IssueParticipants like this:

IssueParticipant participant = new IssueParticipant();
participant.setUser(accountUser);
participant.setIssue(issue);

So these are of course not managed by Hibernate at this point.

Then in my DAO I iterate through them and call saveOrUpdate(), expecting that if an IssueParticipant with the same synthetic key exists in the database, it will updated; otherwise it will be inserted:

    for (IssueParticipant participant : participants) {
        getCurrentSession().saveOrUpdate(participant);
        savedIds.add(participant.getIssueUserKey());
    }

(savedIds is a List I am maintaining so that I later will know what IssueParticipants I should delete from the database).

Instead of what I expect, though, I get an Exception:

org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "issue_participant_pkey"

Here is my entity class, abbreviated:

public class IssueParticipant extends Entity {

    private String issueUserKey;
    private Long issueId;
    private Long userId;

     // Edit: adding 'dateAdded' definition
    private Date dateAdded;
// ...

    // below may be null
    private SPUser user;
    private Issue issue;

    public static IssueParticipant nulledIssueParticipant() {
        IssueParticipant ip = new IssueParticipant();
        return ip;
    }
    public String getIssueUserKey() {
        return issueUserKey;
    }

    public void setIssueUserKey(String issueUserKey) {
        this.issueUserKey = issueUserKey;
    }

    public Long getId() {
        // currently meaningless
        return 0L;
    }

    public Long getIssueId() {
        return this.issueId;
    }

    public void setIssueId(Long issueId) {
        this.issueId = issueId;
        updateKey();
    }

    public Long getUserId() {
        return this.userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
        updateKey();
    }

    private void updateKey() {
        issueUserKey = getIssueId() + KEY_SEP + getUserId();
    }

    public SPUser getUser() {
        return user;
    }

    public void setUser(SPUser user) {
        this.user = user;
        setUserId(user.getId());
    }

    public Issue getIssue() {
        return issue;
    }

    public void setIssue(Issue issue) {
        this.issue = issue;
        setIssueId(issue.getId());
    }

// edit: adding 'dateAdded' methods
public Date getDateAdded() {
    return dateAdded;
}

public void setDateAdded(Date dateAdded) {
    this.dateAdded = dateAdded;
}

...

}

Here is its hbm file:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping default-lazy="false">
    <class name="com.xxx.yyy.IssueParticipant" table="issue_participant">
        <id name="issueUserKey" column="issue_user_key" type="string">
            <generator class="assigned"/>
        </id> 
        <version name="dateAdded" column="date_added" type="timestamp" unsaved-value="null" />
        <property name="issueId" column="issue_id" />
        <many-to-one name="user" column="user_id" class="com.xxx.yyy.SPUser" not-null="true" cascade="none" />
        <property name="alertRss" column="alert_rss" type="boolean" />
        <property name="alertEmail" column="alert_email" type="boolean" />
        <property name="alertWeb" column="alert_web" type="boolean" />
        <property name="alertClient" column="alert_client" type="boolean" />

    </class>
</hibernate-mapping>

And indeed user_issue_key is the primary key in the corresponding database table.

I feel like the right solution might just be to use SpringJDBC in this case, but I'd really love to figure out what's going on here. Anyone have any thoughts? Thanks in advance.

like image 713
Dave Taubler Avatar asked Jun 06 '12 15:06

Dave Taubler


People also ask

How does hibernate know when to update?

saveOrUpdate() Hibernate will check if the object is transient (it has no identifier property) and if so it will make it persistent by generating it the identifier and assigning it to session. If the object has an identifier already it will perform . update() .

What is the difference between the Save () and saveOrUpdate () method of hibernate?

Difference between save and saveOrUpdate in Hibernate The main difference between save and saveOrUpdate method is that save() generates a new identifier and INSERT record into the database while saveOrUpdate can either INSERT or UPDATE based upon the existence of a record.

What is the difference between Save () and persist () method in hibernate?

The save() method provides an identifier with the intent of an insert query being executed immediately for getting the identifier. It does not matter whether it is outside or inside a transaction. The persist() method fails to execute a given insert query in case it is placed outside transaction boundaries.

What is saveOrUpdate in hibernate?

Hibernate saveOrUpdate results into insert or update queries based on the provided data. If the data is present in the database, update query is executed.


2 Answers

saveOrUpdate() doesn't query the database to decide whether it should save or update the given entity. It makes that decision based on the state of the entity, as follows:

  • if the object is already persistent in this session, do nothing
  • if another object associated with the session has the same identifier, throw an exception
  • if the object has no identifier property, save() it
  • if the object's identifier has the value assigned to a newly instantiated object, save() it
  • if the object is versioned by a <version> or <timestamp>, and the version property value is the same value assigned to a newly instantiated object, save() it
  • otherwise update() the object

So, as far as I understand in your case decision is based on the value of dateAdded field, therefore you need to keep it to distinguish between new and detached instances.

See also:

  • 11.7. Automatic state detection
like image 137
axtavt Avatar answered Oct 24 '22 01:10

axtavt


You can get it to query the database to make this determination if you really want to. Change your identifier mapping to:

<id name="issueUserKey" column="issue_user_key" type="string" unsaved-value="undefined">
    <generator class="assigned"/>
</id> 

Usually that is not the best way though. You do in fact have a <version/> mapping which is usually a better fallback from the identifier for deciding between save/update without the overhead of querying the database. You tried setting it to unsaved-value="null". But you did not show the property mapping for your IssueParticipant.dateAdded property (which is what you try to map as <version/>; you do realize version is incremented every update right?; also, you do know about <timestamp/> instead of <version/> right?). Anyway, that is where your problem is. Could you show the definition of your IssueParticipant.dateAdded property?

like image 2
Steve Ebersole Avatar answered Oct 24 '22 02:10

Steve Ebersole