-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 
Author Message
 Post subject: How to avoid race condition in own findOrCreate() method?
PostPosted: Thu Dec 14, 2006 2:51 pm 
Beginner
Beginner

Joined: Thu Apr 13, 2006 12:56 pm
Posts: 23
Hi,

I'd appreciate guidance on how to write a safe findOrCreate method. I'd like to write a method that, given a natural

key:
-returns POJO representing a row (if one exists), or
-creates and inserts a new row and returns the object.

The constraints are:
-app is running on multiple appservers (thus making method 'syncrhonized' won't help)
-app logic allows for multiple transactions to attempt this method w/same argument at the same time
-DB is running under REPEATABLE_READ TX iso level

Here's what I have so far:

Code:
/**
*
* @author nikita
*
* @hibernate.class table="clients" dynamic-update="true" dynamic-insert="true"
*/
public class Client extends Persistent{
   
    private String name;
   
    /** Creates a new instance of Client */
    public Client() {
    }
   
    /**
     * guaranteed to return a Client object with given name:
     * -if a row w/ name = aName exist return it
     * -if such row does not exist, insert one and return it
     */
    public static Client findOrCreate(String aName) {
        Client c = null;
       
        //get a matching row from DB
        String hql = "select * from Client as client where client.name = ?";
        Query q = HibernateUtil.getCurrentSession().createQuery(hql);
        q.setParameter(0, aName);
        c = (Client) q.uniqueResult();
       
        //if row does not exist, create & insert one
        if(c == null) {
            c = new Client();
            c.setName(aName);
            HibernateUtil.getCurrentSession().persist(c); //may result in constraint violation exception!!!
            //PROBLEM: what if another DB tx does an INSERT after this TX did a SELECT,
            //but before this TX could commit an INSERT?
        }       
       
        return c;
    }
   
    /**
     * @hibernate.property unique="true" not-null="true"
     */
    public String getName() {
        return name;
    }
   
    public void setName(String name) {
        this.name = name;
    }
   
}

abstract public class Persistent implements Serializable{
    private String id = UUID.randomUUID().toString();
   
    transient Logger logger = Logger.getLogger(Persistent.class.getName());
   
    private Integer hibernateVersion;
    private Date creationDate = new Date();;
   
    /**
     * @hibernate.id  generator-class="assigned"
     */
    public String getId() {
        return id;
    }
   
    void setId(String id) {
        this.id = id;
    }
   
    Logger getLogger() {
        return this.logger;
    }
   
    public String toString() {
        String s = "";
       
        s += this.getClass().getName();
        s += ": ";
        s += "id="+this.getId();
        s +=" ";
        return s;
    }
   
   
    /**
     *do NOT manipulate directly
     *
     * @hibernate.version unsaved-value="null"
     */
    public Integer getHibernateVersion() {
        return hibernateVersion;
    }
   
    private void setHibernateVersion(Integer hibernateVersion) {
        this.hibernateVersion = hibernateVersion;
    }
   
    /**
     * @hibernate.property
     */
    public Date getCreationDate() {
        return creationDate;
    }
   
    public void setCreationDate(Date creationDate) {
        this.creationDate = creationDate;
    }
   
    /**
     * equal if IDs are equal
     */
    public boolean equals(Object obj) {
        if(this == obj) {
            return true;
        }
       
        if(! (obj instanceof Persistent)){
            return false;
        }
       
        final Persistent other = (Persistent)obj;
        return this.getId().equals(other.getId());
    }
   
    /**
     * accompanying equals()
     */
    public int hashCode() {
        return this.getId().hashCode();
    }
   



Problem:
Given two DB transactions T1, and T2 and the same natural key, the following race condition is possible:

Code:
T1: BEGIN TX;

T2: BEGIN TX;

T1: SELECT w/ name = "joe"; //returns nothing

T2: SELECT w/ name = "joe"; //returns nothing

T1: INSERT w/ name = "joe";
T1: COMMIT; //row inserted

T2: INSERT w/ name = "joe";
T2: COMMIT; //constraint violation



What I really need is "Insert if doesn't exist, but if exists then return that" but I am unsure on how to express that in Java (or even SQL for that matter) layer.

thanks
-nikita



Hibernate version:
3.1.3

Mapping documents:

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

<hibernate-mapping>
  <class dynamic-update="true" table="clients" name="net.eviltwinstudios.common.beans.Client" dynamic-insert="true">
    <id name="id">
      <generator class="assigned"/>
    </id>
    <version unsaved-value="null" name="hibernateVersion"/>
    <property name="name" not-null="true" unique="true"/>
    <property name="creationDate"/>
  </class>
</hibernate-mapping>


Name and version of the database you are using:

MySQL 5.0
REPEATABLE_READ


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.