两次保存实体时出现 OptimisticLockException
OptimisticLockException when saving an entity twice
我已经阅读了与此相关的 SO 建议问题,并检查了我是否有与版本、并发修改等相同的问题,但我认为我的问题略有不同。
堆栈:
- JavaEE
- PostgreSQL
- JSF2.2
- Primefaces
我正在保存一个实体端点,它在第一次调用时工作,但再次保存同一个实体会在标题中引发错误。
public E update(E entity) throws BusinessException {
preUpdate(entity);
try {
entity = getEntityManager().merge(entity);
} catch(Exception e) {
if(e instanceof UndeclaredThrowableException) {
throw new BusinessException(e.getCause().getCause());
} else {
throw new BusinessException(e);
}
}
postUpdate(entity);
return entity;
}
public void updateNoMerge(E entity) throws BusinessException {
preUpdate(entity);
postUpdate(entity);
}
@Entity
@Table(name = "service_endpoint")
@GenericGenerator(name = "ID_GENERATOR", strategy = "increment")
@NoIntersectionBetween(
firstCollection = "pathParameters.endpointParameter.parameter",
secondCollection = "parametersMapping.endpointParameter.parameter"
)
@NamedQueries({
@NamedQuery(name = "findByParameterName", query = "SELECT e FROM Endpoint e " +
"INNER JOIN e.service as service " +
"LEFT JOIN e.pathParameters as pathParameter " +
"LEFT JOIN e.parametersMapping as parameterMapping " +
"WHERE service.code = :serviceCode " +
"AND (pathParameter.endpointParameter.parameter = :propertyName OR parameterMapping.endpointParameter.parameter = :propertyName)"),
@NamedQuery(name = "Endpoint.deleteByService", query = "DELETE from Endpoint e WHERE e.service.id=:serviceId")})
@ImportOrder(5)
@ExportIdentifier({ "code" })
@ModuleItem("Endpoint")
@ModuleItemOrder(80)
@ObservableEntity
public class Endpoint extends BusinessEntity {
private static final long serialVersionUID = 6561905332917884613L;
@ElementCollection(fetch = FetchType.LAZY)
@Fetch(value = FetchMode.SUBSELECT)
@CollectionTable(name = "service_endpoint_roles", joinColumns = @JoinColumn(name = "endpoint_id"))
@Column(name = "role")
private Set<String> roles = new HashSet<>();
/**
* Technical service associated to the endpoint
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "service_id", updatable = false, nullable = false)
private Function service;
/**
* Whether the execution of the service will be syncrhonous.
* If asynchronous, and id of execution will be returned to the user.
*/
@Type(type = "numeric_boolean")
@Column(name = "synchronous", nullable = false)
private boolean synchronous;
/**
* Method used to access the endpoint.
* Conditionates the input format of the endpoint.
*/
@Enumerated(EnumType.STRING)
@Column(name = "method", nullable = false)
private EndpointHttpMethod method;
/**
* Parameters that will be exposed in the endpoint path
*/
@OneToMany(mappedBy = "endpointParameter.endpoint", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@OrderColumn(name = "position")
private List<EndpointPathParameter> pathParameters = new ArrayList<>();
/**
* Mapping of the parameters that are not defined as path parameters
*/
@OneToMany(mappedBy = "endpointParameter.endpoint", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<TSParameterMapping> parametersMapping = new ArrayList<>();
/**
* JSONata query used to transform the result
*/
@Column(name = "jsonata_transformer")
private String jsonataTransformer;
/**
* Context variable to be returned by the endpoint
*/
@Column(name = "returned_variable_name")
private String returnedVariableName;
/**
* Context variable to be returned by the endpoint
*/
@Type(type = "numeric_boolean")
@Column(name = "serialize_result", nullable = false)
private boolean serializeResult;
/**
* Content type of the response
*/
@Column(name = "content_type")
private String contentType;
persistence.xml 文件
https://github.com/meveo-org/meveo/blob/master/meveo-admin/web/src/main/resources/META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="MeveoAdmin">
<jta-data-source>java:jboss/datasources/MeveoAdminDatasource</jta-data-source>
<jar-file>lib/meveo-model-${project.version}.jar</jar-file>
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<properties>
<property name="hibernate.archive.autodetection" value="class" />
<property name="hibernate.hbm2ddl.auto" value="validate" /> <!-- DB structure is managed by liquibase, not hibernate -->
<property name="hibernate.show_sql" value="true" />
<property name="format_sql" value="true"/>
<property name="use_sql_comments" value="false"/>
<property name="hibernate.connection.harSet" value="utf-8"/>
<property name="hibernate.connection.useUnicode" value="true"/>
<property name="hibernate.connection.characterEncoding" value="UTF-8" />
<!-- <property name="hibernate.default_schema" value="public" /> Disable for Mysql/mariaDB instalation -->
<property name="hibernate.cache.use_second_level_cache" value="true" />
<property name="hibernate.cache.use_query_cache" value="true" />
<property name="hibernate.cache.use_minimal_puts" value="true" />
<property name="hibernate.cache.default_cache_concurrency_strategy" value="transactional" />
<property name="hibernate.generate_statistics" value="false" />
<property name="hibernate.discriminator.ignore_explicit_for_joined" value="true" />
<property name="hibernate.ejb.event.flush" value="org.meveo.jpa.event.FlushEventListener" /> <!-- Needed for ES -->
<property name="hibernate.jpa.compliance.global_id_generators" value="false"/>
<property name="hibernate.c3p0.min_size" value="5"></property>
<property name="hibernate.c3p0.max_size" value="20"></property>
<property name="hibernate.c3p0.acquire_increment" value="5"></property>
<property name="hibernate.c3p0.timeout" value="1800"></property>
<!-- <property name="hibernate.persister.resolver" value="org.hibernate.util.CustomPersisterClassResolver"></property> -->
<property name="hibernate.transaction.factory_class" value="org.hibernate.transaction.JTATransactionFactory"/>
<property name="hibernate.connection.isolation" value="4"></property>
</properties>
</persistence-unit>
在 EndpointService.update 中,我尝试尝试:
- super.update - 调用 em.merge
- super.updateNoMerge - 基本上对实体什么都不做,它只是调用一些前后更新触发器。
在第二次更新中,我得到了上述两种方法的这些日志:
19:31:31,022 错误 [org.jboss.as.ejb3.invocation](默认任务 1)WFLYEJB0034:方法 public long [=76= 的组件端点服务上的 EJB 调用失败](org.meveo.admin.util.pagination.PaginationConfiguration): javax.ejb.EJBException: javax.persistence.OptimisticLockException: 行已被另一个事务更新或删除(或 unsaved-value 映射不正确): [org.meveo.model.technicalservice.endpoint.Endpoint#1]
-> 执行计数查询时触发。我认为此时休眠决定刷新保存事务。
此处有更多日志...
原因:javax.persistence.OptimisticLockException:行已被另一个事务更新或删除(或 unsaved-value 映射不正确):[org.meveo.model.technicalservice.endpoint.Endpoint#1]
报错后,再次保存实体即可。就是这个循环:
1,0,PAGE_RELOAD,1,0
其中 1 成功,0 失败。
我已经检查了实体关系以及 child 个实体,但我无法找出问题所在。
有什么想法吗?
这是完整的错误日志:https://www.dropbox.com/s/a0r12rf1vf6rshe/optimisticlockexception.txt
问题原因:在同一个服务中定义了Event Observer。
public void onEndpointUpdate(@Observes E entity) {
// call another service that manipulates the entity here.
}
解决方案:将事件侦听器重构为另一个无状态 class 以避免陈旧的异常。
我已经阅读了与此相关的 SO 建议问题,并检查了我是否有与版本、并发修改等相同的问题,但我认为我的问题略有不同。
堆栈:
- JavaEE
- PostgreSQL
- JSF2.2
- Primefaces
我正在保存一个实体端点,它在第一次调用时工作,但再次保存同一个实体会在标题中引发错误。
public E update(E entity) throws BusinessException {
preUpdate(entity);
try {
entity = getEntityManager().merge(entity);
} catch(Exception e) {
if(e instanceof UndeclaredThrowableException) {
throw new BusinessException(e.getCause().getCause());
} else {
throw new BusinessException(e);
}
}
postUpdate(entity);
return entity;
}
public void updateNoMerge(E entity) throws BusinessException {
preUpdate(entity);
postUpdate(entity);
}
@Entity
@Table(name = "service_endpoint")
@GenericGenerator(name = "ID_GENERATOR", strategy = "increment")
@NoIntersectionBetween(
firstCollection = "pathParameters.endpointParameter.parameter",
secondCollection = "parametersMapping.endpointParameter.parameter"
)
@NamedQueries({
@NamedQuery(name = "findByParameterName", query = "SELECT e FROM Endpoint e " +
"INNER JOIN e.service as service " +
"LEFT JOIN e.pathParameters as pathParameter " +
"LEFT JOIN e.parametersMapping as parameterMapping " +
"WHERE service.code = :serviceCode " +
"AND (pathParameter.endpointParameter.parameter = :propertyName OR parameterMapping.endpointParameter.parameter = :propertyName)"),
@NamedQuery(name = "Endpoint.deleteByService", query = "DELETE from Endpoint e WHERE e.service.id=:serviceId")})
@ImportOrder(5)
@ExportIdentifier({ "code" })
@ModuleItem("Endpoint")
@ModuleItemOrder(80)
@ObservableEntity
public class Endpoint extends BusinessEntity {
private static final long serialVersionUID = 6561905332917884613L;
@ElementCollection(fetch = FetchType.LAZY)
@Fetch(value = FetchMode.SUBSELECT)
@CollectionTable(name = "service_endpoint_roles", joinColumns = @JoinColumn(name = "endpoint_id"))
@Column(name = "role")
private Set<String> roles = new HashSet<>();
/**
* Technical service associated to the endpoint
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "service_id", updatable = false, nullable = false)
private Function service;
/**
* Whether the execution of the service will be syncrhonous.
* If asynchronous, and id of execution will be returned to the user.
*/
@Type(type = "numeric_boolean")
@Column(name = "synchronous", nullable = false)
private boolean synchronous;
/**
* Method used to access the endpoint.
* Conditionates the input format of the endpoint.
*/
@Enumerated(EnumType.STRING)
@Column(name = "method", nullable = false)
private EndpointHttpMethod method;
/**
* Parameters that will be exposed in the endpoint path
*/
@OneToMany(mappedBy = "endpointParameter.endpoint", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@OrderColumn(name = "position")
private List<EndpointPathParameter> pathParameters = new ArrayList<>();
/**
* Mapping of the parameters that are not defined as path parameters
*/
@OneToMany(mappedBy = "endpointParameter.endpoint", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<TSParameterMapping> parametersMapping = new ArrayList<>();
/**
* JSONata query used to transform the result
*/
@Column(name = "jsonata_transformer")
private String jsonataTransformer;
/**
* Context variable to be returned by the endpoint
*/
@Column(name = "returned_variable_name")
private String returnedVariableName;
/**
* Context variable to be returned by the endpoint
*/
@Type(type = "numeric_boolean")
@Column(name = "serialize_result", nullable = false)
private boolean serializeResult;
/**
* Content type of the response
*/
@Column(name = "content_type")
private String contentType;
persistence.xml 文件 https://github.com/meveo-org/meveo/blob/master/meveo-admin/web/src/main/resources/META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="MeveoAdmin">
<jta-data-source>java:jboss/datasources/MeveoAdminDatasource</jta-data-source>
<jar-file>lib/meveo-model-${project.version}.jar</jar-file>
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<properties>
<property name="hibernate.archive.autodetection" value="class" />
<property name="hibernate.hbm2ddl.auto" value="validate" /> <!-- DB structure is managed by liquibase, not hibernate -->
<property name="hibernate.show_sql" value="true" />
<property name="format_sql" value="true"/>
<property name="use_sql_comments" value="false"/>
<property name="hibernate.connection.harSet" value="utf-8"/>
<property name="hibernate.connection.useUnicode" value="true"/>
<property name="hibernate.connection.characterEncoding" value="UTF-8" />
<!-- <property name="hibernate.default_schema" value="public" /> Disable for Mysql/mariaDB instalation -->
<property name="hibernate.cache.use_second_level_cache" value="true" />
<property name="hibernate.cache.use_query_cache" value="true" />
<property name="hibernate.cache.use_minimal_puts" value="true" />
<property name="hibernate.cache.default_cache_concurrency_strategy" value="transactional" />
<property name="hibernate.generate_statistics" value="false" />
<property name="hibernate.discriminator.ignore_explicit_for_joined" value="true" />
<property name="hibernate.ejb.event.flush" value="org.meveo.jpa.event.FlushEventListener" /> <!-- Needed for ES -->
<property name="hibernate.jpa.compliance.global_id_generators" value="false"/>
<property name="hibernate.c3p0.min_size" value="5"></property>
<property name="hibernate.c3p0.max_size" value="20"></property>
<property name="hibernate.c3p0.acquire_increment" value="5"></property>
<property name="hibernate.c3p0.timeout" value="1800"></property>
<!-- <property name="hibernate.persister.resolver" value="org.hibernate.util.CustomPersisterClassResolver"></property> -->
<property name="hibernate.transaction.factory_class" value="org.hibernate.transaction.JTATransactionFactory"/>
<property name="hibernate.connection.isolation" value="4"></property>
</properties>
</persistence-unit>
在 EndpointService.update 中,我尝试尝试:
- super.update - 调用 em.merge
- super.updateNoMerge - 基本上对实体什么都不做,它只是调用一些前后更新触发器。
在第二次更新中,我得到了上述两种方法的这些日志:
19:31:31,022 错误 [org.jboss.as.ejb3.invocation](默认任务 1)WFLYEJB0034:方法 public long [=76= 的组件端点服务上的 EJB 调用失败](org.meveo.admin.util.pagination.PaginationConfiguration): javax.ejb.EJBException: javax.persistence.OptimisticLockException: 行已被另一个事务更新或删除(或 unsaved-value 映射不正确): [org.meveo.model.technicalservice.endpoint.Endpoint#1] -> 执行计数查询时触发。我认为此时休眠决定刷新保存事务。
此处有更多日志...
原因:javax.persistence.OptimisticLockException:行已被另一个事务更新或删除(或 unsaved-value 映射不正确):[org.meveo.model.technicalservice.endpoint.Endpoint#1]
报错后,再次保存实体即可。就是这个循环:
1,0,PAGE_RELOAD,1,0
其中 1 成功,0 失败。
我已经检查了实体关系以及 child 个实体,但我无法找出问题所在。
有什么想法吗?
这是完整的错误日志:https://www.dropbox.com/s/a0r12rf1vf6rshe/optimisticlockexception.txt
问题原因:在同一个服务中定义了Event Observer。
public void onEndpointUpdate(@Observes E entity) {
// call another service that manipulates the entity here.
}
解决方案:将事件侦听器重构为另一个无状态 class 以避免陈旧的异常。