多说 GenerationType.AUTO 适用于多个数据库,
为什么我今天玩 sqlserver 可以创建主键, 但是主键却没有 IDENTITY标识呢?
难道是老人说的是错的? 难道教科书上写得不对? 事出反常必有妖,我偏偏不信这个邪
1.现象
现在这么一个简单的JPA类
@Entity @Table(name = "T_SYS_CHOOSE_OPTION") public class ChooseOption extends BaseModel implements Command{ private Long id; @Id @Column(name = "ID") @GeneratedValue(strategy = GenerationType.AUTO) public Long getId(){ return id; } public void setId(Long id){ this.id = id; } }
但是启动程序, 生成的表,id列却没有 IDENTITY 标识
2.调试
将 logback 相关类日志调整成 debug
<logger name="org.hibernate.tool.hbm2ddl.SchemaUpdate" level="DEBUG" />
对比下 GenerationType.AUTO 与 GenerationType.IDENTITY 生成的SQL语句
--GenerationType.IDENTITY
create table T_SYS_CHOOSE_OPTION (ID numeric(19,0) identity not null)
--GenerationType.AUTO
create table T_SYS_CHOOSE_OPTION (ID numeric(19,0) not null)
很清晰的发现,生成的SQL不同点
3.分析问题
上面的想象,搜索了 google ,stackoverflow 等网站, 有用的文章不是很多
那我只有自己动手了, 拿出绝招, 那就是 源代码跟踪
最终定位到 org.hibernate.mapping.Table.sqlCreateString(Dialect, Mapping, String, String) 方法
核心代码片段:
public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) { StringBuffer buf = new StringBuffer( hasPrimaryKey() ? dialect.getCreateTableString() : dialect.getCreateMultisetTableString() ) .append( ' ' ) .append( getQualifiedName( dialect, defaultCatalog, defaultSchema ) ) .append( " (" ); boolean identityColumn = idValue != null && idValue.isIdentityColumn( p.getIdentifierGeneratorFactory(), dialect ); // Try to find out the name of the primary key to create it as identity if the IdentityGenerator is used String pkname = null; if ( hasPrimaryKey() && identityColumn ) { pkname = ( (Column) getPrimaryKey().getColumnIterator().next() ).getQuotedName( dialect ); } Iterator iter = getColumnIterator(); while ( iter.hasNext() ) { Column col = (Column) iter.next(); buf.append( col.getQuotedName( dialect ) ) .append( ' ' ); if ( identityColumn && col.getQuotedName( dialect ).equals( pkname ) ) { // to support dialects that have their own identity data type if ( dialect.hasDataTypeInIdentityColumn() ) { buf.append( col.getSqlType( dialect, p ) ); } buf.append( ' ' ) .append( dialect.getIdentityColumnString( col.getSqlTypeCode( p ) ) ); } else { .....
生成 Identity 的关键语句是
buf.append( ' ' ) .append( dialect.getIdentityColumnString( col.getSqlTypeCode( p ) ) );
我这里 dialect 配置的是
hibernate.dialect=org.hibernate.dialect.SQLServerDialect
该方言 getIdentityColumnString 方法的实现 如下:
可以看出 GenerationType.IDENTITY 的实现原理是拼接了 identity not null
那为什么 GenerationType.AUTO 就没有拼接呢? 难道是我的人品不好?
别急,还有个关键代码 是 上面的 if 流程判断
if ( identityColumn && col.getQuotedName( dialect ).equals( pkname ) ) {
在于
boolean identityColumn = idValue != null && idValue.isIdentityColumn( p.getIdentifierGeneratorFactory(), dialect );
而我当 GenerationType.AUTO , 调试到这个地方的时候, identityColumn =false
进一步跟踪 发现
org.hibernate.mapping.SimpleValue.isIdentityColumn(IdentifierGeneratorFactory, Dialect)
public boolean isIdentityColumn(IdentifierGeneratorFactory identifierGeneratorFactory, Dialect dialect) { identifierGeneratorFactory.setDialect( dialect ); return identifierGeneratorFactory.getIdentifierGeneratorClass( identifierGeneratorStrategy ) .equals( IdentityGenerator.class ); }
其中的 identifierGeneratorStrategy 值为 org.hibernate.id.enhanced.SequenceStyleGenerator
导致 下面的代码,并没有使用 native 去取到 dialect.getNativeIdentifierGeneratorClass()
/** * {@inheritDoc} */ public Class getIdentifierGeneratorClass(String strategy) { if ( "native".equals( strategy ) ) { return dialect.getNativeIdentifierGeneratorClass(); } Class generatorClass = ( Class ) generatorStrategyToClassNameMap.get( strategy ); try { if ( generatorClass == null ) { generatorClass = ReflectHelper.classForName( strategy ); } } catch ( ClassNotFoundException e ) { throw new MappingException( "Could not interpret id generator strategy [" + strategy + "]" ); } return generatorClass; }
4. 为毛 identifierGeneratorStrategy 值为 org.hibernate.id.enhanced.SequenceStyleGenerator
追本溯源 , 我们来到了 generatorType 定义的地方
org.hibernate.cfg.AnnotationBinder.processId(PropertyHolder, PropertyData, SimpleValue, HashMap<String, IdGenerator>, boolean, ExtendedMappings)
private static void processId( PropertyHolder propertyHolder, PropertyData inferredData, SimpleValue idValue, HashMap<String, IdGenerator> classGenerators, boolean isIdentifierMapper, ExtendedMappings mappings) { ......... String generatorType = generatedValue != null ? generatorType( generatedValue.strategy(), mappings ) : "assigned"; String generatorName = generatedValue != null ? generatedValue.generator() : BinderHelper.ANNOTATION_STRING_DEFAULT; if ( isComponent ) { generatorType = "assigned"; } //a component must not have any generator BinderHelper.makeIdGenerator( idValue, generatorType, generatorName, mappings, localGenerators ); .....
以及 org.hibernate.cfg.AnnotationBinder.generatorType(GenerationType, ExtendedMappings)
private static String generatorType(GenerationType generatorEnum, ExtendedMappings mappings) { boolean useNewGeneratorMappings = mappings.useNewGeneratorMappings(); switch ( generatorEnum ) { case IDENTITY: return "identity"; case AUTO: return useNewGeneratorMappings ? org.hibernate.id.enhanced.SequenceStyleGenerator.class.getName() : "native"; case TABLE: return useNewGeneratorMappings ? org.hibernate.id.enhanced.TableGenerator.class.getName() : MultipleHiLoPerTableGenerator.class.getName(); case SEQUENCE: return useNewGeneratorMappings ? org.hibernate.id.enhanced.SequenceStyleGenerator.class.getName() : "seqhilo"; } throw new AssertionFailure( "Unknown GeneratorType: " + generatorEnum ); }
哦, 原来在这里
case AUTO: return useNewGeneratorMappings ? org.hibernate.id.enhanced.SequenceStyleGenerator.class.getName() : "native";
发现, 当 AUTO 的时候, 如果 useNewGeneratorMappings为true的话 ,那么 就会 使用 SequenceStyleGenerator 而不是 传说中的 native
那么 这里的 useNewGeneratorMappings 是否可以变更呢?
看下 useNewGeneratorMappings 的 源码 org.hibernate.cfg.AnnotationConfiguration.ExtendedMappingsImpl.useNewGeneratorMappings()
public boolean useNewGeneratorMappings() { if ( useNewGeneratorMappings == null ) { final String booleanName = getConfigurationProperties().getProperty( USE_NEW_ID_GENERATOR_MAPPINGS ); useNewGeneratorMappings = Boolean.valueOf( booleanName ); } return useNewGeneratorMappings.booleanValue(); }
hoho,原来读取的是 配置文件中 hibernate.id.new_generator_mappings 的值
刚好我这里的该参数的值是true
hibernate.id.new_generator_mappings=true
原来如此, 仅需把该参数的值设为false 即可解决问题
5. hibernate.id.new_generator_mappings 是什么鬼?
知其然而知其所以然,是吾辈的目标
先来看看 hibernate 对这个参数的定义javadoc
/** * Setting which indicates whether or not the new {@link org.hibernate.id.IdentifierGenerator} are used * for AUTO, TABLE and SEQUENCE. * Default to false to keep backward compatibility. */ public static final String USE_NEW_ID_GENERATOR_MAPPINGS = "hibernate.id.new_generator_mappings";
在 http://stackoverflow.com/questions/25047226/jpa-generationtype-auto-not-considering-column-with-auto-increment#25052275 这个文章里面, 我们可以发现点有价值的内容
properties.put("hibernate.id.new_generator_mappings", "true");
The AUTO will actually use a SequenceStyleGenerator and where the database doesn't support sequences, you end up using a TABLE generator instead (which is a portable solution but it's less efficient than IDENTITY or SEQUENCE).
我们再来看看 SequenceStyleGenerator
SequenceStyleGenerator | It’s an enhanced version of the previous sequence generator. It uses a sequence if the underlying database supports them. If the current database doesn’t support sequences it switches to using a table for generating sequence values. While the previous generators were having a predefined optimization algorithm, the enhanced generators can be configured with an optimizer strategy:
Pooled is the default optimizer strategy. |
大意是, SequenceStyleGenerator 是个增强的 sequence 生成器 ,如果底层数据库支持 序列,那么使用 序列, 如果不支持, 那么切换成 table来生成序列 values.
这也是,我在sqlserver 中,发现 了一个 hibernate_sequence 莫名其妙的表的原因
6. 总结
如果是 sqlserver 数据库, 使用GenerationType.AUTO ,不要配置 hibernate.id.new_generator_mappings=true