All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.j256.ormlite.jdbc.JdbcDatabaseConnection Maven / Gradle / Ivy

package com.j256.ormlite.jdbc;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Types;

import com.j256.ormlite.dao.ObjectCache;
import com.j256.ormlite.field.FieldType;
import com.j256.ormlite.logger.Logger;
import com.j256.ormlite.logger.LoggerFactory;
import com.j256.ormlite.misc.IOUtils;
import com.j256.ormlite.stmt.GenericRowMapper;
import com.j256.ormlite.stmt.StatementBuilder.StatementType;
import com.j256.ormlite.support.CompiledStatement;
import com.j256.ormlite.support.DatabaseConnection;
import com.j256.ormlite.support.DatabaseResults;
import com.j256.ormlite.support.GeneratedKeyHolder;

/**
 * Wrapper around a JDBC {@link Connection} object which we delegate to.
 * 
 * @author graywatson
 */
public class JdbcDatabaseConnection implements DatabaseConnection {

	private static Logger logger = LoggerFactory.getLogger(JdbcDatabaseConnection.class);
	private static final String JDBC_META_TABLE_NAME_COLUMN = "TABLE_NAME";

	private static Object[] noArgs = new Object[0];
	private static FieldType[] noArgTypes = new FieldType[0];
	private static GenericRowMapper longWrapper = new OneLongWrapper();

	private Connection connection;
	private Boolean supportsSavePoints;

	public JdbcDatabaseConnection(Connection connection) {
		this.connection = connection;
		logger.trace("connection opened: {}", connection);
	}

	@Override
	public boolean isAutoCommitSupported() {
		return true;
	}

	@Override
	public boolean isAutoCommit() throws SQLException {
		boolean autoCommit = connection.getAutoCommit();
		logger.trace("connection autoCommit is {}", autoCommit);
		return autoCommit;
	}

	@Override
	public void setAutoCommit(boolean autoCommit) throws SQLException {
		connection.setAutoCommit(autoCommit);
		logger.trace("connection set autoCommit to {}", autoCommit);
	}

	@Override
	public Savepoint setSavePoint(String name) throws SQLException {
		if (supportsSavePoints == null) {
			DatabaseMetaData metaData = connection.getMetaData();
			supportsSavePoints = metaData.supportsSavepoints();
			logger.trace("connection supports save points is {}", supportsSavePoints);
		}
		if (supportsSavePoints) {
			Savepoint savepoint = connection.setSavepoint(name);
			logger.trace("save-point {} set with name {}", savepoint, name);
			return savepoint;
		} else {
			return null;
		}
	}

	@Override
	public void commit(Savepoint savepoint) throws SQLException {
		if (savepoint == null) {
			connection.commit();
			logger.trace("connection committed");
		} else {
			// release might clear the name so we record it beforehand
			Object nameOrSavepoint = savepoint.getSavepointName();
			if (nameOrSavepoint == null) {
				nameOrSavepoint = savepoint;
			}
			/*
			 * Initially I was doing a connection.releaseSavepoint(savepoint) which was only dropping the savepoint --
			 * not committing it like I thought. I'm still surprised there is not a commit(savepoint).
			 */
			connection.commit();
			logger.trace("connection is committed for save-point {}", nameOrSavepoint);
		}
	}

	@Override
	public void rollback(Savepoint savepoint) throws SQLException {
		if (savepoint == null) {
			connection.rollback();
			logger.trace("connection is rolled back");
		} else {
			// rollback might clear the name so we record it beforehand
			Object obj = savepoint.getSavepointName();
			if (obj == null) {
				obj = savepoint;
			}
			connection.rollback(savepoint);
			logger.trace("save-point {} is rolled back", obj);
		}
	}

	@Override
	public void releaseSavePoint(Savepoint savePoint) throws SQLException {
		if (savePoint == null) {
			logger.trace("release of null savepoint ignored");
		} else {
			connection.releaseSavepoint(savePoint);
		}
	}

