JPA 的查询方式

约定方法名一定要根据命名规范来书写,Spring Data 会根据前缀、中间连接词(Or、And、Like、NotNull 等类似 SQL 中的关键字)、内部拼接 SQL 代理生成方法的实现。约定方法名的方法见表:

关键词

SQL符号

样例

对应JPQL 语句片段

And

and

findByLastnameAndFirstname

… where x.lastname = ?1 and x.firstname = ?2

Or

or

findByLastnameOrFirstname

… where x.lastname = ?1 or x.firstname = ?2

Is,Equals

=

findByFirstname,findByFirstnameIs,findByFirstnameEquals

… where x.firstname = ?1

Between

between xxx and xxx

findByStartDateBetween

… where x.startDate between ?1 and ?2

LessThan

<

findByAgeLessThan

… where x.age < ?1

LessThanEqual

<=

findByAgeLessThanEqual

… where x.age <= ?1

GreaterThan

>

findByAgeGreaterThan

… where x.age > ?1

GreaterThanEqual

>=

findByAgeGreaterThanEqual

… where x.age >= ?1

After

>

findByStartDateAfter

… where x.startDate > ?1

Before

<

findByStartDateBefore

… where x.startDate < ?1

IsNull

is null

findByAgeIsNull

… where x.age is null

IsNotNull,NotNull

is not null

findByAge(Is)NotNull

… where x.age not null

Like

like

findByFirstnameLike

… where x.firstname like ?1

NotLike

not like

findByFirstnameNotLike

… where x.firstname not like ?1

StartingWith

like 'xxx%'

findByFirstnameStartingWith

… where x.firstname like ?1(parameter bound with appended %)

EndingWith

like 'xxx%'

findByFirstnameEndingWith

… where x.firstname like ?1(parameter bound with prepended %)

Containing

like '%xxx%'

findByFirstnameContaining

… where x.firstname like ?1(parameter bound wrapped in %)

OrderBy

order by

findByAgeOrderByLastnameDesc

… where x.age = ?1 order by x.lastname desc

Not

<>

findByLastnameNot

… where x.lastname <> ?1

In

in()

findByAgeIn(Collection<Age> ages)

… where x.age in ?1

NotIn

not in()

findByAgeNotIn(Collection<Age> ages)

… where x.age not in ?1

TRUE

=true

findByActiveTrue()

… where x.active = true

FALSE

=false

findByActiveFalse()

… where x.active = false

IgnoreCase

upper(xxx)=upper(yyyy)

findByFirstnameIgnoreCase

… where UPPER(x.firstame) = UPPER(?1)

接口方法的命名规则也很简单,明白And、Or、Is、Equal、Greater、StartingWith等英文单词的含义,就可以写接口方法了。
具体用法如下:

package com.example.repository;

import com.example.jpa.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.Repository;

import java.util.List;

public interface UserRepository extends Repository<User, Long> {
    User findFirstByOrderByNameAsc();

    List<User> findByEmailOrName(String email, String name);

    Page<User> queryFirst100ByName(String name, Pageable pageable);

    Slice<User> findTop100ByName(String name, Pageable pageable);

    List<User> findFirst100ByName(String name, Sort sort);
}

用 JPQL 进行查询

JPQL 语言(Java Persistence Query Language)是一种和 SQL 非常类似的中间性和对象化的查询语言,它最终会被编译成针对不同底层数据库的 SQL 语言,从而屏蔽不同数据库的差异。

JPQL 语言通过 Query 接口封装执行,Query 接口封装了执行数据库查询的相关方法。调用 EntityManager 的 Query、NamedQuery 及 NativeQuery 方法可以获得查询对象,进而可调用 Query 接口的相关方法来执行查询操作。

JPQL 是面向对象进行查询的语言,可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作。JPQL 不支持使用 INSERT。对于 UPDATE 或 DELETE 操作,必须使用注解 @Modifying 进行修饰。

JPQL 的用法:

package com.example.repository;

import com.example.jpa.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

public interface UserRepository2 extends JpaRepository<User, Long> {

    // 根据名称查询
    @Query("select u from User u where u.name = ?1")
    User fingByName(String name);
    
    // 根据名称模糊查询
    @Query("select u from User u where u.name like %?1")
    List<User> findByName(String name);
}

用原生 SQL 进行查询

package com.example.repository;

import com.example.jpa.User;
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.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;

public interface UserRepository2 extends JpaRepository<User, Long> {

    // 根据 ID 查询
    @Query(value = "select * from User u where u.id = :id", nativeQuery = true)
    Optional<User> findById(@Param("id") Long id);

    // 查询所有用户
    @Query(value = "select * from User", nativeQuery = true)
    List<User> findAllNative();

    // 根据 Email 查询
    @Query(value = "select * from User where email = ?1", nativeQuery = true)
    User findByEmail(String email);

