Hibernate5 SessionImpl is not recognizing transaction started by JpaTransactionManager - spring

Hibernate5 SessionImpl is not recognizing transaction started by JpaTransactionManager
I recently migrated a legacy java web application to spring-boot, Hibernate. Jersey was already used in legacy app, so continued with spring-boot-starter-jersey. So the stack is:
RestfulService(Jersey) -> Spring Service -> Hibernate DAO (legacy code).
Both Spring service and Hibernate DAOs are used through respective public interface and has #Transactional on public method of implementation class.
Hibernate DAO looks like:
#Repository
public class UserDaoImpl implements UserDao {
#Autowired
#Qualifier("entityManagerFactory")
EntityManagerFactory entityManagerFactory;
public SessionFactory getSessionFactory() {
return entityManagerFactory.unwrap(SessionFactory.class);
}
#Override
public User getUserByUserName(String userName) throws Exception {
User user = null;
Criteria criteria = getSessionFactory().getCurrentSession().createCriteria(User.class);
criteria.add(Restrictions.eq("userName", userName));
user = (User) criteria.uniqueResult();
return user;
}
}
With JpaTransactionManager I was getting exception :
javax.persistence.TransactionRequiredException: no transaction is in progress.
javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.internal.SessionImpl.checkTransactionNeeded(SessionImpl.java:3505)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1427)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1423)
at org.springframework.orm.hibernate5.SessionFactoryUtils.flush(SessionFactoryUtils.java:147)
at org.springframework.orm.hibernate5.SpringSessionSynchronization.beforeCommit(SpringSessionSynchronization.java:95)
at org.springframework.transaction.support.TransactionSynchronizationUtils.triggerBeforeCommit(TransactionSynchronizationUtils.java:96)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerBeforeCommit(AbstractPlatformTransactionManager.java:922)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:730)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:532)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
at com.iotu.serviceImpl.UserServiceImpl$$EnhancerBySpringCGLIB$$d0c10f8c.checkLoginDetails(<generated>)
at com.iotu.webservices.UserWS.userAuthentication(UserWS.java:1467)
at com.iotu.webservices.UserWS$$FastClassBySpringCGLIB$$66e6577d.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:684)
at com.iotu.webservices.UserWS$$EnhancerBySpringCGLIB$$99ca5efe.userAuthentication(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.lambda$static$0(ResourceMethodInvocationHandlerFactory.java:76)
at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:148)
at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:191)
Switched to HibernateTransactionManager and Hibernate is able to recognize transaction in progress. No exception thrown.
#Configuration
#EnableJpaRepositories("com.app.repository")
public class AppConfiguration {
#Bean
public HibernateTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
SessionFactory sf = entityManagerFactory.unwrap(SessionFactory.class);
return new HibernateTransactionManager(sf);
}
But it has a down side. For newer features i am using spring data repository. With HibernateTransactionManager, the JpaRepository's save method is NOT working on entity. Its not firing the update sql (No error on console). findXX methods are working fine.
#Repository
public interface LessonCategoryRepository extends JpaRepository<LessonCategory, Long> {
public List<LessonCategory> findByCategoryAndLanguage(#Param("category") String category, #Param("language") String language);
}
When I switch to JpaTransactionManager, updates through JpaRepository are working fine but start getting the javax.persistence.TransactionRequiredException: no transaction is in progress for services calls that use legacy DAO classes..
To summarize:
With HibernateTransactionManager: Hibernate5 recognizes current transaction but JpaRepository is not persisting data.
With JpaTransactionManager: Hibernate5 session does NOT recognizes current transaction but JpaRepository is persisting data.
Can I make JpaTransactionManager to work with existing hibernate DAO classes? Or
can I make HibernateTransactionManager to work with JpaRepository? .. so that I can use legacy DAO classes as is but can continue using the JpaRepositories.

Related

#Transactional not starting transactions with Spring Boot 3 / Hibernate 6

I am currently migrating to Spring Boot 3 / Hibernate 6.
Hibernate is correctly parsing all the entities and repos, connecting to the database, etc...
However, it seems #Transactional is not starting transactions correctly.
Small example:
#Component
public class Test {
#Autowired
private EntityManagerFactory entityManager;
#Transactional
public void test() {
Session s = entityManager.unwrap(SessionFactory.class).getCurrentSession();
s.createQuery("FROM sometable").list();
}
}
Error:
Caused by: org.hibernate.HibernateException: Calling method 'createQuery' is not valid without an active transaction (Current status: NOT_ACTIVE)
at org.hibernate.context.internal.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:341)
Relevant Config:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages="com.somepackage")
#EntityScan(basePackages="com.somepackage")
public class TransactionConfig {
...
}
session context class in application.properties
...
spring.jpa.properties.hibernate.current_session_context_class=thread
...
If I remove the above setting of session_content_class=thread,
I get this error:
Caused by: org.hibernate.HibernateException: No CurrentSessionContext configured
Edit 1:
The below still results in the same error "is not valid without an active transaction"
#PersistenceUnit
private EntityManagerFactory entityManager;
Edit 2:
If I do not unwrap a session and just call a class with extends extends JpaRepository, it works... but it creates a new transaction and ignores the parent #Transaction
Fix was the following:
#PersistenceContext
private EntityManager entityManager;
and to unwrap:
Session s = entityManager.unwrap(Session.class);

