Почему jpa / hibernate по-прежнему выполняет «выбор-затем-обновление» при сохранении объекта, полученного из метода «findBy», кажется, что он был отсоединен - PullRequest
0 голосов
/ 16 июня 2020

Я создаю веб-сайт блога, и есть два типа объектов, за которые можно проголосовать прямо сейчас: блог и комментарий. Я хочу использовать одну таблицу для хранения этих двух типов счетчиков голосов. Я думаю, что пара (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);
    }
}

Класс basi c VoteCounter имеет два подкласса, BlogVoteCounter и CommentVoteCounter.

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

I ' Мы создадим метод для проверки поведения этих классов. Как видите, семанти c здесь похоже на «проверьте, существует ли 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);
        });
    }

Теперь вот проблема: после создания начального BlogVoteCounter в базе данных для определенного c Blog, каждый раз, когда я выполняю метод для этого 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=?

Но если я добавлю аннотацию @Transactional к методу incrBlogVoteCounter, дополнительный запрос выбора перед обновлением не появится. Я предполагаю, что BlogVoteCounter, полученный методом findByBlog, отсоединен, поэтому запрос выбора будет выполнен перед обновлением, но мне просто интересно, почему он будет отсоединен (если я предполагаю, что это запись). Я использую тот же шаблон (поиск и обновление) в другом месте моего приложения, и все они не вызовут эту проблему. Я хочу знать, как избежать этого лишнего запроса?

1 Ответ

0 голосов
/ 16 июня 2020

но мне просто интересно, почему он будет отсоединен

Именно потому, что метод не заключен в транзакцию. В таком сценарии только findByBlog создает свою собственную транзакцию, и после завершения вызова метода контекст сохранения очищается, и все ранее управляемые объекты отсоединяются.

Если вам интересно, почему дополнительный выбор выполняется вообще , это потому, что blogVoteCounterRepository.save(...) вызывает EntityManager.merge() внутри. EntityManager.merge() необходимо загрузить существующую версию объекта в контекст персистентности, чтобы иметь возможность выполнить слияние (отсюда и дополнительный SELECT). Однако, когда вы используете @Transactional, объект уже находится в контексте, поэтому загружать нечего (на самом деле EntityManager.merge() не действует).

...