JPA — Populate Child-Side parent association via proxy

Prateek
3 min read1 day ago

--

A Hibernate proxy can be useful when a child entity can be persisted with a reference to its parent (@ManyToOne or @OneToOneassociation). In such cases, fetching the parent entity from the database (execute the SELECT statement) is a performance penalty and a pointless action, because Hibernate can set the underlying foreign key value for an uninitialized proxy.

Key points:

  • rely on EntityManager#getReference()
  • in Spring, use JpaRepository#getOne() -> used in this example
  • in Hibernate, use load()
  • assume two entities, Author and Book, involved in a unidirectional @ManyToOne association (Author is the parent-side)
  • we fetch the author via a proxy (this will not trigger a SELECT), we create a new book, we set the proxy as the author for this book and we save the book (this will trigger an INSERT in the book table)

Output example:

  • the console output will reveal that only an INSERT is triggered, and no SELECT

Author.java

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Author implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private String genre;
private int age;

// setters & getters

@Override
public String toString() {
return "Author{" + "id=" + id + ", name=" + name
+ ", genre=" + genre + ", age=" + age + '}';
}
}

Book.java

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

@Entity
public class Book implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String title;
private String isbn;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
private Author author;

// setters & getters

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (getClass() != obj.getClass()) {
return false;
}
return id != null && id.equals(((Book) obj).id);
}

@Override
public int hashCode() {
return 2021;
}

@Override
public String toString() {
return "Book{" + "id=" + id + ", title=" + title + ", isbn=" + isbn + '}';
}

}

AuthorRepo

@Repository
public interface AuthorRepository extends JpaRepository<Author, Long> {

@Transactional(readOnly=true)
Author findByName(String name);
}

BookRepository.java

@Repository
public interface BookRepository extends JpaRepository<Book, Long> {

@Transactional(readOnly = true)
Book findByTitle(String title);
}

BookstoreService.java

import com.bookstore.repository.BookRepository;
import com.bookstore.repository.AuthorRepository;
import com.bookstore.entity.Author;
import com.bookstore.entity.Book;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BookstoreService {

private final AuthorRepository authorRepository;
private final BookRepository bookRepository;

public BookstoreService(AuthorRepository authorRepository,
BookRepository bookRepository) {

this.authorRepository = authorRepository;
this.bookRepository = bookRepository;
}

@Transactional
public void addBookToAuthor() {
// behind getOne() we have EntityManager#getReference()
Author proxy = authorRepository.getOne(1L);

Book book = new Book();
book.setIsbn("001-MJ");
book.setTitle("The Canterbury Anthology");
book.setAuthor(proxy);

bookRepository.save(book);
}
}

MainApp.java

import com.bookstore.service.BookstoreService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class MainApplication {

@Autowired
private BookstoreService bookstoreService;

public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}

@Bean
public ApplicationRunner init() {
return args -> {
bookstoreService.addBookToAuthor();
};
}
}

Output — As you can see only one statement is triggered.

Hibernate: insert into book (author_id, isbn, title) values (?, ?, ?)
mysql> show tables;
+-----------------------+
| Tables_in_bookstoredb |
+-----------------------+
| author |
| book |
+-----------------------+
2 rows in set (0.01 sec)

mysql> select * from book;
+----+--------+--------------------------+-----------+
| id | isbn | title | author_id |
+----+--------+--------------------------+-----------+
| 1 | 001-MJ | The Canterbury Anthology | 1 |
+----+--------+--------------------------+-----------+
1 row in set (0.01 sec)

mysql> select * from author;
+----+-----+-----------+------------+
| id | age | genre | name |
+----+-----+-----------+------------+
| 1 | 23 | Anthology | Mark Janel |
+----+-----+-----------+------------+
1 row in set (0.00 sec)

--

--

Prateek
Prateek

Written by Prateek

Java Developer and enthusiast

No responses yet