JPA #onetomany cascade insert is throwing org.hibernate.exception.ConstraintViolationException - java

Attachement class:
#Entity
#Table(name="attachments")
#Getter
#Setter
public class AttachmentModel {
//#EmbeddedId
//private AttachmentId attachmentId;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="notice_attachment_id")
private long attachmentId;
#Column(name="notice_id")
private long noticeId;
#Column(name="attachment")
private String attachmentUrl;
#JsonIgnore
#ManyToOne(cascade = {CascadeType.PERSIST , CascadeType.MERGE,
CascadeType.DETACH , CascadeType.REFRESH},optional = false)
#JoinColumn(name="notice_id", insertable=false, updatable=false)
#MapsId("notice_id")
NoticesModel notice;
public void addNotice(NoticesModel notice) {
this.notice = notice;
}
public AttachmentModel() {
}
}
Notices class:
#Entity
#Table(name = "notices")
#Getter #Setter
public class NoticesModel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "notice_id" ,updatable = false, nullable = false,insertable = true)
private long noticeID;
#OneToMany(fetch = FetchType.EAGER, cascade = { CascadeType.ALL } , mappedBy = "notice")
//#mappedBy(name = "notice_id")
private List<AttachmentModel> attachments;
}
Code to parse JSON and saving it
public HashMap<String,Object> saveNotices(#RequestBody List<NoticesModel> tmpNotices)
{
List<NoticesModel> notices = tmpNotices;
for (NoticesModel notice : notices) {
List<AttachmentModel> attachments = notice.getAttachments();
for (AttachmentModel attachment : attachments) {
attachment.addNotice(notice);
System.out.println(attachment.getAttachmentUrl());
}
for (AttachmentModel attachment : attachments) {
//attachment.addNotice(notice);
System.out.println(attachment.getNotice().getContent());
System.out.println(attachment.getNotice().getNoticeID());
}
}
int result = noticesServices.saveNotice(notices);
HashMap<String,Object> res = new HashMap<>();
res.put("message",result);
return res;
}
This is my JSON I am sending
[
{
"attachments": [
{
"attachmentUrl": "/abc/bcd"
}
],
"content": "string",
}
]
For this case I am trying to save save my notice and attachment.
in this particular case notice_id is getting created while saving to database.
so while trying to save attachement table it is trying to save with notice_id as 0.
so I am getting the exception.
could not execute statement; SQL [n/a]; constraint [attachments_notices_fk]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
How I can be able to solve this issue?
Is this possible to get the notice_id before saving to DB so that I can get notice_id so that I can set it in attachment so that it will not be saved with 0?
What am I doing wrong(Any alternative approach I can take) in this case(I am pretty new to JPA and springboot)?

I think you just should not need to use any notice_id. Remove notice_id and relevant things from your AttachmentModel and usenotice for mapping (NOTE: there will still be column notice_id in db after removal), so:
#ManyToOne
private NoticesModel notice;
and change also the mapping in the NoticesModel to refer to the correct field:
// ALL is just a suggestion
#OneToMany(mappedBy = "noticesModel", cascade = CascadeType.ALL)
private List<AttachmentModel> attachementModels;
Then your for loop might look like:
for (NoticesModel notice : notices) {
for (AttachmentModel am : notice.getAttachments()) {
am.setNotice(notice);
}
noticesServices.save(notice);
}
You could also add something like this in your NoticesModel to handle setting the reference always before persisting:
#PrePersist
private void prePersist() {
for (AttachmentModel am : attachments) {
am.setNotice(this);
}
}

Related

set a relationship in jpa

Yes, i know there's lots of this questions about jpa and foreign keys
But i dont found any answer for my case.
I need to import json data in a mysql database. The data looks like this example
order -(1:n)> item
{
type: "order",
name: "abcd",
business_key: "2020_0001",
}
[
{
type: "item",
desc: "xxxx",
business_key: "2020_0001",
date: "2020-01-01",
},
{
type:"item",
desc: "aaaaa",
business_key: "2020_0001",
date: "2020-01-01",
}
]
The business_key is in the old datasource the foreign key.
What i tried was to create a java entity class for order with a oneToMany items.
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "business_key")
private String businessKey;
#OneToMany(mappedBy = "order")
private List<Item> items;
}
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "business_key")
private String businessKey;
#ManyToOne
#JoinColumn(name = "order_id", referencedColumnName = "id")
private Order order;
}
And here is the part to insert the data in the database
private void processObjectMapping(String className, File file) throws IOException {
if (className.matches("^item.json$")) {
List<Item> list = mapper.readValue(file, new TypeReference<List<Item>>() {
});
itemRepository.saveAll(list);
} else if (className.matches("^order.json$")) {
Order order = mapper.readValue(file, new TypeReference<Order>() {
});
orderRepository.save(order);
}
Of course the JPA cant insert a value in the Foreign_key column all objects were created independently.
Question: is there a way to build a relationship between the entities in jpa with the existing business_key.
Or what i thought is to run after the import is done a process which set the releationships?
Could help me here a JPA Entity Graph?
Or maybe other recommendations?

Spring - Save entity after delete return EntityNotFoundException

I have many tables that belong to the same Project by ID. When I reload a Project with an existing ID, I need to clear all entities from the database.
Controller:
#CrossOrigin
#RequestMapping(value = "projects", method = RequestMethod.POST)
public ResponseEntity<?> uploadProject(MultipartFile file) {
JsonDataDto projectDto = converterService.convertToDto(file, JsonDataDto.class);
if(projectRepository.exists(projectDto.getId())) {
// Delete all project entities from DB
projectService.delete(projectDto.getId());
}
// Save project to DB
importService.import(projectDto);
}
Project Service (delete):
#Service
#Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
public class GenericProjectService implements ProjectService {
// Fields
#Override
public void delete(UUID projectId) {
entity1Repository.deleteByProjectId(projectId)
...
// Most entities are associated with a project by a foreign key.
// Some entities are not linked by a foreign key and are removed manually (entity1Repository for example)
projectRepository.delete(projectId);
}
}
Import Service (save):
#Service
public class GenericImportService implements ImportService {
// Fields
#Override
#Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
public void import(JsonDataDto projectDto) {
Collection<Entity1> entity1 = projectDto.getEntity1()
.stream().map(e -> e1Repository.save(e1Mapper.to(e))).collect(...);
Map<UUID, Type> types = new HashMap<>();
Map<UUID, TypeDto> typeDtosById = projectDto.getTypes().stream()
.collect(Collectors.toMap(TypeDto::getId, Function.identity()));
for (UUID typeId : typeDtosById.keySet()) {
saveType(typeId, typeDtosById, types, ...);
}
}
private void saveType(...) {
Type type = new Type();
// Set fields and relations
// Get exception here
type = typeRepository.save(type);
types.put(typeId, type);
}
}
Type Class:
#Entity
#Data
#Table(name = "...", schema = "...")
public class Type {
#Id
private TypePK typeId;
/*
#Data
#NoArgsConstructor
#AllArgsConstructor
#Embeddable
public class TypePK implements Serializable {
#Type(type = "pg-uuid")
#Column(name = "id")
private UUID id;
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "project_id", insertable = false, updatable = false)
private Project project;
}
*/
// Fields
#org.hibernate.annotations.Type(type = "pg-uuid")
#Column(name = "parent_id")
private UUID parentId;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "parent_id", referencedColumnName = "id", updatable = false, insertable = false),
#JoinColumn(name = "project_id", referencedColumnName = "project_id", updatable = false, insertable = false)})
private Type parent;
}
When the project does not exist in database, the save is successful. If I delete project from controller, it will also be successfully deleted from the database.
If project exists in database and I try to save it again, I get an error: "Unable to find package.Type with id TypePK(id=7e8281fe-77b8-475d-8ecd-c70522f5a403, project=Project(id=8d109d33-e15e-ca81-5f75-09e00a81a194))"
The entities are removed from the database, but the save transaction is rolled back.
I tried to force close the transaction after delete but it did not help:
public void delete(UUID projectId) {
TransactionStatus ts = TransactionAspectSupport.currentTransactionStatus();
entity1Repository.deleteByProjectId(projectId)
...
ts.flush();
}
The only way I found is, in fact, a crutch. I just wait a couple of seconds before starting save:
if(projectRepository.exists(projectDto.getId())) {
// Delete all project entities from DB
projectService.delete(projectDto.getId());
}
// Any timer
DateTime waitFor = DateTime.now().plusSeconds(2);
while(DateTime.now().isBefore(waitFor)) { }
// Save project to DB
importService.import(projectDto);
I managed to solve the problem using the suggestion in this comment: https://stackoverflow.com/a/14369708/10871976
I added an adapter to the delete transaction. On successful deletion in the "afterCompletion" method, I call project saving.

