set a relationship in jpa - java

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?

Related

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.

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

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

List Results Mapping Spring JPA

I use PostgreSQL and I have these tables, product and product_media with relation OneToMany on product with product_media. I want to retrieve a list with product which each of them contains a list of product_media.
And I have two options in my mind in order to retrieve them from DB.
First solution is initially retrieve the list of product and then iterate the retrieved list and execute query in order to retrieve the list of product_media.
Query1:
select * from product as p where p.status=1;
Retrieve List and then iterate this list and execute this query:
select * from product_media as pm where pm.product_id=?
Second is to implement join in query and retrieve all data from my DB.
Query:
select * from product as p Join product_media as pm on (p.id=pm.product_id)
Retrieve a complex list with all data.
The problem of second option is to do not know an elegant way to map this list into an object which has the format below. Do you know how can map automatically the results into this format?
product:[
{
id:1,
name:'Pro1',
medias:[
{
id:1,
uuid:'asdfi-asdf-rg-fgsdf-do'
},
{
id:2,
uuid:'asdfi-asdf-rg-fgsdf-do'
}
]
},
{
id:2,
name:'Pro2',
medias:[
{
id:5,
uuid:'asdfi-asdf-rg-fgsdf-do'
},
{
id:7,
uuid:'asdfi-asdf-rg-fgsdf-do'
}
]
}
]
I think the second variant is the better option. After fetching the object tree from the database you can do something like the following to achieve what you are posted above:
Assuming your entities are defined as follows:
Product.java
public class Product {
private long id;
private String name;
private List<ProductMedia> mediaList;
public Product() {
mediaList = new ArrayList<ProductMedia>();
}
public Product(long id, String name) {
this.id = id;
this.name = name;
mediaList = new ArrayList<ProductMedia>();
}
// getters + setters
}
ProductMedia.java
public class ProductMedia {
private long id;
private String uuid;
public ProductMedia() { }
public ProductMedia(long id, String uuid) {
this.uuid = uuid;
}
// getters + setters
}
Using the Jackson library you can generate output as follows:
public class JsonTest {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
Product prod = new Product(1, "p1");
ProductMedia pm = new ProductMedia(1, "uuid1");
ProductMedia pm2 = new ProductMedia(2, "uuid2");
prod.getMediaList().add(pm);
prod.getMediaList().add(pm2);
Product prod1 = new Product(2, "p2");
ProductMedia pm3 = new ProductMedia(3, "uuid3");
ProductMedia pm4 = new ProductMedia(4, "uuid4");
prod1.getMediaList().add(pm3);
prod1.getMediaList().add(pm4);
Product[] pList = {prod, prod1};
mapper.writeValue(System.out, pList);
}
}
In this example, I am writing the output onto the console. But you are not restricted to it; you can write to a file passing in a FileOutputStream.
To be able to run this example you need to add the dependency; if you use Maven you can add the following into your POM:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.4</version>
</dependency>
Otherwise add the jar of the dependency into your project build path.
If your response is not in json format you can try below
There is a many-to-many relationship between Product and Media.
Product_Media is a helper table to maintain many-to-many relationship between Product and Media entities.
Product entity:
#Entity(name = "product")
public class Product {
#Id
#GeneratedValue
private Long product_id;
#Column
private String name;
#ManyToMany(cascade = { CascadeType.MERGE }, fetch = FetchType.EAGER)
#JoinTable(name = "product_media", joinColumns = {
#JoinColumn(name = "product_id", table = "product") }, inverseJoinColumns = {
#JoinColumn(name = "media_id", table = "media") })
List<Media> medias;
}
Media entity
#Entity(name = "media")
public class Media {
#Id
#GeneratedValue
private Long media_id;
#Column
private String name;
}
SQL generated by Hibernate
select
product0_.product_id as product_1_1_0_,
product0_.name as name2_1_0_,
medias1_.product_id as product_1_1_1_,
media2_.media_id as media_id2_2_1_,
media2_.media_id as media_id1_0_2_,
media2_.name as name2_0_2_
from
product product0_
left outer join
product_media medias1_
on product0_.product_id=medias1_.product_id
left outer join
media media2_
on medias1_.media_id=media2_.media_id
where
product0_.product_id=?
If the relationship is one-to-many, change entities like below
Media Entity
#Entity(name = "media")
public class Media {
#Id
#GeneratedValue
private Long id;
#Column
private String name;
#ManyToOne
#JoinColumn(name = "product_id", referencedColumnName = "id", nullable = false, updatable = false)
private Product product;
public Media() {
}
}
Product Entity
#Entity(name = "product")
public class Product {
#Id
#GeneratedValue
private Long id;
#Column
private String name;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "product")
List<Media> medias;
}
Hibernate generated SQL
select
product0_.id as id1_2_0_,
product0_.name as name2_2_0_,
medias1_.product_id as product_3_2_1_,
medias1_.id as id1_0_1_,
medias1_.id as id1_0_2_,
medias1_.name as name2_0_2_,
medias1_.product_id as product_3_0_2_
from
product product0_
left outer join
media medias1_
on product0_.id=medias1_.product_id
where
product0_.id=?

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.

Hibernate #Any annotation usage

I have one entity called Change where I need log changes in database like inserting, updating or deleting rows.
So my Change table contains some data and now I would like to add foreign key to record changes in another table, but I have different tables. For example I have Weather table, Group table,... So I have done some searching and I have found a little bit about #Any annotation. So I added some columns to my Change entity:
#Entity
#Table(name = "CHANGE")
public class Change {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "CHANGE_ID")
private int changeId;
...
#Any(metaColumn = #Column(name = "RECORD_TABLE"))
#AnyMetaDef(idType = "int", metaType = "string",
metaValues = {
#MetaValue(targetEntity = Weather.class, value = "WEATHER"),
#MetaValue(targetEntity = Group.class, value = "GROUP"),
...
})
#JoinColumn(name="recordID")
private Object record;
#ManyToOne
#JoinColumn(name = "USER_ID")
private User user;
public Object getRecord() {
return record;
}
public void setRecord(Object record) {
this.record = record;
}
...
And my stupid question is:
How can I insert data into database (like foreign ID and class name) and how could I retrieve them?
Please go through this Link
You should care about your entity relationship (1-1 or 1-M or M-M)

Categories

Resources