Ở bài trước, tôi trình bày Bổ sung phương thức vào JPA repository / tham chiếu đến EntityManager trong Repository . Bản chất là tôi đã khai báo một custom repository interfacre, rồi tạo một class hiện thực hoá các method bổ xung... Giờ tôi còn muốn hơn thế. Vì method bổ xung có thể áp dụng cho nhiều kiểu Entity khác nhau nên tôi muốn tạo một Custom Repository, có phương thức do tôi viết, nhưng dùng cho nhiều kiểu Entity khác nhau.
Tải về ví dụ mẫu ở đây

1. Tình huống đặt ra

Tôi có 2 Entity là Person và Car. Người nhập liệu sẽ truy vấn bản ghi Person hoặc Car từ bảng tương ứng trong cơ sở dữ liệu, rồi sửa đổi một số trường thông tin. Đôi khi do gõ nhầm, nên người nhập liệu muốn reset lại những thay đổi chưa lưu xuống CSDL, bằng các giá trị đã lưu trong CSDL trước đó. Việc này thường xuyên xảy ra với rất nhiều bảng. Ở bài trước tôi sử dụng phương thức EntityManager.refresh để đổ dữ liệu từ một bản ghi trong CSDL vào một đối tượng có cùng id. Còn bài này tôi sẽ tạo Custom Interface Repository với kiểu tổng quát (generic type). Đây là định nghĩa 2 Entity Car và Person làm mẫu.

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

  private String name;
}

Cấu trúc tổng thể của dự án. Bạn nhớ chạy Unit Test vì tôi code ví dụ mẫu trong Unit Test

.
├── main
│   ├── java
│   │   └── vn
│   │       └── techmaster
│   │           └── custom_repository
│   │               ├── model <-- Định nghĩa Entity
│   │               │   ├── Car.java 
│   │               │   └── Person.java
│   │               ├── repository <-- Định nghĩa các Repository
│   │               │   ├── CarRepository.java
│   │               │   ├── CustomRepo.java
│   │               │   ├── CustomRepoImpl.java
│   │               │   └── PersonRepository.java
│   │               └── CustomRepositoryApplication.java
│   └── resources
│       └── application.properties
└── test
    └── java
        └── vn
            └── techmaster
                └── custom_repository
                    └── RepositoryTest.java <-- Hãy chạy ví dụ minh hoạ trong Test Case

2. Khai báo Generic Interface Repository nhận tham số kiểu T tổng quát

Tôi khai báo một custom interface repository có mẫu hàm refresh. Tham số kiểu tổng quát cho entity là T.

public interface CustomRepo<T>{  
  void refresh(T entity);  
}

3. Tạo class thực hiện Generic Interface Repository

Tôi tạo một class có tên là  CustomRepoImpl. Chú ý tên của class buộc phải tuân theo quy ước InterfaceName + "Impl" . Nếu không tuân thủ quy tắc này, khi biên dịch sẽ gặp lỗi kiểu như thế này

Failed to create query for method public abstract void
vn.techmaster.custom_repository.repository.RepositoryCustom.refresh(java.lang.Object)! 
No property refresh found for type Person!
public class CustomRepoImpl<T> implements CustomRepo<T>{
  @PersistenceContext
  private EntityManager em;

  @Override
  @Transactional
  public void refresh(T entity) {
    em.refresh(entity);    
  }  
}

4. Tạo ra interface  repository ứng với từng Entity cụ thể

Ở đây generic interface CustomRepo được truyền tham số kiểu PersonCar

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

Trong file RepositoryTest.java tôi chỉ cần inject Bean có kiểu PersonRepository và CarRepository vào.

class RepositoryTest {
	@Autowired
	PersonRepository personRepo;

	@Autowired
	CarRepository carRepo;
}

5. Tổng kết

  1. Chúng ta có thể tạo, bổ xung phương thức vào Custom Repository. Kết hợp tính năng của EntityManager với tính năng của JpaRepository
  2. Custom Repository có tham số kiểu tổng quát sẽ áp dụng được cho nhiều kiểu Entity khác nhau. Code sẽ đẹp, gọn hơn rất nhiều.
  3. Class thực hiện Custom Interface Repository cần đặt tên theo quy ước  thì khi biên dịch mới không báo lỗi.
  4. Bạn cứ chịu khó vào trang blog techmaster.vn mỗi ngày, bạn sẽ thấy một tip tricks lập trình Java, Spring Boot và nhiều công nghệ khác.