Hibernate generates extra left join with many to one association - java

I have a simple Hibernate #ManyToOne mapping between two entities, using an association table thanks to annotation #JoinTable.
Here is my mapping:
#Entity
public class Customer {
private Long id;
private Address address;
#Id
#GeneratedValue
public Long getId() {
return id;
}
#ManyToOne
#JoinTable(
name = "customer_address_association",
joinColumns = #JoinColumn(name = "customer_id"),
inverseJoinColumns = #JoinColumn(name = "address_id")
)
public Address getAddress() {
return address;
}
public void setId(Long id) {
this.id = id;
}
public void setAddress(Address address) {
this.address = address;
}
}
and
#Entity
public class Address {
private Long id;
private String street;
#Id
#GeneratedValue
public Long getId() {
return id;
}
public String getStreet() {
return street;
}
public void setId(Long id) {
this.id = id;
}
public void setStreet(String street) {
this.street = street;
}
}
When I query on Customer entity, I always get an extra left join to the join table. For example, a HQL query such as SELECT c.id from Customer c generates the following SQL query: select customer0_.id as col_0_0_ from customer customer0_ left outer join customer_address_association customer0_1_ on customer0_.id=customer0_1_.customer_id.
Full source code to reproduce is available here: https://github.com/ndionisi/hibernate-extra-left-join
I reproduce it with Hibernate 5.1, 5.2 and 5.3. Is there any way to prevent Hibernate from generating the left join to customer_address_association table? It impacts the performance on large tables.

Related

Hibernate PostgreSQL OneToOne Relationship Trigger Child Query First

I'm building app to learn Hibernate on PostgreSQL. And I'm currently trying to add variable to database that has OneToOne relationship.
First, I create two tables in my database with schema below. On person_detail table it has primary key that's also foreign key to person table.
Then I created two classes, Person and PersonDetail. PersonDetail is child of Person that has OneToOne relationship. And I use code below to add person with personDetail as attribute.
Person person = new Person(
"Rick",
1.7,
dateFromString("1969-4-2"),
new Date()
);
PersonDetail personDetail =
new PersonDetail("myemail#email.com", "Marley");
person.setPersonDetail(personDetail);
session.beginTransaction();
session.save(person);
session.save(personDetail);
session.getTransaction().commit();
System.out.println(person.toString());
But the problem with code above is that Hibernate execute child query first instead of parent query.
Hibernate: insert into person_detail (address, email) values (?, ?)
And since person still empty, we cannot insert any row to person_detail because it violates foreign key constraint.
Is there a way to solve this?
Thanks!
In case some one want to check how I annotate those two classes, I put the code below.
#Entity
#Table(name="person")
#Data
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="name")
private String name;
#Column(name="height")
private double height;
#Column(name="birth_date")
private Date dateBirth;
#Column(name="last_seen")
private Date lastSeen;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id")
private PersonDetail personDetail;
public Person() {}
public Person(String name, double height, Date dateBirth, Date lastSeen){
this.name = name;
this.height = height;
this.dateBirth = dateBirth;
this.lastSeen = lastSeen;
}
}
#Data
#Entity
#Table(name="person_detail")
public class PersonDetail {
#Id
#Column(name="id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name="email")
private String email;
#Column(name="address")
private String address;
public PersonDetail(){}
public PersonDetail(String email, String address){
this.email = email;
this.address = address;
}
}
I see that you have primary key in table person_details as foreign key to person table, you can use #PrimaryKeyJoinColumn like this:
#Entity
#Table(name="person")
#Data
public class Person {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "height")
private String height;
#OneToOne(mappedBy = "person", cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private PersonDetail personDetail;
public Person() {}
public Person(String name, String height) {
this.name = name;
this.height = height;
}
}
#Data
#Entity
#Table(name="person_detail")
public class PersonDetail {
#Id
#Column(name="id")
private Long id;
#Column(name="email")
private String email;
#OneToOne
#MapsId
#JoinColumn(name = "id", referencedColumnName = "id")
private Person person;
public PersonDetail(){}
public PersonDetail(String email){
this.email = email;
}
}
And if you save your entity, don't forget set Person to PersonDetails:
Person person = new Person("Rick", "1.7");
PersonDetail personDetail = new PersonDetail("myemail#email.com");
personDetail.setPerson(person);
person.setPersonDetail(personDetail);
repository.save(person);

