JPA Hibernate Lazy Fetch Attributes with one Query - java

Consider following entities:
UserEntity:
#Entity(name = "user")
public class UserEntity {
#Id
#Column(name = "id", columnDefinition = "serial")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"))
private List<RoleEntity> roles = new ArrayList<>();
}
RoleEntity:
#Entity(name = "role")
#NamedEntityGraph(
name = "role.entity_graph",
attributeNodes = {
#NamedAttributeNode(value = "permissions")
})
public class RoleEntity {
#Id
#Column(name = "id", columnDefinition = "serial")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "role_permission", joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "permission_id", referencedColumnName = "id"))
private List<PermissionEntity> permissions = new ArrayList<>();
}
PermissionEntity:
#Entity(name = "permission")
public class PermissionEntity {
#Id
#Column(name = "id", columnDefinition = "serial")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
}
roles of the UserEntity should be fetched lazy. Problem is when the roles are used (getter is called), hibernate fetches them with a query for each role and a query for each permission of each role, resulting in the n+1 issue.
Thus my question is: How can I lazy fetch the user roles with one query? Can I somehow utilize RoleEntity's EntityGraph?
Note: I already tried using #Fetch:
#Fetch(FetchMode.JOIN)
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "role_permission", joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "permission_id", referencedColumnName = "id"))
private List<PermissionEntity> permissions = new ArrayList<>();
This did not work. However together with #BatchSize it works as excpeted.
#Fetch(FetchMode.JOIN)
#BatchSize(size = 1000)
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "role_permission", joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "permission_id", referencedColumnName = "id"))
private List<PermissionEntity> permissions = new ArrayList<>();
I'm not happy with this solution though. Why do I need to add #BatchSize?

