OFBiz 数据库自增SequenceUtil.java

来源:互联网 发布:淘宝同款排除王工具 编辑:程序博客网 时间:2024/05/20 21:22
/******************************************************************************* * 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.ofbiz.entity.util; import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;import java.util.Hashtable;import java.util.Map; import javax.transaction.Transaction; import org.ofbiz.base.util.Debug;import org.ofbiz.base.util.UtilProperties;import org.ofbiz.entity.GenericDelegator;import org.ofbiz.entity.GenericEntityException;import org.ofbiz.entity.datasource.GenericHelperInfo;import org.ofbiz.entity.jdbc.ConnectionFactory;import org.ofbiz.entity.model.ModelEntity;import org.ofbiz.entity.model.ModelField;import org.ofbiz.entity.transaction.GenericTransactionException;import org.ofbiz.entity.transaction.TransactionUtil; /** * Sequence Utility to get unique sequences from named sequence banks * Uses a collision detection approach to safely get unique sequenced ids in banks from the database */public class SequenceUtil {     public static final String module = SequenceUtil.class.getName();     /**     * 以key-value的结构将SequenceBank的实例存储在Map中,     * 其中,key为seqName(数据库专门有一张表`SEQUENCE_VALUE_ITEM`来存储所有的自增主键的当前值,该表的主键为SEQ_NAME即对应此处的key)     * value为`SequenceBank`的实例,每个实例负责一个自增主键的生成     */    private final MapString, SequenceBank sequences = new HashtableString, SequenceBank();    private final GenericHelperInfo helperInfo;                     //提供数据库连接相关的信息    private final long bankSize;                                    //bank size,扩充SequenceBank的步长/基准    private final String tableName;                                 //存储自增主键值的表名,默认是`SEQUENCE_VALUE_ITEM`    private final String nameColName;                               //获得`SEQUENCE_VALUE_ITEM`的主键名(`SEQ_NAME`)    private final String idColName;                                 //获得`SEQUENCE_VALUE_ITEM`的`SEQ_ID`字段(它存储对应主键的当前值)    private final boolean clustered;                                //是否采用分布式缓存     /**     * 实例初始化,同时将bankSize设置为默认值     */    public SequenceUtil(GenericDelegator delegator, GenericHelperInfo helperInfo, ModelEntity seqEntity, String nameFieldName, String idFieldName) {        this.helperInfo = helperInfo;        if (seqEntity == null) {            throw new IllegalArgumentException("The sequence model entity was null but is required.");        }        this.tableName = seqEntity.getTableName(helperInfo.getHelperBaseName());         ModelField nameField = seqEntity.getField(nameFieldName);         if (nameField == null) {            throw new IllegalArgumentException("Could not find the field definition for the sequence name field " + nameFieldName);        }        this.nameColName = nameField.getColName();         ModelField idField = seqEntity.getField(idFieldName);         if (idField == null) {            throw new IllegalArgumentException("Could not find the field definition for the sequence id field " + idFieldName);        }        this.idColName = idField.getColName();        long bankSize = SequenceBank.defaultBankSize;        if (seqEntity.getSequenceBankSize() != null) {            bankSize = seqEntity.getSequenceBankSize().longValue();        }        this.bankSize = bankSize;        clustered = delegator.useDistributedCacheClear() || "Y".equals(UtilProperties.getPropertyValue("general.properties", "clustered"));                    }     /**     * 获得序列的下一个值     * @params:     * seqName - 需要获取序列的主键     * staggerMax - 用于错开序列值的最大值(如果为1,则下次增长为本次加1,否则下次增长幅度为1~staggerMax之间的值)     * seqModelEntity - 表`SEQUENCE_VALUE_ITEM`对应的Entity     */    public Long getNextSeqId(String seqName, long staggerMax, ModelEntity seqModelEntity) {        SequenceBank bank = this.getBank(seqName, seqModelEntity);        return bank.getNextSeqId(staggerMax);    }     /**     * 强制刷新某个序列的当前值     */    public void forceBankRefresh(String seqName, long staggerMax) {        // don't use the get method because we don't want to create if it fails        //不要使用方法`getBank`,因为它在获取SequenceBank失败后会创建一个新的,而此处不应该这么做        SequenceBank bank = sequences.get(seqName);        if (bank == null) {            return;        }         bank.refresh(staggerMax);    }     /**     * 获得`SequenceBank`的实例,如果不存在,则创建一个新的并设置到map中     */    private SequenceBank getBank(String seqName, ModelEntity seqModelEntity) {        SequenceBank bank = sequences.get(seqName);         if (bank == null) {            synchronized(this) {                bank = sequences.get(seqName);                if (bank == null) {                    bank = new SequenceBank(seqName);                    sequences.put(seqName, bank);                }            }        }         return bank;    }     private class SequenceBank {        public static final long defaultBankSize = 10;              //SequenceBank默认增长的步长        public static final long maxBankSize = 5000;                //SequenceBank最大增长的步长        public static final long startSeqId = 10000;                //sequenceId的起始增长值        public static final long minWaitMillis = 5;                 //当发生sequence collision时用于计算线程sleep的最小时常        public static final long maxWaitMillis = 50;                //当发生sequence collision时用于计算线程sleep的最大时常        public static final int maxTries = 5;                       //当发生序列不同步时,最大尝试次数         private long curSeqId;                                      //当前序列值        private long maxSeqId;                                      //最大序列值        private final String seqName;                               //序列名称                /**         * 初始化内部变量,同时调用`fillBank`设置初始值          */        private SequenceBank(String seqName) {            this.seqName = seqName;            curSeqId = 0;            maxSeqId = 0;            fillBank(1);        }         /**         * 同步方法,根据给定的错开序列的最大值,获得下一个序列         */        private synchronized Long getNextSeqId(long staggerMax) {            long stagger = 1;                                       //默认值设置为1            if (staggerMax  1) {                                                   stagger = Math.round(Math.random() * staggerMax);   //取(staggerMax * 0 ~ 1之间的任意double值)结果最解决的整数                if (stagger == 0) stagger = 1;                      //如果为0,则默认基准为1            }             if ((curSeqId + stagger) = maxSeqId) {                 //if 当前序列值 + 错开基准 = 最大序列值                                                                     //注:此处(cur/max)SeqId 在构造方法中调用fillBank时被初始化                Long retSeqId = Long.valueOf(curSeqId);                curSeqId += stagger;                return retSeqId;                                    //直接返回当前序列值加上错开基准后的值            } else {                                                //else 说明当前序列值跟最大序列值非常接近,                                                                    //不足以生成下一个序列,需要扩充SequenceBank                fillBank(stagger);                                  //扩充当前sequence bank,再次设置curSeqId/maxSeqId                if ((curSeqId + stagger) = maxSeqId) {             //再次判断当前序列值是否符合要求                    Long retSeqId = Long.valueOf(curSeqId);                    curSeqId += stagger;                    return retSeqId;                                //符合则返回                } else {                    Debug.logError("[SequenceUtil.SequenceBank.getNextSeqId] Fill bank failed, returning null", module);                    return null;                                    //不符合,返回                }            }        }         /**         * 刷新sequence bank(通过设置curSeqId为maxSeqId,跳过fillBank方法的第一个判断)         */        private void refresh(long staggerMax) {            this.curSeqId = this.maxSeqId;            this.fillBank(staggerMax);        }         /**         * 同步方法,扩充sequence bank,可能会存在多线程调用,因此需要设置方法为同步         */        private synchronized void fillBank(long stagger) {            //Debug.logWarning("[SequenceUtil.SequenceBank.fillBank] Starting fillBank Thread Name is: " + Thread.currentThread().getName() + ":" + Thread.currentThread().toString(), module);             // no need to get a new bank, SeqIds available            //如果满足要求,则不扩充sequence bank(这也是为什么在refresh方法中,需要将curSeqId设置为maxSeqId的原因)            if ((curSeqId + stagger) = maxSeqId) return;             //初始化为默认大小,50            long bankSize = SequenceUtil.this.bankSize;            if (stagger  1) {                              //如果stagger,则bankSize默认值太小,需要扩充stagger倍                // NOTE: could use staggerMax for this, but if that is done it would be easier to guess a valid next id without a brute force attack                bankSize = stagger * defaultBankSize;            }             //如果bankSize 大于 maxBankSize,则重新设置为maxBankSize            if (bankSize  maxBankSize) bankSize = maxBankSize;             long val1 = 0;              //存储curSeqId,先从数据库获取当前值,存于该变量            long val2 = 0;              //存储扩充SequenceBank之后的该sequence的新的计算起点(更新过数据库之后),即为maxSeqId             // NOTE: the fancy ethernet type stuff is for the case where transactions not available, or something funny happens with really sensitive timing (between first select and update, for example)            int numTries = 0;           //初始化,尝试次数             while (val1 + bankSize != val2) {       //结束循环的条件: 如果curSeqId + bankSize = maxSeqId,否则视为扩充失败                if (Debug.verboseOn()) Debug.logVerbose("[SequenceUtil.SequenceBank.fillBank] Trying to get a bank of sequenced ids for " +                        this.seqName + "; start of loop val1=" + val1 + ", val2=" + val2 + ", bankSize=" + bankSize, module);                 // not sure if this synchronized block is totally necessary, the method is synchronized but it does do a wait/sleep                // outside of this block, and this is the really sensitive block, so making sure it is isolated; there is some overhead                // to this, but very bad things can happen if we try to do too many of these at once for a single sequencer                //翻译上一段:                //不确定是否这里设置为synchronized block,该方法是synchronized的,但他确实会在该block外面进行wait/sleep操作                //并且这确实是一个非常敏感的block,所以需要确保它是完全隔离的。这确实会花费一些开销,但如果我们对单个sequencer                //同时太多次调用,会是一件非常糟糕的事情                //备注:这里锁住的是`this`即为当前对象本身,而不是一个静态变量,这是由该类的结构决定的。                //该类被实例化后,才会在内存对`SEQUENCE_VALUE_ITEM`产生一份缓存(sequences是个实例变量),而且是按需创建的,为if not then set的动作                //所以对于每个该类的实例而言,它都存在一份缓存,因而锁住的是当前对象,至于多个线程中使用不同的该类的实例,                //可能产生的冲突,会在下面对数据库的值进行冲突检测                synchronized (this) {                    Transaction suspendedTransaction = null;                    try {                        //if we can suspend the transaction, we'll try to do this in a local manual transaction                        suspendedTransaction = TransactionUtil.suspend();           //挂起事务                         boolean beganTransaction = false;                        try {                            beganTransaction = TransactionUtil.begin();             //开始一个新事务                             Connection connection = null;                            Statement stmt = null;                            ResultSet rs = null;                             try {                                connection = ConnectionFactory.getConnection(SequenceUtil.this.helperInfo);     //获取一个数据库连接                            } catch (SQLException sqle) {                                Debug.logWarning("[SequenceUtil.SequenceBank.fillBank]: Unable to esablish a connection with the database... Error was:" + sqle.toString(), module);                                throw sqle;                            } catch (GenericEntityException e) {                                Debug.logWarning("[SequenceUtil.SequenceBank.fillBank]: Unable to esablish a connection with the database... Error was: " + e.toString(), module);                                throw e;                            }                             if (connection == null) {                                throw new GenericEntityException("[SequenceUtil.SequenceBank.fillBank]: Unable to esablish a connection with the database, connection was null...");                            }                             String sql = null;                             try {                                // we shouldn't need this, and some TX managers complain about it, so not including it: connection.setAutoCommit(false);                                 stmt = connection.createStatement();                                if (clustered) {                                //判断是否是集群模式,拼接sql:查询数据库现存的当前sequence值                                                                                //其中的`for update`子句为互斥锁,可以防止在select的同时被别的连接更改                                    sql = "SELECT " + SequenceUtil.this.idColName + " FROM " + SequenceUtil.this.tableName + " WHERE " + SequenceUtil.this.nameColName + "='" + this.seqName + "'" + " FOR UPDATE";                                                                    } else {                                    sql = "SELECT " + SequenceUtil.this.idColName + " FROM " + SequenceUtil.this.tableName + " WHERE " + SequenceUtil.this.nameColName + "='" + this.seqName + "'";                                }                                rs = stmt.executeQuery(sql);                                boolean gotVal1 = false;                                if (rs.next()) {                                //如果拿到当前该sequence的值,赋给val1,gotVal1为true                                    val1 = rs.getLong(SequenceUtil.this.idColName);                                    gotVal1 = true;                                }                                rs.close();                                 if (!gotVal1) {                                 //如果没有获取到,则视为没有该记录,执行插入动作,插入的初始值为上面的startSeqId                                    Debug.logWarning("[SequenceUtil.SequenceBank.fillBank] first select failed: will try to add new row, result set was empty for sequence [" + seqName + "] \nUsed SQL: " + sql + " \n Thread Name is: " + Thread.currentThread().getName() + ":" + Thread.currentThread().toString(), module);                                    sql = "INSERT INTO " + SequenceUtil.this.tableName + " (" + SequenceUtil.this.nameColName + ", " + SequenceUtil.this.idColName + ") VALUES ('" + this.seqName + "', " + startSeqId + ")";                                    if (stmt.executeUpdate(sql) = 0) {                                        throw new GenericEntityException("No rows changed when trying insert new sequence row with this SQL: " + sql);                                    }                                    continue;                                   //如果之前没有获取到,插入该记录后,跳过本次循环                                }                                 //执行更新,以达到扩充SequenceBank,更新的幅度为数据库的当前值 + bankSize                                 //注意,此处由于多线程、多数据库连接问题,数据库的当前值有可能以及不是上面的val1了                                //这个跟上面的synchronized没有关系,这是数据库层面上的问题                                sql = "UPDATE " + SequenceUtil.this.tableName + " SET " + SequenceUtil.this.idColName + "=" + SequenceUtil.this.idColName + "+" + bankSize + " WHERE " + SequenceUtil.this.nameColName + "='" + this.seqName + "'";                                if (stmt.executeUpdate(sql) = 0) {                                    throw new GenericEntityException("[SequenceUtil.SequenceBank.fillBank] update failed, no rows changes for seqName: " + seqName);                                }                                //更新完之后重新查询该值,进一步确认该值不为脏数据                                if (clustered) {                                    sql = "SELECT " + SequenceUtil.this.idColName + " FROM " + SequenceUtil.this.tableName + " WHERE " + SequenceUtil.this.nameColName + "='" + this.seqName + "'" + " FOR UPDATE";                                                                     } else {                                    sql = "SELECT " + SequenceUtil.this.idColName + " FROM " + SequenceUtil.this.tableName + " WHERE " + SequenceUtil.this.nameColName + "='" + this.seqName + "'";                                }                                rs = stmt.executeQuery(sql);                                boolean gotVal2 = false;                                if (rs.next()) {                //如果获取到扩充后的值,则赋予val2,置gotVal2为true                                    val2 = rs.getLong(SequenceUtil.this.idColName);                                    gotVal2 = true;                                }                                 rs.close();                                 if (!gotVal2) {                 //如果未获取到,则抛出异常                                    throw new GenericEntityException("[SequenceUtil.SequenceBank.fillBank] second select failed: aborting, result set was empty for sequence: " + seqName);                                }                                 // got val1 and val2 at this point, if we don't have the right difference between them, force a rollback (with                                //setRollbackOnly and NOT with an exception because we don't want to break from the loop, just err out and                                //continue), then flow out to allow the wait and loop thing to happen                                if (val1 + bankSize != val2) {      //比较上面数据库操作的正确性,如果出现不一致,则进行rollback动作                                    TransactionUtil.setRollbackOnly("Forcing transaction rollback in sequence increment because we didn't get a clean update, ie a conflict was found, so not saving the results", null);                                }                            } catch (SQLException sqle) {                                Debug.logWarning(sqle, "[SequenceUtil.SequenceBank.fillBank] SQL Exception while executing the following:\n" + sql + "\nError was:" + sqle.getMessage(), module);                                throw sqle;                            } finally {                             //资源清理                                try {                                    if (stmt != null) stmt.close();                                } catch (SQLException sqle) {                                    Debug.logWarning(sqle, "Error closing statement in sequence util", module);                                }                                try {                                    if (connection != null) connection.close();                                } catch (SQLException sqle) {                                    Debug.logWarning(sqle, "Error closing connection in sequence util", module);                                }                            }                        } catch (Exception e) {                            String errMsg = "General error in getting a sequenced ID";                            Debug.logError(e, errMsg, module);                            try {                                TransactionUtil.rollback(beganTransaction, errMsg, e);          //rollback                            } catch (GenericTransactionException gte2) {                                Debug.logError(gte2, "Unable to rollback transaction", module);                            }                             // error, break out of the loop to not try to continue forever                            //此处为break,表示认为此为严重错误,没有继续循环的必要                            break;                        } finally {                            try {                                TransactionUtil.commit(beganTransaction);                       //commit                            } catch (GenericTransactionException gte) {                                Debug.logError(gte, "Unable to commit sequence increment transaction, continuing anyway though", module);                            }                        }                    } catch (GenericTransactionException e) {                        Debug.logError(e, "System Error suspending transaction in sequence util", module);                    } finally {                        if (suspendedTransaction != null) {                            try {                                TransactionUtil.resume(suspendedTransaction);                   //恢复开始时挂起的事物                            } catch (GenericTransactionException e) {                                Debug.logError(e, "Error resuming suspended transaction in sequence util", module);                            }                        }                    }                }                 if (val1 + bankSize != val2) {                  //如果数据库产生脏数据导致扩充结果不一致                    if (numTries = maxTries) {                 //如果尝试次数大于最大限制,则推出方法,否则随机休眠,解决冲突                        String errMsg = "[SequenceUtil.SequenceBank.fillBank] maxTries (" + maxTries + ") reached for seqName [" + this.seqName + "], giving up.";                        Debug.logError(errMsg, module);                        return;                    }                     // collision happened, wait a bounded random amount of time then continue                    //当发生冲突,等待一定范围的随机时间                    long waitTime = (long) (Math.random() * (maxWaitMillis - minWaitMillis) + minWaitMillis);                     Debug.logWarning("[SequenceUtil.SequenceBank.fillBank] Collision found for seqName [" + seqName + "], val1=" + val1 + ", val2=" + val2 + ", val1+bankSize=" + (val1 + bankSize) + ", bankSize=" + bankSize + ", waitTime=" + waitTime, module);                     try {                        // using the Thread.sleep to more reliably lock this thread: this.wait(waitTime);                        java.lang.Thread.sleep(waitTime);                    } catch (Exception e) {                        Debug.logWarning(e, "Error waiting in sequence util", module);                        return;                    }                }                 numTries++;         //尝试次数+1            }             //到此,说明整个过程没有产生任何异常,序列的计数值被正确增加,val1,val2将被赋予curSeqId,maxSeqId,            //后面的getNextSeqId可以顺利进行            curSeqId = val1;            maxSeqId = val2;            if (Debug.infoOn()) Debug.logInfo("Got bank of sequenced IDs for [" + this.seqName + "]; curSeqId=" + curSeqId + ", maxSeqId=" + maxSeqId + ", bankSize=" + bankSize, module);            //Debug.logWarning("[SequenceUtil.SequenceBank.fillBank] Ending fillBank Thread Name is: " + Thread.currentThread().getName() + ":" + Thread.currentThread().toString(), module);        }    }}

0 0
原创粉丝点击