OneToOne relationship for a non primary key column - java

I'm having a problem when I query an entity who has a OneToOne relationship with another one. This is the scenario:
Database tables:
create table people (
id decimal(10,0) NOT NULL,
email varchar(512) NOT NULL
);
create table users (
email varchar(512) NOT NULL
);
Test data:
insert into users (email) values ('jhon#domain.com');
insert into users (email) values ('mary#domain.com');
insert into people (id, email) values (1, 'jhon#domain.com');
insert into people (id, email) values (2, 'mary#domain.com');
Entities:
#Entity(name = "people")
public class Person implements Serializable {
#Column
#Id
private long id;
#Column
private String email;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
#Entity(name = "tbl_users")
public class User implements Serializable {
#Id
private String email;
#OneToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name = "email", referencedColumnName = "email")
private Person person;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
Invocation:
...
User user = entityManager.find(User.class, "jhon#domain.com");
...
After de invocation, the hibernate's logs shows:
select user1_.email as email2_0_, person2_.id as id1_1_, person2_.email as email1_1_
from users user1_ left outer join people person2_ on user1_.email=person2_.id
where user1_.email=?
As you can see, the join is wrong because is comparing users.email with people.id (user1_.email=person2_.id), so it returns an User without its corresponding Person.
Any ideas about how can I fix it?
Thanks a lot !!

Strictly speaking, the JPA specification does not allow references to non-primary key columns. It may work in some JPA implementations, but it's not proper.
However, i think you can do this by making the relationship bidirectional, and owned by the side with the non-primary key:
#Entity
public class Person {
#Id
private long id;
#OneToOne
#JoinColumn(name = "email")
private User user;
public String getEmail() {
return user.getEmail();
}
public void setEmail(String email) {
// left as an exercise for the reader
}
}
#Entity
public class User {
#Id
private String email;
#OneToOne(mappedBy = "user")
private Person person;
}
I haven't actually tried that, though, so caveat hackor.

I think you should rethink your datamodel. The relationship between User and Person looks more like one of Inheritance.
For the issue with your mappings as they stand see here for some further disussion:
JPA providers: why do relationships/FKs to non-PK columns work in Hibernate and EclipseLink?
Does the JPA specification allow references to non-primary key columns?

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);

Hibernate generates extra left join with many to one association

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.

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.

Hibernate- Avoid many-to-many association- How to implement it right?

I'm a newbie to JPA and Hibernate.
I was able to set up some small basic examples as expected.
Now I'm trying to use it for a first real world project.
Studying the Hibernate best practices I found that you should avoid many-to-many relationships. I found relating questions here and I do understand the concept why not to use it but I'm not understanding how it should be implemented.
So when I have the often used example of a user that can the part of many groups and a group that does have many users, how to implement that.
So this is a many-to-many relationship. But I should not use many-to-many, as far as I understood because it is very likely that I will need other information in the future, like a specific role a user has in a group or a date when he joined.
So I use a two one-to-many relationships and a joining table which doesnt only contains the ids but also will contain additional information like role or date.
Is that right?
And then neither the
class group has a property users
nor
the class users has a property groups
both have a property joinTableEntries?
Did I get the concept right so far?
#Entity
#Table(name="user")
public class User {
private int userId;
private String username;
private Set<JTUserGroup> jtUserGroupSet=new HashSet<JTUserGroup>(0);
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "userid")
public int getUserId()
{
return this.userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
public Set<JTUserGroup> getJtUserGroupSet() {
return jtUserGroupSet;
}
public void setJtUserGroupSet(Set<JTUserGroup> jtUserGroupSet) {
this.jtUserGroupSet = jtUserGroupSet;
}
}
#Entity
#Table(name = "forumgroup")
public class Group {
private int groupId;
private String groupname;
private Set<JTUserGroup> jtUserGroupSet=new HashSet<JTUserGroup>(0);
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "groupid")
public int getGroupId() {
return groupId;
}
public void setGroupId(int groupId) {
this.groupId = groupId;
}
public String getGroupname() {
return groupname;
}
public void setGroupname(String groupname) {
this.groupname = groupname;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "group")
public Set<JTUserGroup> getJtUserGroupSet() {
return jtUserGroupSet;
}
public void setJtUserGroupSet(Set<JTUserGroup> jtUserGroupSet) {
this.jtUserGroupSet = jtUserGroupSet;
}
}
#Entity
#Table(name = "jtusergroup")
public class JTUserGroup {
private int joinId;
private User user;
private Group group;`enter code here`
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public int getJoinId() {
return joinId;
}
public void setJoinId(int joinId) {
this.joinId = joinId;
}
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "userid", nullable = false)
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "groupid", nullable = false)
public Group getGroup() {
return group;
}
public void setGroup(Group group) {
this.group = group;
}
}
and then to use these I add a new user to a group like this:
User user1=new User();
User user2=new User();
User user3=new User();
Group group1=new Group();
Group group2=new Group();
user1.setUsername("Mark");
user2.setUsername("Anton");
user3.setUsername("Maggy");
group1.setGroupname("Mark_Maggy");
group2.setGroupname("Maggy_Anton");
JTUserGroup jt1=new JTUserGroup();
jt1.setGroup(group1);
jt1.setUser(user1);
JTUserGroup jt2=new JTUserGroup();
jt2.setGroup(group1);
jt2.setUser(user3);
JTUserGroup jt3=new JTUserGroup();
jt3.setGroup(group2);
jt3.setUser(user3);
JTUserGroup jt4=new JTUserGroup();
jt4.setGroup(group2);
jt4.setUser(user2);
GenericDAO<JTUserGroup> jtDao=new GenericDAO<JTUserGroup>();
jtDao.beginTransaction();
jtDao.insert(jt1);
jtDao.insert(jt2);
jtDao.insert(jt3);
jtDao.insert(jt4);
jtDao.commit();
Just image this, you have User and let's say that Group is extending your user. Now your 'Group' has a sub-class which is JTUserGroup. Let's say that User has one-to-many relationship with Group (based on your logic, user can belong on many groups). Now the question, how can User know about JTUserGroup? You need somehow to get FK(as everybody knows that FK creates assosiations with classes) in your User class to know about all your 'Group' sub-classes and rely on logic, belong (let's say) for a several sub-classes which belongs to 'Group'. It is not impossible to do it or you need to make complex solutions to implement it, and what about SQL queries - it will look very complex. So you need to handle it somehow - the solution is inheritance. Doing this you can create associations between classes in very easy way. If you will not have many info (or let's say complex tables schema with a lot of info) you can use SINGLE_TABLE strategy. If there will be a lot of info/columns(in your tables), your data will not be normalized with this strategy so better use JOINED or TABLE_PER_CONCRETE_CLASS strategy.

