April 16, 2019

Vlad Mihalcea High-Performance Hibernate Tutorial

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 entityClass, Object primaryKey) NOT JPQL. Using JPQL overrides EAGER relationship, which makes sense sense JPA is doing what it is told.

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.

No comments: