refactor: rename packages and files to use 'flash' prefix for consistency

This commit is contained in:
Relism
2026-04-26 12:34:49 +02:00
parent 9e19f439be
commit 8a52c4f143
279 changed files with 2774 additions and 752 deletions
@@ -0,0 +1,177 @@
package dev.relism.flash.ext.data.hibernate;
import dev.relism.flash.ext.data.core.*;
import org.hibernate.Session;
import org.hibernate.query.MutationQuery;
import jakarta.persistence.TypedQuery;
import java.io.Serializable;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* Hibernate-backed repository base.
* Never extend this directly — extend {@link Repository} from the core.
* This class is instantiated internally by flash-ext-data-hibernate.
*/
public abstract class HibernateRepository<T, ID extends Serializable>
extends Repository<T, ID> {
private final Class<T> type;
protected HibernateRepository(Class<T> type) {
this.type = type;
}
// ── Session — always safe, tx() wrapper guarantees active transaction ─────
protected Session session() {
return Tx.resource(Session.class);
}
// ── Repository abstract impl ──────────────────────────────────────────────
@Override
protected Optional<T> doFindById(ID id) {
return Optional.ofNullable(session().get(type, id));
}
@Override
protected List<T> doFindAll() {
return hql("from " + type.getSimpleName()).getResultList();
}
@Override
protected List<T> doFindAll(int page, int size) {
return hql("from " + type.getSimpleName())
.setFirstResult(page * size)
.setMaxResults(size)
.getResultList();
}
@Override
protected List<T> doFindAll(Sort sort) {
return hql("from " + type.getSimpleName() + orderClause(sort))
.getResultList();
}
@Override
protected List<T> doFindAll(int page, int size, Sort sort) {
return hql("from " + type.getSimpleName() + orderClause(sort))
.setFirstResult(page * size)
.setMaxResults(size)
.getResultList();
}
@Override
protected Page<T> doFindPage(int page, int size) {
long total = doCount();
return new Page<>(doFindAll(page, size), page, size, total);
}
@Override
protected Page<T> doFindPage(int page, int size, Sort sort) {
long total = doCount();
return new Page<>(doFindAll(page, size, sort), page, size, total);
}
@Override
protected T doSave(T entity) {
session().persist(entity);
return entity;
}
@Override
protected T doUpdate(T entity) {
return session().merge(entity);
}
@Override
protected void doDelete(T entity) {
Session s = session();
s.remove(s.contains(entity) ? entity : s.merge(entity));
}
@Override
protected void doDeleteById(ID id) {
doFindById(id).ifPresent(this::doDelete);
}
@Override
protected boolean doExistsById(ID id) {
return doFindById(id).isPresent();
}
@Override
protected long doCount() {
return session()
.createQuery("select count(*) from " + type.getSimpleName(), Long.class)
.uniqueResultOptional()
.orElse(0L);
}
// ── Query helpers — usabili nelle sottoclassi domain ─────────────────────
protected TypedQuery<T> hql(String hql) {
return session().createQuery(hql, type);
}
protected <R> TypedQuery<R> hql(String hql, Class<R> resultType) {
return session().createQuery(hql, resultType);
}
protected Optional<T> findOne(String hql, Consumer<TypedQuery<T>> params) {
TypedQuery<T> q = hql(hql);
params.accept(q);
return q.getResultStream().findFirst();
}
protected List<T> findMany(String hql, Consumer<TypedQuery<T>> params) {
return tx(() -> {
TypedQuery<T> q = hql(hql);
params.accept(q);
return q.getResultList();
});
}
protected List<T> findMany(String hql, Consumer<TypedQuery<T>> params,
int page, int size) {
return tx(() -> {
TypedQuery<T> q = hql(hql);
params.accept(q);
return q.setFirstResult(page * size).setMaxResults(size).getResultList();
});
}
protected Page<T> findManyPaged(String hql, String countHql,
Consumer<TypedQuery<T>> params,
int page, int size) {
return tx(() -> {
long total = session()
.createQuery(countHql, Long.class)
.uniqueResultOptional()
.orElse(0L);
List<T> content = findMany(hql, params, page, size);
return new Page<>(content, page, size, total);
});
}
protected int execute(String hql, Consumer<MutationQuery> params) {
return tx(() -> {
MutationQuery q = session().createMutationQuery(hql);
params.accept(q);
return q.executeUpdate();
});
}
protected Class<T> entityType() {
return type;
}
private String orderClause(Sort sort) {
return " order by " + sort.columns().stream()
.map(c -> c.column() + (c.asc() ? " ASC" : " DESC"))
.collect(Collectors.joining(", "));
}
}
@@ -0,0 +1,114 @@
package dev.relism.flash.ext.data.hibernate;
import dev.relism.flash.ext.data.core.*;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import java.util.Objects;
public class HibernateTxManager implements TxManager {
private static final TxResourceKey HIBERNATE_STATUS_KEY = TxResourceKey.of("hibernate.tx.status");
private final SessionFactory sf;
public HibernateTxManager(SessionFactory sessionFactory) {
this.sf = Objects.requireNonNull(sessionFactory);
}
@Override
public TxStatus begin(TxDefinition definition) {
return switch (definition.propagation()) {
case REQUIRED -> ResourceRegistry.isBound(HIBERNATE_STATUS_KEY)
? joinExisting(definition)
: beginNew(definition);
case REQUIRES_NEW -> beginNew(definition);
case MANDATORY -> {
if (!ResourceRegistry.isBound(HIBERNATE_STATUS_KEY))
throw new IllegalStateException("MANDATORY: no active transaction");
yield joinExisting(definition);
}
case NOT_SUPPORTED -> throw new UnsupportedOperationException("NOT_SUPPORTED is not implemented");
};
}
private TxStatus beginNew(TxDefinition definition) {
HibernateTxStatus suspended = ResourceRegistry.getOrNull(HIBERNATE_STATUS_KEY, HibernateTxStatus.class);
if (suspended != null) {
ResourceRegistry.unbind(HIBERNATE_STATUS_KEY);
}
Session s = sf.openSession();
s.beginTransaction();
if (definition.readOnly()) s.setDefaultReadOnly(true);
if (definition.isolation() != TransactionIsolation.DEFAULT) {
s.doWork(connection -> connection.setTransactionIsolation(definition.isolation().level()));
}
HibernateTxStatus status = new HibernateTxStatus(
s,
true,
definition.readOnly(),
suspended,
new HibernateTxStatus.RollbackMarker()
);
ResourceRegistry.bind(HIBERNATE_STATUS_KEY, status);
return status;
}
private TxStatus joinExisting(TxDefinition definition) {
HibernateTxStatus existing = ResourceRegistry.get(HIBERNATE_STATUS_KEY, HibernateTxStatus.class);
if (definition.readOnly() && !existing.isReadOnly()) {
throw new TxException("Cannot join read-write tx as read-only");
}
return new HibernateTxStatus(
existing.session(),
false,
definition.readOnly(),
null,
existing.rollbackMarker()
);
}
@Override
public void commit(TxStatus status) {
HibernateTxStatus s = (HibernateTxStatus) status;
if (!s.isNewTransaction()) {
return;
}
try {
if (s.isRollbackOnly() && s.session().getTransaction().isActive()) {
s.session().getTransaction().rollback();
ResourceRegistry.fireSynchronizations(TxOutcome.ROLLED_BACK);
} else {
s.session().getTransaction().commit();
ResourceRegistry.fireSynchronizations(TxOutcome.COMMITTED);
}
} finally {
cleanupAndResume(s);
}
}
@Override
public void rollback(TxStatus status) {
HibernateTxStatus s = (HibernateTxStatus) status;
if (!s.isNewTransaction()) {
s.markRollbackOnly();
return;
}
try {
if (s.session().getTransaction().isActive()) {
s.session().getTransaction().rollback();
}
ResourceRegistry.fireSynchronizations(TxOutcome.ROLLED_BACK);
} finally {
cleanupAndResume(s);
}
}
private void cleanupAndResume(HibernateTxStatus status) {
ResourceRegistry.unbind(HIBERNATE_STATUS_KEY);
status.session().close();
HibernateTxStatus suspended = status.suspended();
if (suspended != null) {
ResourceRegistry.bind(HIBERNATE_STATUS_KEY, suspended);
}
}
}
@@ -0,0 +1,44 @@
package dev.relism.flash.ext.data.hibernate;
import dev.relism.flash.ext.data.core.TxStatus;
import org.hibernate.Session;
class HibernateTxStatus implements TxStatus {
static final class RollbackMarker {
boolean rollbackOnly;
}
private final Session session;
private final boolean newTransaction;
private final boolean readOnly;
private final HibernateTxStatus suspended;
private final RollbackMarker rollbackMarker;
HibernateTxStatus(
Session session,
boolean newTransaction,
boolean readOnly,
HibernateTxStatus suspended,
RollbackMarker rollbackMarker
) {
this.session = session;
this.newTransaction = newTransaction;
this.readOnly = readOnly;
this.suspended = suspended;
this.rollbackMarker = rollbackMarker;
}
@Override public boolean isNewTransaction() { return newTransaction; }
@Override public boolean isReadOnly() { return readOnly; }
@Override public boolean isRollbackOnly() { return rollbackMarker.rollbackOnly; }
@Override public void markRollbackOnly() { rollbackMarker.rollbackOnly = true; }
@Override
public <R> R resource(Class<R> type) {
return type.cast(session);
}
Session session() { return session; }
HibernateTxStatus suspended() { return suspended; }
RollbackMarker rollbackMarker() { return rollbackMarker; }
}
@@ -0,0 +1,68 @@
package dev.relism.flash.ext.data.hibernate;
import dev.relism.flash.ext.data.core.*;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class HibernateTxManagerTest {
static SessionFactory sf;
static HibernateTxManager manager;
@BeforeAll
static void setup() {
sf = TestHelper.buildSessionFactory();
manager = new HibernateTxManager(sf);
}
@AfterAll
static void teardown() {
if (sf != null) {
sf.close();
}
}
@AfterEach
void cleanup() {
ResourceRegistry.clear();
}
@Test
void required_starts_new_when_absent() {
TxStatus s = manager.begin(TxDefinition.DEFAULTS);
assertNotNull(s.resource(Session.class));
assertTrue(s.isNewTransaction());
assertDoesNotThrow(() -> manager.commit(s));
}
@Test
void required_joins_existing_when_present() {
TxStatus outer = manager.begin(TxDefinition.DEFAULTS);
TxStatus inner = manager.begin(TxDefinition.DEFAULTS.withPropagation(TransactionPropagation.REQUIRED));
assertSame(outer.resource(Session.class), inner.resource(Session.class));
manager.rollback(outer);
}
@Test
void requires_new_uses_separate_session() {
TxStatus outer = manager.begin(TxDefinition.DEFAULTS);
TxStatus inner = manager.begin(TxDefinition.DEFAULTS.withPropagation(TransactionPropagation.REQUIRES_NEW));
assertNotSame(outer.resource(Session.class), inner.resource(Session.class));
manager.commit(inner);
manager.rollback(outer);
}
@Test
void rollback_on_joined_marks_outer_rollback_only() {
TxStatus outer = manager.begin(TxDefinition.DEFAULTS);
TxStatus inner = manager.begin(TxDefinition.DEFAULTS.withPropagation(TransactionPropagation.REQUIRED));
manager.rollback(inner);
assertTrue(outer.isRollbackOnly());
manager.rollback(outer);
}
}
@@ -0,0 +1,24 @@
package dev.relism.flash.ext.data.hibernate;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
public class TestHelper {
public static SessionFactory buildSessionFactory() {
final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.applySetting("hibernate.connection.url", "jdbc:h2:mem:tx-hibernate;DB_CLOSE_DELAY=-1")
.applySetting("hibernate.connection.driver_class", "org.h2.Driver")
.applySetting("hibernate.dialect", "org.hibernate.dialect.H2Dialect")
.applySetting("hibernate.hbm2ddl.auto", "none")
.applySetting("hibernate.show_sql", "false")
.build();
try {
return new MetadataSources(registry).buildMetadata().buildSessionFactory();
} catch (Exception e) {
StandardServiceRegistryBuilder.destroy(registry);
throw e;
}
}
}