I have a Project and Employee entities, which has ManyToMany relationship like below.
#Entity
public class Project {
#Id #GeneratedValue
private int projectId;
private String projectName;
// has some additional columns
#ManyToMany(mappedBy = "projects")
private List<Employee> emp = new ArrayList<Employee> ();
....
.....
}
#Entity
public class Employee {
#Id #GeneratedValue
private int id;
private String firstName;
private String lastName;
#ManyToMany(cascade=CascadeType.ALL)
List<Project> projects = new ArrayList<Project> ();
....
....
}
When I use above entities, JPA create a mpping table 'Employee_Project' like below.
create table Employee_Project (emp_id integer not null, projects_projectId integer not null)
My question is, whenever new employee is added, I want to update both employee table and Employee_Project mapping table only, assume I know project id that I would like to map this employee to. (without touching project table/entity, I mean why should I provide complete project object, while saving employee entity alone, how can I do this via jpa?)
You don't need to provide the entire Project object. Use EntityManager.getReference(projectId) or JpaRepository.getOne(projectId).
Those methods will create a proxy object with the appropriate id, rather than loading the entire Project entity from the data store.
EDIT Your service method should look pretty much like the following:
#Transactional
public void createEmployee(Employee employee, Long projectId) {
employee.setProjects(List.of(projectRepository.getOne(projectId));
employeeRepository.save(employee);
}
As a side note, CascadeType.ALL (in particular, because it includes CascadeType.MERGE and CascadeType.REMOVE) doesn't make sense for #ManyToMany. Unless you're planning to create a Project by creating an Employee, CascadeType.PERSIST makes no sense, either.
Related
I'm starting a project to know more in detail JPA.
Context:
At the end of his internship, the student has a report to make and a presentation in front of his professor to do about the internship.
I've a database, which is called "grade_management". It must contains a "student", "presentation", "report", "professor" and a "mark" (there are several rating criteria such as expression, quality of powerpoint ...) table. But now it's empty, since I want to make it throught JPA.
I've a "Presentation" class. Which countain this:
#Entity
public class Presentation implements Serializable {
#Id
#GeneratedValue (strategy=GenerationType.AUTO)
private int presentation_id;
private Date date;
private mark_id;
private int professor_id;
public Soutenance() {}
public Soutenance(Date date) {
this.date = date;
}
}
But the Presentation table contain 2 foreign key: professor_id and mark_id.
My question is: How can I indicate that both of them are foreign key ?
I'm sorry if I'm not clear, don't hesitation to ask question.
Cordially
You shouldn't reference other entities by their ID, but by a direct reference to the entity.
Something like that :
#ManyToOne
#JoinColumn(name = "mark_id", referencedColumnName = "id")
private Mark mark; // supposed here that mark_id if link to entity `Mark`
#ManyToOne
#JoinColumn(name = "professor_id", referencedColumnName = "id") // suppose "id" is the column name of the PK inside the table Professor.
private Professor professor; // supposed here that professor_id if link to entity `Professor`
This code is supposing that you use an unidirectional relation.
For bidirectional you have to define this in the other side (Mark/Professor type)
#OneToMany(mappedBy = "professor")
private Presentation presentation;
From your explanation, it looks like you have a Database named grade_management and in that database you have "student", "presentation", "report", "professor" and a "mark" tables (i.e: which are #Entity by themselves defined in their separate respective classes )
I'm not sure whether you have defined them or not. If not then you have to define them first and then use the refactored code mentioned below.
So, you will have many-to-one relation mapping. You can annotate your foreign keys belonging to different tables using #ManyToOne annotation to indicate relation type and #JoinColumn annotation to indicate that this entity has a foreign key to the referenced table.
You can redefine your Presentation class show below:
#Entity
#Table(name = "Presentation")
public class Presentation implements Serializable {
#Id
#Column(name="presentation_id")
#GeneratedValue (strategy=GenerationType.AUTO)
private int presentation_id;
private Date date;
#ManyToOne
#JoinColumn(name = "mark_id")
private Mark mark_id;
#ManyToOne
#JoinColumn(name = "professor_id")
private Professor professor_id;
public Soutenance() {}
public Soutenance(Date date) {
this.date = date;
}
//getter and setter
}
Also, if you need more information to read upon for yourself you can always checkout this Hibernate Documentation that explains everything you'll need to know.
Given this very simple DTO:
#Entity
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
#OneToOne
private Employee boss;
}
I'd like to make a query that gathers all employee names and their boss' id, put in a nice clean POJO:
public class EmployeeInfo {
private String name;
private Long bossId;
public EmployeeInfo(String name, Long bossId) {
this.name = name;
this.bossId = bossId;
}
}
This query should be of use:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<EmployeeInfo> query = cb.createQuery(EmployeeInfo.class);
Root<Employee> root = query.from(Employee.class);
query.select(
cb.construct(EmployeeInfo.class,
root.get("name").as(String.class),
root.get("boss").get("id").as(Long.class)));
result = em.createQuery(query).getResultList();
When a bossId is present in the employee column this works just fine. But when no boss id is set the record will be completly ignored. So how do i treat this non existing boss relation as null or 0 for the construct/multiselect?
In pure SQL it is easy:
SELECT name, COALESCE(boss_id, 0) FROM EMPLOYEE;
But for the love of god i cannot make the criteria api do this.
cb.construct(EmployeeInfo.class,
root.get("name").as(String.class),
cb.coalesce(root.get("boss").get("id").as(Long.class), 0L)));
The problem is that root.get("boss") generate query with cross join like this from Employee employee, Employee boss where employee.boss.id=boss.id. So records where employee.boss.id is null are ignored.
To solve the problem you should use root.join("boss", JoinType.LEFT) instead of root.get("boss")
Let's assume the domain model of an application that is supposed to be built from scratch is described as follows:
A Person might live at an address. A Person can own multiple cars.
If I had to design the database first, I would probably come up with the following database design (normalization, cascading, etc. are not supposed to play a major role for my concrete question).
Person (id, name)
Address (id, street, zip, city, person_id)
Car (id, manufacturer, yearBuilt, color, person_id)
I have mainly followed standard design concepts (e.g. described in this link http://db.grussell.org/section006.html).
As you can see the address table has a foreign key to the person table as the person - address relationship can be considered optional.
The fact that a person can own multiple cars is implemented by putting a foreign key to a person in the car table. I think this is the standard way of modelling 1..m relationships.
If I had to design the domain model first, I would probably come up with the following design:
public class Person {
private String name;
private Address address;
private List<Car> cars;
// Getters and setters
}
public class Address {
private String street;
private String zip;
private String city;
// Getters and setters
}
public class Car {
private String color;
private Date yearBuilt;
// Getters and setters
}
In that domain model, the Person class has all necessary relationships. The Address and Car classes do not need to know anything about their owning Person.
I could now turn these classes into JPA entities by adding #Entity and providing an #Id attribute for every class.
#Entity
public class Person implements Serializable {
#Id
private Long id;
private String name;
#OneToOne
private Address address;
#OneToMany
private List<Car> cars;
public Person() { }
// Getters and setters
}
#Entity
class Address implements Serializable {
#Id
private Long id;
private String street;
private String zip;
private String city;
public Address() { }
// Getters and setters
}
#Entity
class Car implements Serializable {
#Id
private Long id;
private String color;
private Date yearBuilt;
public Car() { }
// Getters and setters
}
If my JPA provider creates the tables according to the provided annotations, the following database structure is created:
Person (id, name, address_id)
Address (id, street, zip, city)
Car (id, manufacturer, yearBuilt, color)
Person_Car (person_id, car_id)
As you can see, that does not correspond to the database structure I would create if I had to design the database first. I see a few flaws in the database model created by the JPA provider:
As the person - address relationship is optional, I would have put the foreign key to a Person into the Address table and not vice versa.
As the standard way of modelling a 1..m relationship is to put the foreign key of the owning class into the detail class, I would have never come up with a relation or association table. Why would I want to have that if the relation is not described by additional attributes?
To join a Person to a Car, the JPA provider needs to perform an additional join to the relation or association table. Does this measurably decrease the performance?
What I could do now is to provide the JPA entity classes with additional fields and/or annotations to strive for the database structure one might expect.
Is it desirable to strive for a domain model/JPA entity design that is able to create an expected database structure (as if database-first-approach was used)? If so, is it acceptable to have a domain model that is different from a domain model one would create intuitively? What are the advantages in designing a domain model/JPA entity model that will create some sort of "best practice" database structure?
Update the mapping from Person to Car as below:
#OneToMany
#JoinColumn(name = "person_id")
private List<Car> cars;
the DDL generator of your JPA provider should then create the desired table structure.
I am writing small app, using Play Framework 2.0 which uses Ebean as ORM.
So I need many-to-many relationship between User class and UserGroup class.
Here is some code:
#Entity
public class User extends Domain {
#Id
public Long id;
public String name;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public Set<UserGroup> groups = new HashSet();
}
#Entity
public class UserGroup extends Domain {
#Id
public Long id;
public String name;
#ManyToMany(mappedBy="groups")
public Set<User> users = new HashSet();
}
Database scheme generator generates good scheme for that code with intermediate table and all work quite ok, till I using many-to-many.
So I am adding group in one request:
user.groups.add(UserGroup.find.byId(groupId));
user.update();
And trying output them to System.out in another:
System.out.println(user.groups);
And this returns:
BeanSet deferred
Quick search show that BeanSet is lazy-loading container from Ebean. But seems like it doesn't work in proper way or I missed something important.
So is there any ideas about what I am doing wrong?
You need to save associations manually
user.groups.add(UserGroup.find.byId(groupId));
user.saveManyToManyAssociations("groups");
user.update();
Say I have this class:
#Entity
#Table(name="PICTURE")
public class Picture{
private String category1, category2;
}
but the database structure looks like this:
TABLE PICTURE {
int category1;
int category2;
...
}
TABLE PICTURE_REF {
int category;
String categoryName;
...
}
How would I use JPA annotations on Picture so that any time I request an instance of it, category1 and category2 contains the categoryName from the PICTURE_REF table instead of the actual integer id stored in the PICTURE table?
I'm also wondering how saves would work because the user would select a category from a dropdown and the corresponding category integer ID would be what's stored in the PICTURE table.
From your description , PICTURE.category1 and PICTURE.category2 have the many-to-one relationship to the PICTURE_REF
The following shows the bi-directional mapping between them using annotation:
For table PICTURE:
#Entity
#Table(name="PICTURE")
public Class Picture{
#Id
private Integer id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "category1")
private PictureRef category1,
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "category2")
private PictureRef category2;
//getter and setters
}
For table PICTURE_REF:
#Entity
#Table(name="PICTURE_REF")
public Class PictureRef{
#Id
private Integer id;
#OneToMany(mappedBy = "category1")
List <Picture> listOfCat1Picture= new ArrayList<Picture>();
#OneToMany(mappedBy = "category2")
List <Picture> listOfCat2Picture= new ArrayList<Picture>();
//getter and setters
}
Important Points:
#Entity marks the java class as an hibernate entity. It is mapped to the name of the table specified in the #Table
Use #ManyToOne to define the many-to-one relationship
In the relational database , many-to-one relationship is expressed by using the following foreign key constraint:
"Many side table" has a FK column which only accepts the PK of the "one side table".
In your case , these FK columns are PICTURE.category1 and PICTURE.category2. The name of these FK columns can be explicitly defined by the name attribute of #JoinColumn.
FetchType.EAGER makes that PictureRef will be eagerly fetched whenever Picture is loaded or get
Depending on your requirement , you can do the unidirectional mapping by omitting #OneToMany in the PictureRef.It will also work .But given PictureRef , you cannot access its Picture
Given a Picture instance , you can get its categoryName and categoryId by
picture.getCategory1().getCategoryName()
picture.getCategory1().getId()
picture.getCategory2().getCategoryName()
picture.getCategory2().getId()
If you can't modify the schema
You can modify your mapping so Category is an entity instead of just a String. Then you would have a OneToOne or (more likely) a ManyToOne from Picture to Category for category1 and category2.
If you CAN modify the schema
You can use an ElementCollection on Picture to store a List instead of having category1 and category2. This would give you a schema something like
TABLE PICTURE {
long key;
}
TABLE PICTURE_CATEGORY {
long picture_key;
String category_name;
}
OR you can again map Category to an entity and use a ManyToMany from Picture to Category which would give you a schema like
TABLE PICTURE {
long key;
...
}
TABLE PICTURE_CATEGORY {
long picture_key;
long category_key;
}
TABLE CATEGORY {
long key;
String name;
}
As for saving, you will can use a converter in whatever your view technology is that will converter from key to Category, or you can load the Category from the key in your controller and set it in the Picture before you save. I doubt you'll want saving a Picture to cascade into a Category.