Package org.apache.cayenne.dba

Source Code of org.apache.cayenne.dba.JdbcPkGenerator$PkRetrieveProcessor

/*****************************************************************
*   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.List;
import java.util.Map;

import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.DataRow;
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.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.
*/
public class JdbcPkGenerator implements PkGenerator {

    private JdbcAdapter adapter;

    public static final int DEFAULT_PK_CACHE_SIZE = 20;

    protected Map<String, LongPkRange> pkCache = new HashMap<String, LongPkRange>();
    protected int pkCacheSize = DEFAULT_PK_CACHE_SIZE;

    public JdbcPkGenerator(JdbcAdapter adapter) {
        super();
        this.adapter = adapter;
    }

    public JdbcAdapter getAdapter() {
        return adapter;
    }

    public void createAutoPk(DataNode node, List<DbEntity> 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
        for (DbEntity ent : dbEntities) {
            runUpdate(node, pkCreateString(ent.getName()));
        }
    }

    public List<String> createAutoPkStatements(List<DbEntity> dbEntities) {
        List<String> list = new ArrayList<String>(dbEntities.size() + 2);

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

        for (DbEntity ent : dbEntities) {
            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<DbEntity> dbEntities) throws Exception {
        if (autoPkTableExists(node)) {
            runUpdate(node, dropAutoPkString());
        }
    }

    public List<String> dropAutoPkStatements(List<DbEntity> dbEntities) {
        List<String> list = new ArrayList<String>(1);
        list.add(dropAutoPkString());
        return list;
    }

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

        return buf.toString();
    }

    protected String pkDeleteString(List<DbEntity> dbEntities) {
        StringBuilder buf = new StringBuilder();
        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 = dbEntities.get(i);
            buf.append('\'').append(ent.getName()).append('\'');
        }
        buf.append(')');
        return buf.toString();
    }

    protected String pkCreateString(String entName) {
        StringBuilder buf = new StringBuilder();
        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) {
        StringBuilder buf = new StringBuilder();
        buf.append("SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = '").append(
                entName).append('\'');
        return buf.toString();
    }

    protected String pkUpdateString(String entName) {
        StringBuilder buf = new StringBuilder();
        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();
        }
    }

    /**
     * Generates a unique and non-repeating primary key for specified dbEntity.
     * <p>
     * This implementation is naive since it does not lock the database rows when
     * executing select and subsequent update. Adapter-specific implementations are more
     * robust.
     * </p>
     *
     * @since 3.0
     */
    public Object generatePk(DataNode node, DbAttribute pk) throws Exception {

        DbEntity entity = (DbEntity) pk.getEntity();

        switch (pk.getType()) {
            case Types.BINARY:
            case Types.VARBINARY:
                return IDUtil.pseudoUniqueSecureByteSequence(pk.getMaxLength());
        }

        DbKeyGenerator pkGenerator = entity.getPrimaryKeyGenerator();
        long cacheSize;
        if (pkGenerator != null && pkGenerator.getKeyCacheSize() != null)
            cacheSize = pkGenerator.getKeyCacheSize().intValue();
        else
            cacheSize = pkCacheSize;

        long value;

        // if no caching, always generate fresh
        if (cacheSize <= 1) {
            value = longPkFromDatabase(node, entity);
        }
        else {
            synchronized (pkCache) {
                LongPkRange r = pkCache.get(entity.getName());

                if (r == null) {
                    // created exhausted LongPkRange
                    r = new LongPkRange(1l, 0l);
                    pkCache.put(entity.getName(), r);
                }

                if (r.isExhausted()) {
                    long val = longPkFromDatabase(node, entity);
                    r.reset(val, val + cacheSize - 1);
                }

                value = r.getNextPrimaryKey();
            }
        }

        if (pk.getType() == Types.BIGINT) {
            return Long.valueOf(value);
        }
        else {
            // leaving it up to the user to ensure that PK does not exceed max int...
            return Integer.valueOf((int) value);
        }
    }

    /**
     * Performs primary key generation ignoring cache. Generates a range of primary keys
     * as specified by "pkCacheSize" bean property.
     * <p>
     * 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".
     * </p>
     *
     * @since 3.0
     */
    protected long longPkFromDatabase(DataNode node, DbEntity entity) throws Exception {
        String select = "SELECT #result('NEXT_ID' 'long' 'NEXT_ID') "
                + "FROM AUTO_PK_SUPPORT "
                + "WHERE TABLE_NAME = '"
                + entity.getName()
                + '\'';

        // run queries via DataNode to utilize its transactional behavior
        List<Query> queries = new ArrayList<Query>(2);
        queries.add(new SQLTemplate(entity, select));
        queries.add(new SQLTemplate(entity, pkUpdateString(entity.getName())));

        PkRetrieveProcessor observer = new PkRetrieveProcessor(entity.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 <code>pkCacheSize</code>
     * parameter is less than 1, cache size is set to "one".
     * <p>
     * <i>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. </i>
     * </p>
     */
    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;
        }

        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 nextRows(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);
            }

            DataRow lastPk = (DataRow) 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 nextGeneratedRows(Query query, ResultIterator keysIterator) {
        }

        public void nextRows(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);
        }
    }
}
TOP

Related Classes of org.apache.cayenne.dba.JdbcPkGenerator$PkRetrieveProcessor

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.