2016-04-06 14 views
1

は、私が使用するレガシーアプリを持って働いていません。 2 タペストリー5.3.8 MySQLの のTomcat 7.0.64Hibernateは楽観的ロック

これは、複数のユーザーが同時に同じテーブルの行を更新すると、最初の更新を失うことの深刻な問題があります。基本的には、ユーザーAは「レコードを所有したい」と言っていますが、ユーザーBは「レコードを所有したい」と言っています。処理されるコードには少し時間がかかります。そこで、ユーザAはそれを取得し、ユーザBはユーザAがそれを持っていることに気付かず、ユーザBはそれを持っていないはずです。テーブルのエンティティクラスの

@org.hibernate.annotations.Entity(dynamicUpdate = true, optimisticLock = OptimisticLockType.ALL) 

をし、それが決してSQL UPDATE文にテーブルの列を追加していない生産休止状態SQLを見て:

私が使って試してみました。それは単に更新しています... id =?

私はまた、問題のテーブルとEntityクラスにバージョン列を追加しようとした。これは、生成されたSQLで何も使っていない、上記のようにまったく同じ効果を持つ

@Version. 

でフィールドを注釈を付けましたバージョンの列。どちらも増加することはありません。

私はこれを設定することで何かが不足していると推測しているか、アプリが休止状態を使用する方法について何かがあります。

appContext.xml

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> 

    <!-- Configurer that replaces ${...} placeholders with values from a properties 
    file --> 
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 
    <property name="location" value="classpath:app_jdbc.properties"/> 
    </bean> 

    <!-- Message source for this context, loaded from localized files --> 
    <bean id="messageSource" 
    class="org.springframework.context.support.ResourceBundleMessageSource"> 
    <property name="basenames"> 
     <list> 
     <value>app_app</value> 
     <value>app_env</value> 
     <value>pdf</value> 
     </list> 
    </property> 
    </bean> 

    <!-- Define data source --> 
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 
    destroy-method="close"> 
    <property name="driverClassName"> 
     <value>${jdbc.driverClassName}</value> 
    </property> 
    <property name="url"> 
     <value>${jdbc.url}</value> 
    </property> 
    <property name="username"> 
     <value>${jdbc.username}</value> 
    </property> 
    <property name="password"> 
     <value>${jdbc.password}</value> 
    </property> 
    <property name="defaultAutoCommit"> 
     <value>${jdbc.autoCommit}</value> 
    </property> 
    <property name="maxActive"> 
     <value>${dbcp.maxActive}</value> 
    </property> 
    <property name="maxWait"> 
     <value>${dbcp.maxWait}</value> 
    </property> 
    </bean> 

    <!-- Hibernate SessionFactory --> 
    <bean id="sessionFactory" 
    class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> 
    <property name="dataSource" ref="dataSource" /> 
    <property name="annotatedClasses"> 
     <list> 
     ... 
     <value>company.app.domain.Overtime</value> 
     ... 
     </list> 
    </property> 
    <property name="hibernateProperties"> 
     <props> 
     <prop key="hibernate.dialect">${hibernate.dialect}</prop> 
     <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> 
     <prop key="hibernate.query.substitutions">${hibernate.query.substitutions}</prop> 
     </props> 
    </property> 
    </bean> 

    <!-- Transaction manager for a single Hibernate SessionFactory (alternative 
    to JTA) --> 
    <bean id="txManager" 
    class="org.springframework.orm.hibernate3.HibernateTransactionManager"> 
    <property name="sessionFactory"> 
     <ref local="sessionFactory" /> 
    </property> 
    </bean> 

    <!-- regular beans --> 
    <bean id="baseDao" class="vive.db.BaseHbDao"> 
    <property name="sessionFactory"> 
     <ref local="sessionFactory" /> 
    </property> 
    </bean> 
    ... 
    <bean id="overtimeDao" class="company.app.dataaccess.OvertimeDao"> 
    <property name="sessionFactory"> 
     <ref local="sessionFactory" /> 
    </property> 
    </bean> 
    ... 

    <!-- service beans --> 
    <bean id="appService" class="company.app.services.AppService"> 
    <property name="baseDao"><ref local="baseDao"/></property> 
    ... 
    </bean> 

    <!-- transaction advice --> 
    <tx:advice id="txAdvice" transaction-manager="txManager"> 
    <tx:attributes> 
     <tx:method name="get*" read-only="true" /> 
     <tx:method name="*" /> 
    </tx:attributes> 
    </tx:advice> 
    <aop:config> 
    <aop:pointcut id="serviceOperation" 
     expression="execution(* company.app.services.*Service.*(..))" /> 
    <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice" /> 
    </aop:config> 

</beans> 

残業Entityクラス:

package company.app.domain; 

import java.util.Date; 

import javax.persistence.Column; 
import javax.persistence.Entity; 
import javax.persistence.FetchType; 
import javax.persistence.GeneratedValue; 
import javax.persistence.Id; 
import javax.persistence.JoinColumn; 
import javax.persistence.ManyToOne; 
import javax.persistence.Table; 
import javax.persistence.Transient; 

import org.hibernate.annotations.GenericGenerator; 
import org.hibernate.annotations.Parameter; 
import org.hibernate.annotations.OptimisticLockType; 

@Entity 
@Table(name = "over_time") 
@org.hibernate.annotations.Entity(dynamicUpdate = true, optimisticLock = OptimisticLockType.ALL) 
public class Overtime implements java.io.Serializable { 

    private static final long serialVersionUID = 7263309927526074109L; 
    @Id 
    @GeneratedValue(generator = "ot_gen") 
    @GenericGenerator(name = "ot_gen", strategy = "hilo", parameters = { 
     @Parameter(name = "table", value = "unique_key"), @Parameter(name = "column", value = "next_hi"), 
     @Parameter(name = "max_lo", value = "99") }) 
    private Integer id; 

    @Deprecated 
    @Column(name = "from_time") 
    private Date fromTime; 

    @Deprecated 
    @Column(name = "to_time") 
    private Date toTime; 

    @Column(name = "fm_dttm") 
    private Long fromDttm; 

    @Column(name = "to_dttm") 
    private Long toDttm; 

    @Column(name = "post_dttm") 
    private Long postDttm; 

    private String dow; 
    private String shift; 

    @Column(name = "sub_groups") 
    private String subGroups; 

    @Column(name = "created_by") 
    private String createdBy; 

    @Column(name = "signed_up_by") 
    private String signedUpBy; 

    @Column(name = "signed_up_via") 
    private String signedUpVia; 

    @Column(name = "date_signed_up") 
    private Date dateSignedUp; 

    @Column(name = "signed_up_by_partner_username") 
    private String signedUpByPartnerUsername; 

    @Column(name = "signed_up_by_partner_ot_refno") 
    private String signedUpByPartnerOtRefNo; 

    private String comment; 
    private Integer status; 

    @Column(name = "title_abbrev") 
    private String titleAbbrev; 

    @Column(name = "record_status") 
    private String recordStatus; 

    @Column(name = "ref_no") 
    private String refNo; 

    @Column(name = "ref_id") 
    private String refId; 

    @Column(name = "misc_notes") 
    private String miscNotes; 

    @Column(name = "sends_notif_upon_posting") 
    private Boolean sendsNotificationUponPosting; 

    @Column(name = "notify_post_person_when_filled") 
    private Boolean notifyPostPersonWhenFilled; 

    @Column(name = "notify_others_when_filled") 
    private Boolean notifyOthersWhenFilled; 

    @Column(name = "vehicle_needed") 
    private Boolean vehicleNeeded; 

    @Column(name = "agency_id") 
    private Integer agencyId; 

    @Column(name = "schedule_id") 
    private Integer scheduleId; 

    @Column(name = "post_date") 
    private Date postDate; 

    @Column(name = "enrollment_opens_at") 
    private Date enrollmentOpensAt; 

    @Column(name = "enrollment_closes_at") 
    private Date enrollmentClosesAt; 

    @ManyToOne(fetch = FetchType.EAGER) 
    @JoinColumn(name = "class_id") 
    private OvertimeClass overtimeClass; 

    public Overtime() { 
    } 
//getters and setters 
} 

ユーザーが残業にサインアップしようとしたタペストリーページクラス:

package company.app.pages; 

import java.io.*; 
import java.text.MessageFormat; 
import java.util.*; 

import org.apache.tapestry5.StreamResponse; 
import org.apache.tapestry5.annotations.Component; 
import org.apache.tapestry5.annotations.InjectComponent; 
import org.apache.tapestry5.annotations.InjectPage; 
import org.apache.tapestry5.annotations.Property; 
import org.apache.tapestry5.annotations.SessionState; 
import org.apache.tapestry5.annotations.Persist; 
import org.apache.tapestry5.corelib.components.Form; 
import org.apache.tapestry5.corelib.components.Zone; 
import org.apache.tapestry5.ioc.Messages; 
import org.apache.tapestry5.ioc.annotations.Inject; 
import org.apache.tapestry5.services.PageRenderLinkSource; 
import org.apache.tapestry5.services.Request; 
import org.apache.tapestry5.services.RequestGlobals; 
import org.hibernate.StaleObjectStateException; 
import org.slf4j.Logger; 
import org.springframework.transaction.annotation.Transactional; 

import vive.util.*; 

import company.t5ext.LabelValueSelectModel; 
import company.t5ext.components.DateTimeField; 

import company.app.*; 
import company.app.domain.*; 
import company.app.services.CacheService; 
import company.app.services.AppService; 
import company.app.comparator.OtComparator; 

@RequiresLogin 
public class ListPostedOvertime { 
    @SessionState 
    @Property 
    private AppSessionState visit; 

    @Inject 
    private RequestGlobals requestGlobals; 

    @Inject 
    @Property 
    private AppService appService; 

    @Inject 
    private Request request; 

    void setupRender() { 
    ... 
    } 

    // this method handle the case when a user tries to sign up for an overtime slot 
    void onSignUp(Integer overtimeId) { 
    // check to see if the OT has been deleted or modified or signed-up 
    Overtime ot = (Overtime)appService.getById(Overtime.class, overtimeId); 
    if (ot == null) { 
     visit.setOneTimeMessage("The overtime has already been deleted."); 
     return; 
    } 
    if (ot.getStatus() != null && ot.getStatus() != AppConst.OT_NEW) { 
     visit.setOneTimeMessage("The overtime has already been signed up. Please choose a different one to sign up."); 
     return; 
    } 

    ... 

    try { 
     appService.validateOvertimeForUser(agency, user, ot); 

     appService.handleSignUpOvertime(agency, user, ot); 

     // log activity 
     String what = "Signed up for overtime " + ot.getRefNo() + "."; 
     appService.logActivity(user, AppConst.LOG_OVERTIME, what); 
    } catch(StaleObjectStateException e) { 
     visit.setOneTimeMessage("The overtime record has been changed by another user, please try again."); 
     return; 
    } catch(Exception e) { 
     visit.setOneTimeMessage(e.getMessage()); 
     return; 
    } 

    ... 
    } 

} 

AppServiceクラス残業記録を更新するためにTapestryページで使用される:

DAOクラスのすべてのための
package company.app.services; 

import java.io.Serializable; 
import java.util.*; 
import java.text.DecimalFormat; 

import org.apache.commons.logging.Log; 
import org.apache.commons.logging.LogFactory; 

import org.hibernate.LockMode; 

import org.springframework.context.support.ResourceBundleMessageSource; 

import vive.db.BaseHbDao; 
import vive.util.*; 

import company.app.*; 
import company.app.comparator.LeaveRequestComparator; 
import company.app.comparator.UserOtInterestComparator; 
import company.app.dataaccess.*; 
import company.app.domain.*; 

public class AppService 
{ 
    private Log log = LogFactory.getLog(this.getClass().getName()); 

    private BaseHbDao baseDao; 
    private OvertimeDao otDao; 
    private MiscDao miscDao; 

    private ResourceBundleMessageSource msgSource; 

    /** 
    * Default constructor. 
    */ 
    public AppService() { 
    } 

    public void save(Object item) { 
    if (item != null) { 
     baseDao.save(item); 
    } 
    } 

    public void update(Object item) { 
    if (item != null) { 
     baseDao.update(item); 
    } 
    } 

    public void saveOrUpdate(Object item) { 
    if (item != null) { 
     baseDao.saveOrUpdate(item); 
    } 
    } 

