06 Spring Data JPA查询操作

Spring Data JPA查询操作

Spring Data JPA的实现原理是采用动态代理机制,所以将介绍两种查询方式

  • 从方法名称中可以指定特定用于存储的查询和更新
  • 通过使用@Query手动定义查询

想要以上操作,只需要实体Repository继承Spring Data Common里面的Repository接口即可。

方法的查询策略设置

通过@EnableJpaRepositories(queryLookupStrategy = QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND)可以配置方法的查询策略,其中QueryLookupStrategy.Key的值一共有三个

  • CREATE:直接根据方法名进行创建。
  • USE_DECLARED_QUERY:声明方式创建,及注解方式
  • CREATE_IF_NOT_FOUND:默认的,以上两种方式的结合

除非有特殊需求,一般情况下不用管。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.repository.query.QueryLookupStrategy;

@EnableJpaRepositories(queryLookupStrategy = QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND)
@SpringBootApplication
public class Springdatajpa01Application {

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

}

查询方法的创建

内部基础架构中有个根据方法名的查询生成器机制,对于在存储库的实体上创建约束查询很有用。该机制方法的前缀有find...By、read...By、query...By、count...By和get...By,从这些方法可以分析它的其余部分。引入子句可以包含其他表达式。用一句话概述:待查询功能的方法名由查询策略、查询字段和一些限制条件组成。在如下例子中,可以直接在controller里面调用

import org.springframework.data.repository.Repository;

import java.util.List;

/**
 * TODO
 *
 * @author: Yizq
 * @data: 2020/8/23 6:47 下午
 */
public interface PersonRepository extends Repository<User,Long> {
    /**
     *
     * @description: and的查询关系
     * @param email 邮箱
     * @param lastName 姓氏
     * @return: {@link List< User>}
     * @author: Yizq
     * @data: 2020/8/23 6:57 下午
     */
    List<User> findByEmailAndLastName(String email, String lastName);

    /**
     *
     * @description:  包含distinct去重、or的SQL语法
     * @param email 邮箱
     * @param lastName 姓氏
     * @return: {@link List< User>}
     * @author: Yizq
     * @data: 2020/8/23 6:57 下午
     */
    List<User> findDistinctPeopleByEmailOrLastName(String email, String lastName);
    List<User> findPeopleDistinctByEmailOrLastName(String email, String lastName);


    /**
     *
     * @description: 根据LastName字段查询忽略大小写
     * @param lastName 姓氏
     * @return: {@link List< User>}
     * @author: Yizq
     * @data: 2020/8/23 6:58 下午
     */
    List<User> findByLastNameIgnoreCase(String lastName);


    /**
     *
     * @description:  根据LastName和firstName查询equa并且忽略大小写
     * @param lastName  姓氏
     * @param firstName 名称
     * @return: {@link List< User>}
     * @author: Yizq
     * @data: 2020/8/23 6:59 下午
     */
    List<User> findByLastNameAndFirstNameAllIgnoreCase(String lastName, String firstName);

    /**
     *
     * @description: 对查询结果根据lastName排序
     * @param lastName 姓氏
     * @return: {@link List< User>}
     * @author: Yizq
     * @data: 2020/8/23 7:00 下午
     */
    List<User> findByLastNameOrderByFirstNameAsc(String lastName);


}

关键字列表

