Spring application duplicating functionality because of ApplicationContext - java

I've got a Spring application with Hibernate integration, deployed on a JBoss server locally. I configured the app using annotations throughout and a Config.java class for configurations of Spring and Hibernate.
I'm getting this warning in my logs:
17:13:18,153 WARN spi.TypeConfiguration$Scope:273 - HHH000233:
Scoping types to session factory
org.hibernate.internal.SessionFactoryImpl#4945417e after already
scoped org.hibernate.internal.SessionFactoryImpl#4945417e
When I tried debugging throughout my program, I found out about the place in my program the log was coming from. In one of my servlets, I've got this:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
...
}
I've tried instead using
#Autowired
private ApplicationContext context;
in the class, but that breaks my application.
A little more verbose logging:
17:57:35,211 DEBUG internal.SessionFactoryImpl:292 - Instantiated session factory
17:57:35,211 DEBUG spi.TypeConfiguration:146 - Scoping TypeConfiguration [org.hibernate.type.spi.TypeConfiguration#42990ff8] to SessionFactoryImpl [org.hibernate.internal.SessionFactoryImpl#494db1a3]
17:57:35,211 DEBUG spi.TypeConfiguration:148 - Scoping TypeConfiguration [org.hibernate.type.spi.TypeConfiguration#42990ff8] to SessionFactory [org.hibernate.internal.SessionFactoryImpl#494db1a3]
17:57:35,211 WARN spi.TypeConfiguration$Scope:273 - HHH000233: Scoping types to session factory org.hibernate.internal.SessionFactoryImpl#494db1a3 after already scoped org.hibernate.internal.SessionFactoryImpl#494db1a3
17:57:35,225 TRACE internal.AbstractServiceRegistryImpl:228 - Initializing service [role=org.hibernate.persister.spi.PersisterFactory]
17:57:35,227 TRACE internal.AbstractServiceRegistryImpl:228 - Initializing service [role=org.hibernate.persister.spi.PersisterClassResolver]
What this all results in is all of my transactions taking place twice, both in my classes and in my database. Anyone have any ideas?

Related

Can't config Java Spring Boot data session Mongodb

I've been using this guide to set up spring session data with mongodb
https://docs.spring.io/spring-session-data-mongodb/docs/2.1.1.RELEASE/reference/htmlsingle/#introduction
However I am having problems with configuration. I'm using Mongodb with Spring boot and I'm trying to config my session time and session name for Spring boot web application, but it keeps defaulting to 30 minutes and the collection name in mongodb is still 'sessions'
These are what I have tried:
Added these to application.properties:
server.session.timeout=1
spring.session.mongodb.collection-name=TestSESSIONS
and this
server.servlet.session.timeout=60s
spring.session.mongodb.collection-name=TestSESSIONS
none of those config work
I've looked over this URL for spring common application properties for mongodb but none of it help to config the session time and collection name for mongodb.
After doing hours of research it seems like spring boot uses some kind of autoconfig with this "org.springframework.boot.autoconfigure"
so then I added this in my application.properties
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration
to disable the autoconfigure.
but now it just give me this error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method mongoSessionRepository in org.springframework.session.data.mongo.config.annotation.web.http.MongoHttpSessionConfiguration required a bean of type 'org.springframework.data.mongodb.core.MongoOperations' that could not be found.
The following candidates were found but could not be injected:
- Bean method 'mongoTemplate' in 'MongoDataAutoConfiguration' not loaded because AnyNestedCondition 0 matched 2 did not; NestedCondition on MongoDataAutoConfiguration.AnyMongoClientAvailable.FallbackClientAvailable #ConditionalOnBean (types: com.mongodb.client.MongoClient; SearchStrategy: all) did not find any beans of type com.mongodb.client.MongoClient; NestedCondition on MongoDataAutoConfiguration.AnyMongoClientAvailable.PreferredClientAvailable #ConditionalOnBean (types: com.mongodb.MongoClient; SearchStrategy: all) did not find any beans of type com.mongodb.MongoClient
Action:
Consider revisiting the entries above or defining a bean of type 'org.springframework.data.mongodb.core.MongoOperations' in your configuration.
this is the #bean from the spring.io guide 'mongoSessionConverter' from above link
this is the java file MongoHttpSessionConfiguration from spring that that's autoconfig by spring; I have tried extending "MongoHttpSessionConfiguration" and overriding the setter methods my self. Such as the "setMaxInactiveIntervalInSeconds" for sessionTime and
"setCollectionName" for mongododb database collection name.
but I've have this error:
Description:
The bean 'mongoSessionRepository', defined in class path resource [com/khatpass/app/config/SessionListenerConfig.class], could not be registered. A bean with that name has already been defined in class path resource [org/springframework/session/data/mongo/config/annotation/web/http/MongoHttpSessionConfiguration.class] and overriding is disabled.
I am stuck on trying to configure spring boot session with Mongodb. The session always defaulting to 30 minutes and the collection name is always 'sessions' in mongodb collections. Not sure how to change that serverSelectionTimeout='30000 ms' and mongodb collections name "sessions" I don't know what to do, need help.
2019-02-24 13:39:54.501 INFO 36113 --- [ main] org.mongodb.driver.cluster : Cluster created with settings {hosts=[localhost:27017], mode=MULTIPLE, requiredClusterType=UNKNOWN, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500}
After doing so much research and then finally, going through the source code, I found the solution:
#EnableMongoHttpSession(maxInactiveIntervalInSeconds = 24 * 60 * 60)
public class SessionConfiguration {}
To override the default collection name, there is another annotation attribute collectionName.
This is working for Spring Boot 2.1.1
After looking over the class MongoOperationsSessionRepository from org.springframework.session.data.mongo it seems like it can't be config through application.properties because the class is using static final values
public static final int DEFAULT_INACTIVE_INTERVAL = 1800;
and
public static final String DEFAULT_COLLECTION_NAME = "sessions";
only way to change the value is intercept the object before it gets saved. No getters or setters for those fields, it can't be change in an easy way, what a joke!

EJB with CMT when migrate from JBoss 7 to WildFly 9

I'm migrating my application from JBoss 7 to WildFly (v9.0.1) and it is not deployed because of bean transaction management error.
Caused by: javax.naming.NamingException: WFLYNAM0062: Failed to lookup env/com.component.eventmgt.EventServiceImpl/transaction [Root exception is java.lang.RuntimeException: WFLYNAM0059: Resource lookup for injection failed: java:jboss/UserTransaction]
at org.jboss.as.naming.ServiceBasedNamingStore.lookup(ServiceBasedNamingStore.java:157)
at org.jboss.as.naming.ServiceBasedNamingStore.lookup(ServiceBasedNamingStore.java:83)
at org.jboss.as.naming.NamingContext.lookup(NamingContext.java:207)
at org.jboss.as.naming.NamingContext.lookup(NamingContext.java:193)
at org.jboss.as.naming.NamingContext.lookup(NamingContext.java:189)
at org.jboss.as.naming.deployment.ContextNames$BindInfo$1$1.getReference(ContextNames.java:316)
... 90 more
Caused by: java.lang.RuntimeException: WFLYNAM0059: Resource lookup for injection failed: java:jboss/UserTransaction
at org.jboss.as.naming.deployment.ContextNames$BindInfo$1$1.getReference(ContextNames.java:319)
at org.jboss.as.naming.ServiceBasedNamingStore.lookup(ServiceBasedNamingStore.java:143)
... 95 more
Caused by: javax.naming.NameNotFoundException: UserTransaction [Root exception is java.lang.IllegalStateException: WFLYEJB0137: Only session and message-driven beans with bean-managed transaction demarcation are allowed to access UserTransaction]
at org.jboss.as.naming.ServiceBasedNamingStore.lookup(ServiceBasedNamingStore.java:153)
at org.jboss.as.naming.ServiceBasedNamingStore.lookup(ServiceBasedNamingStore.java:83)
at org.jboss.as.naming.NamingContext.lookup(NamingContext.java:207)
at org.jboss.as.naming.NamingContext.lookup(NamingContext.java:193)
at org.jboss.as.naming.NamingContext.lookup(NamingContext.java:189)
at org.jboss.as.naming.deployment.ContextNames$BindInfo$1$1.getReference(ContextNames.java:316)
... 96 more
Here is the EventServiceImpl class.
#Stateless
#Remote(EventService.class)
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public class EventServiceImpl implements EventService {
/**
* Logger
*/
private static Logger log = LoggerFactory.getLogger(EventService.class);
private EventTableDAO eventDao;
#PersistenceContext(unitName = "SOMF-GT")
private EntityManager entityManager;
#Resource
private UserTransaction transaction;
public List<Map> loadEvents() throws EventsException {
Configuration configurationEntry = new Configuration();
try {
Map configuration = configurationService.getConfiguration();
if (configuration != null) {
eventDao = new EventTableDAO(Event.class, entityManager, transaction);
List<Map> eventsMapList = new ArrayList();
}
}
I know that if i changed the transaction management to BMT with #TransactionManagement(TransactionManagementType.BEAN) but then the following error emerge
WFLYJPA0060: Transaction is required to perform this operation (either use a transaction or extended persistence context)
I want to know why we have to change this in the first place ?
Any information, please !
These changes rolled out in Wildfly 8 and were (as noted below) based on standardization of the global JNDI namespace in EJB 3.1.
From the Wildfly 8 Developer Guide:
EJB 3.1 introduced a standardized global JNDI namespace and a series of related namespaces that map to the various scopes of a Java EE application. The three JNDI namespaces used for portable JNDI lookups are java:global, java:module, and java:app. If you use JNDI lookups in your application, you will need to change them to follow the new standardized JNDI namespace convention.
To conform to the new portable JNDI namespace rules, you will need to review the JNDI namespace rules and modify the application code to follow these rules.
The guide further notes:
WildFly 8 has tightened up on JNDI namespace names to provide predictable and consistent rules for every name bound in the application server and to prevent future compatibility issues. This means you might run into issues with the current namespaces in your application if they don't follow the new rules.
Here's a snippet from the table showing Examples of JNDI mappings in previous releases and how they might look now specific to the UserTransaction:
Previous Namespace New Namespaces
------------------ --------------
java:comp/UserTransaction java:comp/UserTransaction (This will not be accessible for non EE threads, e.g. Threads your application directly creates)
java:comp/UserTransaction java:jboss/UserTransaction (Globally accessible, use this if java:comp/UserTransaction is not available)
Edit re: WFLYEJB0137:
This is theory-craft and may be worthless - let me know and I'll delete it. Java EE 6 Tutorial - Container-Managed Transactions says:
Enterprise beans that use container-managed transaction demarcation also must not use the javax.transaction.UserTransaction interface.
Further:
(Transaction) Required Attribute
If the client is running within a transaction and invokes the enterprise bean’s method, the method executes within the client’s transaction. If the client is not associated with a transaction, the container starts a new transaction before running the method.
The Required attribute is the implicit transaction attribute for all enterprise bean methods running with container-managed transaction demarcation. You typically do not set the Required attribute unless you need to override another transaction attribute. Because transaction attributes are declarative, you can easily change them later.
The exception message pretty much says it all:
WFLYEJB0137: Only session and message-driven beans with bean-managed transaction demarcation are allowed to access UserTransaction
Your EJB is using container managed transaction (CMT) demarcation which does not inter-operate with bean managed transaction (BMT) demarcation wherein UserTransaction lies.
Regarding a switch to BMT and
WFLYJPA0060: Transaction is required to perform this operation (either use a transaction or extended persistence context)
I found Transaction is required to perform this operation (either use a transaction or extended persistence context) which seems to indicate that the transaction is to be managed by you, as you noted in your comments #Marco. It appears that you have made the appropriate modification.

Spring is unable to inject an EntityManager using #PersistenceContext

I have a Java 8/spring 4.3.5.RELEASE web application tha runs on a wildfly 10 server. I use a persistence.xml file. I enabled trace logging on the jboss jpa and hibernate classes and I can see this file gets picked up and is resolved smoothly into a persistence unit:
DEBUG [] [org.hibernate.jpa.internal.util.LogHelper] PersistenceUnitInfo [
name: testcontext
persistence provider classname: org.hibernate.jpa.HibernatePersistenceProvider
classloader: ModuleClassLoader for Module "deployment.BasicWebapp.war:main" from Service Module Loader
excludeUnlistedClasses: false
JTA datasource: org.jboss.as.connector.subsystems.datasources.WildFlyDataSource#fb80232
Non JTA datasource: null
Transaction type: JTA
PU root URL: vfs:/C:/Users/Me/Wildfly 10.0.0/standalone/deployments/BasicWebapp.war/WEB-INF/classes/
Shared Cache Mode: UNSPECIFIED
Validation Mode: AUTO
Jar files URLs []
Managed classes names [
com.company.project.data.User]
Mapping files names []
Properties [
jboss.entity.manager.jndi.name: persistence/testcontext]
I now want a dao class to have an entity manager injected by spring:
UserDao.class
#Repository
public class UserDao
{
#PersistenceContext(unitName = "testcontext")
private EntityManager entityManager;
}
I have component scanning and annotation config so both the #Repository and the #PersistenceContext annotation gets processed upon starting my application:
spring-servlet.xml
<context:component-scan base-package="com.company.project" />
<context:annotation-config/>
<jee:jndi-lookup id="entityManagerFactory" jndi-name="persistence/testcontext"/>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"/>
<tx:annotation-driven/>
The injection fails though, in two possible ways:
If I use #PersistenceContext(unitName = "testcontext"), the error is:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'testcontext' available
If I use #PersistenceContext, the error is:
Caused by: java.lang.NullPointerException
at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findDefaultEntityManagerFactory(PersistenceAnnotationBeanPostProcessor.java:580)
at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findEntityManagerFactory(PersistenceAnnotationBeanPostProcessor.java:546)
at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$PersistenceElement.resolveEntityManager(PersistenceAnnotationBeanPostProcessor.java:707)
at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$PersistenceElement.getResourceToInject(PersistenceAnnotationBeanPostProcessor.java:680)
at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:169)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.postProcessPropertyValues(PersistenceAnnotationBeanPostProcessor.java:354)
... 44 more
So I'm missing something in this configuration to tell the part of Spring that processes the #PersistenceContext annotation to look at either the entityManagerFactory bean that I declared in the spring-servlet.xml or just use the container's persistence unit directly. What do I need to add to achieve that?
I'm also a bit sketchy on the transactionmanager part. Does wildfly provide the transactionmanager or not? If it does, do I need to create a bean for it in Spring (will it pick up the one created by jboss or make its own one?)
I think you should double check you configuration setup against Spring official docs : https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#orm-jpa-setup-jndi .No, the transaction manager is provided by Spring. More info on this: https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#transaction-strategies

Spring #Transactional(Propagation.NEVER) should create Hibernate session?

Let's assume that we have correctly configured JPA backed by Hibernate (version 4.3.11) in Spring (version 4.2.7). Hibernate first level cache is enabled. We use declarative transactions.
We have OuterBean:
#Service
public class OuterBean {
#Resource
private UserDao userDao;
#Resource
private InnerBean innerBean;
#Transactional(propagation = Propagation.NEVER)
public void withoutTransaction() {
User user = userDao.load(1l);
System.out.println(user.getName()); //return userName
innerBean.withTransaction();
user = userDao.load(1l);
System.out.println(user.getName()); //return userName instead of newUserName
}
}
And InnerBean that is called from OuterBean:
#Service
public class InnerBean {
#Resource
private UserDao userDao;
#Transactional
public void withTransaction() {
User user = userDao.load(1l);
user.setName("newUserName");
}
}
Is it correct behaviour that method user.getName() in OuterBean returns the same value twice (second time is after update name in database)?
In other words is it correct behaviour that #Transactional(propagation = Propagation.NEVER) creates Hibernate session for method withoutTransaction() that causes that second call user.getName() reads from Hibernate first level cache instead of database?
EDIT
To explain problem more I attache trace from creation of hibernate sessions
TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl - Opening Hibernate Session. tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl#c17285e
TRACE org.hibernate.internal.SessionImpl - Opened session at timestamp: 14689173439
TRACE org.hibernate.internal.SessionImpl - Setting flush mode to: AUTO
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
userName
TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl - Opening Hibernate Session. tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl#715c48ca
TRACE org.hibernate.internal.SessionImpl - Opened session at timestamp: 14689173439
TRACE org.hibernate.internal.SessionImpl - Setting flush mode to: AUTO
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl - Automatically flushing session
TRACE org.hibernate.internal.SessionImpl - before transaction completion
TRACE org.hibernate.internal.SessionImpl - after transaction completion
TRACE org.hibernate.internal.SessionImpl - Closing session
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
userName
TRACE org.hibernate.internal.SessionImpl - Closing session
Now let's compare trace when I remove #Transactional(propagation = Propagation.NEVER)
TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl - Opening Hibernate Session. tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl#4ebd2c5f
TRACE org.hibernate.internal.SessionImpl - Opened session at timestamp: 14689203905
TRACE org.hibernate.internal.SessionImpl - Setting flush mode to: AUTO
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl - Closing session
userName
TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl - Opening Hibernate Session. tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl#5af84083
TRACE org.hibernate.internal.SessionImpl - Opened session at timestamp: 14689203905
TRACE org.hibernate.internal.SessionImpl - Setting flush mode to: AUTO
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl - Automatically flushing session
TRACE org.hibernate.internal.SessionImpl - before transaction completion
TRACE org.hibernate.internal.SessionImpl - after transaction completion
TRACE org.hibernate.internal.SessionImpl - Closing session
TRACE org.hibernate.internal.SessionFactoryImpl$SessionBuilderImpl - Opening Hibernate Session. tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl#35f4f41f
TRACE org.hibernate.internal.SessionImpl - Opened session at timestamp: 14689203906
TRACE org.hibernate.internal.SessionImpl - Setting flush mode to: AUTO
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl - Setting cache mode to: NORMAL
TRACE org.hibernate.internal.SessionImpl - Closing session
newUserName
Please notice when I omit #Transactional(propagation = Propagation.NEVER) separate session is create for every invocation of method from userDao.
So my question can be formulated also as
Shouldn’t be #Transactional(propagation = Propagation.NEVER)
implemented in Spring as a guardian that prevents us from accidental use of
transaction, without any side effect (session creation)?
The behavior is correct - Hibernate will always create a session (how else would you expect it to perform any operation?), and by loading the entity you have associated it with that session. Since withoutTransaction is not participating in a transaction, the changes made within withTransaction will happen within a new transaction and shouldn't be visible unless you call refresh, which will force a re-load from the database.
I'm quoting Hibernate's official documentation:
The main function of the Session is to offer create, read and delete operations for instances of mapped entity classes. Instances may exist in one of three states:
transient: never persistent, not associated with any Session
persistent: associated with a unique Session detached: previously
persistent, not associated with any Session
Transient instances may be made persistent by calling save(), persist() or saveOrUpdate(). Persistent instances may be made transient by calling delete(). Any instance returned by a get() or load() method is persistent.
Taken from Java Persistence With Hibernate, Second Edition by Christian Bauer, Gavin King, and Gary Gregory:
The persistence context acts as a first-level cache; it remembers all entity instances you’ve handled in a particular unit of work. For example, if you ask Hibernate to load an entity instance using a primary key value (a lookup by identifier), Hibernate can first check the current unit of work in the persistence context. If Hibernate finds the entity instance in the persistence context, no database hit occurs—this is a repeatable read for an application. Consecutive em.find(Item.class, ITEM_ID) calls with the same persistence context will yield the same result.
Also from Java Persistence With Hibernate, Second Edition:
The persistence context cache is always on—it can’t be turned off. It ensures the following:
The persistence layer isn’t vulnerable to stack overflows in the case of circular references in an object graph.
There can never be conflicting representations of the same database row at the end of a unit of work. The provider can safely write all changes made to an entity instance to the database.
Likewise, changes made in a particular persistence context are always immediately visible to all other code executed inside that unit of work and its persistence context. JPA guarantees repeatable entity-instance reads.
Concerning transactions, here's an excerpt taken from official Hibernate's documentation:
Defines the contract for abstracting applications from the configured underlying means of transaction management. Allows the application to define units of work, while maintaining abstraction from the underlying transaction implementation (eg. JTA, JDBC).
So, to sum it up, withTransaction and withoutTransaction will not share UnitOfWork and therefore will not share the first-level cache, which is why the second load returns the original value.
As to the reasons why these two methods do not share the unit of work, you can refer to Shailendra's answer.
EDIT:
You seem to misunderstand something. A session must always be created - that's how Hibernate works, period. Your expectation of no sessions being created is equal to expecting to execute a JDBC query without having a JDBC connection :)
The difference between your two examples is that with #Transactional(propagation = Propagation.NEVER) your method is intercepted and proxied by Spring and only a single session is created for the queries in withoutTransaction. When you remove the annotation you exclude your method from Spring's transactional interceptor so a new session will be created for each DB-related operation. I repeat again, and I cannot stress this enough - you must have an open session to perform any queries.
As far as guarding goes - try swapping the annotations on the two methods by making withTransaction use Propagation.NEVER and withoutTransaction use the default #Transactional annotation and see what happens (spoiler: you'll get an IllegalTransactionStateException).
EDIT2:
As for why the session is shared between two loads in the outer bean - that's just what JpaTransactionManager is supposed to do, and by annotating your method with #Transactional you've notified Spring that it should use the configured transaction manager to wrap your method. Here's what the official documentation says about JpaTransactionManager's expected behavior:
PlatformTransactionManager implementation for a single JPA EntityManagerFactory. Binds a JPA EntityManager from the specified factory to the thread, potentially allowing for one thread-bound EntityManager per factory. SharedEntityManagerCreator and #PersistenceContext are aware of thread-bound entity managers and participate in such transactions automatically. Using either is required for JPA access code supporting this transaction management mechanism.
Also, to know how Spring is handling declarative transaction management (i.e. #Transactional annotations on methods), refer to the official documentation. For ease of navigation, I'll include a quote:
The most important concepts to grasp with regard to the Spring Framework’s declarative transaction support are that this support is enabled via AOP proxies, and that the transactional advice is driven by metadata (currently XML- or annotation-based). The combination of AOP with transactional metadata yields an AOP proxy that uses a TransactionInterceptor in conjunction with an appropriate PlatformTransactionManager implementation to drive transactions around method invocations.
First of all, as you use hibernate behind JPA API I will use the term EntityManager instead of session (strictly the same thing, just a matter of terminology).
Every access to the database using JPA will involve an EntityManager, you are fetching entities, you need an EntityManager (EM). What's called 1st level cache is nothing more than the EM managed entities state.
Theoretically the lifecycle of the EM is short and bound to a unit of work (and so generally to a transaction, see Struggling to understand EntityManager proper use).
Now JPA can be used in different way : Container-Managed or User-Managed persistence. When the EM is managed by the container (your case, here spring is the container) this last is in charge of managing the EM scope / lifecycle (create, flush and destroy it for you). As the EM is bounded to a transaction / Unit of Work, this task is delegated to the TransactionManager (the object handling the #Transactional annotations).
When you annotate a method using #Transactional(propagation = Propagation.NEVER), you are creating a spring logical transaction scope which will ensure that there is no existing underlying JDBC transaction bound to an eventual existing EM, which will not create one and will use JDBC autocommit mode but which will create an EM for this logical transaction scope if none already exists.
Regarding the fact that a new EM instance is created for each DAO call when no transaction logical scope is defined, you have to remember that you cannot access database using JPA outside of the EM. AFAIK hibernate used to throw a no session bound to thread error in this case but this may have evolved with later releases, otherwise your DAO may be annotated with #Transactional(propagation = Propagation.SUPPORT) which would also automatically create an EM if no enclosing logical scope exists. This is a bad practice as transaction should be defined at the unit of work, eg. the service level and not the DAO one.
#Transactional(propagation = Propagation.NEVER) would still create a session. If you are using Spring/Hibernate/JPA combination for non distributed transactions then you are most certainly using JpaTransactionManager as the Spring
transaction manager. The answer to your question lies in this class. A good idea would be do use a debugger in your IDE to follow what's happening. The doBegin method of this class ( which is called by Spring transaction infrastructure is :-
protected void doBegin(Object transaction, TransactionDefinition definition) {
JpaTransactionObject txObject = (JpaTransactionObject) transaction;
if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
throw new IllegalTransactionStateException(
"Pre-bound JDBC Connection found! JpaTransactionManager does not support " +
"running within DataSourceTransactionManager if told to manage the DataSource itself. " +
"It is recommended to use a single JpaTransactionManager for all transactions " +
"on a single DataSource, no matter whether JPA or JDBC access.");
}
try {
if (txObject.getEntityManagerHolder() == null ||
txObject.getEntityManagerHolder().isSynchronizedWithTransaction()) {
EntityManager newEm = createEntityManagerForTransaction();
if (logger.isDebugEnabled()) {
logger.debug("Opened new EntityManager [" + newEm + "] for JPA transaction");
}
txObject.setEntityManagerHolder(new EntityManagerHolder(newEm), true);
}
EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
// Delegate to JpaDialect for actual transaction begin.
final int timeoutToUse = determineTimeout(definition);
Object transactionData = getJpaDialect().beginTransaction(em,
new DelegatingTransactionDefinition(definition) {
#Override
public int getTimeout() {
return timeoutToUse;
}
});
txObject.setTransactionData(transactionData);
// Register transaction timeout.
if (timeoutToUse != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getEntityManagerHolder().setTimeoutInSeconds(timeoutToUse);
}
// Register the JPA EntityManager's JDBC Connection for the DataSource, if set.
if (getDataSource() != null) {
ConnectionHandle conHandle = getJpaDialect().getJdbcConnection(em, definition.isReadOnly());
if (conHandle != null) {
ConnectionHolder conHolder = new ConnectionHolder(conHandle);
if (timeoutToUse != TransactionDefinition.TIMEOUT_DEFAULT) {
conHolder.setTimeoutInSeconds(timeoutToUse);
}
if (logger.isDebugEnabled()) {
logger.debug("Exposing JPA transaction as JDBC transaction [" +
conHolder.getConnectionHandle() + "]");
}
TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
txObject.setConnectionHolder(conHolder);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Not exposing JPA transaction [" + em + "] as JDBC transaction because " +
"JpaDialect [" + getJpaDialect() + "] does not support JDBC Connection retrieval");
}
}
}
// Bind the entity manager holder to the thread.
if (txObject.isNewEntityManagerHolder()) {
TransactionSynchronizationManager.bindResource(
getEntityManagerFactory(), txObject.getEntityManagerHolder());
}
txObject.getEntityManagerHolder().setSynchronizedWithTransaction(true);
}
catch (TransactionException ex) {
closeEntityManagerAfterFailedBegin(txObject);
throw ex;
}
catch (Throwable ex) {
closeEntityManagerAfterFailedBegin(txObject);
throw new CannotCreateTransactionException("Could not open JPA EntityManager for transaction", ex);
}
}
The transactional resource when using JPA is actually the entity manager (underlying implementation is session in hibernate) as you can see and this is the first thing this method does
EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
So definitely an entity manager / session is created. The transaction attributes are then passed to the underlying JpaDialect (HibernateJpaDialect) via TransactionDefinition. This class in turn actually gets the underlying Hibernate Session and the transaction API of session.
HibernateJpaDialect {
........
public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
Session session = getSession(entityManager);
entityManager.getTransaction().begin();
......
......
}
......
I don't think this is correct behavior. It is true what the colleagues are saying that even without transaction the hibernate is creating a session. But this mean that we are facing two sessions S1 and S2 for the two separate reads from the DAO. At the same time L1 cache is always per session, so it does not make sense for two separate sessions to have a hit for the L1 cache. It seems like your Spring is not respecting the #Transactional(propagation = Propagation.NEVER)
The #Transactional(propagation = Propagation.NEVER) should be equivalent to if you just initialize your service from a main method and do the subsequent calls to the DAO yourself.
Try it in a main class and see how it will react. I doubt it will hit the L1 cache again.
Also I will copy paste the doc from Sprint on propagation NEVER:
NEVER Execute non-transactionally, throw an exception if a transaction
exists.
One more question - Is the hibernate configured to AutoCommit. Is it possible that the "runInTransaction" - method is not committing ?

No session currently bound to execution context

I got below exception when I used session.getCurrentSession().
I have mentioned
hibernate.current_session_context_class: managed
org.hibernate.HibernateException: No session currently bound to execution context
at org.hibernate.context.internal.ManagedSessionContext.currentSession(ManagedSessionContext.java:75)
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1014)
at io.dropwizard.hibernate.AbstractDAO.currentSession(AbstractDAO.java:36)
at io.dropwizard.hibernate.AbstractDAO.persist(AbstractDAO.java:149)
I use this with dropwizard. Can anyone help me to solve this?
If you are using Dropwizard Hibernate. You need to add #UnitOfWork annotation to your Resource method. More info within dropwizard manual, hibernate chapter.
Can you try with : session.openSession() - It tell hibernate to always opens a new session and you have to close once you are done with the operations. With session.getCurrentSession(), hibernate returns a session bound to a context that you don't need to close and only need to set the hibernate.current_session_context_class to thread.
You can also configure session with SpringSessionContext, if your application is Spring based.
Edit your hibernate-cfg.xml with below line:
hibernate.current_session_context_class=org.springframework.orm.hibernate3.SpringSessionContext
What above line will do?
Making session context class as "org.springframework.orm.hibernate3.SpringSessionContext
", Hibernate will assume it is executing inside of a Spring transactional context (i.e. through a Spring transactional aspect) and Spring will now manage your transaction for you. However if you call getCurrentSession() outside of such a context, Hibernate will throw an exception complaining that no Session is bound to the thread.

Categories