Hibernate - Random occurrence of an error: Not-null property references a transient value - transient instance must be saved before current operation

Hibernate - Random occurrence of an error: Not-null property references a transient value - transient instance must be saved before current operation

我无法理解将实体保存到数据库时出错的原因。我想澄清一下,有时实体会被保存。

我有两个实体:

@Data
@Entity
@Table(...)
public class RuleCollection {
    // Fields
}

@Data
@Entity
@Table(...)
public class RuleAttribute {
    // Fields

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "rule_collection_id", nullable = false)
    private RuleCollection ruleCollection;
}

RuleAttribute 必须包含 RuleCollection。

首先在 JSON 文件的处理过程中,“RuleCollection”对象被保存:

Map<UUID, RuleCollection> ruleCollectionsMap = 
json.getRuleCollections().stream().map(ruleCollectionDto -> {
        RuleCollection ruleCollection = new RuleCollection();
        // Set ruleCollection fields
        return ruleCollectionRepository.save(ruleCollection);
    }
).collect(Collectors.toMap(RuleCollection::getId, Function.identity()));

之后,填写并保存“RuleAttribute”:

List<RuleAttribute> attributes = json.getAttributes().stream()
    .filter(attr -> ruleCollectionsMap.get(attr.getCollectionId()) != null)
    .parallel()
    .map(attr -> {
        RuleAttribute ruleAttribute = new RuleAttribute();
        // Set ruleAttribute fields
        ruleAttribute.setRuleCollection(ruleCollectionsMap.get(attr.getCollectionId()));
        return ruleAttribute;
    }
).collect(Collectors.toList());
ruleAttributeRepository.save(attributes);

然后可能出现错误:

could not initialize proxy - no Session; nested exception is org.hibernate.LazyInitializationException: could not initialize proxy - no Session

如果我在流中进行属性保存,错误会更改:

.map(attr -> {
    ...
    return ruleAttributeRepository.save(ruleAttribute);
    }
).collect(Collectors.toList());

org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : RuleAttribute.ruleCollection -> RuleCollection; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : RuleAttribute.ruleCollection -> RuleCollection

我不知道可能是什么问题,因为“RuleCollection”已经保存。

我尝试更改 class“RuleAttribute”,如 post 中所述,在 Whosebug 上出现类似问题:

@ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name = "rule_collection_id", nullable = false)
private RuleCollection ruleCollection;

同样的数据出现错误。有一次我保存数据没有报错,从数据库中删除,再次保存时出现这个错误。

更新:

RuleCollectionRepository 和 RuleAttributeRepository 扩展 org.springframework.data.repository.CrudRepository

保存和处理在具有@Transactional属性的方法中。

显然,这种情况下的并行流违反了 Hibernate 会话: Java .parallelStream() with spring annotated methods

搜索类似问题后,找到了几种解决方案:

  • 将属性@Transactional 中的参数传播设置为Propagation.REQUIRES_NEW。 但是,此选项不可靠,因为主会话不知道子会话的状态,可能会导致数据丢失或类似问题。

  • 不要使用并行流。

  • 如果无法拒绝并行流,则需要去掉@Transactional属性,先设置必要的数据,然后在单独的方法中进行顺序保存,标有@Transactional 属性。 例如:

    public void saveData(Json json) {
        Map<UUID, RuleCollection> ruleCollectionsMap = // Set data
        List<RuleAttribute> attributes = // Set data
    
        saveInDB(ruleCollectionsMap.values(), attributes);
    }
    
    @Transactional
    private void saveInDB(Collection<RuleCollection> ruleCollections, Collection<RuleAttribute> attributes) {
        ruleCollectionRepository.save(ruleCollections);
        ruleAttributeRepository.save(attributes);
    }