manyToMany does not retrieve all mapped data [duplicate] - java

This question already has an answer here:
the manytomany does not retrieve the mapped data
(1 answer)
Closed 3 months ago.
Spring boot 2.5.6 (I can't mount a version)
(1) LAZY
Profil.java
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
#FieldDefaults(level = AccessLevel.PRIVATE)
#Table(name = "t_profil")
public class Profil {
...
...
#ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
}, fetch = FetchType.LAZY)
#JoinTable(
name = "t_profils_fonctionnalites",
joinColumns = { #JoinColumn(name = "profil_id") },
inverseJoinColumns = { #JoinColumn(name = "fonctionnalite_id") }
)
public Set<Fonctionnalite> fonctionnalites = new HashSet();
}
Fonctionnalite.java
#jakarta.persistence.Entity
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
#FieldDefaults(level = AccessLevel.PRIVATE)
#Table(name = "t_fonctionnalite")
public class Fonctionnalite {
...
...
#ManyToMany(mappedBy = "fonctionnalites", fetch = FetchType.LAZY)
private Set<Profil> profils = new HashSet();
I launch the project, the intermediate table is created: "t_profils_fonctionnalites"
I insert data into this table :
profil fonctionnalite
1 1
1 2
2 1
2 4
controller
...
...
List<Profil> profilList= profilDao.findAll();
profilList.forEach(profil -> { profil.getFonctionnalites(); });
return new ResponseEntity<>(profilList, HttpStatus.OK);
I get this data with postman :
[
{
"id": 1,
"code": "CODEP1",
"label": "defaut",
"description": "defaut description1",
"fonctionnalites": [] <--------- no fonctionnalites data
},
{
"id": 2,
"code": "CODEP2",
"label": "label2",
"description": "description2",
"fonctionnalites": [
{
"id": 1,
"code": "codeF1",
"label": "labelF1",
"description": "descriptionF1",
"profils": []
},
{
"id": 4,
"code": "codeF4",
"label": "labelF4",
"description": "descriptionF4",
"profils": []
}
]
},
"fonctionnalites for profil id = 1 is empty ! why ?
(2)
I put the fetch in EAGER on Profil :
Profil.java
#ManyToMany(cascade = {
CascadeType.ALL,
}, fetch = FetchType.EAGER)
#JoinTable(
name = "t_profil_fonctionnalite",
joinColumns = { #JoinColumn(name = "profil_id") },
inverseJoinColumns = { #JoinColumn(name = "fonctionnalite_id") }
)
public Set<Fonctionnalite> fonctionnalites = new HashSet();
This time I get different data, some profiles have features. sometimes just one when there are 2.
I do not understand what is going on. Thank you for your help

Why this code snippet is needed? profilList.forEach(profil -> { profil.getFonctionnalites(); });
Can you try this:
...
...
List<Profil> profilList= profilDao.findAll();
return new ResponseEntity<>(profilList, HttpStatus.OK);

Related

Unable to find column with logical name - hibernate exception when using #OneToMany and #JoinColumn

I have got two entities in my application Orders and Products (one-to-many association). I am aiming to set up a unidirectional OneToMany relationship in Order entity (I'm aware that it is not the best solution, however this is a business requirement). The database schema is generated by Liquibase and validated by Hibernate. Entities have been simplified for the sake of clarity. Database is Postgres.
Although the schema is created correctly, Hibernate throws an exception:
Caused by: org.hibernate.cfg.RecoverableException: Unable to find column with logical name: productId in org.hibernate.mapping.Table(orders) and its related supertables and secondary tables
at org.hibernate.cfg.Ejb3JoinColumn.checkReferencedColumnsType(Ejb3JoinColumn.java:844) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:126) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.cfg.annotations.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:1740) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
... 37 common frames omitted
Caused by: org.hibernate.MappingException: Unable to find column with logical name: productId in org.hibernate.mapping.Table(orders) and its related supertables and secondary tables
at org.hibernate.cfg.Ejb3JoinColumn.checkReferencedColumnsType(Ejb3JoinColumn.java:839) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
... 39 common frames omitted
To prove that the schema is generated appropriately, I replaced #OneToMany annotation with #ManyToOne and it works fine! All of the sudden, Hibernate is able to find the column. After that I began assuming that there is some kind of bug in Hibernate...
Does anyone have an idea, how to solve this issue?
My code looks as follows:
Order.java
#Entity
#Table(name = "orders")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long orderId;
#Column
private String clientName;
#OneToMany
#JoinColumn(name = "productIdFK", referencedColumnName = "productId")
private List<Product> productList = new ArrayList<>();
}
Product.java
#Entity
#Table(name = "products")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long productId;
#Column
private String name;
}
Liquibase script
{
"databaseChangeLog": [
{
"changeSet": {
"id": "Create PRODUCT table",
"author": "me",
"changes": [
{
"createTable": {
"tableName": "products",
"columns": [
{
"column": {
"name": "productId",
"type": "bigint",
"constraints": {
"nullable": false,
"unique": true,
"primaryKey": true
}
}
},
{
"column": {
"name": "name",
"type": "varchar(250)",
"constraints": {
"nullable": true,
"unique": false
}
}
}
]
}
},
{
"createTable": {
"tableName": "orders",
"columns": [
{
"column": {
"name": "orderId",
"type": "bigint",
"constraints": {
"nullable": false,
"unique": true,
"primaryKey": true
}
}
},
{
"column": {
"name": "clientName",
"type": "varchar(250)",
"constraints": {
"nullable": true,
"unique": false
}
}
},
{
"column": {
"name": "productIdFK",
"type": "bigint",
"constraints": {
"nullable": true,
"unique": false
}
}
}
]
}
}
]
}
}
]
}
Snippet from liquibase script generating relationships
{
"addForeignKeyConstraint": {
"constraintName": "fk_product_order",
"baseColumnNames": "productIdFK",
"baseTableName": "orders",
"referencedColumnNames": "productId",
"referencedTableName": "products"
}
}
#OneToMany
#JoinColumn(name = "orderId", referencedColumnName = "productId")// try orderId
private List<Product> productList = new ArrayList<>();
Once you try this
hope this will work out
change to this in Order.java
#OneToMany(cascade = CascadeType.ALL, mappedBy= "order")
private List<Product> productList = new ArrayList<>();
add this in Product.java
#ManyToOne
#JsonIgnore
private Order order;
let me know
You should try this too.
#OneToMany(cascade = CascadeType.ALL, mappedBy = "order")
private List<Product> productList = new ArrayList<>();
along with this in Product.java
#ManyToOne
#JoinColumn(name = "productId",
foreignKey = #ForeignKey(
name = "product_id_fk"))
private Order order;
First, let's clarify what 1 is and what many are at your schema. As Order has a reference to Product that means many Orders might be related to the same Product, mightn't they? A bidirectional relationship is usually preferred but you need the unidirectional one, it should be something like
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long orderId;
#Column
private String clientName;
}
and
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long productId;
#Column
private String name;
#OneToMany
#JoinColumn(name = "productIdFK", referencedColumnName = "productId")
private List<Order> orderList = new ArrayList<>();
}

