2010-02-12 97 views
0

學術討論會(縮短) -重複的關鍵問題 - 需要援助

我有一個加載從相應的DAO一個配置文件對象的控制器。它更新了一些屬性,其中許多屬性集,然後調用saveOrUpdate(通過保存在DAO中)來重新附加和更新配置文件對象。以看似隨機的間隔,我們得到一個org.hibernate.exception.ConstraintViolationException,其根本原因是:由於:java.sql.BatchUpdateException:對於鍵1重複條目'3-56'。堆棧跟蹤指向調用的saveOrUpdate方法從配置文件更新控制器。我不能在我的測試環境中複製,我們只在生產環境中看到這一點,所以我想知道是否缺少與線程安全相關的東西(這就是爲什麼我發佈了太多代碼/配置信息)。有任何想法嗎?

- 代碼 -

我試圖提供儘可能多的相關配置/代碼可能 - 讓我知道,如果需要更多:

這裏是有問題的控制器的摘錄:

public class EditProfileController extends SimpleFormController { 

protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception 
{ 
    if(!checkLoggedIn(request)) 
    { 
     return new ModelAndView("redirect:" + invalidRedirect); 
    } 

    HttpSession session = request.getSession(); 
    Resource resource = (Resource)session.getAttribute("resource"); //The resource object is stored in session upon login and upon account creation. 
    Profile profile = profiles.getProfileByResource(resource); 

    if(profile == null) 
    { 
     profile = new Profile(); 
     profile.setResource(resource); 
    } 

    //I use custom editors to populate the sets in the command object with objects based on the selection 

    if(profile.getPrimaryRoleSkills() != null && editProfileCommand.getPrimaryRoleSkills() != null) 
    { 
     profile.getPrimaryRoleSkills().addAll(editProfileCommand.getPrimaryRoleSkills()); 
     profile.getPrimaryRoleSkills().retainAll(editProfileCommand.getPrimaryRoleSkills()); 
    } 
    else 
     profile.setPrimaryRoleSkills(editProfileCommand.getPrimaryRoleSkills()); 

    profiles.save(profile); //This is the line that appears in the stack trace 
    return new ModelAndView(getSuccessView()); 
} 
//Other methods omitted 
} 

縮Profile類:

public class Profile implements java.io.Serializable { 

private long id; 
private Resource resource; 
private Set<PrimaryRoleSkill> primaryRoleSkills = new HashSet<PrimaryRoleSkill>(0); 

public Profile() { 
} 
//Other properties trivial or similar to above. Getters and setters omitted 
//toString, equals, and hashCode are all generated by hbm2java 
} 

namevaluepairs中淺E類(PrimaryRoleSkill擴展了這種不添加任何東西):

public class NameValuePairs implements java.io.Serializable { 
private long id; 
private String name; 
private boolean active = true; 

public NameValuePairs() { 
} 
//equals and hashCode generated by hbm2java, getters & setters omitted 
} 

這裏是我的DAO基類:

public class DAO { 

protected DAO() { 
} 

public static Session getSession() { 
     Session session = (Session) DAO.session.get(); 
     if (session == null) { 
     session = sessionFactory.openSession(); 
     DAO.session.set(session); 
     } 
     return session; 
} 

protected void begin() { 
    getSession().beginTransaction(); 
} 

protected void commit() { 
    getSession().getTransaction().commit(); 
} 

protected void rollback() { 
    try { 
    getSession().getTransaction().rollback(); 
    } catch(HibernateException e) { 
    log.log(Level.WARNING,"Cannot rollback",e); 
    } 

    try { 
    getSession().close(); 
    } catch(HibernateException e) { 
    log.log(Level.WARNING,"Cannot close",e); 
    } 
    DAO.session.set(null); 
} 

public boolean save(Object object) 
{ 
    try { 
     begin(); 
     getSession().saveOrUpdate(object); 
     commit(); 
     return true; 
    } 
    catch (HibernateException e) { 
     log.log(Level.WARNING,"Cannot save",e); 
     rollback(); 
     return false; 
    } 
} 

private static final ThreadLocal<Session> session = new ThreadLocal<Session>(); 

private static final SessionFactory sessionFactory = new Configuration() 
    .configure().buildSessionFactory(); 
private static final Logger log = Logger.getAnonymousLogger(); 

//Non-related methods omitted. 
} 

下面是簡介DAO的重要組成部分:

public class Profiles extends DAO { 
public Profile getProfileByResource(Resource resource) 
{ 
    try 
    { 
     begin(); 
     Query q = getSession().createQuery("from Profile where resource = :resource"); 
     q.setLong("resource", resource.getId()); 
     commit(); 
     if(q.uniqueResult() == null) 
      return null; 

     return (Profile) q.uniqueResult(); 
    } 
    catch(HibernateException e) 
    { 
     rollback(); 
    } 
    return null; 
} 
//Non-related methods omitted. 
} 

相關彈簧配置:

<bean id="profiles" class="com.xxxx.dao.Profiles" /> 

<bean id="editProfileController" class="com.xxxx.controllers.EditProfileController"> 
    <property name="sessionForm" value="false" /> 
    <property name="commandName" value="editProfileCommand" /> 
    <property name="commandClass" value="com.xxxx.commands.EditProfileCommand" /> 

    <property name="profiles" ref="profiles" />  

    <property name="formView" value="EditProfile" /> 
    <property name="successView" value="redirect:/profile" /> 
    <property name="validator" ref="profileValidator" /> 
