java - Spring Boot 3 JPA 规范未使用 jakarta 进行过滤

标签 java spring-boot spring-data-jpa

我正在迁移到 SpringBoot 3.0.1。更新并用 jakarta.persistence 替换所有 javax.persistence 后,所有基于 org.springframework.data.jpa.domain.Specification 的过滤器不起作用,没有错误,只是没有过滤。

示例实体:

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

import java.util.List;
import java.util.Objects;

@Getter
@Setter
@Entity
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "roles")
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "system_name")
    private String systemName;

    @Column(name = "title")
    private String title;

    @ManyToMany(mappedBy = "roles", fetch = FetchType.LAZY)
    @ToString.Exclude
    private List<User> users;

    // equals hashcode
}

存储库和规范实现:

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import my.package.model.Role;
import my.package.repository.filter.RoleFilter;
import my.package.util.SqlUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;


import java.util.List;

@Repository
public interface RoleRepository extends JpaRepository<Role, Long>, JpaSpecificationExecutor<Role> {

    record RoleSpecification(@NotNull RoleFilter filter) implements Specification<Role> {

        @NotNull
        @Override
        public Predicate toPredicate(@NotNull Root<Role> root,
                                     @NotNull CriteriaQuery<?> query,
                                     @NotNull CriteriaBuilder builder) {

            Predicate predicate = builder.conjunction();
            List<Expression<Boolean>> exps = predicate.getExpressions();

            filter.getTitle().ifPresent(title ->
                    exps.add(builder.like(builder.lower(root.get("title")), SqlUtils.toLikeLower(title))));

            return predicate;
        }
    }
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RoleFilter {

    private String title;

    public Optional<String> getTitle() {
        return Objects.isNull(title) || title.isEmpty() ? Optional.empty() : Optional.of(title);
    }

}

及用法:

import static org.springframework.data.jpa.domain.Specification.where;

@Service
@RequiredArgsConstructor
public class RoleServiceImpl implements RoleService {

    private final RoleRepository roleRepository;

    @Override
    public List<Role> list() {
        RoleFilter filter = new RoleFilter();
        filter.setTitle("test");
        return roleRepository.findAll(where(new RoleSpecification(filter)));
    }
}

hibernate 生成这个奇怪的查询:

select
    g1_0.id,
    g1_0.created_at,
    g1_0.is_hidden,
    g1_0.title 
from
    groups g1_0 
where
    1=1 
order by
    g1_0.id desc offset ? rows fetch first ? rows only

WHERE 语句中没有关于 title 的内容。

构建.gradle

plugins {
    id 'org.springframework.boot' version '3.0.1'
    id 'io.spring.dependency-management' version '1.0.14.RELEASE'
    id 'java'
}

ext {
    jupiterVersion = '5.9.1'
    lombokVersion = '1.18.24'
    openApiVersion = '1.6.14'
    mapstructVersion = '1.5.3.Final'
    lombokMapstructBindingVersion = "0.2.0"
}

group = 'my.package'
version = '1.0'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-batch'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation "org.springdoc:springdoc-openapi-ui:${openApiVersion}"
    implementation "org.mapstruct:mapstruct:${mapstructVersion}"
    implementation 'com.google.guava:guava:31.1-jre'
    implementation 'net.minidev:json-smart:2.4.8'
    implementation 'io.nats:jnats:2.16.5'
    implementation 'io.jsonwebtoken:jjwt:0.9.1'
    implementation 'javax.xml.bind:jaxb-api:2.3.1'
    implementation 'org.flywaydb:flyway-core:9.10.1'
    implementation 'org.jetbrains:annotations:23.1.0'
    compileOnly "org.projectlombok:lombok:${lombokVersion}"
    runtimeOnly 'com.h2database:h2:2.1.214'
    runtimeOnly 'org.postgresql:postgresql:42.5.1'
    annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
    annotationProcessor "org.projectlombok:lombok-mapstruct-binding:${lombokMapstructBindingVersion}"
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
    testImplementation 'org.springframework.batch:spring-batch-test'
    testImplementation "org.junit.jupiter:junit-jupiter-api:${jupiterVersion}"
    testImplementation "org.junit.jupiter:junit-jupiter-engine:${jupiterVersion}"
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:${jupiterVersion}'
    testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.8.1'
    testCompileOnly 'junit:junit:4.12'
}

tasks.named('test') {
    useJUnitPlatform()
}

没有错误,没有异常,没有警告,只是忽略所有条件。 为什么会发生这种情况以及如何解决?

最佳答案

恐怕以前从来不应该工作 - 您正在修改构成谓词的 boolean 表达式列表,并根据 javadoc此类修改不会影响结果查询:

Return the top-level conjuncts or disjuncts of the predicate. Returns empty list if there are no top-level conjuncts or disjuncts of the predicate. Modifications to the list do not affect the query.

Previous implementation of CompoundPredicate没有遵循上面提到的 JPA 契约(Contract):

    @Override
    public List<Expression<Boolean>> getExpressions() {
        return expressions;
    }

现在it does :

    @Override
    public List<Expression<Boolean>> getExpressions() {
        return new ArrayList<>( predicates );
    }

UPD。正确的实现应该是这样的:

        @NotNull
        @Override
        public Predicate toPredicate(@NotNull Root<Role> root,
                                     @NotNull CriteriaQuery<?> query,
                                     @NotNull CriteriaBuilder builder) {

            List<Predicate> exps = new ArrayList<>;

            filter.getTitle().ifPresent(title ->
                    exps.add(builder.like(builder.lower(root.get("title")), SqlUtils.toLikeLower(title))));

            return builder.and(exps.toArray(new Predicate[0]));
        }

关于java - Spring Boot 3 JPA 规范未使用 jakarta 进行过滤,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74946324/

相关文章:

Java Slick2d 矩形碰撞检测奇怪行为

Java 参数传递问题

mysql - Hibernate 查询不起作用

java - 使用自己的数据库配置 Spring Cloud 数据流任务

java - 获取 MAX 1 Child 时避免 OneToMany 的内存计算

java - 在 Quarkus 中异步保存数据

java - 在 AbstractTableModel 中使用 ResultSet 后,ResultSet 关闭后不允许进行操作

java - 如何忽略 spring.factories 中定义的 ApplicationListener?

java - 在java中上传和读取text/csv文件

spring-data-jpa - 带有 JPA 锁的 Spring 处理时间长