package io.ebeaninternal.server.transaction;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.TransactionEvent;
import io.ebeaninternal.api.TxnProfileEventCodes;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
Read only transaction expected to use autoCommit connection and for implicit use only.
This transaction is created implicitly and not expected to be exposed to application code and has
none of the features for supporting inserts, updates and deletes etc (and throws errors if those
persisting features are attempted to be used - which is not expected).
/**
* Read only transaction expected to use autoCommit connection and for implicit use only.
* <p>
* This transaction is created implicitly and not expected to be exposed to application code and has
* none of the features for supporting inserts, updates and deletes etc (and throws errors if those
* persisting features are attempted to be used - which is not expected).
* </p>
*/
class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCodes {
private static final Logger logger = LoggerFactory.getLogger(ImplicitReadOnlyTransaction.class);
private static final String illegalStateMessage = "Transaction is Inactive";
private static final String notExpectedMessage = "Not expected on read only transaction";
private final TransactionManager manager;
private final boolean logSql;
private final boolean logSummary;
The status of the transaction.
/**
* The status of the transaction.
*/
private boolean active;
The underlying Connection which is expected to use autoCommit such that we avoid the
explicit commit call at the end of the 'transaction' (for performance).
/**
* The underlying Connection which is expected to use autoCommit such that we avoid the
* explicit commit call at the end of the 'transaction' (for performance).
*/
private Connection connection;
Holder of the objects fetched to ensure unique objects are used.
/**
* Holder of the objects fetched to ensure unique objects are used.
*/
private PersistenceContext persistenceContext;
private Object tenantId;
private Map<String, Object> userObjects;
private final long startNanos;
private ProfileLocation profileLocation;
Create without a tenantId.
/**
* Create without a tenantId.
*/
ImplicitReadOnlyTransaction(TransactionManager manager, Connection connection) {
this.manager = manager;
this.logSql = manager.isLogSql();
this.logSummary = manager.isLogSummary();
this.active = true;
this.connection = connection;
this.persistenceContext = new DefaultPersistenceContext();
this.startNanos = System.nanoTime();
}
Create with a tenantId.
/**
* Create with a tenantId.
*/
ImplicitReadOnlyTransaction(TransactionManager manager, Connection connection, Object tenantId) {
this(manager, connection);
this.tenantId = tenantId;
}
@Override
public long getStartNanoTime() {
// not used on read only transaction
return startNanos;
}
@Override
public void setLabel(String label) {
// do nothing
}
@Override
public String getLabel() {
return null;
}
@Override
public long profileOffset() {
return 0;
}
@Override
public void profileEvent(SpiProfileTransactionEvent event) {
// do nothing
}
@Override
public void setProfileStream(ProfileStream profileStream) {
// do nothing
}
@Override
public ProfileStream profileStream() {
return null;
}
@Override
public void setProfileLocation(ProfileLocation profileLocation) {
this.profileLocation = profileLocation;
}
@Override
public ProfileLocation getProfileLocation() {
return profileLocation;
}
@Override
public boolean isSkipCache() {
return false;
}
@Override
public boolean isSkipCacheExplicit() {
return false;
}
@Override
public void setSkipCache(boolean skipCache) {
}
@Override
public String getLogPrefix() {
return null;
}
@Override
public void addBeanChange(BeanChange beanChange) {
throw new IllegalStateException(notExpectedMessage);
}
@Override
public void sendChangeLog(ChangeSet changesRequest) {
throw new IllegalStateException(notExpectedMessage);
}
@Override
public void register(TransactionCallback callback) {
throw new IllegalStateException(notExpectedMessage);
}
@Override
public int getDocStoreBatchSize() {
return 0;
}
@Override
public void setDocStoreBatchSize(int docStoreBatchSize) {
throw new IllegalStateException(notExpectedMessage);
}
@Override
public DocStoreMode getDocStoreMode() {
return null;
}
@Override
public void setDocStoreMode(DocStoreMode docStoreMode) {
throw new IllegalStateException(notExpectedMessage);
}
@Override
public void registerDeferred(PersistDeferredRelationship derived) {
throw new IllegalStateException(notExpectedMessage);
}
@Override
public void registerDeleteBean(Integer persistingBean) {
throw new IllegalStateException(notExpectedMessage);
}
Return true if this is a bean that has already been saved/deleted.
/**
* Return true if this is a bean that has already been saved/deleted.
*/
@Override
public boolean isRegisteredDeleteBean(Integer persistingBean) {
return false;
}
@Override
public void unregisterBeans() {
throw new IllegalStateException(notExpectedMessage);
}
Return true if this is a bean that has already been saved. This will
register the bean if it is not already.
/**
* Return true if this is a bean that has already been saved. This will
* register the bean if it is not already.
*/
@Override
public boolean isRegisteredBean(Object bean) {
return false;
}
@Override
public boolean isSaveAssocManyIntersection(String intersectionTable, String beanName) {
throw new IllegalStateException(notExpectedMessage);
}
@Override
public void depth(int diff) {
}
Return the current depth.
/**
* Return the current depth.
*/
@Override
public int depth() {
return 0;
}
@Override
public void markNotQueryOnly() {
}
@Override
public boolean isNestedUseSavepoint() {
return false;
}
@Override
public void setNestedUseSavepoint() {
}
@Override
public boolean isReadOnly() {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
try {
return connection.isReadOnly();
} catch (SQLException e) {
throw new PersistenceException(e);
}
}
@Override
public void setReadOnly(boolean readOnly) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
try {
connection.setReadOnly(readOnly);
} catch (SQLException e) {
throw new PersistenceException(e);
}
}
@Override
public void setUpdateAllLoadedProperties(boolean updateAllLoadedProperties) {
}
@Override
public Boolean isUpdateAllLoadedProperties() {
return null;
}
@Override
public void setBatchMode(boolean batchMode) {
}
@Override
public boolean isBatchMode() {
return false;
}
@Override
public boolean isBatchOnCascade() {
return false;
}
@Override
public void setBatchOnCascade(boolean batchMode) {
}
@Override
public Boolean getBatchGetGeneratedKeys() {
return null;
}
@Override
public void setGetGeneratedKeys(boolean getGeneratedKeys) {
}
@Override
public void setFlushOnMixed(boolean batchFlushOnMixed) {
}
Return the batchSize specifically set for this transaction or 0.
Returning 0 implies to use the system wide default batch size.
/**
* Return the batchSize specifically set for this transaction or 0.
* <p>
* Returning 0 implies to use the system wide default batch size.
* </p>
*/
@Override
public int getBatchSize() {
return 0;
}
@Override
public void setBatchSize(int batchSize) {
}
@Override
public boolean isFlushOnQuery() {
return false;
}
@Override
public void setFlushOnQuery(boolean batchFlushOnQuery) {
}
Return true if this request should be batched. Returning false means that
this request should be executed immediately.
/**
* Return true if this request should be batched. Returning false means that
* this request should be executed immediately.
*/
@Override
public boolean isBatchThisRequest() {
return false;
}
@Override
public void checkBatchEscalationOnCollection() {
}
@Override
public void flushBatchOnCollection() {
}
@Override
public PersistenceException translate(String message, SQLException cause) {
return new PersistenceException(message, cause);
}
Flush after completing persist cascade.
/**
* Flush after completing persist cascade.
*/
@Override
public void flushBatchOnCascade() {
}
@Override
public void flushBatchOnRollback() {
}
@Override
public boolean checkBatchEscalationOnCascade(PersistRequestBean<?> request) {
return false;
}
@Override
public BatchControl getBatchControl() {
return null;
}
Set the BatchControl to the transaction. This is done once per transaction
on the first persist request.
/**
* Set the BatchControl to the transaction. This is done once per transaction
* on the first persist request.
*/
@Override
public void setBatchControl(BatchControl batchControl) {
}
Flush any queued persist requests.
This is general will result in a number of batched PreparedStatements
executing.
/**
* Flush any queued persist requests.
* <p>
* This is general will result in a number of batched PreparedStatements
* executing.
* </p>
*/
@Override
public void flush() {
}
@Override
public void flushBatch() {
flush();
}
Return the persistence context associated with this transaction.
/**
* Return the persistence context associated with this transaction.
*/
@Override
public PersistenceContext getPersistenceContext() {
return persistenceContext;
}
Set the persistence context to this transaction.
This could be considered similar to EJB3 Extended PersistanceContext. In
that you get the PersistanceContext from a transaction, hold onto it, and
then set it back later to a second transaction.
/**
* Set the persistence context to this transaction.
* <p>
* This could be considered similar to EJB3 Extended PersistanceContext. In
* that you get the PersistanceContext from a transaction, hold onto it, and
* then set it back later to a second transaction.
* </p>
*/
@Override
public void setPersistenceContext(PersistenceContext context) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
this.persistenceContext = context;
}
@Override
public TransactionEvent getEvent() {
throw new IllegalStateException(notExpectedMessage);
}
Return true if this was an explicitly created transaction.
/**
* Return true if this was an explicitly created transaction.
*/
@Override
public boolean isExplicit() {
return false;
}
@Override
public boolean isLogSql() {
return logSql;
}
@Override
public boolean isLogSummary() {
return logSummary;
}
@Override
public void logSql(String msg) {
manager.log().sql().debug(msg);
}
@Override
public void logSummary(String msg) {
manager.log().sum().debug(msg);
}
Return the transaction id.
/**
* Return the transaction id.
*/
@Override
public String getId() {
return null;
}
@Override
public void setTenantId(Object tenantId) {
this.tenantId = tenantId;
}
@Override
public Object getTenantId() {
return tenantId;
}
Return the underlying connection for internal use.
/**
* Return the underlying connection for internal use.
*/
@Override
public Connection getInternalConnection() {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
return connection;
}
Return the underlying connection for public use.
/**
* Return the underlying connection for public use.
*/
@Override
public Connection getConnection() {
return getInternalConnection();
}
private void deactivate() {
try {
connection.close();
} catch (Exception ex) {
// the connection pool will automatically remove the
// connection if it does not pass the test
logger.error("Error closing connection", ex);
}
connection = null;
active = false;
manager.collectMetricReadOnly((System.nanoTime() - startNanos) / 1000L);
}
Perform a commit, fire callbacks and notify l2 cache etc.
This leaves the transaction active and expects another commit
to occur later (which closes the underlying connection etc).
/**
* Perform a commit, fire callbacks and notify l2 cache etc.
* <p>
* This leaves the transaction active and expects another commit
* to occur later (which closes the underlying connection etc).
* </p>
*/
@Override
public void commitAndContinue() {
// do nothing, expect AutoCommit
}
Commit the transaction.
/**
* Commit the transaction.
*/
@Override
public void commit() {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
// expect AutoCommit so just deactivate / put back into pool
deactivate();
}
Return true if the transaction is marked as rollback only.
/**
* Return true if the transaction is marked as rollback only.
*/
@Override
public boolean isRollbackOnly() {
return false;
}
Mark the transaction as rollback only.
/**
* Mark the transaction as rollback only.
*/
@Override
public void setRollbackOnly() {
// expect AutoCommit so we can't really support rollbackOnly
throw new IllegalStateException(notExpectedMessage);
}
Rollback the transaction.
/**
* Rollback the transaction.
*/
@Override
public void rollback() throws PersistenceException {
rollback(null);
}
Rollback the transaction. If there is a throwable it is logged as the cause
in the transaction log.
/**
* Rollback the transaction. If there is a throwable it is logged as the cause
* in the transaction log.
*/
@Override
public void rollback(Throwable cause) throws PersistenceException {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
// expect AutoCommit so it really has already committed
deactivate();
}
If the transaction is active then perform rollback.
/**
* If the transaction is active then perform rollback.
*/
@Override
public void end() throws PersistenceException {
if (isActive()) {
rollback();
}
}
@Override
public void preCommit() {
// do nothing
}
@Override
public void postCommit() {
// do nothing
}
@Override
public void postRollback(Throwable cause) {
// do nothing
}
Return true if the transaction is active.
/**
* Return true if the transaction is active.
*/
@Override
public boolean isActive() {
return active;
}
@Override
public boolean isPersistCascade() {
return false;
}
@Override
public void setPersistCascade(boolean persistCascade) {
}
@Override
public void addModification(String tableName, boolean inserts, boolean updates, boolean deletes) {
throw new IllegalStateException(notExpectedMessage);
}
@Override
public DocStoreTransaction getDocStoreTransaction() {
throw new IllegalStateException(notExpectedMessage);
}
@Override
public void putUserObject(String name, Object value) {
if (userObjects == null) {
userObjects = new HashMap<>();
}
userObjects.put(name, value);
}
@Override
public Object getUserObject(String name) {
if (userObjects == null) {
return null;
}
return userObjects.get(name);
}
Alias for end(), which enables this class to be used in try-with-resources.
/**
* Alias for end(), which enables this class to be used in try-with-resources.
*/
@Override
public void close() {
end();
}
}