Reputation: 949
I have the following classes:
A Product class:
package com.springtraining.hibernate.invoice;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
@NoArgsConstructor
@Getter
@Entity
@Table(name="PRODUCTS")
public class Product {
@Id
@GeneratedValue
@NotNull
@Column(name = "ID")
private int id;
@Column(name = "NAME")
private String name;
@OneToMany(
targetEntity = Item.class,
mappedBy = "product",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY
)
private List<Item> items = new ArrayList<>();
public Product(String name) {
this.name = name;
}
}
package com.springtraining.hibernate.invoice.dao;
import com.springtraining.hibernate.invoice.Product;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import javax.transaction.Transactional;
@Repository
@Transactional
public interface ProductDao extends CrudRepository<Product, Integer> {
}
Now, an Item class:
package com.springtraining.hibernate.invoice;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@NoArgsConstructor
@Getter
@Entity
@Table(name="ITEMS")
public class Item {
@Id
@GeneratedValue
@NotNull
@Column(name = "ID")
private int id;
@JoinColumn(name = "PRODUCT_ID", referencedColumnName = "id")
@ManyToOne
private Product product;
@NotNull
@Column(name = "PRICE")
private BigDecimal price;
@NotNull
@Column(name = "QUANTITY")
private int quantity;
@NotNull
@Column(name = "VALUE")
private BigDecimal value;
@JoinColumn(name="INVOICE_ID", referencedColumnName = "id")
@ManyToOne
private Invoice invoice;
public Item(Product product, String price, int quantity) {
this.product = product;
this.product.getItems().add(this);
this.price = new BigDecimal(price);
this.quantity = quantity;
this.value = this.price.multiply(new BigDecimal(quantity));
}
public void setInvoice(Invoice invoice) {
this.invoice = invoice;
invoice.getItems().add(this);
}
}
package com.springtraining.hibernate.invoice.dao;
import com.springtraining.hibernate.invoice.Item;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import javax.transaction.Transactional;
@Repository
@Transactional
public interface ItemDao extends CrudRepository<Item, Integer> {
}
And an Invoice class:
package com.springtraining.hibernate.invoice;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
@NoArgsConstructor
@Getter
@Entity
@Table(name="INVOICES")
public class Invoice {
@Id
@GeneratedValue
@NotNull
@Column(name = "ID")
private int id;
@NotNull
@Column(name = "NUMBER")
private String number;
@OneToMany(
targetEntity = Item.class,
mappedBy = "invoice",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY
)
private List<Item> items = new ArrayList<>();
public Invoice(String number) {
this.number = number;
}
}
package com.springtraining.hibernate.invoice.dao;
import com.springtraining.hibernate.invoice.Invoice;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import javax.transaction.Transactional;
@Repository
@Transactional
public interface InvoiceDao extends CrudRepository<Invoice, Integer> {
}
Now, when I am running a unit test with these classes, I get the following error:
org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.springtraining.hibernate.invoice.Item.product -> com.springtraining.hibernate.invoice.Product; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.springtraining.hibernate.invoice.Item.product -> com.springtraining.hibernate.invoice.Product
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.springtraining.hibernate.invoice.Item.product -> com.springtraining.hibernate.invoice.Product; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.springtraining.hibernate.invoice.Item.product -> com.springtraining.hibernate.invoice.Product
The unit test code looks like such:
package com.springtraining.hibernate.invoice.dao;
import com.springtraining.hibernate.invoice.Invoice;
import com.springtraining.hibernate.invoice.Item;
import com.springtraining.hibernate.invoice.Product;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
@RunWith(SpringRunner.class)
@SpringBootTest
public class InvoiceDaoTestSuite {
@Autowired
private InvoiceDao invoiceDao;
@Test
public void testInvoiceDaoSave() {
// Given
Product product1 = new Product("prod1");
Product product2 = new Product("prod2");
Item item1 = new Item(product1, "100", 10);
Item item2 = new Item(product1, "200", 10);
Item item3 = new Item(product2, "50", 2);
Item item4 = new Item(product2, "250", 25);
Invoice invoice1 = new Invoice("HK-47");
item2.setInvoice(invoice1);
item3.setInvoice(invoice1);
Invoice invoice2 = new Invoice("HK-48");
item1.setInvoice(invoice2);
item4.setInvoice(invoice2);
// When
invoiceDao.save(invoice1);
invoiceDao.save(invoice2);
int invoice1_id = invoice1.getId();
int invoice2_id = invoice2.getId();
// Then
Assert.assertNotEquals(0, invoice1_id);
Assert.assertNotEquals(0, invoice2_id);
Assert.assertTrue(invoice1.getItems().containsAll(Arrays.asList(item2, item3)));
Assert.assertTrue(invoice2.getItems().containsAll(Arrays.asList(item1, item4)));
Assert.assertTrue(product1.getItems().containsAll(Arrays.asList(item1, item2)));
Assert.assertTrue(product1.getItems().containsAll(Arrays.asList(item3, item4)));
// Clean-up
try {
invoiceDao.deleteById(invoice1_id);
invoiceDao.deleteById(invoice2_id);
} catch (Exception e) {
// Do nothing
}
}
}
I have been looking at this code for a few hours now, and I still do not get it, where I have missed something. Saving Invoice entity, should automatically instantiate Item and Product objects associated with it as well.
Anyone?
Upvotes: 2
Views: 1550
Reputation: 7819
In public class Item
, add @ManyToOne
cascade = CascadeType.ALL property, like so:
@JoinColumn(name = "PRODUCT_ID", referencedColumnName = "id")
@ManyToOne(cascade = CascadeType.ALL)
private Product product;
@JoinColumn(name = "INVOICE_ID", referencedColumnName = "id")
@ManyToOne(cascade = CascadeType.ALL)
private Invoice invoice;
When you create a new entity, using the keyword new
, it is in the Transient
state. To persist/save it to the DB, you first need to add it to the Persistence Context
. CascadeType.ALL
includes CascadeType.PERSIST
, which will instruct Hibernate
to persist the product and invoice entities.
Also, remove the @NotNull
on your entity fields annotated by @Id
. It is not required, since your field is a primary key and instead of this
invoiceDao.save(invoice1);
invoiceDao.save(invoice2);
int invoice1_id = invoice1.getId();
int invoice2_id = invoice2.getId();
You can do:
int invoice1_id = invoiceDao.save(invoice1).getId();
int invoice2_id = invoiceDao.save(invoice2).getId();
Upvotes: 2