关键字样品方法SQPL片段原生SQL
AndfindPersonByLastNameAndFirstName(String lastName,String firstName)...where x.lastName = ?1 and x.firstName = ?2...where lastName = 1? and firstName = 2?
OrfindPersonByLastnameOrFirstname(String lastName,String firstName)...where x.lastName= ?1 or x.firstName = ?2...where lastName = ?1 or firstName = ?2
Is、EqualsfindPersonByLastName(String lastName) findPersonByLastNameIs(String lastName) findPersonByLastNameEquals(String lastName)...where x.lastName= ?1...where lastName = ?1
BetweenfindPersonByAge(int minAge, int maxAge)...where x.age between ?1 and ?2...where age between ?1 and ?2
LessThanfindPersonByAgeLessThan(int age)...where x.age < ?1...where age < ?1
LessThanEqualfindPersonByAgeLessThanEqual(int age)...where x.age <= ?1...where age <= ?1
GreaterThanfindPersonByAgeGreaterThanEqual(int age)...where x.age > ?1...where age > ?1
GreaterThanEqualfindPersonByAgeGreaterThanEqual(int age)...where x.age >= ?1...where age >= ?1
AfterfindPersonByStartDateAfter(Date startDate)...where x.startDate > ?1...where startDate > ?1
BeforefindPersonByStartDateBefore(Date startDate)...where x.startDate < ?1...where startDate < ?1
isNull、NullfindPersonByLastNameIsNull/findPersonByLastNameNull...where x.lastName is null...where lastName is null
isNotNull、NotNullfindPersonByLastNameisNotNull/findPersonByLastNameNotNull...where x.lastName is not null...where lastName is not null
LikefindPersonByLastNameLike(String lastName)...where x.lastName like '%' + ?1 + '%'...where lastName like ?1
NotLikefindPersonByLastNameNotLike(String lastName)...where x.lastName not like ?1...where lastName not like ?1
StartingWithfindPersonByLastNameStartingWith(String lastName)...where x.lastName like '%' + ?1...where lastName like '%' + ?1
EndingWithfindPersonByLastNameEndingWith(String lastName)...where x.lastName like ?1 + '%'...where lastName like ?1 + '%'
ContainingfindPersonByLastNameContaiting(String lastName)...where x.lastName like '%' + ?1 + '%'...where lastName like '%' + ?1 + '%'
OrderByfindPersonByOrderByLastNameDesc/Asc...order by x.lastName desc...order by lastName desc
NotfindPersonByLastNameNot(String lastName)...where x.lastName <> ?1...where lastName <> ?1
InfindPersonByAgeIn(List ages) ...where x.lastName in(?1)...where lastName in(?1)
NotInfindPersonByAgeNotIn(List ages) ...where x.lastName not in(?1)...where lastName not in(?1)
TruefindPersonByFlagTrue(Boolean flag)...where x.flag = true...where flag = true
FalsefindPersonByFlagFalse(Boolean flag)...where x.flag = false...where flag = false
IgnoreCasefindPersonByLaseNameIgnoreCase(String lastName)...where upper(x.lastName) = upper(?1)...where upper(lastName) = upper(?1)

方法的查询策略的属性表达式

属性表达式只能引用托管(泛型)实体的直接属性。如假设Person实体对象中有一个Address属性里面包含一个ZipCode属性。

在这种情况下,方法名为:

List<Person> findByAddressZipCode(ZipCode zipCode);

创建及其查找的过程是:解析算法首先将整个part解析为属性,并使用该名称检查对象中的属性。如果算法成功,就使用该属性。如果不是,就拆分右侧驼峰部分的信号源到头部和尾部,并试图找出相应的属性,在我们的例子中是AddressZip和Code。如果算法找到一个具有头部的属性,那么它需要尾部,并从那里继续构建树,然后按照刚刚描述的方式将尾部分割。如果第一个分割不匹配,就将分割点移动到左侧(Address、ZipCode),然后继续。

虽然在这种大多数情况下应该起作用,但是算法可能会选择错误的属性。假设Person类也有一个addressZip属性,该算法将在第一个分割轮中匹配,并且基本上会选择错误的属性,最后失败(因为该类姓addressZip可能没有code属性)

要解决这个歧义,可以在方法名称中手动定义遍历点,所以我们的方法名称最终会是:

List<Person> findByAddress_ZipCode(ZipCode zipCode);

当然Spring JPA里面将下划线视为保留字符,但是强烈建议遵循标准Java命令规范雨夜定

查询结果的处理

参数选择分页和排序

1、特定类型的参数,动态地将分页和排序应用到查询

示例:在查询方法中使用Pageable、Silce和Sort

