Introduction
Below is a short summary of the most important things when designing JPA. For complete tutorial see https://vladmihalcea.com/tutorials/hibernate/.
@Id - Database-generated identifiers (Primary Key, PK)
The most common way to generate PK, is to let the DB generate it.
@Id
@GeneratedValue
protected Long id;
@javax.persistence.GeneratedValue(strategy = GenerationType) has 4 generation strategies:
- AUTO (Default. Do not use for MySQL, see https://vladmihalcea.com/why-should-not-use-the-auto-jpa-generationtype-with-mysql-and-hibernate/)
- TABLE (Which you should NOT use, see https://vladmihalcea.com/why-you-should-never-use-the-table-identifier-generator-with-jpa-and-hibernate/)
- SEQUENCE
- IDENTITY
Best choice is SEQUENCE, but is only supported by:
- Oracle
- SQL Server 2012
- PostgreSQL
- DB2
- HSQLDB
Next best choice is IDENTITY, which is supported by:
- Oracle 12c
- SQL Server
- MySQL (AUTO_INCREMENT)
- DB2
- HSQLDB
@OneToOne
https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
Reuse Parent PK as PK and FK in "Child", with @MapsId
@Entity
public class Parent {
@Id
@GeneratedValue
private Long id;
@OneToOne(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
private ParentDetails details;
}
@Entity
public class ParentDetails {
@Id
// @GeneratedValue No generated value, with @MapsId pk is fk
private Long id;
@OneToOne(fetch = FetchType.LAZY)
@MapsId
private Parent parent;
}
@OneToMany
https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/
Best Unidirectional @ManyToOne On the Child.
@Entity
public class Parent {
@Id
@GeneratedValue
private Long id;
}
@Entity
public class Child {
@Id
@GeneratedValue
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private Parent parent;
}
List comments = entityManager.createQuery(
"SELECT c " +
"from Child c " +
"where c.parent.id = :parentId", Child.class)
.setParameter( "parentId", 1L )
.getResultList();
Next best Bidirectional @OneToMany.
@Entity
public class Parent {
@Id
@GeneratedValue
private Long id;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List children = new ArrayList<>();
public void addChild(Child chilld) {
children.add(child);
child.setParent(this);
}
public void removeComment(Child child) {
children.remove(child);
child.setParent(this);
}
}
@Entity
public class Child {
@Id
@GeneratedValue
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private Parent parent;
// Note equals and hashCode are important to implement when working with detached objects.
// https://vladmihalcea.com/the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Child )) return false;
return id != null && id.equals(((Child) o).getId());
}
// Note equals and hashCode are important to implement when working with detached objects.
@Override
public int hashCode() {
// Database-generated identifiers
// https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
return 31;
}
}
@ManyToMany
https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/
Best Bidirectional @ManyToMany with Set instead of List. Note that relationship is managed by Post, so to add or remove Tag, you need to load Post.
@Entity
public class Post {
@Id
@GeneratedValue
private Long id;
@ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
@JoinTable(name = "post_tag",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id")
)
private Set tags = new HashSet<>();
public void addTag(Tag tag) {
tags.add(tag);
tag.getPosts().add(this);
}
public void removeTag(Tag tag) {
tags.remove(tag);
tag.getPosts().remove(this);
}
// https://vladmihalcea.com/the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Post)) return false;
return id != null && id.equals(((Post) o).getId());
}
@Override
public int hashCode() {
// Database-generated identifiers
return 31;
}
}
@Entity
public class Tag {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String name;
@ManyToMany(mappedBy = "tags")
private Set posts = new HashSet<>();
// https://vladmihalcea.com/the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tag tag = (Tag) o;
return Objects.equals(name, tag.name);
}
@Override
public int hashCode() {
// Natural identifiers
return Objects.hash(name);
}
}
Read also "The best way to map a many-to-many association with extra columns when using JPA and Hibernate" https://vladmihalcea.com/the-best-way-to-map-a-many-to-many-association-with-extra-columns-when-using-jpa-and-hibernate/
Fetching Strategy
https://vladmihalcea.com/hibernate-facts-the-importance-of-fetch-strategy/
Always use 'fetch = FetchType.LAZY' on JPA mapped relationship, UNLESS you knew that you will always need relationship objects.
IF you have EAGER relationship fetch THOSE objects with EntityManager#find(Class
When using JPQL use INNER or LEFT JOIN FETCH for fetching relationship.
See https://www.w3schools.com/sql/sql_join.asp
JavaFaker - Generating Test Data
See https://magnus-k-karlsson.blogspot.com/2019/04/generating-test-data-javafaker.html for generating test data very easily.