Infer hints for Hibernate generators
This commit updates PersistenceManagedTypesBeanRegistrationAotProcessor in order to infer hints for Hibernate annotations meta annotated with `@ValueGenerationType` (like `@CreationTimestamp`) and `@IdGeneratorType`. `@GenericGenerator` is not supported as it is deprecated as of Hibernate 6.5. Closes gh-32842
This commit is contained in:
		
							parent
							
								
									7102c33661
								
							
						
					
					
						commit
						4da1511ed3
					
				|  | @ -188,38 +188,53 @@ class PersistenceManagedTypesBeanRegistrationAotProcessor implements BeanRegistr | ||||||
| 
 | 
 | ||||||
| 		@SuppressWarnings("unchecked") | 		@SuppressWarnings("unchecked") | ||||||
| 		private void contributeHibernateHints(RuntimeHints hints, @Nullable ClassLoader classLoader, Class<?> managedClass) { | 		private void contributeHibernateHints(RuntimeHints hints, @Nullable ClassLoader classLoader, Class<?> managedClass) { | ||||||
| 			Class<? extends Annotation> embeddableInstantiatorClass = loadEmbeddableInstantiatorClass(classLoader); |  | ||||||
| 			if (embeddableInstantiatorClass == null) { |  | ||||||
| 				return; |  | ||||||
| 			} |  | ||||||
| 			ReflectionHints reflection = hints.reflection(); | 			ReflectionHints reflection = hints.reflection(); | ||||||
| 			registerInstantiatorForReflection(reflection, |  | ||||||
| 					AnnotationUtils.findAnnotation(managedClass, embeddableInstantiatorClass)); |  | ||||||
| 			ReflectionUtils.doWithFields(managedClass, field -> { |  | ||||||
| 				registerInstantiatorForReflection(reflection, |  | ||||||
| 						AnnotationUtils.findAnnotation(field, embeddableInstantiatorClass)); |  | ||||||
| 				registerInstantiatorForReflection(reflection, |  | ||||||
| 						AnnotationUtils.findAnnotation(field.getType(), embeddableInstantiatorClass)); |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		private void registerInstantiatorForReflection(ReflectionHints reflection, @Nullable Annotation annotation) { | 			Class<? extends Annotation> embeddableInstantiatorClass = loadClass("org.hibernate.annotations.EmbeddableInstantiator", classLoader); | ||||||
| 			if (annotation == null) { | 			if (embeddableInstantiatorClass != null) { | ||||||
| 				return; | 				registerForReflection(reflection, | ||||||
|  | 						AnnotationUtils.findAnnotation(managedClass, embeddableInstantiatorClass), "value"); | ||||||
|  | 				ReflectionUtils.doWithFields(managedClass, field -> { | ||||||
|  | 					registerForReflection(reflection, | ||||||
|  | 							AnnotationUtils.findAnnotation(field, embeddableInstantiatorClass), "value"); | ||||||
|  | 					registerForReflection(reflection, | ||||||
|  | 							AnnotationUtils.findAnnotation(field.getType(), embeddableInstantiatorClass), "value"); | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			Class<? extends Annotation> valueGenerationTypeClass = loadClass("org.hibernate.annotations.ValueGenerationType", classLoader); | ||||||
|  | 			if (valueGenerationTypeClass != null) { | ||||||
|  | 				ReflectionUtils.doWithFields(managedClass, field -> registerForReflection(reflection, | ||||||
|  | 						AnnotationUtils.findAnnotation(field, valueGenerationTypeClass), "generatedBy")); | ||||||
|  | 				ReflectionUtils.doWithMethods(managedClass, method -> registerForReflection(reflection, | ||||||
|  | 						AnnotationUtils.findAnnotation(method, valueGenerationTypeClass), "generatedBy")); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			Class<? extends Annotation> idGeneratorTypeClass = loadClass("org.hibernate.annotations.IdGeneratorType", classLoader); | ||||||
|  | 			if (idGeneratorTypeClass != null) { | ||||||
|  | 				ReflectionUtils.doWithFields(managedClass, field -> registerForReflection(reflection, | ||||||
|  | 						AnnotationUtils.findAnnotation(field, idGeneratorTypeClass), "value")); | ||||||
|  | 				ReflectionUtils.doWithMethods(managedClass, method -> registerForReflection(reflection, | ||||||
|  | 						AnnotationUtils.findAnnotation(method, idGeneratorTypeClass), "value")); | ||||||
| 			} | 			} | ||||||
| 			Class<?> embeddableInstantiatorClass = (Class<?>) AnnotationUtils.getAnnotationAttributes(annotation).get("value"); |  | ||||||
| 			reflection.registerType(embeddableInstantiatorClass, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		@Nullable | 		@Nullable | ||||||
| 		private static Class<? extends Annotation> loadEmbeddableInstantiatorClass(@Nullable ClassLoader classLoader) { | 		private static Class<? extends Annotation> loadClass(String className, @Nullable ClassLoader classLoader) { | ||||||
| 			try { | 			try { | ||||||
| 				return (Class<? extends Annotation>) ClassUtils.forName( | 				return (Class<? extends Annotation>) ClassUtils.forName(className, classLoader); | ||||||
| 						"org.hibernate.annotations.EmbeddableInstantiator", classLoader); |  | ||||||
| 			} | 			} | ||||||
| 			catch (ClassNotFoundException ex) { | 			catch (ClassNotFoundException ex) { | ||||||
| 				return null; | 				return null; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
|  | 		private void registerForReflection(ReflectionHints reflection, @Nullable Annotation annotation, String attribute) { | ||||||
|  | 			if (annotation == null) { | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			Class<?> embeddableInstantiatorClass = (Class<?>) AnnotationUtils.getAnnotationAttributes(annotation).get(attribute); | ||||||
|  | 			reflection.registerType(embeddableInstantiatorClass, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,62 @@ | ||||||
|  | /* | ||||||
|  |  * Copyright 2002-2024 the original author or authors. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *      https://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package org.springframework.orm.jpa.hibernate.domain; | ||||||
|  | 
 | ||||||
|  | import java.time.Instant; | ||||||
|  | 
 | ||||||
|  | import jakarta.persistence.Entity; | ||||||
|  | import jakarta.persistence.Id; | ||||||
|  | import org.hibernate.annotations.CreationTimestamp; | ||||||
|  | 
 | ||||||
|  | @Entity | ||||||
|  | public class Book { | ||||||
|  | 
 | ||||||
|  | 	@Id | ||||||
|  | 	private Long id; | ||||||
|  | 
 | ||||||
|  | 	private String title; | ||||||
|  | 
 | ||||||
|  | 	@CreationTimestamp | ||||||
|  | 	private Instant createdOn; | ||||||
|  | 
 | ||||||
|  | 	public Book() { | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public Long getId() { | ||||||
|  | 		return id; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void setId(Long id) { | ||||||
|  | 		this.id = id; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public String getTitle() { | ||||||
|  | 		return title; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void setTitle(String title) { | ||||||
|  | 		this.title = title; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public Instant getCreatedOn() { | ||||||
|  | 		return createdOn; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void setCreatedOn(Instant createdOn) { | ||||||
|  | 		this.createdOn = createdOn; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| /* | /* | ||||||
|  * Copyright 2002-2023 the original author or authors. |  * Copyright 2002-2024 the original author or authors. | ||||||
|  * |  * | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  * you may not use this file except in compliance with the License. |  * you may not use this file except in compliance with the License. | ||||||
|  | @ -21,6 +21,7 @@ import java.util.function.Consumer; | ||||||
| 
 | 
 | ||||||
| import javax.sql.DataSource; | import javax.sql.DataSource; | ||||||
| 
 | 
 | ||||||
|  | import org.hibernate.tuple.CreationTimestampGeneration; | ||||||
| import org.junit.jupiter.api.Test; | import org.junit.jupiter.api.Test; | ||||||
| 
 | 
 | ||||||
| import org.springframework.aot.hint.MemberCategory; | import org.springframework.aot.hint.MemberCategory; | ||||||
|  | @ -30,7 +31,6 @@ import org.springframework.aot.test.generate.TestGenerationContext; | ||||||
| import org.springframework.context.ApplicationContextInitializer; | import org.springframework.context.ApplicationContextInitializer; | ||||||
| import org.springframework.context.annotation.AnnotationConfigApplicationContext; | import org.springframework.context.annotation.AnnotationConfigApplicationContext; | ||||||
| import org.springframework.context.annotation.Bean; | import org.springframework.context.annotation.Bean; | ||||||
| import org.springframework.context.annotation.Configuration; |  | ||||||
| import org.springframework.context.aot.ApplicationContextAotGenerator; | import org.springframework.context.aot.ApplicationContextAotGenerator; | ||||||
| import org.springframework.context.support.GenericApplicationContext; | import org.springframework.context.support.GenericApplicationContext; | ||||||
| import org.springframework.core.io.ResourceLoader; | import org.springframework.core.io.ResourceLoader; | ||||||
|  | @ -64,7 +64,7 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests { | ||||||
| 	@Test | 	@Test | ||||||
| 	void processEntityManagerWithPackagesToScan() { | 	void processEntityManagerWithPackagesToScan() { | ||||||
| 		GenericApplicationContext context = new AnnotationConfigApplicationContext(); | 		GenericApplicationContext context = new AnnotationConfigApplicationContext(); | ||||||
| 		context.registerBean(EntityManagerWithPackagesToScanConfiguration.class); | 		context.registerBean(JpaDomainConfiguration.class); | ||||||
| 		compile(context, (initializer, compiled) -> { | 		compile(context, (initializer, compiled) -> { | ||||||
| 			GenericApplicationContext freshApplicationContext = toFreshApplicationContext( | 			GenericApplicationContext freshApplicationContext = toFreshApplicationContext( | ||||||
| 					initializer); | 					initializer); | ||||||
|  | @ -75,14 +75,14 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests { | ||||||
| 					EmployeeLocationConverter.class.getName()); | 					EmployeeLocationConverter.class.getName()); | ||||||
| 			assertThat(persistenceManagedTypes.getManagedPackages()).isEmpty(); | 			assertThat(persistenceManagedTypes.getManagedPackages()).isEmpty(); | ||||||
| 			assertThat(freshApplicationContext.getBean( | 			assertThat(freshApplicationContext.getBean( | ||||||
| 					EntityManagerWithPackagesToScanConfiguration.class).scanningInvoked).isFalse(); | 					JpaDomainConfiguration.class).scanningInvoked).isFalse(); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@Test | 	@Test | ||||||
| 	void contributeHints() { | 	void contributeJpaHints() { | ||||||
| 		GenericApplicationContext context = new AnnotationConfigApplicationContext(); | 		GenericApplicationContext context = new AnnotationConfigApplicationContext(); | ||||||
| 		context.registerBean(EntityManagerWithPackagesToScanConfiguration.class); | 		context.registerBean(JpaDomainConfiguration.class); | ||||||
| 		contributeHints(context, hints -> { | 		contributeHints(context, hints -> { | ||||||
| 			assertThat(RuntimeHintsPredicates.reflection().onType(DriversLicense.class) | 			assertThat(RuntimeHintsPredicates.reflection().onType(DriversLicense.class) | ||||||
| 					.withMemberCategories(MemberCategory.DECLARED_FIELDS)).accepts(hints); | 					.withMemberCategories(MemberCategory.DECLARED_FIELDS)).accepts(hints); | ||||||
|  | @ -108,6 +108,15 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests { | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@Test | ||||||
|  | 	void contributeHibernateHints() { | ||||||
|  | 		GenericApplicationContext context = new AnnotationConfigApplicationContext(); | ||||||
|  | 		context.registerBean(HibernateDomainConfiguration.class); | ||||||
|  | 		contributeHints(context, hints -> | ||||||
|  | 				assertThat(RuntimeHintsPredicates.reflection().onType(CreationTimestampGeneration.class) | ||||||
|  | 				.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(hints)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| 	@SuppressWarnings("unchecked") | 	@SuppressWarnings("unchecked") | ||||||
| 	private void compile(GenericApplicationContext applicationContext, | 	private void compile(GenericApplicationContext applicationContext, | ||||||
|  | @ -135,10 +144,25 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests { | ||||||
| 		result.accept(generationContext.getRuntimeHints()); | 		result.accept(generationContext.getRuntimeHints()); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@Configuration(proxyBeanMethods = false) | 	public static class JpaDomainConfiguration extends AbstractEntityManagerWithPackagesToScanConfiguration { | ||||||
| 	public static class EntityManagerWithPackagesToScanConfiguration { |  | ||||||
| 
 | 
 | ||||||
| 		private boolean scanningInvoked; | 		@Override | ||||||
|  | 		protected String packageToScan() { | ||||||
|  | 			return "org.springframework.orm.jpa.domain"; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public static class HibernateDomainConfiguration extends AbstractEntityManagerWithPackagesToScanConfiguration { | ||||||
|  | 
 | ||||||
|  | 		@Override | ||||||
|  | 		protected String packageToScan() { | ||||||
|  | 			return "org.springframework.orm.jpa.hibernate.domain"; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public abstract static class AbstractEntityManagerWithPackagesToScanConfiguration { | ||||||
|  | 
 | ||||||
|  | 		protected boolean scanningInvoked; | ||||||
| 
 | 
 | ||||||
| 		@Bean | 		@Bean | ||||||
| 		public DataSource mockDataSource() { | 		public DataSource mockDataSource() { | ||||||
|  | @ -156,7 +180,7 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests { | ||||||
| 		public PersistenceManagedTypes persistenceManagedTypes(ResourceLoader resourceLoader) { | 		public PersistenceManagedTypes persistenceManagedTypes(ResourceLoader resourceLoader) { | ||||||
| 			this.scanningInvoked = true; | 			this.scanningInvoked = true; | ||||||
| 			return new PersistenceManagedTypesScanner(resourceLoader) | 			return new PersistenceManagedTypesScanner(resourceLoader) | ||||||
| 					.scan("org.springframework.orm.jpa.domain"); | 					.scan(packageToScan()); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		@Bean | 		@Bean | ||||||
|  | @ -169,6 +193,8 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests { | ||||||
| 			return entityManagerFactoryBean; | 			return entityManagerFactoryBean; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		protected abstract String packageToScan(); | ||||||
|  | 
 | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue