Java Spring TransientPropertyValueException 与 @OneToMany

标签 java spring spring-boot hibernate jpa

我有以下类(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> {
}

现在,一个 Item 类:

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> {
}

还有一个 Invoice 类:

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> {
}

现在,当我使用这些类运行单元测试时,出现以下错误:

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

单元测试代码如下所示:

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
        }
    }
}

我已经看了这个代码几个小时了,但我仍然不明白,我错过了一些东西。保存发票实体时,应该自动实例化与其关联的 Item 和 Product 对象。

有人吗?

最佳答案

public class Item中,添加@ManyToOne cascade = CascadeType.ALL属性,如下所示:

@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;

当您使用关键字new创建实体时,它处于Transient状态。要将其持久化/保存到数据库,您首先需要将其添加到持久化上下文CascadeType.ALL 包含 CascadeType.PERSIST,它将指示 Hibernate持久产品发票实体。

此外,删除实体字段上由 @Id 注释的 @NotNull。这不是必需的,因为您的字段是主键,而不是这个

invoiceDao.save(invoice1);
invoiceDao.save(invoice2);
int Invoice1_id = Invoice1.getId();
int Invoice2_id = Invoice2.getId();

你可以这样做:

int invoice1_id = invoiceDao.save(invoice1).getId();
int invoice2_id = invoiceDao.save(invoice2).getId();

关于Java Spring TransientPropertyValueException 与 @OneToMany,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62724462/

相关文章:

java - Spring Security 每次都返回相同的 token

java - 如何使用Gson序列化LocalDate?

java - 从 JUnit 测试自连接到 Websocket Spring 服务器

java - 在 java spring boot 中使用 Mysql 函数

java - 如何为 Spring Boot 应用程序配置端口

java - 如何使用集合和队列查找图的可达性

java - 无法将 Integer 对象插入通用数组

java - 在 Eclipse 4.2 中验证 XML 和 XSD - 缺少选项?

hibernate - 是 Hibernate、PostgreSQL 还是 Spring Boot : Unexpected error trying to gauge level of JDBC REF_CURSOR support : null

java - BaseButton<E> 和 BaseButton<E extends BaseButton<E>> 有什么区别?