2012-10-12 106 views
0

我有一個電話對象和一個電話歷史記錄對象。我希望能夠檢索電話歷史記錄對象,然後獲取其父母(電話號碼),但出於某種原因,當我嘗試執行此操作並堅持該孩子時,我收到一個異常,表示phone_id未填充。不知道如何解決這個問題,雖然我懷疑它與我的映射註釋有關。使用JPA從小孩獲得父母

下面是我的實體類。我試着改變註釋以匹配雙向關係部分中的建議:http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html/entity.html#entity-mapping-association,但這似乎沒有辦法。我是JPA新手,所以對於有更多經驗的人來說,這可能很簡單,但我很難過。 TIA。

電話實體:

package com.blah.model; 

import java.util.ArrayList; 
import java.util.Calendar; 
import java.util.List; 

import javax.persistence.CascadeType; 
import javax.persistence.Column; 
import javax.persistence.Entity; 
import javax.persistence.FetchType; 
import javax.persistence.GeneratedValue; 
import javax.persistence.Id; 
import javax.persistence.JoinColumn; 
import javax.persistence.ManyToOne; 
import javax.persistence.OneToMany; 
import javax.persistence.Table; 
import javax.persistence.Temporal; 
import javax.persistence.TemporalType; 

@Entity 
@Table(name = "phones") 
public class EPhone implements BaseModel { 

    private static final long serialVersionUID = 3497994786207995664L; 

    @Id 
    @GeneratedValue 
    @Column(name = "id") 
    private Long id; 

    @ManyToOne(fetch = FetchType.LAZY) 
    @JoinColumn(name = "account_id", 
      nullable = false) 
    private EAccount account; 

    @Temporal(TemporalType.TIMESTAMP) 
    private Calendar created; 

    @Column(name = "value", 
     nullable = false) 
     private Long value; 

    @OneToMany(fetch = FetchType.LAZY, 
      mappedBy = "phone", 
      cascade = CascadeType.ALL) 
    private final List<EPhoneHistory> history; 

    public EPhone() { 
     this(Calendar.getInstance()); 
    } 

    public EPhone(final Calendar createdDate) { 
     setCreated(createdDate); 
     history = new ArrayList<EPhoneHistory>(); 
    } 

    public void add(EPhoneHistory phoneHistoryItem) { 
     history.add(phoneHistoryItem); 
    } 

    public EAccount getAccount() { 
     return account; 
    } 

    public Calendar getCreated() { 
     return created; 
    } 

    @OneToMany(fetch = FetchType.LAZY, 
      mappedBy = "phone", 
      cascade = CascadeType.ALL) 
    public List<EPhoneHistory> getHistory() { 
     return history; 
    } 

    public Long getId() { 
     return id; 
    } 

    public Long getValue() { 
     return value; 
    } 

    public void setAccount(EAccount account) { 
     this.account = account; 
    } 

    public void setCreated(Calendar created) { 
     this.created = created; 
    } 

    public void setId(Long id) { 
     this.id = id; 
    } 

    public void setValue(Long value) { 
     this.value = value; 
    } 

} 

電話歷史實體:

package com.blah.model; 

import java.util.Calendar; 

import javax.persistence.Column; 
import javax.persistence.Entity; 
import javax.persistence.EnumType; 
import javax.persistence.Enumerated; 
import javax.persistence.FetchType; 
import javax.persistence.GeneratedValue; 
import javax.persistence.Id; 
import javax.persistence.JoinColumn; 
import javax.persistence.ManyToOne; 
import javax.persistence.Table; 
import javax.persistence.Temporal; 
import javax.persistence.TemporalType; 

@Entity 
@Table(name = "phone_history") 
public class EPhoneHistory implements BaseModel { 

    private static final long serialVersionUID = 4591314258588732076L; 

    @Id 
    @GeneratedValue 
    @Column(name = "id") 
    private Long Id; 

    @ManyToOne(fetch = FetchType.LAZY) 
    @JoinColumn(name = "phone_id", 
      nullable = false) 
    private EPhone phone; 

    @Temporal(TemporalType.TIMESTAMP) 
    private Calendar created; 

    @Enumerated(EnumType.ORDINAL) 
    @Column(name = "type_id") 
    private PhoneHistoryType type; 

    public Calendar getCreated() { 
     return created; 
    } 

    public Long getId() { 
     return Id; 
    } 

    public EPhone getPhone() { 
     return phone; 
    } 

    public PhoneHistoryType getType() { 
     return type; 
    } 

    public void setCreated(Calendar created) { 
     this.created = created; 
    } 

    public void setId(Long id) { 
     Id = id; 
    } 

    public void setPhone(EPhone phone) { 
     this.phone = phone; 
    } 

