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.
Related
I have two entities, "User" and "Record", where the Record references a User but not by the User's primary key, but rather another column that is also unique, namely the username:
#Entity
public class User implements Serializable {
#Id
private Long id;
#NaturalId
private String username;
...
}
#Entity
public class Record {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "username", referencedColumnName = "username")
private User user;
...
}
When creating a new Record, using Hibernate's getReferenceById does not work:
#Transactional
public Record createRecord(Long userId) {
Record record = new Record()
record.setUser(userRepository.getReferenceById(userId));
return recordRepository.save(record);
}
The not-null constraint on the username column of the Record table is violated since the username is not loaded. This does make sense since the getReferenceById method of the JpaRepository interface just returns a proxy and would not return the username. Using the findById method solves this problem, but executes an additional query that I would like to avoid:
#Transactional
public Record createRecord(Long userId) {
Record record = new Record()
record.setUser(userRepository.findById(userId).orElseThrow(RuntimeException::new);
return recordRepository.save(record);
}
Is it possible to fetch an entity reference via a "natural ID" or another unique column?
Additional things to note:
The database schema is managed by Flyway, even in the test context.
Yes, I could just use the numeric ID as the foreign key reference, but I would like to instead use the username.
I know that I could also forgo using the #ManyToOne relationship all together and just use the username in the record class, but I am more interested in the general possibility of leveraging such unique non-primary key columns with Hibernate/Spring Data JPA to the same extent that IDs can be used.
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.
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.
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 have a User class:
#Entity
public class User extends Model {
#Id
public Long id;
public String email;
public String name;
public String password;
}
and a driver class
#Entity
public class Driver extends Model {
#Id
public Long id;
#OneToOne (cascade = CascadeType.ALL)
#Column(unique = true)
public User user;
}
I want to make sure that the user_id is unique inside the Drivers table. But the code above does not enforce that. (I can create multiple drivers with the same user id).
Ideally, I do not want to add the #OneToOne relations in the User class because there are several different roles inside my app (e.g. driver, teacher, agent etc.) and I don't want to pollute user class with all those relations.
How can I achieve this?
I have tried this code on the model for me, and it worked. One thing to be noted, that you must use #OneToOne annotation to let the ORM knows that you have foreign key reference to other model.
The model look like following:
#Entity
// add unique constraint to user_id column
#Table(name = "driver",
uniqueConstraints = #UniqueConstraint(columnNames = "user_id")
)
public class Driver extends Model {
#Id
public Long id;
#OneToOne
#JoinColumn(name = "user_id")
public User user;
}
It will generate evolution script like this :
create table driver (
id bigint not null,
user_id bigint,
constraint uq_driver_1 unique (user_id), # unique database constraint
constraint pk_driver primary key (id)
);
So, with this method you can make sure that you will have unique user reference on driver table.
Additional Info
Because there is an additional constraint, that is not handled by framework but by the database applied on the model (such as the unique constraint), to validate the input or handling the occurred exception, you can surround Model.save() or form.get().save() expression (saving-the-model) with try-catch block to handle the PersistenceException.