java - 为什么 jpa/hibernate 在保存从 "select-then-update"方法检索的实体时仍然执行 "findBy",似乎它已分离

标签 java spring hibernate jpa spring-data-jpa

我正在构建一个博客网站,现在可以投票的实体有两种类型:博客和评论。我想用一张表来存储这两种类型的投票计数器。我认为 (entity_id, type) 对可以是复合主键,这是我的 Java @Entity 基类:

@Entity
@Table(name = "vote_counter")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorOptions(insert = false)
@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.INTEGER)
public abstract class VoteCounter extends ManuallyAssignIdEntitySuperClass<VoteCounterId> {

    @EmbeddedId
    private VoteCounterId id;

    @Column(nullable = false)
    private Integer voteCount = 0;

    public VoteCounterId getId() {
        return id;
    }

    public void setId(VoteCounterId id) {
        this.id = id;
    }

    public Integer getVoteCount() {
        return voteCount;
    }

    public void setVoteCount(Integer voteCount) {
        this.voteCount = voteCount;
    }

    public void incrVoteCount(int value){
        voteCount += value;
    }

    public void decrVoteCount(int value){
        voteCount -= value;
    }

    public VoteCounter() {
    }

    public VoteCounter(VoteCounterId id) {
        this.id = id;
    }

    public VoteCounter(long entityId, int type){
        this.id = new VoteCounterId(entityId, type);
    }
}

我遵循教程post并创建ManuallyAssignIdEntitySuperClass。这是 @EmbeddedId 类定义:

@Embeddable
public class VoteCounterId implements Serializable {
    @Column(name = "entity_id", nullable = false, updatable = false)
    private Long entityId;

    @Column(name = "type", nullable = false, insertable = false, updatable = false)
    private Integer type;

    public VoteCounterId() {
    }

    public VoteCounterId(Long entityId, Integer type) {
        this.entityId = entityId;
        this.type = type;
    }

    public Long getEntityId() {
        return entityId;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof VoteCounterId)) return false;
        VoteCounterId that = (VoteCounterId) o;
        return Objects.equals(entityId, that.entityId) &&
            Objects.equals(type, that.type);
    }

    @Override
    public int hashCode() {
        return Objects.hash(entityId, type);
    }
}

基本的VoteCounter类有两个子类,BlogVoteCounterCommentVoteCounter

@Entity
@DiscriminatorValue(VoteType.COMMENT_DISCRIMINATOR)
public class CommentVoteCounter extends VoteCounter {
    @OneToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "entity_id", updatable = false, nullable = false, insertable = false)
    private Comment comment;

    public CommentVoteCounter() {
    }

    public CommentVoteCounter(long commentId) {
        super(commentId, VoteType.COMMENT);
    }

    public Comment getComment() {
        return comment;
    }
}

我创建了一个方法来测试这些类的行为。正如你所看到的,这里的语义就像“检查blogVoteCounter是否存在,如果存在,则增加其投票数,如果不存在,则创建一个”。

    public BlogVoteCounter incrBlogVoteCounter(long id){
        return blogVoteCounterRepository.findByBlog(blogRepository.getOne(id)).map(blogVoteCounter -> {
            blogVoteCounter.incrVoteCount(1);
            return blogVoteCounterRepository.save(blogVoteCounter);
        }).orElseGet(() -> {
            BlogVoteCounter blogVoteCounter = new BlogVoteCounter(id);
            return blogVoteCounterRepository.save(blogVoteCounter);
        });
    }

现在问题来了,在数据库中为特定的 Blog 创建初始 BlogVoteCounter 后,每次我在该 Blog 上执行该方法时>,似乎 hibernate/jpa 总是会在 save 之前触发一个额外的选择查询。就像:

Hibernate: 
    select
        blogvoteco0_.entity_id as entity_i2_8_,
        blogvoteco0_.type as type1_8_,
        blogvoteco0_.vote_count as vote_cou3_8_ 
    from
        vote_counter blogvoteco0_ 
    where
        blogvoteco0_.type=1 
        and blogvoteco0_.entity_id=?
Hibernate: 
    select
        blogvoteco0_.entity_id as entity_i2_8_0_,
        blogvoteco0_.type as type1_8_0_,
        blogvoteco0_.vote_count as vote_cou3_8_0_ 
    from
        vote_counter blogvoteco0_ 
    where
        blogvoteco0_.entity_id=? 
        and blogvoteco0_.type=? 
        and blogvoteco0_.type=1
Hibernate: 
    update
        vote_counter 
    set
        vote_count=? 
    where
        entity_id=? 
        and type=?

但是如果我在 incrBlogVoteCounter 方法上添加 @Transactional 注释,则更新前的额外选择查询将不会出现。我猜测由 findByBlog 方法检索的 BlogVoteCounter 已分离,因此选择查询将在更新之前执行,但我只是想知道为什么它会被分离(如果我的猜测是写)。我在应用程序的其他地方使用相同的模式(查找和更新),并且所有这些都不会出现此问题。我想知道如何避免这个额外的查询?

最佳答案

but i just wonder why it will me detached

正是因为该方法没有包装在事务中。在这种情况下,只有findByBlog创建自己的事务,一旦方法调用完成,持久化上下文就会被清除,并且所有以前管理的实体都会被分离。

如果您想知道为什么要执行额外的选择,那是因为 blogVoteCounterRepository.save(...)调用EntityManager.merge()内部。 EntityManager.merge()需要将实体的现有版本加载到持久化上下文中,以便能够执行合并(因此需要额外的 SELECT )。当您使用@Transactional时不过,该实体已经在上下文中,因此无需加载任何内容(事实上, EntityManager.merge() 没有任何效果)。

关于java - 为什么 jpa/hibernate 在保存从 "select-then-update"方法检索的实体时仍然执行 "findBy",似乎它已分离,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62405061/

相关文章:

java - 使用 OpenEntityManagerInViewFilter 进行延迟初始化?

JavaFX - setText() 不工作

java - hibernate 启动很慢

java - 从 Kafka 消息中将 TraceId 注入(inject) spring sleuth

hibernate - grails 在 hibernate 中映射非域类的正确方法

java - 为什么 DATE 函数不能与 Hibernate 中的 count unique 一起使用?

java - 创建多用户客户端-服务器应用程序的合适架构是什么?

java - 在java中将MD5转换成String

java - 如何将学生添加到学生组

java - 为什么我的 Spring @Autowired 字段为空?