How to make Hibernate and Spring track transactions consistently?

When using TransactionTemplate in an Executor thread, it appears unable to consistently find the current transaction, resulting in problems like this:
14:20:56.022 [pool-2-thread-1] DEBUG HibernateTransactionManager - Creating new transaction with name [null]: PROPAGATION_REQUIRED,ISOLATION_SERIALIZABLE
14:20:56.022 [pool-2-thread-1] DEBUG HibernateTransactionManager - Opened new Session [SessionImpl(1516598186<open>)] for Hibernate transaction
14:20:56.022 [pool-2-thread-1] DEBUG HibernateTransactionManager - Preparing JDBC Connection of Hibernate Session [SessionImpl(1516598186<open>)]
14:20:56.023 [pool-2-thread-1] DEBUG HibernateTransactionManager - Exposing Hibernate transaction as JDBC [org.springframework.orm.hibernate5.HibernateTransactionManager$$Lambda$1238/0x00000008009dfc40#5cc5f4]
14:20:56.026 [pool-2-thread-1] DEBUG HibernateTransactionManager - Found thread-bound Session [SessionImpl(1516598186<open>)] for Hibernate transaction
14:20:56.026 [pool-2-thread-1] DEBUG HibernateTransactionManager - Participating in existing transaction
14:20:56.052 [pool-2-thread-1] DEBUG SessionFactoryUtils - Flushing Hibernate Session on transaction synchronization
14:20:56.054 [pool-2-thread-1] DEBUG HibernateTransactionManager - Initiating transaction rollback after commit exception
javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.internal.AbstractSharedSessionContract.checkTransactionNeededForUpdateOperation(AbstractSharedSessionContract.java:445)
at org.hibernate.internal.SessionImpl.checkTransactionNeededForUpdateOperation(SessionImpl.java:3478)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1394)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1389)
at org.springframework.orm.hibernate5.SessionFactoryUtils.flush(SessionFactoryUtils.java:113)
at org.springframework.orm.hibernate5.SpringSessionSynchronization.beforeCommit(SpringSessionSynchronization.java:95)
at org.springframework.transaction.support.TransactionSynchronizationUtils.triggerBeforeCommit(TransactionSynchronizationUtils.java:97)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerBeforeCommit(AbstractPlatformTransactionManager.java:916)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:727)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:152)
at org.springframework.transaction.support.TransactionOperations.executeWithoutResult(TransactionOperations.java:67)
at com.example.MyClass.doProcessing(MyClass.java:109)
at com.example.MyClass.lambda$scheduleJob$4(MyClass.java:93)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:829)
14:20:56.054 [pool-2-thread-1] DEBUG HibernateTransactionManager - Rolling back Hibernate transaction on Session [SessionImpl(1516598186<open>)]
14:20:56.056 [pool-2-thread-1] DEBUG HibernateTransactionManager - Closing Hibernate Session [SessionImpl(1516598186<open>)] after transaction
JPA is configured manually, because I have multiple DataSources in the application.
Relevant beans:
#Bean
#TelemetryDb
public LocalContainerEntityManagerFactoryBean telemetryEntityManagerFactory(
EntityManagerFactoryBuilder builder,
#TelemetryDb DataSource dataSource
) {
return builder.dataSource(dataSource)
.packages("com.example.model.telemetry")
.persistenceUnit("telemetry")
.properties(getVendorProperties(dataSource))
.jta(false)
.build();
}
#Bean
#TelemetryDb
#Order(Ordered.HIGHEST_PRECEDENCE)
public PlatformTransactionManager telemetryTransactionManager(#TelemetryDb EntityManagerFactory factory) {
return new HibernateTransactionManager((SessionFactory) factory);
}
#Configuration
#EnableJpaRepositories(
basePackageClasses = PackageMarker.class,
includeFilters = #ComponentScan.Filter(type = FilterType.CUSTOM, value = TelemetryFilter.class),
entityManagerFactoryRef = "telemetryEntityManagerFactory",
transactionManagerRef = "telemetryTransactionManager"
)
public static class Telemetry { }
The properties are:
hibernate.auto_quote_keyword=true
hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext
hibernate.dialect=com.example.PostgreSQL12Dialect
And some code that gives the above log:
#Service
#RequriedArgsConstructor
public class MyClass {
private final MyRepository repository;
private ExecutorService executor = Executors.newSingleThreadExecutor();
private SessionFactory sessionFactory;
private TransactionTemplate transactionTemplate;
#Autowired
public void setTransactionManager(#TelemetryDb PlatformTransactionManager manager) {
this.sessionFactory = ((HibernateTransactionManager) manager).getSessionFactory();
this.transactionTemplate = new TransactionTemplate(manager);
this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
}
public scheduleJob(long entityId) {
Boolean submit = transactionTemplate.execute(status -> {
MyEntity entity = repository.findById(entity).orElseThrow();
if (entity.isProcessing()) {
return false;
} else {
entity.setProcessing(true);
return true;
}
});
if (Boolean.TRUE.equals(submit)) {
executor.submit(() -> doProcessing(entityId));
}
}
protected void doProcessing(long entityId) {
transactionTemplate.executeWithoutResult(status -> {
MyEntity entity = repository.findById(entity).orElseThrow();
entity.setBlobField(sessionFactory.getCurrentSession().getLobHelper().createBlob(new byte[]{});
entity.setProcessing(false);
status.flush();
});
}
}
Note that the usage of TransactionTemplate outside of the executor thread completes with no issues. Note also that the createBlob() and flush() do not complain about a missing transaction.
I assume I have missed something in the configuration, but I have been trying various permutations of it, and the doProcessing method, but cannot get anything to work.
After further debugging, it appears that the getCurrentSession() call causes SpringSessionContext to look for, and then create, a session using a SessionFactoryImpl as the key rather than the LocalContainerEntityManagerFactoryBean proxy.
There doesn't seem to be any way to control the construction of the context within Hibernate. I think if I could obtain the raw SessionFactoryImpl to pass to HibernateTransactionManager then that would fix it.
I tried that (using EntityManagerFactory.unrwap) but that broke a whole load of other stuff, because when Spring Data calls createQuery it looks for the current session using the bean proxy.
It appears that SpringSessionContext doesn't work as intended. Either there was a change in Hibernate and/or there wasn't sufficient integration testing.
There is a workaround. Ignore SessionFactory.getCurrentSession() and use the TransactionSynchronizationManager directly:
#PersistenceUnit(unitName = "telemetry")
private EntityManagerFactory entityManagerFactory;
private #NonNull Session getCurrentSession() {
Object resource = TransactionSynchronizationManager.getResource(entityManagerFactory);
if (resource == null) {
throw new IllegalStateException("No current transaction");
}
return ((SessionHolder) resource).getSession();
}
That only works within an existing session. More code would be required to create a new one to match the full behaviour.
Unsetting hibernate.current_session_context_class ensures you will always get an exception when trying to use the incompatible interface, rather than getting surprise extra sessions.

