I would like to change a concrete superclass to one of its subclass. I've included an example below:
#Entity
#Table(name = "employees")
#Inheritance(strategy = InheritanceType.JOINED)
public class Employee {
#Id
#Column(name = "id")
private String id;
public Employee( String id ) {
this.id = id;
}
...
}
#Entity
#Table(name = "managers")
#PrimaryKeyJoinColumn(name = "id", referencedColumnName = "id")
public class Manager extends Employee {
public Manager( String id ) {
super(id);
}
...
}
#Entity
#Table(name = "history")
public class History {
...
/**
*
*/
#ManyToOne
#JoinColumn(name = "employee_id")
private Employee employee;
...
}
The three classes I'm working with are Employee, Manager and History. All Managers are Employees, but not all Employees are Managers. All Employees (and Managers) have a History. Employees may be promoted to Management. When this happens an Employee's history should be retained by keeping their Employee ID the same. This will allow a Manager's History through employment to be easily found.
Implementing the promotion operation is complicated by constraints withing the database: the database will not allow removing the old Employee and creating a new Manager with the same ID without removing all of the History objects by cascading operation - which Human Resources won't allow, otherwise my job would be easy!
Is it possible to add or attach the Manager (new managers) row to an existing Employee without resorting to custom SQL operation?
I've tried the following solutions without success:
public void promote( Employee employee ) {
/* copy over the ID of the employee to the manager. Will not work because employee with ID already exists */
Manager manager = new Manager(employee.getId());
this.entityManager.persist( manager );
}
... or ...
public void promote( Employee employee ) {
/* detach the employee then merge. Will not work: fails, again, with a NonUniqueObject Exception */
this.entityManager.detach( employee );
Manager manager = new Manager(employee.getId());
this.entityManager.merge( manager );
}
How can I get this done? Am I even on the right track with detach() and merge()?
Is this possible in Hibernate/JPA?
Any ideas will be helpful at this point as I'm stumped!
Aaron
As you're no doubt starting to see, you're rowing against the wind here. Unfortunately, it looks like you have a classic example of using inheritance to represent role. Both Hibernate--an Object-Relational Mapper--and Java--an Object-oriented language--are going to fight you on this one because your object model is wrong. I'd say your best bet is to fix it now with a refactoring and data migration. The end result should be that everyone is an Employee, and some Employees have some kind of "manages" relationship with one or more departments or other Employees.
I ran into a similar situation and I don't believe a flawed data model is to blame since the data model matches the real world model quite well.
You can insert the subclass record manually to achieve the desired result. Sometimes it's requisite to go around Hibernate to get something done, so this is what I did in this case as a workaround:
sessionFactory.getCurrentSession().createSQLQuery(
"insert into manager (employee_id, rank) " +
"values (:employeeId, :rank) ")
.setParameter("employeeId", employeeId)
.setParameter("rank", rank)
.executeUpdate();
Related
I was given this assignment, just for practice, it became very long and challenging, but it has taught me a lot, on lambdas and JPA mainly.
It is a basic Rest API, which is used to create Hotels, Rooms, Guests, Reservations, types of guests, types of rooms, etc.
My initial problem was learning about JPA relations, OneToOne, OneToMany, etc., unidirectional, bidirectional, and what not.
I'm also using PostgreSQL, using "sping.jpa.hibernate.ddl-auto=create-drop(or update)", change as needed, when I want to recreate the DB for whatever reason.
So I'm very happy and excited using my new #Annotations to relate my Entities, and fetch back lists of whatever information I needed, came across multiple problems, read many many questions here, solved my problems, but now I have come across a new problem, but then, started questioning my approach, maybe I should not leave everything to JPA.
Let me show you what I mean. I'm going to keep my classes short to show only relevant information.
I have my reservation entity.
#Data
#Entity
#Table(name = "reservation")
public class Reservation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "guest", referencedColumnName = "id")
#JsonManagedReference
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private Guest guest;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "room", referencedColumnName = "id")
private Room room;
#ManyToMany(fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
#JoinTable(name = "reservation_rooms",
joinColumns = { #JoinColumn(name = "reservation_id" )},
inverseJoinColumns = { #JoinColumn(name = "room_id") }
)
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private List<ReservationRoom> roomList = new ArrayList<>();
private LocalDate start_date;
private LocalDate end_date;
private Boolean check_in;
private Boolean check_out;
public void addRoom(Room room) {
this.roomList.add(room);
}
public void removeRoom(Long id) {
Room room = this.roomList.stream().filter(g -> g.getId() == id).findFirst().orElse(null);
if (room != null) {
this.roomList.remove(room);
}
}
}
This is my Room entity.
#Data
#Entity
#Table(name = "room")
public class Room {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String description;
private Integer floor;
#JsonProperty("max_guests")
private Integer maxGuests;
#ManyToOne(fetch = FetchType.LAZY)
#JsonBackReference
private Hotel hotel;
#ManyToOne(fetch = FetchType.LAZY)
#JsonProperty("type")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private RoomType roomType;
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Room)) {
return false;
}
return id != null && id.equals(((Room) o).getId());
}
#Override
public int hashCode() {
return getClass().hashCode();
}
}
And this is my Guest entity.
#Data
#Entity
#Table(name = "guest")
public class Guest {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String first_name;
private String last_name;
private String email;
#ManyToOne(fetch = FetchType.LAZY)
#JsonProperty("type")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private GuestType guest_type;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "guestList"
)
#JsonBackReference
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private List<Reservation> reservationList = new ArrayList<>();
public Guest(){}
public Guest(Long id) {
this.id = id;
}
public List<Reservation> getReservationList() {
return reservationList;
}
public void setReservationList(List<Reservation> reservationList) {
this.reservationList = reservationList;
}
}
At the beginning a reservation could only have 1 room, but the requirement changed and it can have multiple rooms now. So now, the guest list needs to be linked to the room linked to the reservation, and not directly to the reservation. (I know I have a Guest and a Room, and also the List of both, this is because I'm using the single Guest as the name for the reservation, and the single Room, as the "Main" room, but don't mind that please).
Letting JPA aside, because every challenge I have faced I would ask my self "how to do it JPAish?", and then research how to do it with JPA (that's how I learned about the #ManyToMany, etc. annotations).
What I would do is just create a new table, to relate the reservations to the room (which is already done in my entities with JPA), and then add also de guest id.
So, this new table, would have a PK with reservation_id, room_id and guest_id. Very easy, then create my Reservation model, which have a List of Room, and this Room model, would have a List of Guest. Easy.
But I don't want to add a List of Guest in my current Room entity, because I have an endpoint and maybe a couple of other functions, which retrieves my Room entity, and I don't want to add a List of Guest, because as the time passes, this list would grow bigger and bigger, and it is information you don't need to be passing around.
So I did some research and found that I can extend my entity with #Inheritance or #MappedSuperclass, and I could create maybe a Reservation_Room model, which includes a List of Guest and add a List of Reservation_Room instead of a List of Room in my Reservation Entity, which I really wouldn't know if it is even possible.
Having said that, and before I keep researching and start making modifications to my code, it got me wondering, if this would be the right approach? Or if I'm forcing JPA too much on this? What would be the best approach for this? Can a 3 id relation table be easily implemented/mapped on JPA?
The main goal would be to have my Room entity exposed as it is, but when a Room is added to a Reservation, this Room would also have a List of Guest. Can I do this JPAish? Or should I create a new model and fill with the information as needed? This wouldn't exempt me from creating my 3 ids table.
Based on what you wrote here, I think you might be at a point where you are realizing that the persistence model doesn't always match the presentation model, which you use in your HTTP endpoints. This is usually the point where people discover DTOs, which you also seem to have heard of.
DTOs should be adapted/created to the needs of the representation of an endpoint. If you don't want to expose certain state, then simply don't declare a getter/field for that data in a DTO. The persistence model should simply be designed in a way, so that you can persist and query data the way you need it. Translation between DTOs and entities is a separate thing, for which I can only recommend you to give Blaze-Persistence Entity Views a try.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Reservation.class)
public interface ReservationDto {
#IdMapping
Long getId();
GuestDto getGuest();
List<RoomDto> getRooms();
}
#EntityView(Guest.class)
public interface GuestDto {
#IdMapping
Long getId();
String getName();
}
#EntityView(Room.class)
public interface RoomDto {
#IdMapping
Long getId();
String getName();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
ReservationDto a = entityViewManager.find(entityManager, ReservationDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<ReservationDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
I would say that you need to add a layer between persistence and the endpoints. So, you will have Controllers/Services/Repositories (in the Spring world). You should use entities as return type from Repositories (so used them in Services as well), but return DTOs to Controllers. In this way, you will decouple any modification that you do between them (e.g. you may lose interest to return a field stored in an entity, or you may want to add more information to the dto from other sources).
In this particular case, I would create 4 tables: Reservations, Guests, Rooms, GuestsForReservation.
Guests will contain id + guests data (name, phone number, etc)
Rooms will contain id + room data
GuestsForReservation will contain id + reservationId + guestId (so you can get the list of guests for each reservation). FK for reservationId and guestId, PK for synthetic id mentioned.
Reservations will contain id (synthetic), room id, date from, date to, potentially main guest id (it could be the person paying the bill, if it makes sense for you). No link to the GuestForReservation table, or you can have a list of GuestForReservation if you need to.
When you want to reserve a room, you have a ReservationRequest object, which will go to the ReservationService, here you are going to query the ReservationRepository by roomId and dates. If nothing is returned, you create the various entities and persist them in ReservationRepository and GuestForReservation repository.
By using the service and the combination of various repositories, you should be able to get all the information that you need (list of guests per room, list of guests per date, etc). At the service level, you then map the data you need to a DTO and pass it to the controller (in the format that you need), or even to other services (depending on your needs).
For what concern the mapping between entities and DTOs, there are different options, you could simply create a Component called ReservationMapper (for example) and do it yourself (take an entity and build a DTO with what you need); implements Converter from the Springframework; use MapStruct (cumbersome in my opinion); etc.
If you want to represent in JPA an id made of multiple columns, usually #Embeddable classes are used (you should mark them as EmbeddedId when you use them), you can google them for more info.
We are trying to save many child in a short amount of time and hibernate keep giving OptimisticLockException.
Here a simple exemple of that case:
University
id
name
audit_version
Student
id
name
university_id
audit_version
Where university_id can be null.
The java object look like:
#Entity
#Table(name = "university")
#DynamicUpdate
#Data
#Accessors(chain = true)
#EqualsAndHashCode(callSuper = true)
public class University {
#Id
#SequenceGenerator(name = "university_id_sequence_generator", sequenceName = "university_id_sequence", allocationSize = 1)
#GeneratedValue(strategy = SEQUENCE, generator = "university_id_sequence_generator")
#EqualsAndHashCode.Exclude
private Long id;
#Column(name = "name")
private String name;
#Version
#Column(name = "audit_version")
#EqualsAndHashCode.Exclude
private Long auditVersion;
#OptimisticLock(excluded = true)
#OneToMany(mappedBy = "student")
#ToString.Exclude
private List<Student> student;
}
#Entity
#Table(name = "student")
#DynamicUpdate
#Data
#Accessors(chain = true)
#EqualsAndHashCode(callSuper = true)
public class Student {
#Id
#SequenceGenerator(name = "student_id_sequence_generator", sequenceName = "student_id_sequence", allocationSize = 1)
#GeneratedValue(strategy = SEQUENCE, generator = "student_id_sequence_generator")
#EqualsAndHashCode.Exclude
private Long id;
#Column(name = "name")
private String name;
#Version
#Column(name = "audit_version")
#EqualsAndHashCode.Exclude
private Long auditVersion;
#OptimisticLock(excluded = true)
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "university_id")
#ToString.Exclude
private University university;
}
It seem when we assign university and then save Student, if we do more than 4 in a short amount of time we will get the OptimisticLockException.
It seem hibernate is creating update version on the University table even though the University didn't change at the db level.
UPDATE: code that save the student
Optional<University> universityInDB = universidyRepository.findById(universtityId);
universityInDB.ifPresent(university -> student.setUniversity(university);
Optional<Student> optionalExistingStudent = studentRepository.findById(student);
if (optionalExistingStudent.isPresent()) {
Student existingStudent = optionalExistingStudent.get();
if (!student.equals(existingStudent)) {
copyContentProperties(student, existingStudent);
studentToReturn = studentRepository.save(existingStudent);
} else {
studentToReturn = existingStudent;
}
} else {
studentToReturn = studentRepository.save(student);
}
private static final String[] IGNORE_PROPERTIES = {"id", "createdOn", "updatedOn", "auditVersion"};
public void copyContentProperties(Object source, Object target) {
BeanUtils.copyProperties(source, target, Arrays.asList(IGNORE_PROPERTIES)));
}
We tried the following
#OptimisticLock(excluded = true)
Doesn't work, still give the optimistic lock exception.
#JoinColumn(name = "university_id", updatable=false)
only work on a update since we don't save on the update
#JoinColumn(name = "university_id", insertable=false)
work but don't save the relation and university_id is always null
Change the Cascade behaviour.
The only one value that seem to made sense was Cascade.DETACH, but give a org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing.
Other solution we though of but are not sure what to pick
Give the client a 409 (Conflict) error
After the 409 the client must retry his post.
for a object sent via the queue the queue will retry that entry
later.
We don't want our client to manage this error
Retry after a OptimisticLockException
It's not clean since when the entry come from the queue we already doing it but might be the best solution so far.
Make the parent owner of the relationship
This might be fine if there are not a big number of relation, but we have case that might go in the 100 even in the 1000, which
will
make the object to big to be sent on a queue or via a Rest call.
Pessimistic Lock
Our whole db is currently in optimisticLocking
and we managed to prevent these case of optimisticLocking so far, we
don't want to change our whole locking strategy just because of this
case. Maybe force pessimistic locking for that subset of the model
but I haven't look if it can be done.
It does NOT need it unless you need it.
Do this:
University universityProxy = universidyRepository.getOne(universityId);
student.setUniversity(universityProxy);
In order to assign a University you don't have to load a University entity into the context. Because technically, you just need to save a student record with a proper foreign key (university_id). So when you have a university_id, you can create a Hibernate proxy using the repository method getOne().
Explanation
Hibernate is pretty complex under the hood. **When you load an entity to the context, it creates a snapshot copy of its fields and keeps track if you change any of it**. It does much more... So I guess this solution is the simplest one and it should help (unless you change the `university` object somewhere else in the scope of the same session). It's hard to say when other parts are hidden.
Potential issues
wrong #OneToMany mapping
#OneToMany(mappedBy = "student") // should be (mappedBy = "university")
#ToString.Exclude
private List<Student> student;
the collection should be initialized. Hibernate uses it's own impls of collections, and you should not set fields manually. Only call methods like add() or remove(), or clear()
private List<Student> student; // should be ... = new ArrayList<>();
*overall some places are not clear, like studentRepository.findById(student);. So if you want to have a correct answer it's better to be clear in your question.
If you enable your query logs from Hibernate, it would be worthwhile to see the queries that your ORM is performing. You'll likely realize that your ORM is doing too much.
In your application properties or config file enable hibernate.show_sql=true
I wouldn't be surprised if your single update to a Student becomes an update to a University which becomes an update to all of its containing Students. Everything gets a version bump.
ORM and entity mappings are for strategically retrieving data. They should not be used to actually define object relationships.
You'll want to visit strategies and design your entities based on how they are used in their REST endpoints.
You specified in your question that you are trying to save a Student but you're noticing that the University also gets updated along with every Student update.
Likely there would never be a time when a Student should ever update a University
Keep your entities lean!
You can structure your entity in such a way that supports this unidirectional relationship. I removed some of the annotation just to demonstrate the structure. You will want to keep in mind that when creating entities, you are writing them for how they are retrieved...
public class University {
#Id
private Long id;
private String name;
private Long auditVersion;
#OneToMany
private List<Student> student;
}
public class Student {
#Id
private Long id;
private String name;
private Long auditVersion;
private Long universityId;
}
This will ensure that updates to the student remains targeted and clean. You are simply assigning a university id to the student therefore establishing that relationship.
You typically want to respect LockExceptions. Retrying upon a LockException is simply bullying your database into submission and will cause more headaches as your application scales.
You always have the option to work with lean entities and create custom response or message objects that would zip the results together.
ORMs are not to be used to create shortcuts
The performance consequence of a SELECT on an indexed/foreign key is roughly the same cost of grabbing them joined... you only introduce a little extra network latency. A second trip to the database is not always a bad idea. (Often times, this is exactly how Hibernate fetches your entities)
You won't have to write queries, but you will still need to understand the retrieval and update strategies.
You're sacrificing database performance and introducing complexity for a convenient .getChild() method. You'll find that you resolve more performance/locking issues by removing annotations, not adding them.
I am building a blog system, and like to provide the upvote/downvote feature for the blog. Since the vote count number of blog should be persisted, i choose to use MySQL to act as the data store. And i use Spring JPA(Hibernate) to do the ORM job. Here's my data objects:
class Blog{
// ...
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(optional = false, fetch = FetchType.EAGER)
#PrimaryKeyJoinColumn
private BlogVoteCounter voteCounter;
}
And the counter class:
#Entity
public class BlogVoteCounter extends ManuallyAssignIdEntitySuperClass<Long> {
#Id
private Long id;
private Integer value;
}
The reason why i separate the BlogVoteCounter from Blog is that i think the voteCount field will be modified by a totally different frequency comparing to other fields of Blog, since i want to use cache to cache the Blog, following this guide, i choose to separate them.
However, since the VoteCount field might be always needed when return the Blog object to the front end, and to avoid the n+1 problem, i declared the BlogVoteCounter field in Blog class with EAGER fetch type.
I've already seen this article. Thus according to my personal comprehension, i use unidirectional relationship and only declare OneToOne in the Blog side.
However, when i examine the query, it turns out that jpa will still trigger a secondary query to retrieve BlogVoteCounter from database without simply using a join when use findAll method on BlogRepository.
select
blogvoteco0_.id as id1_2_0_,
blogvoteco0_.value as value2_2_0_
from
blog_vote_counter blogvoteco0_
where
blogvoteco0_.id=?
So how should i config, to always make the BlogVoteCounter field in Blog be fetched eagerly.
The usage of ManuallyAssignIdEntitySuperClass is following the Spring JPA doc, since i manually assign id for BlogVoteCounter class.
#MappedSuperclass
public abstract class ManuallyAssignIdEntitySuperClass<ID> implements Persistable<ID> {
#Transient
private boolean isNew = true;
#Override
public boolean isNew() {
return isNew;
}
#PrePersist
#PostLoad
void markNotNew(){
this.isNew = false;
}
}
And the BlogRepository is derived from JpaRepository
public interface BlogRepository extends JpaRepository<Blog, Long>{
// ...
}
I trigger the query by using findAll method, but using findById or other conditional query seems no difference.
When to fetch vs How to fetch : fetchType defines when to fetch the association ( instantlyvs later when someone access) the association but not how to fetch the association(i.e second select vs join query). So from JPA Spec point of view, EAGER means dont wait until someone access that field to populate it but JPA provider is free to use JOIN or second select as long as they do it immediately.
Even though they are free to use join vs second select, still I thought they should have optimised for join in the case of EAGER. So interested in finding out the logical reasoning for not using the join
1. Query generated for repository.findById(blogId);
select
blog0_.id as id1_0_0_,
blog0_.vote_counter_id as vote_cou2_0_0_,
blogvoteco1_.id as id1_1_1_,
blogvoteco1_.value as value2_1_1_
from
blog blog0_
inner join
blog_vote_counter blogvoteco1_
on blog0_.vote_counter_id=blogvoteco1_.id
where
blog0_.id=?
2. Updated Mapping
public class Blog {
#Id
private Long id;
#ManyToOne(optional = false, cascade = ALL, fetch = FetchType.EAGER)
#PrimaryKeyJoinColumn
private BlogVoteCounter voteCounter;
public Blog() {
}
public Blog(Long id, BlogVoteCounter voteCounter) {
this.id = id;
this.voteCounter = voteCounter;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public BlogVoteCounter getVoteCounter() {
return voteCounter;
}
public void setVoteCounter(BlogVoteCounter voteCounter) {
this.voteCounter = voteCounter;
}
}
3. Issues with current Mapping
As per your mapping, it is impossible to create blog and votecounter as it causes a chicken and egg problem.
i.e
blog and votecounter need to share the same primary key
blog's primary key is generated by database.
so in order to get the primary key of blog and assign it to votecounter as well, you need to store blog first
but the #OneToOne relationship is not optional, so you cannot store blog first alone
4.Changes
Either need to make the relationship optional so blog can be stored first, get the id, assign to BlogVoteCounter and save the counter
Or Don't auto generate Id and manually assign the id so blog and votecounter can be saved at the same time.(I have gone for this option but you can do first option)
5.Notes
default repository.findAll was generating 2 queries so I overridden that method to generate one join query
public interface BlogRepository extends JpaRepository<Blog, Long> {
#Override
#Query("SELECT b from Blog b join fetch b.voteCounter ")
List<Blog> findAll();
}
select
blog0_.id as id1_0_0_,
blogvoteco1_.id as id1_1_1_,
blog0_.vote_counter_id as vote_cou2_0_0_,
blogvoteco1_.value as value2_1_1_
from
blog blog0_
inner join
blog_vote_counter blogvoteco1_
on blog0_.vote_counter_id=blogvoteco1_.id
I have a ManyToMany relationship established with a join table that is exactly like the one described on the Java Persistence wiki Example of a ManyToMany relationship annotations. Using that wiki example of Employees & Projects as a reference, the code listed in the example works fine to create three tables: EMP, PROJ and the EMP_PROJ join table. What I would like to do is use that EMP_PROJ join table in a separate entity. I don't want to add additional columns to the EMP_PROJ join table. For example, suppose an administrator associates projects with an employee. That list is stored in EMP_PROJ. What I would like to do is create a separate entity called ManagerReport that will return, for an employee, the list of projects associated with that employee. The manager can then enter specific info regarding each project for the employee, like start date, end date, performance, etc.
Below are the tables and sample code pulled from the wiki page.
EMPLOYEE (table)
ID FIRSTNAME LASTNAME
1 Bob Way
2 Sarah Smith
EMP_PROJ (table)
EMP_ID PROJ_ID
1 1
1 2
2 1
PROJECT (table)
ID NAME
1 GIS
2 SIG
#Entity
public class Employee {
#Id
#Column(name="ID")
private long id;
...
#ManyToMany
#JoinTable(
name="EMP_PROJ",
joinColumns={#JoinColumn(name="EMP_ID", referencedColumnName="ID")},
inverseJoinColumns={#JoinColumn(name="PROJ_ID", referencedColumnName="ID")})
private List<Project> projects;
...
}
You will have to create a ManagerReportProject entity which maps to EMP_PROJ table with #Table annotation.
In Employee entity, instead of #ManyToMany mapping for a collection of Projects use #OneToMany mapping for collection mapping to ManagerReportProject entities.
You will still be able to get a list of employee's projects because each ManagerReportProject further points to Project. You can even create a helper getProjects() method inside Employee to get a list of projects. Method must be annotated with #Transient to mark getProjects() method as not JPA persitent (by default all properties or fields inside Entity mappings are persitent)
#Entity
public class Employee
{
#Id
#Column(name="ID")
private long id;
...
#OneToMany...
private List<ManagerReportProject> managerReportProjects;
...
/*
YOU DON'T NEED THIS ANYMORE:
#ManyToMany
#JoinTable(
name="EMP_PROJ",
joinColumns={#JoinColumn(name="EMP_ID", referencedColumnName="ID")},
inverseJoinColumns={#JoinColumn(name="PROJ_ID", referencedColumnName="ID")})
private List<Project> projects;
*/
#Transient
public List<Project> getProjects()
{
List<Project> projects = new ArrayList<Project>();
for(ManagerReportProject managerReportProject: managerReportProjects)
{
projects.add(managerReportProject.getProject());
}
return projects;
}
...
}
ManagerReportProject should point to Employee, Project and Manager entity with #ManyToOne association.
Put manager report specific columns into ManagerReportProject (start date, end date, performance, etc.).
ManagerReportProject maps to EMP_PROJ table with #Table annotation.
#Entity
#Table(name= "EMP_PROJ")
public class ManagerReportProject{
#Id
#Column(name="ID")
private long id;
//manager report columns
private Date startDate;
private Date endDate;
performance, etc.
#ManyToOne...
private Manager manager;
...
#ManyToOne...
private Employee employee;
#ManyToOne...
private Project project;
...
}
Create a Manager entity, use #OneToMany for collection mapping to ManagerReportProject entities:
#Entity
public class Manager {
#Id
#Column(name="ID")
private long id;
...
#OneToMany...
private List<ManagerReportProject> managerReportProjects;
...
}
Now you can enter specific info regarding each project for the employee, like start date, end date, performance, etc.
This is a sketch just to demonstrate an idea of how to edit existing manager report for a specific empolyee working for a specific manager:
Emyployee employee = ...
Manager manager = ...
List<ManagerReportProject> managerReportProjects= employee.getManagerReportProjects()
for(ManagerReportProject managerReportProject: managerReportProjects )
{
if(manager.equals(managerReportProject.getManager()))
{
Project project = managerReportProject.getProject();
managerReportProject.setStartDate(...);
managerReportProject.setEndDate(...);
managerReportProject.setperformance(...);
...
}
}
I want to create Many-One Mapping between two tabels, Expense(ID, NAME, CATEGORY) and
Category(ID, NAME).
In my class i have created a field 'Category category' and its setters and getters.
I did them after seeing some stuff from internet. What are all the changes i have to do in my Category.java class. For now, its looks like,
public class Category{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int catId;
private String catName;
public Category() {
}
public int getCatId() {
return this.catId;
}
public void setCatId(int catId) {
this.catId = catId;
}
public String getCatName() {
return this.catName;
}
public void setCatName(String catName) {
this.catName = catName;
}
}
I dont want to do mappings with xml config. I think, annotations is good for a beginner like me.
And my Old! SQL query looks like,
SELECT EXPENSES.EXPNS_ID, EXPENSES.CAT_ID, EXPENSES.NAME, CATEGORY.CAT_NAME FROM EXPENSES INNER JOIN CATEGORY ON EXPENSES.CAT_ID = CATEGORY.CAT_ID WHERE USER_NAME="+currentUserName
How to use inner join in Hibernate?
Any Suggestions!!
Thanks!
Update
Thanks for all answerers,
I tried what you told and it returns a empty list.
To, test i set the 'userName=Tamil' which is in the table.
The query generated by Hibernate is looks like below,
select expens0_.expnsId as expnsId1_, expens0_.catId as catId1_, expens0_.category_catId as category7_1_, expens0_.userName as userName1_ from Expens expens0_ inner join Category category1_ on expens0_.category_catId=category1_.catId where expens0_.userName=?
As a beginner, i have some doubts in JPQL, I want catName from Category[catId, catName] table. And the catId is also available in Expens[expnsId, catId, userName].
By adding the below lines in Expens.java class, how it will give me catName along with the other variables in the Expens table.
#ManyToOne
private Category category
// getters, setters
I cant able to understand it. Without understanding this i cant move further, i have to give more mappings in my project. If clear with this mapping, i can move to the rest with confidence.
The query i used is pascal's version: Query query = hSession.createQuery("SELECT e FROM Expens e JOIN e.category c WHERE e.userName = :userName").setParameter("userName", userName);
For me, the query generated by hibernate is looks like same as my Old SQl query. I cant able to find problem here.
Actually, a big part of the documentation that would be useful in your case is located in the Hibernate Annotations Reference Guides (links provided below). Reading it would be very worth it.
That being said, regarding your specific question, the simplest possible mapping would be:
#Entity
public class Expense {
#Id #GeneratedValue
private Long;
#ManyToOne
private Category category
// getters, setters
...
}
That's all.
If you want to make it bi-directional, you'll have to add a OneToMany on the other side (and don't forget the mappedBy element since the association is bidirectional):
#Entity
public class Category {
#Id #GeneratedValue
private Long id;
#OneToMany(mappedBy="category")
private Set<Expense> expenses = new HashSet<Expense>();
....
}
And a possible JPQL query would be:
SELECT e FROM Expense e JOIN e.category c WHERE e.username = :username
Update: Hibernate and JDBC are different. With Hibernate, you need to think objects and the above HQL query (which was more an example) will actually return a List<Expense>. To get a category name, iterate over the results and navigate through the association. For example:
List<Expense> expenses = ... // some code to retrieve a list by username
for (Expense expense : expenses) {
System.out.println(expense.getCategory().getName());
}
References
2.2. Mapping with JPA (Java Persistence Annotations)
2.2.5.2. Many-to-one
As Bozho suggested,
#ManyToOne(fetch=FetchType.EAGER) // Gonna be eager by default anyway
#JoinColumn(name="CATEGORY_ID")
private Category category;
Plus this in your Category class to make it bidirectional,
#OneToMany(mappedBy="category")
private List<Expense> expense;
You need not do an inner join like that. When you query the expense, the related category will automatically get loaded eagerly, most likely using join.
In your Expense class have:
#ManyToOne
#JoinColumn(name="CATEGORY_ID")
private Category category
As pointed in the comments, if you need to access all expenses in a given category, i.e. have the one-to-many relationship, you can have:
#OneToMany
private List<Expense> expenses;
I, for example, prefer to use as little #OneToMany mappings as possible - you'd have to manager eager/lazy loading, at some point limiting the number of results, etc. For them I tend to use HQL queries that fetch the subset of objects (expenses in your case) that I need.