    public void setType(PhoneHistoryType type) { 
     this.type = type; 
    } 

} 

更新說明(這是一個失敗的試驗):

package com.blah.model.repository; 

import org.junit.Test; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.transaction.annotation.Transactional; 

import com.blah.AbstractBaseDbTestCase; 
import com.blah.model.EPhoneHistory; 
import com.blah.model.PhoneHistoryType; 

public class PhoneHistoryRepositoryTest extends AbstractBaseDbTestCase { 

    @Autowired 
    PhoneHistoryRepository phoneHistoryRepo; 

    @Test 
    @Transactional 
    public void testCreateAndSavePhoneHistory() { 

     EPhoneHistory existingPhoneHistory = phoneHistoryRepo.find(1L); 

     EPhoneHistory newPhoneHistory = new EPhoneHistory(); 
     newPhoneHistory.setType(PhoneHistoryType.VOICE); 

     // I assume this is where I would 
     // set the phone, but I don't have it. 
     // existingPhoneHistory.setPhone(phone); 

     // This is what I'm having trouble with. getPhone() doesn't populate the 
     // phone field with the parent phone object. 
     existingPhoneHistory.getPhone().add(newPhoneHistory); 

     phoneHistoryRepo.persist(existingPhoneHistory); 
     phoneHistoryRepo.flush(); 
    } 
} 

,故障堆棧跟蹤:

org.springframework.dao.DataIntegrityViolationException: NULL not allowed for column "PHONE_ID"; SQL statement: 
insert into phone_history (id, created, phone_id, type_id) values (null, ?, ?, ?) [23502-168]; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: NULL not allowed for column "PHONE_ID"; SQL statement: 
insert into phone_history (id, created, phone_id, type_id) values (null, ?, ?, ?) [23502-168] 
    at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:643) 
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:104) 
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:403) 
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:58) 
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213) 
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:163) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) 
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) 
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622) 
    at com.blah.model.repository.PhoneHistoryRepository$$EnhancerByCGLIB$$4f90cdde.flush(<generated>) 
    at com.blah.model.repository.PhoneHistoryRepositoryTest.testCreateAndSavePhoneHistory(PhoneHistoryRepositoryTest.java:29) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
    at java.lang.reflect.Method.invoke(Method.java:597) 
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45) 
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) 
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42) 
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20) 
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28) 
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74) 
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:30) 
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83) 
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72) 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231) 
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47) 
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231) 
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60) 
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229) 
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50) 
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222) 
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) 
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71) 
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300) 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174) 
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) 
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) 
Caused by: org.hibernate.exception.ConstraintViolationException: NULL not allowed for column "PHONE_ID"; SQL statement: 
insert into phone_history (id, created, phone_id, type_id) values (null, ?, ?, ?) [23502-168] 
    at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:128) 
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:49) 
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:125) 
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:110) 
    at org.hibernate.engine.jdbc.internal.proxy.AbstractStatementProxyHandler.continueInvocation(AbstractStatementProxyHandler.java:129) 
    at org.hibernate.engine.jdbc.internal.proxy.AbstractProxyHandler.invoke(AbstractProxyHandler.java:81) 
    at $Proxy37.executeUpdate(Unknown Source) 
    at org.hibernate.id.IdentityGenerator$GetGeneratedKeysDelegate.executeAndExtract(IdentityGenerator.java:96) 
    at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:58) 
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2870) 
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3381) 
    at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:81) 
    at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:362) 
    at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:203) 
    at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:183) 
    at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:167) 
    at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:320) 
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:287) 
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:193) 
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:126) 
    at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:78) 
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:208) 
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:151) 
    at org.hibernate.internal.SessionImpl.firePersistOnFlush(SessionImpl.java:870) 
    at org.hibernate.internal.SessionImpl.persistOnFlush(SessionImpl.java:863) 
    at org.hibernate.engine.spi.CascadingAction$8.cascade(CascadingAction.java:346) 
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:380) 
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323) 
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208) 
    at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:409) 
    at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:350) 
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:326) 
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208) 
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:165) 
    at org.hibernate.event.internal.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:160) 
    at org.hibernate.event.internal.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:151) 
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:88) 
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51) 
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1214) 
    at org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:986) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
    at java.lang.reflect.Method.invoke(Method.java:597) 
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:240) 
    at $Proxy32.flush(Unknown Source) 
    at com.blah.model.repository.AbstractRepository.flush(AbstractRepository.java:41) 
    at com.blah.model.repository.AbstractRepository$$FastClassByCGLIB$$1ba8d45c.invoke(<generated>) 
    at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:149) 
    at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint(Cglib2AopProxy.java:689) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150) 
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155) 
    ... 36 more 