	@Override
	public int executeStatement(String statementStr, int resultFlags) throws SQLException {
		if (resultFlags == DatabaseConnection.DEFAULT_RESULT_FLAGS) {
			resultFlags = ResultSet.TYPE_FORWARD_ONLY;
		}
		Statement statement = connection.createStatement(resultFlags, ResultSet.CONCUR_READ_ONLY);
		statement.execute(statementStr);
		return statement.getUpdateCount();
	}

	@Override
	public CompiledStatement compileStatement(String statement, StatementType type, FieldType[] argFieldTypes,
			int resultFlags, boolean cacheStore) throws SQLException {
		if (resultFlags == DatabaseConnection.DEFAULT_RESULT_FLAGS) {
			resultFlags = ResultSet.TYPE_FORWARD_ONLY;
		}
		JdbcCompiledStatement compiledStatement = new JdbcCompiledStatement(
				connection.prepareStatement(statement, resultFlags, ResultSet.CONCUR_READ_ONLY), statement, type,
				cacheStore);
		logger.trace("compiled statement: {}", statement);
		return compiledStatement;
	}

	@Override
	public void close() throws Exception {
		try {
			connection.close();
		} catch (SQLException e) {
			throw new SQLException("could not close SQL connection", e);
		}
		logger.trace("connection closed: {}", connection);
	}

	@Override
	public void closeQuietly() {
		IOUtils.closeQuietly(this);
	}

	/**
	 * Returns whether the connection has already been closed. Used by {@link JdbcConnectionSource}.
	 */
	@Override
	public boolean isClosed() throws SQLException {
		boolean isClosed = connection.isClosed();
		logger.trace("connection is closed returned {}", isClosed);
		return isClosed;
	}

	@Override
	public int insert(String statement, Object[] args, FieldType[] argFieldTypes, GeneratedKeyHolder keyHolder)
			throws SQLException {
		PreparedStatement stmt;
		if (keyHolder == null) {
			stmt = connection.prepareStatement(statement);
		} else {
			stmt = connection.prepareStatement(statement, new String[] { keyHolder.getColumnName() });
		}
		try {
			statementSetArgs(stmt, args, argFieldTypes);
			int rowN = stmt.executeUpdate();
			logger.trace("insert statement is prepared and executed: {}", statement);
			if (keyHolder != null) {
				ResultSet resultSet = stmt.getGeneratedKeys();
				ResultSetMetaData metaData = resultSet.getMetaData();
				int colN = metaData.getColumnCount();
				boolean wasSet = false;
				while (resultSet.next()) {
					for (int colC = 1; colC <= colN; colC++) {
						// get the id column data so we can pass it back to the caller thru the keyHolder
						Number id = getIdColumnData(resultSet, metaData, colC);
						keyHolder.addKey(id);
						wasSet = true;
					}
				}
				if (!wasSet) {
					throw new SQLException(
							"no generated-keys were returned from statement, maybe a schema mismatch between entity and database table?: "
									+ statement);
				}
			}
			return rowN;
		} finally {
			stmt.close();
		}
	}

	@Override
	public int update(String statement, Object[] args, FieldType[] argFieldTypes) throws SQLException {
		return update(statement, args, argFieldTypes, "update");
	}

	@Override
	public int delete(String statement, Object[] args, FieldType[] argFieldTypes) throws SQLException {
		// it's a call to executeUpdate
		return update(statement, args, argFieldTypes, "delete");
	}

	@Override
	public  Object queryForOne(String statement, Object[] args, FieldType[] argFieldTypes,
			GenericRowMapper rowMapper, ObjectCache objectCache) throws SQLException {
		return queryForOne(statement, args, argFieldTypes, rowMapper, objectCache, "query for one");
	}

	@Override
	public long queryForLong(String statement) throws SQLException {
		return queryForLong(statement, noArgs, noArgTypes);
	}

	@Override
	public long queryForLong(String statement, Object[] args, FieldType[] argFieldTypes) throws SQLException {
		// don't care about the object cache here
		Object result = queryForOne(statement, args, argFieldTypes, longWrapper, null, "query for long");
		if (result == null) {
			throw new SQLException("No results returned in query-for-long: " + statement);
		} else if (result == MORE_THAN_ONE) {
			throw new SQLException("More than 1 result returned in query-for-long: " + statement);
		} else {
			return (Long) result;
		}
	}

