feat: introduce WebSocket support with new endpoints and transaction propagation enhancements
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# flash-ext-data-core
|
||||
|
||||
Core comune per il layer dati di Flash.
|
||||
|
||||
## Scopo
|
||||
|
||||
Questo modulo definisce il contratto transazionale condiviso tra le implementazioni backend.
|
||||
Non parla con Hibernate o JDBC direttamente: espone solo astrazioni e un runtime minimale.
|
||||
|
||||
## Componenti
|
||||
|
||||
- `TxDefinition`: metadata immutabile della transazione.
|
||||
- `TxStatus`: stato runtime restituito dal manager.
|
||||
- `TxManager`: contratto per `begin`, `commit`, `rollback`.
|
||||
- `Tx`: orchestration runtime e stack transazionale per thread.
|
||||
- `ResourceRegistry`: storage thread-local di risorse e synchronizations.
|
||||
- `Repository<T, ID>`: base repository auto-transazionale.
|
||||
- `Spec<T>`: predicato componibile.
|
||||
- `Query<T>`: oggetto query con spec, sort e paging.
|
||||
- `SpecBuilder<T>`: DSL fluente per costruire spec tipizzate.
|
||||
- `RepositorySupport<T, ID>`: helper interno condiviso.
|
||||
- `TransactionPropagation`: semantica di propagazione.
|
||||
- `TransactionIsolation`: livello di isolamento.
|
||||
- `TxSynchronization`: hook lifecycle.
|
||||
|
||||
## Modello di esecuzione
|
||||
|
||||
Il flusso è:
|
||||
|
||||
1. `Tx.call(definition, work)` chiama `TxManager.begin(definition)`.
|
||||
2. Il `TxManager` crea un `TxStatus` backend-specific.
|
||||
3. Lo status viene pushato nello stack thread-local.
|
||||
4. Il lavoro usa `Tx.resource(Class)` per ottenere la risorsa corrente.
|
||||
5. A fine lavoro `Tx` decide tra `commit` e `rollback`.
|
||||
6. Lo stack viene poppato e il thread-local viene pulito se vuoto.
|
||||
|
||||
## Propagation supportata
|
||||
|
||||
- `REQUIRED`: usa la tx attiva oppure ne apre una nuova.
|
||||
- `REQUIRES_NEW`: sospende la tx corrente e apre una nuova tx.
|
||||
- `SUPPORTS`: se esiste una tx attiva si aggancia, altrimenti esegue senza tx.
|
||||
- `NOT_SUPPORTED`: sospende la tx corrente ed esegue senza tx.
|
||||
- `MANDATORY`: richiede una tx attiva.
|
||||
|
||||
## Uso di `Repository`
|
||||
|
||||
`Repository` è la base comune per le repository concrete.
|
||||
Ogni operazione pubblica usa internamente una tx `REQUIRED` o `REQUIRED` read-only.
|
||||
|
||||
Le sottoclassi implementano i metodi `doXxx(...)` del nuovo modello:
|
||||
|
||||
- `doFind(Query<T>)`
|
||||
- `doFindOne(Spec<T>)`
|
||||
- `doFindPage(Query<T>)`
|
||||
- `doDeleteAll(Spec<T>)`
|
||||
- `doUpdateAll(Spec<T>, T)`
|
||||
|
||||
I vecchi overload di `findAll(...)` e `findPage(...)` sono stati ridotti a una combinazione di `Query<T>` e `Spec<T>`.
|
||||
|
||||
```java
|
||||
public abstract class Repository<T, ID> {
|
||||
protected Repository(Tx tx) { ... }
|
||||
protected final <R> R tx(Tx.TxCallable<R> work) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
## Composizione con Flash
|
||||
|
||||
`DataExtension` registra:
|
||||
|
||||
- `Tx` nel `FlashContext`
|
||||
- `TxManager` nel `FlashContext`
|
||||
- un annotation processor per `@Transactional`
|
||||
|
||||
Questo rende il layer dati componibile con il sistema di extension di Flash senza stato globale.
|
||||
|
||||
## Note implementative
|
||||
|
||||
- Lo stack transazionale è thread-local e viene ripulito quando torna vuoto.
|
||||
- Le risorse backend sono sospese e ripristinate per `REQUIRES_NEW` e `NOT_SUPPORTED`.
|
||||
- `TxSynchronization` è il punto di aggancio per hook di commit/rollback/completion.
|
||||
+6
-4
@@ -7,7 +7,6 @@ import dev.relism.flash.ext.data.core.TransactionPropagation;
|
||||
import dev.relism.flash.extension.ExtensionPhase;
|
||||
import dev.relism.flash.extension.FlashContext;
|
||||
import dev.relism.flash.extension.FlashExtension;
|
||||
import dev.relism.flash.extension.FlashRegistrar;
|
||||
import dev.relism.flash.routing.Middleware;
|
||||
import jakarta.transaction.Transactional;
|
||||
|
||||
@@ -16,14 +15,16 @@ import java.util.Objects;
|
||||
|
||||
public final class DataExtension implements FlashExtension {
|
||||
private final TxManager txManager;
|
||||
private final Tx tx;
|
||||
|
||||
public DataExtension(TxManager txManager) {
|
||||
this.txManager = Objects.requireNonNull(txManager);
|
||||
this.tx = new Tx(txManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provide(FlashContext ctx) {
|
||||
Tx.init(txManager);
|
||||
ctx.provide(Tx.class, tx);
|
||||
ctx.provide(TxManager.class, txManager);
|
||||
ctx.addAnnotationProcessor(handlerClass -> {
|
||||
Transactional ann = handlerClass.getAnnotation(Transactional.class);
|
||||
@@ -33,7 +34,7 @@ public final class DataExtension implements FlashExtension {
|
||||
TxDefinition definition = TxDefinition.DEFAULTS
|
||||
.withPropagation(mapTxType(ann.value()));
|
||||
Middleware middleware = next -> (req, res) -> {
|
||||
return Tx.call(definition, () -> next.handle(req, res));
|
||||
return tx.call(definition, () -> next.handle(req, res));
|
||||
};
|
||||
return List.of(middleware);
|
||||
});
|
||||
@@ -46,8 +47,9 @@ public final class DataExtension implements FlashExtension {
|
||||
|
||||
private TransactionPropagation mapTxType(Transactional.TxType txType) {
|
||||
return switch (txType) {
|
||||
case REQUIRED, SUPPORTS -> TransactionPropagation.REQUIRED;
|
||||
case REQUIRED -> TransactionPropagation.REQUIRED;
|
||||
case REQUIRES_NEW -> TransactionPropagation.REQUIRES_NEW;
|
||||
case SUPPORTS -> TransactionPropagation.SUPPORTS;
|
||||
case MANDATORY -> TransactionPropagation.MANDATORY;
|
||||
case NOT_SUPPORTED, NEVER -> TransactionPropagation.NOT_SUPPORTED;
|
||||
};
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package dev.relism.flash.ext.data.core;
|
||||
|
||||
public record Query<T>(Spec<T> spec, Sort sort, Integer page, Integer size) {
|
||||
public Query {
|
||||
spec = spec == null ? Spec.all() : spec;
|
||||
sort = sort == null ? Sort.unsorted() : sort;
|
||||
}
|
||||
|
||||
public static <T> Query<T> all() {
|
||||
return new Query<>(Spec.all(), Sort.unsorted(), null, null);
|
||||
}
|
||||
|
||||
public Query<T> where(Spec<T> spec) {
|
||||
return new Query<>(spec, sort, page, size);
|
||||
}
|
||||
|
||||
public Query<T> orderBy(Sort sort) {
|
||||
return new Query<>(spec, sort, page, size);
|
||||
}
|
||||
|
||||
public Query<T> page(int page, int size) {
|
||||
return new Query<>(spec, sort, page, size);
|
||||
}
|
||||
|
||||
public boolean isPaged() {
|
||||
return page != null && size != null;
|
||||
}
|
||||
}
|
||||
+100
-91
@@ -4,109 +4,118 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Base repository. Subclasses only extend this — never HibernateRepository
|
||||
* or JdbcRepository directly. The concrete backing is transparent.
|
||||
*
|
||||
* Every method auto-wraps in REQUIRED transaction — safe to call with or
|
||||
* without an active transaction on the thread.
|
||||
*/
|
||||
public abstract class Repository<T, ID> {
|
||||
public abstract class Repository<T, ID> extends RepositorySupport<T, ID> {
|
||||
|
||||
private final TxDefinition required = TxDefinition.DEFAULTS
|
||||
.withPropagation(TransactionPropagation.REQUIRED);
|
||||
|
||||
// ── CRUD ──────────────────────────────────────────────────────────────────
|
||||
protected Repository(Tx tx) {
|
||||
super(tx);
|
||||
}
|
||||
|
||||
public Optional<T> findById(ID id) {
|
||||
return tx(() -> doFindById(id));
|
||||
}
|
||||
|
||||
public List<T> findAll() {
|
||||
return tx(this::doFindAll);
|
||||
}
|
||||
|
||||
public List<T> findAll(int page, int size) {
|
||||
return tx(() -> doFindAll(page, size));
|
||||
}
|
||||
|
||||
public List<T> findAll(Sort sort) {
|
||||
return tx(() -> doFindAll(sort));
|
||||
}
|
||||
|
||||
public List<T> findAll(int page, int size, Sort sort) {
|
||||
return tx(() -> doFindAll(page, size, sort));
|
||||
}
|
||||
|
||||
public Page<T> findPage(int page, int size) {
|
||||
return tx(() -> doFindPage(page, size));
|
||||
}
|
||||
|
||||
public Page<T> findPage(int page, int size, Sort sort) {
|
||||
return tx(() -> doFindPage(page, size, sort));
|
||||
}
|
||||
|
||||
public T save(T entity) {
|
||||
return tx(() -> doSave(entity));
|
||||
}
|
||||
|
||||
public List<T> saveAll(Iterable<T> entities) {
|
||||
return tx(() -> {
|
||||
List<T> saved = new ArrayList<>();
|
||||
for (T e : entities) saved.add(doSave(e));
|
||||
return saved;
|
||||
});
|
||||
}
|
||||
|
||||
public T update(T entity) {
|
||||
return tx(() -> doUpdate(entity));
|
||||
}
|
||||
|
||||
public void delete(T entity) {
|
||||
tx(() -> { doDelete(entity); return null; });
|
||||
}
|
||||
|
||||
public void deleteById(ID id) {
|
||||
tx(() -> { doDeleteById(id); return null; });
|
||||
}
|
||||
|
||||
public void deleteAll(Iterable<T> entities) {
|
||||
tx(() -> { entities.forEach(this::doDelete); return null; });
|
||||
return roQuery(() -> doFindById(id));
|
||||
}
|
||||
|
||||
public boolean existsById(ID id) {
|
||||
return tx(() -> doExistsById(id));
|
||||
return roQuery(() -> doExistsById(id));
|
||||
}
|
||||
|
||||
public long count() {
|
||||
return tx(this::doCount);
|
||||
return roQuery(this::doCount);
|
||||
}
|
||||
|
||||
// ── Auto-wrap helper ──────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Ensures the work runs inside a transaction.
|
||||
* If one is already active (caller annotated @Transactional or inside Tx.run)
|
||||
* it joins it — no new connection opened.
|
||||
* If none is active it opens one, commits, and closes it transparently.
|
||||
*/
|
||||
protected final <R> R tx(Tx.TxCallable<R> work) {
|
||||
return Tx.call(required, work);
|
||||
public List<T> findAll() {
|
||||
return findAll(Query.all());
|
||||
}
|
||||
|
||||
// ── Abstract — implemented by HibernateRepository / JdbcRepository ────────
|
||||
public List<T> findAll(Spec<T> spec) {
|
||||
return findAll(Query.<T>all().where(spec));
|
||||
}
|
||||
|
||||
public List<T> findAll(Query<T> query) {
|
||||
return roQuery(() -> doFind(query));
|
||||
}
|
||||
|
||||
public Page<T> findPage(Query<T> query) {
|
||||
return roQuery(() -> doFindPage(query));
|
||||
}
|
||||
|
||||
public Optional<T> findOne(Spec<T> spec) {
|
||||
return roQuery(() -> doFindOne(spec));
|
||||
}
|
||||
|
||||
public T save(T entity) {
|
||||
return rwQuery(() -> doSave(entity));
|
||||
}
|
||||
|
||||
public T update(T entity) {
|
||||
return rwQuery(() -> doUpdate(entity));
|
||||
}
|
||||
|
||||
public List<T> saveAll(Iterable<T> entities) {
|
||||
return rwQuery(() -> doSaveAll(entities));
|
||||
}
|
||||
|
||||
public void delete(T entity) {
|
||||
rwQuery(() -> {
|
||||
doDelete(entity);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public void deleteById(ID id) {
|
||||
rwQuery(() -> {
|
||||
doDeleteById(id);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public int deleteAll(Spec<T> spec) {
|
||||
return rwQuery(() -> doDeleteAll(spec));
|
||||
}
|
||||
|
||||
public int updateAll(Spec<T> spec, T patch) {
|
||||
return rwQuery(() -> doUpdateAll(spec, patch));
|
||||
}
|
||||
|
||||
public List<T> findAll(int page, int size) {
|
||||
return findAll(Query.<T>all().page(page, size));
|
||||
}
|
||||
|
||||
public List<T> findAll(Sort sort) {
|
||||
return findAll(Query.<T>all().orderBy(sort));
|
||||
}
|
||||
|
||||
public List<T> findAll(int page, int size, Sort sort) {
|
||||
return findAll(Query.<T>all().orderBy(sort).page(page, size));
|
||||
}
|
||||
|
||||
public Page<T> findPage(int page, int size) {
|
||||
return findPage(Query.<T>all().page(page, size));
|
||||
}
|
||||
|
||||
public Page<T> findPage(int page, int size, Sort sort) {
|
||||
return findPage(Query.<T>all().orderBy(sort).page(page, size));
|
||||
}
|
||||
|
||||
public void deleteAll(Iterable<T> entities) {
|
||||
rwQuery(() -> {
|
||||
for (T entity : entities) {
|
||||
doDelete(entity);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract Optional<T> doFindById(ID id);
|
||||
protected abstract List<T> doFindAll();
|
||||
protected abstract List<T> doFindAll(int page, int size);
|
||||
protected abstract List<T> doFindAll(Sort sort);
|
||||
protected abstract List<T> doFindAll(int page, int size, Sort sort);
|
||||
protected abstract Page<T> doFindPage(int page, int size);
|
||||
protected abstract Page<T> doFindPage(int page, int size, Sort sort);
|
||||
protected abstract T doSave(T entity);
|
||||
protected abstract T doUpdate(T entity);
|
||||
protected abstract void doDelete(T entity);
|
||||
protected abstract void doDeleteById(ID id);
|
||||
protected abstract boolean doExistsById(ID id);
|
||||
protected abstract long doCount();
|
||||
}
|
||||
protected abstract List<T> doFind(Query<T> query);
|
||||
protected abstract Optional<T> doFindOne(Spec<T> spec);
|
||||
protected abstract Page<T> doFindPage(Query<T> query);
|
||||
protected abstract boolean doExistsById(ID id);
|
||||
protected abstract long doCount();
|
||||
protected abstract T doSave(T entity);
|
||||
protected abstract List<T> doSaveAll(Iterable<T> entities);
|
||||
protected abstract T doUpdate(T entity);
|
||||
protected abstract void doDelete(T entity);
|
||||
protected abstract void doDeleteById(ID id);
|
||||
protected abstract int doDeleteAll(Spec<T> spec);
|
||||
protected abstract int doUpdateAll(Spec<T> spec, T patch);
|
||||
}
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package dev.relism.flash.ext.data.core;
|
||||
|
||||
public abstract class RepositorySupport<T, ID> {
|
||||
private final Tx tx;
|
||||
private final TxDefinition rw = TxDefinition.DEFAULTS
|
||||
.withPropagation(TransactionPropagation.REQUIRED);
|
||||
private final TxDefinition ro = rw.asReadOnly();
|
||||
|
||||
protected RepositorySupport(Tx tx) {
|
||||
this.tx = tx;
|
||||
}
|
||||
|
||||
protected final Tx tx() {
|
||||
return tx;
|
||||
}
|
||||
|
||||
protected final <R> R roQuery(Tx.TxCallable<R> work) {
|
||||
return tx.call(ro, work);
|
||||
}
|
||||
|
||||
protected final <R> R rwQuery(Tx.TxCallable<R> work) {
|
||||
return tx.call(rw, work);
|
||||
}
|
||||
}
|
||||
+5
@@ -31,6 +31,11 @@ public final class ResourceRegistry {
|
||||
SYNCHRONIZATIONS.get().clear();
|
||||
}
|
||||
|
||||
public static void cleanup() {
|
||||
RESOURCES.remove();
|
||||
SYNCHRONIZATIONS.remove();
|
||||
}
|
||||
|
||||
public static <R> R get(TxResourceKey key, Class<R> type) {
|
||||
Object value = RESOURCES.get().get(key);
|
||||
if (value == null) {
|
||||
|
||||
+5
-1
@@ -7,6 +7,10 @@ public record Sort(List<Column> columns) {
|
||||
|
||||
public record Column(String column, boolean asc) {}
|
||||
|
||||
public static Sort unsorted() { return new Sort(List.of()); }
|
||||
|
||||
public boolean isSorted() { return !columns.isEmpty(); }
|
||||
|
||||
public static Sort by(String column) { return new Sort(List.of(new Column(column, true))); }
|
||||
public static Sort desc(String column) { return new Sort(List.of(new Column(column, false))); }
|
||||
public static Sort by(String col, boolean asc){ return new Sort(List.of(new Column(col, asc))); }
|
||||
@@ -19,4 +23,4 @@ public record Sort(List<Column> columns) {
|
||||
next.add(new Column(column, asc));
|
||||
return new Sort(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package dev.relism.flash.ext.data.core;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Spec<T> {
|
||||
String toFragment(SpecContext ctx);
|
||||
|
||||
default Spec<T> and(Spec<T> other) {
|
||||
return ctx -> "(" + this.toFragment(ctx) + " AND " + other.toFragment(ctx) + ")";
|
||||
}
|
||||
|
||||
default Spec<T> or(Spec<T> other) {
|
||||
return ctx -> "(" + this.toFragment(ctx) + " OR " + other.toFragment(ctx) + ")";
|
||||
}
|
||||
|
||||
default Spec<T> not() {
|
||||
return ctx -> "NOT (" + this.toFragment(ctx) + ")";
|
||||
}
|
||||
|
||||
static <T> Spec<T> all() {
|
||||
return ctx -> "1=1";
|
||||
}
|
||||
|
||||
static <T> Spec<T> none() {
|
||||
return ctx -> "1=0";
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package dev.relism.flash.ext.data.core;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class SpecBuilder<T> {
|
||||
private SpecBuilder() {}
|
||||
|
||||
public static <T, V> FieldSpec<T, V> field(String column) {
|
||||
return new FieldSpec<>(column);
|
||||
}
|
||||
|
||||
public static final class FieldSpec<T, V> {
|
||||
private final String column;
|
||||
|
||||
private FieldSpec(String column) {
|
||||
this.column = Objects.requireNonNull(column);
|
||||
}
|
||||
|
||||
public Spec<T> eq(V value) { return ctx -> column + " = " + ctx.bind(value); }
|
||||
public Spec<T> neq(V value) { return ctx -> column + " != " + ctx.bind(value); }
|
||||
public Spec<T> like(String pattern) { return ctx -> column + " like " + ctx.bind(pattern); }
|
||||
public Spec<T> isNull() { return ctx -> column + " is null"; }
|
||||
public Spec<T> isNotNull() { return ctx -> column + " is not null"; }
|
||||
|
||||
public Spec<T> in(Collection<V> values) {
|
||||
return ctx -> column + " in (" + values.stream().map(ctx::bind).collect(Collectors.joining(", ")) + ")";
|
||||
}
|
||||
|
||||
public <C extends Comparable<C>> Spec<T> gt(C value) { return ctx -> column + " > " + ctx.bind(value); }
|
||||
public <C extends Comparable<C>> Spec<T> lt(C value) { return ctx -> column + " < " + ctx.bind(value); }
|
||||
public <C extends Comparable<C>> Spec<T> between(C lo, C hi) {
|
||||
return ctx -> column + " between " + ctx.bind(lo) + " and " + ctx.bind(hi);
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package dev.relism.flash.ext.data.core;
|
||||
|
||||
public interface SpecContext {
|
||||
String bind(Object value);
|
||||
}
|
||||
+1
@@ -3,6 +3,7 @@ package dev.relism.flash.ext.data.core;
|
||||
public enum TransactionPropagation {
|
||||
REQUIRED,
|
||||
REQUIRES_NEW,
|
||||
SUPPORTS,
|
||||
NOT_SUPPORTED,
|
||||
MANDATORY
|
||||
}
|
||||
|
||||
+38
-31
@@ -2,83 +2,78 @@ package dev.relism.flash.ext.data.core;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class Tx {
|
||||
private static final ThreadLocal<Deque<TxStatus>> STATUS_STACK =
|
||||
ThreadLocal.withInitial(ArrayDeque::new);
|
||||
private static volatile TxManager manager;
|
||||
private final TxManager manager;
|
||||
|
||||
private Tx() {}
|
||||
|
||||
public static void init(TxManager txManager) {
|
||||
if (manager != null) {
|
||||
throw new IllegalStateException("TxManager already initialized");
|
||||
}
|
||||
manager = txManager;
|
||||
public Tx(TxManager txManager) {
|
||||
this.manager = Objects.requireNonNull(txManager);
|
||||
}
|
||||
|
||||
public static void run(TxRunnable work) {
|
||||
public void run(TxRunnable work) {
|
||||
run(TxDefinition.DEFAULTS, work);
|
||||
}
|
||||
|
||||
public static void run(TxDefinition definition, TxRunnable work) {
|
||||
public void run(TxDefinition definition, TxRunnable work) {
|
||||
call(definition, () -> {
|
||||
work.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> T call(TxCallable<T> work) {
|
||||
public <T> T call(TxCallable<T> work) {
|
||||
return call(TxDefinition.DEFAULTS, work);
|
||||
}
|
||||
|
||||
public static <T> T call(TxDefinition definition, TxCallable<T> work) {
|
||||
TxStatus status = manager().begin(definition);
|
||||
public <T> T call(TxDefinition definition, TxCallable<T> work) {
|
||||
TxStatus status = manager.begin(definition);
|
||||
pushStatus(status);
|
||||
try {
|
||||
T result = work.call();
|
||||
if (status.isRollbackOnly()) {
|
||||
manager().rollback(status);
|
||||
manager.rollback(status);
|
||||
} else {
|
||||
manager().commit(status);
|
||||
manager.commit(status);
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
manager().rollback(status);
|
||||
silentRollback(status);
|
||||
throw (e instanceof TxException txException) ? txException : new TxException(e);
|
||||
} catch (Throwable t) {
|
||||
silentRollback(status);
|
||||
throw sneakyThrow(t);
|
||||
} finally {
|
||||
popStatus();
|
||||
if (STATUS_STACK.get().isEmpty()) {
|
||||
STATUS_STACK.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isActive() {
|
||||
public boolean isActive() {
|
||||
return !STATUS_STACK.get().isEmpty();
|
||||
}
|
||||
|
||||
public static void setRollbackOnly() {
|
||||
public void setRollbackOnly() {
|
||||
currentStatus().markRollbackOnly();
|
||||
}
|
||||
|
||||
public static <R> R resource(Class<R> type) {
|
||||
public <R> R resource(Class<R> type) {
|
||||
return currentStatus().resource(type);
|
||||
}
|
||||
|
||||
public static TxDefinition requiresNew() {
|
||||
public TxDefinition requiresNew() {
|
||||
return TxDefinition.DEFAULTS.withPropagation(TransactionPropagation.REQUIRES_NEW);
|
||||
}
|
||||
|
||||
public static TxDefinition readOnly() {
|
||||
public TxDefinition readOnly() {
|
||||
return TxDefinition.DEFAULTS.asReadOnly();
|
||||
}
|
||||
|
||||
private static TxManager manager() {
|
||||
if (manager == null) {
|
||||
throw new IllegalStateException("No TxManager installed");
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
|
||||
private static TxStatus currentStatus() {
|
||||
private TxStatus currentStatus() {
|
||||
TxStatus status = STATUS_STACK.get().peek();
|
||||
if (status == null) {
|
||||
throw new IllegalStateException("No active transaction");
|
||||
@@ -86,17 +81,29 @@ public final class Tx {
|
||||
return status;
|
||||
}
|
||||
|
||||
private static void pushStatus(TxStatus status) {
|
||||
private void pushStatus(TxStatus status) {
|
||||
STATUS_STACK.get().push(status);
|
||||
}
|
||||
|
||||
private static void popStatus() {
|
||||
private void popStatus() {
|
||||
Deque<TxStatus> stack = STATUS_STACK.get();
|
||||
if (!stack.isEmpty()) {
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
private void silentRollback(TxStatus status) {
|
||||
try {
|
||||
manager.rollback(status);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E extends Throwable> RuntimeException sneakyThrow(Throwable t) throws E {
|
||||
throw (E) t;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface TxRunnable {
|
||||
void run();
|
||||
|
||||
Reference in New Issue
Block a user