2012-10-12 109 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]

确保对关系的反面进行更改会导致拥有方的适当更新以确保更改在与数据库同步时不会丢失,这一点尤其重要。

:-)