JPA实体主键使用自增长的策略下,使id在Null的情况使用数据库自增长的值,不为Null的情况下使用设置的值

1. 问题描述

背景:有个项目中在内网和外网各部署了一套,且要将外网的数据摆渡到内网中入库;但是内网中的数据也会新增,类似双主双写操作,外网的主键使用奇数增长,内网主键使用偶数增长,保证内外网数据主键不冲突。

内网入库的发现,JPA主键是自增长的策略,即使id不为空,但是保存的数据后还是会使用数据库的自增长的id,未使用id值,导致两边的数据不一致。

期望:

  1. 当id==null时,插入时使用数据库的自增长的id,
  2. 当id不为null时,插入时则使用给的id值作为主键入库。

2. 解决方案

使用自定义主键生成策略,继承原有主键自增长策略实现类org.hibernate.id.IdentityGenerator,重新实现IdentifierGenerator#generate(SharedSessionContractImplementor session, Object object)

public class TestIdGenerator extends IdentityGenerator {

    @Override
    public Serializable generate(SharedSessionContractImplementor session, Object obj) throws HibernateException {
        if (obj instanceof Test) {
            Test t = (Test) obj;
            if (t.getId() != null) {//当id不空时,使用该id值作为主键
                return new Assigned().generate(session,obj);
            }
        }
        //当id == null 时 使用原因的自增长策略
        return super.generate(session, obj);
    }
}

使用方法

@Data
@Entity
public class Test {
    @Id
//    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @GeneratedValue(generator = "sq_id")
    @GenericGenerator(name = "sq_id", strategy = "com.test.entity.TestIdGenerator")
    private Integer id;

    private String name;
}

3. 源码解析

由于JPA默认的实现是Hibernate,save()方法最终会调用hibernate的AbstractSaveEventListener#saveWithGeneratedId( Object entity, String entityName, Object anything, EventSource source, boolean requiresImmediateIdAccess)来完成入库。

3.1 saveWithGeneratedId() 源码:

	/**
	 * Prepares the save call using a newly generated id.
	 *
	 * @param entity The entity to be saved
	 * @param entityName The entity-name for the entity to be saved
	 * @param anything Generally cascade-specific information.
	 * @param source The session which is the source of this save event.
	 * @param requiresImmediateIdAccess does the event context require
	 * access to the identifier immediately after execution of this method (if
	 * not, post-insert style id generators may be postponed if we are outside
	 * a transaction).
	 *
	 * @return The id used to save the entity; may be null depending on the
	 *         type of id generator used and the requiresImmediateIdAccess value
	 */
	protected Serializable saveWithGeneratedId(
			Object entity,
			String entityName,
			Object anything,
			EventSource source,
			boolean requiresImmediateIdAccess) {
		callbackRegistry.preCreate( entity );

		if ( entity instanceof SelfDirtinessTracker ) {
			( (SelfDirtinessTracker) entity ).$$_hibernate_clearDirtyAttributes();
		}
		EntityPersister persister = source.getEntityPersister( entityName, entity );
		Serializable generatedId = persister.getIdentifierGenerator().generate( source, entity );
		if ( generatedId == null ) {
			throw new IdentifierGenerationException( "null id generated for:" + entity.getClass() );
		}
		else if ( generatedId == IdentifierGeneratorHelper.SHORT_CIRCUIT_INDICATOR ) {
			return source.getIdentifier( entity );
		}
		else if ( generatedId == IdentifierGeneratorHelper.POST_INSERT_INDICATOR ) {
			return performSave( entity, null, persister, true, anything, source, requiresImmediateIdAccess );
		}
		else {
			// TODO: define toString()s for generators
			if ( LOG.isDebugEnabled() ) {
				LOG.debugf(
						"Generated identifier: %s, using strategy: %s",
						persister.getIdentifierType().toLoggableString( generatedId, source.getFactory() ),
						persister.getIdentifierGenerator().getClass().getName()
				);
			}

			return performSave( entity, generatedId, persister, false, anything, source, true );
		}
	}

项目启动时会扫描所有的@Entity标记的实体,转换成SingleTableEntityPersister,该persister包含实体类的源数据信息以及对应的插入,修改、删除的sql语句模板信息
通过实体类的persister 获取主键生成器IdentifierGenerator,通过generate()方法来获取id。

  1. 当自增长时则获取到的generatedId 值则为IdentifierGeneratorHelper.POST_INSERT_INDICATOR ,则调用performSave( entity, null, persister, true, anything, source, requiresImmediateIdAccess ) 进入后续方法。注意第二个参数和第四个参数,null 代表id值为空,true:表示id依赖数据库列id增长
  2. 当id不空null时,走的是最后else判断中,依然调用的是performSave(...)方法。注意第二个参数和第四个参数,id值,false:id不需要依赖数据库列增长

3.2 performSave()方法源码

见源码中的注释,最终执行performSaveOrReplicate方法

protected Serializable performSave(
			Object entity,
			Serializable id,
			EntityPersister persister,
			boolean useIdentityColumn,
			Object anything,
			EventSource source,
			boolean requiresImmediateIdAccess) {
		final EntityKey key;
		//上面方面传入的第四参数,只有id不能空时,才会执行
		if ( !useIdentityColumn ) {
			//判断当前系统中,实体类中设置的id值是否是唯一值
			key = source.generateEntityKey( id, persister );
			Object old = source.getPersistenceContext().getEntity( key );
			if ( old != null ) {
				if ( source.getPersistenceContext().getEntry( old ).getStatus() == Status.DELETED ) {
					source.forceFlush( source.getPersistenceContext().getEntry( old ) );
				}
				else {
					throw new NonUniqueObjectException( id, persister.getEntityName() );
				}
			}
			//设置主键id的值
			persister.setIdentifier( entity, id, source );
		}
		else {
			key = null;
		}
		if ( invokeSaveLifecycle( entity, persister, source ) ) {
			return id; //EARLY EXIT
		}
		return performSaveOrReplicate(
				entity,
				key,
				persister,
				useIdentityColumn,
				anything,
				source,
				requiresImmediateIdAccess
		);
	}

3.3 performSaveOrReplicate()方法源码

见源码中的注释。最核心的代码是addInsertAction()方法,该方法是对自增长和其他主键策略生成实际SQL执行类

protected Serializable performSaveOrReplicate(
			Object entity,
			EntityKey key,
			EntityPersister persister,
			boolean useIdentityColumn,
			Object anything,
			EventSource source,
			boolean requiresImmediateIdAccess) {

		Serializable id = key == null ? null : key.getIdentifier();

		boolean inTrx = source.isTransactionInProgress();
		boolean shouldDelayIdentityInserts = !inTrx && !requiresImmediateIdAccess;
		// 省略部分无关的代码。。。。。
		
		//核心代码,对自增长和其他主键策略生成实际SQL执行类
		AbstractEntityInsertAction insert = addInsertAction(
				values, id, entity, persister, useIdentityColumn, source, shouldDelayIdentityInserts
		);
		// 省略部分无关核心的代码。。。。。
		return id;
	}

3.4 addInsertAction() 源码

  1. 主键自增长时,生成EntityIdentityInsertAction Sql执行类对象
  2. 主键是其他策略时,生成EntityInsertAction Sql执行类对象

通过addAction(insert)方法,来调用Executable#execute方法,由于EntityIdentityInsertAction EntityInsertAction 都实现了该方法,实际调用的就是这类下的execute()方法

