OFBiz entity engine 关于数据库自增序列生成算法的源码解读
来源:互联网 发布:java math.ceil 编辑:程序博客网 时间:2024/06/06 03:10
/******************************************************************************* * 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); } }}
0 0
- OFBiz entity engine 关于数据库自增序列生成算法的源码解读
- Apache OFbiz entity engine源码解读
- Apache OFbiz entity engine源码解读
- Apache OFbiz entity engine源码解读
- Apache OFbiz service engine 源码解读
- Apache OFbiz service engine 源码解读
- Apache OFbiz service engine 源码解读
- OFBiz entity engine中的设计模式总结
- OFBiz entity engine中的设计模式总结
- OFBiz entity engine中的设计模式总结
- ofbiz 下的entity
- OFBiz 数据库自增SequenceUtil.java
- Apache OFbiz MiniLang 源码解读
- Apache OFbiz MiniLang 源码解读
- 关于Entity Framework中自增主键的问题
- ofbiz entity
- oracle数据库自增序列的创建
- Apache OFBiz源码解读之MVC模型
- NYOJ 网络的可靠性(换个思路想超简单的一道题)
- android源码使用U盘激活Launcher
- android OTA 升级包含增量升级
- android打包混淆-proguard配置详解
- android ueventd 本地native部分源码分析
- OFBiz entity engine 关于数据库自增序列生成算法的源码解读
- float (**def)[10]这个是什么?
- Android中通过xml资源文件定义数组
- 黑马程序员-点语法,成员变量作用域,@property,@synthsize和 id
- 两个Fragment之间的跳转,和数据的传递
- C++习题 矩阵求和--重载运算符
- 太阳能热水器前十强
- android ormlite
- 纹理坐标