多说 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
相关推荐
标题中的“【飞天奔月出品】windows版nginx 快速操控神器(启动start,关闭stop,重启restart) 批处理”指的是一个专为Windows操作系统设计的Nginx管理工具,它通过批处理脚本实现了Nginx服务的便捷启动、停止和重启...
在这款“兔子奔月吃月饼”的游戏中,我们可以推测玩家需要控制兔子角色在月球表面跳跃,收集月饼以获得分数。游戏可能包含计时机制、等级系统或者难度递增的障碍物,以增加挑战性和趣味性。同时,由于是微信小游戏,...
"兔子奔月"这个主题很可能使用了CSS3来完成动画效果,比如兔子跳跃的动画,月亮升起的过程等。CSS3的过渡(transition)和动画(animation)属性可以轻松创建平滑的视觉效果,增强游戏的吸引力。 此外,"吃月饼"这...
《奔月》是鲁迅先生的一篇短篇小说,收录于其《故事新编》之中,通过对传统神话的再创作,鲁迅以戏拟的手法揭示了深刻的社会与人性问题。这篇作品通过对后羿这一昔日英雄形象的塑造,反映出鲁迅对时代变迁下英雄命运...
这篇文档介绍了如何使用Adobe Photoshop软件创作一张奔月女孩的梦幻艺术照片效果。以下是详细步骤: 1. **新建文件与导入素材**: - 首先创建一个新文件,大小与素材1相同,命名为"奔月女孩"。 - 然后打开素材1,...
奔月生物:2021年半年度报告.PDF
HTML5奔月游戏是一款完全基于HTML5技术开发的互动小游戏,玩家可以扮演一只兔子,目标是在游戏中尽可能地收集月饼并安全抵达月球。这款游戏的亮点在于其无需任何额外的插件或软件支持,只需浏览器就能运行,这得益于...
《H5游戏源码解析:奔月游戏》 在当今数字化时代,HTML5(简称H5)技术以其跨平台、轻量级、易部署的特点,成为制作网页游戏的热门选择。"奔月游戏"作为一款H5游戏,其源码为我们提供了一窥H5游戏开发的窗口。本文...
在奔月游戏中,可能使用canvas来绘制角色、背景、轨迹等元素,并通过定时更新画面来实现动画效果。 2. **Web Audio API**:HTML5提供了Web Audio API,用于处理和播放音频。游戏往往需要背景音乐和音效,这个API...
在这个兔子奔月吃月饼游戏中,Canvas可能被用来绘制游戏场景,如月亮、兔子、月饼等元素,以及处理游戏中的动态效果,如兔子跳跃、月饼移动等。 JavaScript是HTML5游戏的核心,它负责处理游戏逻辑、用户交互以及...
【标题】"小游戏源码-火贱兔奔月.rar" 提供的是一个小型游戏的源代码,名为"火贱兔奔月"。这类源码通常用于教学、学习或游戏开发者的参考,帮助开发者理解游戏的基本架构和编程逻辑。 【描述】"小游戏源码-火贱兔...
在这个兔子奔月吃月饼的游戏中,Canvas可能是用来绘制游戏背景、角色、月饼以及其他交互元素的平台。 CSS3(层叠样式表3)也在这其中扮演了重要角色。CSS3不仅提供了更精细的样式控制,如边框阴影、圆角、过渡和...
【知识点】 1. 小学语文教学:这个教案属于小学五年级语文的教学材料,主要针对的是苏教版的教材,体现了小学阶段语文教学的特点和要求。 2. 课文讲解:《嫦娥奔月》是中国传统神话故事,这篇课文旨在通过讲述嫦娥...
《鲁迅的《奔月》:颠覆传统,开创审美新向度》 鲁迅的短篇小说《奔月》是其《故事新编》中的一篇,它颠覆了我们对古代神话的传统认知,尤其对嫦娥这一角色的刻画,使得这篇作品在文学史上占据了独特的地位。鲁迅...
游戏源码分享下载 --- hjby.zipHTML5小游戏【火贱兔奔月--425款经典优秀H5小游戏合集】游戏源码分享下载 --- hjby.zipHTML5小游戏【火贱兔奔月--425款经典优秀H5小游戏合集】游戏源码分享下载 --- hjby.zipHTML5小...
在这款"兔子奔月吃月饼游戏"中,HTML5起到了核心作用,提供了游戏的基础结构和用户界面。 JavaScript是网页开发中的重要脚本语言,它与HTML5结合,赋予了网页动态功能。在这个游戏中,JavaScript用于处理用户输入,...
【标题】:“奔月生物:2021年半年度报告.rar”是一个压缩文件,其中包含了一份关于奔月生物科技公司在2021年上半年业务运营、财务状况和业绩表现的详细报告。这类报告通常由上市公司发布,以供投资者、分析师和其他...