Spring Data JPA 3.X集成Querydsl

Komisch's Blog / 2023-07-28 / 原文

注意!!

截止到20237七月二十八号,由于querydsl没有适配包的变更,源码中大量地方使用的javax包,搭配spring dada jpa由很多问题,如果使用的是3.x版本,不建议集成querydsl使用,实际上集成后也是不可能用的状态

2.X集成方式

参考官方文档,querydsl/querydsl-jpa at master · querydsl/querydsl (github.com),集成方式比较简单

  • pom.xml添加query的依赖,包括querydsl-jpa依赖和apt-maven-pluginmaven插件
<!--依赖-->
<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
    <version>${querydsl.version}</version>
</dependency>
<!--插件-->
<plugin>
    <groupId>com.mysema.maven</groupId>
    <artifactId>apt-maven-plugin</artifactId>
    <version>1.1.3</version>
    <executions>
      <execution>
        <goals>
          <goal>process</goal>
        </goals>
        <configuration>
          <outputDirectory>target/generated-sources/java</outputDirectory>
          <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
        </configuration>
      </execution>
    </executions>
    <dependencies>
      <dependency>
        <groupId>com.querydsl</groupId>
        <artifactId>querydsl-apt</artifactId>
        <version>${querydsl.version}</version>
      </dependency>
    </dependencies>
</plugin>

这个插件是用来生成实体对象对应的查询对象,假设有个Student对象,那么生成的查询对象就是QStudent

  • 插件和依赖引入完毕后点击下图所示的按钮,或者执行mvn install命令,就可以在target/generated-sources/java中生成查询对象,打包后也会将该对象引入jar包中

    image-20230728092614237

成功生成查询对象,在代码中即可正常使用

image-20230728092836895

3.X集成方式

Spring Data JPA 3.0之后,JPA相应的注解包发生变化,之前是在javax包中,新版本包名变成了jakarta.*,按照老版本的方式无法生成实体类的查询对象,网络上查询的集成方式都是基于老版本的。所以在翻看官方文档时提到了一个解决方式

image-20230728094035710

文档如果使用的Hibernate的注解,需要用Hibernate对应的processor处理,其实两种类型注解名称都是一样的只是包名不同,和我们的场景是一样的,我们把源码下载之后,看看HibernateAnnotationProcessor是如何实现的,会发现这个类解析的还是javax包的注解

image-20230728100932330

玄机是在HibernateConfiguration这个类中,这个类重写了getAnnotations方法,将javax的包名换成了org.hibernate.annotations,从而实现了解析Hibernate注解的功能,那我们就可以模仿这两个类自定义processor,将解析的几个注解包改一下即可,直接复JPAAnnotationProcessorJPAConfiguration,把报错的注解替换成jakarta.persistence包的注解即可,源码如下,然后修改pom.xml插件的processor的全限定名为自定义类的全限定名即可

JPAConfiguration源码

package com.komisch.springdatajpademo.processor;

import com.querydsl.apt.DefaultConfiguration;
import com.querydsl.apt.QueryTypeImpl;
import com.querydsl.apt.TypeUtils;
import com.querydsl.apt.VisitorConfig;
import com.querydsl.codegen.Keywords;
import com.querydsl.core.annotations.*;
import com.querydsl.core.util.Annotations;
import jakarta.persistence.*;

import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Configuration for {@link JPAAnnotationProcessor}
 *
 * @see JPAAnnotationProcessor
 */
public class JPAConfiguration extends DefaultConfiguration {

    private final List<Class<? extends Annotation>> annotations;

    private final Types types;

    public JPAConfiguration(RoundEnvironment roundEnv,
                            ProcessingEnvironment processingEnv,
                            Class<? extends Annotation> entityAnn,
                            Class<? extends Annotation> superTypeAnn,
                            Class<? extends Annotation> embeddableAnn,
                            Class<? extends Annotation> embeddedAnn,
                            Class<? extends Annotation> skipAnn) {
        super(processingEnv, roundEnv, Keywords.JPA, QueryEntities.class, entityAnn, superTypeAnn,
                embeddableAnn, embeddedAnn, skipAnn);
        this.annotations = getAnnotations();
        this.types = processingEnv.getTypeUtils();
        setStrictMode(true);
    }

    @SuppressWarnings("unchecked")
    protected List<Class<? extends Annotation>> getAnnotations() {
        return Collections.unmodifiableList(Arrays.asList(
                Access.class, Basic.class, Column.class, ElementCollection.class,
                Embedded.class, EmbeddedId.class, Enumerated.class, GeneratedValue.class, Id.class,
                JoinColumn.class, ManyToOne.class, ManyToMany.class, MapKeyEnumerated.class,
                OneToOne.class, OneToMany.class, PrimaryKeyJoinColumn.class, QueryType.class, QueryInit.class,
                QueryTransient.class, Temporal.class, Transient.class, Version.class));
    }

