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

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

 

写道
在我们的应用中,一般选用@GeneratedValue(strategy=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 标识


【飞天奔月出品】聊聊JPA之GenerationType.AUTO_第1张图片
 

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 方法的实现 如下:


【飞天奔月出品】聊聊JPA之GenerationType.AUTO_第2张图片
 

 

可以看出  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 这个文章里面, 我们可以发现点有价值的内容

 

写道
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 莫名其妙的表的原因

 



【飞天奔月出品】聊聊JPA之GenerationType.AUTO_第3张图片
 
 

 

6. 总结

 

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

 

 

 

你可能感兴趣的:(jpa)