Serialization exception using spring-sessions

Primefaces/Joinfaces JSF app in Spring-Boot.
App is working fine running stand-alone, but I have recently started implementing session replication via Spring-Session. When the session is persisted to the session store, I get a not serializable exception.
Caused by: java.io.NotSerializableException:
com.company.application.service.dao.security.RoleBasedSecurityDao$$EnhancerBySpringCGLIB$$9de506c
Looking at that error message, it looks like the serialization exception is not for the class itself, but for something owned by the class. The only thing it has on it is the JDBCTemplate.
#Repository
public class RoleBasedSecurityDao {
private final static Logger log = LoggerFactory.getLogger(RoleBasedSecurityDao.class);
private NamedParameterJdbcTemplate jdbcTemplate;
#Autowired
#Qualifier("dataSource")
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
[...]
}
If I add "implements Serializable" to the class definition, the error changes:
Caused by: java.io.NotSerializableException:
org.springframework.dao.support.PersistenceExceptionTranslationInterceptor
I am not familiar with JSF, but from what I have read, the expectation is that all of your JSF classes are serializable. How can I make my DAO serializable, when it needs an instance of JdbcTemplate?
As #Selaron pointed out, the issue was non-transient spring beans on JSF controllers. Don't do that.

Spring - Data persisted by JdbcTemplate unable to be seen by JpaRepository

