Hibernate JPA Доступ к сущности из нескольких потоков - PullRequest
0 голосов
/ 01 мая 2020

Прекурсор

Извините, что не предоставил SSCCE. За последние несколько дней я провел много исследований, посвященных гибернации и доступу к сущностям из нескольких потоков. Если это потенциально дубликат, пожалуйста, не стесняйтесь пометить его как таковой.

Контекст

У меня есть приложение по крайней мере с 2 потоками.

  • Thread-1 обновляет игровой лог c каждые 50 миллисекунд .
  • Поток-2 выполняет несколько транзакций каждые 250-500 мс (примерно).

Проблема

Невозможно заблокировать Поток-1 во время обработки транзакции. Это приводит к получению ссылок на объекты Entity из Thread-2 и их использованию в Thread-1. Это небезопасно из-за условий гонки. Возможно, что Thread-1 и Thread-2 имеют два разных представления сущности.

Что я сделал

Вместо использования сеанса с сохранением контекст, вместо этого я использую StatelessSession. Я также гарантирую, что все реализации Entity являются поточно-ориентированными, так что они эффективно являются окончательными, неизменяемыми и не содержат никаких мутаторов.

Вопросы (извините за множественные)

  1. Действительно ли это решение действительно? Если нет, то почему? Если да, то какие проблемы могут возникнуть?
  2. Есть ли другое поточно-ориентированное решение для использования сеанса с постоянным контекстом?
  3. Следует ли использовать шаблон DTO для сеанса с сеансом с контекст сохраняемости?

Как я обрабатываю транзакции в Thread-2

Коллекции являются параллельными реализациями очереди.

   @Override
    public void run() {
        if (pendingTransactions.isEmpty()) {
            return;
        }
        processingTransactions.addAll(pendingTransactions);

        pendingTransactions.clear();

        Iterator<TransactionWork> transactionWorkIterator = processingTransactions.iterator();

        try (StatelessSession session = factory.openStatelessSession()) {
            while (transactionWorkIterator.hasNext()) {
                TransactionWork transactionWork = transactionWorkIterator.next();

                Transaction transaction = session.beginTransaction();

                logger.info(String.format("Processing transaction %s.", transaction.getClass()));

                try {
                    transactionWork.execute(session);

                    transaction.commit();
                } catch (Throwable exception) {
                    exception.printStackTrace();
                    transaction.rollback();
                } finally {
                    transactionWorkIterator.remove();
                }
            }
        }
    }

Поточно-ориентированная модель сущности

@Entity
@Table(name = "clans")
public class ClanEntity {

    @Id
    @Column(columnDefinition = "BINARY(16)")
    private final UUID id;

    @Column(name = "name")
    private final String name;

    @OneToMany(mappedBy="clan")
    private final Set<ClanMemberEntity> members;

    @OneToMany(mappedBy="clan")
    private final Set<ClanInviteEntity> invites;

    public ClanEntity(UUID id, String name) {
        this(id, name,  ImmutableSet.of(),  ImmutableSet.of());
    }

    public ClanEntity() {
        this(null, null, ImmutableSet.of(), ImmutableSet.of());
    }

    public ClanEntity(UUID id, String name, Set<ClanMemberEntity> members, Set<ClanInviteEntity> invites) {
        this.id = id;
        this.name = name;
        this.members = ImmutableSet.copyOf(members);
        this.invites = ImmutableSet.copyOf(invites);
    }

    public ClanEntity(UUID id, String name, Set<ClanMemberEntity> members) {
        this(id, name, members, ImmutableSet.of());
    }

    public ClanEntity addClanMember(ClanMemberEntity entity) {
        return new ClanEntity(id, name,
                Stream.concat(members.stream(), Stream.of(entity)).collect(Collectors
                        .toCollection(ImmutableSet::of)), invites);
    }

    public ClanEntity removeClanMember(ClanMemberEntity entity) {
        return new ClanEntity(id, name,
                members.stream().filter(member -> member.getId() != entity.getId())
                .collect(Collectors.toCollection(ImmutableSet::of)), invites);
    }

    public UUID getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Set<ClanMemberEntity> getMembers() {
        return members;
    }

    public Set<ClanInviteEntity> getInvites() {
        return invites;
    }
}

Пример обновления сущности, которая является неизменной в StatelessSession

    @Override
    public PlayerProfileEntity result(StatelessSession session) throws HibernateException {
        Object clanMemberObject = session.get(ClanMemberEntity.class, player.getId());

        if (clanMemberObject == null) {
            throw new IllegalStateException("Clan member cannot be null.");
        }
        Object clanObject = session.get(ClanEntity.class, clan.getId());

        if (clanObject == null) {
            throw new IllegalStateException("Clan object cannot be null");
        }
        ClanMemberEntity clanMemberEntity = (ClanMemberEntity) clanMemberObject;

        ClanEntity clanEntity = (ClanEntity) clanObject;

        clanEntity = clanEntity.removeClanMember(clanMemberEntity);

        session.update(clanEntity);

        session.delete(clanMemberEntity);

        PlayerProfileEntity playerProfileNoClanMember = player.withClanMember(null);

        session.update(playerProfileNoClanMember);

        return playerProfileNoClanMember;
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...