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

org.apache.cayenne.dba.JdbcPkGenerator Maven / Gradle / Ivy

There is a newer version: 2.0.4
Show newest version
/*****************************************************************
 *   Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 ****************************************************************/


package org.apache.cayenne.dba;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Level;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.access.DataNode;
import org.apache.cayenne.access.OperationObserver;
import org.apache.cayenne.access.QueryLogger;
import org.apache.cayenne.access.ResultIterator;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.DbKeyGenerator;
import org.apache.cayenne.map.ObjAttribute;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.SQLTemplate;
import org.apache.cayenne.util.IDUtil;

/**
 * Default primary key generator implementation. Uses a lookup table named
 * "AUTO_PK_SUPPORT" to search and increment primary keys for tables.
 * 
 * @author Andrei Adamchik
 */
public class JdbcPkGenerator implements PkGenerator {

    public static final int DEFAULT_PK_CACHE_SIZE = 20;

    /**
     * @deprecated Since 1.2 unused.
     */
    protected static final String NEXT_ID = "NEXT_ID";

    /**
     * @deprecated Since 1.2 unused.
     */
    protected static final ObjAttribute[] objDesc = new ObjAttribute[] {
        new ObjAttribute("nextId", Integer.class.getName(), null)
    };

    /**
     * @deprecated Since 1.2 unused.
     */
    protected static final DbAttribute[] resultDesc = new DbAttribute[] {
        new DbAttribute(NEXT_ID, Types.INTEGER, null)
    };

    protected Map pkCache = new HashMap();
    protected int pkCacheSize = DEFAULT_PK_CACHE_SIZE;

    static {
        objDesc[0].setDbAttributePath(NEXT_ID);
    }

    public void createAutoPk(DataNode node, List dbEntities) throws Exception {
        // check if a table exists

        // create AUTO_PK_SUPPORT table
        if (!autoPkTableExists(node)) {
            runUpdate(node, pkTableCreateString());
        }

        // delete any existing pk entries
        runUpdate(node, pkDeleteString(dbEntities));

        // insert all needed entries
        Iterator it = dbEntities.iterator();
        while (it.hasNext()) {
            DbEntity ent = (DbEntity) it.next();
            runUpdate(node, pkCreateString(ent.getName()));
        }
    }

    public List createAutoPkStatements(List dbEntities) {
        List list = new ArrayList();

        list.add(pkTableCreateString());
        list.add(pkDeleteString(dbEntities));

        Iterator it = dbEntities.iterator();
        while (it.hasNext()) {
            DbEntity ent = (DbEntity) it.next();
            list.add(pkCreateString(ent.getName()));
        }

        return list;
    }

    /**
     * Drops table named "AUTO_PK_SUPPORT" if it exists in the database.
     */
    public void dropAutoPk(DataNode node, List dbEntities) throws Exception {
        if (autoPkTableExists(node)) {
            runUpdate(node, dropAutoPkString());
        }
    }

    public List dropAutoPkStatements(List dbEntities) {
        List list = new ArrayList();
        list.add(dropAutoPkString());
        return list;
    }

    protected String pkTableCreateString() {
        StringBuffer buf = new StringBuffer();
        buf
                .append("CREATE TABLE AUTO_PK_SUPPORT (")
                .append("  TABLE_NAME CHAR(100) NOT NULL,")
                .append("  NEXT_ID INTEGER NOT NULL,")
                .append("  PRIMARY KEY(TABLE_NAME)")
                .append(")");

        return buf.toString();
    }

    protected String pkDeleteString(List dbEntities) {
        StringBuffer buf = new StringBuffer();
        buf.append("DELETE FROM AUTO_PK_SUPPORT WHERE TABLE_NAME IN (");
        int len = dbEntities.size();
        for (int i = 0; i < len; i++) {
            if (i > 0) {
                buf.append(", ");
            }
            DbEntity ent = (DbEntity) dbEntities.get(i);
            buf.append('\'').append(ent.getName()).append('\'');
        }
        buf.append(')');
        return buf.toString();
    }