    // 根据 Name 查询,并返回分页对象 Page
    @Query(value = "select * from User where name = ?1",
            countQuery = "select count(*) from User where name = ?1",
            nativeQuery = true)
    Page<User> findByName(String name, Pageable pageable);

    // 根据 Name 来修改 Email 的值
    @Modifying
    @Transactional
    @Query("update User set email = :email where name = :name")
    Integer updateUserEmailByName(@Param("name") String name, @Param("email") String email);
}

使用事务

UPDATE 或 DELETE 操作需要使用事务。此时需要先定义 Service 层,然后在 Service 层的方法上添加事务操作。对于自定义的方法,如果需要改变 Spring Data 提供的事务默认方法,则可以在方法上使用注解 @Transactional

// 根据 Name 来修改 Email 的值
    @Modifying
    @Transactional
    @Query("update User set email = :email where name = :name")
    Integer updateUserEmailByName(@Param("name") String name, @Param("email") String email);

测试

package com.example.bean;

import com.example.jpa.User;
import com.example.repository.UserRepository2;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class UserServiceTest {

    @Autowired
    UserRepository2 userRepository2;

    @Test
    void TestUpdateEmailByName() {
        Integer i = userRepository2.updateUserEmailByName("张三", "sdfgsgsdfg@163.com");
        System.out.println(i);
    }

    @Test
    void TestFindById() {
        List<User> list = userRepository2.findAllNative();
        System.out.println(list);
    }
}

用 Specifications 查询

如果想使 Repository 支持 Specification 查询,则需要在 Repository 中继承 JpaSpecificationExecutor 接口

package com.example.repository;

import com.example.jpa.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.*;

@SpringBootTest
class UserRepository3Test {

    @Autowired
    private UserRepository3 userRepository3;

    @Test
    public void testJpa() {
        PageRequest pageable = PageRequest.of(0, 10);
        // 通常使用 Specification 的匿名内部类

        Specification<User> specification = new Specification<User>() {
            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                Path path = root.get("id");
                // gt 是大于的意思,这里表示 ID 大于 2
                Predicate predicate1 = criteriaBuilder.gt(path, 2);
                // equal 是等于的意思,代表查询 name 值为 赵四 的数据记录
                Predicate predicate2 = criteriaBuilder.equal(root.get("name"), "赵四");
                // 构建组合的 Predicate
                Predicate predicate = criteriaBuilder.and(predicate1, predicate2);
                return predicate;
            }
        };

        Page<User> page = userRepository3.findAll(specification, pageable);
        System.out.println("总记录数:" + page.getTotalElements());
        System.out.println("当前第:" + (page.getNumber() + 1) + " 页");
        System.out.println("总页数:" + page.getTotalPages());
        System.out.println("当前页面的 List:" + page.getContent());
        System.out.println("当前页面的记录数:" + page.getNumberOfElements());
    }
}

Hibernate: select user0_.id as id1_0_, user0_.email as email2_0_, user0_.name as name3_0_, user0_.pswd as pswd4_0_ from user user0_ where user0_.id>2 and user0_.name=? limit ?
Hibernate: select count(user0_.id) as col_0_0_ from user user0_ where user0_.id>2 and user0_.name=?
总记录数:100
当前第:1 页
总页数:10
当前页面的 List:[User(id=4, name=赵四, pswd=123456, email=345634@qq.com), User(id=5, name=赵四, pswd=123456, email=345634@qq.com), User(id=6, name=赵四, pswd=123456, email=345634@qq.com), User(id=7, name=赵四, pswd=123456, email=345634@qq.com), User(id=8, name=赵四, pswd=123456, email=345634@qq.com), User(id=9, name=赵四, pswd=123456, email=345634@qq.com), User(id=10, name=赵四, pswd=123456, email=345634@qq.com), User(id=11, name=赵四, pswd=123456, email=345634@qq.com), User(id=12, name=赵四, pswd=123456, email=345634@qq.com), User(id=13, name=赵四, pswd=123456, email=345634@qq.com)]
当前页面的记录数:10

用 ExampleMatcher 进行查询

Spring Data 可以通过 Example 对象来构造 JPQL 查询

package com.example.repository;

import com.example.jpa.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.*;
import java.util.List;

@SpringBootTest
class UserRepository3Test {

    @Autowired
    private UserRepository3 userRepository3;

    @Test
    public void testExample() {
        User user = new User();
        // 构建查询条件
        user.setName("张三");
        // 创建一个 ExampleMatcher
        ExampleMatcher matcher = ExampleMatcher.matching()
                // 不区分大小写匹配 Name
                .withIgnorePaths("name")
                // 包含 null 值
                .withIncludeNullValues();
        // 通过 Example 构建查询
        Example<User> example = Example.of(user, matcher);
        List<User> list = userRepository3.findAll(example);
        System.out.println(list);
    }
}