How to use JsonIgnore in Spring Boot to stop infinite loop? [duplicate]

This question already has answers here:
Infinite Recursion with Jackson JSON and Hibernate JPA issue
(29 answers)
Closed 1 year ago.
I have a many to many relationship in my project between User and a Product. A User can have many products and a Product can be owned by many Users.
User.java
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable( name = "user_product",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "product_id"))
private List<Product> listOfProductsForUser;
Product.java
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "listOfProductsForUser")
private List<User> listOfUsersForProduct;
In my controller, I have a path that gets me all the users and the nested products associated with the user like this.
[
{
"userId": 1,
"userName": "Fido",
"age": 3,
"listOfProductsForUser": [
{
"productId": 1,
"productName": "tooth brush",
},
{
"productId": 2,
"productName": "potatoes",
},
{
"productId": 3,
"productName": "carrot",
}
]
},
{
"userId": 2,
"userName": "David",
"age": 12,
"listOfProductsForUser": [
{
"productId": 6,
"productName": "oranges",
},
{
"productId": 7,
"productName": "tomatoes",
},
{
"productId": 6,
"productName": "buns",
}
]
}
]
This is fine but I now want to get all products which should have nested users (to get all products and see which users are using them).
While attempting to do this I run into a recursive problem and I have used JsonIgnore on the product.java's listOfProductsForUser variable as shown above to stop the recursion. How can I accomplish this?
There are couple of ways to deal with this bi-directional relationship issue. I think #JsonIdentityInfo is the best way in your case.
What you need to do is change your User and Product class as follows.
//other annotations
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "userId")
public class User {
// your code
}
//other annotations
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "productId")
public class Product {
// your code
}
Note: For more info here
Try the following:
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable( name = "user_product",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "product_id"))
#JsonManagedReference
private List<Product> listOfProductsForUser;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "listOfProductsForUser")
#JsonBackReference
private List<User> listOfUsersForProduct;
These two annotations (#JsonManagedReference and #JsonBackReference) avoid infinite recursion.
I misunderstood the question. Both listOfUsersForProduct and listOfProductsForUsershould be present in the JSON. The previous solution omits listOfUsersForProduct. To have both you need to use #JsonIdentityInfo as #ray already suggested:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "userId")
public class User { }
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "productId")
public class Product { }

