Spring:嵌套实体保存空值问题

Posted

技术标签:

【中文标题】Spring:嵌套实体保存空值问题【英文标题】:Spring : nested entity saving null value problem 【发布时间】:2021-01-15 14:27:19 【问题描述】:

我是 JPA 和 Spring 的初学者。也是我在这里的第一个问题。所以,对不起我的错误。我正在练习简单的场景作为开始。我有两个实体作为产品和类别,具有双向多对一/一对多关联。

类别类:

@Entity
@Table(name = "categories")
@NoArgsConstructor
@AllArgsConstructor
@Data
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Long.class)

public class Category implements Serializable 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column(name = "name", length = 50, nullable = false)
    private String name;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "category")
    private List<Product> products = new ArrayList<Product>();

产品类别:

@Table(name = "products")
@NoArgsConstructor
@AllArgsConstructor
@Data
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Long.class)

public class Product implements Serializable 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column(name = "title", nullable = false, length = 50)
    private String title;

    @Column(name = "price", precision = 4, scale = 2, nullable = false)
    private Double price;

    @Column(name = "quantity", nullable = false)
    private int quantity;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id", foreignKey = @ForeignKey(name = "FK_product_category"))
    private Category category;

CategoryRepository 和 ProductRepository 都是从 JpaRepository 实现的。 带有@Query 注解的方法签名很少。

使用带有@Autowired 的存储库的服务。没有任何业务逻辑。

CategoryService 类:

@Transactional
public class CategoryService implements IRepositoryService<Category, Long> 

    private ICategoryRepository categoryRepository;

    @Autowired
    public CategoryService(ICategoryRepository categoryRepository) 
        this.categoryRepository = categoryRepository;
    

    @Override
    public List<Category> findAllOrderById() 
        return categoryRepository.findAllOrderById();
    

    @Override
    public Category findById(Long id) 
        return categoryRepository.findById(id).orElseThrow(EntityNotFoundException::new);
    

    @Override
    public List<Category> findByForeignKey(Long categoryId) 
        return null;
    

    @Override
    public void add(Category category) 
        category.getProducts().forEach(product -> product.setCategory(category));
        categoryRepository.save(category);
    

    @Override
    public void update(Category category) 
        categoryRepository.save(category);
    

    @Override
    public void deleteById(Long id) 
        categoryRepository.deleteById(id);
    

ProductService 类:

@Transactional
public class ProductService implements IRepositoryService<Product, Long>

    @Autowired
    private IProductRepository productRepository;

    @Override
    public List<Product> findAllOrderById() 
        return productRepository.findAllOrderById();
    

    @Override
    public Product findById(Long id) 
        return productRepository.findById(id).orElseThrow(EntityNotFoundException::new);
    

    @Override
    public List<Product> findByForeignKey(Long categoryId) 
        return productRepository.findByCategoryId(categoryId);
    

    @Override
    public void add(Product entity) 
        productRepository.save(entity);
    

    @Override
    public void update(Product entity) 
        productRepository.save(entity);
    

    @Override
    public void deleteById(Long id) 
        productRepository.deleteById(id);
    

最终,两个实体都有单独的 restcontroller 类。 我正在从 Postman 添加一个只有名称的类别,所以产品为空(这部分是正常的)。但是当我从 Postman 或其他前端应用程序添加产品时,没有设置产品的类别(我正在使用 json 设置 category_id)。但在数据库中,products 表的 category_id 列值为空。 json string

我也遇到了延迟获取类型和 json 问题。但主要问题是第一位的。

感谢您的帮助。

【问题讨论】:

你在你的 json 有效载荷上发送什么?你也可以发布你的控制器吗? 对于您的惰性获取类型问题,请随时提出另一个问题 【参考方案1】:

只要您传递正确的 json 请求正文,上述 JPA 实体映射应该可以工作。此外,您可能需要使用 @JsonBackReference/@JsonManagedReference 来避免 *** 异常(请参阅 JsonManagedReference vs JsonBackReference 了解更多信息)

请求: POST:本地主机:/猫

 "name" :"Construction" 

POST: localhost:/prod,这里我们使用类别 id = 1。将其更改为匹配 'Construction' 类别的 id


"title" :"Bricks",
"price" :"1.2",
"quantity": "100",
"category": 
        "id": 1 
    

将这个类添加到jpademo包并运行。

package jpademo;

import com.fasterxml.jackson.annotation.*;
import lombok.*;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.web.bind.annotation.*;

import javax.persistence.*;
import java.util.*;

@SpringBootApplication
public class DemoApplication 
    public static void main(String[] args) 
        SpringApplication.run(DemoApplication.class, args);
    


@RestController
@RequiredArgsConstructor
class Ctrl 

    final CategoryRepo categoryRepo;
    final ProductRepo prodRepo;

    @PostMapping("/cat")
    void cat(@RequestBody Category cat) 
        categoryRepo.save(cat);
    

    @PostMapping("/prod")
    void prod(@RequestBody Product p) 
        prodRepo.save(p);
    

    @GetMapping("/cat")
    List<Category> cats() 
        return categoryRepo.findAll();
    

    @GetMapping("/prod")
    List<Product> prods() 
        return prodRepo.findAll();
    



interface CategoryRepo extends JpaRepository<Category, Long> 

interface ProductRepo extends JpaRepository<Product, Long> 

@Entity
@NoArgsConstructor
@AllArgsConstructor
@Data
class Category 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "category")
    @JsonManagedReference //to avoid ***
    private List<Product> products = new ArrayList<>();


@Entity
@NoArgsConstructor
@AllArgsConstructor
@Data
class Product 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    private Double price;

    private int quantity;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id", foreignKey = @ForeignKey(name = "FK_product_category"))
    @JsonBackReference
    private Category category;

【讨论】:

感谢您的帮助。我意识到我的 json 身体是错误的。我看到了我的错,谢谢你。现在,我在性能方面遇到了另一个问题。当我发送一个请求(例如 findAllProducts 或 Categories)时,休眠会产生许多 sql 查询(sql 计数等于实体编号)。我可以处理 jackson-datatype-hibernate 模块,但是这一次,获取的字段带有空值。我想我需要配置这个模块。 您可能需要执行 join-fetch 以便在单个查询中获取所需的数据。请参阅此以获取 @EntityGraph 的示例以获取记录 ***.com/questions/64013319/… 因为这是一个不同的问题.. 随时问一个新问题.. 如果***.com/questions/64013319/… 没有帮助 这似乎不像我的问题。此外,JsonBackReference 和 JsonManagedReference 让我丢失了获取的字段。就像我列出类别时一样,json 中只有 id 和 name 字段(没有产品列)。所以我正在寻找用杰克逊模块解决这个问题,但仍然找不到解决方案。【参考方案2】:

这是使用 jackson 数据类型模块的唯一配置。

@Configuration
public class JacksonConfig 

    @Bean
    public Hibernate5Module hibernate5Module() 
        return new Hibernate5Module();
    

    @Bean
    public AfterburnerModule afterburnerModule() 
        return new AfterburnerModule();
    

这样的回应: json response

【讨论】:

以上是关于Spring:嵌套实体保存空值问题的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法在 gcloud-python 中保存嵌套实体?

在 Core Data 中的相关实体之间传递数据时的空值

Spring-Data JPA:保存引用现有实体的新实体

Spring JPA实体不保存到数据库

DRF保存嵌套对象并更新另一个

Spring/Hibernate:保存后是不是必须加载实体才能使用它?