/*
* JEF - Copyright 2009-2010 Jiyi (mr.jiyi@gmail.com)
*
* Licensed 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 jef.database;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import javax.persistence.PersistenceException;
import jef.common.log.LogUtil;
import jef.database.DbMetaData.ObjectType;
import jef.database.dialect.DatabaseDialect;
import jef.database.meta.AbstractSequence;
import jef.database.meta.DbProperty;
import jef.database.meta.SequenceInfo;
import jef.tools.JefConfiguration;
import jef.tools.StringUtils;
/**
* 默认的数据库原生Sequence实现
* <p>
* 注:当某{@code SequenceKeyHolder}实例创建后,与该实例相关的sequence被删掉,则该sequence不会再被自动创建。
* </p>
*
*/
final class SequenceNativeImpl extends AbstractSequence {
/**
* Sequence Schema
*/
private String schema;
/**
* Sequence Name
*/
private String sequence;
/**
* Sequence Step
*/
private int step = 1;
/**
* 描述Sequence是否存在
*/
private boolean exists;
/**
*/
private String selectSql;
private int initValue;
private int length;
private String table;
private String column;
/*
* @param rawSeqName 名称
*
* @param cacheSize 缓存大小
*
* @param client 获取数据库
*
* @param columnSize 列大小(当seq自动创建时使用)
*
* @param table 表名 (可为null) 用于校准初始值
*
* @param columne 列名 (可为null) 用于校准初始值
*
* @throws SQLException
*/
SequenceNativeImpl(String name, OperateTarget target, int length, String table, String column, int initValue,SequenceManager parent) throws SQLException {
super(target,parent);
initName(name);
this.length = length;
this.table = table;
this.column = column;
this.initValue = initValue;
if (target != null) {
tryInit();
}
}
@Override
protected boolean doInit(DbClient session, String dbKey) throws SQLException {
DbMetaData meta=session.getMetaData(dbKey);
ensureExists(meta, table, column, length, initValue);
this.selectSql = generateSQL(meta.getProfile());
if (exists) {
OperateTarget target=session.selectTarget(dbKey);
this.step = calcStep(target, selectSql);
int cacheSize = getCacheSize();
if (cacheSize > 1)
this.setCacheSize(cacheSize / step);// 根据步长做除法
}
return true;
}
private String generateSQL(DatabaseDialect dialect) {
String sql;
if (schema == null) {
sql = sequence;
} else {
sql = schema + "." + sequence;
}
String template = dialect.getProperty(DbProperty.SEQUENCE_FETCH);
if (template != null) {
return String.format(template, sql);
}
sql = sql + ".nextval";
template = dialect.getProperty(DbProperty.SELECT_EXPRESSION);
if (template == null) {
return "SELECT " + sql;
} else {
return String.format(template, sql);
}
}
private void ensureExists(DbMetaData meta, String table, String column, int length, int initValue) throws SQLException {
// 检测Sequence
if (meta.existsInSchema(ObjectType.SEQUENCE, schema, sequence)) {
exists = true;
return;
}
// can be created automatically/
if (!ORMConfig.getInstance().isAutoCreateSequence()) {
throw new PersistenceException("Sequence " + schema + "." + sequence + " does not exist on " + meta + "!");
}
long max = StringUtils.toLong(StringUtils.repeat('9', length), Long.MAX_VALUE);
long start = caclStartValue(meta, schema, table, column, initValue, max);
try {
meta.createSequenceWithoutCheck(schema, sequence, start+1, max);
exists = true;
} catch (SQLException e) {
LogUtil.error("Sequence [{}.{}] create error on database {}", schema, sequence, meta);
throw e;
}
}
private void initName(String name) {
int index = name.indexOf('.');
if (index > 0) {
this.schema = name.substring(0, index);
this.sequence = name.substring(index + 1);
} else {
this.sequence = name;
}
}
/*
* 初始化Sequence步长
*/
private int calcStep(OperateTarget client, String selectSql) {
int step = JefConfiguration.getInt(DbCfg.DB_SEQUENCE_STEP, 0);
//强制设定步长时,直接return
if (step > 0) {
return step;
}
//尝试从数据库获取
List<SequenceInfo> info=client.getProfile().getSequenceInfo(client.getMetaData(), this.schema, this.sequence);
if(info!=null) {
if(info.isEmpty() || info.size()>1) {
throw new IllegalArgumentException("The sequence "+sequence+" was not correct in system_tables. size="+info.size());
}else if(info.get(0).getStep()>0){
return info.get(0).getStep();
}
}
//无论数据库是否支持,强制计算步长
if (step == -1) {
try {
long[] min_max = getSequenceStepByConsumeTwice(client, selectSql);
step = (int) (min_max[1] - min_max[0]);
pushRange(min_max[0], min_max[1]);// 加入缓存,避免浪费序列号
} catch (SQLException e) {
throw new PersistenceException(e);
}
}
//修复数据
if (step < 1)
step = 1;// 不允许出现0或者负数
return step;
}
/**
* 消耗两次Sequence值,得到两次消耗的Sequence值
*
* @param schema
* @param sequence
* @return
* @throws SQLException
*/
long[] getSequenceStepByConsumeTwice(OperateTarget client, String sql) throws SQLException {
PreparedStatement st = null;
ResultSet rs = null;
try {
st = client.prepareStatement(sql);
st.setMaxRows(1);
rs = st.executeQuery();
long min = 0;
if (!rs.next()) {
throw new SQLException("The select expression not return any result.");
}
min = rs.getLong(1);
DbUtils.close(rs);
rs = st.executeQuery();
if (!rs.next()) {
throw new SQLException("The select expression not return any result.");
}
return new long[] { min, rs.getLong(1) };
} finally {
DbUtils.close(rs);
DbUtils.close(st);
client.releaseConnection();
}
}
protected long getFirstAndPushOthers(int size, DbClient conn, String dbKey) throws SQLException {
// 开始
long start = System.currentTimeMillis();
OperateTarget target = (OperateTarget) conn.getSqlTemplate(dbKey);
PreparedStatement ps = null;
long result = 0;
try {
ps = target.prepareStatement(selectSql);
ps.setMaxRows(1);
long value = queryOnce(ps);
result = value; // 直接将选出的值作为sequence
// 向后取值(比如value=10, step=5, 那么实际有效的是10,11,12,13,14)
pushRange(value + 1, value + step - 1); // 从第二个值开始推送到缓存
for (int i = 1; i < size; i++) {// 获取多次
value = queryOnce(ps);
pushRange(value, value + step - 1);
}
} catch (SQLException e) {
DebugUtil.setSqlState(e, selectSql);
throw e;
} finally {
DbUtils.close(ps);
target.releaseConnection();
}
if (ORMConfig.getInstance().isDebugMode()) {
LogUtil.info(StringUtils.concat(selectSql, " (fetch size=", String.valueOf(size), ")\t[Cost:", String.valueOf(System.currentTimeMillis() - start), "ms]|", target.getTransactionId()));
}
return result;
}
private long queryOnce(PreparedStatement ps) throws SQLException {
ResultSet rs = ps.executeQuery();
try {
rs.next();
return rs.getLong(1);
} finally {
rs.close();
}
}
@Override
public String toString() {
return this.schema + "." + this.sequence;
}
public boolean isTable() {
return false;
}
public String getName() {
if (schema == null)
return sequence;
return schema + "." + sequence;
}
public boolean isRawNative() {
return step==1;
}
}