| /* |
| * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2013, 2018 IBM Corporation. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation from Oracle TopLink |
| // 02/19/2015 - Rick Curtis |
| // - 458877 : Add national character support |
| package org.eclipse.persistence.internal.sessions; |
| |
| import java.sql.ResultSet; |
| import java.sql.ResultSetMetaData; |
| import java.sql.SQLException; |
| import java.util.Vector; |
| |
| import org.eclipse.persistence.exceptions.DatabaseException; |
| import org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor; |
| import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform; |
| import org.eclipse.persistence.internal.helper.ClassConstants; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| |
| /** |
| * PERF: Record used by ObjectLevelReadQuery ResultSet optimization. |
| * This is a subclass of ResultSetRecord that's used with simple descriptors |
| * (descriptor.getObjectBuilder().isSimple()) - those having only DirectToField mappings. |
| * In this case the record is not cached by any mapping therefore could be reused |
| * in ReadAllQuery - (which calls resultSet.next()). |
| * 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 are nullified after use. |
| */ |
| public class SimpleResultSetRecord extends ResultSetRecord { |
| |
| /** Indicates whether the values requested while populating object (isPopulatingObject == true) should be saved in valuesArray */ |
| transient protected boolean shouldKeepValues; |
| /** Indicates whether to use optimization while getting values from result set instead of calling accessor.getObject method. */ |
| transient protected boolean shouldUseOptimization; |
| /** |
| * Indicates that the whole object is being populated. |
| * At first the primary key is extracted from the row - to see |
| * if the object with the same pk is already in the cache. |
| * Then in case of refresh the locking field value is extracted, too. |
| * Indication of the object being populated from the record (looping through all the mappings) |
| * is request for a value, which has been already returned before (usually primary key, but may be version). |
| * At this point the flag is set to true. |
| */ |
| transient protected boolean isPopulatingObject; |
| |
| protected SimpleResultSetRecord() { |
| super(); |
| } |
| |
| public SimpleResultSetRecord(Vector fields, DatabaseField[] fieldsArray, ResultSet resultSet, ResultSetMetaData metaData, DatabaseAccessor accessor, AbstractSession session, DatabasePlatform platform, boolean optimizeData) { |
| super(fields, fieldsArray, resultSet, metaData, accessor, session, platform, optimizeData); |
| } |
| |
| /** |
| * Obtains all the value from resultSet and removes it. |
| * resultSet must be non null. |
| */ |
| @Override |
| 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] = getValueFromResultSet(index, field); |
| } |
| } |
| } |
| this.resultSet = null; |
| this.metaData = null; |
| this.accessor = null; |
| this.platform = null; |
| this.session = null; |
| } |
| |
| /** |
| * 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) { |
| if (!this.isPopulatingObject) { |
| this.isPopulatingObject = true; |
| } |
| if (!this.shouldKeepValues) { |
| this.valuesArray[index] = null; |
| } |
| } else { |
| if (this.shouldUseOptimization) { |
| try { |
| Class fieldType = field.getType(); |
| if (fieldType == ClassConstants.STRING) { |
| if(platform.shouldUseGetSetNString()){ |
| value = resultSet.getNString(index + 1); |
| }else { |
| value = resultSet.getString(index + 1); |
| } |
| |
| } else if (fieldType == ClassConstants.LONG) { |
| value = resultSet.getLong(index + 1); |
| } else if (fieldType == ClassConstants.INTEGER) { |
| value = resultSet.getInt(index + 1); |
| } else { |
| value = this.accessor.getObject(this.resultSet, field, this.metaData, index + 1, this.platform, this.optimizeData, this.session); |
| } |
| } catch (SQLException exception) { |
| DatabaseException commException = this.accessor.processExceptionForCommError(session, exception, null); |
| if (commException != null) { |
| throw commException; |
| } |
| throw DatabaseException.sqlException(exception, accessor, session, false); |
| } } else { |
| value = this.accessor.getObject(this.resultSet, field, this.metaData, index + 1, this.platform, this.optimizeData, this.session); |
| } |
| if (!this.isPopulatingObject || this.shouldKeepValues) { |
| this.valuesArray[index] = value; |
| } |
| } |
| 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) { |
| if (!this.isPopulatingObject) { |
| this.isPopulatingObject = true; |
| } |
| if (!this.shouldKeepValues) { |
| this.valuesArray[index] = null; |
| } |
| } else { |
| if (this.shouldUseOptimization) { |
| try { |
| Class fieldType = field.getType(); |
| if (fieldType == ClassConstants.STRING) { |
| if(platform.shouldUseGetSetNString()){ |
| value = resultSet.getNString(index + 1); |
| }else { |
| value = resultSet.getString(index + 1); |
| } |
| } else if (fieldType == ClassConstants.LONG) { |
| value = resultSet.getLong(index + 1); |
| } else if (fieldType == ClassConstants.INTEGER) { |
| value = resultSet.getInt(index + 1); |
| } else { |
| value = this.accessor.getObject(this.resultSet, field, this.metaData, index + 1, this.platform, this.optimizeData, this.session); |
| } |
| } catch (SQLException exception) { |
| DatabaseException commException = this.accessor.processExceptionForCommError(session, exception, null); |
| if (commException != null) { |
| throw commException; |
| } |
| throw DatabaseException.sqlException(exception, accessor, session, false); |
| } } else { |
| value = this.accessor.getObject(this.resultSet, field, this.metaData, index + 1, this.platform, this.optimizeData, this.session); |
| } |
| if (!this.isPopulatingObject || this.shouldKeepValues) { |
| this.valuesArray[index] = value; |
| } |
| } |
| return value; |
| } else { |
| return this.valuesArray[index]; |
| } |
| } else { |
| return super.get(key); |
| } |
| } |
| |
| protected Object getValueFromResultSet(int index, DatabaseField field) { |
| if (this.shouldUseOptimization) { |
| try { |
| Class fieldType = field.getType(); |
| if (fieldType == ClassConstants.STRING) { |
| if(platform.shouldUseGetSetNString()){ |
| return resultSet.getNString(index + 1); |
| }else { |
| return resultSet.getString(index + 1); |
| } |
| } else if (fieldType == ClassConstants.LONG) { |
| return resultSet.getLong(index + 1); |
| } else if (fieldType == ClassConstants.INTEGER) { |
| return resultSet.getInt(index + 1); |
| } |
| } catch (SQLException exception) { |
| DatabaseException commException = this.accessor.processExceptionForCommError(session, exception, null); |
| if (commException != null) { |
| throw commException; |
| } |
| throw DatabaseException.sqlException(exception, accessor, session, false); |
| } |
| } |
| return this.accessor.getObject(this.resultSet, field, this.metaData, index + 1, this.platform, this.optimizeData, this.session); |
| } |
| |
| public void reset() { |
| if (this.isPopulatingObject) { |
| this.isPopulatingObject = false; |
| if (this.shouldKeepValues) { |
| int size = this.valuesArray.length; |
| for (int index = 0; index < size; index++) { |
| this.valuesArray[index] = null; |
| } |
| } |
| } else { |
| // TODO: this row hasn't been used to populate object, so values for all fields are null except for primary key fields and, possibly, version field. |
| int size = this.valuesArray.length; |
| for (int index = 0; index < size; index++) { |
| this.valuesArray[index] = null; |
| } |
| } |
| } |
| |
| public boolean shouldKeepValues() { |
| return this.shouldKeepValues; |
| } |
| |
| public void setShouldKeepValues(boolean shouldKeepValues) { |
| this.shouldKeepValues = shouldKeepValues; |
| } |
| |
| public boolean shouldUseOptimization() { |
| return this.shouldUseOptimization; |
| } |
| |
| public void setShouldUseOptimization(boolean shouldUseOptimization) { |
| this.shouldUseOptimization = shouldUseOptimization; |
| } |
| |
| public boolean hasValues() { |
| return this.valuesArray[0] != null; |
| } |
| |
| public boolean isPopulatingObject() { |
| return this.isPopulatingObject; |
| } |
| |
| @Override |
| protected String toStringAditional() { |
| return (this.shouldKeepValues ? " shouldKeepValues" : "") + (shouldUseOptimization ? " shouldUseOptimization" : "") + (isPopulatingObject ? " isPopulatingObject" : ""); |
| } |
| |
| @Override |
| public void setSopObject(Object sopObject) { |
| this.sopObject = sopObject; |
| // sopObject is set - the row is used to populate object |
| this.isPopulatingObject = true; |
| } |
| } |