/******************************************************************************* * Copyright (c) 2013, 2015 Oracle. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.internal.sessions; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.util.Vector; import org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor; import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform; /** * PERF: Record used by ObjectLevelReadQuery ResultSet optimization. * The record corresponds to a single position (resultSet.next() is never called). * In case the cached object used instead of creating a new one from the record, * the not needed fields' values are never obtained from resultSet * (that's especially important for expensive LOBs). * If alternatively the record is used to populate an object then all * the values obtained from resultSet and resultSet is nullified. * In this case the fields' values not required after object population * (those not involved in indirection) are nullified to save space in the record. */ public class ResultSetRecord extends ArrayRecord { transient protected ResultSet resultSet; transient protected ResultSetMetaData metaData; transient protected DatabaseAccessor accessor; transient protected DatabasePlatform platform; transient protected boolean optimizeData; transient protected AbstractSession session; protected ResultSetRecord() { super(); } public ResultSetRecord(Vector fields, DatabaseField[] fieldsArray, ResultSet resultSet, ResultSetMetaData metaData, DatabaseAccessor accessor, AbstractSession session, DatabasePlatform platform, boolean optimizeData) { super(fields, fieldsArray, new Object[fieldsArray.length]); this.resultSet = resultSet; this.metaData = metaData; this.accessor = accessor; this.platform = platform; this.optimizeData = optimizeData; this.session = session; } /** * Obtains all the value from resultSet and removes it. * resultSet must be non null. */ public void loadAllValuesFromResultSet() { int size = this.valuesArray.length; for (int index = 0; index < size; index++) { if (this.valuesArray[index] == null) { DatabaseField field = this.fieldsArray[index]; // Field can be null for fetch groups. if (field != null) { this.valuesArray[index] = this.accessor.getObject(this.resultSet, field, this.metaData, index + 1, this.platform, this.optimizeData, this.session); } } } this.resultSet = null; this.metaData = null; this.accessor = null; this.platform = null; this.session = null; } /** * Remove values corresponding to all fields not related to indirection. */ public void removeNonIndirectionValues() { if (this.fieldsArray != null) { int size = this.valuesArray.length; for (int index = 0; index < size; index++) { DatabaseField field = this.fieldsArray[index]; // Field can be null for fetch groups. if (field != null) { if (!field.keepInRow) { this.valuesArray[index] = null; } } } } } public void removAllValue() { if (this.valuesArray != null) { int size = this.valuesArray.length; for (int index = 0; index < size; index++) { this.valuesArray[index] = null; } } } /** * Indicates whether resultSet is still here. * @return */ public boolean hasResultSet() { return this.resultSet != null; } public void removeResultSet() { this.resultSet = null; this.metaData = null; this.accessor = null; this.platform = null; this.session = null; } /** * PUBLIC: * Clear the contents of the row. */ @Override public void clear() { removeResultSet(); this.fieldsArray = null; this.valuesArray = null; super.clear(); } /** * Reset the fields and values from the arrays. * This removes the optimization if a non-optimized method is called. */ @Override protected void checkValues() { if (this.resultSet != null) { loadAllValuesFromResultSet(); } super.checkValues(); } /** * PUBLIC: * Check if the value is contained in the row. */ @Override public boolean containsValue(Object value) { if (this.resultSet != null) { loadAllValuesFromResultSet(); } return super.containsValue(value); } /** * INTERNAL: * Retrieve the value for the field. If missing null is returned. */ @Override public Object get(DatabaseField key) { if (this.fieldsArray != null) { // Optimize check. int index = key.index; if ((index < 0) || (index >= this.size)) { index = 0; } DatabaseField field = this.fieldsArray[index]; if ((field != key) && !field.equals(key)) { index = -1; for (int fieldIndex = 0; fieldIndex < this.size; fieldIndex++) { field = this.fieldsArray[fieldIndex]; if ((field == key) || field.equals(key)) { // PERF: If the fields index was not set, then set it. if (key.index == -1) { key.setIndex(fieldIndex); } index = fieldIndex; break; } } if (index < 0) { return null; } } if (this.resultSet != null) { Object value = this.valuesArray[index]; if (value == null) { value = this.accessor.getObject(this.resultSet, field, this.metaData, index + 1, this.platform, this.optimizeData, this.session); this.valuesArray[index] = value; } else { // field's value has been already extracted earlier - the row is used to populate object loadAllValuesFromResultSet(); } return value; } else { return this.valuesArray[index]; } } else { return super.get(key); } } /** * INTERNAL: * Retrieve the value for the field. If missing DatabaseRow.noEntry is returned. * PERF: This method is a clone of get() for performance. */ @Override public Object getIndicatingNoEntry(DatabaseField key) { if (this.fieldsArray != null) { // Optimize check. int index = key.index; if ((index < 0) || (index >= this.size)) { index = 0; } DatabaseField field = this.fieldsArray[index]; if ((field != key) && !field.equals(key)) { index = -1; for (int fieldIndex = 0; fieldIndex < this.size; fieldIndex++) { field = this.fieldsArray[fieldIndex]; if ((field == key) || field.equals(key)) { // PERF: If the fields index was not set, then set it. if (key.index == -1) { key.setIndex(fieldIndex); } index = fieldIndex; break; } } if (index < 0) { return null; } } if (this.resultSet != null) { Object value = this.valuesArray[index]; if (value == null) { value = this.accessor.getObject(this.resultSet, field, this.metaData, index + 1, this.platform, this.optimizeData, this.session); this.valuesArray[index] = value; } else { // field's value has been already extracted earlier - the row is used to populate object loadAllValuesFromResultSet(); } return value; } else { return this.valuesArray[index]; } } else { return super.get(key); } } @Override protected String toStringAditional() { return (this.resultSet != null ? " hasResultSet" : ""); } @Override public void setSopObject(Object sopObject) { super.setSopObject(sopObject); // sopObject is set - the row is used to populate object if (this.resultSet != null) { loadAllValuesFromResultSet(); } } }