Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

querydsl mongodb does not generate Q-Classes after upgrade to Spring boot 3 #3651

Open
romulus-ai opened this issue Dec 21, 2023 · 14 comments
Open
Labels

Comments

@romulus-ai
Copy link

Observed vs. expected behavior

Previously on Spring boot 2.7.17 everything worked fine and the classes were generated as expected. However now I try to upgrade the project to Spring Boot 3 and Spring Cloud 2022.0.3 and querydsl is not working anymore.

The issue is only related to mongodb.

Steps to reproduce

Try generating Q-Classes in the environment describe below

Environment

Spring Boot 3.0.13
Spring Cloud 2022.0.3
Java 17.0.7
Gradle 8.5 (gradle wrapper)

Querydsl version: 5.0.0

Querydsl module: querydsl-mongodb

Database: mongodb

JDK: 17

Additional details

We are using Lombok and jakarta annotation API additionally in the annotation processors
And we are using GraphQL Codegen as well.

@romulus-ai romulus-ai added the bug label Dec 21, 2023
@liuchengts
Copy link

I also have this problem, the Q class is not generated. This is very fatal, forcing me to temporarily give up using Querydsl. I hope there is a solution to save this bad situation.

environment:

springboot 3.2.0
kotlin 1.9.20
gradle 8.5
openjdk 20
querydsl 5.0.0
h2database 2.2.224
IntelliJ IDEA 2023.3.2 (Ultimate Edition)

Here are the relevant parts of build.gradle.kts:

plugins {
    id("org.springframework.boot") version "3.2.0"
    id("io.spring.dependency-management") version "1.1.4"
    kotlin("jvm") version "1.9.20"
    kotlin("plugin.spring") version "1.9.20"
    kotlin("plugin.jpa") version "1.9.20"
}
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
    implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta")
    annotationProcessor("com.querydsl:querydsl-apt:5.0.0:jakarta")
    annotationProcessor("org.springframework.boot:spring-boot-starter-data-jpa")
    runtimeOnly("com.h2database:h2")
}

Phenomenon:

  1. There are no errors in the gradle build phase
  2. The following error will appear when starting in idea:
2023-12-26T16:45:26.479+08:00 ERROR 21926 --- [job-server] [  restartedMain] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'jobServiceImpl' defined in file [/Users/liucheng/it/lc/lc-job/build/classes/kotlin/main/com/lc/job/domain/service/impl/JobServiceImpl.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'jobRepository' defined in com.lc.job.domain.repository.JobRepository defined in @EnableJpaRepositories declared on LcJobApplication: Did not find a query class com.lc.job.domain.model.QJobModel for domain class com.lc.job.domain.model.JobModel
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:802) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:241) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1356) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1193) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:946) ~[spring-context-6.1.1.jar:6.1.1]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:616) ~[spring-context-6.1.1.jar:6.1.1]
	at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:66) ~[spring-boot-3.2.0.jar:3.2.0]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:753) ~[spring-boot-3.2.0.jar:3.2.0]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:455) ~[spring-boot-3.2.0.jar:3.2.0]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:323) ~[spring-boot-3.2.0.jar:3.2.0]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1342) ~[spring-boot-3.2.0.jar:3.2.0]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1331) ~[spring-boot-3.2.0.jar:3.2.0]
	at com.lc.job.LcJobApplicationKt.main(LcJobApplication.kt:21) ~[main/:na]
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:578) ~[na:na]
	at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-3.2.0.jar:3.2.0]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jobRepository' defined in com.lc.job.domain.repository.JobRepository defined in @EnableJpaRepositories declared on LcJobApplication: Did not find a query class com.lc.job.domain.model.QJobModel for domain class com.lc.job.domain.model.JobModel
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1775) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1441) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1348) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:911) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:789) ~[spring-beans-6.1.1.jar:6.1.1]
	... 22 common frames omitted
