spring-data-jpa 入門

什麼是jpa

系列文章:
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解析時延遲加載問題

spring-data-jpa 入門二:常用技術使用之關聯關係查詢配置

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章