    public void saveOrUpdateAll(Collection col) { 
    if (col != null) { 
     baseDao.saveOrUpdateAll(col); 
    } 
    } 

    public void delete(Object item) { 
    if (item != null) { 
     baseDao.delete(item); 
    } 
    } 

    public void deleteAll(Collection col) { 
    if (col != null) { 
     baseDao.deleteAll(col); 
    } 
    } 

    public Object getById(Class clazz, Serializable id) { 
    return baseDao.get(clazz, id); 
    } 

    public Object getById(Class clazz, Serializable id, LockMode lockMode) { 
    return baseDao.get(clazz, id, lockMode); 
    } 

    public void validateOvertimeForUser(Agency agency, User user, Overtime ot) throws Exception { 
    validateOvertimeForUser(agency.getId(), agency, user, ot); 
    } 

    public void validateOvertimeForUser(AgencyLite agency, User user, Overtime ot) throws Exception { 
    validateOvertimeForUser(agency.getId(), agency, user, ot); 
    } 

    public void handleSignUpOvertime(AgencyBase agency, User user, Integer otId) { 
    Overtime ot = (Overtime)getById(Overtime.class, otId); 
    handleSignUpOvertime(agency, user, ot); 
    } 

    public void handleSignUpOvertime(AgencyBase agency, User user, Overtime ot) { 
    handleSignUpOvertime(agency, user, ot, 1.0d); 
    } 

    public void handleSignUpOvertime(AgencyBase agency, User user, Integer otId, Double ptsPerOt) { 
    Overtime ot = (Overtime)getById(Overtime.class, otId); 
    handleSignUpOvertime(agency, user, ot, ptsPerOt); 
    } 

    public void handleSignUpOvertime(AgencyBase agency, User user, Overtime ot, Double ptsPerOt) { 
    handleSignUpOvertime(agency, user, ot, ptsPerOt, null, null); 
    } 

    public void handleSignUpOvertime(AgencyBase agency, User user, Overtime ot, Double ptsPerOt, String viaUsername, String viaName) { 
    Date today = new Date(); 
    boolean isOtConfirmRequired = AppUtil.isTrue(agency.getOtConfirmRequired()); 
    Integer otConfirmThreshold = 0; 
    if (agency.getOtConfirmThreshold() != null) { 
     otConfirmThreshold = agency.getOtConfirmThreshold(); 
    } 
    long otInDays = (ot.getFromDttm() - today.getTime())/AppConst.MILLIS_IN_DAY; 

    ot.setSignedUpBy(user.getUsername()); 
    ot.setDateSignedUp(today); 
    ot.setSignedUpVia(viaUsername); 
    if (isOtConfirmRequired && otInDays >= otConfirmThreshold) { 
     ot.setStatus(AppConst.OT_PDG); 
    } else { 
     ot.setStatus(AppConst.OT_FIN); 
    } 
    saveOrUpdate(ot); 

    user.setLastOtSignupDate(today); 
    user.setPoints(AppUtil.addPoints(ptsPerOt, user.getPoints())); 
    saveOrUpdate(user); 

    ... 

    // email notification sent from caller 
    } 

    ... 
} 

ベースクラス:私はそれが働いて得た

package vive.db; 

import java.io.Serializable; 

import java.util.*; 

import org.apache.commons.logging.Log; 
import org.apache.commons.logging.LogFactory; 

import org.hibernate.LockMode; 
import org.hibernate.Query; 
import org.hibernate.Session; 
import org.hibernate.HibernateException; 
import org.hibernate.type.Type; 

import org.springframework.orm.hibernate3.support.HibernateDaoSupport; 

import vive.XException; 
import vive.util.XUtil; 

/** 
* The superclass for hibernate data access object. 
*/ 
public class BaseHbDao extends HibernateDaoSupport implements BaseHbDaoInterface 
{ 
    private Log log; 

    public BaseHbDao() { 
    super(); 
    log = LogFactory.getLog(getClass()); 
    } 

    ... 

    /** 
    * Save or update an object. 
    */ 
    public void saveOrUpdate(Object obj) { 
    getHibernateTemplate().saveOrUpdate(obj); 
    } 

