寫Java時很多人會使用Spring Data JPA,這是一個非常方便的資料庫Connection與Mapping的框架,它可以幫你建立資料表、添加資料表的欄位、維護連接池、將Java物件轉換為Table資料、將Table資料轉換為Java物件、透過命名就自動完成各種方便的新刪修查。

問題

在建立DAO的時候,通常我們會寫一個interface來繼承Repository,Spring就會自動按照Interface裡面的方法名稱完成該完成的事情,讓開發變得非常愉快。但Repository有非常多種,例如CrudRepository、ListCrudRepository、PagingAndSortingRepository等,裡面會有不同的預設Method。

有時候我們會希望自己建立一個我們的公版Interface,讓他繼承某一個Repository再添加一些方法,然後之後的DAO Interface再繼承我們的公版Interface,可以簡化更多程式碼。但你直接寫一個Interface去繼承Repository,編譯的時候Spring就會告訴你不行,例如在啟動時出現以下訊息。

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'IBaseRepository' defined in ... defined in @EnableJpaRepositories declared on AchvServerApplication: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Not a managed type: class java.lang.Object

說明

會出現這些錯誤是因為我們寫了一個Interface,雖然繼承了Repository卻沒有指定用在哪一個Entiry Class上,解決方式也很簡單,在Interface上標註@NoRepositoryBean就可以了。如果你點開Spring Data JPA內建的CrudRepository等Interface,會看到上面也是標注了@NoRepositoryBean喔!

以下寫一個範例自訂我們自己的Repository。

範例程式

package tw.klab.demo.dao;

import java.util.List;

import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;

@NoRepositoryBean
public interface IBaseRepo<T, ID> extends PagingAndSortingRepository<T, ID> {
    T findById(ID id);
    List<T> findAll();
}

在此範例中繼承了PagingAndSortingRepository,因此可以得到許多方便的分頁與排序功能,然後定義了T findById(ID id)讓透過ID尋找資料時返回的是資料型態,而不是Optional;定義了List<T> findAll()讓findAll返回的是List而不是Iterable,並且在Class前面加上了@NoRepositoryBean,讓Spring知道這個Repository不是真的要成為Bean。

使用時可以向以下這樣使用它。

package tw.klab.demo.dao;

import tw.klab.demo.model.User;

public interface UserDao extends IBaseRepo<User, String> {
}

Spring Data JPA真的很棒,以上三行程式碼就可以讓我們的程式碼可以對一個User資料表進行各種新刪修查了😄。