Skip to content

Commit 540f3fb

Browse files
christophstroblmp911de
authored andcommitted
Add support for AOT repositories.
Closes #3830
1 parent 9f1dfa8 commit 540f3fb

23 files changed

+2341
-18
lines changed

pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@
145145
<version>${spring}</version>
146146
<scope>provided</scope>
147147
</dependency>
148+
<dependency>
149+
<groupId>org.jboss.logging</groupId>
150+
<artifactId>jboss-logging</artifactId>
151+
<version>3.6.1.Final</version>
152+
</dependency>
148153
</dependencies>
149154

150155
<build>

spring-data-envers/pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@
6060
<version>${project.version}</version>
6161
</dependency>
6262

63+
<dependency>
64+
<groupId>org.jboss.logging</groupId>
65+
<artifactId>jboss-logging</artifactId>
66+
<version>3.6.1.Final</version>
67+
</dependency>
68+
6369
<!-- Hibernate -->
6470
<dependency>
6571
<groupId>org.hibernate.orm</groupId>

spring-data-jpa/pom.xml

+18-3
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,16 @@
8888
<optional>true</optional>
8989
</dependency>
9090

91+
<dependency>
92+
<groupId>org.junit.platform</groupId>
93+
<artifactId>junit-platform-launcher</artifactId>
94+
<scope>test</scope>
95+
</dependency>
9196
<dependency>
92-
<groupId>org.junit.platform</groupId>
93-
<artifactId>junit-platform-launcher</artifactId>
97+
<groupId>org.springframework</groupId>
98+
<artifactId>spring-core-test</artifactId>
9499
<scope>test</scope>
95100
</dependency>
96-
97101
<dependency>
98102
<groupId>org.hsqldb</groupId>
99103
<artifactId>hsqldb</artifactId>
@@ -239,6 +243,12 @@
239243
<optional>true</optional>
240244
</dependency>
241245

246+
<dependency>
247+
<groupId>org.jboss.logging</groupId>
248+
<artifactId>jboss-logging</artifactId>
249+
<version>3.6.1.Final</version>
250+
</dependency>
251+
242252
</dependencies>
243253