I am building a Spring Boot application which requires the need for persistence via JDBC and selecting/reading via JPA/Hibernate. I have implemented both of these types of operations using Spring's JdbcTemplate and Spring Data's JpaRepository.
After I persist using JdbcTemplate I am unable to see the data via JpaRepository even though they share the same datasource. I am able to read the data if I use JdbcTemplate.
NOTE: I am using two data sources. One is configured in another class without the #Primary annotation using its own entity manager factory and transaction manager, which is why I've needed to explicitly define it below using Spring Boot's default bean terminology "transactionManager" and "entityManagerFactory".
The following is my embedded database configuration for the primary beans:
#Configuration
#EnableJpaRepositories(basePackages = {"com.repository"})
public class H2DataSourceConfiguration {
private static final Logger log = LoggerFactory.getLogger(H2DataSourceConfiguration.class);
#Bean(destroyMethod = "shutdown")
#Primary
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.setName("dataSource")
.build();
}
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.my.domain", "org.springframework.data.jpa.convert.threeten")
.build();
}
#Bean
#Primary
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager(entityManagerFactory);
return jpaTransactionManager;
}
}
The persistence happens in a different transaction to the reading of the data, however they share the same service.
Both operations happen within the #Transactional annotation. Both repository beans are specified in the same service and also contain the #Transactional annotation. The service looks as follows:
#Service
#Transactional
public class MyServiceImpl implements MyService {
private static final Logger log = LoggerFactory.getLogger(MyServiceImpl.class);
#Autowired
private MyJpaRepository myJpaRepository;
#Autowired
private MyJdbcRepository myJdbcRepository;
...
}
MyJdbcRepositoryImpl.java:
#Repository
#Transactional(propagation = Propagataion.MANDATORY)
public class MyJdbcRepositoryImpl implements MyJdbcRepository {
#Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
// methods within here all use jdbcTemplate.query(...)
}
MyJpaRepository.java:
#Repository
#Transactional(propagation = Propagataion.MANDATORY)
public interface AcquisitionJpaRepository extends JpaRepository<AcquisitionEntity, Long> {
}
Is it at all possible that the jdbctemplate calls are saving to a different h2 database?
The above configuration is correct!
The problem was that the JdbcTemplate calls had the schema owner as a prefix.
For example:
select * from I_AM_SCHEMA.KILL_ME
However, I had both the #Entity annotation and the #Table annotation on the entity object and only specified the table name!
Example:
#Entity
#Table(name = "KILL_ME")
So, we were writing to one table with JdbcTemplate but reading from a completely different other table via JPA/Hibernate due to us missing the prefix.
The correct fix was to prefix the entity name in the #Entity annotation:
#Entity("I_AM_SCHEMA.KILL_ME")
DONE!

Spring Data JPA Transaction - No Transaction in progress - Spring Data Neo4j

I think i'm missing something obvious. Iam trying to make a entity persist into a database via a JUnit Test case, however it doesnt seem to be persisting due to no active transaction.
Configuration:
#Configuration
#EnableTransactionManagement
public class TransactionConfig {
#Inject
private EntityManagerFactory entityMangerFactory;
#Bean
public JpaTransactionManager transactionManager(){
return new JpaTransactionManager(entityMangerFactory);
}
TestCase:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = { Application.class })
#ActiveProfiles(CommonConstants.SPRING_PROFILE_TEST)
#IntegrationTest
#WebAppConfiguration
public class UserRepositoryTest {
#Inject
UserRepository userRepo;
#Test
#Rollback(false)
#Transactional("transactionManager")
public void addUser() {
User user = BootstrapDataPopulator.getUser();
userRepo.save(user);
System.out.println(user.getId()); //Successfully outputs the id generate by hibernate
assertNotNull(user.getId());
}
}
^This test case executed successfully however I do not see any entiites persisted in the database as expected.
When I change the from userRepo.save(user) to userRepo.saveAndFlush(user) I get the following exception:
javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.checkTransactionNeeded(AbstractEntityManagerImpl.java:1171)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:1332)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Spring Boot AutoConfiguration Report: http://dumptext.com/YcGaR3Wf
Names of all Spring Beans Initialized: http://dumptext.com/jp9O6l8v
I am using Spring Data Neo4j (SDN) in my application as well. SDN comes with a default class Neo4jConfiguration which has:
#Bean(name = {"neo4jTransactionManager","transactionManager"})
#Qualifier("neo4jTransactionManager")
public PlatformTransactionManager neo4jTransactionManager() throws Exception {
return new JtaTransactionManagerFactoryBean(getGraphDatabaseService()).getObject();
}
The "transactionManager" overrides the bean defined in my TransactionConfig class. Hence the reason no Entity transaction was in progress. I stopped using the SDN class Neo4jConfiguration. This resolved my issue.

Resources