文章目录
- 1. 问题描述
- 2. 解决方案
- 3. 源码解析
- 3.1 `saveWithGeneratedId()` 源码:
- 3.2 `performSave()`方法源码
- 3.3 `performSaveOrReplicate()`方法源码
- 3.4 `addInsertAction()` 源码
- 3.5 `EntityIdentityInsertAction #execute()` 自增长策略执行SQL源码
- 3.6 `EntityInsertAction #execute()` 其他策略SQL执行源码
1. 问题描述
背景:有个项目中在内网和外网各部署了一套,且要将外网的数据摆渡到内网中入库;但是内网中的数据也会新增,类似双主双写操作,外网的主键使用奇数增长,内网主键使用偶数增长,保证内外网数据主键不冲突。
内网入库的发现,JPA主键是自增长的策略,即使id不为空,但是保存的数据后还是会使用数据库的自增长的id,未使用id值,导致两边的数据不一致。
期望:
- 当id==null时,插入时使用数据库的自增长的id,
- 当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。
- 当自增长时则获取到的
generatedId
值则为IdentifierGeneratorHelper.POST_INSERT_INDICATOR
,则调用performSave( entity, null, persister, true, anything, source, requiresImmediateIdAccess )
进入后续方法。注意第二个参数和第四个参数,null 代表id值为空,true:表示id依赖数据库列id增长 - 当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()
源码
- 主键自增长时,生成
EntityIdentityInsertAction
Sql执行类对象 - 主键是其他策略时,生成
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 );
}
}
}