Package com.j256.ormlite.stmt.mapped

Source Code of com.j256.ormlite.stmt.mapped.MappedCreate$KeyHolder

package com.j256.ormlite.stmt.mapped;

import java.sql.SQLException;

import com.j256.ormlite.dao.ObjectCache;
import com.j256.ormlite.db.DatabaseType;
import com.j256.ormlite.field.FieldType;
import com.j256.ormlite.logger.Log.Level;
import com.j256.ormlite.misc.SqlExceptionUtil;
import com.j256.ormlite.support.DatabaseConnection;
import com.j256.ormlite.support.GeneratedKeyHolder;
import com.j256.ormlite.table.TableInfo;

/**
* A mapped statement for creating a new instance of an object.
*
* @author graywatson
*/
public class MappedCreate<T, ID> extends BaseMappedStatement<T, ID> {

  private final String queryNextSequenceStmt;
  private String dataClassName;
  private int versionFieldTypeIndex;

  private MappedCreate(TableInfo<T, ID> tableInfo, String statement, FieldType[] argFieldTypes,
      String queryNextSequenceStmt, int versionFieldTypeIndex) {
    super(tableInfo, statement, argFieldTypes);
    this.dataClassName = tableInfo.getDataClass().getSimpleName();
    this.queryNextSequenceStmt = queryNextSequenceStmt;
    this.versionFieldTypeIndex = versionFieldTypeIndex;
  }

  /**
   * Create an object in the database.
   */
  public int insert(DatabaseType databaseType, DatabaseConnection databaseConnection, T data, ObjectCache objectCache)
      throws SQLException {
    KeyHolder keyHolder = null;
    if (idField != null) {
      boolean assignId;
      if (idField.isAllowGeneratedIdInsert() && !idField.isObjectsFieldValueDefault(data)) {
        assignId = false;
      } else {
        assignId = true;
      }
      if (idField.isSelfGeneratedId() && idField.isGeneratedId()) {
        if (assignId) {
          idField.assignField(data, idField.generateId(), false, objectCache);
        }
      } else if (idField.isGeneratedIdSequence() && databaseType.isSelectSequenceBeforeInsert()) {
        if (assignId) {
          assignSequenceId(databaseConnection, data, objectCache);
        }
      } else if (idField.isGeneratedId()) {
        if (assignId) {
          // get the id back from the database
          keyHolder = new KeyHolder();
        }
      } else {
        // the id should have been set by the caller already
      }
    }

    try {
      // implement {@link DatabaseField#foreignAutoCreate()}, need to do this _before_ getFieldObjects() below
      if (tableInfo.isForeignAutoCreate()) {
        for (FieldType fieldType : tableInfo.getFieldTypes()) {
          if (!fieldType.isForeignAutoCreate()) {
            continue;
          }
          // get the field value
          Object foreignObj = fieldType.extractRawJavaFieldValue(data);
          if (foreignObj != null && fieldType.getForeignIdField().isObjectsFieldValueDefault(foreignObj)) {
            fieldType.createWithForeignDao(foreignObj);
          }
        }
      }

      Object[] args = getFieldObjects(data);
      Object versionDefaultValue = null;
      // implement {@link DatabaseField#version()}
      if (versionFieldTypeIndex >= 0 && args[versionFieldTypeIndex] == null) {
        // if the version is null then we need to initialize it before create
        FieldType versionFieldType = argFieldTypes[versionFieldTypeIndex];
        versionDefaultValue = versionFieldType.moveToNextValue(null);
        args[versionFieldTypeIndex] = versionFieldType.convertJavaFieldToSqlArgValue(versionDefaultValue);
      }

      int rowC;
      try {
        rowC = databaseConnection.insert(statement, args, argFieldTypes, keyHolder);
      } catch (SQLException e) {
        logger.debug("insert data with statement '{}' and {} args, threw exception: {}", statement,
            args.length, e);
        if (args.length > 0) {
          // need to do the (Object) cast to force args to be a single object
          logger.trace("insert arguments: {}", (Object) args);
        }
        throw e;
      }
      logger.debug("insert data with statement '{}' and {} args, changed {} rows", statement, args.length, rowC);
      if (args.length > 0) {
        // need to do the (Object) cast to force args to be a single object
        logger.trace("insert arguments: {}", (Object) args);
      }
      if (rowC > 0) {
        if (versionDefaultValue != null) {
          argFieldTypes[versionFieldTypeIndex].assignField(data, versionDefaultValue, false, null);
        }
        if (keyHolder != null) {
          // assign the key returned by the database to the object's id field after it was inserted
          Number key = keyHolder.getKey();
          if (key == null) {
            // may never happen but let's be careful out there
            throw new SQLException(
                "generated-id key was not set by the update call, maybe a schema mismatch between entity and database table?");
          }
          if (key.longValue() == 0L) {
            // sanity check because the generated-key returned is 0 by default, may never happen
            throw new SQLException(
                "generated-id key must not be 0 value, maybe a schema mismatch between entity and database table?");
          }
          assignIdValue(data, key, "keyholder", objectCache);
        }
        /*
         * If we have a cache and if all of the foreign-collection fields have been assigned then add to cache.
         * However, if one of the foreign collections has not be assigned then don't add it to the cache.
         */
        if (objectCache != null && foreignCollectionsAreAssigned(tableInfo.getForeignCollections(), data)) {
          Object id = idField.extractJavaFieldValue(data);
          objectCache.put(clazz, id, data);
        }
      }

      return rowC;
    } catch (SQLException e) {
      throw SqlExceptionUtil.create("Unable to run insert stmt on object " + data + ": " + statement, e);
    }
  }

