Greem666
Greem666

Reputation: 949

Java Spring TransientPropertyValueException with @OneToMany

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

Answers (1)

jumping_monkey
jumping_monkey

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

Related Questions