/* * Copyright 2017 requery.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.requery.sql; import io.requery.EntityCache; import io.requery.Transaction; import io.requery.TransactionException; import io.requery.TransactionIsolation; import io.requery.TransactionListener; import io.requery.meta.Type; import io.requery.proxy.EntityProxy; import io.requery.util.Objects; import java.sql.Connection; import java.sql.SQLException; import java.util.Collection; /** * {@link Transaction} implementation using JDBC {@link Connection} operations * {@link Connection#commit()} and {@link Connection#rollback()}. * * @author Nikhil Purushe */ class ConnectionTransaction implements EntityTransaction, ConnectionProvider { private final ConnectionProvider connectionProvider; private final TransactionEntitiesSet entities; private final TransactionListener transactionListener; private final boolean supportsTransaction; private Connection connection; private Connection uncloseableConnection; private boolean committed; private boolean rolledBack; private int previousIsolationLevel; ConnectionTransaction(TransactionListener transactionListener, ConnectionProvider connectionProvider, EntityCache cache, boolean supportsTransaction) { this.transactionListener = Objects.requireNotNull(transactionListener); this.connectionProvider = Objects.requireNotNull(connectionProvider); this.supportsTransaction = supportsTransaction; this.entities = new TransactionEntitiesSet(cache); this.previousIsolationLevel = -1; } @Override public Connection getConnection() { return uncloseableConnection; } @Override public Transaction begin() { return begin(null); } @Override public Transaction begin(TransactionIsolation isolation) { if (active()) { throw new IllegalStateException("transaction already active"); } try { transactionListener.beforeBegin(isolation); connection = connectionProvider.getConnection(); uncloseableConnection = new UncloseableConnection(connection); if (supportsTransaction) { connection.setAutoCommit(false); if (isolation != null) { previousIsolationLevel = connection.getTransactionIsolation(); int level; switch (isolation) { case NONE: level = Connection.TRANSACTION_NONE; break; case READ_UNCOMMITTED: level = Connection.TRANSACTION_READ_UNCOMMITTED; break; case READ_COMMITTED: level = Connection.TRANSACTION_READ_COMMITTED; break; case REPEATABLE_READ: level = Connection.TRANSACTION_REPEATABLE_READ; break; case SERIALIZABLE: level = Connection.TRANSACTION_SERIALIZABLE; break; default: throw new UnsupportedOperationException(); } connection.setTransactionIsolation(level); } } committed = false; rolledBack = false; entities.clear(); transactionListener.afterBegin(isolation); } catch (SQLException e) { throw new TransactionException(e); } return this; } @Override public void close() { if (connection != null) { if (!committed && !rolledBack) { try { rollback(); } catch (Exception ignored) { } } try { connection.close(); } catch (SQLException e) { throw new TransactionException(e); } finally { connection = null; } } } @Override public void commit() { try { transactionListener.beforeCommit(entities.types()); if (supportsTransaction) { connection.commit(); committed = true; } transactionListener.afterCommit(entities.types()); entities.clear(); } catch (SQLException e) { throw new TransactionException(e); } finally { resetConnection(); close(); } } @Override public void rollback() { try { transactionListener.beforeRollback(entities.types()); if (supportsTransaction) { connection.rollback(); rolledBack = true; entities.clearAndInvalidate(); } transactionListener.afterRollback(entities.types()); entities.clear(); } catch (SQLException e) { throw new TransactionException(e); } finally { resetConnection(); } } @Override public boolean active() { try { return connection != null && !connection.getAutoCommit(); } catch (SQLException e) { return false; } } @Override public void addToTransaction(EntityProxy<?> proxy) { entities.add(proxy); } @Override public void addToTransaction(Collection<Type<?>> types) { entities.types().addAll(types); } private void resetConnection() { if (supportsTransaction) { try { connection.setAutoCommit(true); // restore default isolation level if (previousIsolationLevel != -1) { connection.setTransactionIsolation(previousIsolationLevel); } } catch (SQLException ignored) { } } } }