many to many spring jpa not returning all data in JSON

I have created a many to many relationship. It is returning the data correctly. However, I noticed that if the data exist in canUse section of the json and has been called before by another Player, it does not print the data again only the new values are printed.
JOSN
[
{
"id": 12,
"firstname": "asdfghjkl ",
"surname": "asda",
"canUse": [
{
"id": 0,
"techniqueName": "JAVA"
},
{
"id": 1,
"techniqueName": "PHP"
},
{
"id": 2,
"techniqueName": "PYTHON"
}
]
},
{
"id": 49,
"firstname": "asc",
"surname": "as",
"canUse": []
},
{
"id": 90,
"firstname": "LOL",
"surname": "LOL",
"canUse": []
}*,
{
"id": 91,
"firstname": "Kareem",
"surname": "LOL",
"canUse": [
1,
2,
{
"id": 3,
"techniqueName": "HASKELL"
}
]
},*
{
"id": 92,
"firstname": "Omar",
"surname": "LOL",
"canUse": []
}
]
Player entity
#Entity
#Table(name = "players")
#EntityListeners(AuditingEntityListener.class)
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Players {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="player_id")
private int id;
private String firstname;
private String surname;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name = "able_to_use",
joinColumns = {#JoinColumn(name = "player_id", referencedColumnName = "player_id")},
inverseJoinColumns = {#JoinColumn(name = "technique_id", referencedColumnName = "id")}
)
private List<Technique> canUse;
//Getters and Setters
Technique entity
#Entity
#Table(name = "technique")
#EntityListeners(AuditingEntityListener.class)
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Technique {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "technique_name")
private String techniqueName;
#JsonIgnore
#ManyToMany(mappedBy = "canUse", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Players> ableToUse;
//Getters and Setters
Thank you in advance, the problem is occurring for player with ID 91. this player should also print PHP and PYTHON but only the ID is printed.

Parent id is not saved in child table Spring JPA2 #OneToMany relation

I have json like as shown below which comes from a third party service which I gets it through open feign client. I am trying to save these details into my MySQL database using Spring JPA2.
[
{
"request_id": 111,
"name": "ABC",
"groups": [
{
"id": 21,
"group": "grp_A"
},
{
"id": 22,
"group": "grp_B"
}
]
},
{
"request_id": 222,
"name": "ABC",
"groups": [
{
"id": 23,
"group": "grp_C"
}
]
},
{
"request_id": 333,
"name": "ABC",
"groups": [
{
"id": 24,
"group": "grp_A"
}
]
},
{
"request_id": 444,
"name": "ABC",
"groups": [
{
"id": 25,
"group": "grp_C"
}
]
},
{
"request_id": 555,
"name": "ABC",
"groups": []
}
]
I have wrote the mappings for the above json like as shown blow
Requests.java
#Entity
#Table(name = "requests")
#JsonIgnoreProperties(ignoreUnknown = true)
public class Requests {
#Id
#Column(name = "request_id")
private String id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "id", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Groups> groups = Sets.newHashSet();
// getters and setters
}
Groups.java
#Entity
#Table(name = "groups")
#JsonIgnoreProperties(ignoreUnknown = true)
public class Groups{
#Id
#Column(name = "id")
private String id;
#Column(name = "group")
private String group;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "request_id")
private Requests requests;
// getters and setters
}
now within my service class I have done like as shown below
List<Requests> allRequests = requestFeignClient.getRequests();
requestRepository.saveAll(allRequests);
all details are saved into the database except the request_id like as shown below. My Expected group table is also shown below
Can anyone please tell me why the request_id is not getting saved.
Try replacing this:
#OneToMany(mappedBy = "id", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
With this:
#OneToMany(mappedBy = "requests", cascade = CascadeType.ALL, fetch = FetchType.LAZY)

Hibernate mapping list inside list

I have 2 tables email_address and group_email
I want to get a list of email addresses and if it is group also list the emails of group. Here is my hibernate dom object:
#Entity
#Table(name = "email_address")
public class EmailAddressDom {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "email_address_id_seq")
#SequenceGenerator(name = "email_address_id_seq", sequenceName = "email_address_id_seq")
private int id;
#Column(name = "name")
private String name;
#Column(name = "email")
private String email;
#Column(name = "is_group")
private boolean isGroup;
#Column(name = "status")
private boolean status;
// Getters and setters
And here is the implementation how I am getting the list
public List<EmailAddressDom> getEmailAddresses() {
Session session = getCurrentSession();
Criteria criteria = session.createCriteria(EmailAddressDom.class);
List<EmailAddressDom> emailAddresses = criteria.list();
return emailAddresses;
}
And the result is
[
{
"id": 451,
"name": "HS",
"isGroup": true,
"status": true
},
{
"id": 452,
"name": "test4",
"email": "test4#mail.com",
"isGroup": false,
"status": true
},
{
"id": 450,
"name": "test3",
"email": "test3#mail.com",
"isGroup": false,
"status": true
},
{
"id": 402,
"name": "test2",
"email": "test2#mail.com",
"isGroup": false,
"status": true
},
{
"id": 401,
"name": "test1",
"email": "test1#mail.com",
"isGroup": false,
"status": true
}
]
I want to get result like this
[
{
"id":451,
"name":"HS",
"isGroup":true,
"status":true,
"groupEmails":[
{
"id":450,
"name":"test3",
"email":"test3#mail.com",
"isGroup":false,
"status":true
},
{
"id":452,
"name":"test4",
"email":"test4#mail.com",
"isGroup":false,
"status":true
}
]
},
{
"id":402,
"name":"test2",
"email":"test2#mail.com",
"isGroup":false,
"status":true
}
]
Which mapping should I use in EmailAddressDom entity?
Updated accordingly to comments
I have written a simple application mapping following way and it is working fine
#ManyToMany(fetch= FetchType.EAGER, cascade={CascadeType.ALL})
#JoinTable(name="group_email",
joinColumns={#JoinColumn(name="group_id")},
inverseJoinColumns={#JoinColumn(name="email_addr_id")})
private List<EmailAddressDom> groupEmails;
But I have confused setting it in my web application, it throws following exception:
Association has already been visited: AssociationKey(table=group_email, columns={email_addr_id})
What can be the reason?
Either Do Eager loading
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(name = "group_email",
joinColumns = #JoinColumn(name = "email_addr_id", referencedColumnName = "id"),
inverseJoinColumns=#JoinColumn(name="group_id", referencedColumnName="id" ))
private List<EmailAddressDom> emailAddressDoms;
OR
//explicitly join in criteria
public List<EmailAddressDom> getEmailAddresses() {
Session session = getCurrentSession();
Criteria criteria = session.createCriteria(EmailAddressDom.class);
criteria.setFetchMode("emailAddressDoms", fetchMode.JOIN);
List<EmailAddressDom> emailAddresses = criteria.list();
return emailAddresses;
}
To achieve self join, in your EmailAddressDom class add reference to itself like:
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(name = "group_email",
joinColumns = #JoinColumn(name = "email_addr_id", referencedColumnName = "id"),
inverseJoinColumns=#JoinColumn(name="group_id", referencedColumnName="id" ))
private List<EmailAddressDom> emailAddressDoms;
In group_email table you do not require id column as email_addr_id and group_id combination would be unique or do you allow same email more than once in a group?
Update after comment from OP:
To group by email groups, change the mapping like shown below.
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "group_email",
joinColumns = #JoinColumn(name="group_id", referencedColumnName="id" ),
inverseJoinColumns=#JoinColumn(name = "email_addr_id", referencedColumnName = "id"))
private List<EmailAddressDom> emailAddressDoms;

Categories