Spring Boot 之 JPA的查询方式

本文详细介绍了如何在SpringData JPA中利用约定方法名进行高效查询,包括SQL符号与对应方法的映射,如And、Or、Like等,以及使用JPQL、原生SQL、Specifications和ExampleMatcher的实例。掌握这些技巧,提升数据检索的灵活性和代码可读性。

JPA 的查询方式

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

关键词SQL符号样例对应JPQL 语句片段
AndandfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrorfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,Equals=findByFirstname,findByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1
Betweenbetween xxx and xxxfindByStartDateBetween… 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
IsNullis nullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullis not nullfindByAge(Is)NotNull… where x.age not null
LikelikefindByFirstnameLike… where x.firstname like ?1
NotLikenot likefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithlike 'xxx%'findByFirstnameStartingWith… where x.firstname like ?1(parameter bound with appended %)
EndingWithlike 'xxx%'findByFirstnameEndingWith… where x.firstname like ?1(parameter bound with prepended %)
Containinglike '%xxx%'findByFirstnameContaining… where x.firstname like ?1(parameter bound wrapped in %)
OrderByorder byfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
Not<>findByLastnameNot… where x.lastname <> ?1
Inin()findByAgeIn(Collection<Age> ages)… where x.age in ?1
NotInnot in()findByAgeNotIn(Collection<Age> ages)… where x.age not in ?1
TRUE=truefindByActiveTrue()… where x.active = true
FALSE=falsefindByActiveFalse()… where x.active = false
IgnoreCaseupper(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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值