com.scalar.db.sql.springdata.twopc.ScalarDbTwoPcRepository Maven / Gradle / Ivy
package com.scalar.db.sql.springdata.twopc;
import com.scalar.db.sql.springdata.ScalarDbRepository;
import com.scalar.db.sql.springdata.exception.ScalarDbNonTransientException;
import com.scalar.db.sql.springdata.exception.ScalarDbTransientException;
import com.scalar.db.sql.springdata.exception.ScalarDbUnknownTransactionStateException;
import java.util.Collection;
import java.util.function.Supplier;
import javax.annotation.Nonnull;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.transaction.annotation.Transactional;
/**
* Spring Data fragment repository interface for two phase commit transaction in ScalarDB. The
* default Spring Transaction manager doesn't support two phase commit, so
* {@code @Transaction(transactionManager = "scalarDbSuspendableTransactionManager")} is also needed
* for the primitive APIs.
*
* @param a model class
*/
@NoRepositoryBean
public interface ScalarDbTwoPcRepository
extends CrudRepository, ScalarDbRepository {
// Primitive 2PC APIs
String begin();
void prepare();
void validate();
void suspend();
void commit();
void rollback();
void join(String transactionId);
void resume(String transactionId);
// High level abstract 2PC APIs
// Common
/**
* Execute oneshot operations in two-phase commit mode. The operations passed as an argument are
* executed in a transaction. All the transactional operations (e.g, BEGIN, PREPARE and COMMIT)
* are automatically executed.
*
* @param oneshotOperations CRUD operations to be executed in a transaction
* @param a result type returned from {@code oneshotOperations}
* @return result of the CRUD operations
*/
@Transactional(transactionManager = "scalarDbSuspendableTransactionManager")
default T executeOneshotOperations(@Nonnull Supplier oneshotOperations) {
begin();
T result = oneshotOperations.get();
prepare();
validate();
commit();
return result;
}
// API for coordinators
/**
* Execute a 2PC transaction on the coordinator service. See {@code executeTwoPcTransaction}
* above. This method starts a 2PC transaction with an auto generated transaction ID.
*
* @param executionPhaseOps execution phase task containing both local and remote operations
* (local CRUD, remote service call and so on.) This task is also supposed to start
* transactions with a given transaction ID on remote participants. Local and remote
* operations will be rolled back if any exception is thrown
* @param remotePrepareCommitPhaseOps a collection of remote 2PC operations (prepare, validate,
* commit and rollback) for all the remote participants
* @param a result type returned from {@code executionPhaseOps}
* @throws ScalarDbUnknownTransactionStateException if the 2PC transaction fails due to a
* non-transient cause with an unknown final status. The exception contains {@code
* transactionId}. Note that the final 2PC transaction status is unknown. Whether the
* transaction is actually committed or not needs to be decided by the application side (e.g.
* check if the target record is expectedly updated)
* @throws ScalarDbNonTransientException if the 2PC transaction fails due to a non-transient
* cause. The exception contains {@code transactionId}
* @throws ScalarDbTransientException if the 2PC transaction fails due to a transient cause. The
* exception contains {@code transactionId}
* @return a return value from {@code executionPhaseOps} that can contain any result of local
* operation and/or remote operations
*/
@Transactional(transactionManager = "scalarDbSuspendableTransactionManager")
default TwoPcResult executeTwoPcTransaction(
ExecutionPhaseOperations executionPhaseOps,
Collection remotePrepareCommitPhaseOps) {
String transactionId = begin();
return TwoPcOperationsProcessor.create()
.execute(
transactionId,
executionPhaseOps,
LocalPrepareCommitPhaseOperations.createSerializable(
this::prepare, this::validate, this::commit, this::rollback),
RemotePrepareCommitOperationsProcessor.create(true, remotePrepareCommitPhaseOps));
}
// API for participants
/**
* Join the transaction, execute the CRUD operations and suspend the transaction on the
* participant service. This API should be called first, and then {@code
* prepareTransactionOnParticipant} and following APIs are supposed to be called.
*
* @param transactionId transaction ID
* @param crudOperations CRUD operations as a part of execution phase
* @param a result type returned from {@code crudOperations}
* @return result of the CRUD operations
*/
@Transactional(transactionManager = "scalarDbSuspendableTransactionManager")
default T joinTransactionOnParticipant(
String transactionId, @Nonnull Supplier crudOperations) {
join(transactionId);
T result = crudOperations.get();
suspend();
return result;
}
/**
* Resume the transaction, execute the CRUD operations and suspend the transaction on the
* participant service. This API can be called after {@code joinTransactionOnParticipant} before
* {@code prepareTransactionOnParticipant} if needed.
*
* @param transactionId transaction ID
* @param crudOperations CRUD operations as a part of execution phase
* @param a result type returned from {@code crudOperations}
* @return result of the CRUD operations
*/
@Transactional(transactionManager = "scalarDbSuspendableTransactionManager")
default T resumeTransactionOnParticipant(
String transactionId, @Nonnull Supplier crudOperations) {
resume(transactionId);
T result = crudOperations.get();
suspend();
return result;
}
/**
* Prepare the transaction and suspend the transaction on the participant service. This API should
* be called after {@code joinTransactionOnParticipant}, and then {@code
* validateTransactionOnParticipant} and following APIs are supposed to be called.
*
* @param transactionId transaction ID
*/
@Transactional(transactionManager = "scalarDbSuspendableTransactionManager")
default void prepareTransactionOnParticipant(String transactionId) {
resume(transactionId);
prepare();
suspend();
}
/**
* Validate the transaction and suspend the transaction on the participant service. This API
* should be called after {@code prepareTransactionOnParticipant}, and then {@code
* commitTransactionOnParticipant} or {@code rollbackTransactionOnParticipant} is supposed to be
* called. This is needed only if `scalar.db.consensus_commit.isolation_level` is `SERIALIZABLE`
* and `scalar.db.consensus_commit.serializable_strategy` is `EXTRA_READ`
*
* @param transactionId transaction ID
*/
@Transactional(transactionManager = "scalarDbSuspendableTransactionManager")
default void validateTransactionOnParticipant(String transactionId) {
resume(transactionId);
validate();
suspend();
}
/**
* Commit the transaction on the participant service. This API should be called after {@code
* prepareTransactionOnParticipant} or {@code validateTransactionOnParticipant}, depending on the
* transaction manager configurations.
*
* @param transactionId transaction ID
*/
@Transactional(transactionManager = "scalarDbSuspendableTransactionManager")
default void commitTransactionOnParticipant(String transactionId) {
resume(transactionId);
commit();
}
/**
* Rollback the transaction on the participant service. This API should be called after {@code
* prepareTransactionOnParticipant} or {@code validateTransactionOnParticipant}, depending on the
* transaction manager configurations.
*
* @param transactionId transaction ID
*/
@Transactional(transactionManager = "scalarDbSuspendableTransactionManager")
default void rollbackTransactionOnParticipant(String transactionId) {
resume(transactionId);
rollback();
}
}