    protected String pkCreateString(String entName) {
        StringBuffer buf = new StringBuffer();
        buf
                .append("INSERT INTO AUTO_PK_SUPPORT")
                .append(" (TABLE_NAME, NEXT_ID)")
                .append(" VALUES ('")
                .append(entName)
                .append("', 200)");
        return buf.toString();
    }

    protected String pkSelectString(String entName) {
        StringBuffer buf = new StringBuffer();
        buf.append("SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = '").append(
                entName).append('\'');
        return buf.toString();
    }

    protected String pkUpdateString(String entName) {
        StringBuffer buf = new StringBuffer();
        buf.append("UPDATE AUTO_PK_SUPPORT").append(" SET NEXT_ID = NEXT_ID + ").append(
                pkCacheSize).append(" WHERE TABLE_NAME = '").append(entName).append('\'');
        return buf.toString();
    }

    protected String dropAutoPkString() {
        return "DROP TABLE AUTO_PK_SUPPORT";
    }

    /**
     * Checks if AUTO_PK_TABLE already exists in the database.
     */
    protected boolean autoPkTableExists(DataNode node) throws SQLException {
        Connection con = node.getDataSource().getConnection();
        boolean exists = false;
        try {
            DatabaseMetaData md = con.getMetaData();
            ResultSet tables = md.getTables(null, null, "AUTO_PK_SUPPORT", null);
            try {
                exists = tables.next();
            }
            finally {
                tables.close();
            }
        }
        finally {
            // return connection to the pool
            con.close();
        }

        return exists;
    }

    /**
     * Runs JDBC update over a Connection obtained from DataNode. Returns a number of
     * objects returned from update.
     * 
     * @throws SQLException in case of query failure.
     */
    public int runUpdate(DataNode node, String sql) throws SQLException {
        QueryLogger.logQuery(sql, Collections.EMPTY_LIST);

        Connection con = node.getDataSource().getConnection();
        try {
            Statement upd = con.createStatement();
            try {
                return upd.executeUpdate(sql);
            }
            finally {
                upd.close();
            }
        }
        finally {
            con.close();
        }
    }

    /**
     * @deprecated Since 1.2 corresponding interface method is deprecated.
     */
    public String generatePkForDbEntityString(DbEntity ent) {
        StringBuffer buf = new StringBuffer();
        buf.append(pkSelectString(ent.getName())).append('\n').append(
                pkUpdateString(ent.getName()));
        return buf.toString();
    }