244254
<build>
@@ -370,6 +380,11 @@
370380
<artifactId>jakarta.persistence-api</artifactId>
371381
<version>${jakarta-persistence-api}</version>
372382
</path>
383+
<path>
384+
<groupId>org.jboss.logging</groupId>
385+
<artifactId>jboss-logging</artifactId>
386+
<version>3.6.1.Final</version>
387+
</path>
373388
</annotationProcessorPaths>
374389
</configuration>
375390
</plugin>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright 2024-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jpa.repository.aot.generated;
17+
18+
import jakarta.persistence.EntityManager;
19+
import jakarta.persistence.EntityManagerFactory;
20+
import jakarta.persistence.metamodel.EmbeddableType;
21+
import jakarta.persistence.metamodel.EntityType;
22+
import jakarta.persistence.metamodel.ManagedType;
23+
import jakarta.persistence.metamodel.Metamodel;
24+
import jakarta.persistence.spi.ClassTransformer;
25+
26+
import java.util.List;
27+
import java.util.Map;
28+
import java.util.Set;
29+
30+
import org.hibernate.jpa.HibernatePersistenceProvider;
31+
import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl;
32+
import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor;
33+
import org.springframework.data.util.Lazy;
34+
import org.springframework.instrument.classloading.SimpleThrowawayClassLoader;
35+
import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
36+
37+
/**
38+
* @author Christoph Strobl
39+
*/
40+
public class AotMetaModel implements Metamodel {
41+
42+
private final String persistenceUnit;
43+
private final Set<Class<?>> managedTypes;
44+
private final Lazy<EntityManagerFactory> entityManagerFactory = Lazy.of(this::init);
45+
private final Lazy<Metamodel> metamodel = Lazy.of(() -> entityManagerFactory.get().getMetamodel());
46+
private final Lazy<EntityManager> entityManager = Lazy.of(() -> entityManagerFactory.get().createEntityManager());
47+
48+
public AotMetaModel(Set<Class<?>> managedTypes) {
49+
this("dynamic-tests", managedTypes);
50+
}
51+
52+
private AotMetaModel(String persistenceUnit, Set<Class<?>> managedTypes) {
53+
this.persistenceUnit = persistenceUnit;
54+
this.managedTypes = managedTypes;
55+
}
56+
57+
public static AotMetaModel hibernateModel(Class<?>... types) {
58+
return new AotMetaModel(Set.of(types));
59+
}
60+
61+
public static AotMetaModel hibernateModel(String persistenceUnit, Class<?>... types) {
62+
return new AotMetaModel(persistenceUnit, Set.of(types));
63+
}
64+
65+
public <X> EntityType<X> entity(Class<X> cls) {
66+
return metamodel.get().entity(cls);
67+
}
68+
69+
@Override
70+
public EntityType<?> entity(String s) {
71+
return metamodel.get().entity(s);
72+
}
73+
74+
public <X> ManagedType<X> managedType(Class<X> cls) {
75+
return metamodel.get().managedType(cls);
76+
}
77+
78+
public <X> EmbeddableType<X> embeddable(Class<X> cls) {
79+
return metamodel.get().embeddable(cls);
80+
}
81+
82+
public Set<ManagedType<?>> getManagedTypes() {
83+
return metamodel.get().getManagedTypes();
84+
}
85+
86+
public Set<EntityType<?>> getEntities() {
87+
return metamodel.get().getEntities();
88+
}
89+
90+
public Set<EmbeddableType<?>> getEmbeddables() {
91+
return metamodel.get().getEmbeddables();
92+
}
93+
94+
public EntityManager entityManager() {
95+
return entityManager.get();
96+
}
97+
98+
EntityManagerFactory init() {
99+
100+
MutablePersistenceUnitInfo persistenceUnitInfo = new MutablePersistenceUnitInfo() {
101+
@Override
102+
public ClassLoader getNewTempClassLoader() {
103+
return new SimpleThrowawayClassLoader(this.getClass().getClassLoader());
104+
}
105+
106+
@Override
107+
public void addTransformer(ClassTransformer classTransformer) {
108+
// just ingnore it
109+
}
110+
};
111+
112+
persistenceUnitInfo.setPersistenceUnitName(persistenceUnit);
113+
this.managedTypes.stream().map(Class::getName).forEach(persistenceUnitInfo::addManagedClassName);
114+
115+
persistenceUnitInfo.setPersistenceProviderClassName(HibernatePersistenceProvider.class.getName());
116+
117+
return new EntityManagerFactoryBuilderImpl(new PersistenceUnitInfoDescriptor(persistenceUnitInfo) {
118+
@Override
119+
public List<String> getManagedClassNames() {
120+
return persistenceUnitInfo.getManagedClassNames();
121+
}
122+
}, Map.of("hibernate.dialect", "org.hibernate.dialect.H2Dialect")).build();
123+
}
124+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jpa.repository.aot.generated;
17+
18+
import jakarta.persistence.metamodel.Metamodel;
19+
20+
import org.springframework.data.jpa.repository.Query;
21+
import org.springframework.data.jpa.repository.query.EscapeCharacter;
22+
import org.springframework.data.jpa.repository.query.JpaParameters;
23+
import org.springframework.data.jpa.repository.query.JpaQueryCreator;
24+
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider;
25+
import org.springframework.data.jpa.repository.support.JpqlQueryTemplates;
26+
import org.springframework.data.repository.aot.generate.AotRepositoryMethodGenerationContext;
27+
import org.springframework.data.repository.query.ParametersSource;
28+
import org.springframework.data.repository.query.ReturnedType;
29+
import org.springframework.data.repository.query.parser.PartTree;
30+
31+
/**
32+
* @author Christoph Strobl
33+
* @since 2025/01
34+
*/
35+
public class AotQueryCreator {
36+
37+
Metamodel metamodel;
38+
39+
public AotQueryCreator(Metamodel metamodel) {
40+
this.metamodel = metamodel;
41+
}
42+
43+
AotStringQuery createQuery(PartTree partTree, ReturnedType returnedType,
44+
AotRepositoryMethodGenerationContext context) {
45+
46+
ParametersSource parametersSource = ParametersSource.of(context.getRepositoryInformation(), context.getMethod());
47+
JpaParameters parameters = new JpaParameters(parametersSource);
48+
ParameterMetadataProvider metadataProvider = new ParameterMetadataProvider(parameters, EscapeCharacter.DEFAULT,
49+
JpqlQueryTemplates.UPPER);
50+
51+
JpaQueryCreator queryCreator = new JpaQueryCreator(partTree, returnedType, metadataProvider,
52+
JpqlQueryTemplates.UPPER, metamodel);
53+
AotStringQuery query = AotStringQuery.bindable(queryCreator.createQuery(), metadataProvider.getBindings());
54+
55+
if (partTree.isLimiting()) {
56+
query.setLimit(partTree.getResultLimit());
57+
}
58+
query.setCountQuery(context.annotationValue(Query.class, "countQuery"));
59+
return query;
60+
}
61+
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jpa.repository.aot.generated;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
21+
import org.springframework.data.domain.Limit;
22+
import org.springframework.data.jpa.repository.query.ParameterBinding;
23+
import org.springframework.data.jpa.repository.query.ParameterBindingParser;
24+
import org.springframework.data.jpa.repository.query.ParameterBindingParser.Metadata;
25+
import org.springframework.data.jpa.repository.query.QueryUtils;
26+
import org.springframework.lang.Nullable;
27+
import org.springframework.util.StringUtils;
28+
29+
/**
30+
* @author Christoph Strobl
31+
* @since 2025/01
32+
*/
33+
class AotStringQuery {
34+
35+
private final String raw;
36+
private final String sanitized;
37+
private @Nullable String countQuery;
38+
private final List<ParameterBinding> parameterBindings;
39+
private final Metadata parameterMetadata;
40+
private Limit limit;
41+
private boolean nativeQuery;
42+
43+
public AotStringQuery(String raw, String sanitized, List<ParameterBinding> parameterBindings,
44+
Metadata parameterMetadata) {
45+
this.raw = raw;
46+
this.sanitized = sanitized;
47+
this.parameterBindings = parameterBindings;
48+
this.parameterMetadata = parameterMetadata;
49+
}
50+
51+
static AotStringQuery of(String raw) {
52+
53+
List<ParameterBinding> bindings = new ArrayList<>();
54+
Metadata metadata = new Metadata();
55+
String targetQuery = ParameterBindingParser.INSTANCE
56+
.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(raw, bindings, metadata);
57+
58+
return new AotStringQuery(raw, targetQuery, bindings, metadata);
59+
}
60+
61+
static AotStringQuery nativeQuery(String raw) {
62+
AotStringQuery q = of(raw);
63+
q.nativeQuery = true;
64+
return q;
65+
}
66+
67+
static AotStringQuery bindable(String query, List<ParameterBinding> bindings) {
68+
return new AotStringQuery(query, query, bindings, new Metadata());
69+
}
70+
71+
public String getQueryString() {
72+
return sanitized;
73+
}
74+
75+
public String getCountQuery(@Nullable String projection) {
76+
77+
if (StringUtils.hasText(countQuery)) {
78+
return countQuery;
79+
}
80+
return QueryUtils.createCountQueryFor(sanitized, StringUtils.hasText(projection) ? projection : null, nativeQuery);
81+
}
82+
83+
public List<ParameterBinding> parameterBindings() {
84+
return this.parameterBindings;
85+
}
86+
87+
boolean isLimited() {
88+
return limit != null && limit.isLimited();
89+
}
90+
91+
Limit getLimit() {
92+
return limit;
93+
}
94+
95+
public void setLimit(Limit limit) {
96+
this.limit = limit;
97+
}
98+
99+
public boolean isNativeQuery() {
100+
return nativeQuery;
101+
}
102+
103+
public void setCountQuery(@Nullable String countQuery) {
104+
this.countQuery = StringUtils.hasText(countQuery) ? countQuery : null;
105+
}
106+
}

0 commit comments

Comments
 (0)