1. 程式人生 > >spring-boot中jpa使用心得

spring-boot中jpa使用心得

小編是從python轉到java的,因此小編對python世界中的sqlalchemy和django-orm的牛逼和方便記憶有心。轉到java以後,發現java世界裡也有類似的工具,只不過說實話,跟python相比,確實有點弱。java中,提供資料庫ORM功能的工具叫做JPA。在spring中,專門有一個專案叫做spring-data-jpa用來提供對jpa的支援。我理解jpa只是一個標準,通常使用的jpa的實現是hibernate,這就是為啥預設情況下,當在pom裡引入spring-data-jpa的時候,會自動引入hiberate。

廢話不多說,我們先來看看spring-data-jpa是如果簡化我們的開發的,請看以下程式碼。這段程式碼中,我們只需定義一個擴充套件自JpaRepository的介面,而在該介面中,我們只需要按照spring-data-jpa給定的規則來生成一個函式宣告即可。例如該介面中,findById就等於原生的SQL : select * from data_center_info where id = ?

public interface DataCenterInfoDao extends JpaRepository<DataCenterInfo, Long>,
        JpaSpecificationExecutor<DataCenterInfo> {
    /**
     * Find by id optional.
     *
     * @param id the id
     * @return the optional
     */
    Optional<DataCenterInfo> findById(Long id);
}

一開始感覺這種方式還是挺方便的,但是用著用著發現,這樣的方式功能有點不健全的,例如:

  1. 無法實現join操作
  2. 只能返回List<DataCenterInfo> 或者 DataCenterInfo 這樣的物件,如果我想返回物件中某個欄位呢? 瞎了……
  3. 函式的名字一長,真的讓人有點暈乎啊

對於上述的問題,jpa也提供了原生的SQL方式來彌補這樣的問題,例如:

public interface DomainRecordHistoryDao extends JpaRepository<DomainRecordHistory, Serializable> {

    List<DomainRecordHistory> findByDomainNameAndEnterpriseIdAndCreateTime(String domainName, String enterpriseId, Date date);

    @Query
(value = "select distinct(create_time) from domain_record_history where domain_name = ?1 and enterprise_id=?2 order by create_time ASC ", nativeQuery = true) List<Date> findCreateTimeByDomainNameAndEnterpriseId(String domainName, String enterpriseId); }

但是,既然已經用了ORM的方式幹掉SQL了,為啥我要倒退回去重寫SQL,我實在不能接受原生的這種SQL寫法。功夫不負有心人,翻了不少的書和網頁,終於讓我找到更好的方式,並且在《spring實戰(第四版)》中也有提到,書中將接下來我要提到的這種方式,稱之為混合自定義的功能。
具體來說,當spring-data-jpa為Repository介面生產實現的時候,它還會查詢名字與介面相同,並且添加了Impl字尾的一個類。如果這個類存在的話,spring-data-jpa將會把它的方法與spring-data-jpa所生成的方法合併在一起。對於上述DataCenterInfoDao介面而言,要查詢的類名就是DataCenterInfoDaoImpl

我首先定義瞭如下的介面:

     public interface DataCenterInfoAddition {
        List<Tuple> countDataCenterInfoByArea(Province belongProvince);
     }

緊接著,我修改一下之前定義的DataCenterInfoDao:

public interface DataCenterInfoDao extends JpaRepository<DataCenterInfo, Long>,
        JpaSpecificationExecutor<DataCenterInfo>, DataCenterInfoAddition {
    /**
     * Find by id optional.
     *
     * @param id the id
     * @return the optional
     */
    Optional<DataCenterInfo> findById(Long id);
}

最後我定義一個DataCenterInfoDaoImpl實現類,用於實現DataCenterInfoAddition。在這個實現中,我直接使用JPA Criteria API來實現對應的資料庫功能。countDataCenterInfoByArea中實現的功能無法直接使用jpa定義函式名的方式來實現,這個函式返回值有兩個,一個是表的province欄位以及它對應的數目

public class DataCenterInfoDaoImpl implements DataCenterInfoAddition {

    @PersistenceContext
    private EntityManager em;


    @Override
    public List<Tuple> countDataCenterInfoByArea(Province belongProvince) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Tuple> cq = cb.createQuery(Tuple.class);
        Root<DataCenterInfo> nnInfo = cq.from(DataCenterInfo.class);

        cq.multiselect(nnInfo.get("province"), cb.count(nnInfo)).groupBy(nnInfo.get("province"))
                .orderBy(cb.desc(cb.count(nnInfo)));
        if (belongProvince != null && !belongProvince.getName()
                .equals(ProvinceEnum.Jituan.getName()) && !belongProvince.getName()
                .equals(ProvinceEnum.Quanguo.getName())) {
            cq.where(cb.equal(nnInfo.get("belongProvince"), belongProvince));
        }
        return em.createQuery(cq).getResultList();
    }