Прекурсор
Извините, что не предоставил SSCCE. За последние несколько дней я провел много исследований, посвященных гибернации и доступу к сущностям из нескольких потоков. Если это потенциально дубликат, пожалуйста, не стесняйтесь пометить его как таковой.
Контекст
У меня есть приложение по крайней мере с 2 потоками.
- Thread-1 обновляет игровой лог c каждые 50 миллисекунд .
- Поток-2 выполняет несколько транзакций каждые 250-500 мс (примерно).
Проблема
Невозможно заблокировать Поток-1 во время обработки транзакции. Это приводит к получению ссылок на объекты Entity из Thread-2 и их использованию в Thread-1. Это небезопасно из-за условий гонки. Возможно, что Thread-1 и Thread-2 имеют два разных представления сущности.
Что я сделал
Вместо использования сеанса с сохранением контекст, вместо этого я использую StatelessSession. Я также гарантирую, что все реализации Entity являются поточно-ориентированными, так что они эффективно являются окончательными, неизменяемыми и не содержат никаких мутаторов.
Вопросы (извините за множественные)
- Действительно ли это решение действительно? Если нет, то почему? Если да, то какие проблемы могут возникнуть?
- Есть ли другое поточно-ориентированное решение для использования сеанса с постоянным контекстом?
- Следует ли использовать шаблон 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;
}