Spring Boot save nested Entity with JSON RequestBody

I'm trying to create a Rest API for a school project.Therefor I'm trying to save/edit a nested Object.
I have two bidirectional entities which look like this:
EntityA
#Entity
public class EntityA {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
#Column(name = "id", nullable = false)
#JsonProperty("id")
private int id;
#Column(name = "field1", nullable = false, length = -1)
#JsonProperty("field1")
private String field1;
#Column(name = "field2", nullable = false, length = -1)
#JsonProperty("field2")
private String field2;
#OneToMany(mappedBy = "entityA", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JsonProperty("entityB")
private List<EntityB> entityB;
public EntityA() {
}
//Getter+Setter
}
EntityB
#Entity
public class EntityB {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
#Column(name = "id", nullable = false)
#JsonProperty("id")
private int id;
#Column(name = "field1", nullable = false)
#JsonProperty("field1")
private Date field1;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(...)
#JsonProperty("entityA")
private EntityA entityA;
public EntityB() {
}
//Getter+Setter
}
As RequestBody I will get JSON which should look like this.
{
"field1": "Test",
"field2": "User",
"entityB": [
{
"field1": "30.03.2022"
}
]
}
Right now Spring will automatically map the fields but as soon I try to save it to my DB I will get an error, because the relation in EntityB for EntityA is empty.
I've seen a solution, that I should loop through the EntityB list and add EntityA. I tried it with a for-each but it still sais it null.
What am I doing wrong?
public EntityA createEntityA(EntityA entityA) {
for(EntityB entityB : entityA.getEntityB()){
entityB.setEntityA(entityA);
}
return entityARepository.save(entityA);
}
Edit:
Controller
#PostMapping(value = {"/json/entitya/"})
#ResponseBody
public EntityA createEntityAJson(#RequestBody EntityA entityA) {
return entityAService.createEntityA(entityA);
}
Service
#Service
public class EntityAService {
#Autowired
private EntityARepository entityARepository;
public EntityA createEntityA(EntityA entityA) {
return entityARepository.save(entityA); //in this line the error appears
}
}
Error message
null value in column "entityA" violates not-null constraint
#Service
public class EntityAService {
#Autowired
private EntityARepository entityARepository;
#Autowired
private EntityBRepository entityBRepository;
public EntityA createEntityA(EntityA entityA) {
// create an empty arrayList to stock the entities B retrieveed from the DB
List<EnityB> lst = new ArrayList<>();
// get the entities B from the JSON and sabe it to the DB
for(EntityB entityB : entityA.getEntityB()){
entityB.setEntityA(entityA);
entityBRepository.save(entityB); // you should save entities B to the DataBase before
Optional<EntityB > opt = entityBRepository.findById(entityB.getId());
EntityB b = opt.get();
// add the entities B retrieved from the DB to the arrayList
lst.add(b);
}
// set the EntityB list with the new List from the DB ( include ids ..)
entityA.setEntityB(lst);
// save the entityA to the DB
return entityARepository.save(entityA);
}
}
I'm guessing that what is happening here is that the id fields which are of a non-nullable datatype or some other hidden field from the JPA annotations get set to the wrong value by the json deserialization for JPA to understand that they are new entities. Creating these entities manually in the Java code might solve the issue.
You shouldn't reuse your entity classes as data transfer object for your API. Having classes containing both database-specific annotations and annotations for JSON serialization is a bad idea and it goes against the single-responsibility principle (SRP).
Create separate DTO classes for your API endpoint, then read the entities from the database an copy the values from the DTO object to the entities before saving.
// Receive DTO
// Read entity from DB if update or create new entities if insert
// Copy values from DTO to entitiy
// Save entity
I think your problems will go away if you apply this pattern.

