Java tutorial
/** * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2009-2010], VMware, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program is distributed * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. * */ //$Id: MultipleHiLoPerTableGenerator.java 9720 2006-03-31 00:11:54Z epbernard $ package org.hyperic.hibernate.id; import java.io.Serializable; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.LinkedList; import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.MappingException; import org.hibernate.dialect.Dialect; import org.hibernate.engine.SessionImplementor; import org.hibernate.engine.TransactionHelper; import org.hibernate.exception.JDBCExceptionHelper; import org.hibernate.id.Configurable; import org.hibernate.id.IdentifierGeneratorFactory; import org.hibernate.id.PersistentIdentifierGenerator; import org.hibernate.mapping.Table; import org.hibernate.type.Type; import org.hibernate.util.PropertiesHelper; import org.hyperic.hq.context.Bootstrap; import org.hyperic.util.jdbc.DBUtil; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; /** * * A hilo <tt>IdentifierGenerator</tt> that returns a <tt>Long</tt>, constructed * using a hi/lo algorithm. The hi value MUST be fetched in a seperate * transaction to the <tt>Session</tt> transaction so the generator must be able * to obtain a new connection and commit it. If running in a Spring-managed * transaction, the TransactionTemplate will be used to suspend the existing * transaction and create a new one. Else, an attempt will be made to open a new * JDBC connection to do the work. * * A hilo <tt>IdentifierGenerator</tt> that uses a database table to store the * last generated values. A table can contains several hi values. They are * distinct from each other through a key * <p/> * <p> * This implementation is not compliant with a user connection * </p> * <p/> * * <p> * Allowed parameters (all of them are optional): * </p> * <ul> * <li>table: table name (default <tt>hibernate_sequences</tt>)</li> * <li>primary_key_column: key column name (default <tt>sequence_name</tt>)</li> * <li>value_column: hi value column name(default * <tt>sequence_next_hi_value</tt>)</li> * <li>primary_key_value: key value for the current entity (default to the * entity's primary table name)</li> * <li>primary_key_length: length of the key column in DB represented as a * varchar (default to 255)</li> * <li>max_lo: max low value before increasing hi (default to Short.MAX_VALUE)</li> * </ul> * * @author Emmanuel Bernard * @author <a href="mailto:kr@hbt.de">Klaus Richarz</a>. */ public class HQMultipleHiLoPerTableGenerator extends TransactionHelper implements PersistentIdentifierGenerator, Configurable { private static final Log log = LogFactory.getLog(HQMultipleHiLoPerTableGenerator.class); private LinkedList seqs = new LinkedList(); public static final String ID_TABLE = "table"; public static final String PK_COLUMN_NAME = "primary_key_column"; public static final String PK_VALUE_NAME = "primary_key_value"; public static final String VALUE_COLUMN_NAME = "value_column"; public static final String PK_LENGTH_NAME = "primary_key_length"; public static final String INITIAL_HI = "initial_hi"; public static final int DEFAULT_INITIAL_HI = 0; private static final int DEFAULT_PK_LENGTH = 255; public static final String DEFAULT_TABLE = "hibernate_sequences"; private static final String DEFAULT_PK_COLUMN = "sequence_name"; private static final String DEFAULT_VALUE_COLUMN = "sequence_next_hi_value"; private String tableName; private String pkColumnName; private String valueColumnName; private String query; private String insert; private String update; private String keyValue; // hilo params public static final String MAX_LO = "max_lo"; private long hi; private int lo; private int maxLo; private int initialHi; private Class returnClass; private int keySize; public String[] sqlCreateStrings(Dialect dialect) throws HibernateException { return new String[] { new StringBuffer().append("create table ").append(tableName).append(" ( ") .append(pkColumnName).append(" ").append(dialect.getTypeName(Types.VARCHAR, keySize, 0, 0)) .append(", ").append(valueColumnName).append(" ").append(dialect.getTypeName(Types.INTEGER)) .append(", ").append("primary key (" + pkColumnName + ") ").append(" ) ").toString() }; } public String[] sqlDropStrings(Dialect dialect) throws HibernateException { StringBuffer sqlDropString = new StringBuffer().append("drop table "); if (dialect.supportsIfExistsBeforeTableName()) sqlDropString.append("if exists "); sqlDropString.append(tableName).append(dialect.getCascadeConstraintsString()); if (dialect.supportsIfExistsAfterTableName()) sqlDropString.append(" if exists"); return new String[] { sqlDropString.toString() }; } public Object generatorKey() { return tableName; } protected Serializable doWorkInCurrentTransaction(Connection conn, String sql) throws SQLException { return updateSequence(conn); } private Integer updateSequence(Connection connection) throws SQLException { int result; int rows; do { // The loop ensures atomicity of the // select + update even for no transaction // or read committed isolation level // sql = query; log.debug(query); PreparedStatement qps = connection.prepareStatement(query); PreparedStatement ips = null; try { // qps.setString(1, key); ResultSet rs = qps.executeQuery(); boolean isInitialized = rs.next(); if (!isInitialized) { result = 0; ips = connection.prepareStatement(insert); // ips.setString(1, key); ips.setInt(1, initialHi); ips.execute(); } else { result = rs.getInt(1); } rs.close(); } catch (SQLException sqle) { log.error("could not read or init a hi value", sqle); throw sqle; } finally { if (ips != null) { ips.close(); } qps.close(); } // sql = update; PreparedStatement ups = connection.prepareStatement(update); try { ups.setInt(1, result + 1); ups.setInt(2, result); // ups.setString( 3, key ); rows = ups.executeUpdate(); } catch (SQLException sqle) { log.error("could not update hi value in: " + tableName, sqle); throw sqle; } finally { ups.close(); } } while (rows == 0); return new Integer(result); } public synchronized Serializable generate(SessionImplementor session, Object obj) throws HibernateException { Number num; do { num = _generate(session, obj); } while (!numberIsValid(num)); return num; } private int executeInNewTransaction(TransactionTemplate transactionTemplate, final SessionImplementor session) { if (transactionTemplate != null) { return transactionTemplate.execute(new TransactionCallback<Integer>() { //We are in a Spring managed environment public Integer doInTransaction(TransactionStatus status) { try { return updateSequence(Bootstrap.getBean(DBUtil.class).getConnection()); } catch (SQLException sqle) { throw JDBCExceptionHelper.convert(session.getFactory().getSQLExceptionConverter(), sqle, "could not get or update next value", null); } } }); } else { //Use Hibernate's JDBC delegation return (Integer) doWorkInNewTransaction(session); } } private synchronized Number _generate(final SessionImplementor session, Object obj) throws HibernateException { TransactionTemplate transactionTemplate = null; if (Bootstrap.hasAppContext()) { // We are running in Spring-managed server environment. Suspend the // existing Spring-managed transaction and work in a new one. transactionTemplate = new TransactionTemplate( (PlatformTransactionManager) Bootstrap.getBean("transactionManager")); transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); } if (maxLo < 1) { // keep the behavior consistent even for boundary usages int val = executeInNewTransaction(transactionTemplate, session); if (val == 0) { val = executeInNewTransaction(transactionTemplate, session); } Number num = IdentifierGeneratorFactory.createNumber(val, returnClass); if (log.isTraceEnabled()) { log.trace(this + " created seq: " + keyValue + " / " + num); } return num; } else if (lo > maxLo) { int hival = executeInNewTransaction(transactionTemplate, session); lo = (hival == 0) ? 1 : 0; hi = hival * (maxLo + 1); log.debug("new hi value: " + hival); } Number num = IdentifierGeneratorFactory.createNumber(hi + lo++, returnClass); if (log.isTraceEnabled()) { log.trace(this + " created seq: " + keyValue + " / " + num); } return num; } private synchronized boolean numberIsValid(Number num) { if (seqs.contains(num)) { log.warn("sequence generator generated sequence " + keyValue + "/" + num + " which is a duplicate sequence, retrying"); return false; } seqs.add(num); while (seqs.size() >= 10) { seqs.removeLast(); } return true; } public void configure(Type type, Properties params, Dialect dialect) throws MappingException { tableName = PropertiesHelper.getString(ID_TABLE, params, DEFAULT_TABLE); pkColumnName = PropertiesHelper.getString(PK_COLUMN_NAME, params, DEFAULT_PK_COLUMN); valueColumnName = PropertiesHelper.getString(VALUE_COLUMN_NAME, params, DEFAULT_VALUE_COLUMN); initialHi = PropertiesHelper.getInt(INITIAL_HI, params, DEFAULT_INITIAL_HI); String schemaName = params.getProperty(SCHEMA); String catalogName = params.getProperty(CATALOG); keySize = PropertiesHelper.getInt(PK_LENGTH_NAME, params, DEFAULT_PK_LENGTH); keyValue = PropertiesHelper.getString(PK_VALUE_NAME, params, params.getProperty(TABLE)); if (tableName.indexOf('.') < 0) { tableName = Table.qualify(catalogName, schemaName, tableName); } query = "select " + valueColumnName + " from " + dialect.appendLockHint(LockMode.UPGRADE, tableName) + " where " + pkColumnName + " = '" + keyValue + "'" + dialect.getForUpdateString(); update = "update " + tableName + " set " + valueColumnName + " = ? where " + valueColumnName + " = ? and " + pkColumnName + " = '" + keyValue + "'"; insert = "insert into " + tableName + "(" + pkColumnName + ", " + valueColumnName + ") " + "values('" + keyValue + "', ?)"; // hilo config maxLo = PropertiesHelper.getInt(MAX_LO, params, Short.MAX_VALUE); lo = maxLo + 1; // so we "clock over" on the first invocation returnClass = type.getReturnedClass(); } }