package org.sql2o; import org.sql2o.connectionsources.ConnectionSource; import org.sql2o.converters.Converter; import org.sql2o.converters.ConverterException; import org.sql2o.logging.LocalLoggerFactory; import org.sql2o.logging.Logger; import org.sql2o.quirks.Quirks; import java.io.Closeable; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import static org.sql2o.converters.Convert.throwIfNull; /** * Represents a connection to the database with a transaction. */ public class Connection implements AutoCloseable, Closeable { private final static Logger logger = LocalLoggerFactory.getLogger(Connection.class); private ConnectionSource connectionSource; private java.sql.Connection jdbcConnection; private Sql2o sql2o; private Integer result = null; private int[] batchResult = null; private List<Object> keys; private boolean canGetKeys; private boolean rollbackOnException = true; public boolean isRollbackOnException() { return rollbackOnException; } public Connection setRollbackOnException(boolean rollbackOnException) { this.rollbackOnException = rollbackOnException; return this; } private boolean rollbackOnClose = true; public boolean isRollbackOnClose() { return rollbackOnClose; } public Connection setRollbackOnClose(boolean rollbackOnClose) { this.rollbackOnClose = rollbackOnClose; return this; } final boolean autoClose; Connection(Sql2o sql2o, boolean autoClose) { this(sql2o, null, autoClose); } Connection(Sql2o sql2o, ConnectionSource connectionSource, boolean autoClose) { this.connectionSource = connectionSource != null ? connectionSource : sql2o.getConnectionSource(); this.autoClose = autoClose; this.sql2o = sql2o; createConnection(); } void onException() { if (isRollbackOnException()) { rollback(this.autoClose); } } public java.sql.Connection getJdbcConnection() { return jdbcConnection; } public Sql2o getSql2o() { return sql2o; } public Query createQuery(String queryText){ boolean returnGeneratedKeys = this.sql2o.getQuirks().returnGeneratedKeysByDefault(); return createQuery(queryText, returnGeneratedKeys); } public Query createQuery(String queryText, boolean returnGeneratedKeys){ try { if (jdbcConnection.isClosed()){ createConnection(); } } catch (SQLException e) { throw new Sql2oException("Error creating connection", e); } return new Query(this, queryText, returnGeneratedKeys); } public Query createQuery(String queryText, String ... columnNames) { try { if (jdbcConnection.isClosed()) { createConnection(); } } catch(SQLException e) { throw new Sql2oException("Error creating connection", e); } return new Query(this, queryText, columnNames); } public Query createQueryWithParams(String queryText, Object... paramValues){ // due to #146, creating a query will not create a statement anymore; // the PreparedStatement will only be created once the query needs to be executed // => there is no need to handle the query closing here anymore since there is nothing to close return createQuery(queryText) .withParams(paramValues); } public Sql2o rollback(){ return this.rollback(true).sql2o; } public Connection rollback(boolean closeConnection){ try { jdbcConnection.rollback(); } catch (SQLException e) { logger.warn("Could not roll back transaction. message: {}", e); } finally { if(closeConnection) this.closeJdbcConnection(); } return this; } public Sql2o commit(){ return this.commit(true).sql2o; } public Connection commit(boolean closeConnection){ try { jdbcConnection.commit(); } catch (SQLException e) { throw new Sql2oException(e); } finally { if(closeConnection) this.closeJdbcConnection(); } return this; } public int getResult(){ if (this.result == null){ throw new Sql2oException("It is required to call executeUpdate() method before calling getResult()."); } return this.result; } void setResult(int result){ this.result = result; } public int[] getBatchResult() { if (this.batchResult == null){ throw new Sql2oException("It is required to call executeBatch() method before calling getBatchResult()."); } return this.batchResult; } void setBatchResult(int[] value) { this.batchResult = value; } void setKeys(ResultSet rs) throws SQLException { if (rs == null){ this.keys = null; return; } this.keys = new ArrayList<Object>(); while(rs.next()){ this.keys.add(rs.getObject(1)); } } public Object getKey(){ if (!this.canGetKeys){ throw new Sql2oException("Keys were not fetched from database. Please set the returnGeneratedKeys parameter in the createQuery() method to enable fetching of generated keys."); } if (this.keys != null && this.keys.size() > 0){ return keys.get(0); } return null; } @SuppressWarnings("unchecked") // need to change Convert public <V> V getKey(Class returnType){ final Quirks quirks = this.sql2o.getQuirks(); Object key = getKey(); try { Converter<V> converter = throwIfNull(returnType, quirks.converterOf(returnType)); return converter.convert(key); } catch (ConverterException e) { throw new Sql2oException("Exception occurred while converting value from database to type " + returnType.toString(), e); } } public Object[] getKeys(){ if (!this.canGetKeys){ throw new Sql2oException("Keys where not fetched from database. Please set the returnGeneratedKeys parameter in the createQuery() method to enable fetching of generated keys."); } if (this.keys != null){ return this.keys.toArray(); } return null; } @SuppressWarnings("unchecked") // need to change Convert public <V> List<V> getKeys(Class<V> returnType) { final Quirks quirks = sql2o.getQuirks(); if (!this.canGetKeys) { throw new Sql2oException("Keys where not fetched from database. Please set the returnGeneratedKeys parameter in the createQuery() method to enable fetching of generated keys."); } if (this.keys != null) { try { Converter<V> converter = throwIfNull(returnType, quirks.converterOf(returnType)); List<V> convertedKeys = new ArrayList<V>(this.keys.size()); for (Object key : this.keys) { convertedKeys.add(converter.convert(key)); } return convertedKeys; } catch (ConverterException e) { throw new Sql2oException("Exception occurred while converting value from database to type " + returnType.toString(), e); } } return null; } void setCanGetKeys(boolean canGetKeys) { this.canGetKeys = canGetKeys; } private final Set<Statement> statements = new HashSet<>(); void registerStatement(Statement statement){ statements.add(statement); } void removeStatement(Statement statement){ statements.remove(statement); } public void close() { boolean connectionIsClosed; try { connectionIsClosed = jdbcConnection.isClosed(); } catch (SQLException e) { throw new Sql2oException("Sql2o encountered a problem while trying to determine whether the connection is closed.", e); } if (!connectionIsClosed) { for (Statement statement : statements) { try { getSql2o().getQuirks().closeStatement(statement); } catch (Throwable e) { logger.warn("Could not close statement.", e); } } statements.clear(); boolean rollback = rollbackOnClose; if (rollback) { try { rollback = !jdbcConnection.getAutoCommit(); } catch (SQLException e) { logger.warn("Could not determine connection auto commit mode.", e); } } // if in transaction, rollback, otherwise just close if (rollback) { this.rollback(true); } else { this.closeJdbcConnection(); } } } private void createConnection(){ try{ this.jdbcConnection = connectionSource.getConnection(); } catch(Exception ex){ throw new Sql2oException("Could not acquire a connection from DataSource - " + ex.getMessage(), ex); } } private void closeJdbcConnection() { try { jdbcConnection.close(); } catch (SQLException e) { logger.warn("Could not close connection. message: {}", e); } } }