Training, Workshops, Softwareentwicklung

Hibernate Tutorial

Unidirektionales ManyToOne

Bei einer unidirektionalen ManyToOne Beziehung bildet man nur in der Invoice die Beziehung zum Customer ab. Auch hier gibt es Überraschungen.

Das unidirektionale Mapping in den Entities sieht so aus:

Mapping
@Entity
public class Invoice {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "inv_gen")
    @SequenceGenerator(name = "inv_gen", sequenceName = "invoice_seq", allocationSize = 1)
    private Long id;
    private String invoiceNo;
    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) (1)
    private Customer customer;
}
1 @ManyToOne bestimmt das Mapping. cascade steuert, welche Operationen von der Invoice auf den Customer kaskadiert werden sollen. Die Default-Einstellung für den FetchType ist hier EAGER, deswegen geben wir den FetchType explizit an.

Unser Setup hat erst einmal nur eine Invoice und einen Customer:

Setup
Customer customer = new Customer();
customer.setFirstname("Buck");
customer.setLastname("Rogers");

Invoice invoice = new Invoice();
invoice.setInvoiceNo("1234");

invoice.setCustomer(customer);
em.persist(invoice);

Das n+1 Problem zeigt sich auch hier:

Test
TypedQuery<Invoice> query =
        em.createQuery("SELECT i FROM Invoice i", Invoice.class);
List<Invoice> resultList = query.getResultList(); (1)

for (Invoice invoice : resultList) {
    Customer customer = invoice.getCustomer(); (2)
    assertNotNull(customer);
    assertEquals("Buck", customer.getFirstname());
}
assertSelectCount(2); (3)
1 Erst einmal werden nur die Invoices geladen.
2 Der Customer wird an dieser Stelle nachgeladen.
3 Wir sehen 2 (n+1) Selects!
Query:["select invoice0_.id as id1_1_, invoice0_.customer_id as customer3_1_, invoice0_.invoiceNo as invoiceN2_1_ from Invoice invoice0_"]
Query:["select customer0_.id as id1_0_0_, customer0_.firstname as firstnam2_0_0_, customer0_.lastname as lastname3_0_0_ from Customer customer0_ where customer0_.id=?"]

Das n+1 Problem können wir mit einem JOIN FETCH lindern:

Join Fetch
TypedQuery<Invoice> query =
        em.createQuery("SELECT i FROM Invoice i LEFT JOIN FETCH i.customer", Invoice.class);
List<Invoice> resultList = query.getResultList();

for (Invoice invoice : resultList) {
    Customer customer = invoice.getCustomer();
    assertNotNull(customer);
    assertEquals("Buck", customer.getFirstname());
}
assertSelectCount(1); (1)
1 Wir sehen nur noch einen Select

Das Lazy Loading erfordert nur einen getter für den Customer, es funktioniert dann auch mit Field-Access:

Field Access
@Getter
@Entity
@Access(AccessType.FIELD)
public class Invoice {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "inv_gen")
    @SequenceGenerator(name = "inv_gen", sequenceName = "invoice_seq", allocationSize = 1)
    private Long id;
    private String invoiceNo;

    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) (1)
    private Customer customer;

    public Invoice() {
    }

    public Invoice(String invoiceNo, Customer customer) {
        this.invoiceNo = invoiceNo;
        this.customer = customer;
    }


}