1. 程式人生 > >SpringBoot+Hibernate Repository的簡單使用與進階

SpringBoot+Hibernate Repository的簡單使用與進階

一、簡單使用

       CrudRepository介面提供了簡單的增刪改查,只要如下,寫一個介面繼承,就可以直接使用。

public interface UserDao extends CrudRepository<User,Integer> {
}

//直接呼叫介面
User user = userDao.findById(2).get();

       類似還有PagingAndSortingRepository提供簡單的分頁與排序實現,JpaRepository提供進一步介面等,可以參考:Spring Boot學習筆記(三)Repository的使用

       到這裡其實只使用Spring而初次接觸Spring-JPA的話,其實就已經有疑問了:為什麼這裡只有一個interface繼承,卻可以直接使用介面,具體實現跑哪裡去了?

二、原理

       具體原理可以參見spring-data-jpa原理探祕(1)-執行環境建立及載入Repository介面,簡單來講,spring-data-jpa利用cg-lib,在需要注入一個實現了Repository介面的派生介面時,都會嘗試實現一個例項建立Bean注入進去。如例中的UserDao,派生自CrudRepository–>Repository,所以在注入時,會自動實現一個例項建立Bean(由工廠類來生成,該工廠類可自己實現,預設實現類為SimpleJpaRepository)注入。

       有看得仔細的會注意到,所有派生自Repository的介面,那應該包括CrudRepository啊!是的,也包括這個介面,所以又專門寫了一個註解@NoRepositoryBean,來標註不需要這種操作的介面,看原始碼的話會發現CrudRepository是添加了這個註解的。

三、進階一——自定義簡單函式

       spring-data-jpa支援自定義簡單函式,只要在interface中直接宣告函式就可以了,Respository會根據函式名,自動生成sql,如:

public interface UserDao extends CrudRepository<User,Integer> {
    List<User> findByName(String name);
}

//直接使用,無需實現函式體
List<User> list = userDao.findByName("test");
//可以檢視日誌,生成的sql,類似:
//select * from user where name=?

       而且還支援findByNameAndValue/findByNameLike等複雜邏輯,具體支援的規則及生成的SQL參見:官方文件:Spring-data-jpa Query Creation

四、進階二——自定義函式實現

       上面實現的是按Spring-data-jpa定義的規則生成函式,如果需要自己定義複雜點的實現,也提供了一種方式

  • 首先簡單定義一個CustomizedUserRepository介面以及實現CustomizedUserRepositoryImpl,除了名稱上,實現的名字必須是介面名+Impl外,沒有其他強制規則
  • 讓上例中的UserDao,同時繼承Repository

       就是這麼簡單,剩餘的事情,spring-data-jpa會幫你做,也就是說在原理中提到的自動生成例項並注入時,會將你自定義的實現也合併進去。具體程式碼如下:

public interface CustomizedUserRepository {
    User getUserByName();
}

public class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
    @PersistenceContext
    private EntityManager em;
    @Override
    public User getUserByName() {
        return em.find(User.class,2);
    }
}
public interface UserDao extends CrudRepository<User,Integer>,CustomizedUserRepository {
}

//直接使用
User user = userDao.getUserByName();

五、進階三——自定義Repository基類

       雖然Spring-data-jpa提供了詳細且方便的實現,但我們有時還是會需要自定義一些公共的函式,Spring-data-jpa也提供了實現的方法:

  • 自定義一個基礎介面BaseRepository及實現類BaseRepositoryImpl,實現自己的公共函式
  • 新增自定義Repository類配置:@EnableJpaRepositories(repositoryBaseClass = BaseRepositoryImpl.class)

       實現雖然很簡單,但也碰到了一個坑,因為官方文件上並沒有提到使用介面,介面只是為了後續自己的interface使用方便,具體看程式碼:

//下面再解釋為什麼要加@NoRepositoryBean
@NoRepositoryBean
public interface BaseRepository<T> {
    public List<T> findBySql(String sql, Object... values);
}


public class BaseRepositoryImpl<T,ID extends Serializable> extends SimpleJpaRepository<T,ID> implements BaseRepository<T> {
    private final EntityManager entityManager;

    public BaseRepositoryImpl(JpaEntityInformation entityInformation,
                              EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityManager = entityManager;
    }

    private Session getSession(){
        return entityManager.unwrap(Session.class);
    }

    private NativeQuery createSqlQuery(String sql , Object... values){
        NativeQuery query = getSession().createNativeQuery(sql);
        for(int i = 0;i < values.length;++i){
            query.setParameter(i + 1,values[i]);
        }
        return query;

    }

    public List<T> findBySql(String sql, Object... values){
        return createSqlQuery(sql,values).addEntity(getDomainClass()).list();
    }
}

public interface UserDao extends CrudRepository<User,Integer>, BaseRepository<User> {
}

//直接使用
List<User> list = userDao.findBySql("select * from user");

       這裡的坑就是為什麼BaseRepository要用@NoRepositoryBean這個註解,因為BaseRepository只是一個簡單的介面,明明什麼都沒做,為什麼要特殊處理呢?其實做了一個實驗就會知道為什麼:如果將BaseRepository重新命名為OtherRepository,但BaseRepositoryImpl保持不變,則不加@NoRepositoryBean註解也是可以的。再聯想下第四條裡的內容,就可以知道,因為我們這裡使用的介面+實現且實現名=介面名+Impl的命名規則,以及UserDao的繼承方式,和第四條中的處理都是完全一樣的,所以會被誤處理,這時,加上@NoRepositoryBean註解,就是用來取消這個錯誤處理的!        所以SpringBoot有很多自動配置,確實可以減少大量工作,但用不好的話,很難除錯及發現。        官方文件這部分的內容較少,參見:4.6.2. Customize the Base Repository