Caused by: org.h2.jdbc.JdbcSQLException: NULL not allowed for column "PHONE_ID"; SQL statement: 
insert into phone_history (id, created, phone_id, type_id) values (null, ?, ?, ?) [23502-168] 
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:329) 
    at org.h2.message.DbException.get(DbException.java:169) 
    at org.h2.message.DbException.get(DbException.java:146) 
    at org.h2.table.Column.validateConvertUpdateSequence(Column.java:293) 
    at org.h2.table.Table.validateConvertUpdateSequence(Table.java:689) 
    at org.h2.command.dml.Insert.insertRows(Insert.java:120) 
    at org.h2.command.dml.Insert.update(Insert.java:84) 
    at org.h2.command.CommandContainer.update(CommandContainer.java:75) 
    at org.h2.command.Command.executeUpdate(Command.java:230) 
    at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:156) 
    at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:142) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
    at java.lang.reflect.Method.invoke(Method.java:597) 
    at org.hibernate.engine.jdbc.internal.proxy.AbstractStatementProxyHandler.continueInvocation(AbstractStatementProxyHandler.java:122) 
    ... 83 more 
+0

只需在一個側面說明:如果你想跟蹤您的域實體的歷史考慮使用Hibernate Envers –

+0

@StefanHaberl非常酷。謝謝你的提示。 – Luke

回答

1

由於您的加入lazy,我懷疑,你沒有填入你的子對象phoneHistory父對象phone。請驗證(通過phoneHistory調試/打印手機對象值)。如果是這樣,plase在保存之前先在phoneHistory中設置phone對象。在你的代碼

 public void addPhoneHistory(PhoneHistory phoneHistory){ 
     phoneHistory.setPhone(this); //This is importtant as its sets the parent 
     if(this.phoneHistories == null){ 
      this.phoneHistories = new ArrayList<PhoneHistory>(); 
     } 
     this.phoneHistories.add(phoneHistory); 
     } 

行添加您身在何處Phone對象添加PhoneHistory對象使用上述方法:

Phone對象添加一個方法addPhoneHistory如下。

existingPhoneHistory.getPhone().addPhoneHistory(newPhoneHistory); 

而且它的好主意,電話​​3210 existingPhoneHistory`保存」如下:

Phone phone = existingPhoneHistory.getPhone(); 
    phone.addPhoneHistory(newPhoneHistory); 
    entityManager.save(phone);//use your PhoneRepo, if there to persist 

希望這有助於。

+0

你是對的。電話對象未在phoneHistory中填充。除了將獲取類型更改爲渴望(我不一定要這樣做,因爲我並不總是需要在樹中向上遍歷),我是否可以將手機填充到phoneHistory中?這不就是映射的重點嗎?我認爲這會自動發生在持久化(通過觸發延遲加載),因爲它需要它。您要求我將手機設置在手機歷史記錄中,但由於手機未裝入手機歷史記錄中,我怎麼知道父母是什麼?對不起,這是所有的新東西。 – Luke

+0

僅當需要時才加載子對象,即當您訪問集合以檢索「提供您的會話仍然可用」時的值(您的對象仍與JPA會話連接)。您指的是下面的級別。當您訪問「PhoneHistory」及其父母(「電話」)時,請確保您仍在會話範圍內。一旦你完成檢索,對象應該被填充。 –

+0

我已經添加了更新,以便您可以確切地查看我的問題所在。測試失敗,說明phone_id不能爲空。 – Luke

0

檢查填充phone_id變量的SQL查詢(或您使用的其他數據採集系統)是否已用於獲取列字段。 其次,檢查那些字段甚至被任何系統應該創建phone_id數據填充(例如,其他servlet管理和發送信息到sql表中)。

0

應該很容易修復。

  1. LAZY加載不是一個問題在這裏。
    所有的測試調用都在同一個Spring測試事務中運行。因此,當您通過.getXXX()調用遍歷關係以獲取必要的數據時,您的所有LAZY加載關係將由JPA提供程序自動加載。
  2. 測試代碼中的錯誤。 在子對象newPhoneHistory中,您尚未設置提供強制列phoneId的電話對象。前或調用後:

    existingPhoneHistory.getPhone().add(newPhoneHistory); 
    

    只需添加:

    newPhoneHistory.setPhone(existingPhoneHistory.getPhone()); 
    

您需要手動對雙向關係的兩側設置關係的數據。根據JPA 2規範:

受管實體之間的雙向關係將基於關係擁有方所持有的引用而持續存在。開發人員有責任保持內存中的引用保持在持有者一方,而保留在內存中的引用在更改時保持一致。在單向一對一和一對多關係的情況下,開發者有責任確保(sic)遵守關係的語義。 [0122]

確保對關係的反面進行更改會導致擁有方的適當更新以確保更改在與數據庫同步時不會丟失,這一點尤其重要。

:-)