package jef.database; import java.sql.SQLException; import java.util.Collections; import java.util.HashMap; import javax.persistence.GenerationType; import javax.persistence.PersistenceException; import javax.persistence.SequenceGenerator; import javax.persistence.TableGenerator; import jef.database.DbMetaData.ObjectType; import jef.database.annotation.HiloGeneration; import jef.database.dialect.ColumnType; import jef.database.dialect.ColumnType.AutoIncrement; import jef.database.dialect.DatabaseDialect; import jef.database.dialect.type.AutoIncrementMapping; import jef.database.dialect.type.AutoIncrementMapping.GenerationResolution; import jef.database.jdbc.result.IResultSet; import jef.database.meta.AbstractSequence; import jef.database.meta.Feature; import jef.database.meta.TupleMetadata; import jef.database.wrapper.populator.AbstractResultSetTransformer; import jef.database.wrapper.populator.ResultSetExtractor; import jef.tools.Assert; import jef.tools.JefConfiguration; import jef.tools.StringUtils; /** * Sequence管理接口。 * * @author jiyi * */ public final class SequenceManager { private final HashMap<String, Sequence> holders = new HashMap<String, Sequence>(); private boolean hiloFlag = JefConfiguration.getBoolean(DbCfg.DB_AUTOINCREMENT_HILO, false); private DbClient parent; /** * 建sequence时对表名长度的限制,因为默认的seq名是:表名_SEQ 数据库对sequence名字长度的限制是30 */ protected SequenceManager(DbClient parent) { this.parent = parent; } /** * 获取Sequence,无论什么数据库都可以获取Sequence。如果是原生支持Sequence的数据库,会返回原生的实现;如果非原生支持, * 会返回用数据表的模拟实现。 * * @param seqName * @param client * @return * @throws SQLException */ public Sequence getSequence(AutoIncrementMapping fieldDef, OperateTarget client) throws SQLException { if (fieldDef == null) return null; String name = fieldDef.getSequenceName(client.getProfile()); Sequence s = holders.get(name); if (s == null) { synchronized (holders) { s = holders.get(name); if (s == null) {// 双重检查锁定: 防止被多线程的情况下初始化多次 DatabaseDialect profile = client.getProfile(); AutoIncrement a = (AutoIncrement) fieldDef.get(); GenerationType type = a.getGenerationType(profile, false); // 绑定DataSource String datasource = fieldDef.getSequenceDataSource(profile); if (datasource != null) {// 必须绑定DataSource client = (OperateTarget) client.getSession().getSqlTemplate(StringUtils.trimToNull(datasource)); } String columnName = fieldDef.getColumnName(profile, true); if (type == GenerationType.SEQUENCE) { s = createSequence(name, client, a.getPrecision(), fieldDef.getMeta().getTableName(true), columnName, a.getSeqGenerator()); } else if (type == GenerationType.TABLE) { s = createTable(name, client, a.getPrecision(), fieldDef.getMeta().getTableName(true), columnName, a.getTableGenerator()); } holders.put(name, wrapForHilo((AbstractSequence) s, a.getHiloConfig())); } } } return s; } /** * 获取Sequence,无论什么数据库都可以获取Sequence。如果是原生支持Sequence的数据库,会返回原生的实现;如果非原生支持, * 会返回用数据表的模拟实现。 * * @param fieldDef * @param dbKey * @return * @throws SQLException */ public Sequence getSequence(AutoIncrementMapping fieldDef, String dbKey) throws SQLException { return getSequence(fieldDef, parent.selectTarget(dbKey)); } /** * 获取Sequence,无论什么数据库都可以获取Sequence。如果是原生支持Sequence的数据库,会返回原生的实现;如果非原生支持, * 会返回用数据表的模拟实现。 * * @param seqName * @param client * @param length * @return * @throws SQLException */ public Sequence getSequence(String seqName, OperateTarget client, int length) throws SQLException { Sequence s = holders.get(seqName); if (s == null) { synchronized (holders) { s = holders.get(seqName); if (s == null) {// 双重检查锁定: 防止被多线程的情况下初始化多次 if (client == null) { client = this.parent.selectTarget(null); } if (client.getProfile().has(Feature.SUPPORT_SEQUENCE)) { s = createSequence(seqName, client, length, null, null, null); } else { s = createTable(seqName, client, length, null, null, null); } holders.put(seqName, s); } } } return s; } /** * 删除Sequence * * @param mapping * @param meta * @throws SQLException */ public void dropSequence(AutoIncrementMapping mapping, OperateTarget meta) throws SQLException { DatabaseDialect profile = meta.getProfile(); GenerationResolution type = mapping.getGenerationType(profile); String datasource = mapping.getSequenceDataSource(profile); if (datasource != null) {// 必须绑定DataSource meta = (OperateTarget) meta.getSession().getSqlTemplate(StringUtils.trimToNull(datasource)); } String name = mapping.getSequenceName(profile); if (type == GenerationResolution.SEQUENCE) { meta.getMetaData().dropSequence(name); } else if (type == GenerationResolution.TABLE) { String pname = JefConfiguration.get(DbCfg.DB_GLOBAL_SEQUENCE_TABLE); if (StringUtils.isEmpty(pname)) { meta.getMetaData().dropTable(name); } else { removeRecordInSeqTable(pname.trim(), name, meta); } } } /** * must clean cache on junit 4 tests.... */ public void clearHolders() { for (Sequence s : holders.values()) { s.clear(); } holders.clear(); } private static boolean removeRecordInSeqTable(String table, String key, OperateTarget sqlTemplate) throws SQLException { if (StringUtils.isEmpty(table) || StringUtils.isEmpty(key)) { throw new IllegalArgumentException(); } table = sqlTemplate.getMetaData().getExists(ObjectType.TABLE, table); if (table != null) { String sql = "delete from " + table + " where T=?"; int i = sqlTemplate.executeSql(sql, key); return i > 0; } return false; } private Sequence wrapForHilo(AbstractSequence s, HiloGeneration hilo) { if (hilo != null && hilo.maxLo() > 1) { if (hiloFlag || hilo.always()) { if (s instanceof AbstractSequence) {// 减少Cache大小 AbstractSequence as = (AbstractSequence) s; as.setCacheSize(as.getCacheSize() / hilo.maxLo()); } s = new SequenceHiloGenerator(s, hilo.maxLo()); } } return s; } private Sequence createTable(String name, OperateTarget client, int length, String tableName, String columnName, TableGenerator config) { String pname = JefConfiguration.get(DbCfg.DB_GLOBAL_SEQUENCE_TABLE); if (StringUtils.isEmpty(pname)) { return new SeqTableImpl(client, name, config, tableName, columnName, this); } else { return new AdvSeqTableImpl(client, name, config, tableName, columnName, this); } } private Sequence createSequence(String seqName, OperateTarget client, int columnSize, String tableName, String columnName, SequenceGenerator config) throws SQLException { int initValue = 1; if (config != null) { seqName = config.sequenceName(); initValue = config.initialValue(); if (StringUtils.isNotEmpty(config.schema())) { seqName = config.schema().trim() + "." + seqName; } } return new SequenceNativeImpl(seqName, client, columnSize, tableName, columnName, initValue, this); } /** * 第一种SQL实现,每个Sequence一张表 */ private static final class SeqTableImpl extends AbstractSequence { static TupleMetadata seqtable; static { seqtable = new TupleMetadata("SEQ"); seqtable.addColumn("V", new ColumnType.Int(12)); } private int valueStep; private String table; private String rawTable; private String rawColumn; private int initValue; private long last = -1; private String update; private String select; /* * @param rawSeqName Sequence表名称 * * @param tableName 表名 * * @param columnName 列名 * * @throws SQLException */ SeqTableImpl(OperateTarget target, String seqTable, TableGenerator config, String rawTableName, String rawColumnName, SequenceManager parent) { super(target, parent); Assert.notNull(target); this.table = seqTable; this.rawTable = rawTableName; this.rawColumn = rawColumnName; if (config != null) this.initValue = config.initialValue(); String valueColumn = config == null ? "V" : config.valueColumnName(); this.update = "UPDATE " + table + " SET " + valueColumn + "=? WHERE " + valueColumn + "=?"; this.select = "SELECT " + valueColumn + " FROM " + table; this.valueStep = JefConfiguration.getInt(DbCfg.SEQUENCE_BATCH_SIZE, 20);// 每次取一批 if (valueStep < 1) valueStep = config == null ? 20 : config.allocationSize(); if (target != null) { tryInit(); } } @Override protected long getFirstAndPushOthers(int num, DbClient conn, String dbKey) throws SQLException { DbMetaData meta = conn.getMetaData(dbKey); if (last < 0) { last = queryLast(meta); } long nextVal = last + valueStep; int updated = conn.executeSql(update, nextVal, last); while (updated == 0) { // 基于CAS操作的乐观锁, last = queryLast(meta); nextVal = last + valueStep; updated = conn.executeSql(update, nextVal, last); } long result = last + 1; super.pushRange(last + 2, nextVal); last = nextVal; return result; } private long queryLast(DbMetaData conn) throws SQLException { long value = conn.selectBySql(select, GET_LONG_OR_TABLE_NOT_EXIST, 1, Collections.EMPTY_LIST); if (value == -9999L) { long start = super.caclStartValue(conn, null, rawTable, rawColumn, initValue, 99999999999L); conn.executeSql("INSERT INTO " + table + " VALUES(?)", start); value = 0; } return value; } public boolean isTable() { return true; } public String getName() { return table; } @Override protected boolean doInit(DbClient session, String dbKey) throws SQLException { DbMetaData meta = session.getMetaData(dbKey); String tableExists = meta.getExists(ObjectType.TABLE, table); if (tableExists == null) { if (ORMConfig.getInstance().isAutoCreateSequence()) { int start = 0; meta.createTable(seqtable, table); meta.executeSql("INSERT INTO " + this.table + " VALUES(?)", start); } else { throw new PersistenceException("Table for sequence " + table + " does not exist on " + meta + "!"); } }else{ this.table=tableExists; } return true; } public boolean isRawNative() { return false; } } /** * 第二种SQL实现,所有Sequence公用一张表 */ private static final class AdvSeqTableImpl extends AbstractSequence { static String publicTableName = StringUtils.trimToNull(JefConfiguration.get(DbCfg.DB_GLOBAL_SEQUENCE_TABLE, "JEF_SEQUENCES")); static TupleMetadata seqtable; static { seqtable = new TupleMetadata(publicTableName); seqtable.addColumn("V", new ColumnType.Int(12)); seqtable.addColumn("T", "T", new ColumnType.Varchar(64), true); } private String table; private String key; private int valueStep; private String rawTable; private String rawColumn; private int initValue; private String update; private String select; private long last = -1; /* * @param key Sequence名称 * * @param tableName 表名 */ AdvSeqTableImpl(OperateTarget target, String key, TableGenerator config, String rawTable, String rawColumn, SequenceManager parent) { super(target, parent); Assert.notNull(target); this.table = publicTableName; this.key = key; this.rawTable = rawTable; this.rawColumn = rawColumn; this.initValue = config == null ? 0 : config.initialValue(); this.valueStep = JefConfiguration.getInt(DbCfg.SEQUENCE_BATCH_SIZE, 20); if (valueStep < 1) valueStep = 20; this.update = "UPDATE " + table + " SET V=? WHERE V=? AND T='" + key + "'"; this.select = "SELECT V FROM " + table + " WHERE T='" + key + "'"; if (target != null) { tryInit(); } } @Override protected long getFirstAndPushOthers(int num, DbClient conn, String dbKey) throws SQLException { DbMetaData meta = conn.getNoTransactionSession().getMetaData(dbKey); if (last < 0) { last = queryLast(meta); } long nextVal = last + valueStep; int updated = conn.executeSql(update, nextVal, last); while (updated == 0) { // 基于CAS操作的乐观锁, last = queryLast(meta); nextVal = last + valueStep; updated = conn.executeSql(update, nextVal, last); } long result = last + 1; super.pushRange(last + 2, nextVal); last = nextVal; return result; } private long queryLast(DbMetaData conn) throws SQLException { long value = conn.selectBySql(select, GET_LONG_OR_TABLE_NOT_EXIST, 1, Collections.EMPTY_LIST); if (value == -9999L) { long start = super.caclStartValue(conn, null, rawTable, rawColumn, initValue, 99999999999L); conn.executeSql("INSERT INTO " + table + "(V,T) VALUES(?,?)", start, key); value = 0; } return value; } public boolean isTable() { return true; } public String getName() { return key; } @Override protected boolean doInit(DbClient session, String dbKey) throws SQLException { DbMetaData meta = session.getMetaData(dbKey); String exists=meta.getExists(ObjectType.TABLE, this.table); if (exists==null) { if (ORMConfig.getInstance().isAutoCreateSequence()) { meta.createTable(seqtable, table); } else { throw new PersistenceException("Table for sequence " + table + " does not exist on " + meta + "!"); } }else{ this.table=exists; } return true; } public boolean isRawNative() { return false; } } /** * 从结果中获得单个LONG值 */ private static final ResultSetExtractor<Long> GET_LONG_OR_TABLE_NOT_EXIST = new AbstractResultSetTransformer<Long>() { public Long transformer(IResultSet rs) throws SQLException { if (rs.next()) { return rs.getLong(1); } else { return -9999L; } } }; public void close() { this.clearHolders(); } }