OFBiz entity engine 关于数据库自增序列生成算法的源码解读

/*******************************************************************************
 * 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 Map<String, SequenceBank> sequences = new Hashtable<String, 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);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值