【飞天奔月出品】聊聊JPA之GenerationType.AUTO

多说 GenerationType.AUTO 适用于多个数据库,

 

写道
在我们的应用中,一般选用@GeneratedValue(strategy=GenerationType.AUTO)这种方式,自动选择主键生成策略,以适应不同的数据库移植。

 

为什么我今天玩 sqlserver 可以创建主键, 但是主键却没有 IDENTITY标识呢?

 

难道是老人说的是错的? 难道教科书上写得不对? 事出反常必有妖,我偏偏不信这个邪

 

1.现象

 

现在这么一个简单的JPA类

 

 

Java代码   收藏代码
  1. @Entity  
  2. @Table(name = "T_SYS_CHOOSE_OPTION")  
  3. public class ChooseOption extends BaseModel implements Command{  
  4.   
  5.     private Long id;  
  6.   
  7.     @Id  
  8.     @Column(name = "ID")  
  9.     @GeneratedValue(strategy = GenerationType.AUTO)  
  10.     public Long getId(){  
  11.         return id;  
  12.     }  
  13.   
  14.     public void setId(Long id){  
  15.         this.id = id;  
  16.     }  
  17. }  

 

 

但是启动程序,  生成的表,id列却没有 IDENTITY 标识



 

2.调试

 

将 logback 相关类日志调整成 debug

 

 

Xml代码   收藏代码
  1. <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) 方法

 

核心代码片段:

 

 

