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);
}
一開始感覺這種方式還是挺方便的,但是用著用著發現,這樣的方式功能有點不健全的,例如:
- 無法實現join操作
- 只能返回
List<DataCenterInfo>
或者DataCenterInfo
這樣的物件,如果我想返回物件中某個欄位呢? 瞎了…… - 函式的名字一長,真的讓人有點暈乎啊
對於上述的問題,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();
}