简体   繁体   中英

Spring : nested entity saving null value problem

I'm beginner with JPA and Spring. Also my first question in here. So, sorry for my mistakes. I'm practicing with simple scenario for beginning. I have two entities as Product and Category with bi-directional many-to-one/one-to-many association.

Category class :

@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>();
}

Product class :

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

Both of CategoryRepository and ProductRepository are implementing from JpaRepository. There are few method signatures with @Query annotations.

Services using repositories with @Autowired. There aren't any business logic.

CategoryService class :

@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 class :

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

At final, both entities have seperate restcontroller classes. I'm adding a category from Postman with only name, so product is null (This part is normal). But when I add a product from Postman or another frontend app, product's category is not set (I'm setting category_id with json). But in database, products table's category_id column values are null. json string

Also I've problem with lazy fetch type and json problem. But main problem comes first.

Thank you for any help.

The above JPA entity mapping should work as long as you are passing correct json request body. Also, you might need to use @JsonBackReference/@JsonManagedReference to avoid stackoverflow exception (see JsonManagedReference vs JsonBackReference for more info)

Requests: POST: localhost:/cat

{ "name" :"Construction" }

POST: localhost:/prod, here we are using category id = 1. change it to match the id of 'Construction' category

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

Add this class to jpademo package and run.

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

This is the only configuration for using jackson datatype module.

@Configuration
public class JacksonConfig {

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

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

And the response like that : json response

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM