Trong Spring Boot JPA chúng ta có 2 giao diện API để tương tác với dữ liệu:
- EntityManager
- 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.
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'sProxyFactory
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 ý:
- 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.
- Ở đây chúng ta vẫn định nghĩa
interface PersonRepository
để tận dụng tính năng sẵn có trongJpaRepository
kết hợp với custom method định nghĩa bởiPersonRepositoryCustom
@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:
- Bổ xung một phương thức hoàn toàn mới, tự viết vào Custom Repository
- Gọi được
EntityManager
nhờ annotation@PersistenceContext
Bình luận