Creating a table dynamicaly with hibernate

I am learning Hibernate ORM(v. 3 ) now and I've a got a question.
I have a table called USERS, created with annotations :
package com.hibernatedb.entities;
import javax.persistence.*;
#Entity
#Table(name = "USERS",uniqueConstraints = {#UniqueConstraint(columnNames={"USER_LOGIN", "USER_EMAIL"})})
public class User {
#Column(name = "USER_LOGIN", length=80, nullable=false)
private String login;
#Column(name = "USER_PASS", length=80, nullable=false)
private String password;
#Column(name = "USER_EMAIL", length=80, nullable=false)
private String email;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "USER_ID", nullable=false)
private Long id;
...
// some getters and setters, toString() and other stuff
...
}
And a Product entity :
#Entity
#Table(name = "PRODUCTS",uniqueConstraints = {#UniqueConstraint(columnNames={"PRODUCT_ID", "PRODUCT_NAME"})})
public class Product {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="PRODUCT_ID")
private long id;
#Column(name="PRODUCT_NAME", length=85, nullable=false)
private String name;
#Column(name="PRODUCT_DESCRIPTION", columnDefinition="mediumtext", length=1000)
private String description;
#Column(name="PRODUCT_COST", nullable=false)
private double cost;
So my question is : How can a create a table called like "USER +
User.getId()
BUYS", which contains a 2 foreign keys (USER_ID and PRODUCT_ID) for user in entity (table record) "User" without raw SQL table creation, but using Hibernate annotations or XML mapping.So i want to have something like
public class TransactionEntityBulider() {
public TransactionEntityBulder(User user)
// something that build me "USER + User.getId() BUYS" table and
}
public TransactionEntity getEntity() {
// something that return a "USER + User.getId() BUYS" table entity
}
Also i would like to see some another ways to solve my problem.
I think hibernate is not done for that kind of usage, because you would have to use dynamic mappings. Hibernate provide ways to specify mapping statically (xml and annotations).
I suggest you modify your approach. It normally should not be harmfull to have all the "USER_BUY" in the same table. Example :
#Entity
public class User {
...
#OneToMany(cascade=CascadeType.ALL, mappedBy="user")
List<UserBuys> buys = new ArrayList<UserBuys>();
...
}
#Entity
public class Product { ... }
#Entity
public class UserBuys {
...
#ManyToOne
Product product;
#ManyToOne
User user;
}

Categories