I have a scenario where I have a Object called Page, and another object called Tag, relationship between these two is Page has Tags(many to many), but the same Tags can also shared with Product, here also the relationship is same Product has Tags (many to many).
In normal scenario I will create a type column in Tag where type may be Enum value (product, page) and use query like SELECT * from Tags where parent_id = page_id and type = page.
How to do this in JPA (how to create this relationship and how to query data)
I think you should create two tables of associations. One to associate Pages with tags, one to associate Products with tags. The code:
#Entity
#Table(name = "page")
class Page {
....
#OneToMany
#JoinTable(name = "jnd_pages_tags", joinColumns = #JoinColumn(name = "page_fk"),
inverseJoinColumns = #JoinColumn(name = "tag_fk"))
private Set<Tag> tags;
}
#Entity
#Table(name = "page")
class Product {
....
#OneToMany
#JoinTable(name = "jnd_products_tags", joinColumns = #JoinColumn(name = "products_fk"),
inverseJoinColumns = #JoinColumn(name = "tag_fk"))
private Set<Tag> tags;
}
#Entity
#Table(name = "tags")
class Tag {
.....
}
to select corresponding tags for page, use
SELECT p.t FROM Page p WHERE p.id = :id
to select corresponding tags for product, use
SELECT p.t FROM Product p WHERE p.id = :id
Related
I have two classes: Child and Guardian
in Guardian class i have that field:
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "guardian_child", schema = "schema",
joinColumns = #JoinColumn(name = "guardianid"),
inverseJoinColumns = #JoinColumn(name = "childid"))
private List<Child> children = new ArrayList<>();
I have table guardian_child in my Postgres. And now i need to get all children by id of guardian? Need i create special entity and repository for this table? Or how can i do it?
You can get data using JPQL query like this: #Query(SELECT g FROM Guardian JOIN g.children c)... if it doesn't work let me know once again
I would like to query all products for a company. The products should be loaded with the list of countries. I managed to write a Spring JPA repository method to query what I want but I wonder why I need a DISTINCT clause.
If I run the following query, I get one product per country. So if a product has 3 countries, the query will return the same 3 rows. Can you explain why?
#EntityGraph(attributePaths = "countries", type = EntityGraph.EntityGraphType.LOAD)
List<Product> findByCompanyIdOrderByIdAsc(Long companyId);
So to fix that issue I added a Distinct clause which return what I want.
#EntityGraph(attributePaths = "countries", type = EntityGraph.EntityGraphType.LOAD)
List<Product> findDistinctByCompanyIdOrderByIdAsc(Long companyId);
I have the same issue if I run a JPQL Select p from Product LEFT JOIN FETCH p.countries WHERE p.company.id = ?1 which is equivalent to findByCompanyIdOrderByIdAsc.
The entities:
public class Product implements Serializable {
#ManyToOne
#JsonIgnore
private Company company;
#ManyToMany
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JoinTable(name = "product_country",
joinColumns = #JoinColumn(name="product_id", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="country_id", referencedColumnName="id"))
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id",
resolver = EntityIdResolver.class, scope = Country.class)
#JsonIdentityReference(alwaysAsId = true)
private Set<Country> countries = new HashSet<>();
}
public class Country implements Serializable {
}
I got a many to many relationship between two tables. I mapping this using the annotation #ManyToMany.
Table1 ---> Relational Table ---> Table 2
Using hibernate i don't have to create any entity for the relationship table so I have.
Entity 1(Table 1) ---> Entity 2(Table 2)
But my problem is that i have another table and i must do a relationship between this 3th table and the relation table between the previous and i don't have any entity for do the relation.
Table 3 ---> Relational Table
I mean this 3th table got a foreign key with the relational table that i used before...
How can i accomplishment this? Sorry for my english
Thanks
here is an example.
Imagine we have Products and Categories
You have 2 entities Product and Category
Using the hibernate code-first approach, the entities will be like this:
#Entity
#Table(name = "products")
public class Product implements Serializable {
private Long id;
....
#ManyToMany
#JoinTable(name = "categories_products",
joinColumns = #JoinColumn(name = "category_id",
referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "product_id",
referencedColumnName = "id"))
private Set<Category> categories;
...
And here is the code for the code
public class Category {
private Long id;
private String name;
...
}
By using the annotation #JoinTable a third table will be created with the following constraints. It will have category_id which will point to Categories id and product_id referring to Products id.
Short version
I have a basic setup where a user table is linked to a role table and a role table is linked to a right. These are both Many-to-Many relations. The roles are a dynamic entity and not of interest for the application (only for visual aspects). When I fetch a user I want to return the data in the user table including a list of the names of all rights.
To clarify, this is what I want the solution to do:
I managed to get the rights in my user object and return them, but it's inefficient due to the extra query calls hibernate makes after the original query was called.
Detailed version
Let me first give you some information on how to entities are linked and what the code looks like. My (simplified) database table structure looks like this:
User.java
#Entity
#Table(name = "user")
public class User {
#Id
#Column(name = "user_id", columnDefinition = "user_id")
private Long userId;
#Transient
private List<String> rights
#ManyToMany
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "role_id"))
#JsonIgnore
private List<Role> roles;
//Getters and setters
}
Role.java
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "role_id", columnDefinition = "role_id")
private Long roleId;
#ManyToMany
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "role_id"),
inverseJoinColumns = #JoinColumn(name = "user_id", referencedColumnName = "user_id"))
#JsonIgnore
private List<Employee> employees;
#ManyToMany
#JoinTable(
name = "role_right",
joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "role_id"),
inverseJoinColumns = #JoinColumn(name = "right_id", referencedColumnName = "right_id"))
#JsonIgnore
private List<Right> rights;
//Getters and setters
}
Right.java
#Entity
#Table(name = "right")
public class Right {
#Id
#Column(name = "right_id", columnDefinition = "right_id")
private Long rightId;
#Column(name = "name", columnDefinition = "name")
private String name;
#ManyToMany
#JoinTable(
name = "role_right",
joinColumns = #JoinColumn(name = "right_id", referencedColumnName = "right_id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "role_id"))
#JsonIgnore
private List<Role> roles;
//Getters and setters
}
It's important to know that I use the Java Specifications API to join the tables:
return (root, query, cb) -> {
query.distinct(true);
Join rolesJoin = root.join("roles", JoinType.LEFT);
Join rightsJoin = rolesJoin.join("rights", JoinType.LEFT);
return cb.conjunction();
};
This creates the correct query:
select <columns go here>
from employee user0_
left outer join user_role roles1_ on user0_.user_id=roles1_.user_id
left outer join role role2_ on roles1_.role_id=role2_.role_id
left outer join role_right rights3_ on role2_.role_id=rights3_.role_id
left outer join right right4_ on rights3_.right_id=right4_.right_id
Everything looked to good to me till now. But when I tried to fetch the names of all roles, there where more than two queries (count for page and the original one) being executed
//The original code uses lambda for this
for(Role role : user.getRoles()){
for(Right right: role.getRights()){
user.addRight(right.getName());
}
}
The extra query looks like:
select <column stuff>
from role_right rights0_
inner join right right1_ on rights0_.right_id=right1_.right_id
where rights0_.role_id=?
This makes the call very inefficient to me. In this case it's a single user, but with multiple users it adds up.
Is there a way to have a single query put the names of all rights in the user entity, without adding extra query executions?
Things I tried so far:
Using #SecondaryTable to directly define column from the Right table in my User entity. I could not get to first link the Role to the User and then use fields from the Role table to link the Right table. So in the end I would have to #SecondaryTable annotation on top of my User object and define columns of the Right object below.
Using #Formula in the User entity to insert a native call into the query. This did also not work as the annotation did not understand how to map everything into a list of rights.
There might be other options here, or I did something horribly wrong with implementing the ones above. But for now I don't which way to go in finding a solution for my problem. If someone could tell me, that would be great.
Thanks in advance,
Robin
You are using Root.join which does just the joining of tables for the purposes of the query; lazy associations in the loaded entities will still not be initialized.
As I see, your intention is to initialize the lazy collections as well. For that you have to use Root.fetch (defined in the interface method inherited from the FetchParent):
Create a fetch join to the specified collection-valued attribute using
the given join type.
However, your intention is not a good practice; do not join multiple collections in one query, otherwise the query result set will explode with full Cartesian product between the joined collections. Your result set contains <num of users> * <num of roles per user> * <num of rights per role> rows. So, each user data is repeated <num of roles per user> * <num of rights per role> times in the generated result set.
The approach I find to be the best and most straightforward is to specify batch size on lazy associations.
I have two entities. For example, posts and tags to it.
I have to write a method that will take only posts, which have all of tags, mentioned in query.
I tried
#Query("select distinct p from posts p join p.tags t where t in ?1")
Page<Post> findDistinctByTagsIn(Set<Tag> tagSet, Pageable pageable);
However it takes Post if at least one of its tags included in tagSet.
How can I solve this using only HQL and JPA Repositories?
UPD:
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "posts_tags", joinColumns = {
#JoinColumn(name = "post_id", nullable = false, updatable = false)},
inverseJoinColumns = {#JoinColumn(name = "tag_id")})
public Set<Tag> getTags() {
return tags;
}
Add to you Tag class next ManyToOne relation:
#Entity
public class Tag{
...
private Post post;
#ManyToOne
#JoinTable(name = "posts_tags",
joinColumns = {#JoinColumn(name = "tag_id")},
inverseJoinColumns = {#JoinColumn(name = "post_id")})
public Post getPost(){
return post;
}
...
}
Let's try to build query.
We don't need posts that have tags that are out of our list of tags. We will select them with next query:
select t.post from Tag t where t not in (:tagSet) and t.post is not null
And we don't need posts, that have not any tags at all. Let's select them as well:
select p from Post p where p.tags is empty
Now let's join our queries together:
select p from Post p where
p not in (select t.post from Tag t where t not in (:tagSet) and t.post is not null)
and p not in (select p2 from Post p2 where p2.tags is empty)
And you can use named parameter to bind this query:
Page<Post> findDistinctByTagsIn(#Param("tagSet") Set<Tag> tagSet, Pageable pageable);