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

com.hfg.sql.table.DatabaseRow Maven / Gradle / Ivy

There is a newer version: 20240423
Show newest version
package com.hfg.sql.table;

import java.lang.reflect.Constructor;
import java.sql.*;
import java.util.*;

import com.hfg.exception.ProgrammingException;
import com.hfg.sql.*;
import com.hfg.sql.jdbc.JDBCException;
import com.hfg.sql.table.field.*;
import com.hfg.util.CompareUtil;
import com.hfg.util.StringBuilderPlus;
import com.hfg.util.StringUtil;
import com.hfg.util.collection.DirtyMap;
import com.hfg.util.collection.OrderedSet;
import com.hfg.xml.XMLTag;

//------------------------------------------------------------------------------
/**
 Database row object.
 
@author J. Alex Taylor, hairyfatguy.com
*/ //------------------------------------------------------------------------------ // com.hfg XML/HTML Coding Library // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com // [email protected] //------------------------------------------------------------------------------ public abstract class DatabaseRow implements Cloneable, Comparable { private Set mFields; private Map mFieldMap; private DatabaseCol mIdCol; //########################################################################### // CONSTRUCTORS //########################################################################### //--------------------------------------------------------------------------- public DatabaseRow(DatabaseTable inTable) { init(inTable, null); } //--------------------------------------------------------------------------- public DatabaseRow(DatabaseTable inTable, ResultSet inResultSet) { init(inTable, inResultSet); } //--------------------------------------------------------------------------- public DatabaseRow(XMLTag inXMLTag) { init(getDatabaseTable(), null); inXMLTag.verifyTagName(DatabaseXML.ROW); List fieldTags = inXMLTag.getSubtagsByName(DatabaseXML.FIELD); for (XMLTag fieldTag : fieldTags) { addField(DatabaseField.instantiate(fieldTag, getDatabaseTable())); } } //--------------------------------------------------------------------------- private void init(DatabaseTable inTable, ResultSet inResultSet) { // A large number of row objects could get created and since we know // exactly how many columns there are, try to be memory efficient in // allocating the field map. List cols = inTable.getCols(); mFields = new OrderedSet<>(cols.size()); mFieldMap = new DirtyMap<>(cols.size(), 1.0f); for (DatabaseCol col : cols) { addField(allocateField(col, inResultSet)); } } //########################################################################### // PUBLIC METHODS //########################################################################### //--------------------------------------------------------------------------- @SuppressWarnings("unchecked") public static DatabaseRow instantiate(XMLTag inXMLTag) { DatabaseRow row; try { Class clazz = Class.forName(inXMLTag.getAttributeValue(DatabaseXML.CLASS_ATT)); Constructor constructor = clazz.getConstructor(XMLTag.class); row = (DatabaseRow) constructor.newInstance(inXMLTag); } catch (Exception e) { throw new JDBCException(e); } return row; } //--------------------------------------------------------------------------- @SuppressWarnings("unchecked") @Override public DatabaseRow clone() { DatabaseRow cloneObj; try { cloneObj = (DatabaseRow) super.clone(); } catch (CloneNotSupportedException e) { throw new ProgrammingException(e); } cloneObj.mFields = new OrderedSet<>(mFieldMap.size()); cloneObj.mFieldMap = new HashMap<>(mFieldMap.size()); for (DatabaseField origField : getFields()) { DatabaseField field = origField.clone(); if (field.getCol().isId()) { // Clear the primary key field.setValue(null); } cloneObj.addField(field); } return cloneObj; } //--------------------------------------------------------------------------- @Override public int compareTo(Object inObj2) { int result = -1; if (inObj2 instanceof DatabaseRow) { DatabaseRow row2 = (DatabaseRow) inObj2; // Compare qualified tablenames result = CompareUtil.compare(getDatabaseTable().getQualifiedName(), row2.getDatabaseTable().getQualifiedName()); if (0 == result) { // Rows are from the same table. Compare row ids if the table has an id column DatabaseCol idCol = getIdCol(); if (idCol != null) { // Compare row ids result = CompareUtil.compareValues(getField(idCol).getValue(), row2.getField(idCol).getValue()); } } } return result; } //--------------------------------------------------------------------------- public XMLTag toXMLTag() { XMLTag tag = new XMLTag(DatabaseXML.ROW); tag.setAttribute(DatabaseXML.CLASS_ATT, getClass().getName()); if (mFieldMap != null) { for (DatabaseField field : mFieldMap.values()) { if (! field.isNull()) { tag.addSubtag(field.toXMLTag()); } } } return tag; } //--------------------------------------------------------------------------- public DatabaseField getField(DatabaseCol inCol) { return mFieldMap.get(inCol.name()); } //--------------------------------------------------------------------------- public Collection getFields() { return mFields; } //--------------------------------------------------------------------------- public abstract DatabaseTable getDatabaseTable(); //--------------------------------------------------------------------------- /** Invokes insert() or update() depending on whether there is an id column and if it already has a value. * @param inConn the connection to use * @return the result of the insert() or update() * @throws SQLException exception thrown if the insert/update fails */ public boolean save(Connection inConn) throws SQLException { boolean result; DatabaseCol idCol = getIdCol(); if (idCol != null && getField(idCol).getValue() != null) { result = update(inConn); } else { result = insert(inConn); } return result; } //--------------------------------------------------------------------------- @SuppressWarnings("unchecked") public boolean insert(Connection inConn) throws SQLException { boolean result = false; boolean initialState = inConn.getAutoCommit(); try { if (initialState) { inConn.setAutoCommit(false); } preInsert(inConn); if (! containsBinaryFields()) { SQLInsert sql = new SQLInsert().setTable(getDatabaseTable()).setFields(mFieldMap.values()); result = sql.execute(inConn); } else { // Because there is a binary field, we should insert via a prepared statement StringBuilderPlus sql = new StringBuilderPlus("INSERT INTO " + getDatabaseTable() + " ("); List insertCols = new ArrayList<>(getFields().size()); StringBuilderPlus colList = new StringBuilderPlus().setDelimiter(", "); StringBuilderPlus values = new StringBuilderPlus().setDelimiter(","); for (DatabaseField field : getFields()) { if (! getIdCol().equals(field.getCol()) && ! field.isNull()) { insertCols.add(field.getCol()); colList.delimitedAppend(field.getCol().name()); values.delimitedAppend("?"); } } sql.append(colList.toString()); sql.append(") VALUES ("); sql.append(values.toString()); sql.append(")"); PreparedStatement ps = inConn.prepareStatement(sql.toString()); int i = 1; for (DatabaseCol col : insertCols) { DatabaseField field = getField(col); try { field.setValueInPreparedStatement(ps, i++); } catch (Exception e) { throw new SQLException("Problem setting value (" + field.getValue() + ") for " + col + "!", e); } } SQLUtil.executeUpdate(ps); ps.close(); } DatabaseCol idCol = getIdCol(); if (idCol != null) { DatabaseField idField = getField(idCol); if (idField.isNull()) { // Save the new row id back to the row object's id field Long newIdValue = getDatabaseTable().getRDBMS().getLastGeneratedId(inConn, idCol); if (idField.getCol().getType() == Types.BIGINT) { idField.setValue(newIdValue); } else if (idField.getCol().getType() == Types.INTEGER) { idField.setValue(newIdValue.intValue()); } else if (idField.getCol().getType() == Types.SMALLINT) { idField.setValue(newIdValue.intValue()); } } } postInsert(inConn); if (initialState) { inConn.commit(); } bless(); } catch (Exception e) { if (initialState) { inConn.rollback(); } throw e; } finally { if (initialState != inConn.getAutoCommit()) { inConn.setAutoCommit(initialState); } } return result; } //--------------------------------------------------------------------------- public synchronized boolean update(Connection inConn) throws SQLException { if (null == getIdCol()) { throw new SQLException("update() cannot be used for a row without an id column!"); } boolean result = false; boolean initialState = inConn.getAutoCommit(); try { if (initialState) { inConn.setAutoCommit(false); } preUpdate(inConn); if (isDirty()) { if (! containsBinaryFields()) { SQLUpdate sql = new SQLUpdate(this); if (sql.hasSetFields()) // We should always have set fields if we are dirty, but just in case... { int rowsUpdated = sql.execute(inConn); result = (1 == rowsUpdated); } } else { // Because there is a binary field, we should update via a prepared statement StringBuilderPlus sql = new StringBuilderPlus("UPDATE " + getDatabaseTable() + " SET"); List updateFields = new ArrayList<>(getFields().size()); for (DatabaseField field : getFields()) { if (!getIdCol().equals(field.getCol()) // Skip the id column && field.isDirty()) { sql.append((updateFields.size() > 0 ? "," : "") + " " + field.getCol().name() + " = ?"); updateFields.add(field); } } sql.append(" WHERE " + getIdCol().name() + " = " + getField(getIdCol()).getSQLValue()); PreparedStatement ps = inConn.prepareStatement(sql.toString()); int i = 1; for (DatabaseField field : updateFields) { field.setValueInPreparedStatement(ps, i++); } SQLUtil.executeUpdate(ps); ps.close(); } } postUpdate(inConn); if (initialState) { inConn.commit(); } bless(); } catch (Exception e) { if (initialState) { inConn.rollback(); } throw e; } finally { if (initialState != inConn.getAutoCommit()) { inConn.setAutoCommit(initialState); } } return result; } //--------------------------------------------------------------------------- public boolean delete(Connection inConn) throws SQLException { boolean result; boolean initialState = inConn.getAutoCommit(); try { if (initialState) { inConn.setAutoCommit(false); } preDelete(inConn); SQLDelete sql = new SQLDelete(this); int rowsUpdated = sql.execute(inConn); postDelete(inConn); if (initialState) { inConn.commit(); } result = (1 == rowsUpdated); } catch (Exception e) { if (initialState) { inConn.rollback(); } throw e; } finally { if (initialState != inConn.getAutoCommit()) { inConn.setAutoCommit(initialState); } } return result; } //--------------------------------------------------------------------------- public DatabaseCol getIdCol() { if (null == mIdCol) { mIdCol = getDatabaseTable().getIdCol(); } return mIdCol; } //--------------------------------------------------------------------------- public boolean isDirty() { boolean isDirty = false; for (DatabaseField field : mFieldMap.values()) { if (field.isDirty()) { isDirty = true; break; } } return isDirty; } //--------------------------------------------------------------------------- /** Indicates whether the specified field is dirty or not. @param inColumn the DatabaseCol corresponding to the field @return whether or not the specified field is dirty */ public boolean isDirty(DatabaseCol inColumn) { DatabaseField field = mFieldMap.get(inColumn.name()); return (field != null && field.isDirty()); } //--------------------------------------------------------------------------- /** Sets the dirty flag on all fields to false. */ public void bless() { for (DatabaseField field : mFieldMap.values()) { field.setIsDirty(false); } } //########################################################################### // PROTECTED METHODS //########################################################################### //--------------------------------------------------------------------------- protected DatabaseField allocateField(DatabaseCol inCol) { return allocateField(inCol, (Object) null); } //--------------------------------------------------------------------------- protected DatabaseField allocateField(DatabaseCol inCol, Object inValue) { DatabaseField field; switch (inCol.getType()) { case Types.VARCHAR: field = new DatabaseStringField(inCol, inValue); break; case Types.CHAR: field = new DatabaseCharField(inCol, inValue); break; case Types.DATE: case Types.TIMESTAMP: case Types.TIMESTAMP_WITH_TIMEZONE: field = new DatabaseDateField(inCol, inValue); break; case Types.BIGINT: field = new DatabaseLongField(inCol, inValue); break; case Types.INTEGER: field = new DatabaseIntField(inCol, inValue); break; case Types.SMALLINT: field = new DatabaseShortField(inCol, inValue); break; case Types.FLOAT: field = new DatabaseFloatField(inCol, inValue); break; case Types.REAL: case Types.DOUBLE: field = new DatabaseDoubleField(inCol, inValue); break; case Types.BOOLEAN: field = new DatabaseBooleanField(inCol, inValue); break; case Types.BINARY: field = new DatabaseBinaryField(inCol, inValue); break; case Types.ARRAY: field = new DatabaseArrayField(inCol, inValue); break; default: throw new JDBCException(StringUtil.singleQuote(inCol.getType()) + " is not a currently supported data type!"); } return field; } //--------------------------------------------------------------------------- protected DatabaseField allocateField(DatabaseCol inCol, ResultSet inResultSet) { DatabaseField field; switch (inCol.getType()) { case Types.VARCHAR: field = new DatabaseStringField(inCol, inResultSet); break; case Types.CHAR: field = new DatabaseCharField(inCol, inResultSet); break; case Types.DATE: case Types.TIMESTAMP: case Types.TIMESTAMP_WITH_TIMEZONE: field = new DatabaseDateField(inCol, inResultSet); break; case Types.SMALLINT: field = new DatabaseShortField(inCol, inResultSet); break; case Types.BIGINT: field = new DatabaseLongField(inCol, inResultSet); break; case Types.INTEGER: field = new DatabaseIntField(inCol, inResultSet); break; case Types.FLOAT: field = new DatabaseFloatField(inCol, inResultSet); break; case Types.REAL: case Types.DOUBLE: field = new DatabaseDoubleField(inCol, inResultSet); break; case Types.BOOLEAN: field = new DatabaseBooleanField(inCol, inResultSet); break; case Types.BINARY: field = new DatabaseBinaryField(inCol, inResultSet); break; case Types.ARRAY: field = new DatabaseArrayField(inCol, inResultSet); break; default: throw new JDBCException(StringUtil.singleQuote(inCol.getType()) + " is not a currently supported data type!"); } return field; } //--------------------------------------------------------------------------- protected void addField(DatabaseField inField) { mFields.add(inField); mFieldMap.put(inField.getCol().name(), inField); } //--------------------------------------------------------------------------- protected void preInsert(Connection inConn) throws SQLException { } //--------------------------------------------------------------------------- protected void postInsert(Connection inConn) throws SQLException { } //--------------------------------------------------------------------------- protected void preUpdate(Connection inConn) throws SQLException { } //--------------------------------------------------------------------------- protected void postUpdate(Connection inConn) throws SQLException { } //--------------------------------------------------------------------------- protected void preDelete(Connection inConn) throws SQLException { } //--------------------------------------------------------------------------- protected void postDelete(Connection inConn) throws SQLException { } //########################################################################### // PRIVATE METHODS //########################################################################### //--------------------------------------------------------------------------- private boolean containsBinaryFields() { boolean result = false; for (DatabaseField field : getFields()) { if (field instanceof DatabaseBinaryField) { result = true; break; } } return result; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy