問題描述
最近使用springdata自動化的JpaRepository進行對mysql數(shù)據(jù)庫操作,遇到一個問題。
有一個實體bean是OrderInfoBean,主鍵為orderPKId,利用orderInfoRepository.save方法保存orderInfoBean時,返回的orderInfoBean的orderPKId的屬性值是MySQL數(shù)據(jù)庫的自增主鍵id的值。后期由于要增加分區(qū),主鍵變成了reqDate和orderPkId之后,用orderInfoRepository.save方法保存orderInfoBean時,返回的orderInfoBean的orderPKId的屬性值就為空了。以下是改成聯(lián)合主鍵之后的部分代碼
@Entity
@IdClass(OrderIdClass.class)
public class OrderInfoBean {
@Id
private String reqDate;
@Id
@GeneratedValue
private Long orderPkId;
private String orderNo;
}
public class OrderIdClass implements Serializable {
private static final long serialVersionUID = -5781462543461514912L;
private String reqDate;
private Long orderPkId;
public String getReqDate() {
return reqDate;
}
public void setReqDate(String reqDate) {
this.reqDate = reqDate;
}
public Long getOrderPkId() {
return orderPkId;
}
public void setOrderPkId(Long orderPkId) {
this.orderPkId = orderPkId;
}
public OrderIdClass(String reqDate) {
this.reqDate = reqDate;
this.orderPkId = 0L;
}
public OrderIdClass(String reqDate, Long orderPkId) {
this.reqDate = reqDate;
this.orderPkId = orderPkId;
}
public OrderIdClass() {
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
OrderIdClass idClass = (OrderIdClass) o;
return Objects.equals(orderPkId, idClass.orderPkId) && Objects.equals(reqDate, idClass.reqDate);
}
@Override
public int hashCode() {
return Objects.hash(orderPkId, reqDate);
}
}
OrderInfoBean orderInfoBean = new OrderInfoBean;
orderInfoBean.setOrderNo("1534548648645646");
orderInfoBean.setReqDate("20180426");
/*主鍵為orderPkId的時候,orderInfoBean的orderPkId屬性會賦值成MySQL數(shù)據(jù)庫返回的自增id,但是主鍵變?yōu)閛rderPkId和reqDate之后,orderInfoBean的orderPkId屬性不再有返回值
orderInfoBean = orderInfoRepository.save(orderInfoBean);
在網(wǎng)上沒有找到直接資料,于是debug了一下,發(fā)現(xiàn)主鍵不同時,在SimpleJpaRepository(JpaRepository的實現(xiàn)類)這個類的save方法會對實體進行不同的處理。
springdata的JpaRepository對于insert和update操作都使用的save方法,具體調(diào)用insert還是update可以先去了解一下hibernate的中對象的三種狀態(tài)瞬時狀態(tài)transient、持久狀態(tài)(托管)persistent、游離(脫管)detached狀態(tài)的關(guān)系。
當主鍵是單獨的orderPkId時,由于orderPkId為空時,默認這個實體是新的直接執(zhí)行insert操作,對應(yīng)到hibernate之中即是persist。而當主鍵是orderPkId和reqDate的聯(lián)合主鍵時,對應(yīng)hibernate的merge操作。
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
hibernate中persist和merge的區(qū)別
首先看看源碼:
em.merge()調(diào)用的DefaultMergeEventListener的onmerge方法
switch ( entityState ) {
case DETACHED:
entityIsDetached( event, copyCache );
break;
case TRANSIENT:
entityIsTransient( event, copyCache );
break;
case PERSISTENT:
entityIsPersistent( event, copyCache );
break;
default: //DELETED
throw new ObjectDeletedException(
"deleted instance passed to merge",
null,
getLoggableName( event.getEntityName(), entity )
);
}
首先,會根據(jù)實體名字和主鍵的值在session里面查找,是否這個實體在session的管理中,如果沒有,則假設(shè)entityState是detached狀態(tài),在這個狀態(tài)下,session會根據(jù)主鍵去數(shù)據(jù)庫查找是否有對應(yīng)行,如果查找結(jié)果 為空
final Object result = source.get( entityName, clonedIdentifier );
source.getLoadQueryInfluencers().setInternalFetchProfile( previousFetchProfile );
if ( result == null ) {
//TODO: we should throw an exception if we really *know* for sure
// that this is a detached instance, rather than just assuming
//throw new StaleObjectStateException(entityName, id);
// we got here because we assumed that an instance
// with an assigned id was detached, when it was
// really persistent
entityIsTransient( event, copyCache );
}
首先會判斷這個實體屬于三種狀態(tài)的哪一種,
DefaultMergeEventListener
if ( ForeignKeys.isTransient( entityName, entity, getAssumedUnsaved(), source ) ) {
if ( traceEnabled ) {
LOG.tracev( "Transient instance of: {0}", getLoggableName( entityName, entity ) );
}
return EntityState.TRANSIENT;
}
if ( traceEnabled ) {
LOG.tracev( "Detached instance of: {0}", getLoggableName( entityName, entity ) );
}
return EntityState.DETACHED;
那么將entity的copy一份,copy的對象變成了transient狀態(tài)
protected void entityIsTransient(MergeEvent event, Map copyCache) {
LOG.trace( "Merging transient instance" );
final Object entity = event.getEntity();
final EventSource source = event.getSession();
final String entityName = event.getEntityName();
final EntityPersister persister = source.getEntityPersister( entityName, entity );
final Serializable id = persister.hasIdentifierProperty() ?
persister.getIdentifier( entity, source ) :
null;
if ( copyCache.containsKey( entity ) ) {
persister.setIdentifier( copyCache.get( entity ), id, source );
}
else {
( (MergeContext) copyCache ).put( entity, source.instantiate( persister, id ), true ); //before cascade!
}
final Object copy = copyCache.get( entity );
// cascade first, so that all unsaved objects get their
// copy created before we actually copy
//cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE);
super.cascadeBeforeSave( source, persister, entity, copyCache );
copyValues( persister, entity, copy, source, copyCache, ForeignKeyDirection.FROM_PARENT );
saveTransientEntity( copy, entityName, event.getRequestedId(), source, copyCache );
// cascade first, so that all unsaved objects get their
// copy created before we actually copy
super.cascadeAfterSave( source, persister, entity, copyCache );
copyValues( persister, entity, copy, source, copyCache, ForeignKeyDirection.TO_PARENT );
event.setResult( copy );
}
從以上代碼可以看到,將orderInfoBean這個entity的值賦給了copy,并且將copy的值保存到了數(shù)據(jù)庫,并且返回給event,及repository的save方法返回這個copy對象。
em.persist調(diào)用的DefaultPersistEventListener的onPersist方法,判斷出entity
EntityState entityState = getEntityState( entity, entityName, entityEntry, source );
的狀態(tài)是Transient,
@SuppressWarnings({"unchecked"})
protected void entityIsTransient(PersistEvent event, Map createCache) {
LOG.trace( "Saving transient instance" );
final EventSource source = event.getSession();
final Object entity = source.getPersistenceContext().unproxy( event.getObject() );
if ( createCache.put( entity, entity ) == null ) {
saveWithGeneratedId( entity, event.getEntityName(), createCache, source, false );
}
}
然后會返回帶有自增鍵的entity。
Spring Data JPA offers the following strategies to detect whether an entity is new or not:
- Id-Property inspection (default): By default Spring Data JPA inspects the identifier property of the given entity. If the identifier property is
null, then the entity is assumed to be new. Otherwise, it is assumed to be not new.- Implementing
Persistable: If an entity implementsPersistable, Spring Data JPA delegates the new detection to theisNew(…)method of the entity. See the JavaDoc for details.- Implementing
EntityInformation: You can customize theEntityInformationabstraction used in theSimpleJpaRepositoryimplementation by creating a subclass ofJpaRepositoryFactoryand overriding thegetEntityInformation(…)method accordingly. You then have to register the custom implementation ofJpaRepositoryFactoryas a Spring bean. Note that this should be rarely necessary. See the JavaDoc for details.
以上摘自SpringDataJPA 2.1.0.M2官方文檔。SpringDataJPA判斷一個實體是否是新建的:
- 默認是根據(jù)實體的主鍵,如果主鍵是空,那么實體默認是新建的,否則不是新的
- 實體實現(xiàn)了Persistable接口,JPA會根據(jù)isNew()方法判定
- 第三種方法好復(fù)雜,暫時略過不表
解決方案
- 方案一
實體類實現(xiàn)Persistable接口,并且重寫isNew()方法
@Override
public boolean isNew() {
if (this.orderPkId == null) {
return true;
} else {
return false;
}
}
按照這樣修改之后,JpaRepository的實現(xiàn)類SimpleJpaRepository的save方法確實回去調(diào)用em.persist方法,但是還是不能返回MySQL數(shù)據(jù)庫自增的主鍵。
- 方案二
最后經(jīng)過請教了線內(nèi)大佬,得到答復(fù),在程序里面僅標記自增鍵為主鍵,然后問題就解決了o(╥﹏╥)o。但是這樣一來聯(lián)合查找的時候,關(guān)聯(lián)關(guān)系就只有主鍵關(guān)聯(lián),帶不上req_date的分區(qū)關(guān)聯(lián)了,不知道會不會有性能上的損耗。針對這個,其實可以用QueryDSL來自定義SQL查詢語言。
之前一直想的解決方案都是,換一種freestyle點的插入查找方法,比如我小師傅現(xiàn)在用的queryDSL和criteria;結(jié)果解決方案還是蠻簡單的,不過key point還是看了源碼,找出了問題關(guān)鍵所在,才能精準解決問題。