Issue Description
Basically, I have a simple one to many setup, with a quote object having several quote Items as it's children. When I save the quote, I want to have all quote items saved as well. The issue is that when I do this, the quote items do not get the correct quote set to them and hibernate tries to insert a null value instead. Not sure if my annotations are not configured correctly or something.
Hibernate version:
3.3.0.SP1
3.4.0.GA (annotations)
Name and version of the database you are using:
Oracle 10G
Classes
Code:
@Entity
@org.hibernate.annotations.GenericGenerator(name="increment", strategy="increment")
@Table(name="QUOTES")
public class Quote {
@Id @GeneratedValue(generator="increment")
@Column(name="QUOTE_ID", nullable=false)
private Integer quoteId;
@Column(name="QUOTE_NUMBER")
private String quoteNumber;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="USER_ID")
private LoginUser user;
@Column(name="ISSUE_DATE")
private Date issueDate;
@Column(name="EXPIRATION_DATE")
private Date expirationDate;
@OneToMany(mappedBy="quote", fetch=FetchType.LAZY, cascade=CascadeType.ALL)
private Set<QuoteItem> quoteItems = new HashSet<QuoteItem>();
public void addQuoteItem(QuoteItem item){
this.quoteItems.add(item);
}
public void removeQuoteItem(QuoteItem item){
this.quoteItems.remove(item);
}
/*
* Getters and Setters
*/
public Integer getQuoteId() {
return quoteId;
}
public void setQuoteId(Integer quoteId) {
this.quoteId = quoteId;
}
public String getQuoteNumber() {
return quoteNumber;
}
public void setQuoteNumber(String quoteNumber) {
this.quoteNumber = quoteNumber;
}
public LoginUser getUser() {
return user;
}
public void setUser(LoginUser user) {
this.user = user;
}
public Date getIssueDate() {
return issueDate;
}
public void setIssueDate(Date issueDate) {
this.issueDate = issueDate;
}
public Date getExpirationDate() {
return expirationDate;
}
public void setExpirationDate(Date expirationDate) {
this.expirationDate = expirationDate;
}
public Set<QuoteItem> getQuoteItems() {
return quoteItems;
}
public void setQuoteItems(Set<QuoteItem> quoteItems) {
this.quoteItems = quoteItems;
}
Code:
@Entity
@org.hibernate.annotations.GenericGenerator(name="increment", strategy="increment")
@Inheritance(strategy=InheritanceType.JOINED)
@Table(name="QUOTE_ITEMS")
public class QuoteItem {
@Id @GeneratedValue(generator="increment")
@Column(name="QUOTE_ITEM_ID")
private Integer quoteItemId;
@ManyToOne
@JoinColumn(name="QUOTE_ID")
private Quote quote;
@Column(name="ITEM_COST")
private BigDecimal itemCost;
@Column(name="ITEM_OVERHEAD")
private BigDecimal itemOverhead;
@Column(name="QUANTITY")
private BigDecimal quantity;
@Column(name="TOTAL_COST")
private BigDecimal totalCost;
/*
* Getters and setters
*/
public Integer getQuoteItemId() {
return quoteItemId;
}
public void setQuoteItemId(Integer quoteItemId) {
this.quoteItemId = quoteItemId;
}
public Quote getQuote() {
return quote;
}
public void setQuote(Quote quote) {
this.quote = quote;
}
public BigDecimal getItemCost() {
return itemCost;
}
public void setItemCost(BigDecimal itemCost) {
this.itemCost = itemCost;
}
public BigDecimal getItemOverhead() {
return itemOverhead;
}
public void setItemOverhead(BigDecimal itemOverhead) {
this.itemOverhead = itemOverhead;
}
public BigDecimal getQuantity() {
return quantity;
}
public void setQuantity(BigDecimal quantity) {
this.quantity = quantity;
}
public BigDecimal getTotalCost() {
return totalCost;
}
public void setTotalCost(BigDecimal totalCost) {
this.totalCost = totalCost;
}
}
Test CaseCode:
public void testCascading() throws Exception{
Quote quote = new Quote();
quote.setQuoteNumber("09BPF87654321");
LoginUser user = loginUserDao.findByID(5255);
quote.setUser(user);
quote.setExpirationDate(new Date());
quote.setIssueDate(new Date());
QuoteItem item = new QuoteItem();
item.setItemCost(new BigDecimal("1"));
item.setItemOverhead(new BigDecimal("1"));
item.setQuantity(new BigDecimal("1"));
item.setTotalCost(new BigDecimal("1"));
quote.addQuoteItem(item);
quoteDao.save(quote);
Quote assertQuote = quoteDao.findByID(quoteDao.getHighestID());
Assert.assertEquals(new BigDecimal("1"), assertQuote.getQuoteItems().iterator().next().getItemCost());
}
SQL Generated with Stack TraceCode:
Hibernate: insert into QUOTE_ITEMS (ITEM_COST, ITEM_OVERHEAD, QUANTITY, QUOTE_ID, TOTAL_COST, QUOTE_ITEM_ID) values (?, ?, ?, ?, ?, ?)
Jan 20, 2009 1:03:55 PM org.hibernate.util.JDBCExceptionReporter logExceptions
WARNING: SQL Error: 1400, SQLState: 23000
Jan 20, 2009 1:03:55 PM org.hibernate.util.JDBCExceptionReporter logExceptions
SEVERE: ORA-01400: cannot insert NULL into ("WWW"."QUOTE_ITEMS"."QUOTE_ID")
Jan 20, 2009 1:03:55 PM org.hibernate.util.JDBCExceptionReporter logExceptions
WARNING: SQL Error: 1400, SQLState: 23000
Jan 20, 2009 1:03:55 PM org.hibernate.util.JDBCExceptionReporter logExceptions
SEVERE: ORA-01400: cannot insert NULL into ("WWW"."QUOTE_ITEMS"."QUOTE_ID")
Jan 20, 2009 1:03:55 PM org.hibernate.event.def.AbstractFlushingEventListener performExecutions
SEVERE: Could not synchronize database state with session
org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:71)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:253)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:266)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:167)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
at org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:41)
at org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:969)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1114)
at org.hibernate.impl.QueryImpl.list(QueryImpl.java:79)
at org.hibernate.impl.AbstractQueryImpl.uniqueResult(AbstractQueryImpl.java:811)
at edu.harvard.med.genome.database.GenericDaoHibernate.getHighestID(GenericDaoHibernate.java:75)
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.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:310)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at $Proxy24.getHighestID(Unknown Source)
at edu.harvard.med.genome.database.orders.quotes.test.TestQuote.testCascading(TestQuote.java:94)
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.test.context.junit4.SpringTestMethod.invoke(SpringTestMethod.java:160)
at org.springframework.test.context.junit4.SpringMethodRoadie.runTestMethod(SpringMethodRoadie.java:233)
at org.springframework.test.context.junit4.SpringMethodRoadie$RunBeforesThenTestThenAfters.run(SpringMethodRoadie.java:333)
at org.springframework.test.context.junit4.SpringMethodRoadie.runWithRepetitions(SpringMethodRoadie.java:217)
at org.springframework.test.context.junit4.SpringMethodRoadie.runTest(SpringMethodRoadie.java:197)
at org.springframework.test.context.junit4.SpringMethodRoadie.run(SpringMethodRoadie.java:143)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.invokeTestMethod(SpringJUnit4ClassRunner.java:160)
at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:51)
at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:44)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:27)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:37)
at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:42)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:97)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:45)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Caused by: java.sql.BatchUpdateException: ORA-01400: cannot insert NULL into ("WWW"."QUOTE_ITEMS"."QUOTE_ID")
at oracle.jdbc.driver.DatabaseError.throwBatchUpdateException(DatabaseError.java:343)
at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:10698)
at org.apache.commons.dbcp.DelegatingPreparedStatement.executeBatch(DelegatingPreparedStatement.java:231)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:48)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:246)
... 44 more
Hack FixI'm able to work around this by changing the "addQuoteItem" method to be as follows:
Code:
public void addQuoteItem(QuoteItem item){
item.setQuote(this);
this.quoteItems.add(item);
}
However, I was under the impression that this should happen automatically in hibernate.
Thanks,
Tim