Caused by: java.lang.IllegalArgumentException: Did not find a query class com.lc.job.domain.model.QJobModel for domain class com.lc.job.domain.model.JobModel
	at org.springframework.data.querydsl.SimpleEntityPathResolver.createPath(SimpleEntityPathResolver.java:78) ~[spring-data-commons-3.2.0.jar:3.2.0]
	at org.springframework.data.jpa.repository.support.QuerydslJpaPredicateExecutor.<init>(QuerydslJpaPredicateExecutor.java:97) ~[spring-data-jpa-3.2.0.jar:3.2.0]
	at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getRepositoryFragments(JpaRepositoryFactory.java:282) ~[spring-data-jpa-3.2.0.jar:3.2.0]
	at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getRepositoryFragments(JpaRepositoryFactory.java:252) ~[spring-data-jpa-3.2.0.jar:3.2.0]
	at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepositoryComposition(RepositoryFactorySupport.java:428) ~[spring-data-commons-3.2.0.jar:3.2.0]
	at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:293) ~[spring-data-commons-3.2.0.jar:3.2.0]
	at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:279) ~[spring-data-commons-3.2.0.jar:3.2.0]
	at org.springframework.data.util.Lazy.getNullable(Lazy.java:135) ~[spring-data-commons-3.2.0.jar:3.2.0]
	at org.springframework.data.util.Lazy.get(Lazy.java:113) ~[spring-data-commons-3.2.0.jar:3.2.0]
	at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:285) ~[spring-data-commons-3.2.0.jar:3.2.0]
	at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:132) ~[spring-data-jpa-3.2.0.jar:3.2.0]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1822) ~[spring-beans-6.1.1.jar:6.1.1]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1771) ~[spring-beans-6.1.1.jar:6.1.1]
	... 33 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.lc.job.domain.model.QJobModel
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) ~[na:na]
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) ~[na:na]
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]
	at java.base/java.lang.Class.forName0(Native Method) ~[na:na]
	at java.base/java.lang.Class.forName(Class.java:496) ~[na:na]
	at java.base/java.lang.Class.forName(Class.java:475) ~[na:na]
	at org.springframework.boot.devtools.restart.classloader.RestartClassLoader.loadClass(RestartClassLoader.java:121) ~[spring-boot-devtools-3.2.0.jar:3.2.0]
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]
	at java.base/java.lang.Class.forName0(Native Method) ~[na:na]
	at java.base/java.lang.Class.forName(Class.java:496) ~[na:na]
	at java.base/java.lang.Class.forName(Class.java:475) ~[na:na]
	at org.springframework.util.ClassUtils.forName(ClassUtils.java:304) ~[spring-core-6.1.1.jar:6.1.1]
	at org.springframework.data.querydsl.SimpleEntityPathResolver.createPath(SimpleEntityPathResolver.java:71) ~[spring-data-commons-3.2.0.jar:3.2.0]
	... 45 common frames omitted

@liuchengts
Copy link

Very good news, I solved this problem and made the following changes based on the build.gradle.kts I posted earlier:

1. Add kapt plug-in

plugins {
    ...
    kotlin("kapt") version "1.9.22"
}

2. Use kapt to build, select jakarta, originally jpa

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
    implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta")
  // kapt("com.querydsl:querydsl-apt:5.0.0:jpa")
    kapt("com.querydsl:querydsl-apt:5.0.0:jakarta")
    runtimeOnly("com.h2database:h2")
}

After recompiling and starting, everything is normal.

@romulus-ai
Copy link
Author

That is nice for jpa, means for postgres users, however on mongoDB this does not help! There is no jakarta classifier.. here the relevant parts of my build.gradle:

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.0.13'
    id 'io.spring.dependency-management' version '1.1.4'
    id "io.github.kobylynskyi.graphql.codegen" version "5.9.0"
    id 'jacoco'
}

...

ext {
    set('springCloudVersion', '2022.0.3')
    querydslVersion = '5.0.0'
    lombokVersion = '1.18.30'
    set('testcontainersVersion', "1.19.0")
}

...

dependencies {
   implementation('org.springframework.boot:spring-boot-starter-data-mongodb')

  ...

    // https://mvnrepository.com/artifact/jakarta.persistence/jakarta.persistence-api
    implementation("jakarta.persistence:jakarta.persistence-api:3.1.0")
    implementation("jakarta.annotation:jakarta.annotation-api")

    implementation("com.querydsl:querydsl-mongodb:${querydslVersion}")
    compileOnly "com.querydsl:querydsl-apt:${querydslVersion}:jakarta"
    compileOnly('org.springframework.boot:spring-boot-configuration-processor')

    annotationProcessor(
            "com.querydsl:querydsl-apt:${querydslVersion}:jakarta",
            "jakarta.annotation:jakarta.annotation-api",
            "jakarta.persistence:jakarta.persistence-api:3.1.0",
            "org.projectlombok:lombok:${lombokVersion}",
    )

....
}

@romulus-ai
Copy link
Author

The Q-Classes for my Postgres entities are getting generated, however not the ones for mongodb entities

@liuchengts
Copy link

The Q-Classes for my Postgres entities are getting generated, however not the ones for mongodb entities

Is it easy to post Mongdb-related entity classes?

@romulus-ai
Copy link
Author

Sure, here one of them:

import com.querydsl.core.annotations.QueryEntity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@Getter
@NoArgsConstructor
@QueryEntity
public class Provider {

    private ProviderType type;

    /**
     * id of the original activity assigned by the provider
     */
    private String externalId;
}

And here the generated one from the version as we used spring 2.7:

