JPA many to one relation - java

I have a question regarding ManyToOne relationship.
Assume I have 2 beans:
#Entity
#Table(name = "accounts")
public class Account {
#Id
#Column(name = "account_id")
private int account_id;
}
#Entity
#Table(name = "broker_account")
public class BrokerAccount {
#Id
#Column(name = "broker_account_id")
private int broker_account_id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="account_id", referencedColumnName = "account_id")
private Account account;
}
I am querying the entity below (plain get all query)
entityManager.createQuery("from BrokerAccount", BrokerAccount.class)
I thought that if I query BrokerAccount entity the account_id column will be populated by default on the Account object, since it exists in the BrokerAccount table as well, however all the Account fields are empty.
Am I missing something, should I define this field/column on the BrokerAccount entity itself as well to get its value?

You have defined the Account association as #ManyToOne(fetch = FetchType.LAZY). This means that while performing the entityManager.createQuery("from BrokerAccount", BrokerAccount.class), there will be no join on the Account and its data will not be fetched at that time.
In order to make the persistence provider fetch the Account data you would need to interact with the reference while being in the same transactional method, f.e.: brokerAccount.getAccount().getAccountId();
If you want to simply have a repeated column for the fk you can do:
#Column(name = "account_id", insertable=false, updatable=false)
private int account_id;

Related

Eager fetch performs left join in hibernate but fires seperate sql queries in springboot/JPA

I see a lot of posts where Eager fetch performs left join of child table parent table in hibernate. But when I use springboot , hibernate fires seperate sql queries - means one select query for parent table and one select query for child table. Why is there a difference? Has there been any upgrades in springboot or is it something I am doing wrong ?
Below are the entities I am using:
Order Entity:
#Entity
#Table(name="Ordertable", schema="cf_2583f365_c3c6_499a_a60d_138e7e7023eb")
public class Order {
#Id
#Column(name = "ORDER_ID")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int orderId;
#Column(name = "DAT_SRC_ID")
private String dataSourceId;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "ORDER_CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
private Customer customer;
}
Customer Entity:
#Entity
#Table(name="Customer", schema="cf_2583f365_c3c6_499a_a60d_138e7e7023eb")
public class Customer {
#Id
#Column(name = "CUSTOMER_ID")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long customerId;
#Column(name = "CUSTOMER_NAME")
private String customer_name;
#Column(name = "CUSTOMER_address_id")
private int customer_address_id;
#Column(name = "DAT_SRC_ID")
private String dataSourceId;
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name = "ORDER_CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
private List<Order> order;
}
Controller:
#RequestMapping(value="/getByCustid/{id}", method=RequestMethod.GET,produces=MediaType.APPLICATION_JSON_VALUE)
public Customer getByCustid (#PathVariable Long id) {
Customer s1 = customerRepository.findByCustomerId(id);
return s1;
}
Repository:
public interface CustomerRepository extends JpaRepository<Customer,Long> {
public Customer findByCustomerId(Long customerId);
}
Below are the queries that are getting executed:
select
customer0_.CUSTOMER_ID as CUSTOMER1_0_,
customer0_.CUSTOMER_address_id as CUSTOMER2_0_,
customer0_.CUSTOMER_NAME as CUSTOMER3_0_,
customer0_.DAT_SRC_ID as DAT_SRC_4_0_
from
Customer customer0_
where
customer0_.CUSTOMER_ID=?
select
order0_.ORDER_CUSTOMER_ID as ORDER_CU3_5_0_,
order0_.ORDER_ID as ORDER_ID1_5_0_,
order0_.ORDER_ID as ORDER_ID1_5_1_,
order0_.ORDER_CUSTOMER_ID as ORDER_CU3_5_1_,
order0_.DAT_SRC_ID as DAT_SRC_2_5_1_
from
Ordertable order0_
where
order0_.ORDER_CUSTOMER_ID=?
EDIT : is this related to #Fetch(FetchMode.JOIN) -- Link:JPA eager fetch does not join
Hibernate: Multiple select queries made by Hibernate for Fetch mode Eager
To check whether FetchMode.JOIN works, I have added FetchMode.JOIN in entities as shown below but still no success with Join Query:
Customer Entity:
#Entity
#Table(name="Customer", schema="cf_2583f365_c3c6_499a_a60d_138e7e7023eb")
public class Customer {
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name = "ORDER_CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
#Fetch(FetchMode.JOIN)
private List<Order> order;
}
Order Entity:
#Entity
#Table(name="Ordertable", schema="cf_2583f365_c3c6_499a_a60d_138e7e7023eb")
public class Order {
#JsonIgnore
#ManyToOne()
#JoinColumn(name = "ORDER_CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
#Fetch(FetchMode.JOIN)
private Customer customer;
}
The findByCustomerId will actually generate a query based on that method instead of using em.find. It will create something along the lines of SELECT c FROM Customer c WHERE c.customerId=:customerId. afterwards it will notice the fetch strategy and obtain the needed references. This is also explained here. The query will do exactlly what you instruct it to do.
If you want to eagerly load the reference you would need to write the query yourself along the lines of SELECT c FROM Customer c JOIN FETCH c.orders o WHERE c.customerId=:customerId, this will automatically retrieve the orders.
However the customerId is actually the primary key or identitifier for your entity and thus you should actually be using the findById or findOne method (depending on your Spring Data JPA version). This will use the EntityManager.find which should take the mapping information into account and create the appropriate query.

Java hibernate OneToOne: which side should be the owner

So i'm learning from these simple examples, there're 2 tables, USERS and USER_DETAILS, simple enough, each user has user_details and it's 1-to-1 relationship. So this sample is like this,
#Entity
#Table(name = "USERS")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USR_ID")
private long id;
#Column(name = "USERNAME", nullable = false, unique = true)
private String username;
#Column(name = "PASSWORD")
private String password;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "user", fetch = FetchType.LAZY)
private UserDetail userDetail;
//Setter and getter methods
}
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USR_DET_ID")
private long id;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "EMAIL")
private String email;
#Column(name = "DBO")
private LocalDate dob;
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "USR_ID")
private User user;
//Setter and Getter methods
}
If you look at mappedBy, it's in the User not UserDetails.
Q1: so USER is the owner, if it calls save(),
USER_DETAILS table will be updated as well ?
Q2: same examples put mappedBy in the USER_DETAILS side,
why people want to do this ?
How to determine which side to put mappedBy ?
Thanks for your help !
Q2: same examples put mappedBy in the USER_DETAILS side,
why people want to do this ?
How to determine which side to put mappedBy ?
In a bidirectional relationship, each entity has a relationship field
or property that refers to the other entity. Through the relationship
field or property, an entity class’s code can access its related
object. If an entity has a related field, the entity is said to “know”
about its related object.
There is a bidirectional one-to-one relationship in your example. Both User and UserDetail entities have a relationship field. #OneToOne annotation specified on both the entities.
For one-to-one bidirectional relationships, the owning side
corresponds to the side that contains the corresponding foreign key.
The owner of the relationship is UserDetail entity. The owner has #JoinColumn annotation to specify foreign key (USR_ID).
Inverse side of relationship (User) has mappedBy attribute.
Q1: so USER is the owner, if it calls save(),
USER_DETAILS table will be updated as well ?
In your example UserDetail is the owner. Therefore the saving process:
User user = new User(); // Ignoring the constructor parameters...
UserDetail userDetail = new UserDetail();
user.setUserDetail(userDetail);
userDetail.setUser(user);
userRepository.save(user);
You only need to save the parent. It will save the child as well.

How to manage OnetoOne inserting data in child only

I am very new to hibernate and I am working with JPA and Hibernate4. Trying to insert parent object in child as onetoone relationship.
I went through some tutorials but All the example in the web shows, inserting both parent and child tables.
I want to insert data in child table only.
I have two tables called user and department.
User table consists of user details with department as onetoone relationship, as follows,
#Entity
#Table(name = "User")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "_id")
private String id;
#Column(name = "firstName")
private String firstName;
#Column(name = "lastName")
private String lastName;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "departmentId")
private Department departmentId;
// getters and setters...
}
Below is my Department entity,
#Entity
#Table(name = "Department")
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "_id")
private String id;
#Column(name = "name")
private String name;
// getters and setters...
}
In department table there is only 4 data. I want to insert data only in user data while insert into it and don't want to insert in Department.
How can I do that.Please assist.
You have to use mappedBy for this, as mentoned below in child Table, Department in your case
#OneToOne(mappedBy="department")
private UserEntity user;
These posts explain you better this,
JPA JoinColumn vs mappedBy
Understanding mappedBy annotation in Hibernate
You need to specify the relationship owner using mappedBy property in the OneToOne mapping in the owner side, here in your case in the Department class, you should add:
#OneToOne(mappedBy="department")
private UserEntity user;
I updated your code, to included the stated annotation and also renamed the Department property in your UserEntity class from departmentId to department to avoid confusion between relationship owner and its id:
#Entity
#Table(name = "User")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "_id")
private String id;
#Column(name = "firstName")
private String firstName;
#Column(name = "lastName")
private String lastName;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "departmentId")
private Department department;
// getters and setters...
}
Below is the Department entity,
#Entity
#Table(name = "Department")
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "_id")
private String id;
#Column(name = "name")
private String name;
#OneToOne(mappedBy="department")
private UserEntity user;
// getters and setters...
}
This will give you the right mapping with the expected behaviour.
In the #OneToOne annotation, the default value for parameter optional is true. So your annotation is the same as #OneToOne(fetch = FetchType.EAGER, optional = true). This means you can simply leave the Department in a UserEntity instance empty. In that case, persisting it results in persisting only a user entity and no department.
Even if you created a Department instance and assigned it to a UserEntity instance, persisting the UserEntity would not automatically persist the Department, since you don't have any cascade parameter in your annotation. If you don't automatically cascade persists, you would have to persist the Department first and then persist the corresponding user entity.
Maybe you're asking about using existing departments for your user entities. In that case, you first need to get the department via Hibernate (or the JPA API) from an entity manager. The entity instance you get is managed by Hibernate, and you can then set it in a UserEntity and persist that, to have it refer to the department.
Finally, I think one department will probably have more than one user. It might make more sense to have a #ManyToOne annotation instead of #OneToOne, indicating multiple users can refer to the same department, but that depends on your domain model.

Eliminating extra join in JPA query by just using the FK column instead of the related entity's ID

I have an entity relationship such that:
STUDENT many-to-one STUDENT_COURSE one-to-many COURSE
Basically, there's a many-to-many relationship between students and their courses. That relationship is represented by the STUDENT_COURSE table.
Assume I have entities set up for STUDENT, STUDENT_COURSE, and COURSE such that:
#Entity
#Table(name = "STUDENT")
public course Student {
#Id
#Column(name = "ID", nullable = false)
private Long id;
#OneToMany(mappedBy = "student")
private Set<StudentCourse> studentCoursees;
// ... other fields and getters and setters
}
#Entity
#Table(name = "COURSE")
public course Course {
#Id
#Column(name = "ID", nullable = false)
private Long id;
#OneToMany(mappedBy = "course")
private Set<StudentCourse> studentCourses;
// ... other fields and getters and setters
}
#Entity
#Table(name = "STUDENT_COURSE")
public course StudentCourse {
#Id
#Column(name = "ID", nullable = false)
private Long id;
#ManyToOne
#JoinColumn(name = "STUDENT_ID", referencedColumnName = "ID")
#NotNull
private Student student;
#ManyToOne
#JoinColumn(name = "COURSE_ID", referencedColumnName = "ID")
#NotNull
private Course course;
// ... other fields and getters and setters
}
Then I have a complicated criteria query I'm creating for search purposes that wants all of the students for a particular course. I have the courseId that I want to add to the restriction. In SQL, I'd do something like this:
select *
from STUDENT, STUDENT_COURSE
where STUDENT.ID = STUDENT_COURSE.STUDENT_ID
and STUDENT_COURSE.COURSE_ID = <courseId>
Notice that I'm only joining STUDENT and STUDENT_COURSE. With criteria and the entities set up as described above, it seems like I'm forced to join STUDENT, STUDENT_COURSE, and COURSE because I don't have a courseId field on STUDENT_COURSE:
Join<Person, PersonCourse> personCourse = root.join("personCourses");
Join<PersonCourse, Course> course = personCourse.join("course");
Predicate onlySpecificCourse = builder.equal(course.get("id"), courseId);
Is this just something where I should have BOTH the #ManyToOne field from StudentCourse to Course AND the courseId field on StudentCourse? It looks like I can do this if I declare the courseId field as:
#Column(name = "USER_ID", insertable = false, updatable = false)
private String userId;
And then the joining becomes:
Join<Person, PersonCourse> personCourse = root.join("personCourses");
Predicate onlySpecificCourse = builder.equal(personCourse.get("courseId"), courseId);
If I do this, are there any gotchas that I should watch out for? In particular, it seems strange to have setters for both courseId and course on the PersonCourse entity.
Update
I am updating my answer to offer you a solution, even though I don't like it. :-)
But first, it sounds like you wish to do this in a OOP way, but you don't want to think of the persisted data as an Object Tree, in that Person, PersonCourse and Course are all part of the same object tree, yet for some special cases, you would like to forget that fact. You can only push ORM up to a certain point, after which you will have to fall back on a native SQL.
However, I will offer an ORM solution here which you may not like, so here it goes:
Add a new attribute to PersonCourse entity and map it to the COURSE_ID column in the join table. But you have to ensure that new attribute is not used in inserts and updates.
#Column(name = "COURSE_ID", insertable = false, updatable = false)
private Long courseId;
And now you can just remove the Course Root from the equation and just use the Predicate that you showed above.
Original answer
If STUDENT_CLASS table has no other columns besides the IDs for STUDENT and CLASS relations, then just use #ManyToMany between Student and Class entities, instead of #ManyToOne, and you don't need a third entity; Hibernate will take care of it for you.
If the join table does have other columns, for example GRADE or RATING columns, then use a solution like the one described here: Mapping many-to-many association table with extra column(s).

Annotation to join columns with OR condition instead of AND condition

I have 2 java classes, Relation and Person, which both are present in my database.
Person:
#Entity
#Table(name = "persons")
public class Person {
#Id
#Column
private int id;
#Column
private String name;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "slave_id", referencedColumnName="id"),
#JoinColumn(name = "master_id", referencedColumnName="id")
})
private List<Relation> relations;
//Getters and setters
}
Relation:
#Entity
#Table(name = "relations")
public class Relation {
#Id
#Column
private int id;
#Column
private int child_id;
#Column
private int parent_id;
#Column
private String type;
//Getters and setters
}
Each Person has a list of relations (or not), the relation should be added to the list when the child_id or the parent_id of the relation is equal to the id of the person.
TL;DR:
When relation.child_id OR relation.parent_id = person.id => add relation to list of relations to the person
The issue I am facing is that this annotation:
#JoinColumns({
#JoinColumn(name = "child_id", referencedColumnName="id"),
#JoinColumn(name = "parent_id", referencedColumnName="id")
})
creates following SQL (just the necessary part):
relations relations6_
on this_.id=relations6_.slave_id
and this_.id=relations6_.master_id
What is the correct annotation in Java Hibernate to generate an SQL statement saying OR instead of AND
Some of the options that you could utilize:
Database views. Create the view that does custom join for you and map the entity to the view.
Join formula. I managed to make them work only on many-to-one associations. Nevertheless, you could make the association bidirectional and apply the formula in the Relation entity.
#Subselect. This is a kind of Hibernate view, suitable if you can't afford to create a real database view or change the db schema to better suit the entity model structure.
This and this answer could also be helpful.
Also, you can always use two separate associations for slaves and masters:
public class Person {
#OneToMany
#JoinColumn(name = "slave_id"),
private List<Relation> slaves;
#OneToMany
#JoinColumn(name = "master_id"),
private List<Relation> masters;
public List<Relation> getRelations() {
List<Relation> result = new ArrayList<>(slaves);
result.addAll(masters);
return result;
}
}
However, keep in mind that joining all of them in a single query requires full Cartesian product between masters and slaves.
You can use #FilterDef and #Filter annotations.

Categories