I think using Subgraphs might help you. Take a look at a description from Thorben Janssen on his Website: Entitygraph with multiple Subgraphs, quote:
#NamedEntityGraph(
name = "graph.AuthorBooksPublisherEmployee",
attributeNodes = #NamedAttributeNode(value = "books", subgraph = "subgraph.book"),
subgraphs = {
#NamedSubgraph(name = "subgraph.book",
attributeNodes = #NamedAttributeNode(value = "publisher", subgraph = "subgraph.publisher")),
#NamedSubgraph(name = "subgraph.publisher",
attributeNodes = #NamedAttributeNode(value = "employees")) })

Related

How to join table with multiple columns - spring jpa #manytomany three join columns java

I've come to a problem, where i have to a user which can be registered in multiple companies. Those companies can have registered multiple products. And user should be aware to which products in which companies he has rights.
This is my solution so far, but it does not return the roles correctly:
class User {
#Id
#Column(name = "USER_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "USERNAME", unique = true)
private String username;
#Column(name = "PASSWORD")
private String password;
#ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "USER_ROLES",
joinColumns = {#JoinColumn(name = "USER_ID"), #JoinColumn(name = "COMPANY_ID"), #JoinColumn(name="PRODUCT_ID")},
inverseJoinColumns = {#JoinColumn(name = "ROLE_ID"), #JoinColumn(name = "COMPANY_ID"), #JoinColumn(name="PRODUCT_ID")})
private Set<Role> roles = new HashSet<>();
#ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "USER_COMPANIES",
joinColumns = {#JoinColumn(name = "USER_ID")},
inverseJoinColumns = {#JoinColumn(name = "COMPANY_ID")})
private Set<Company> companies = new HashSet<>();
}
class Role {
#Id
#Column(name = "ROLE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "ROLE_NAME")
#Enumerated(EnumType.STRING)
#NaturalId
private RoleName role;
#ManyToMany(mappedBy = "roles", fetch = FetchType.LAZY)
#JsonIgnore
private Set<User> userList = new HashSet<>();
}
class Company {
#Id
#Column(name = "COMPANY_ID")
private Long id;
#ManyToMany(mappedBy = "companies", fetch = FetchType.LAZY)
#JsonIgnore
private Set<User> userList = new HashSet<>();
#OneToMany(mappedBy = "company")
private Set<Product> products;
}
class Product {
#Id
#Column(name = "COMPANY_ID")
private Long id;
#ManyToOne
#JoinColumn(name="COMPANY_ID", nullable=false)
private Company company;
private ProductType productType;
}
My ultimate goal is to get roles from user, could anyone help, please?
Thanks a lot in advance!
#ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "USER_ROLES", joinColumns = {#JoinColumn(name = "USER_ID"),
#JoinColumn(name = "COMPANY_ID"), #JoinColumn(name="PRODUCT_ID")}, inverseJoinColumns =
{#JoinColumn(name = "ROLE_ID"), #JoinColumn(name = "COMPANY_ID"),
#JoinColumn(name="PRODUCT_ID")})
***#JsonBackReference***
private Set<Role> roles = new HashSet<>();
try with #JsonBackReference in user pojo.#JsonBackReference will set roles to user in the deserialization process.

JPA: how to map an entitiy multiple times in a Bi-Directional OneToMany

In the system being built, and Invoice has a billedFrom, billedTo and shippedTo all of which are an Entity Branch. I don't know how to define the #OneToMany (if that is indeed the right way to map this) side of this relationship on Branch class. Here's what I've done so far
The Branch Entity
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Branch extends AuditModel implements Serializable {
private static final long serialVersionUID = -8841725432779534218L;
#Column(name = "branch_name", length = 25, nullable = false)
private String branchName;
#Column(length = 15, unique = true)
private String gstin;
#Column(name = "vat_tin", length = 11, nullable = true)
private String vatTin;
private Address address;
private Boolean sez;
public Boolean isSez() {
return sez;
}
#OneToOne(fetch = FetchType.LAZY)
#JoinTable(name = "branch_contact", joinColumns = #JoinColumn(name = "branch_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "contact_id", referencedColumnName = "id"))
private Contact contact;
}
the Invoice Entity
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Invoice extends AuditModel implements Serializable {
private static final long serialVersionUID = 1560474818107754225L;
#Column(unique = true, length = 20)
private String invoiceNumber;
#Column(nullable = false, length = 25)
private String placeOfSupply;
private Double roundOff;
#Temporal(TemporalType.DATE)
#Column(nullable = false)
private Date invoiceDate;
#Temporal(TemporalType.DATE)
#Column(nullable = false)
private Date dueDate;
#Column(nullable = false)
private String paymentTerms;
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(name = "invoice_line_items", joinColumns = #JoinColumn(name = "invoice_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "line_item_id", referencedColumnName = "id"))
private List<LineItem> lineItems;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "invoice_id", referencedColumnName = "id")
private List<Payment> payments;
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "billed_from_invoices", joinColumns = #JoinColumn(name = "invoice_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "branch_id", referencedColumnName = "id"))
private Branch billedFrom;
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "customer_invoices", joinColumns = #JoinColumn(name = "invoice_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "branch_id", referencedColumnName = "id"))
private Customer customer;
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "billed_to_invoices", joinColumns = #JoinColumn(name = "invoice_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "branch_id", referencedColumnName = "id"))
private Branch billedTo;
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "shipped_to_invoices", joinColumns = #JoinColumn(name = "invoice_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "branch_id", referencedColumnName = "id"))
private Branch shippedTo;
}
I should be able to find out
Invoices Billed To a (Customer) Branch
Invoices Billed From my (Company) Branch
Invoices Shipped To a (Customer) Branch

Can we have a #OneToMany and #ManyToOne relationship between two tables in hibernate?

Eg.
public class Portfolio implements Serializable {
#ManyToOne()
#JoinColumn(name = "PORTFOLIO_OWNER", referencedColumnName = "USER_ID", foreignKey = #ForeignKey(name = "FK__USER__PORTFOLIO"), nullable = false)
private User portfolioOwner;
#ManyToOne()
#JoinColumn(name = "ACCOUNT_CAPTAIN", referencedColumnName = "USER_ID", foreignKey = #ForeignKey(name = "FK__USER__PORTFOLIO2"))
private User accountCaptain;
}
and
public class User {
#ManyToOne
#JoinColumn(name = "PORTFOLIO_ID", referencedColumnName = "PORTFOLIO_ID", foreignKey = #ForeignKey(name = "FK_DEF_PORTFOLIO_USER"))
#Fetch(FetchMode.JOIN)
private Portfolio defaultPortfolio;
}
I run into Stackoverflow on fetching them using JACKSON as a JSON
org.springframework.http.converter.HttpMessageNotWritableException:
Could not write content: Infinite recursion (StackOverflowError)
(through reference chain:
com.User["defaultPortfolio"]->com..Portfolio["portfolioOwner"]->com.User["defaultPortfolio"]->com.Portfolio["portfolioOwner"]->com..User["defaultPortfolio"]-
you need to add json ignore to one side of the relations ex:
public class Portfolio implements Serializable {
#ManyToOne()
#JoinColumn(name = "PORTFOLIO_OWNER", referencedColumnName = "USER_ID", foreignKey = #ForeignKey(name = "FK__USER__PORTFOLIO"), nullable = false)
#JsonIgnore
private User portfolioOwner;
#ManyToOne()
#JoinColumn(name = "ACCOUNT_CAPTAIN", referencedColumnName = "USER_ID", foreignKey = #ForeignKey(name = "FK__USER__PORTFOLIO2"))
private User accountCaptain;
}

Illegal attempt to map a non collection as a #OneToMany, #ManyToMany or #CollectionOfElements

I have one lawyer table which is having id(int) as a primary key and Country table having country_code(String ) as a primary key. I want to create third table using #JoinTable annotation in hibernate with two foreign key in it. But when I run it following error is coming. Not sure how to map one string and one int as foreign keys in third table.
Illegal attempt to map a non collection as a #OneToMany, #ManyToMany or #CollectionOfElements: com.test.common.entities.Country.lawyer
This is my code
#Entity
#Table(name = "lawyer")
public class Lawyer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "lawyer_batch_no")
private int lawyerbatchNo;
#ManyToOne(targetEntity = Country.class, cascade = { CascadeType.ALL })
#JoinTable(name = "lawyer_cscd", joinColumns = {
#JoinColumn(name = "lawyer_batch_no", referencedColumnName = "lawyer_batch_no") }, inverseJoinColumns = {
#JoinColumn(name = "country_code", referencedColumnName = "country_code") })
private Country country;
getter setter...
}
#Entity
#Table(name = "country")
public class Country {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "country_code")
protected String country_code;
#Column(name = "abbreviation")
protected String abbreviation;
#Column(name = "name", nullable = false)
protected String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "country")
protected Set<State> state = new HashSet<State>();
#OneToMany(targetEntity = Lawyer.class, cascade = { CascadeType.ALL }, orphanRemoval = true)
#JoinTable(name = "lawyer_cscd", joinColumns = {
#JoinColumn(name = "country_code", referencedColumnName = "country_code") }, inverseJoinColumns = {
#JoinColumn(name = "lawyer_batch_no", referencedColumnName = "lawyer_batch_no") })
private Lawyer lawyer;
getter setter....
}
The error indicates that private Lawyer lawyer needs to be a collection as it's a #OneToMany relationship. In the Country class, the last relationship should be
#OneToMany(targetEntity = Lawyer.class, cascade = { CascadeType.ALL }, orphanRemoval = true)
#JoinTable(name = "lawyer_cscd", joinColumns = {
#JoinColumn(name = "country_code", referencedColumnName = "country_code") }, inverseJoinColumns = {
#JoinColumn(name = "lawyer_batch_no", referencedColumnName = "lawyer_batch_no") })
private Set<Lawyer> lawyer;
// or a Collection/List/etc.

JoinTable in JPA for 3 different entities

I'm trying to create a join table with #JoinTable from 3 different entities. Below is the code sample I'm using. While creating join table I'm getting below error. Please help to resolve.
There are 3 entities in my design. Credential, Category and Tenant.
Now I'm trying to make a join table that will contain the pk of these 3 tables using #ManyToMany annotation between them. Below is the relationship which I'm trying to make.
#Entity
#Table(name = "CREDENTIALS")
public class CredentialsEntity {
/** The credential id. */
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private int credentialId;
#ManyToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "CATEGORY_HAS_CREDENTIALS",
joinColumns = {
#JoinColumn(table="CATEGORY", name = "CATEGORY_ID", referencedColumnName = "ID"),
#JoinColumn(table ="TENANT", name = "TENANT_ID", referencedColumnName = "ID")},
inverseJoinColumns = #JoinColumn(table="CREDENTIALS", name = "CREDENTIAL_ID", referencedColumnName = "ID"))
private List<TenantEntity> tenantEntities;
}
==========================
#Entity
#Table(name = "TENANT")
public class TenantEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private int tenantId;
#ManyToMany(mappedBy = "tenantEntities", cascade = CascadeType.ALL)
private List<CredentialsEntity> credentialsEntities;
}
==========================
#Entity
#Table(name = "CATEGORY")
public class NodeCategory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int categoryId;
#ManyToMany(cascade = CascadeType.ALL)
private List<CredentialsEntity> credentialsEntities;
}
Caused by:
org.hibernate.AnnotationException: Cannot find the expected secondary
table: no TENANT available for
com.aricent.aricloud.entity.CredentialsEntity at
org.hibernate.cfg.Ejb3Column.getJoin(Ejb3Column.java:363) at
org.hibernate.cfg.Ejb3Column.getTable(Ejb3Column.java:342) at
org.hibernate.cfg.Ejb3Column.checkPropertyConsistency(Ejb3Column.java:584)
at
org.hibernate.cfg.annotations.CollectionBinder.buildCollectionKey(CollectionBinder.java:1018)
at
org.hibernate.cfg.annotations.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:1336)
EDIT
I'm able to do the jointable like below, as mentioned in link
is this correct approach or I'm doing something wrong?
#ManyToMany(cascade={CascadeType.ALL})
#JoinTable(name = "CATEGORY_HAS_CREDENTIALS",
joinColumns = {
#JoinColumn(name = "CREDENTIAL_ID", referencedColumnName = "ID")},
inverseJoinColumns = #JoinColumn(table = "CATEGORY", name = "CATEGORY_ID", referencedColumnName = "ID"))
#MapKeyJoinColumn(name = "TENANT_ID", referencedColumnName ="ID")
#ElementCollection
private Map<TenantEntity, NodeCategory> tenantCategoryMap = new HashMap<TenantEntity, NodeCategory>();

Categories