Spring Boot JSON serialization with parent table

Good morning, I am trying to receive from spring boot with a ajax call a JSON with a series of fields containing daughter tables and also father.
I already had the problem of infinite recursion and such and fixed it with #JsonManagedReference and #JsonBackReference. What is happening now? I need to at least get to the data in the parent table to get a value but this one, when we get to the answer in Ajax it doesn't appear.
How could I do so that I could get to that data without falling into infinite recursion?
These would be the tables, I remove the fields so that it is not very long.
#Entity
#Table(name="actividad_servicio")
public class Actividad_Servicio implements Serializable {
#JsonBackReference
#ManyToOne
#JoinColumn(foreignKey = #ForeignKey(name = "fk_actividad_servicio_tipo_actividad_servicio_id"), name="tipo_actividad_servicio_id", nullable = false, columnDefinition = "int")
private Tipo_Actividad_Servicio tipo_actividad_servicio;
#JsonBackReference
#ManyToOne
#JoinColumn(foreignKey = #ForeignKey(name = "fk_actividad_servicio_empleado_id"), name="empleado_id",columnDefinition = "int")
private Empleado empleado;
#JsonManagedReference
#OneToMany(mappedBy = "actividad_servicio", cascade=CascadeType.ALL, orphanRemoval = true)
private Set<Actividad_Servicio_Alumno> actividad_servicio_alumno = new HashSet<Actividad_Servicio_Alumno>();
#JsonManagedReference
#OneToOne(mappedBy = "actividad_servicio", cascade=CascadeType.ALL, orphanRemoval = true)
private Horario_Actividad_Servicio horario_actividad_servicio = new Horario_Actividad_Servicio();
#JsonManagedReference
#OneToMany(mappedBy = "actividad_servicio", cascade=CascadeType.ALL, orphanRemoval = true)
private Set<Factura> factura = new HashSet<Factura>();
}
#Entity
#Table(name="horario_actividad_servicio")
public class Horario_Actividad_Servicio implements Serializable {
#JsonBackReference
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(foreignKey = #ForeignKey(name = "fk_horario_actividad_servicio_actividad_servicio_id"), name="actividad_servicio_id", referencedColumnName = "id", columnDefinition = "int")
private Actividad_Servicio actividad_servicio;
}
#Entity
#Table(name="tipo_actividad_servicio")
public class Tipo_Actividad_Servicio implements Serializable {
#JsonManagedReference
#OneToMany(mappedBy = "tipo_actividad_servicio", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Actividad_Servicio> actividad_servicio = new HashSet<Actividad_Servicio>();
}
Before returning the response back to Ajax, I look at the content of the service_activity object and it shows me the service_activity_type and its attributes.
#RestController
#RequestMapping("/actividades/rest")
public class ActividadRestController {
#Autowired
private Actividad_ServicioService actividad_servicioService;
#GetMapping
public Response findOne(#RequestParam int id, Model model) {
Response response = new Response();
try {
Actividad_Servicio actividad_servicio = actividad_servicioService.findById(id);
response = new Response("Done", actividad_servicio);
} catch (Exception e) {
response = new Response("Error", Constants.Error);
e.printStackTrace();
}
return response;
}
}
But when it comes back to Ajax I have inspected the content what I get is this:
Success: - {…}
​ - data: {…}
​​ + actividad_servicio_alumno: Array []
​​ + factura: Array []
​​ + horario_actividad_servicio: Object { id: 10, l: null, hora_inicio_l: "", …}
​​ <prototype>: Object { … }
​ status: "Done"
​ <prototype>: Object { … }
But the service_activity_type does not appear in order to obtain the value of some field. I know that this collides with the issue of infinite recursion but I suppose that there is something there is an annotation that happens to me that I have left to put so that I can obtain those parent values.
Thank you very much for your help.
First, don't use Entity class directly as response, it will create many problem when you want to ignore partially.
Create DTO class for the response to serialize only those fields you want to send. Map entity data in response using any model mapping tools like Modelmapper or MapStruct.

Hibernate and JSON - is there a definitive solution to circular dependencies?

I'm struggling with Hibernate entities and JSON in these days and, although there is a lot of questions regarding the object, I'm yet not capable to serialize in presence of circular dependencies. I tried with both Gson and jackson but I didn't get a lot of progresses.
Here is an excerpt from my objects.
This is the "parent" class.
#Entity
public class User extends RecognizedServerEntities implements java.io.Serializable
{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval = false)
#Cascade({CascadeType.SAVE_UPDATE})
private Set<Thread> threads = new HashSet<Thread>(0);
//...other attributes, getters and setters
}
and this is the "children" class
#Entity
#Table(name = "thread")
public class Thread extends RecognizedServerEntities implements java.io.Serializable
{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "author", nullable = true)
private User user;
//...other attributes, getters and setters
}
I've written a simple class to test both gson and jackson features; as said, they both raise an exception.
public class MyJsonsTest
{
private static User u;
public static void main(String[] args)
{
u = new User("mail", "password", "nickname", new Date());
u.setId(1); // Added with EDIT 1
// testGson();
testJackson();
}
private static void testJackson()
{
Thread t = new Thread("Test", u, new Date(), new Date());
t.setId(1); // Added with EDIT 1
u.getThreads().add(t);
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try
{
mapper.writeValue(new File("result.json"), u);
}
catch {/[various exceptions catched, but a JsonMappingException was thrown]}
}
private static void testGson()
{
Gson gson = new Gson();
System.out.println(u.toString());
System.out.println(gson.toJson(u, User.class));
Thread t = new Thread("Test", u, new Date(), new Date());
u.getThreads().add(t);
//This raise an exception overflow
System.out.println(gson.toJson(u, User.class));
}
}
To solve the problem, on jackson side, I tried to use this annotation
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
on both User and Thread class. However, it doesn't solve the problem.
On gson side, I read about the GraphAdapterBuilder class, but I wasn't able to properly use it. I don't find any jar, so I copy/pasted the source code from here. However, there is a compile time error at this line
private final ConstructorConstructor constructorConstructor = new ConstructorConstructor();
because the ConstructorConstructor() is undefined; the right syntax should be
ConstructorConstructor(Map<Type>, InstanceCreator<?> instanceCreators)
So, is there a definitive solution to this problem? Obviously, I can't use transient variables.
EDIT 1
I finally found the issue with jackson. In the test class, I forgot to initialize the id field (in real scenarios it is initialized by the database) and this is the reason of the exception. When I finally set the id, all works. This is the output
{
"id" : 1,
"email" : "mail",
"password" : "password",
"nick" : "nickname",
"registeredDate" : 1414703168409,
"threads" : [ {
"id" : 1,
"thread" : null,
"user" : 1,
"title" : "Test",
"lastModifiedDate" : 1414703168410,
"createdDate" : 1414703168410,
"messages" : [ ],
"threads" : [ ]
} ],
"messages" : [ ]
}
When dealing with circular dependencies you need to build a parent-children JSON hierarchy, because the marshalling must be cascaded from root to the inner-most child.
For bi-directional associations, when the Parent has a one-to-many children collection and the child has a many-to-one reference to Parent, you need to annotate the many-to-one side with #JsonIgnore:
#Entity
#Table(name = "thread")
public class Thread extends RecognizedServerEntities implements java.io.Serializable
{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#org.codehaus.jackson.annotate.JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "author", nullable = true)
private User user;
//...other attributes, getters and setters
}
This way you will no longer have a Json serializing-time circular dependency.
Jackson
As said, I was able to solve the problem using
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id", scope=MyEntity.class)`
for each entity as suggested here.
The scope attribute was necessary to make sure that the name "id" is unique within the scope. Actually, without the scope attribute, as you can see here, it throws an exception saying
com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id java.lang.String) [com.fasterxml.jackson.annotation.ObjectIdGenerator$IdKey#3372bb3f] (through reference chain: ParentEntity["children"]->java.util.ArrayList[0]->ChildEntity["id"])
...stacktrace...
Caused by: java.lang.IllegalStateException: Already had POJO for id (java.lang.String) [com.fasterxml.jackson.annotation.ObjectIdGenerator$IdKey#3372bb3f]
...stacktrace...
Gson
I still haven't found a clean way to serialize circular dependencies.
I have done this using org.codehaus.jackson.annotate.JsonManagedReference and org.codehaus.jackson.annotate.JsonBackReference in this way...
look at how i used #JsonManagedReference
#Id
#TableGenerator(name="CatId", table="catTable",pkColumnName="catKey",pkColumnValue="catValue", allocationSize=1)
#GeneratedValue(strategy=GenerationType.TABLE, generator="CatId")
#Column(name = "CategId", unique = true, nullable = false)
private long categoryId;
private String productCategory;
#JsonManagedReference("product-category")
#OneToMany(targetEntity=ProductDatabase.class,mappedBy="category", cascade=CascadeType.ALL, fetch=FetchType.EAGER)
private List<ProductDatabase> catProducts;
and then at the other end i used #JsonBackReference as shown below.
#Id#GeneratedValue
private int productId;
private String description;
private int productPrice;
private String productName;
private String ProductImageURL;
#JsonBackReference("product-category")
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "CategId")
private Category category;
just apply these annotations and check if it works for you.
Its not good design to serialize Hibernate POJO to client. As you may send some data to client location, which he is not authorize to view. You should create client POJO and copy data from hibernate POJO to client POJO, which you want to send to client. If you don't want to do that, you can use #JsonIgnore or Fetch all data eagerly.

Categories

Resources