package jef.database.dialect.type; import java.sql.SQLException; import java.sql.Types; import java.util.List; import javax.persistence.GenerationType; import javax.persistence.SequenceGenerator; import javax.persistence.TableGenerator; import jef.common.Entry; import jef.database.DbMetaData; import jef.database.DbUtils; import jef.database.Field; import jef.database.IQueryableEntity; import jef.database.ORMConfig; import jef.database.OperateTarget; import jef.database.Sequence; import jef.database.annotation.PartitionFunction; import jef.database.annotation.PartitionKey; import jef.database.dialect.ColumnType; import jef.database.dialect.ColumnType.AutoIncrement; import jef.database.dialect.DatabaseDialect; import jef.database.meta.Column; import jef.database.meta.Feature; import jef.database.meta.ITableMetadata; import jef.database.meta.MetaHolder; import jef.database.wrapper.clause.InsertSqlClause; import jef.database.wrapper.processor.InsertStep; import jef.database.wrapper.processor.InsertStep.JdbcAutoGeneratedKeyCallback; import jef.database.wrapper.processor.InsertStep.SequenceGenerateCallback; import jef.tools.StringUtils; import jef.tools.reflect.Property; /** * 自增的映射实现 * * @author jiyi * * @param <T> */ public abstract class AutoIncrementMapping extends AColumnMapping { protected Property accessor; private int len; private boolean isBig; // 缓存的计算结果 private transient GenerationResolution generationType; private transient String[] sequenceName; private transient InsertStep autoGenerateCall; public enum GenerationResolution { TABLE, SEQUENCE, IDENTITY_SKIP, IDENTITY_DEFAULT, CHECK_IS_IDENTITY } @Override public void init(Field field, String columnName, ColumnType type, ITableMetadata meta) { super.init(field, columnName, type, meta); len = ((AutoIncrement) type).getPrecision(); this.isBig = len > 10; } /** * 返回Sequence所在的数据源的名称(重定向已计算) * * @return <li>null表示缺省数据库(表在哪里,Sequence就在哪里。)</li> <li>""表示使用默认数据源。</li> */ public String getSequenceDataSource(DatabaseDialect profile) { if (bindedProfile != profile || sequenceName == null) { String name = profile.getColumnNameToUse(this); rebind(DbUtils.escapeColumn(profile, name), profile); } return sequenceName[0]; } @Override public boolean isGenerated() { return true; } /** * 返回Sequence的名称 * * @return */ public String getSequenceName(DatabaseDialect profile) { if (bindedProfile != profile || sequenceName == null) { String name = profile.getColumnNameToUse(this); rebind(DbUtils.escapeColumn(profile, name), profile); } return sequenceName[1]; } /* * 计算生成策略 */ @Override protected void rebind(String escapedColumn, DatabaseDialect profile) { super.rebind(escapedColumn, profile); AutoIncrement a = (AutoIncrement) columnDef; GenerationType type = a.getGenerationType(profile, this.meta.getEffectPartitionKeys() == null);// 只有非分表的类允许使用Identity方式生成,其他都仅允许Seq或Tble this.generationType = getResolution(type, profile); sequenceName = getSequenceName0(meta.getSchema(), meta.getTableName(false), type); autoGenerateCall = new JdbcAutoGeneratedKeyCallback(accessor, getColumnName(profile, false), profile); } private GenerationResolution getResolution(GenerationType type, DatabaseDialect profile) { if (type == GenerationType.IDENTITY) { if (profile.has(Feature.AI_TO_SEQUENCE_WITHOUT_DEFAULT)) { return GenerationResolution.CHECK_IS_IDENTITY; } if (profile.has(Feature.NOT_SUPPORT_KEYWORD_DEFAULT)) { return GenerationResolution.IDENTITY_SKIP; } return GenerationResolution.IDENTITY_DEFAULT; } else if (type == GenerationType.SEQUENCE) { return GenerationResolution.SEQUENCE; } else if (type == GenerationType.TABLE) { return GenerationResolution.TABLE; } throw new UnsupportedOperationException(type.name()); } /** * * @param profile * @return */ public GenerationResolution getGenerationType(DatabaseDialect profile) { if (profile != bindedProfile || sequenceName == null) { String name = profile.getColumnNameToUse(this); rebind(DbUtils.escapeColumn(profile, name), profile); } return generationType; } private String[] getSequenceName0(String schema, String tableName, GenerationType gtype) { AutoIncrement type = (AutoIncrement) columnDef; SequenceGenerator sg = type.getSeqGenerator(); // 多数据源下,数据源必须计算得到,不能用null表示 boolean isMultiDatasource = isTableOnMultipleDataSources(); if (gtype != GenerationType.TABLE) { if (sg != null && StringUtils.isNotEmpty(sg.sequenceName())) { if (StringUtils.isEmpty(schema)) { schema = sg.schema(); } String datasource = getDataSource(sg, isMultiDatasource); if (StringUtils.isNotEmpty(schema)) { schema = MetaHolder.getMappingSchema(schema); return new String[] { datasource, schema + "." + sg.sequenceName() }; } else { return new String[] { datasource, sg.sequenceName() }; } } } if (gtype != GenerationType.SEQUENCE) { TableGenerator tg = type.getTableGenerator(); if (tg != null && StringUtils.isNotEmpty(tg.table())) { if (StringUtils.isEmpty(schema)) { schema = tg.schema(); } String datasource = getDataSource(tg, isMultiDatasource); if (StringUtils.isNotEmpty(schema)) { schema = MetaHolder.getMappingSchema(schema); return new String[] { datasource, schema + "." + tg.table() }; } else { return new String[] { datasource, tg.table() }; } } } // 即便不使用Seq,sequenceName也必须有值,否则会反复计算 return new String[] { isMultiDatasource ? "" : null, DbUtils.calcSeqNameByTable(schema, tableName, this.rawColumnName) }; } private boolean isTableOnMultipleDataSources() { boolean multiDb = false; if (meta.getEffectPartitionKeys() != null) { for (@SuppressWarnings("rawtypes") Entry<PartitionKey, PartitionFunction> pk : meta.getEffectPartitionKeys()) { if (pk.getKey().isDbName()) { multiDb = true; break; } } } return multiDb && !ORMConfig.getInstance().isSingleSite(); } /** * Use the field catalog to specify the name of 'datasource'. (though I know * the field doesn't mean this in JPA) * * @param tg * @param partition * @return if single site, return null. or return the name of datasource for * multiple site. */ private String getDataSource(TableGenerator tg, boolean partition) { if (!partition) return null; String datasource = ""; if (tg != null) { datasource = MetaHolder.getMappingSite(tg.catalog()); } return datasource == null ? "" : datasource; } /** * Use the field catalog to specify the name of 'datasource'. (though I know * the field doesn't mean this in JPA) * * @param sg * @param partition * @return if single site, return null. or return the name of datasource for * multiple site. */ private String getDataSource(SequenceGenerator sg, boolean partition) { if (!partition) return null; String datasource = ""; if (sg != null) { datasource = MetaHolder.getMappingSite(sg.catalog()); } return datasource == null ? "" : datasource; } public void processInsert(Object value, InsertSqlClause result, List<String> cStr, List<String> vStr, boolean smart, IQueryableEntity obj) throws SQLException { DatabaseDialect profile = result.profile; Field field = this.field; // 手动指定 if (isAssignedSequence(value) && ORMConfig.getInstance().isManualSequence() && obj.isUsed(field)) { cStr.add(rawColumnName); vStr.add(value.toString()); return; } // 核对和刷新生成策略,后续操作对象许多都是从当前对象缓存结果中获取的。所以先刷新一下 GenerationResolution type = getGenerationType(profile); switch (type) { case CHECK_IS_IDENTITY: boolean hasDefaultValue=checkMetadata(result); if(hasDefaultValue){ this.generationType=GenerationResolution.IDENTITY_DEFAULT; }else{ this.generationType=GenerationResolution.SEQUENCE; } processInsert(value,result,cStr,vStr,smart,obj); break; case IDENTITY_DEFAULT: cStr.add(cachedEscapeColumnName); vStr.add("DEFAULT"); // 注意此处不加break(无误); case IDENTITY_SKIP: result.getCallback().addProcessor(autoGenerateCall); break; case SEQUENCE: case TABLE: default: String dbKey = result.getTable() == null ? null : result.getTable().getDatabase(); OperateTarget db = new OperateTarget(result.parent, dbKey); Sequence seq = db.getSequence(this); result.getCallback().addProcessor(autoGenerateCall); cStr.add(cachedEscapeColumnName); vStr.add(seq.getName() + ".nextval");//FIXME if the resolution is TABLE... break; } } private boolean checkMetadata(InsertSqlClause result) throws SQLException { String dbKey = result.getTable() == null ? null : result.getTable().getDatabase(); DbMetaData meta=result.parent.getNoTransactionSession().getMetaData(dbKey); Column column=meta.getColumn(this.meta.getTableName(true), this.rawColumnName); String val=column.getColumnDef(); return val!=null && val.contains("nextval"); } @Override public void processPreparedInsert(IQueryableEntity obj, List<String> cStr, List<String> vStr, InsertSqlClause result, boolean smart) throws SQLException { DatabaseDialect profile = result.profile; Field field = this.field; // 手动指定 if (obj.isUsed(field) && ORMConfig.getInstance().isManualSequence() && isAssignedSequence(accessor.get(obj))) { cStr.add(cachedEscapeColumnName); vStr.add("?"); result.addField(this); return; } // 核对和刷新生成策略,后续操作对象许多都是从当前对象缓存结果中获取的。所以先刷新一下 GenerationResolution gType = getGenerationType(profile); // 是否需要返回自增值 boolean returnKeys = !result.isExtreme(); switch (gType) { case CHECK_IS_IDENTITY: boolean hasDefaultValue=checkMetadata(result); if(hasDefaultValue){ this.generationType=GenerationResolution.IDENTITY_DEFAULT; }else{ this.generationType=GenerationResolution.SEQUENCE; } processPreparedInsert(obj,cStr,vStr,result,smart); break; case IDENTITY_DEFAULT: cStr.add(cachedEscapeColumnName); vStr.add("DEFAULT"); // 注意此处不加break(无误); case IDENTITY_SKIP: if (returnKeys) { result.getCallback().addProcessor(autoGenerateCall); } break; case SEQUENCE: case TABLE: default: String dbKey = result.getTable() == null ? null : result.getTable().getDatabase(); OperateTarget db = new OperateTarget(result.parent, dbKey); Sequence sh = db.getSequence(this); if (!returnKeys && sh.isRawNative()) {// 可以用简略方式操作 cStr.add(cachedEscapeColumnName); vStr.add(sh.getName() + ".nextval"); } else { result.getCallback().addProcessor(new SequenceGenerateCallback(accessor, sh)); cStr.add(cachedEscapeColumnName); vStr.add("?"); result.addField(this); } break; } } /* * 判断是否已经指定了自增序号的值, 如果用户已经赋了有效的值,那么就无需再自动生成 */ private boolean isAssignedSequence(Object value) { if (value instanceof Number) { return ((Number) value).longValue() > 0; } else { return false; } } public Property getAccessor() { return accessor; } public int getSqlType() { return isBig ? Types.BIGINT : Types.INTEGER; } }