Page<User> findByLastName(String lastName,Pageable pageable);
Silce<User> findByLastName(String lastName,Pageable pageable);
List<User> findByLastName(String lastName,Sort sort);
List<User> findByLastName(String lastName,Pageable pageable);

第一个方式允许将org.springframework.data.domain.Pageable实例传递给查询方法,以便动态的将分页添加到静态定义的查询中。Page知道可用的元素和页面的总数。它通过基础框架里面触发计数来计算总数。这种是昂贵的,当用到Pageable的时候会默认执行一条count语句。而Slice的作用是,知道是否有下一个Silce可用,不会执行count,所以当查询较大的结果集时,只知道数据是否足够就可以啦,而且相关的业务场景也不用关心一共又多少页。

排序选项也通过Pageable实例处理。如果只需要排序,那么在org.springframework.data.domain.Sort参数中添加一个参数即可

2、限制查询结果

示例:在查询方法上加限制查询结果的关键字first和top

User findFirstByOrOrderByLastNameAsc();
User findTopByOrOrderByAgeDesc();
Page<User> queryFirst10ByLastName(String lastName, Pageable pageable);
Slice<User> findTop3ByLastName(String lastName, Pageable pageable);
List<User> findFirst10ByLastName(String lastName, Sort sort);
List<User> findTop10ByLastName(String lastName, Sort sort);

查询方法的结果可以通过关键字来限制first或top,其可以被互换地使用。可选的数值可以追到加顶部/第一个以指定要返回的最大结果大小。如果数字被忽略,假设结果大小为1。限制表达式也支持distinct关键字。此外,对于结果集限制为一个实体的查询,支持将结果包装到一个实例中的Optional中。如果将分页或切片应用于限制查询分页,则在限制结果中应用。

3、查询结果的不同形式

3.1、流式查询结果

可以通过使用Java 8 stream<T>作为返回类型来逐步处理查询方式的结果,而不是简单地将结果包装在Stream数据存储中,特定的方式用于执行流

示例:使用Java8 流式传输查询的结果Stream<T>

@Query("select u from User u")
Stream<User> findAllByCustomerQueryAndStream();
Stream<User> readAllByFirstNameNotNull();

@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);

流的关闭问题,try cache是一种关闭方法。

Stream<User> stream;
try {
stream = userRepository.findAllByCustomerQueryAndStream();
stream.forEach(...);
} catch (Exception e) {
e.printStackTrace();
}finally {
if (stream != null) {
	stream.close();
}
}

3.2 异步查询结果

可以使用Spring的异步方法执行功能异步的存储库查询。这意味着方法将在调用时立即返回,并且实际的查询执行将发生在已提交给Spring TaskExceutor的任务中,比较适合定时任务的实际场景。

@Async
Future<User> findByFirstName(String firstName);

@Async
CompletableFuture<User> findOneByFirstName(String firstName);

@Async
ListenableFuture<User> findOneLastName(String lastName);

Projections对查询结果的扩展

Spring JPA对Projections扩展的支持是非常好的。从字面意义上理解就是映射,指的是和DB查询结果的字段映射关系。一般情况下,返回的字段和DB查询结果的字段是一一对应的,但有的时候,我们需要返回一些指定的字段,不需要全部返回,或者只返回一些复合型的字段,还要自己写逻辑。Spring Data正是考虑到这一点,允许对专用返回类型进行建模,以便我们有更多的选择,将部分字段显示成试图对象。

假设Perso是一个正常的实体,和数据表Person一一对应,正常的写法如下:

@Entity
public class Person {
    @Id
    UUID id;
    
    String firstName,lastName;
    Address address;
    @Entity
    static class Address{
        String zipCode,city,Steet;
    }
}

1、我们想要返回其中与name相关的字段,应该怎么做呢?基于projections的思路。首先需要声明一个接口,包含其中的属性的方法即可,例如

public interface NamesOnly{
    String getFirstName;
    String getLastName;
}