	@Override
	public boolean isTableExists(String tableName) throws SQLException {
		DatabaseMetaData metaData = connection.getMetaData();
		logger.trace("Got meta data from connection");
		ResultSet results = null;
		try {
			results = metaData.getTables(null, null, "%", new String[] { "TABLE" });
			// we do it this way because some result sets don't like us to findColumn if no results
			if (!results.next()) {
				return false;
			}
			int col = results.findColumn(JDBC_META_TABLE_NAME_COLUMN);
			do {
				String dbTableName = results.getString(col);
				if (tableName.equalsIgnoreCase(dbTableName)) {
					return true;
				}
			} while (results.next());
			return false;
		} finally {
			if (results != null) {
				results.close();
			}
		}
	}

	@Override
	public Connection getUnderlyingConnection() {
		return connection;
	}

	/**
	 * @deprecated Please use {@link #setInternalConnection(Connection)}
	 */
	@Deprecated
	public void setInternalConnection(Connection connection) {
		this.connection = connection;
	}

	/**
	 * Set the internal database connection. Most likely for testing purposes.
	 */
	public void setUnderlyingConnection(Connection connection) {
		this.connection = connection;
	}

	private int update(String statement, Object[] args, FieldType[] argFieldTypes, String label) throws SQLException {
		PreparedStatement stmt = connection.prepareStatement(statement);
		try {
			statementSetArgs(stmt, args, argFieldTypes);
			int rowCount = stmt.executeUpdate();
			logger.trace("{} statement is prepared and executed returning {}: {}", label, rowCount, statement);
			return rowCount;
		} finally {
			stmt.close();
		}
	}

	private  Object queryForOne(String statement, Object[] args, FieldType[] argFieldTypes,
			GenericRowMapper rowMapper, ObjectCache objectCache, String label) throws SQLException {
		PreparedStatement stmt =
				connection.prepareStatement(statement, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
		DatabaseResults results = null;
		try {
			statementSetArgs(stmt, args, argFieldTypes);
			results = new JdbcDatabaseResults(stmt, stmt.executeQuery(), objectCache, true);
			logger.trace("{} statement is prepared and executed: {}", label, statement);
			if (!results.first()) {
				// no results at all
				return null;
			}
			T first = rowMapper.mapRow(results);
			if (results.next()) {
				return MORE_THAN_ONE;
			} else {
				return first;
			}
		} finally {
			IOUtils.closeQuietly(results);
			stmt.close();
		}
	}

	/**
	 * Return the id associated with the column.
	 */
	private Number getIdColumnData(ResultSet resultSet, ResultSetMetaData metaData, int columnIndex)
			throws SQLException {
		int typeVal = metaData.getColumnType(columnIndex);
		switch (typeVal) {
			case Types.BIGINT:
			case Types.DECIMAL:
			case Types.NUMERIC:
				return (Number) resultSet.getLong(columnIndex);
			case Types.INTEGER:
				return (Number) resultSet.getInt(columnIndex);
			default:
				String columnName = metaData.getColumnName(columnIndex);
				throw new SQLException(
						"Unexpected ID column type " + TypeValMapper.getSqlTypeForTypeVal(typeVal) + " (typeVal "
								+ typeVal + ") in column " + columnName + "(#" + columnIndex + ") is not a number");
		}
	}

	private void statementSetArgs(PreparedStatement stmt, Object[] args, FieldType[] argFieldTypes)
			throws SQLException {
		if (args == null) {
			return;
		}
		for (int i = 0; i < args.length; i++) {
			Object arg = args[i];
			int typeVal = TypeValMapper.getTypeValForSqlType(argFieldTypes[i].getSqlType());
			if (arg == null) {
				stmt.setNull(i + 1, typeVal);
			} else {
				stmt.setObject(i + 1, arg, typeVal);
			}
		}
	}

	/**
	 * Row mapper that handles a single long result.
	 */
	private static class OneLongWrapper implements GenericRowMapper {
		@Override
		public Long mapRow(DatabaseResults rs) throws SQLException {
			// maps the first column (sql #1)
			return rs.getLong(0);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy