JPA #OneToMany is not working when result is list - java

I'm working with JPA, Spring boot.
Using #OneToMany annotation, when I fetch orders containing cart items.
My domain codes are below.
Order:
#Data
#Entity
#Table(name="\"order\"")
#ToString
public class Order {
#Id
#GeneratedValue
#Getter #Setter
private Long id;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name="order_id")
#Getter #Setter
private List<Cart> carts;
public void addCart(Cart cart) {
if (this.carts == null) {
carts = new ArrayList<Cart>();
}
carts.add(cart);
}
}
Cart:
#Data
#Entity
#Table(name="cart")
#ToString
public class Cart {
#Id
#GeneratedValue
#Getter #Setter
#Column(name="id")
private Long id;
#Column(name = "order_id")
#Getter #Setter
private Long orderId;
}
This works very well when I fetch only one order, but doesn't work when I fetch more than two orders. I mean when I fetch only one order, carts field's size is 1 or more, but when I fetch two or more orders, the carts field's size is 0.
How can I solve this problem? Thank you in advance!

It's nearly impossible to find out what the fetching problem is without seeing the code which is loading / querying your entities. So could you please add it to your question?
Meanwhile there are at least some things you could improve to have cleaner entities and faster code, maybe this also can help a little to hunt down the problem.
At first, you are using redundant configurations, respective annotations you are recreating the default values with.
I assume that is lombok what you are using, right? You could remove the #Getter and #Setter annotations on your fields in both entities and add it once on class level to avoid the declaration on every single field, but since you are using #Data you don't need it at all. Same with #toString, #Data is a convenience annotations for all of it (and a little more).
The JavaDoc of #Data says:
Equivalent to {#code #Getter #Setter #RequiredArgsConstructor #ToString #EqualsAndHashCode}.
Then, although the #Table(name="\"order\"") on the order entity is needed because order is a reserved word in some DBMS, the #Table(name="cart") on the cart entity is the default.
Up next, I would not recommend lazy initialization of collections, because in general there is no benefit to do that compared to the penalty it causes while checking for null before every access. Just initialize it within declaration and you will never have to care about handling null again. Beside of that you should think about using a Set instead a List for the carts (so you will have no duplicates).
Also think about the FetchType because EAGER is only useful if you work with detached entities and want to initialize the relation in every case. LAZY is the default for #OneToMany and should be preferred.
A thing you already improved is the #JoinColumn, that will prevent Hibernate (I brazenly assume you are using Hibernate) creating a join table. But even more important is thinking about turning the #OneToMany into a #ManyToOne on the other side or making it bidirectional. That would gain some performance on reading (it also prevents the join table so less joins are needed, faster indexing is possible) and writing time (relation managed by parent side).
So what do you think about this:
#Data
#Entity
#Table(name="\"order\"")
public class Order {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Cart> carts = new HashSet<>();
}
and this:
#Data
#Entity
public class Cart {
#Id
#GeneratedValue
private Long id;
#ManyToOne
private Order order;
}

Order class:
public class Order {
#Id
#GeneratedValue
#Getter #Setter
private Long id;
#OneToMany(mappedBy="orderId" fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#Getter #Setter
private Set<Cart> carts;
}
Cart class:
public class Cart {
#Id
#GeneratedValue
#Getter #Setter
#Column(name="id")
private Long id;
#ManyToOne
#Getter #Setter
private Order orderId;
}
For example: JPA OneToMany and ManyToOne Relationships
I just wrote it to change.
I hope that it will work

Related

Why does the mappedBy parameter cause infinite recursion in spring boot?

These are two entities, each with more fields but these two are causing StackOverflow. When I'm using only #JsonMannagedReference and #JsonBackReference and not using mappedBy, infinite recursion doesn't exist. Still, in that case, I'm unable to create bidirectional relation, when I add a mappedBy parameter it causes a StackOverflow error.
#Entity
#Getter
#Setter
#ToString
#NoArgsConstructor
#AllArgsConstructor
public class Business {
#OneToMany(targetEntity = ProductCategory.class
, cascade = CascadeType.ALL
, fetch = FetchType.LAZY
, mappedBy = "business"
)
#JsonManagedReference
private List<ProductCategory> productCategories;
}
#Entity
#Getter
#Setter
#ToString
#NoArgsConstructor
#AllArgsConstructor
public class ProductCategory {
#ManyToOne(targetEntity = Business.class
, cascade = CascadeType.ALL
, fetch = FetchType.LAZY)
#JoinColumn(name = "business_id")
#JsonBackReference
private Business business;
}
We have a very similar setup and it works. I see two differences.
First, we do not set targetEntity, JPA should be able to figure that out based on field types.
Second, we excluded "business" fields from toString and equalsAndHashCode generated for ProductCategory class.
Try adding annotations
#ToString(exclude = {"business"})
#EqualsAndHashCode(exclude = {"business"})
to your ProductCategory class.
That should exclude cyclic calls in toString and equals/hashcode methods and take away your unwanted infinite recursion.

Automatically persisting reverse relationship in JPA

I have an entity User, that can have exactly one Company. I have a Company, that can be assigned to multiple User objects.
Currently if I want to persist a User, I need to get the Company (as it may exist without any User being assigned to it) and assign it. Further more I have to add the User manually to the Company using Company#addUser. Afterwards I save run CompanyRepository.save(company) (which should suffice to persist the User, too, I think, because I am using cascade = CascadeType.PERSIST).
Is there a way to say, that if I get the User and assign a Company to it, the "back-reference" is dealt with automatically? Or do I always have to get the Company and use Company#addUser to add that reference?
My entities look like this (I omitted more properties and reduced it to the most important properties and methods):
Company.java
package com.portal.user.persistence;
(imports omitted)
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder (toBuilder = true)
#Entity
#Table (name = "companies")
public class Company {
#Id
#GeneratedValue (generator = "uuid")
#GenericGenerator (name = "uuid", strategy = "uuid2")
#Column (name = "id")
private String id;
#Column (name = "ucid")
private String ucid;
#OneToMany (fetch = FetchType.LAZY, mappedBy = "company", cascade = CascadeType.PERSIST)
private List<User> users;
public void addUser(#NonNull User user) {
if (users == null) {
users = new ArrayList<>();
}
users.add(user);
}
public void removeUser(#NonNull User user) {
users.remove(user);
}
}
User.java
package com.portal.user.persistence;
(imports omitted)
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder (toBuilder = true)
#Entity
#Table (name = "users")
public class User {
#Id
#GeneratedValue (generator = "uuid")
#GenericGenerator (name = "uuid", strategy = "uuid2")
#Column (name = "id")
private String id;
#ManyToOne (cascade = CascadeType.PERSIST)
private Company company;
}
There are a lot of answers to your question, based on the implementation you would like to achieve.
The first way is to remove the #OneToMany relation in Company and the user list. In this way you would only have to manage one side of the relation, and when you need to search for all users in a company you could use a custom query performing a left join on users and companies tables.
The second way, keeping both side of the relation, is to implement a method 'setCompany' inside the User class like the following:
public void setCompany(Company c){
c.addUser(this);
this.company = c;
}
However in my experience, the first solution fits better since less relations will lead to a lot less work to do later on, especially regarding DTO conversion and deletion of elements from the DB.

java.sql.SQLException: Table 'myTable.owner' doesn't exist

I am using JPA to create and access a database. My database consists of two tables? These two tables have a #OneToMany relationship. However, when I run my application, I am getting a java.sql.SQLException.
I have done some research but none of the solutions online seem to work for me. The first solution suggests to replace #GeneratedValue(strategy=GenerationType.AUTO) with #GeneratedValue(strategy=GenerationType.IDENTITY)
The second solution consists of Including spring.jpa.hibernate.use-new-id-generator-mappings=false into application.properties.
None of these solutions worked.
Car.java
#Entity
public class Car {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
....
#ManyToOne(fetch = FetchType.LAZY )
#JoinColumn(name = "owner")
private Owner owner;
....
}
Owner.java
#Entity
public class Owner {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long ownerid;
....
#OneToMany(cascade =CascadeType.ALL, mappedBy="owner")
private List<Car> cars;
....
}
application.properties
logging.level.root=INFO
server.port=8080
spring.jpa.show-sql=true
spring.datasource.url=jdbc:mariadb://localhost:3306/cardb
spring.datasource.username=****
spring.datasource.password=*******
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.jpa.generate-dll=true
spring.jpa.hibernate.dll-auto=create-drop
Here is the Exception I am getting :
java.sql.SQLException: Table 'cardb.owner' doesn't exist
EDIT: I am using MariaDB and the database's name is cardb. So, when I run the application, it should create cardb.car, cardb.owner and cardb.car_owner
By the way, the database is properly created when using H2. Does it mean the problem comes from MariaDB ?
#JoinColumn(name = "owner")
should be
#JoinColumn(name = "ownerid")
I think you are missing #Table in your entity classes.
Define Car as,
#Entity
#Table (name="car")
public class Car {
...
}
and
Owner as below,
#Entity
#Table (name = "owner")
public class Owner {
...
}