    @Override
    public VisitorConfig getConfig(TypeElement e, List<? extends Element> elements) {
        Access access = e.getAnnotation(Access.class);
        if (access != null) {
            if (access.value() == AccessType.FIELD) {
                return VisitorConfig.FIELDS_ONLY;
            } else {
                return VisitorConfig.METHODS_ONLY;
            }
        }
        boolean fields = false, methods = false;
        for (Element element : elements) {
            if (hasRelevantAnnotation(element)) {
                fields |= element.getKind().equals(ElementKind.FIELD);
                methods |= element.getKind().equals(ElementKind.METHOD);
            }
        }
        return VisitorConfig.get(fields, methods, VisitorConfig.ALL);
    }

    @Override
    public TypeMirror getRealType(ExecutableElement method) {
        return getRealElementType(method);
    }

    @Override
    public TypeMirror getRealType(VariableElement field) {
        return getRealElementType(field);
    }

    private TypeMirror getRealElementType(Element element) {
        AnnotationMirror mirror = TypeUtils.getAnnotationMirrorOfType(element, ManyToOne.class);
        if (mirror == null) {
            mirror = TypeUtils.getAnnotationMirrorOfType(element, OneToOne.class);
        }
        if (mirror != null) {
            return TypeUtils.getAnnotationValueAsTypeMirror(mirror, "targetEntity");
        }

        mirror = TypeUtils.getAnnotationMirrorOfType(element, OneToMany.class);
        if (mirror == null) {
            mirror = TypeUtils.getAnnotationMirrorOfType(element, ManyToMany.class);
        }
        if (mirror != null) {
            TypeMirror typeArg = TypeUtils.getAnnotationValueAsTypeMirror(mirror, "targetEntity");
            TypeMirror erasure;
            if (element instanceof ExecutableElement) {
                erasure = ((ExecutableElement) element).getReturnType();
            } else {
                erasure = types.erasure(element.asType());
            }
            TypeElement typeElement = (TypeElement) types.asElement(erasure);
            if (typeElement != null && typeArg != null) {
                if (typeElement.getTypeParameters().size() == 1) {
                    return types.getDeclaredType(typeElement, typeArg);
                } else if (typeElement.getTypeParameters().size() == 2) {
                    if (element.asType() instanceof DeclaredType) {
                        TypeMirror first = ((DeclaredType) element.asType()).getTypeArguments().get(0);
                        return types.getDeclaredType(typeElement, first, typeArg);
                    }
                }
            }
        }

        return null;
    }

    @Override
    public void inspect(Element element, Annotations annotations) {
        Temporal temporal = element.getAnnotation(Temporal.class);
        if (temporal != null && element.getAnnotation(ElementCollection.class) == null) {
            PropertyType propertyType = null;
            switch (temporal.value()) {
                case DATE:
                    propertyType = PropertyType.DATE;
                    break;
                case TIME:
                    propertyType = PropertyType.TIME;
                    break;
                case TIMESTAMP:
                    propertyType = PropertyType.DATETIME;
            }
            annotations.addAnnotation(new QueryTypeImpl(propertyType));
        }
    }

    private boolean hasRelevantAnnotation(Element element) {
        for (Class<? extends Annotation> annotation : annotations) {
            if (element.getAnnotation(annotation) != null) {
                return true;
            }
        }
        return false;
    }

}

JPAAnnotationProcessor源码

package com.komisch.springdatajpademo.processor;

import com.querydsl.apt.AbstractQuerydslProcessor;
import com.querydsl.apt.Configuration;
import jakarta.persistence.*;

import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import java.lang.annotation.Annotation;

@SupportedAnnotationTypes({"com.querydsl.core.annotations.*", "jakarta.persistence.*"})
public class JPAAnnotationProcessor extends AbstractQuerydslProcessor {
    public JPAAnnotationProcessor() {
    }

    protected Configuration createConfiguration(RoundEnvironment roundEnv) {
        Class<? extends Annotation> entity = Entity.class;
        Class<? extends Annotation> superType = MappedSuperclass.class;
        Class<? extends Annotation> embeddable = Embeddable.class;
        Class<? extends Annotation> embedded = Embedded.class;
        Class<? extends Annotation> skip = Transient.class;
        System.out.println(processingEnv);
        return new JPAConfiguration(roundEnv, this.processingEnv, entity, superType, embeddable, embedded, skip);
    }
}