import static com.querydsl.core.types.PathMetadataFactory.*;

import com.querydsl.core.types.dsl.*;

import com.querydsl.core.types.PathMetadata;
import javax.annotation.processing.Generated;
import com.querydsl.core.types.Path;


/**
 * QProvider is a Querydsl query type for Provider
 */
@Generated("com.querydsl.codegen.DefaultEntitySerializer")
public class QProvider extends EntityPathBase<Provider> {

    private static final long serialVersionUID = -2130270020L;

    public static final QProvider provider = new QProvider("provider");

    public final StringPath externalId = createString("externalId");

    public final EnumPath<ProviderType> type = createEnum("type", ProviderType.class);

    public QProvider(String variable) {
        super(Provider.class, forVariable(variable));
    }

    public QProvider(Path<? extends Provider> path) {
        super(path.getType(), path.getMetadata());
    }

    public QProvider(PathMetadata metadata) {
        super(Provider.class, metadata);
    }

}

@romulus-ai
Copy link
Author

romulus-ai commented Jan 4, 2024

Think I've found the problem, have to add the @Entity annotation to the classes I need a Q-Class of.. Previously it seems that @QueryEntity was fine.

@liuchengts
Copy link

Think I've found the problem, have to add the @Entity annotation to the classes I need a Q-Class of.. Previously it seems that @QueryEntity was fine.

Very correct, I was just about to say this. I have always used javax.persistence.@entity. Of course, now in openjdk 21, I use jakarta.persistence.@entity.

@romulus-ai
Copy link
Author

Sorry, but I have to reopen this issue. Since the @jakarta.persistence.Entity annotation makes a lot of trouble with mongodb. Before that, an @jakarta.persistence.Id Annotation was not neccessary, now it is, however we have usecases where no ID is needed and where it makes trouble in some points.

i.e. we have a Class A (@entity) which can contain some more complex classes i.e. Lists of Datapoints (also @entity) or complex Datatypes consisting of multiple interger fields. In MongoDB this complex data should be formed to a JSON document i.e.:

{ 
  name: classA,
  id: 1234,
  cadenceData: {
    min: 1
    mean: 2
    max: 3
  },
  datapoints: [
    { date: XXX, value: 1.2 },
    { date: XXX, value: 1.3 }
    ...
  ]
}

Now you may see, it is not necessary to have an ID for the cadenceData or for each datapoint, since those are not separated tables as it would be in a SQL based DB.

So still wondering how to use queryDSL with gradle and Spring boot 3.0.

@romulus-ai
Copy link
Author

reopen

@romulus-ai romulus-ai reopened this Jan 9, 2024
@liuchengts
Copy link

Sorry, but I have to reopen this issue. Since the @jakarta.persistence.Entity annotation makes a lot of trouble with mongodb. Before that, an @jakarta.persistence.Id Annotation was not neccessary, now it is, however we have usecases where no ID is needed and where it makes trouble in some points.

i.e. we have a Class A (@entity) which can contain some more complex classes i.e. Lists of Datapoints (also @entity) or complex Datatypes consisting of multiple interger fields. In MongoDB this complex data should be formed to a JSON document i.e.:

{ 

  name: classA,

  id: 1234,

  cadenceData: {

    min: 1

    mean: 2

    max: 3

  },

  datapoints: [

    { date: XXX, value: 1.2 },

    { date: XXX, value: 1.3 }

    ...

  ]

}

Now you may see, it is not necessary to have an ID for the cadenceData or for each datapoint, since those are not separated tables as it would be in a SQL based DB.

So still wondering how to use queryDSL with gradle and Spring boot 3.0.

As you said, I think it's a relational and non-relational design conflict, and I think the easiest solution right now is to make a compromise between DSL and Mongodb, for example, using @jakarta.persistence.Id to fit the needs of dsl, but actually ignores the existence of this field.

@romulus-ai
Copy link
Author

Thats what I've already tried, however it seems that on this road cascading problems are coming up.. because the next problem after that are JDBC type errors on subclasses used in an Entity. May on of the Maintainers could give a short hint what to do here?

@PapamichMarios
Copy link

PapamichMarios commented Jan 17, 2024

Having the same problem. I am unable to upgrade my project because of this.
There appears to be no classifier jakarta for querydsl-mongodb

@SledgeHammer01
Copy link

SledgeHammer01 commented Mar 1, 2024

I also have this issue. There's more problems with @Entity then just the @id field. It doesn't work with various other types like List<String>.

Luckily, I was able to find a work-around that works with Spring Boot 3.x. The only drawback here is that the mysema plugin is from 2014 :(, but better then nothing lol.

<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/annotations</outputDirectory>
				<processor>org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor</processor>
			</configuration>
		</execution>
	</executions>
</plugin>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants