Reputation: 768
I have a phone object and a phone history object. I'd like to be able to retrieve a phone history object and then get its parent (phone), but for some reason, when I attempt to do this and then persist the child, I get an exception saying that phone_id isn't populated. Not sure how to fix this, although I suspect it has something to do with my mapping annotations.
Below are my entity classes. I've tried changing the annotations to match the suggestion in the bidirectional relationships section here: http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html/entity.html#entity-mapping-association but this didn't seem to do the trick. I'm new to JPA, so this is probably simple for someone with more experience, but I'm stumped. TIA.
Phone entity:
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;
}
}
Phone History entity:
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;
}
}
UPDATE for clarification (this is a test that fails):
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();
}
}
And the failure stack trace:
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
Upvotes: 0
Views: 7904
Reputation: 23115
Should be easy to fix.
Bug in test code. In child object newPhoneHistory, you haven't set phone object, which provides the mandatory column phoneId. Before or after the call to:
existingPhoneHistory.getPhone().add(newPhoneHistory);
Simply add:
newPhoneHistory.setPhone(existingPhoneHistory.getPhone());
You need to manually setup relationship data on both sides of bidirectional relationships. From the JPA 2 spec:
Bidirectional relationships between managed entities will be persisted based on references held by the owning side of the relationship. It is the developer’s responsibility to keep the in-memory references held on the owning side and those held on the inverse side consistent with each other when they change. In the case of unidirectional one-to-one and one-to-many relationships, it is the developer’s responsibility to insure (sic) that the semantics of the relationships are adhered to. [29]
It is particularly important to ensure that changes to the inverse side of a relationship result in appropriate updates on the owning side, so as to ensure the changes are not lost when they are synchronized to the database.
:-)
Upvotes: 0
Reputation: 34397
Since you have made the joins lazy
, I suspect, you don't have parent object phone
populated in your child object phoneHistory
. Please verify (debug/print phone object values through phoneHistory). If so, plase set the phone
object first in phoneHistory
before saving it.
Add a method addPhoneHistory
in Phone
object as below:
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);
}
Add line in your code where you are adding PhoneHistory
object in Phone
object to use above method.
existingPhoneHistory.getPhone().addPhoneHistory(newPhoneHistory);
Also its good idea to save 'Phonein place of
existingPhoneHistory` as below:
Phone phone = existingPhoneHistory.getPhone();
phone.addPhoneHistory(newPhoneHistory);
entityManager.save(phone);//use your PhoneRepo, if there to persist
Hope this helps.
Upvotes: 1
Reputation: 21
check in the SQL query(or some other data acquisition system you use) that populates the phone_id variable has been used to acquire the column fields. Second, check those fields were even filled in by whatever system is supposed to create the phone_id data (e.g. other servlets manage and send the info into the sql table).
Upvotes: 0