【问题标题】:OptimisticLockException when saving an entity twice两次保存实体时出现 OptimisticLockException
【发布时间】:2020-06-19 11:45:54
【问题描述】:

我已经阅读了与此相关的 SO 建议问题,并检查了我是否有与版本、并发修改等相同的问题,但我认为我的问题略有不同。

堆栈:

  • JavaEE
  • PostgreSQL
  • JSF2.2
  • Primefaces

我正在保存一个实体端点,它在第一次调用时工作,但再次保存同一个实体会引发标题中的错误。

这是实体类: https://github.com/meveo-org/meveo/blob/develop/meveo-admin/ejbs/src/main/java/org/meveo/service/technicalservice/endpoint/EndpointService.java

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

这里是保存实体的服务: https://github.com/meveo-org/meveo/blob/develop/meveo-model/src/main/java/org/meveo/model/technicalservice/endpoint/Endpoint.java

@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 中,我尝试过:

  1. super.update - 调用 em.merge
  2. super.updateNoMerge - 基本上对实体没有任何作用,它只是调用一些更新前和更新后的触发器。

在第二次更新中,我得到了上述两种方法的日志:

19:31:31,022 错误 [org.jboss.as.ejb3.invocation](默认任务 1)WFLYEJB0034:EJB 调用在组件 EndpointService 上失败,用于方法 public long org.meveo.service.base.PersistenceService.count( org.meveo.admin.util.pagination.PaginationConfiguration):javax.ejb.EJBException:javax.persistence.OptimisticLockException:行已被另一个事务更新或删除(或未保存的值映射不正确):[org.meveo.model。技术服务.端点.端点#1] -> 执行计数查询时触发。我认为此时 hibernate 决定刷新保存事务。

这里有更多日志...

原因:javax.persistence.OptimisticLockException:行已被另一个事务更新或删除(或未保存值映射不正确):[org.meveo.model.technicalservice.endpoint.Endpoint#1]

出错后,再次保存实体即可。就是这个循环:

1,0,PAGE_RELOAD,1,0

1 成功,0 失败。

我已经检查了实体关系以及子实体,但我无法找出问题所在。

有什么想法吗?

这是完整的错误日志:https://www.dropbox.com/s/a0r12rf1vf6rshe/optimisticlockexception.txt

【问题讨论】:

  • 能否请您发布您的代码而不是链接到外部资源?另外,这两个更新是否有可能同时执行?我不会立即得出存在问题的结论,99% 的时间OptimisticLockException 可以保护您免受竞争条件的影响
  • 我已经添加了代码。我不这么认为,因为它是由 JSF 按钮触发的同步事件。即使等待了一段时间,我在第二次尝试时也遇到了同样的错误。

标签: hibernate jpa java-ee-7


【解决方案1】:

问题原因:同一服务中定义的事件观察者。

public void onEndpointUpdate(@Observes E entity) {
    // call another service that manipulates the entity here.
}

解决方案:将事件侦听器重构为另一个无状态类以避免陈旧异常。

【讨论】:

    猜你喜欢
    • 2015-11-25
    • 2014-02-19
    • 1970-01-01
    • 1970-01-01
    • 2019-02-18
    • 1970-01-01
    • 2023-03-09
    • 2015-10-23
    • 2014-09-05
    相关资源
    最近更新 更多