Spring Boot [組件學習-Spring Data JPA]

導讀:

在上篇文章中對Spring MVC經常使用的一些註解作了簡要的說明,在這篇文章中主要對Spring Data JPA 作一個簡要的說明,並附有一個簡單的例子,能夠體會到Spring Data JPA 的強大之處。html

Spring Data JPA 與JPA的關係:

JPA是什麼?

JPA(Java Persistence API)是Sun官方提出的Java持久化規範。它爲Java開發人員提供了一種對象/關聯映射工具來管理Java應用中的關係數據。他的出現主要是爲了簡化現有的持久化開發工做和整合ORM技術,結束如今Hibernate,TopLink,JDO等ORM框架各自爲營的局面。值得注意的是,JPA是在充分吸取了現有Hibernate,TopLink,JDO等ORM框架的基礎上發展而來的,具備易於使用,伸縮性強等優勢。
JPA定義了在對數據庫中的對象處理查詢和事務運行時的EntityManager的API。JPA定義一個對象級查詢語言,JPQL,若是學習過Hibernate的話你能夠把它看作Hibernate中的Hql語句,以容許從所述數據庫中的對象的查詢。
下面是JPA經常使用的一些解決方案:java

EclipseLink (Eclipse)
Hibernate (RedHat)
Open JPA (Apache)
DataNucleus
Ebean (SourceForge)
TopLink Essentials (Glassfish)
TopLink (Oracle)
Kodo (Oracle)

Spring Data JPA:

你能夠把它作作事JPA的超集 相似於 C與C++的關係,Spring Data JPA能夠極大的簡化JPA的寫法,能夠在幾乎不用寫實現的狀況下,實現對數據的訪問和操做。除了CRUD外,還包括如分頁、排序等一些經常使用的功能。web

也就是說Spring Data JPA 在JPA的基礎上提供了更高層次的抽象,幫助咱們實現了許多經常使用的對數據庫的操做,從而提升開發效率。spring

Spring Data JPA 的一些經常使用方法

在瞭解Spring Data JPA以前咱們首先看一下它爲咱們提供了那些便利。sql

1.簡單查詢:

面對簡單的查詢時能夠經過解析方法名建立查詢或使用使用 @Query 建立查詢語句數據庫

好比如今咱們有個方法叫作 User findByName(String name),咱們能夠很清楚的明白它的意思,但有沒有辦法讓ORM框架根據方法名幫助咱們推斷出Sql來呢?在Spring Data JPA中這是能夠的,咱們只要將咱們的接口繼承 org.springframework.data.repository.Repository <T, ID extends java.io.Serializable>,或者使用@RepositoryDefinition 註解進行修飾。json

解析方法名建立查詢 示例:

public interface UserRepository extends Repository<User, Long> {
    User findByName(String name)
} 


@RepositoryDefinition(domainClass = User.class, idClass = Long.class) 
public interface UserRepository{
    User findByName(String name)
}

@Query註解查詢 示例:

public interface UserRepository extends Repository<User, Long> {
    //對於更新操做須要添加@Modifying
    @Query("from User u where u.name=:name")
    User findUser(@Param("name") String name);
}

固然還不止這些Spring Data JPA 還未咱們提供了幾個經常使用的Repository:數組

  • Repository: 僅僅是一個標識,沒有任何方法,方便Spring自動掃描識別
  • CrudRepository: 繼承Repository,實現了一組CRUD相關的方法
  • PagingAndSortingRepository: 繼承CrudRepository,實現了一組分頁排序相關的方法
  • JpaRepository: 繼承PagingAndSortingRepository,實現一組JPA規範相關的方法

經過繼承它們能夠得到更強大的功能,固然它們之所可以運行是由於有了默認的實現類:app

@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepository<T, ID>,
        JpaSpecificationExecutor<T> {
   
}

在查詢的時候,一般須要同時根據多個屬性進行查詢,Spring Data JPA 爲此提供了一些表達條件查詢的關鍵字,大體以下:框架

  • And --- 等價於 SQL 中的 and 關鍵字,好比 findByUsernameAndPassword(String user, Striang pwd);
  • Or --- 等價於 SQL 中的 or 關鍵字,好比 findByUsernameOrAddress(String user, String addr);
  • Between --- 等價於 SQL 中的 between 關鍵字,好比 findBySalaryBetween(int max, int min);
  • LessThan --- 等價於 SQL 中的 "<",好比 findBySalaryLessThan(int max);
  • GreaterThan --- 等價於 SQL 中的">",好比 findBySalaryGreaterThan(int min);
  • IsNull --- 等價於 SQL 中的 "is null",好比 findByUsernameIsNull();
  • IsNotNull --- 等價於 SQL 中的 "is not null",好比 findByUsernameIsNotNull();
  • NotNull --- 與 IsNotNull 等價;
  • Like --- 等價於 SQL 中的 "like",好比 findByUsernameLike(String user);
  • NotLike --- 等價於 SQL 中的 "not like",好比 findByUsernameNotLike(String user);
  • OrderBy --- 等價於 SQL 中的 "order by",好比 findByUsernameOrderBySalaryAsc(String user);
  • Not --- 等價於 SQL 中的 "! =",好比 findByUsernameNot(String user);
  • In --- 等價於 SQL 中的 "in",好比 findByUsernameIn(Collection<String> userList) ,方法的參數能夠是 Collection 類型,也能夠是數組或者不定長參數;
  • NotIn --- 等價於 SQL 中的 "not in",好比 findByUsernameNotIn(Collection<String> userList) ,方法的參數能夠是 Collection 類型,也能夠是數組或者不定長參數;

