Reputation: 368
I create new Product using Spring Boot / Hibernate / JPA and get Column 'brand_id' cannot be null error. I don't know why this error happen. Does anyone can explain where I was wrong?
Product:
@Entity
public class Product {
@javax.persistence.Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
@NotBlank(message = "Product name is required")
private String name;
private String image;
private String description;
private double price;
private int countInStock;
private double rating;
private int numReviews;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "brand_id", updatable = false, nullable = false)
@JsonIgnore
private Brand brand;
@ManyToMany
@JoinTable(name = "product_category", joinColumns = @JoinColumn(name = "product_id"), inverseJoinColumns = @JoinColumn(name = "category_id"))
@JsonIgnore
private List<Category> categories;
Brand:
@Entity
public class Brand {
@javax.persistence.Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
@NotBlank(message = "Brand name is required")
private String name;
@OneToMany(cascade = CascadeType.REFRESH, fetch = FetchType.LAZY, mappedBy = "brand", orphanRemoval = true)
private List<Product> products;
Category:
@Entity
public class Category {
@javax.persistence.Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
@NotBlank(message = "Category name is required")
private String name;
@ManyToMany(mappedBy = "categories")
private List<Product> products;
And my Product Object that used to input create new Product:
{
"name": "test",
"image": "/images/test.jpg",
"description": "test",
"brand_id": 4,
"category_id": 1,
"price": 99.99,
"countInStock": 100,
"rating": 4.5,
"numReviews": 120
}
Upvotes: 0
Views: 1076
Reputation: 370
Wherever you are creating this Product object, you must provide it with a Brand object. Currently, you are only providing it with an integer as a Brand.
What you need to do is fetch the brand object you are referring to from your DB and include it in the instantiation of your new Product. Probably the best and simplest approach to this would be using the EntityManager to get a reference to that Brand.
Getting the EntityManager is very simple when using spring.
@PersistenceContext
private EntityManager entityManager;
Now, simply use it to get the reference to the target Brand.
Brand brand = entityManager.getReference(Brand.class , brand_id);
Use this brand to instantiate your new Product
and insert it to the DB without any exceptions.
How to automate this logic into the unmarshalling process
If you are always going to want to use this logic when creating a Product, you can use this logic in the constructor. If you only want to use this method when unmarshalling, here is an example that is based of something I wrote recently. I am using an XmlAdapter, but there are also JSONAdapater classes you can look into and should work about the same way.
Create you adapter class. This class is going to be used to parse JSON to java object.
//Once again, I am using an XmlAdapter, but the idea should be similar with JSONAdapters
@Service
public class BrandIdXmlAdapter extends XmlAdapter<String, Brand> {
@PersistenceContext
private EntityManager entityManager;
//v is the String that is going to be unmarshalled.
//In our case, its going to be the brand_id String.
@Override
public Brand unmarshal(String v) {
Brand brand = entityManager.getReference(Brand.class, v);
return brand;
}
There is also a possibility to override the marshal()
method for parsing from a POJO to XML/JSON.
The only problem here is that to be able to use use the PersistenceContext
annotation, this class has to be an EJB.
We are going to workaround that by telling Spring this is a necessary service.
First step is to give the adapter class the Service
annotation(Done in example above).
The next step is to go to where you would want to unmarshall the input into a POJO (either the controller if you receive it as a request or the service if you are going to request it from another service) and autowire the adapter
@Autowired
private BrandIdXmlAdapter xmlAdapter;
Next step is to create the unmarshaller that will use this adapter.
JAXBContext context = JAXBContext.newInstance(Product.class);
brandIdUnmarshaller = context.createUnmarshaller();
brandIdUnmarshaller.setAdapter(xmlAdapter);
Now when receiving the data, use the brandIdUnmarshaller.unmarhsall()
method.
Last step is to annotate your Brand variable in Product to tell it to use the adapter when parsing this specific variable.
public class Product {
.
.
.
//again, find the right annotation according to your JSONAdapter
@XmlJavaTypeAdapter(BrandIdXmlAdapter.class)
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "brand_id", updatable = false, nullable = false)
@JsonIgnore
private Brand brand;
}
Now everytime you parse from JSON to Product, you will automatically get a Product that contains a valid Brand object.
Upvotes: 2