    public void save(Object obj) { 
    getHibernateTemplate().save(obj); 
    } 

    public void update(Object obj) { 
    getHibernateTemplate().update(obj); 
    } 

    /** 
    * Delete an object. 
    */ 
    public void delete(Object obj) { 
    getHibernateTemplate().delete(obj); 
    } 

    /** 
    * Retrieve an object of the given id, null if it does not exist. 
    * Similar to "load" except that an exception will be thrown for "load" if 
    * the given record does not exist. 
    */ 
    public Object get(Class clz, Serializable id) { 
    return getHibernateTemplate().get(clz, id); 
    } 

    public Object get(Class clz, Serializable id, LockMode lockMode) { 
    return getHibernateTemplate().get(clz, id, lockMode); 
    } 

    ... 

    public void flush() { 
    getHibernateTemplate().flush(); 
    } 

    /** 
    * Retrieve a HB session. 
    * Make sure to release it after you are done with the session by calling 
    * releaseHbSession. 
    */ 
    public Session getHbSession() { 
    try { 
     return getSession(); 
    } catch (Exception e) { 
     return null; 
    } 
    } 

    /** 
    * Release a HB Session 
    */ 
    public void releaseHbSession(Session sess) { 
    releaseSession(sess); 
    } 

} 

答えて

0

大丈夫!

まず、@Versionアノテーションを使用していますので、問題のあるバージョンの列をテーブルに追加しました。

alter table over_time add version INT(11) DEFAULT 0; 

第二に、エンティティクラスにバージョン注釈やメンバーを追加します。

public class Overtime implements java.io.Serializable { 

    private static final long serialVersionUID = 7263309927526074109L; 
    @Id 
    @GeneratedValue(generator = "ot_gen") 
    @GenericGenerator(name = "ot_gen", strategy = "hilo", parameters = { 
    @Parameter(name = "table", value = "unique_key"), @Parameter(name   = "column", value = "next_hi"), 
     @Parameter(name = "max_lo", value = "99") }) 
    private Integer id; 

    @Version 
    @Column(name = "version") 
    private int version; 

... 

私は時代の最初のカップルこれを試したとき、私はIntegerオブジェクトではないバージョンのメンバーのためのプリミティブint型を使用していましたクラスの私はこれが問題だと思う。また

他のHibernate固有のアノテーションは、エンティティクラスにいないことを確認してください:

@org.hibernate.annotations.Entity(dynamicUpdate = true, optimisticLock = OptimisticLockType.ALL) 

第三に、スローされます例外は、私が読んだウェブサイトのいずれかが、それがあるべきと言うものではありませんオーバータイムレコードのサインアップを担当するTapestryページクラスで実際にスローされたものをキャッチしましょう。

void onSignUp(Integer overtimeId) { 
    // check to see if the OT has been deleted or modified or signed-up 
    Overtime ot = (Overtime)appService.getById(Overtime.class, overtimeId); 
    if (ot == null) { 
     visit.setOneTimeMessage("The overtime has already been deleted."); 
     return; 
    } 
    if (ot.getStatus() != null && ot.getStatus() != AppConst.OT_NEW) { 
     visit.setOneTimeMessage("The overtime has already been signed up. Please choose a different one to sign up."); 
     return; 
    } 

... 

    try { 
     appService.validateOvertimeForUser(agency, user, ot); 
     appService.handleSignUpOvertime(agency, user, ot); 

     // log activity 
     String what = "Signed up for overtime " + ot.getRefNo() + "."; 
     appService.logActivity(user, AppConst.LOG_OVERTIME, what); 
    } catch(HibernateOptimisticLockingFailureException x) { 
     visit.setOneTimeMessage("The overtime record has been changed by another user, please try again."); 
     return; 
    } catch(Exception e) { 
     visit.setOneTimeMessage(e.getMessage()); 
     return; 
    } 

... 
+0

はまた、それが働いていなかったときの前に、私はバージョンフィールドの取得メソッドに@Version 注釈を入れていたことに注意してください。今回私はそれをフィールドに置き、getter/setterメソッドを追加しませんでした。結局のところ、これはおそらく実際の問題であり、プリミティブではなくIntegerオブジェクトの使用ではありませんでした。 – Scott

関連する問題