</bean> 

的hibernate.cfg.xml

<session-factory> 
    <property name="connection.driver_class">@[email protected]</property> 
    <property name="connection.url">@[email protected]</property> 
    <property name="connection.username">@[email protected]</property> 
    <property name="connection.password">@[email protected]</property> 
    <property name="dialect">org.hibernate.dialect.MySQLInnoDBDialect</property> 
    <property name="dbcp.maxActive">15</property> 
    <property name="dbcp.maxIdle">5</property> 
    <property name="dbcp.maxWait">120000</property> 
    <property name="dbcp.whenExhaustedAction">2</property> 
    <property name="dbcp.testOnBorrow">true</property> 
    <property name="dbcp.testOnReturn">true</property> 
    <property name="dbcp.validationQuery"> 
     select 1 
    </property> 
    <property name="dbcp.ps.maxActive">0</property> 
    <property name="dbcp.ps.maxIdle">0</property> 
    <property name="dbcp.ps.maxWait">-1</property> 
    <property name="dbcp.ps.whenExhaustedAction">2</property> 

    <!-- Echo all executed SQL to stdout 
    <property name="show_sql">true</property> 
    --> 

    <mapping resource="com/xxxx/entity/Resource.hbm.xml"/> 
    <mapping resource="com/xxxx/entity/Authentication.hbm.xml"/> 
    <mapping resource="com/xxxx/entity/NameValuePairs.hbm.xml"/> 
    <mapping resource="com/xxxx/entity/Profile.hbm.xml"/> 
    <mapping resource="com/xxxx/entity/FileData.hbm.xml"/> 
</session-factory> 

從Profile.hbm.xml摘錄:從NameValuePairs.hbm.xml

<hibernate-mapping> 
<class name="com.xxxx.entity.Profile" select-before-update="true"> 
<id name="id" type="long"> 
     <generator class="foreign"> 
      <param name="property">resource</param> 
     </generator> 
</id> 

<set name="primaryRoleSkills" cascade="none"> 
    <key column="profile"/> 
    <many-to-many column="primary_role_skill" class="com.xxxx.entity.PrimaryRoleSkill"/> 
</set> 
</class> 
</hibernate-mapping> 

摘錄:

<hibernate-mapping> 
<class name="com.xxxx.entity.NameValuePairs" abstract="true"> 
    <id name="id" type="long"> 
    <generator class="native" /> 
</id> 
<discriminator column="type" type="string" /> 
    <property type="string" name="name" length="256"> 
     <meta attribute="use-in-equals">true</meta> 
    </property> 
    <property type="boolean" name="active"> 
     <meta attribute="default-value">true</meta> 
    </property> 
    <subclass name="com.xxxx.entity.PrimaryRoleSkill" discriminator-value="PrimaryRoleSkill" /> 
    </class> 
</hibernate-mapping> 

中的應用在Tomcat 6.0.14上運行,並連接到在Linux上運行的MySQL版本5.0.89社區。我們使用Hibernate 3.3.2和Spring Framework 2.5.6。

+0

嘗試只包括相關信息。它是太長的閱讀.. – Bozho

+0

請參閱http://sscce.org/ – Bozho

+1

你的DAO不應該啓動/提交/管理事務 - 業務對象(服務)應該。 – les2

回答

1

經過10天免除異常後,我得出結論,我發現的解決方案已經奏效。

簡短回答:我將我的DAO切換到使用HibernateTemplate,並使用Spring AOP來處理事務。這涉及很多重寫,但它是值得的,因爲解決方案現在按預期工作。此外,我無法在我的JSP視圖中使用惰性加載工作,但這並不是什麼大問題,因爲我的對象相當小(我在Hibernate配置中禁用了惰性加載屬性)

說明:問題在於我獲得Hibernate Session的方式。通過原來的實現,在應用程序啓動時爲每個擴展DAO基類的DAO創建一個Hibernate會話。這造成了兩個問題。 1)Hibernate會話本身不是線程安全的。這就是爲什麼在測試實例中所有測試都可以通過一個用戶進行測試,但在生產中卻有不尋常的行爲2)MySQL喜歡在一段時間後關閉連接。由於會議持續不斷,這導致管道破損(沒有在OP報告,我認爲這是一個單獨的問題)。通過這個修復,Spring現在管理會話創建/關閉,Spring AOP處理事務標識,而SpringTemplate甚至可以處理大部分Hibernate訪問。

1

我沒有立即回答 - 它可能是多線程條件,或者可能是因爲在測試環境中沒有使用正確的輸入數據來觸發問題。如果這是我的代碼,我將開始比較equals(),compareTo()和hashCode()實現與數據庫定義 - 如果這些方法比數據庫比唯一性需要的字段更多的字段,hibernate可能會考慮即使最終使用數據庫中的相同密鑰,兩個對象也會有所不同。

我會考慮的另一種方法是將日誌記錄添加到保存和檢索這些對象的所有位置,包括堆棧跟蹤(顯然帶有開/關開關)。或者,當您得到重複密鑰錯誤時,請查詢數據庫並記錄那裏已有的內容。無論哪種方式,你想找出'第一'的記錄來自哪裏。

+0

感謝您的回覆!我再看看由hbm2java生成的equals和hashcode方法。它只包含「名稱」字段,這是業務密鑰。我一定會查看日誌。 –