Repository里面的写法如下,直接用这个对象接收结果即可

public interface PersonRepository extends Repository<Person, UUID> {
    Collection<NamesOnly> findByLastName(String lastName);
   // 对应sql
   // select user0_.first_name as col_0_0_, user0_.last_name as col_1_0_ from user user0_ where user0_.last_name=?
}

2、查询关联的子对象

interface PersonSummary {
    String getfirstName;
    String getLastName;
    AddressSummary getAddress();
    interface AddressSummary{
        String getCity();
    }
}

3、@Value和SPEL也支持

interface NamesOnly{
    @Value("#{target.firstName + ' '+target.lastName}")
    String getFullName();
}

//select user0_.id as id1_1_, user0_.age as age2_1_, user0_.email as email3_1_, user0_.first_name as first_na4_1_, user0_.gender as gender5_1_, user0_.last_name as last_nam6_1_ from user user0_ where user0_.last_name=?

这里的getFullName不是实体中的属性

4、对Spel表达式的支持远不止这些

@Component
public class MyBean {
    public static String getFullName(User user) {
    		// 可以进行运算、值改变等操作
        return "张三";
    }
}

ps:方法必须是public修饰的,并且是静态

public interface UsersOnly {
    @Value("#{@myBean.getFullName(target)}")
    String getFullName();
}
//select user0_.id as id1_1_, user0_.age as age2_1_, user0_.email as email3_1_, user0_.first_name as first_na4_1_, user0_.gender as gender5_1_, user0_.last_name as last_nam6_1_ from user user0_ where user0_.last_name=?

target所指向的是在Repository里对应的对象泛型

5、还可以通过Spel表达式取到方法里面的参数值

public interface UsersOnly {
    @Value("#{args[0] +' ' + target.firstName +'!'}")
    String getFullName();
}

6、以上方法都是支持interface,还可以支持Dto

public class UserDto {
    private final String firstName;
    private final String lastName;


    public UserDto(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }
}
//Hibernate: select user0_.first_name as col_0_0_, user0_.last_name as col_1_0_ from user user0_ where user0_.last_name=?

ps:直接在Dto中使用get/set属性即可

7、支持动态projections。通过泛型,可以根据不同的业务清空返回不同的字段集合。可以对UserRepository做一些变化,例如:

public interface UserRepository<T> extends JpaRepository<User,Long> {
    <T> Collection<T> findByLastName(String lastName,Class<T> type);
}

实现机制

当一个Repository上的查询方法被调用时,Advice拦截器会在方法真正地实现调用钱限制性MethodIntercept的invoke方法,这样我们可以有机会在真正方法实现执行前执行其他方法

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,可以看出在这个项目中,使用了Kafka作为消息队列,并且通过异步发送消息到Kafka的方式来实现事件的发布。同时,还有一个MyEventPublisher类用于封装对Kafka的API操作。那么,如果要实现Kafka异步更新MySQL,可以按照以下步骤进行: 1. 首先,在项目中引入MySQL的依赖,例如使用Spring Data JPA操作MySQL数据库。 2. 在MyEventPublisher类中,可以添加一个方法来处理接收到的事件,并将事件中的数据存储到MySQL数据库中。可以使用JPARepository来进行数据库操作。 3. 在MyEventPublisher类中的publishEvent方法中,调用新增的处理方法,将事件数据存储到MySQL数据库中。可以使用异步的方式来进行数据库操作,以避免阻塞Kafka的消息发送。 4. 在配置文件中配置MySQL的连接信息,包括数据库URL、用户名、密码等。 通过以上步骤,就可以实现Kafka异步更新MySQL的功能。当接收到事件时,将事件数据存储到MySQL数据库中,实现数据的更新操作。 #### 引用[.reference_title] - *1* *3* [SpringBoot系列之canal和kafka实现异步实时更新](https://blog.csdn.net/u014427391/article/details/122211056)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Kafka 异步发送被阻塞](https://blog.csdn.net/wobenqinren/article/details/120182573)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值