A Hibernate proxy can be useful when a child entity can be persisted with a reference to its parent (@ManyToOne
or @OneToOne
association). 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
andBook
, 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 anINSERT
in thebook
table)
Output example:
- the console output will reveal that only an
INSERT
is triggered, and noSELECT
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)