private AbstractEntityInsertAction addInsertAction(
			Object[] values,
			Serializable id,
			Object entity,
			EntityPersister persister,
			boolean useIdentityColumn,
			EventSource source,
			boolean shouldDelayIdentityInserts) {
		if ( useIdentityColumn ) {
			EntityIdentityInsertAction insert = new EntityIdentityInsertAction(
					values, entity, persister, isVersionIncrementDisabled(), source, shouldDelayIdentityInserts
			);
			source.getActionQueue().addAction( insert );
			return insert;
		}
		else {
			Object version = Versioning.getVersion( values, persister );
			EntityInsertAction insert = new EntityInsertAction(
					id, values, entity, version, persister, isVersionIncrementDisabled(), source
			);
			source.getActionQueue().addAction( insert );
			return insert;
		}
	}

3.5 EntityIdentityInsertAction #execute() 自增长策略执行SQL源码

@Override
   public void execute() throws HibernateException {
   	nullifyTransientReferencesIfNotAlready();

   	final EntityPersister persister = getPersister();
   	final SharedSessionContractImplementor session = getSession();
   	final Object instance = getInstance();

   	// 省略部分无关的代码。。。。。

   	if ( !isVeto() ) {
   		//插入数据
   		generatedId = persister.insert( getState(), instance, session );
   		if ( persister.hasInsertGeneratedProperties() ) {
   			persister.processInsertGeneratedProperties( generatedId, instance, getState(), session );
   		}
   		//need to do that here rather than in the save event listener to let
   		//the post insert events to have a id-filled entity when IDENTITY is used (EJB3)
   		persister.setIdentifier( instance, generatedId, session );
   		session.getPersistenceContext().registerInsertedKey( getPersister(), generatedId );
   		entityKey = session.generateEntityKey( generatedId, persister );
   		session.getPersistenceContext().checkUniqueness( entityKey, getInstance() );
   	}	
   	// 省略部分无关的代码。。。。。
   }

调用3.1 中的persister 的insert(fields, object, session) 来实现插入。insert 实现如下。使用getSQLIdentityInsertString()来获取插入执行sql语句

public Serializable insert(Object[] fields, Object object, SharedSessionContractImplementor session)
			throws HibernateException {
		// apply any pre-insert in-memory value generation
		preInsertInMemoryValueGeneration( fields, object, session );
		final int span = getTableSpan();
		final Serializable id;
		if ( entityMetamodel.isDynamicInsert() ) {
			//动态插入,这边用不到
			.....
		}
		else {
			// 使用静态模板插入。getSQLIdentityInsertString() 获取静态插入模板 persister.sqlIdentityInsertString 字段值
			id = insert( fields, getPropertyInsertability(), getSQLIdentityInsertString(), object, session );
		.......
		}
		return id;
	}

3.6 EntityInsertAction #execute() 其他策略SQL执行源码

@Override
	public void execute() throws HibernateException {
		nullifyTransientReferencesIfNotAlready();

		final EntityPersister persister = getPersister();
		final SharedSessionContractImplementor session = getSession();
		final Object instance = getInstance();
		final Serializable id = getId();

		final boolean veto = preInsert();
		//.......
		
		if ( !veto ) {
			//插入数据
			persister.insert( id, getState(), instance, session );
			PersistenceContext persistenceContext = session.getPersistenceContext();
			final EntityEntry entry = persistenceContext.getEntry( instance );
			if ( entry == null ) {
				throw new AssertionFailure( "possible non-threadsafe access to session" );
			}
			
			entry.postInsert( getState() );
			......
		}
		........
	}

调用3.1 中的persister 的insert(id, fields,object, session) 来实现插入。insert 实现如下,使用getSQLInsertStrings()[j]来获取插入执行sql语句

public void insert(Serializable id, Object[] fields, Object object, SharedSessionContractImplementor session) {
		// apply any pre-insert in-memory value generation
		preInsertInMemoryValueGeneration( fields, object, session );

		final int span = getTableSpan();
		if ( entityMetamodel.isDynamicInsert() ) {
			.......
		}
		else {
			// For the case of dynamic-insert="false", use the static SQL
			for ( int j = 0; j < span; j++ ) {
				insert( id, fields, getPropertyInsertability(), j, getSQLInsertStrings()[j], object, session );
			}
		}
	}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值