  public static <T, ID> MappedCreate<T, ID> build(DatabaseType databaseType, TableInfo<T, ID> tableInfo) {
    StringBuilder sb = new StringBuilder(128);
    appendTableName(databaseType, sb, "INSERT INTO ", tableInfo.getTableName());
    int argFieldC = 0;
    int versionFieldTypeIndex = -1;
    // first we count up how many arguments we are going to have
    for (FieldType fieldType : tableInfo.getFieldTypes()) {
      if (isFieldCreatable(databaseType, fieldType)) {
        if (fieldType.isVersion()) {
          versionFieldTypeIndex = argFieldC;
        }
        argFieldC++;
      }
    }
    FieldType[] argFieldTypes = new FieldType[argFieldC];
    if (argFieldC == 0) {
      databaseType.appendInsertNoColumns(sb);
    } else {
      argFieldC = 0;
      boolean first = true;
      sb.append('(');
      for (FieldType fieldType : tableInfo.getFieldTypes()) {
        if (!isFieldCreatable(databaseType, fieldType)) {
          continue;
        }
        if (first) {
          first = false;
        } else {
          sb.append(",");
        }
        appendFieldColumnName(databaseType, sb, fieldType, null);
        argFieldTypes[argFieldC++] = fieldType;
      }
      sb.append(") VALUES (");
      first = true;
      for (FieldType fieldType : tableInfo.getFieldTypes()) {
        if (!isFieldCreatable(databaseType, fieldType)) {
          continue;
        }
        if (first) {
          first = false;
        } else {
          sb.append(",");
        }
        sb.append("?");
      }
      sb.append(")");
    }
    FieldType idField = tableInfo.getIdField();
    String queryNext = buildQueryNextSequence(databaseType, idField);
    return new MappedCreate<T, ID>(tableInfo, sb.toString(), argFieldTypes, queryNext, versionFieldTypeIndex);
  }

  private boolean foreignCollectionsAreAssigned(FieldType[] foreignCollections, Object data) throws SQLException {
    for (FieldType fieldType : foreignCollections) {
      if (fieldType.extractJavaFieldValue(data) == null) {
        return false;
      }
    }
    return true;
  }

  private static boolean isFieldCreatable(DatabaseType databaseType, FieldType fieldType) {
    // we don't insert anything if it is a collection
    if (fieldType.isForeignCollection()) {
      // skip foreign collections
      return false;
    } else if (fieldType.isReadOnly()) {
      // ignore read-only fields
      return false;
    } else if (databaseType.isIdSequenceNeeded() && databaseType.isSelectSequenceBeforeInsert()) {
      // we need to query for the next value from the sequence and the idField is inserted afterwards
      return true;
    } else if (fieldType.isGeneratedId() && !fieldType.isSelfGeneratedId() && !fieldType.isAllowGeneratedIdInsert()) {
      // skip generated-id fields because they will be auto-inserted
      return false;
    } else {
      return true;
    }
  }

  private static String buildQueryNextSequence(DatabaseType databaseType, FieldType idField) {
    if (idField == null) {
      return null;
    }
    String seqName = idField.getGeneratedIdSequence();
    if (seqName == null) {
      return null;
    } else {
      StringBuilder sb = new StringBuilder(64);
      databaseType.appendSelectNextValFromSequence(sb, seqName);
      return sb.toString();
    }
  }

  private void assignSequenceId(DatabaseConnection databaseConnection, T data, ObjectCache objectCache)
      throws SQLException {
    // call the query-next-sequence stmt to increment the sequence
    long seqVal = databaseConnection.queryForLong(queryNextSequenceStmt);
    logger.debug("queried for sequence {} using stmt: {}", seqVal, queryNextSequenceStmt);
    if (seqVal == 0) {
      // sanity check that it is working
      throw new SQLException("Should not have returned 0 for stmt: " + queryNextSequenceStmt);
    }
    assignIdValue(data, seqVal, "sequence", objectCache);
  }

  private void assignIdValue(T data, Number val, String label, ObjectCache objectCache) throws SQLException {
    // better to do this in one please with consistent logging
    idField.assignIdValue(data, val, objectCache);
    if (logger.isLevelEnabled(Level.DEBUG)) {
      logger.debug("assigned id '{}' from {} to '{}' in {} object",
          new Object[] { val, label, idField.getFieldName(), dataClassName });
    }
  }

  private static class KeyHolder implements GeneratedKeyHolder {
    Number key;

    public Number getKey() {
      return key;
    }

    public void addKey(Number key) throws SQLException {
      if (this.key == null) {
        this.key = key;
      } else {
        throw new SQLException("generated key has already been set to " + this.key + ", now set to " + key);
      }
    }
  }
}
TOP

Related Classes of com.j256.ormlite.stmt.mapped.MappedCreate$KeyHolder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.