Trong Spring Boot JPA chúng ta có 2 giao diện API để tương tác với dữ liệu:

  1. EntityManager
  2. Các loại Repository interfaces: CrudRepository, PagingAndSortingRepository, JpaRepository

Câu hỏi: đặt ra là đôi khi khi đang làm việc với Repository Interface, chúng ta lại muốn sử dụng EntityManager thì làm thế nào? Làm sao để bổ xung một phương thức hoàn toàn mới vào interface Repository?
Trong ví dụ này tôi sẽ định nghĩa một Person Entity, sau đó tạo ra một Custom Repository. Trong một phương thức của Custom Repository này, tôi sẽ dùng EntityManager.

Tải mã nguồn từ github

1. Cấu trúc tổng quan của ví dụ

.
├── main
│   ├── java
│   │   └── vn
│   │       └── techmaster
│   │           └── custom_repository
│   │               ├── model
│   │               │   └── Person.java <-- Định nghĩa Entity
│   │               ├── repository
│   │               │   ├── PersonRepository.java <-- Interface thao tác với bảng Person
│   │               │   ├── PersonRepositoryCustom.java <-- Định nghĩa custom interface method
│   │               │   └── PersonRepositoryImpl.java <-- Class thực hiện custom interface method
│   │               └── CustomRepositoryApplication.java
│   └── resources
│       └── application.properties <-- Cấu hình kết nối đến H2 database
└── test
    └── java
        └── vn
            └── techmaster
                └── custom_repository
                    └── AnimalRepositoryTest.java <-- JUnit5 test

2. Định nghĩa Entity Person

Chúng ta định nghĩa một JPA Entity ánh xạ xuống bảng của CSDL.

@Entity(name="person")
@Table(name="person")
@Data
@NoArgsConstructor
public class Person {
  @Id @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

  private String name;
}

và định nghĩa interface respository dành riêng cho Entity Person. Đây là cách làm truyền thông, phổ biến khi lập trình JPA repository.

@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {
   Optional<Person> findByName(String name);
}

Trong PersonRepository tôi muốn tạo ra một phương thức refresh. Chức năng của nó là truy vấn các giá trị của một bản ghi trong CSDL rồi ghi đè lên những thay đổi thuộc tính của Entity object tương ứng. CrudRepository không có sẵn phương thức này, nhưng EntityManager lại có.  Tôi cũng không muốn bỏ CrudRepository để chuyển sang EntityManager vì cho tôi nhiều phương thức có sẵn rất hữu ích như findByName(String name).

Đọc đến đây chắc hẳn bạn sẽ thắc mắc tại sao chỉ cần định nghĩa mẫu hàm findByName(String name) mà không cần viết logic thực hiện? 
Câu trả lời chi tiết ở đây How are Spring Data repositories actually implemented?
Spring's ProxyFactory API đứng phía sau interface repository.  MethodInterceptor nhận các lệnh và truyền đến những phương thức bên trong JPA để sinh ra câu lệnh SQL hoặc chuyển đến custom method do lập trình viên tự viết.

3. Tạo custom interface PersonRepositoryCustom

Bước đầu tiên là tạo một interface mới có method trùng tên với method của EntityManager mà chúng ta muốn tùy chỉnh. Trong ví dụ này, đó là method refresh. 

public interface PersonRepositoryCustom {
    void refresh(Person person);
}

Điểm mấu chốt là quy ước đặt tên cho custom interface: tên của interface phải trùng với tên Repository và kết thúc bằng từ "Custom". - trừ khi quy ước này bị ghi đè trong Spring configuration.

4. Tạo class PersonRepositoryImpl để thực hiện interface PersonRepositoryCustom

Chúng ta sẽ tạo ra một class PersonRepositoryImpl thực hiện interface PersonRepositoryCustom. Tiếp theo là tham chiếu đến EntityManager bằng annotation @PersistenceContext

public class PersonRepositoryImpl implements PersonRepositoryCustom {
    @PersistenceContext
    private EntityManager em;
    
    @Override
    @Transactional
    public void refresh(Person person) {
        em.refresh(person);
    }
}

Lưu ý, quy ước đặt tên cho class này bắt buộc phải trùng tên Repository và kết thúc bằng từ "Impl".

5. Tạo interface PersonRepository kế thừa cả CrudRepository và PersonRepositoryCustom

Ở bước này có 2 điểm thú vị các bạn cần lưu ý:

  1. Java chỉ hỗ trợ class kế thừa từ một class (single inheritance), tuy nhiên một interface có thể mở rộng nhiều hơn một interface khác.
  2. Ở đây chúng ta vẫn định nghĩa interface PersonRepository để tận dụng tính năng sẵn có trong JpaRepository kết hợp với custom method định nghĩa bởi PersonRepositoryCustom
@Repository
public interface PersonRepository extends JpaRepository<Person, Long>, PersonRepositoryCustom{
  Optional<Person> findByName(String name);  
}

Cuối cùng, chúng ta có thể refresh đối tượng sử dụng phương thực refresh được định nghĩa trong interface PersonRepositoryCustom và hiện thực hoá trong class PersonRepositoryImpl

class AnimalRepositoryTest {
	@Autowired
	PersonRepository personRepo;

	@Test
	@DisplayName("Test refresh method")
	void testRefresh() {
		Person person = new Person();
		person.setName("Cường");
		personRepo.save(person);

		personRepo.flush();
		person.setName("Long");
		personRepo.refresh(person);
		assertThat(person.getName()).isEqualTo("Cường"); //Return true
	}

}

 

6. Kết luận

Trong bài chúng ta bàn đến 2 chiêu thức:

  1. Bổ xung một phương thức hoàn toàn mới, tự viết vào Custom Repository
  2. Gọi được EntityManager nhờ annotation @PersistenceContext