gpt4 book ai didi

mysql - 带/不带版本的 Hibernate 乐观锁不起作用

转载 作者:行者123 更新时间:2023-11-30 22:21:53 25 4
gpt4 key购买 nike

我有一个旧版应用程序使用:

hibernate-3.5.5.jar hibernate -jpa-2.0-api-1.0.0.jar Spring 3.0.2 Tapestry 5.3.8MySQLTomcat 7.0.64

它有一个严重的问题,多个用户同时更新同一个表行并丢失第一个更新。基本上,用户 A 说“我想拥有记录”(将我的 ID 放在记录中),用户 B 说“我想拥有记录”,处理代码需要一些时间。因此,用户 A 获得了它,然后用户 B 没有注意到用户 A 拥有它,因此用户 B 在他不应该拥有它的时候获得了它,因为用户 A 已经拥有它。

我试过使用:

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

在表的实体类上观察 hibernate 生成的 SQL,它从不将表列添加到 SQL 更新语句。它只是有更新... where id=?。

我还尝试在相关表和实体类中添加一个版本列,并用

注释该字段
@Version.

这与上面的效果完全相同,生成的 SQL 中没有任何内容使用版本列。它也永远不会增加。

我猜我在设置时遗漏了一些东西,或者应用程序使用 hibernate 的方式导致它无法正常工作,因为我读过的所有内容都说它应该“正常工作”。

应用上下文.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>

超时实体类:

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
}

用户尝试注册超时的 Tapestry 页面类:

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;
}

...
}

}

Tapestry页面用来更新超时记录的AppService类:

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
}

...
}

所有 DAO 类的基类:

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);
}

}

最佳答案

好的,我成功了!

首先,我使用了@Version 注释,所以我在有问题的表中添加了一个版本列。

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

其次,为Entity类添加Version注解和成员:

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;
}

...

关于mysql - 带/不带版本的 Hibernate 乐观锁不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36457911/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com