    /**
     * 

* Generates new (unique and non-repeating) primary key for specified dbEntity. *

*

* This implementation is naive since it does not lock the database rows when * executing select and subsequent update. Adapter-specific implementations are more * robust. *

*/ public Object generatePkForDbEntity(DataNode node, DbEntity ent) throws Exception { // check for binary pk Object binPK = binaryPK(ent); if (binPK != null) { return binPK; } DbKeyGenerator pkGenerator = ent.getPrimaryKeyGenerator(); int cacheSize; if (pkGenerator != null && pkGenerator.getKeyCacheSize() != null) cacheSize = pkGenerator.getKeyCacheSize().intValue(); else cacheSize = pkCacheSize; // if no caching, always generate fresh if (cacheSize <= 1) { return new Integer(pkFromDatabase(node, ent)); } synchronized (pkCache) { PkRange r = (PkRange) pkCache.get(ent.getName()); if (r == null) { // created exhaused PkRange r = new PkRange(1, 0); pkCache.put(ent.getName(), r); } if (r.isExhausted()) { int val = pkFromDatabase(node, ent); r.reset(val, val + cacheSize - 1); } return r.getNextPrimaryKey(); } } /** * @return a binary PK if DbEntity has a BINARY or VARBINARY pk, null otherwise. This * method will likely be deprecated in 1.1 in favor of a more generic * soultion. * @since 1.0.2 */ protected byte[] binaryPK(DbEntity entity) { List pkColumns = entity.getPrimaryKey(); if (pkColumns.size() == 1) { DbAttribute pk = (DbAttribute) pkColumns.get(0); if (pk.getMaxLength() > 0 && (pk.getType() == Types.BINARY || pk.getType() == Types.VARBINARY)) { return IDUtil.pseudoUniqueSecureByteSequence(pk.getMaxLength()); } } return null; } /** * Performs primary key generation ignoring cache. Generates a range of primary keys * as specified by "pkCacheSize" bean property. *

* This method is called internally from "generatePkForDbEntity" and then generated * range of key values is saved in cache for performance. Subclasses that implement * different primary key generation solutions should override this method, not * "generatePkForDbEntity". *

*/ protected int pkFromDatabase(DataNode node, DbEntity ent) throws Exception { String select = "SELECT #result('NEXT_ID' 'int' 'NEXT_ID') " + "FROM AUTO_PK_SUPPORT " + "WHERE TABLE_NAME = '" + ent.getName() + '\''; // run queries via DataNode to utilize its transactional behavior List queries = new ArrayList(2); queries.add(new SQLTemplate(ent, select)); queries.add(new SQLTemplate(ent, pkUpdateString(ent.getName()))); PkRetrieveProcessor observer = new PkRetrieveProcessor(ent.getName()); node.performQueries(queries, observer); return observer.getId(); } /** * Returns a size of the entity primary key cache. Default value is 20. If cache size * is set to a value less or equals than "one", no primary key caching is done. */ public int getPkCacheSize() { return pkCacheSize; } /** * Sets the size of the entity primary key cache. If pkCacheSize * parameter is less than 1, cache size is set to "one". *

* Note that our tests show that setting primary key cache value to anything much * bigger than 20 does not give any significant performance increase. Therefore it * does not make sense to use bigger values, since this may potentially create big * gaps in the database primary key sequences in cases like application crashes or * restarts. *

*/ public void setPkCacheSize(int pkCacheSize) { this.pkCacheSize = (pkCacheSize < 1) ? 1 : pkCacheSize; } public void reset() { pkCache.clear(); } /** * OperationObserver for primary key retrieval. */ final class PkRetrieveProcessor implements OperationObserver { Number id; String entityName; PkRetrieveProcessor(String entityName) { this.entityName = entityName; } /** * @deprecated since 1.2 super is deprecated */ public Level getLoggingLevel() { return null; } public boolean isIteratedResult() { return false; } public int getId() { if (id == null) { throw new CayenneRuntimeException("No key was retrieved for entity " + entityName); } return id.intValue(); } public void nextDataRows(Query query, List dataRows) { // process selected object, issue an update query if (dataRows == null || dataRows.size() == 0) { throw new CayenneRuntimeException( "Error generating PK : entity not supported: " + entityName); } if (dataRows.size() > 1) { throw new CayenneRuntimeException( "Error generating PK : too many rows for entity: " + entityName); } Map lastPk = (Map) dataRows.get(0); id = (Number) lastPk.get("NEXT_ID"); } public void nextCount(Query query, int resultCount) { if (resultCount != 1) { throw new CayenneRuntimeException("Error generating PK for entity '" + entityName + "': update count is wrong - " + resultCount); } } public void nextBatchCount(Query query, int[] resultCount) { } public void nextGeneratedDataRows(Query query, ResultIterator keysIterator) { } public void nextDataRows(Query q, ResultIterator it) { } public void nextQueryException(Query query, Exception ex) { throw new CayenneRuntimeException("Error generating PK for entity '" + entityName + "'.", ex); } public void nextGlobalException(Exception ex) { throw new CayenneRuntimeException("Error generating PK for entity: " + entityName, ex); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy