As stated in the topic. Why do we need bidirectional synchronized methods? What real world use case does it solve? What happens if I don't use them?
In Hibernate's User Guide:
Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times.
The addPhone() and removePhone() are utility methods that synchronize both ends whenever a child element is added or removed.
Source - Hibernate User Guide
In one of Vlad's blog posts:
However, we still need to have both sides in sync as otherwise, we break the Domain Model relationship consistency, and the entity state transitions are not guaranteed to work unless both sides are properly synchronized.
Source - Vlad Mihalcea Blog
Lastly, in Vlad's book - High Performance Java Persistance, page 216:
For a bidirectional #ManyToMany association, the helper methods must be added to the entity that is more likely to interact with. In our case, the root entity is the Post, so the helper methods are added to the Post entity
However, if I use simple generated setters, Hibernate seems to work just fine as well. Furthermore, synchronized methods might lead to performance degredation.
Synchronized methods:
public void joinProject(ProjectEntity project) {
project.getEmployees().add(this);
this.projects.add(project);
}
Generates this:
Hibernate:
select
employeeen0_.id as id1_0_0_,
projectent2_.id as id1_2_1_,
teamentity3_.id as id1_3_2_,
employeeen0_.first_name as first_na2_0_0_,
employeeen0_.job_title as job_titl3_0_0_,
employeeen0_.last_name as last_nam4_0_0_,
employeeen0_.team_id as team_id5_0_0_,
projectent2_.budget as budget2_2_1_,
projectent2_.name as name3_2_1_,
projects1_.employee_id as employee1_1_0__,
projects1_.project_id as project_2_1_0__,
teamentity3_.name as name2_3_2_
from
employees.employee employeeen0_
inner join
employees.employee_project projects1_
on employeeen0_.id=projects1_.employee_id
inner join
employees.project projectent2_
on projects1_.project_id=projectent2_.id
inner join
employees.team teamentity3_
on employeeen0_.team_id=teamentity3_.id
where
employeeen0_.id=?
Hibernate:
select
projectent0_.id as id1_2_,
projectent0_.budget as budget2_2_,
projectent0_.name as name3_2_
from
employees.project projectent0_
where
projectent0_.id=?
Hibernate:
select
employees0_.project_id as project_2_1_0_,
employees0_.employee_id as employee1_1_0_,
employeeen1_.id as id1_0_1_,
employeeen1_.first_name as first_na2_0_1_,
employeeen1_.job_title as job_titl3_0_1_,
employeeen1_.last_name as last_nam4_0_1_,
employeeen1_.team_id as team_id5_0_1_
from
employees.employee_project employees0_
inner join
employees.employee employeeen1_
on employees0_.employee_id=employeeen1_.id
where
employees0_.project_id=?
Hibernate:
insert
into
employees.employee_project
(employee_id, project_id)
values
(?, ?)
Notice additional select for Employee right after Projects were fetched. If I use simply employeeEntity.getProjects().add(projectEntity);, it generates:
Hibernate:
select
employeeen0_.id as id1_0_0_,
projectent2_.id as id1_2_1_,
teamentity3_.id as id1_3_2_,
employeeen0_.first_name as first_na2_0_0_,
employeeen0_.job_title as job_titl3_0_0_,
employeeen0_.last_name as last_nam4_0_0_,
employeeen0_.team_id as team_id5_0_0_,
projectent2_.budget as budget2_2_1_,
projectent2_.name as name3_2_1_,
projects1_.employee_id as employee1_1_0__,
projects1_.project_id as project_2_1_0__,
teamentity3_.name as name2_3_2_
from
employees.employee employeeen0_
inner join
employees.employee_project projects1_
on employeeen0_.id=projects1_.employee_id
inner join
employees.project projectent2_
on projects1_.project_id=projectent2_.id
inner join
employees.team teamentity3_
on employeeen0_.team_id=teamentity3_.id
where
employeeen0_.id=?
Hibernate:
select
projectent0_.id as id1_2_,
projectent0_.budget as budget2_2_,
projectent0_.name as name3_2_
from
employees.project projectent0_
where
projectent0_.id=?
Hibernate:
insert
into
employees.employee_project
(employee_id, project_id)
values
(?, ?)
No more fetching of employee.
Full code.
Controller.
#RestController
#RequestMapping(path = "${application.endpoints.projects}", produces = MediaType.APPLICATION_JSON_VALUE)
#Validated
public class ProjectsEndPoint {
#PostMapping("add-employee")
#ApiOperation("Add employee to project")
public void addEmployeeToProject(#RequestBody #Valid EmployeeProjectRequest request) {
LOGGER.info("Add employee to project. Request: {}", request);
this.projectsService.addEmployeeToProject(request);
}
}
EmployeeProjectRequest.
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public record EmployeeProjectRequest(
#NotNull #Min(0) Long employeeId,
#NotNull #Min(0) Long projectId) {
}
ProjectService.
#Service
public class ProjectsService {
private final ProjectRepo projectRepo;
private final EmployeeRepo repo;
public ProjectsService(ProjectRepo projectRepo, EmployeeRepo repo) {
this.projectRepo = projectRepo;
this.repo = repo;
}
#Transactional
public void addEmployeeToProject(EmployeeProjectRequest request) {
var employeeEntity = this.repo.getEmployee(request.employeeId())
.orElseThrow(() -> new NotFoundException("Employee with id: %d does not exist".formatted(request.employeeId())));
var projectEntity = this.projectRepo.getProject(request.projectId())
.orElseThrow(() -> new NotFoundException("Project with id: %d does not exists".formatted(request.projectId())));
//This line can be changed with employeeEntity.joinProject(projectEntity);
employeeEntity.getProjects().add(projectEntity);
}
}
ProjectRepo.
#Repository
public class ProjectRepo {
private final EntityManager em;
public ProjectRepo(EntityManager em) {
this.em = em;
}
public Optional<ProjectEntity> getProject(Long id) {
var result = this.em.createQuery("SELECT p FROM ProjectEntity p where p.id = :id", ProjectEntity.class)
.setParameter("id", id)
.getResultList();
return RepoUtils.fromResultListToOptional(result);
}
}
EmployeeRepo.
#Repository
public class EmployeeRepo {
private final EntityManager em;
public EmployeeRepo(EntityManager em) {
this.em = em;
}
public Optional<EmployeeEntity> getEmployee(Long id) {
var employees = this.em.createQuery("""
SELECT e FROM EmployeeEntity e
JOIN FETCH e.projects p
JOIN FETCH e.team t
WHERE e.id = :id""", EmployeeEntity.class)
.setParameter("id", id)
.getResultList();
return Optional.ofNullable(employees.isEmpty() ? null : employees.get(0));
}
}
EmployeeEntity.
#Entity
#Table(name = "employee", schema = "employees")
public class EmployeeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
#Enumerated(EnumType.STRING)
private JobTitle jobTitle;
#ManyToOne(fetch = FetchType.LAZY)
private TeamEntity team;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinTable(schema = "employees", name = "employee_project",
joinColumns = #JoinColumn(name = "employee_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "project_id", referencedColumnName = "id"))
private Set<ProjectEntity> projects = new HashSet<>();
public EmployeeEntity() {
}
public void joinProject(ProjectEntity project) {
project.getEmployees().add(this);
this.projects.add(project);
}
public void leaveProject(ProjectEntity project) {
project.getEmployees().remove(this);
this.projects.remove(project);
}
... Getters and Setters ...
}
ProjectEntity.
Entity
#Table(name = "project", schema = "employees")
public class ProjectEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private BigDecimal budget;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "projects")
private Set<EmployeeEntity> employees = new HashSet<>();
public ProjectEntity() {
}
... Getters and Setters ...
}
If there are really many elements on the Many side, then you probably should not use OneToMany at all. Fetching large collections implies using some kind of pagination\filtering, but OneToMany loads the whole set.
First of all, you need to update an owning entity(where FK resides) to store it in the DB. And what Vlad and Hibernate guide mean about consistency, refers to updating entity objects inside current session. Those objects have transitions during lifecycle, and when you have bidirectional association, if you don't set inverse side, then that inverse side entity won't have the field updated, and would be inconsistent with an owning side entity(and probably with the DB ultimately, after TX commits) in the current session.
Let me illustrate on OneToMany example.
If we get 2 managed entities Company and Employee:
set employee.company = X -> persist(employee) -> managed List<Employee> company.employees gets inconsistent with db
And there might be different types of inconsistencies, like getting from company.employees field after and arising side-effects(guess it was not empty, but just without employee you just added), and if there is Cascade.ALL, you might miss or falsely remove\update\add entities through broken relationships, because your entities are in a ambigious state, and hibernate deals with it in a defensive but sometimes unpredictable way:
Delete Not Working with JpaRepository
Also, you might find interesting this answer: https://stackoverflow.com/a/5361587/2924122
I have been struggling with query dsl joins. My problem is with unidirectional Many to One mapping. I am unable to fetch data from both tables as query dsl is unable to find many to one relationship entity as null. As I am using latest query dsl library and spring data.
#Entity
Public class Parent {
#Id
#GeneratedValue
private Integer id;
#ManyToOne
private Child c;
// Getters and setters.
}
#Entity
public class Child {
#Id
#GenereatedValue
private Integer id;
private String name;
// Getters and setters
}
// Query dsl method.
public List<Parent> getParentsByChildName(String name){
QParent qParent = QParent.parent ;
QChild qChild = QChild.child;
return queryFactory.select(Qparent).from(qChild).innerJoin(qParent). where (qChild.name.eq(name));
}
I am trying to use Hibernate Criteria api to fetch only the topics based on the USER_ID but have no idea how to do it using the criteria.
My Tables are "topic_users" (below)
and "topics" table (below)
I know how to do it using SQL, this would be something like:
SELECT TOPICNAME
FROM topic_users INNER JOIN topics on topic_users.TOPICS_TOPICS_ID = topics.TOPICS_ID
WHERE topic_users.USER_ID = 1
This will return all TOPICNAME of USER_ID 1 which is exactly what I want but how I can do this with Hibernate Criteria. So far I have this in my Repository class (see below) but this will only return a highly nested JSON array. I could loop through the objects, use a DTO and build my response or try the Hibernate createSQLQuery method that will let me call a native SQL statement directly (haven't tried that yet)...but I am trying to learn the Criteria so I hope anyone can answer my query.
#Repository("userTopicsDao")
public class UserTopicsDaoImpl extends AbstractDao<Integer, UserTopics>implements UserTopicsDao {
#Override
public List<UserTopics> findMyTopics(int userId) {
Criteria crit = createEntityCriteria();
crit.add(Restrictions.eq("userId", userId));
List<UserTopics> userTopicsList = (List<UserTopics>)crit.list();
return userTopicsList;
}
and my TOPIC_USERS Entity where I have mapped the TOPICS
#Entity
#Table(name="TOPIC_USERS")
public class UserTopics {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="TOPICUSER_ID")
private Integer id;
#Column(name="USER_ID")
private Integer userId;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "TOPICS_ID")
private Set<Topics> topicsUser;
//getter and setters
Ok starting from the ground up.. you entity classes should look like this:
#Entity
#Table(name="TOPIC_USERS")
public class UserTopics {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="TOPICUSER_ID")
private Integer id;
#Column(name="USER_ID")
private Integer userId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "TOPICS_TOPICS_ID")
private Topics topics;
Your Topics class should look like this:
#Entity
#Table(name="TOPICS")
public class Topic {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="TOPICUS_ID")
private Integer id;
#Column(name="TOPICNAME")
private Integer topicName;
#OneToMany(mappedBy = "topics")
private Set<UserTopics> userTopics;
Finally the Criteria:
Version 1) You get entire entity:
Criteria c = session.createCriteria(Topics.class, "topics");
c.createAlias("topics.userTopics", "userTopics");
c.add(Restrictions.eq("userTopics.userId", userId));
return c.list(); // here you return List<Topics>
Version 2) You project only the topicname:
Criteria c = session.createCriteria(Topics.class, "topics");
c.createAlias("topics.userTopics", "userTopics");
c.add(Restrictions.eq("userTopics.userId", userId));
c.setProjection(Projections.property("topics.topicName"));
List<Object[]> results = (List<Object[]>)c.list();
// Here you have to manually get the topicname from Object[] table.
}
I am using spring 4.1.4.RELEASE + hibernate 4.3.6.Final, here is my entity code:
public class BaseEntity implements Serializable {
}
public class MarketInfo extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#Column(name = "market_id", unique = true, length = 15)
private String marketId;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "market")
private List<MarketChannelGroup> channelGroups;
public List<MarketChannelGroup> getChannelGroups() {
return channelGroups;
}
public void setChannelGroups(List<MarketChannelGroup> channelGroups) {
this.channelGroups = channelGroups;
}
...
}
public class MarketChannelGroup extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "market_id", referencedColumnName = "market_id")
private MarketInfo market;
...
}
From my test I can see the channelGroups in MarketInfo is working fine (if I don't call getChannelGroups(), then channelGroups is null), however if I call getChannelGroups(), the MarketInfo inside each MarketChannelGroup gets fetched, while this should not happen since market's fetch mode is FetchType.LAZY.
From console I do see the following hibernate log when I call its getter:
Hibernate: select channelgro0_.market_id as market_i5_12_1_, channelgro0_.id as id1_9_1_, channelgro0_.id as id1_9_0_, channelgro0_.channel_group_id as channel_2_9_0_, channelgro0_.channel_group_name as channel_3_9_0_, channelgro0_.channel_group_type as channel_4_9_0_, channelgro0_.market_id as market_i5_9_0_ from market_channel_group channelgro0_ where channelgro0_.market_id=?
Hibernate: select marketinfo0_.id as id1_12_0_, marketinfo0_.enable_flag as enable_f2_12_0_, marketinfo0_.enable_time as enable_t3_12_0_, marketinfo0_.market_id as market_i4_12_0_, marketinfo0_.market_name as market_n5_12_0_, marketinfo0_.stb_count as stb_coun6_12_0_ from market_info marketinfo0_ where marketinfo0_.market_id=?
Hibernate: select marketinfo0_.id as id1_12_0_, marketinfo0_.enable_flag as enable_f2_12_0_, marketinfo0_.enable_time as enable_t3_12_0_, marketinfo0_.market_id as market_i4_12_0_, marketinfo0_.market_name as market_n5_12_0_, marketinfo0_.stb_count as stb_coun6_12_0_ from market_info marketinfo0_ where marketinfo0_.market_id=?
Hibernate: select marketinfo0_.id as id1_12_0_, marketinfo0_.enable_flag as enable_f2_12_0_, marketinfo0_.enable_time as enable_t3_12_0_, marketinfo0_.market_id as market_i4_12_0_, marketinfo0_.market_name as market_n5_12_0_, marketinfo0_.stb_count as stb_coun6_12_0_ from market_info marketinfo0_ where marketinfo0_.market_id=?
could anyone help?
UPDATE
there is no optional method for ManyToOne annotation, so the solution in OneToOne doesn't work for my case.
Move the #OneToMany annotation from the declaration to the getter method, like this:
private List<MarketChannelGroup> channelGroups;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "market")
public List<MarketChannelGroup> getChannelGroups() {
return channelGroups;
}
How is your configuration archive? Look if you are using the filter OpenEntityManagerInViewFilter. If you are using it, always that you call the method Lazy getChannelGroups() the hibernate will get fetch inside each element.
You have to take oneToMany annotation to getter if #id is on the getter. Lazy works but if you or some framework get the lazy property in that transction the get method fire the select.
I got issues with my hibernate query. There are two classes, Wohnung and Kosten. Each Kosten has a relation to a Wohnung.
In my controller, kosten.getWohnung() returns null. It looks as Hibernate did not select the related Wohnung.
This is the method of my KostenDao. I assume that I do have to change the query?
public Kosten findById(int id) {
return (Kosten) getSession().createQuery("from Kosten k where k.id = " + id).uniqueResult();
}
for better understanding, here is part of my Kosten model:
#Entity
#Table(name="WNG_KOSTEN")
public class Kosten {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected int id;
// #Column(name = "INSERT_DT", nullable = false)
// #Value("${props.insertDt:SYSDATE}")
// protected java.util.Date insertDt;
#ManyToOne
#JoinColumn(name="WOHNUNG_ID")
private Wohnung wohnung;
...
Use EAGER fetch type, FetchType.LAZY is the default fetch type for all Hibernate relationships.
#ManyToOne (fetch=FetchType.EAGER)
#JoinColumn(name="WOHNUNG_ID")
private Wohnung wohnung;