Hello everyone,
I’m currently switching from hibernate 5 to hibernate 6.2 and spring boot 3, and I’m facing an issue with hibernate 6 - FYI, the code I’ll provide was working with the old version.
I have the following entities:
public class Tour {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
@Positive
private Integer id;
@ManyToOne(fetch = FetchType.EAGER)
private Location primaryLocation;
}
public class Location implements PreviousState {
@Id
@Positive
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@NotNull
@Column(nullable = false)
private String name;
private transient LocationDTO previousState;
@PostLoad
@Override
public void setPreviousState() {
var mapper = SpringContext.getBean(LocationMapper.class);
previousState = mapper.toBasicDTO(this);
}
}
public class LocationToTag {
@Id
@Positive
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@NotNull
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "location_id", nullable = false)
private Location location;
@NotNull
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "tag_id", nullable = false)
private Tag tag;
}
public class Tag {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Integer id;
@Column(length = 50, nullable = false)
@Length(min = 1, max = 50)
@NotNull
private String name;
}
// PreviousState is just an interface here.
public interface PreviousState {
void setPreviousState();
Object getPreviousState();
}
// This only a wrapper over the @Transactional annotation with pre-set values.
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(value = "managementPostgresTransactionManager", propagation = Propagation.REQUIRED)
public @interface ManagementPostgresTransaction {
}
// This is the class for setting the configuration of the management datasource.
@Configuration
@EnableTransactionManagement
public class ManagementPostgresDbConfig {
@Bean
@ConfigurationProperties(prefix = "spring.managementpostgresdatasource.hikari")
@Primary
public HikariConfig managementReadWriteHikariConfig() {
return new HikariConfig();
}
@Bean
@ConfigurationProperties(prefix = "spring.managementpostgresdatasource.read-only.hikari")
public HikariConfig managementReadOnlyHikariConfig() {
return new HikariConfig();
}
@Bean
@Primary
public DataSource managementPostgresDataSource() {
TransactionRoutingDataSource routingDataSource = new TransactionRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(
TransactionRoutingDataSource.DataSourceType.READ_WRITE,
new HikariDataSource(managementReadWriteHikariConfig())
);
dataSourceMap.put(
TransactionRoutingDataSource.DataSourceType.READ_ONLY,
new HikariDataSource(managementReadOnlyHikariConfig())
);
routingDataSource.setTargetDataSources(dataSourceMap);
return routingDataSource;
}
@Bean
@Primary
public DatabaseSchemaValidator managementPostgresSchemaValidator(
@Qualifier("managementPostgresDataSource") DataSource managementDataSource
) {
return new DatabaseSchemaValidator(managementDataSource);
}
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean managementPostgresEntityManager(CommonDatabaseConfig dbConfig) {
var dataSource = managementPostgresDataSource();
var entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource);
entityManagerFactory.setPersistenceUnitName("managementPostgres");
entityManagerFactory.setPackagesToScan("com.models");
entityManagerFactory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
var jpaPropertyMap = dbConfig.getNewJpaPropertyMap();
jpaPropertyMap.put(AvailableSettings.DIALECT, PostgreSQLDialect.class);
jpaPropertyMap.put(
"hibernate.integrator_provider",
(IntegratorProvider) () -> List.of(managementPostgresSchemaValidator(dataSource))
);
// This is required for TransactionRoutingDataSource to work
jpaPropertyMap.put(AvailableSettings.CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT, true);
// The following will delay getting connections until after TransactionSynchronizationManager is ready
// More information can be found here: https://stackoverflow.com/a/68078228/8473419
jpaPropertyMap.put(
AvailableSettings.CONNECTION_HANDLING,
PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION
);
entityManagerFactory.setJpaPropertyMap(jpaPropertyMap);
return entityManagerFactory;
}
@Bean
@Primary
public PlatformTransactionManager managementPostgresTransactionManager(
@Qualifier("managementPostgresEntityManager") EntityManagerFactory entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
@Bean
@Primary
public TransactionTemplate managementPostgresTransactionTemplate(
@Qualifier("managementPostgresTransactionManager") PlatformTransactionManager transactionManager
) {
return new TransactionTemplate(transactionManager);
}
}
The mappers here just maps between the class and it’s DTO, nothing more.
The issues that I’m facing now is when I request the DB using the jpa repository to get all the tours by specific ids tours will be fetched with location eager loaded. Now, after the eager loading of the location, the setPreviousState
method which is called at @PostLoad
will be triggered, now in the location mapper, there a method to fetch the locationToTags related to the location and attach them to the DTO, the call to this method that return the locationToTags by location throws the following exception: Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: Tour
Notes:
- This code was working with hibernate 5.
- I don’t save anything anywhere, I’m only fetching data.
This is how the fetch is happening:
@Repository
public interface TourRepository extends JpaRepository<Tour, Integer> {
List<Tour> findAllByIdIn(Collection<Integer> ids);
}
public class TourService {
private final TourRepository tourRepository;
@ManagementPostgresTransaction
@SuppressWarnings("unchecked")
public void rebuildTourFromLocationIds(List<? extends Integer> ids) {
var tourIds = (List<Integer>) ids;
var tours = tourRepository.findAllByIdIn(tourIds);
}
}
public interface LocationMapper {
public LocationDTO toDto(Location location) {
... mapping
var tourToTagService = SpringContext.getBean(LocationToTagService.class);
var tagsByType = tourToTagService.getTagListAsDTO(location).stream()
.collect(Collectors.groupingBy(TagDTO::getType));
return dto
}
}
public class LocationToTagService {
private final LocationToTagRepository locationToTagRepository;
@ManagementPostgresTransaction
public List<Tag> getTagList(Location location) {
// This is the line that is throwing the exception.
var ltts = locationToTagRepository.findAllByLocationId(location.getId());
return ltts.stream()
.map(LocationToTag::getTag)
.collect(Collectors.toList());
}
@ManagementPostgresTransaction
public List<TagDTO> getTagListAsDTO(Location location) {
return getTagList(location).stream().map(tagMapper::toDTO).collect(Collectors.toList());
}
}
Stacktrace:
org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: Tour
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:289)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:229)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:550)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:134)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
at jdk.proxy3/jdk.proxy3.$Proxy324.findAllByLocation(Unknown Source)
at LocationToTagService.getTagList_aroundBody2(LocationToTagService.java:50)
at LocationToTagService$AjcClosure3.run(LocationToTagService.java:1)
at .LocationToTagService.getTagList(LocationToTagService.java:50)
at .LocationToTagService.getTagListAsDTO_aroundBody6(LocationToTagService.java:75)
at .LocationToTagService$AjcClosure7.run(LocationToTagService.java:1)
at LocationToTagService.getTagListAsDTO(LocationToTagService.java:75)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:391)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:702)
a LocationToTagService$$SpringCGLIB$$0.getTagListAsDTO(<generated>)
at LocationMapper.toDto_aroundBody0(LocationMapper.java:97)
at LocationMapper$AjcClosure1.run(LocationMapper.java:1)
at .LocationMapper.toDto(LocationMapper.java:96)
at .LocationMapperImpl.toDTO(LocationMapperImpl.java:248)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698)
at LocationMapperImpl$$SpringCGLIB$$0.ttDTO(<generated>)
at .Location.setPreviousState(Location.java:231)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.hibernate.jpa.event.internal.EntityCallback.performCallback(EntityCallback.java:50)
at org.hibernate.jpa.event.internal.CallbackRegistryImpl.callback(CallbackRegistryImpl.java:113)
at org.hibernate.jpa.event.internal.CallbackRegistryImpl.postLoad(CallbackRegistryImpl.java:96)
at org.hibernate.event.internal.DefaultPostLoadEventListener.onPostLoad(DefaultPostLoadEventListener.java:44)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
at org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl.lambda$postLoad$0(JdbcValuesSourceProcessingStateStandardImpl.java:182)
at java.base/java.util.HashMap.forEach(HashMap.java:1421)
at org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl.postLoad(JdbcValuesSourceProcessingStateStandardImpl.java:175)
at org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl.finishUp(JdbcValuesSourceProcessingStateStandardImpl.java:164)
at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:206)
at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:33)
at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:362)
at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:168)
at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.list(JdbcSelectExecutorStandardImpl.java:93)
at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:31)
at org.hibernate.loader.ast.internal.SingleIdLoadPlan.load(SingleIdLoadPlan.java:145)
at org.hibernate.loader.ast.internal.SingleIdLoadPlan.load(SingleIdLoadPlan.java:117)
at org.hibernate.loader.ast.internal.SingleIdEntityLoaderStandardImpl.load(SingleIdEntityLoaderStandardImpl.java:72)
at org.hibernate.persister.entity.AbstractEntityPersister.doLoad(AbstractEntityPersister.java:3381)
at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3371)
at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:602)
at org.hibernate.event.internal.DefaultLoadEventListener.loadFromCacheOrDatasource(DefaultLoadEventListener.java:588)
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:557)
at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:550)
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:202)
at org.hibernate.event.internal.DefaultLoadEventListener.proxyImplementation(DefaultLoadEventListener.java:402)
at org.hibernate.event.internal.DefaultLoadEventListener.narrowedProxy(DefaultLoadEventListener.java:395)
at org.hibernate.event.internal.DefaultLoadEventListener.loadWithRegularProxy(DefaultLoadEventListener.java:274)
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:237)
at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:106)
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:78)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:138)
at org.hibernate.internal.SessionImpl.fireLoadNoChecks(SessionImpl.java:1231)
at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1075)
at org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer.initializeInstance(EntitySelectFetchInitializer.java:185)
at org.hibernate.sql.results.internal.InitializersList.initializeInstance(InitializersList.java:70)
at org.hibernate.sql.results.internal.StandardRowReader.coordinateInitializers(StandardRowReader.java:111)
at org.hibernate.sql.results.internal.StandardRowReader.readRow(StandardRowReader.java:87)
at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:199)
at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:33)
at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:362)
at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:168)
at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.list(JdbcSelectExecutorStandardImpl.java:93)
at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:31)
at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.lambda$new$0(ConcreteSqmSelectQueryPlan.java:109)
at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:302)
at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:243)
at org.hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:518)
at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:367)
at org.hibernate.query.Query.getResultList(Query.java:119)
at org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:129)
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:92)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:148)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:136)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:136)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:120)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:164)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:77)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:391)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:134)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
at jdk.proxy3/jdk.proxy3.$Proxy295.findAllByIdIn(Unknown Source)
at TourFromLocationService.rebuildTourFromLocationIds_aroundBody0(TourFromLocationService.java:35)
at TourFromLocationService$AjcClosure1.run(TourFromLocationService.java:1)