/*******************************************************************************
* 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);
}
}
}
OFBiz entity engine 关于数据库自增序列生成算法的源码解读
最新推荐文章于 2021-06-09 10:35:15 发布