KaumeiTxDefinition.java
/*
* SPDX-FileCopyrightText: 2026 kaumei.io
* SPDX-License-Identifier: Apache-2.0
*/
package io.kaumei.jdbc.tx;
import io.kaumei.jdbc.annotation.KaumeiTx;
import static java.util.Objects.requireNonNull;
public record KaumeiTxDefinition(
Isolation isolation,
ReadOnly readOnly,
ExceptionMatcher rollbackOn,
ExceptionMatcher dontRollbackOn) {
public static final ExceptionMatcher NO_THROWABLES = exception -> false;
public static final ExceptionMatcher ALL_THROWABLES = exception -> true;
public static final KaumeiTxDefinition DEFAULT = new KaumeiTxDefinition(
Isolation.DEFAULT,
ReadOnly.DEFAULT,
NO_THROWABLES,
NO_THROWABLES);
@FunctionalInterface
public interface ExceptionMatcher {
boolean matches(Throwable exception);
}
public KaumeiTxDefinition {
requireNonNull(isolation, "isolation");
requireNonNull(readOnly, "readOnly");
requireNonNull(rollbackOn, "rollbackOn");
requireNonNull(dontRollbackOn, "dontRollbackOn");
}
public enum Isolation {
DEFAULT,
READ_UNCOMMITTED,
READ_COMMITTED,
REPEATABLE_READ,
SERIALIZABLE
}
public enum ReadOnly {
DEFAULT,
READ_ONLY,
READ_WRITE
}
public static Builder builder() {
return new Builder();
}
/**
* Creates a transaction definition from the non-propagation attributes of an annotation.
* @param annotation transaction annotation to convert
* @return the corresponding transaction definition
*/
public static KaumeiTxDefinition from(KaumeiTx annotation) {
requireNonNull(annotation, "annotation");
Class<? extends Throwable>[] rollbackOn = annotation.rollbackOn();
Class<? extends Throwable>[] dontRollbackOn = annotation.dontRollbackOn();
if (annotation.isolation() == KaumeiTx.Isolation.DEFAULT
&& annotation.readOnly() == KaumeiTx.ReadOnly.DEFAULT
&& rollbackOn.length == 0
&& dontRollbackOn.length == 0) {
return DEFAULT;
}
return new KaumeiTxDefinition(
isolation(annotation.isolation()),
readOnly(annotation.readOnly()),
matcher(rollbackOn),
matcher(dontRollbackOn));
}
private static Isolation isolation(KaumeiTx.Isolation isolation) {
return switch (isolation) {
case DEFAULT -> Isolation.DEFAULT;
case READ_UNCOMMITTED -> Isolation.READ_UNCOMMITTED;
case READ_COMMITTED -> Isolation.READ_COMMITTED;
case REPEATABLE_READ -> Isolation.REPEATABLE_READ;
case SERIALIZABLE -> Isolation.SERIALIZABLE;
};
}
private static ReadOnly readOnly(KaumeiTx.ReadOnly readOnly) {
return switch (readOnly) {
case DEFAULT -> ReadOnly.DEFAULT;
case READ_ONLY -> ReadOnly.READ_ONLY;
case READ_WRITE -> ReadOnly.READ_WRITE;
};
}
private static ExceptionMatcher matcher(Class<? extends Throwable>[] exceptionTypes) {
if (exceptionTypes.length == 0) {
return NO_THROWABLES;
}
Class<? extends Throwable>[] types = exceptionTypes.clone();
return exception -> {
for (Class<? extends Throwable> type : types) {
if (type.isInstance(exception)) {
return true;
}
}
return false;
};
}
public static final class Builder {
private Isolation isolation = Isolation.DEFAULT;
private ReadOnly readOnly = ReadOnly.DEFAULT;
private ExceptionMatcher rollbackOn = NO_THROWABLES;
private ExceptionMatcher dontRollbackOn = NO_THROWABLES;
public Builder isolation(Isolation isolation) {
this.isolation = requireNonNull(isolation, "isolation");
return this;
}
public Builder readOnly(ReadOnly readOnly) {
this.readOnly = requireNonNull(readOnly, "readOnly");
return this;
}
public Builder rollbackOn(ExceptionMatcher rollbackOn) {
this.rollbackOn = requireNonNull(rollbackOn, "rollbackOn");
return this;
}
public Builder dontRollbackOn(ExceptionMatcher dontRollbackOn) {
this.dontRollbackOn = requireNonNull(dontRollbackOn, "dontRollbackOn");
return this;
}
public KaumeiTxDefinition build() {
return new KaumeiTxDefinition(isolation, readOnly, rollbackOn, dontRollbackOn);
}
}
}