多说 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
Generator Description
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