Java代码   收藏代码
  1. public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) {  
  2.         StringBuffer buf = new StringBuffer( hasPrimaryKey() ? dialect.getCreateTableString() : dialect.getCreateMultisetTableString() )  
  3.                 .append( ' ' )  
  4.                 .append( getQualifiedName( dialect, defaultCatalog, defaultSchema ) )  
  5.                 .append( " (" );  
  6.   
  7.         boolean identityColumn = idValue != null && idValue.isIdentityColumn( p.getIdentifierGeneratorFactory(), dialect );  
  8.   
  9.         // Try to find out the name of the primary key to create it as identity if the IdentityGenerator is used  
  10.         String pkname = null;  
  11.         if ( hasPrimaryKey() && identityColumn ) {  
  12.             pkname = ( (Column) getPrimaryKey().getColumnIterator().next() ).getQuotedName( dialect );  
  13.         }  
  14.   
  15.         Iterator iter = getColumnIterator();  
  16.         while ( iter.hasNext() ) {  
  17.             Column col = (Column) iter.next();  
  18.   
  19.             buf.append( col.getQuotedName( dialect ) )  
  20.                     .append( ' ' );  
  21.   
  22.             if ( identityColumn && col.getQuotedName( dialect ).equals( pkname ) ) {  
  23.                 // to support dialects that have their own identity data type  
  24.                 if ( dialect.hasDataTypeInIdentityColumn() ) {  
  25.                     buf.append( col.getSqlType( dialect, p ) );  
  26.                 }  
  27.                 buf.append( ' ' )  
  28.                         .append( dialect.getIdentityColumnString( col.getSqlTypeCode( p ) ) );  
  29.             }  
  30.             else {  
  31.   
  32. .....  

 

 

生成 Identity 的关键语句是 

 

 

Java代码   收藏代码
  1. buf.append( ' ' )  
  2.                         .append( dialect.getIdentityColumnString( col.getSqlTypeCode( p ) ) );  

 

 

 

我这里 dialect 配置的是 

 

 

Java代码   收藏代码
  1. hibernate.dialect=org.hibernate.dialect.SQLServerDialect  

 

 

该方言  getIdentityColumnString 方法的实现 如下:



 

 

可以看出  GenerationType.IDENTITY 的实现原理是拼接了 identity not null

 

那为什么  GenerationType.AUTO 就没有拼接呢? 难道是我的人品不好?

 

别急,还有个关键代码 是  上面的 if 流程判断

 

 

Java代码   收藏代码
  1. if ( identityColumn && col.getQuotedName( dialect ).equals( pkname ) ) {  

 

 

在于  

Java代码   收藏代码
  1. boolean identityColumn = idValue != null && idValue.isIdentityColumn( p.getIdentifierGeneratorFactory(), dialect );  

 

 

而我当 GenerationType.AUTO , 调试到这个地方的时候,   identityColumn =false 

 

进一步跟踪 发现 

 

org.hibernate.mapping.SimpleValue.isIdentityColumn(IdentifierGeneratorFactory, Dialect)

 

 

Java代码   收藏代码
  1. public boolean isIdentityColumn(IdentifierGeneratorFactory identifierGeneratorFactory, Dialect dialect) {  
  2.     identifierGeneratorFactory.setDialect( dialect );  
  3.     return identifierGeneratorFactory.getIdentifierGeneratorClass( identifierGeneratorStrategy )  
  4.             .equals( IdentityGenerator.class );  
  5. }  

 

 

其中的  identifierGeneratorStrategy 值为   org.hibernate.id.enhanced.SequenceStyleGenerator

 

 

导致  下面的代码,并没有使用  native 去取到  dialect.getNativeIdentifierGeneratorClass()

 

 

 

Java代码   收藏代码
  1. /** 
  2.  * {@inheritDoc} 
  3.  */  
  4. public Class getIdentifierGeneratorClass(String strategy) {  
  5.     if ( "native".equals( strategy ) ) {  
  6.         return dialect.getNativeIdentifierGeneratorClass();  
  7.     }  
  8.   
  9.     Class generatorClass = ( Class ) generatorStrategyToClassNameMap.get( strategy );  
  10.     try {  
  11.         if ( generatorClass == null ) {  
  12.             generatorClass = ReflectHelper.classForName( strategy );  
  13.         }  
  14.     }  
  15.     catch ( ClassNotFoundException e ) {  
  16.         throw new MappingException( "Could not interpret id generator strategy [" + strategy + "]" );  
  17.     }  
  18.     return generatorClass;  
  19. }  

 

 

 

4. 为毛 identifierGeneratorStrategy 值为   org.hibernate.id.enhanced.SequenceStyleGenerator

 

追本溯源 , 我们来到了 generatorType 定义的地方

 

org.hibernate.cfg.AnnotationBinder.processId(PropertyHolder, PropertyData, SimpleValue, HashMap<String, IdGenerator>, boolean, ExtendedMappings)

 

 

Java代码   收藏代码
  1.     private static void processId(  
  2.             PropertyHolder propertyHolder,  
  3.             PropertyData inferredData,  
  4.             SimpleValue idValue,  
  5.             HashMap<String, IdGenerator> classGenerators,  
  6.             boolean isIdentifierMapper,  
  7.             ExtendedMappings mappings) {  
  8. .........  
  9.   
  10.   
  11.         String generatorType = generatedValue != null ?  
  12.                 generatorType( generatedValue.strategy(), mappings ) :  
  13.                 "assigned";  
  14.         String generatorName = generatedValue != null ?  
  15.                 generatedValue.generator() :  
  16.                 BinderHelper.ANNOTATION_STRING_DEFAULT;  
  17.         if ( isComponent ) {  
  18.             generatorType = "assigned";  
  19.         } //a component must not have any generator  
  20.         BinderHelper.makeIdGenerator( idValue, generatorType, generatorName, mappings, localGenerators );  
  21.   
  22. .....  

 

 

以及 org.hibernate.cfg.AnnotationBinder.generatorType(GenerationType, ExtendedMappings)

 

 

Java代码   收藏代码
  1. private static String generatorType(GenerationType generatorEnum, ExtendedMappings mappings) {  
  2.     boolean useNewGeneratorMappings = mappings.useNewGeneratorMappings();  
  3.     switch ( generatorEnum ) {  
  4.         case IDENTITY:  
  5.             return "identity";  
  6.         case AUTO:  
  7.             return useNewGeneratorMappings  
  8.                     ? org.hibernate.id.enhanced.SequenceStyleGenerator.class.getName()  
  9.                     : "native";  
  10.         case TABLE:  
  11.             return useNewGeneratorMappings  
  12.                     ? org.hibernate.id.enhanced.TableGenerator.class.getName()  
  13.                     : MultipleHiLoPerTableGenerator.class.getName();  
  14.         case SEQUENCE:  
  15.             return useNewGeneratorMappings  
  16.                     ? org.hibernate.id.enhanced.SequenceStyleGenerator.class.getName()  
  17.                     : "seqhilo";  
  18.     }  
  19.     throw new AssertionFailure( "Unknown GeneratorType: " + generatorEnum );  
  20. }  

 

 

 

哦,  原来在这里 

 

Java代码   收藏代码
  1. case AUTO:  
  2.     return useNewGeneratorMappings  
  3.             ? org.hibernate.id.enhanced.SequenceStyleGenerator.class.getName()  
  4.             : "native";  

 

 

发现, 当 AUTO 的时候,  如果  useNewGeneratorMappings为true的话 ,那么 就会 使用 SequenceStyleGenerator 而不是 传说中的  native

 

 

那么 这里的  useNewGeneratorMappings 是否可以变更呢?

看下 useNewGeneratorMappings 的 源码  org.hibernate.cfg.AnnotationConfiguration.ExtendedMappingsImpl.useNewGeneratorMappings()

 

 

 

Java代码   收藏代码
  1. public boolean useNewGeneratorMappings() {  
  2.     if ( useNewGeneratorMappings == null ) {  
  3.         final String booleanName = getConfigurationProperties().getProperty( USE_NEW_ID_GENERATOR_MAPPINGS );  
  4.         useNewGeneratorMappings = Boolean.valueOf( booleanName );  
  5.     }  
  6.     return useNewGeneratorMappings.booleanValue();  
  7. }  

 

 

hoho,原来读取的是 配置文件中  hibernate.id.new_generator_mappings 的值

刚好我这里的该参数的值是true

 

 

Java代码   收藏代码
  1. hibernate.id.new_generator_mappings=true  

原来如此, 仅需把该参数的值设为false 即可解决问题

 

 

 

 

5. hibernate.id.new_generator_mappings 是什么鬼?

知其然而知其所以然,是吾辈的目标

 

 

先来看看 hibernate 对这个参数的定义javadoc

 

 

Java代码   收藏代码
  1. /** 
  2.  * Setting which indicates whether or not the new {@link org.hibernate.id.IdentifierGenerator} are used 
  3.  * for AUTO, TABLE and SEQUENCE. 
  4.  * Default to false to keep backward compatibility. 
  5.  */  
  6. 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 这个文章里面, 我们可以发现点有价值的内容

 

写道
If you use the new identifier generators:

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:

 

  • none: there is no optimizing strategy applied, so every identifier is fetched from the database
  • hi/lo: it uses the original hi/lo algorithm. This strategy makes it difficult for other systems to share the same identifier sequence, requiring other systems to implement the same identifier generation logic.
  • pooled: This optimizer uses a hi/lo optimization strategy, but instead of saving the current hi value it stores the current range upper boundary (or lower boundary –hibernate.id.optimizer.pooled.prefer_lo).

Pooled is the default optimizer strategy.

   

大意是,  SequenceStyleGenerator  是个增强的  sequence 生成器 ,如果底层数据库支持 序列,那么使用 序列, 如果不支持, 那么切换成 table来生成序列 values.

 

这也是,我在sqlserver 中,发现 了一个 hibernate_sequence 莫名其妙的表的原因

 




 
 

 

6. 总结

 

如果是 sqlserver 数据库, 使用GenerationType.AUTO ,不要配置  hibernate.id.new_generator_mappings=true

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值