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). *
*/ 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. */ 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). */ private Connection connection; /** * Holder of the objects fetched to ensure unique objects are used. */ private PersistenceContext persistenceContext; private Object tenantId; private Map* Returning 0 implies to use the system wide default batch size. *
*/ @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. */ @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. */ @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. */ @Override public void setBatchControl(BatchControl batchControl) { } /** * Flush any queued persist requests. ** This is general will result in a number of batched PreparedStatements * executing. *
*/ @Override public void flush() { } @Override public void flushBatch() { flush(); } /** * 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. *
*/ @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. */ @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. */ @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. */ @Override public Connection getInternalConnection() { if (!isActive()) { throw new IllegalStateException(illegalStateMessage); } return connection; } /** * 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). *
*/ @Override public void commitAndContinue() { // do nothing, expect AutoCommit } /** * 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. */ @Override public boolean isRollbackOnly() { return false; } /** * 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. */ @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. */ @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. */ @Override public void end() throws PersistenceException { if (isActive()) { rollback(); } } /** * 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. */ @Override public void close() { end(); } }