固然面對比較複雜的查詢時,它們就顯得無力了,若是在面對比較複雜的查詢時,想要有本身的實現方法,只須要經過一個XXXImpl結尾的實現類便可使用咱們本身的實現方法(好比UserRepository接口的同一個包下面創建一個普通類UserRepositoryImpl來表示該類的實現類)。

瞭解更多:使用 Spring Data JPA 簡化 JPA 開發 - IBM

2.複雜的查詢

面對複雜的查詢時可使用本地查詢或JPQL或使用JPA的動態接口(JpaSpecificationExecutor)

在面對負責的條件查詢時,須要有條件判斷這個時候方法名推斷就顯得不夠強大了,好比當這裏有個簡單的例子:「咱們要根據用戶的年齡,地址,性別 查詢用戶信息時」, 在考慮可能存在條件爲空的狀況下,在使用方法名推斷時咱們須要提供8個相應的方法,固然若是使用流的話有一個findAll方法便可,可是這無疑增大了數據庫與發射生成對象壓力因此並不推薦這種方式,那麼在這種狀況下若是可使用判斷進行條件拼接的話,即可以將查詢的方法縮減到一個 這即是以上三種方式的優勢(通常狀況下不推薦 本地查詢)。

JPQL示例:

DO:

@Entity
public class User extends AbstractPersistable<Long> {

    private String name;

    private Integer age;

    private String AddressCode;

    @Enumerated(EnumType.ORDINAL)
    private Sex sex;
    
    /**省略get/set**/
}

DAO:

public interface UserCustomRepository {

    Page<User> search(Integer age, String AddressCode, User.Sex sex, Pageable pageRequest);

}


public interface UserRepository extends CrudRepository<User, Long>, UserCustomRepository {

}


public class UserRepositoryImpl implements UserCustomRepository {

    @PersistenceContext
    private EntityManager em;

    @Override
    public Page<User> search(Integer age, String AddressCode, User.Sex sex, Pageable pageRequest) {
        String querySql = "select t ";
        String countSql = "select count(t) ";
        StringBuffer sqlBuffer = new StringBuffer("from User t where 1=1");

        if (null != age) {
            sqlBuffer.append(" and t.age = :age");
        }
        if (null != AddressCode) {
            sqlBuffer.append(" and t.AddressCode = :address");
        }
        if (null != sex) {
            sqlBuffer.append(" and t.sex = :sex");
        }

        querySql += sqlBuffer.toString();
        countSql += sqlBuffer.toString();

        Query dataQuery = em.createQuery(querySql);
        Query countQuery = em.createQuery(countSql);

        if (null != age) {
            dataQuery.setParameter("age", age);
            countQuery.setParameter("age", age);
        }
        if (null != AddressCode) {
            dataQuery.setParameter("address", AddressCode);
            countQuery.setParameter("address", AddressCode);
        }
        if (null != sex) {
            dataQuery.setParameter("sex", sex);
            countQuery.setParameter("sex", sex);
        }
        // 本地查詢方式 
        // Query query = em.createNativeQuery(sql);
        // query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.aliasToBean(User.class));
        Page<User> page = (pageRequest == null ? new PageImpl(dataQuery.getResultList()) : this.readPage(dataQuery, countQuery, pageRequest));
        return page;
    }

    private Page<User> readPage(Query dataQuery, Query countQuery, Pageable pageable) {
        dataQuery.setFirstResult(pageable.getOffset());
        dataQuery.setMaxResults(pageable.getPageSize());
        long totalSize = (long) countQuery.getSingleResult();
        List<User> content = totalSize > (long) pageable.getOffset() ? dataQuery.getResultList() : Collections.emptyList();
        return new PageImpl(content, pageable, totalSize);
    }


}

Test:

@RunWith(SpringRunner.class)
@SpringBootTest
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SpringBootJpaApplicationTests {

    @Autowired
    private UserRepository userRepository;

    @Test
    public void contextLoads() {
        List<User> userList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            String name = "name" + i;
            Integer age = 19 + (i + 1) % 5;
            String addressCode = "address";
            User.Sex sex = i % 2 == 0 ? User.Sex.MAN : User.Sex.WOMAN;
            User user = new User(name, age, addressCode, sex);
            System.out.println(JSON.toJSON(user));
            userList.add(user);
        }
        userRepository.save(userList);
    }

    @Test
    public void search() {
        int page = 0;
        int size = 2;
        Pageable pageable = new PageRequest(page, size);
        Page<User> pageUser = userRepository.search(null, null, null, pageable);
        pageUser = userRepository.search(19, null, User.Sex.WOMAN, pageable);
        System.out.println(JSON.toJSON(pageUser));
    }

}

JPA的動態接口(JpaSpecificationExecutor):

只作了簡單的修改
DO:

public interface UserRepository extends CrudRepository<User, Long>, UserCustomRepository, JpaSpecificationExecutor<User> {

}

Test:

@RunWith(SpringRunner.class)
@SpringBootTest
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SpringBootJpaApplicationTests {

    @Autowired
    private UserRepository userRepository;

    @Test
    public void contextLoads() {
        List<User> userList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            String name = "name" + i;
            Integer age = 19 + (i + 1) % 5;
            String addressCode = "address";
            User.Sex sex = i % 2 == 0 ? User.Sex.MAN : User.Sex.WOMAN;
            User user = new User(name, age, addressCode, sex);
            System.out.println(JSON.toJSON(user));
            userList.add(user);
        }
        userRepository.save(userList);
    }

    @Test
    public void search() {
        int page = 0;
        int size = 2;
        Pageable pageable = new PageRequest(page, size);
        Page<User> pageUser = userRepository.search(null, null, null, pageable);
//        System.out.println(JSON.toJSON(pageUser));
        pageUser = userRepository.search(19, null, User.Sex.WOMAN, pageable);
        System.out.println(JSON.toJSON(pageUser));
//        pageUser = userRepository.search(null, null, null, pageable);
//        System.out.println(JSON.toJSON(pageUser));
//        pageUser = userRepository.search(null, null, null, pageable);
//        System.out.println(JSON.toJSON(pageUser));
    }

    @Test
    public void findAll() {
        int page = 0;
        int size = 2;
        Pageable pageable = new PageRequest(page, size);
        Page<User> pageUser = search(19, null, User.Sex.WOMAN, pageable);
        System.out.println(JSON.toJSON(pageUser));
    }

    public Page<User> search(final Integer age, final String AddressCode, final User.Sex sex, Pageable pageRequest) {
/*      第一種查詢方式
        Specification<User> specification = (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
            //建立查詢條件
            List<Predicate> predicates = new ArrayList<>();
            if (age != null) {
                predicates.add(cb.equal(root.<Integer>get("age"), age));
            }
            if (AddressCode != null) {
                predicates.add(cb.equal(root.get("AddressCode").as(String.class), AddressCode));
            }
            if (sex != null) {
                predicates.add(cb.equal(root.get("sex").as(User.Sex.class), sex));
            }
            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
        };

*/
        //第二種
        Specification<User> specification = (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
            //建立查詢條件
            List<Predicate> predicates = new ArrayList<>();
            if (age != null) {
                predicates.add(cb.equal(root.<Integer>get("age"), age));
            }
            if (AddressCode != null) {
                predicates.add(cb.equal(root.get("AddressCode").as(String.class), AddressCode));
            }
            if (sex != null) {
                predicates.add(cb.equal(root.get("sex").as(User.Sex.class), sex));
            }
            Predicate[] assetTradingArray = new Predicate[predicates.size()];
            predicates.toArray(assetTradingArray);
            query.where(assetTradingArray);//這種方式使用JPA的API設置了查詢條件,因此不須要再返回查詢條件Predicate給Spring Data Jpa,故最後return null;便可。
            return null;
        };
        return userRepository.findAll(specification, pageRequest);
    }

}

瞭解更多:純乾貨,Spring-data-jpa詳解,全方位介紹。

Spring Data JPA 在Spring-Boot中的使用:

第一步將相關依賴導入POM文件:

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--tool-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.23</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

第二步配置yml文件(使用了內存數據庫所以沒有配數據源):

spring:
  jpa:
    show-sql: true
    properties:
      hibernate:
        hbm2ddl:
          auto: update

第三步編寫main函數:

@SpringBootApplication
public class SpringBootJpaApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootJpaApplication.class, args);
    }
}

第四步編寫相關的Repository:

public interface UserRepository extends CrudRepository<User, Long>, UserCustomRepository, JpaSpecificationExecutor<User> {

}

相關邏輯編寫->運行->測試->發佈

學習資料收集:

教程:

博客介紹:

視頻:

結語:

Spring Data JPA 的解析方法名建立查詢很是強大,只是聲明持久層的接口,相關的查詢它就已經幫你去作了,讓我想起一個問題框架的終點在哪?是人工智能嗎?

參考文檔:

全面闡釋和精彩總結JPA
Spring Data 系列(二) Spring+JPA入門(集成Hibernate)
Spring Data概念【從零開始學Spring Boot】

相關文章
相關標籤/搜索