Spring Boot JPA - OneToMany relationship causes infinite loop

I have a two objects with simple #OneToMany relationship which looks as follows:
parent:
#Entity
public class ParentAccount {
#Id
#GeneratedValue
private long id;
private String name;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "parentAccount")
private Set<LinkedAccount> linkedAccounts;
}
child:
#Entity
public class LinkedAccount {
#Id
#GeneratedValue
private long id;
#ManyToOne(optional = false)
private ParentAccount parentAccount;
private String name;
// empty constructor for JPA
public LinkedAccount() {
}
}
I ma using Spring CrudRepository to operate with these entities. However, when calling ParentAccount parent = parentAccountRepository.findOne(id);, some kind of infinite loop starts happening and hibernate spams this all over the console:
Hibernate: select linkedacco0_.parent_account_id as parent_a6_1_0_, linkedacco0_.id as id1_0_0_, linkedacco0_.id as id1_0_1_, linkedacco0_.aws_id as aws_id2_0_1_, linkedacco0_.key_id as key_id3_0_1_, linkedacco0_.name as name4_0_1_, linkedacco0_.parent_account_id as parent_a6_0_1_, linkedacco0_.secret_key as secret_k5_0_1_ from linked_account linkedacco0_ where linkedacco0_.parent_account_id=?
I tried changed the fetch type to LAZY but then I get this error:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.berrycloud.scheduler.model.ParentAccount.linkedAccounts, could not initialize proxy - no Session
(It seems that it is trying to do the lazy load outside of the transactional context).
This is my CRUD repository:
#Repository
public interface ParentAccountRepository extends CrudRepository<ParentAccount, Long> {
}
Could someone tell me how to resolve this issue? I would prefer the solution with EAGER fetch. Thank you for any tips
EDIT: here is the schema I am using
CREATE TABLE parent_account (
id BIGINT auto_increment,
name VARCHAR(80) null,
PRIMARY KEY (`id`)
);
CREATE TABLE linked_account (
id BIGINT auto_increment,
parent_account_id BIGINT,
name VARCHAR(80) null,
FOREIGN KEY (`parent_account_id`) REFERENCES `parent_account` (`id`),
PRIMARY KEY (`id`)
);
As the first answer suggests:
Do not use Lombok's #Data annotation on #Entity classes.
Reason: #Data generates hashcode(), equals() and toString() methods that use the generated getters. Using the getter means of course fetching new data even if the property was marked with FetchType=LAZY.
Somewhere along the way hibernate tries to log the data with toString() and it crashes.
Problem solved. I was using a custom #toString method in the LinkedAccount which was referencing the ParentAccount. I had no idea that this could cause any problem and therefor I did not include the toString in my question.
Apparently, this was causing an infinite loop of lazy loading and removing this reference fixed the problem.
As user1819111 told, #Data from Lombok is not compatible with #Entity and FetchType=LAZY. I had used Lombok.Data (#Data) and I was getting this error.
As I don't want do create all get/set, I just put the Lombok #Setter and #Getter in your class and all will work fine.
#Setter
#Getter
#Entity
#Table(name = "file")
#SequenceGenerator(name = "File_Sequence", allocationSize=1, sequenceName = "file_id_seq")
public class MyClass{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "File_Sequence")
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "file", cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
private Set<Base2FileDetail> details = new HashSet<>();
}
Something like this does not work?
#Entity
public class Account {
#Id
#GeneratedValue
private long id;
private String name;
#ManyToOne(cascade={CascadeType.ALL})
#JoinColumn(name="manager_id")
private Account manager;
#OneToMany((fetch = FetchType.EAGER, mappedBy="manager")
private Set<Account> linkedAccounts = new HashSet<Account>();
}
I recently had this issue due to a poorly defined Jackson2HttpMessageConverter.
I had done something like the following.
#Bean
RestTemplate restTemplate(#Qualifier("halJacksonHttpMessageConverter")
TypeConstrainedMappingJackson2HttpMessageConverter halConverter) {
final RestTemplate template = new RestTemplateBuilder().build();
halConverter.setSupportedMediaTypes(List.of(/* some media types */));
final List<HttpMessageConverter<?>> converters = template.getMessageConverters();
converters.add(halConverter);
template.setMessageConverters(converters);
return template;
}
This caused a problem because the media types did not include all the defaults. Changing it to the following fixed the issue for me.
halConverter.setSupportedMediaTypes(
new ImmutableList.Builder<MediaType>()
.addAll(halConverter.getSupportedMediaTypes())
.add(/* my custom media type */)
.build()
);
This simple way worked for me. Just use JsonIgnoreProperties .
#JsonIgnoreProperties(value = {"linkedAccounts"})
#ManyToOne(cascade = { CascadeType.PERSIST})
#JoinColumn(name = "abc", referencedColumnName = "abc")
private ParentAccount parentAccount;
This way worked for me without removing #ToSring annotation:
#Entity
#Getter
#Setter
#ToString
#RequiredArgsConstructor
#AllArgsConstructor
#Table(name = "parent_accounts")
public class ParentAccount {
#JsonIgnoreProperties({"parentAccount"})
#OneToMany(mappedBy = "parentAccount",
cascade = CascadeType.ALL,
orphanRemoval = true)
private List<LinkedAccount> linkedAcounts;
// ...
}
#Entity
#Getter
#Setter
#ToString
#RequiredArgsConstructor
#AllArgsConstructor
#Table(name = "linked_accounts")
public class LinkedAccount {
#JsonIgnoreProperties("linkedAcounts")
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "parentAccount_id")
private ParentAccount parentAccount;
// ...
}
PS: In #JsonIgnoreProperties You can also ignore more than one field for preventing infinite loop

Initializing many-to-many association in Hibernate with join table

I have a Company entity that I fetch with a JPQL query with Hibernate. The entity has a many-to-many association with a Keyword entity. Since the join table has an additional column is_active, this table has been mapped to a CompanyKeyword entity. So the association is like this:
Company <-- CompanyKeyword --> Keyword
Now, the association from the Company entity is lazy, and it is not initialized by my JPQL query, as I want to avoid creating a cartesian product performance problem. That is why I want to initialize the association after running the JPQL query, e.g. like this:
#Service
class CompanyServiceImpl implements CompanyService {
#Autowired
private CompanyRepository companyRepository;
#Transactional
public Company findOne(int companyId) {
Company company = this.companyRepository.findOneWithSomeCustomQuery(companyId);
Hibernate.initialize(company.companyKeywords());
return company;
}
}
For a "normal" many-to-many association, this would work great, as all of the associated entities would be fetched in a single query. However, since I have an entity between Company and Keyword, Hibernate will only initialize the first part of the association, i.e. from Company to CompanyKeyword, and not from CompanyKeyword to Keyword. I hope that makes sense. I am looking for a way to initialize this association all the way without having to do something like this:
Company company = this.companyRepository.findOneWithSomeCustomQuery(companyId);
Hibernate.initialize(company.getCompanyKeywords());
for (CompanyKeyword ck : company.getCompanyKeywords()) {
Hibernate.initialize(ck.getKeyword());
}
The above code is neither clean, nor good in terms of performance. If possible, I would like to stick to my current approach of using a JPQL query to fetch my Company entity and then initializing certain associations afterwards; it would take quite a bit of refactoring to change this in my project. Should I just "manually" fetch the association with a second JPQL query, or is there a better way of doing it that I haven't thought of?
Below are my mappings. Thanks in advance!
Company
#Entity
#Table(name = "company")
public class Company implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#Size(max = 20)
#OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
private Set<CompanyKeyword> companyKeywords = new HashSet<>();
// Getters and setters
}
CompanyKeyword
#Entity
#Table(name = "company_service")
#IdClass(CompanyServicePK.class)
public class CompanyKeyword implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Company.class)
#JoinColumn(name = "company_id")
private Company company;
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Keyword.class)
#JoinColumn(name = "keyword_id")
private Keyword keyword;
#Column(nullable = true)
private boolean isActive;
// Getters and setters
}
CompanyKeywordPK
public class CompanyServicePK implements Serializable {
private Company company;
private Service service;
public CompanyServicePK() { }
public CompanyServicePK(Company company, Service service) {
this.company = company;
this.service = service;
}
// Getters and setters
// hashCode()
// equals()
}
Keyword
#Entity
#Table(name = "keyword")
public class Keyword {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
// Fields and getters/setters
}
You'll indeed need to execute an additional JPQL query, fetching the company with its companyKeyWords and with the keyword of each CompanyKeyWord.
You could also doing it by simply looping and initializing every entity, and still avoid executing too many queries, by enabling batch fetching.

Categories

Resources