One-to-many or many-to-many for Ads-Tags relation?

I am developing a Spring Boot app in which I have Ads, and when a user is creating a new Ad, he needs to give it a few tags (we need to filter ads through that tags later). I have fixed list of those Tags and I currently have 20 Tags (that number may change later). I made a many-to-many relationship(with join table) but I don't know is that a good choice because I am struggling now with creating ads.
This is my Ad entity:
#Entity
#Table(name= "ads")
public class Ads {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany
#JoinTable(name = "tags_ads",
joinColumns = #JoinColumn(name = "ads_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "tags_id", referencedColumnName = "id"))
private List<Tag> adsTags;
And this is my Tag entity:
#Entity
#Table(name="Tags")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name="name")
private String name;
#ManyToMany(mappedBy = "adsTags")
private Set<Ads> ads;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Ads> getAds() {
return ads;
}
public void setAds(Set<Ads> ads) {
this.ads = ads;
}
}
But when I want to create ad I have some errors regarding tags. So, I would like to know is many-to-many good choice for this situation or maybe I need to use one-to-many?

What is the real purpose of #OneToOne in Spring boot hibernate?

I have two entities named Users and Dependents. I want to establish a OneToOne relationship between these two entities. As the real meaning of OneToOne states that -
Every user in the Users entity should have one and only one dependent.
And every dependent in the Dependents entity should only be related to
one and only one user.
But when I add #OneToOne to Dependents entity it does not stop me from adding two dependents to the same user. What is the real use of #OneToOne
or any other relationship annotations like #ManyToMany, #OneToMany, #ManyToOne?
Users.java
#Entity
#Table
public class Users {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String teamName;
private Integer salary;
public Users() {
}
public Users(Integer id, String name, String teamName, Integer salary) {
this.id = id;
this.name = name;
this.teamName = teamName;
this.salary = salary;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTeamName() {
return teamName;
}
public void setTeamName(String teamName) {
this.teamName = teamName;
}
public Integer getSalary() {
return salary;
}
public void setSalary(Integer salary) {
this.salary = salary;
}
}
Dependents.java
#Entity
#Table
public class Dependents {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String relationship;
#OneToOne
private Users user;
public Dependents() {
}
public Dependents(int id, String name, String relationship) {
this.id = id;
this.name = name;
this.relationship = relationship;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRelationship() {
return relationship;
}
public void setRelationship(String relationship) {
this.relationship = relationship;
}
public Users getUser() {
return user;
}
public void setUser(Users user) {
this.user = user;
}
}
And in my DependentsService.java I am saving the Dependents object like-
public Dependents addNewDependent(Integer userId, Dependents dependent) {
dependent.setUser(usersRepository.getOne(userId));
return dependentsRepository.save(dependent);
}
Here I am fetching the user from the Users entity with the passed userId and storing it in Dependents object. When I pass the same userId for two or more dependents it will fetch the same user from Users entity and store it in Dependents entity. This violated OneToOne relationship. Can someone please explain to me, how can I achieve true OneToOne relationship? And also please explain what is the true purpose of relationship annotations like - #OneToOne, #OneToMany, #ManyToOne and #ManyToMany?
From Hibernate documentation:
From a relational database point of view, the underlying schema is identical to the unidirectional #ManyToOne association, as the client-side controls the relationship based on the foreign key column.
...
A much more natural mapping would be if the Phone were the parent-side, therefore pushing the foreign key into the PhoneDetails table. This mapping requires a bidirectional #OneToOne association
...
When using a bidirectional #OneToOne association, Hibernate enforces the unique constraint upon fetching the child-side. If there are more than one children associated with the same parent, Hibernate will throw a org.hibernate.exception.ConstraintViolationException.
So you should use a bidirectional one-to-one association.
Additional info: The best way to map a #OneToOne relationship with JPA and Hibernate
Hibernate won't do any extra checks to make sure, the record already exists. It is your responsibility to write the code which satisfies the OneToOne relation (it depends on your UI screens as well). If you still want to throw some exception, make your primary key as Foreign key in dependent table. Then you get DB exception.

Specifying join condition "ON columns" in hibernate HQL?

I am learning Hibernate, and I have a question about basic HQL join syntax. I am following this tutorial. Say I have a Product and Category entity,
#Entity
#Table(name = "CATEGORY")
public class Category {
private long id;
private String name;
private Set<Product> products;
public Category() {
}
public Category(String name) {
this.name = name;
}
#Id
#Column(name = "CATEGORY_ID")
#GeneratedValue
public long getId() {
return id;
}
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
public Set<Product> getProducts() {
return products;
}
// other getters and setters
}
#Entity
#Table(name = "PRODUCT")
public class Product {
private long id;
private String name;
private String description;
private float price;
private Category category;
public Product() {
}
public Product(String name, String description, float price,
Category category) {
this.name = name;
this.description = description;
this.price = price;
this.category = category;
}
#Id
#Column(name = "PRODUCT_ID")
#GeneratedValue
public long getId() {
return id;
}
#ManyToOne
#JoinColumn(name = "CATEGORY_ID")
public Category getCategory() {
return category;
}
// other getters and setters
}
So I have to join category and Product, I will something like this in sql
select * from Category A inner join Product B on A.id=B.category_id,
In HQL, it seems we drop the "on" condition, the HQL for the above query is
String hql = "from Product p inner join p.category";
Query query = session.createQuery(hql);
Why is on not required in HQL?
If you have an association (for an example #ManyToOne), you don't need on, Hibernate will add it to the SQL.
It was a problem prior to Hibernate 5.1, If you don't have an association. From Hibernate 5.1 you can use ad hoc joins:
How to join unrelated entities with JPA and Hibernate
Apart that, HQL also defines a with clause to qualify the join conditions:
Hibernate docs: Explicit joins

how to write join query in hibernate

I have created two beans User and VirtualDomain with many to many relationship
#Entity
#Table(name = "tblUser")
public class User implements Serializable {
private Long id;
private String username;
private Set<VirtualDomain> virtualdomainset;
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "username", length = 50, nullable = false)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#ManyToMany(targetEntity = VirtualDomain.class, cascade = {CascadeType.PERSIST},fetch=FetchType.EAGER)
#JoinTable(name = "tblUserDomainRel", joinColumns = #JoinColumn(name = "userid"), inverseJoinColumns = #JoinColumn(name = "domainid"))
public Set<VirtualDomain> getVirtualdomainset() {
return virtualdomainset;
}
public void setVirtualdomainset(Set<VirtualDomain> virtualdomainset) {
this.virtualdomainset = virtualdomainset;
}
}
#Entity
#Table(name = "tblVirtualDomain")
public class VirtualDomain {
private Long id;
private String domainname;
private Set<User> userset;
#Id
#JoinColumn(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "domain_name")
public String getDomainname() {
return domainname;
}
public void setDomainname(String domainname) {
this.domainname = domainname;
}
#ManyToMany(cascade = {CascadeType.ALL},fetch=FetchType.EAGER, mappedBy = "virtualdomainset", targetEntity = User.class)
public Set<User> getUserset() {
return userset;
}
public void setUserset(Set<User> userset) {
this.userset = userset;
}
}
how to get data of user like username related to particular domain through hibernate.
To add to gid's answer, if for some reason you need to eagerly fetch an entites relations, then the join syntax would be join fetch.
from VirtualDomain vd join fetch vd.usersset u
where vd.domainname = 'example.com' and u.username like 'foo%'
Always difficult to write HQL without a test system...but here we go:
select u from VirtualDomain vd join User vd.usersset u
where vd.domainname = 'example.com' and u.username like 'foo%'
Let me know how you get on.
One tip I often did prior to buying Intellji was to stop the app in the debugger and then use the immediate window to experiment with HQL.
The hibernate documentation on joins has always been a bit cryptic in my opinion.

Categories