JPA - Vazební tabulky s metadaty
Nějakou dobu jsem zanedbával certifikace. Říkal jsem si, že bych mohl konečně dotáhnout JPA a v rámci přípravy jsem narazil na pár zajímavostí. Přitom jsem si zoufal, že jsem mohl předchozí projekty napsat výrazně lépe, ale utěšuje mě myšlenka, že byste měli být nespokojení se svým kódem, který jste napsali před rokem. Konkrétně chci psát o vazebních tabulkách legacy databází, které nejsou triviální, tj. neobsahují jen klíče ale i nějaká metadata.
Databáze
Je dána databáze, nad kterou stavíte aplikaci s JPA.
Naivní řešení
Jako první vás jistě napadne řešení, které jsem sám doposud používal, vytvořit entitu EmployeeProject. Funguje to, ale předpokládejme že nejčastěji vás bude zajímat přistup z entity Employee na Project, ale přitom musíte jít přes entity EmployeeProject.
Elegantní řešení
Existuje ovšem elegantnější řešení, jak se z Employee dostat přímo k entitám Project a zároveň mít případně k dispozici i metadata v embeddable objektu ProjectAllocation. (V anglické terminologii Relationship State či Association class)
import javax.persistence.*; | |
import java.util.Map; | |
@Entity | |
public class Employee { | |
@GeneratedValue(strategy = GenerationType.AUTO) | |
@Id | |
@Column(name = "EMP_ID") | |
private Integer id; | |
@Column(name = "EMP_NAME") | |
private String name; | |
@ElementCollection | |
@CollectionTable(name = "EMP_PROJECTS", joinColumns = @JoinColumn(name = "EMP_ID")) | |
@MapKeyJoinColumn(name = "PROJECT_ID") | |
private Map<Project, ProjectAllocation> currentProjects; | |
} |
CREATE TABLE EMPLOYEE ( | |
EMP_ID INT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1), | |
EMP_NAME VARCHAR (100) NOT NULL, | |
PRIMARY KEY (EMP_ID) | |
); | |
CREATE TABLE PROJECT ( | |
PROJECT_ID INT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1), | |
PRIMARY KEY (PROJECT_ID) | |
); | |
CREATE TABLE EMP_PROJECTS ( | |
EMP_ID INT NOT NULL, | |
PROJECT_ID INT NOT NULL, | |
PROJECT_COMMENT VARCHAR (200) NOT NULL, | |
PROJECT_START_DATE DATE NOT NULL, | |
PROJECT_END_DATE DATE NOT NULL, | |
PROJECT_HOURS_PER_WEEK DOUBLE NOT NULL, | |
PRIMARY KEY (EMP_ID, PROJECT_ID) | |
); |
import javax.persistence.*; | |
@Entity | |
public class Project { | |
@GeneratedValue(strategy = GenerationType.AUTO) | |
@Id | |
@Column(name = "PROJECT_ID") | |
private Integer id; | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) { | |
return true; | |
} | |
if (!(o instanceof Project)) { | |
return false; | |
} | |
Project project = (Project) o; | |
if (id != null ? !id.equals(project.id) : project.id != null) { | |
return false; | |
} | |
return true; | |
} | |
@Override | |
public int hashCode() { | |
return id != null ? id.hashCode() : 0; | |
} | |
} |
import javax.persistence.Column; | |
import javax.persistence.Embeddable; | |
import javax.persistence.Temporal; | |
import javax.persistence.TemporalType; | |
import java.util.Date; | |
@Embeddable | |
public class ProjectAllocation { | |
@Temporal(TemporalType.DATE) | |
@Column(name = "PROJECT_START_DATE") | |
private Date startDate; | |
@Temporal(TemporalType.DATE) | |
@Column(name = "PROJECT_END_DATE") | |
private Date endDate; | |
@Column(name = "PROJECT_COMMENT") | |
private String comment; | |
@Column(name = "PROJECT_HOURS_PER_WEEK") | |
private Double hoursPerWeek; | |
} |