系列文章:
spring-data-jpa 入門二:常用技術使用之關聯關係查詢配置
spring-data-jpa 入門三:常用技術使用之複雜查詢
偶然情況下看了一篇討論的帖子內容大概是在說jpa與mybatis的技術選型這是地址,哈哈不怕出醜,其實當時我對jpa的印象,還只停留在jpa只是一套orm規範,除了這個就一無所知了!閱讀這個技術選型的帖子時候還處於看熱鬧的心態,越看越驚豔到我,原來jpa還可以這麼玩!然後掉坑裏了,自己搭了一套環境實驗,後面的所有內容也是本人血淚總結,如有問題歡迎指正,畢竟我也是剛剛接觸!
話不多說上目錄:
- 爲什麼使用jpa
- spring data jpa 環境搭建
- 簡單使用 增刪改查
- 常用技術使用
- 多表關聯關係查詢
- 原生sql查詢
- 動態sql(兩種方式:Criteria、繼承JpaSpecificationExecutor)
- 多表多條件複雜查詢
- 動態查詢(複雜條件 in、join 等)
- 常用技術使用
- 批量操作、EntityManager狀態分析
- 常用註解總結
- json解析時延遲加載問題
1. 爲什麼使用jpa
這個問題是我研究spring-data-jpa 之前反覆問自己的一個問題,具體分析了一下:
1. mybatis最大優點就是便利的 SQL 操作,自由度高,封裝性好,很多複雜的sql,在Navicat上寫出來直接複製到xml中就搞定了。
2. 對於jpa來說最大的優點就是對於dao層使用非常方便快捷,甚至可以不需要dao層實現,直接定義接口。
3. 如果只是簡單業務僅僅只單表查詢對於mybatis來說臃腫程度太高,雖然有代碼生成工具還是感覺不是太滿意。
4. 如果是複雜的sql查詢,特別是無關聯關係兩張表做複雜查詢(例如數據字典表與其他業務表關聯),jpa使用起來特別複雜。但是現在主流的分佈式架構,貌似越來越淡化多表複雜關聯業務查詢在一個節點上完成。
最後不得不說mybatis與jpa各有千秋,要因地適宜選擇。
2. spring data jpa 環境搭建
搭建環境之前還是想說下:
1. 什麼是spring-data-jpa:簡單來說就是spring 自己做的orm框架spring-data,其中有很多版本,包括,Spring-data-jpa,Spring-data-template,Spring-data-mongodb,Spring-data-redis等。
2. 我們在使用持久化工具時候一般會有個對象來操作數據庫,hibernate中是session,在hibernate與spring集成中我們就是集成進sessionFactory,mybatis中是sqlSession,與spring集成時候就是添加一個sqlSessionFactoryg工廠,那麼在spring-data-jpa中我們其實就是集成entityManagerFactory。
下面開始搭建環境:
1. maven 引入相關jar包,這三個是最核心的包,其中spring-data-jpa這個jar包與其他包衝突的非常多,需要自己去排除,lombok是自動生成get/set 等方法的jar包。
<!--spring-data-jpa start-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.11.5.RELEASE</version>
</dependency>
<!-- hibernate 框架 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.0.7.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.0.7.Final</version>
</dependency>
<!--實體類get/set註解-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
<scope>provided</scope>
</dependency>
<!--spring-data-jpa end-->
2. spring的配置文件中關於spring-data-jpa相關配置了
<!-- 使用cglib進行動態代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- spring整合JPA -->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--指定JPA掃描的實體類所在的包-->
<property name="packagesToScan" value="com.herman.di.entity"/>
<!-- 指定持久層提供者爲Hibernate -->
<property name="persistenceProvider">
<bean class="org.hibernate.ejb.HibernatePersistence"/>
</property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!-- 自動建表 -->
<property name="generateDdl" value="true"/>
<property name="database" value="MYSQL"/>
<property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
<property name="showSql" value="true"/>
</bean>
</property>
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
</property>
<property name="jpaPropertyMap">
<map>
<entry key="hibernate.query.substitutions" value="true 1, false 0"/>
<entry key="hibernate.default_batch_fetch_size" value="16"/>
<entry key="hibernate.max_fetch_depth" value="2"/>
<entry key="hibernate.generate_statistics" value="true"/>
<entry key="hibernate.bytecode.use_reflection_optimizer" value="true"/>
<entry key="hibernate.cache.use_second_level_cache" value="false"/>
<entry key="hibernate.cache.use_query_cache" value="false"/>
</map>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!--指定Spring Data JPA要進行掃描的包,該包中的類框架會自動爲其創建代理-->
<jpa:repositories base-package="com.herman.di.dao" repository-impl-postfix="Impl"
entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="transactionManager"/>
</beans>
在這裏簡單說明下配置文件的含義:
1. 原生jpa中需要將jpa的配置信息放在META-INF目錄下面的,並且名字必須叫做persistence.xml,但是spring-data-jpa在這基礎之上進行了封裝
org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
能讓我們將這個配置文件放在任意位置,甚至可以直接配置出來,直接通過packagesToScan屬性配置我們的bean存放的位置,以加載entity。
<property name="packagesToScan" value="com.herman.di.entity"/>
前面提到過jpa是不需要寫dao層實現的,當然如果想寫spring-data-jpa是支持的,下面這段的配置就是說明你的dao層實現位置,在jpa中dao層是叫Repository(容器;倉庫),repository-impl-postfix屬性代表接口的實現類的後綴結尾字符,spring-data-jpa的習慣是接口和實現類都需要放在同一個包裏面,再次的,這裏我們的實現類定義的時候我們不需要去指定實現接口,spring-data-jpa自動就能判斷二者的關係(自定義實現可以參考)
<jpa:repositories base-package="com.herman.di.dao" repository-impl-postfix="Impl"
entity-manager-factory-ref="entityManagerFactory"
transaction-manager-ref="transactionManager"/>
到這裏spring-data-jpa就已經與spring集成完畢,是不是很簡單,我本人搭建的時候也就是在jar包衝突的時候花費了點時間。
3. 簡單使用 增刪改查
dao層的簡單增刪改查纔是jpa的威力提現,也是它吸引人的地方,通過一個完整的項目來演示一下:
Entity層
package com.herman.di.entity;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
/**
* 用戶實體
*
* @author hsh
* @create 2018-08-30 11:49
**/
@Data
@Entity
@Table(name = "user_info")
public class UserInfo implements Serializable {
private static final long serialVersionUID = -12285021873923699L;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "uname")
private String uName;
@Column(name = "unumber")
private String uNumber;
@Column(name = "password")
private String password;
}
解釋下:
1. @Data 是 lombok 的註解,作用是自動生成object類中的get、set方法,這樣的話代碼更加簡潔,具體的可以百度下lombok。
2. @Entity 表示這個類是和數據庫中某個表對應的實體
3. @Table(name = “user_info”) 該實體所對應的具體表名,與@Entity 配合使用。
4. @Id 主鍵
5. @Column(name = “id”) 對應的列明
6. @GeneratedValue(strategy = GenerationType.IDENTITY) 主鍵生成策略,strategy:表示主鍵生成策略,有AUTO,INDENTITY,SEQUENCE 和 TABLE 4種,分別表示讓ORM框架自動選擇,根據數據庫的 Identity 字段生成,根據數據庫表的 Sequence 字段生成,以有根據一個額外的表生成主鍵,默認爲 AUTO。
當然對應的註解還有一些,後面會詳細說明,這裏就不多拓展了。
**Dao層**
package com.herman.di.dao;
import com.herman.di.entity.UserInfo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* 用戶信息dao
* Created by hsh on 18/08/30.
*/
public interface UserInfoRepository
extends JpaRepository<UserInfo, Integer>, JpaSpecificationExecutor<UserInfo> {
UserInfo findById(Integer id);
/**
* 原生分頁 查詢條件不能爲空
*/
Page<UserInfo> findByUNameContainingAndUNumberEqualsAndIdEquals(String uName,
String uNumber, Integer id, Pageable pageable);
}
dao層就結束了,是不是很簡單,不用寫實現類jpa自動代理搞定。
dao層我們基本什麼都沒幹,就是繼承了JpaRepository這個接口和JpaSpecificationExecutor這個接口,其實如果不是複雜的查詢,JpaSpecificationExecutor是可以不用繼承的。
然後我們定義了一個 findById ,見名知意就是根據id查詢用戶信息,如果在mybatis中我們是不是要寫實現大致應該是這樣的:
select * from user_info t where t.id=#{id}
現在jpa實現不用寫了jpa會自動實現根據方法名組裝sql查詢,打印出的sql信息爲:
select * from user_info t where t.id=?
是不是和我們自己寫的實現差不多,而且我們只定義兩個方法(甚至可以一個方法都不寫)就滿足了基本的增刪改查的業務需求。那麼spring-data-jpa具體是怎麼實現的呢?
首先我們一切的祕密都在我們繼承了 JpaRepository 接口,這個接口只是定義了我們常用的dao層操作
public interface JpaRepository<T, ID extends Serializable>
extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll();
List<T> findAll(Sort var1);
List<T> findAll(Iterable<ID> var1);
<S extends T> List<S> save(Iterable<S> var1);
void flush();
<S extends T> S saveAndFlush(S var1);
void deleteInBatch(Iterable<T> var1);
void deleteAllInBatch();
T getOne(ID var1);
<S extends T> List<S> findAll(Example<S> var1);
<S extends T> List<S> findAll(Example<S> var1, Sort var2);
}
是不是大部分都滿足你的需求,得益於cglib 代理,spring-data-jpa 默認所有繼承JpaRepository接口的實現爲SimpleJpaRepository,
public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepository<T, ID>,
JpaSpecificationExecutor<T>
也就是說無論你寫不寫實現最後都會是SimpleJpaRepository來實現的你接口。這也就是我們dao層可以不寫方法的原因。
至於爲什麼定義一個方法名就可以,不用寫實現,spring-data-jpa 默認會解析以 findby開頭的方法名,並生成sql,我們只需要遵循官方文檔給的規則就行。
這裏我只貼了一部分,具體地址可以去官方瞅瞅。
同樣的 下面這個方法就是根據含有分頁的返回結果,spring-data-jpa會自動封裝,Pageable 是 分頁信息 在org.springframework.data.domain.Pageable包下,通常我們會用它的子類 PageRequest來封裝分頁信息。
Page<UserInfo> findByUNameContainingAndUNumberEqualsAndIdEquals(String uName,
String uNumber, Integer id, Pageable pageable);
dao層差不多解釋到這複雜情況下文在繼續探討。
Service層
service層簡寫直接調用dao層了
package com.herman.di.service.impl;
import com.herman.di.dao.UserInfoRepository;
import com.herman.di.entity.UserInfo;
import com.herman.di.service.UserInfoService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 用戶接口實現
*
* @author hsh
* @create 2018-08-29 15:59
**/
@Service
public class UserInfoServiceImpl implements UserInfoService {
@Resource
private UserInfoRepository userInfoRepository;
public UserInfo getUserInfoById(String userId) throws Exception {
UserInfo one = userInfoRepository.findById(Integer.valueOf(userId));
return one;
}
public boolean addUserInfo(UserInfo userInfo) throws Exception {
UserInfo save = userInfoRepository.save(userInfo);
if (save != null && save.getId() != null)
return true;
return false;
}
}
這裏我們直接代用了我們在dao層定義的方法就可以了。前面說過因爲我們dao層繼承了JpaRepository這個接口,因此常規的增刪改查都已經寫好,另外需要注意的是新增和修改是一個方法 save(S entity) ,它裏面是這麼寫的:
@Transactional
public <S extends T> S save(S entity) {
if(this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
return this.em.merge(entity);
}
}
代碼還是比較簡單,就是判斷是否存在,不存在就新增,否者修改。
查找時候排序的話,我們可以看下JpaRepository爲我們提供的
List<T> findAll(Sort var1);
<S extends T> List<S> findAll(Example<S> var1);
<S extends T> List<S> findAll(Example<S> var1, Sort var2);
Sort 是個排序條件 構造參數:
Sort(Sort.Order... orders)
Sort(List<Sort.Order> orders)
Sort(String... properties)
Sort(Sort.Direction direction, String... properties)
見名知意,第一個方法傳入 order 參數,第二個方法傳入多個orders,第三個傳入需要排序的字段,第四個傳入的是升序還是降序,和需要排序的字段。
Example< S> 可以理解爲查詢條件,下文我們在探討。
到此,我們瞭解到spring-data-jpa是什麼、簡單的實現原理、簡單的增刪改查、以及簡單的分頁查找、排序。
下文我們將繼續探討更爲複雜的情況
- 常用技術使用
- 多表關聯關係查詢
- 原生sql查詢
- 動態sql(兩種方式:Criteria、繼承JpaSpecificationExecutor)
- 多表多條件複雜查詢
- 動態查詢(複雜條件 in、join 等)
- 批量操作、EntityManager狀態分析
- 常用註解總結
- json解析時延遲加載問題