Why #Qualifier not work - spring-boot

I used spring boot + jdbctemplate and I have to use multi datasource, e.g.
#Configuration
public class MultiDBConfig {
#Bean(name = "fooDb")
#ConfigurationProperties(prefix = "foo.datasource")
public DataSource fooDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "fooJdbcTemplate")
public JdbcTemplate fooJdbcTemplate(#Qualifier("fooDb") DataSource ds) {
return new JdbcTemplate(ds);
}
#Bean(name = "barDb")
#ConfigurationProperties(prefix = "bar.datasource")
public DataSource barDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "barJdbcTemplate")
public JdbcTemplate barJdbcTemplate(#Qualifier("barDb") DataSource ds) {
return new JdbcTemplate(ds);
}
}
when start my application, it failed and have below error info
Parameter 0 of method fooJdbcTemplate in com.example.multidatasourcedemo.MultiDBConfig required a single bean, but 3 were found:
- fooDb: defined by method 'fooDataSource' in class path resource [com/example/multidatasourcedemo/MultiDBConfig.class]
- barDb: defined by method 'barDataSource' in class path resource [com/example/multidatasourcedemo/MultiDBConfig.class]
- testDb: defined by method 'testDataSource' in class path resource [com/example/multidatasourcedemo/MultiDBConfig.class]
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
But I obviously have used #Qualifier to identify the bean , e.g.
#Bean(name = "fooJdbcTemplate")
public JdbcTemplate fooJdbcTemplate(#Qualifier("fooDb") DataSource ds)
Why doesn't #Qualifier work here?

So I've done some debugging and found something which might explain what's happening. At this point I'm not sure if it's a bug (could be this one), but I have not been able to find any other documentation to clarify this either.
For reference this is spring-boot 1.5.4.
I started from the log, you can find below an excerpt, more specifically the line regarding DataSourceInitializer.init (below with ==> at the beginning):
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.sql.DataSource' available: expected single matching bean but found 3: fooDb,barDb,testDb
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1041) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:345) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1090) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
==> at org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer.init(DataSourceInitializer.java:77) ~[spring-boot-autoconfigure-1.5.4.RELEASE.jar:1.5.4.RELEASE]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_45]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_45]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_45]
at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_45]
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:366) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:311) ~[spring-beans-4.3.9.RELEASE
...
What happens is, when initialising the data sources, spring-boot tries to initialise the DB as well, feature which is enabled by default according to the docs:
Spring JDBC has a DataSource initializer feature. Spring Boot enables it by default and loads SQL from the standard locations schema.sql and data.sql (in the root of the classpath).
This takes place in the #PostConstruct section of org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer:
#PostConstruct
public void init() {
if (!this.properties.isInitialize()) {
logger.debug("Initialization disabled (not running DDL scripts)");
return;
}
if (this.applicationContext.getBeanNamesForType(DataSource.class, false, false).length > 0) {
==> this.dataSource = this.applicationContext.getBean(DataSource.class);
}
if (this.dataSource == null) {
logger.debug("No DataSource found so not initializing");
return;
}
runSchemaScripts();
}
As you can see, it tries to get the DataSource to execute the DB initialisation using the class this.dataSource = this.applicationContext.getBean(DataSource.class); and since there are 3 instances and no primary, it fails as per the expected behaviour of getBean(class)
<T> T getBean(Class<T> requiredType) throws BeansException
Return the bean instance that uniquely matches the given object type, if any.
This method goes into ListableBeanFactory by-type lookup territory but may also be translated into a conventional by-name lookup based on the name of the given type. For more extensive retrieval operations across sets of beans, use ListableBeanFactory and/or BeanFactoryUtils.
Parameters:
requiredType - type the bean must match; can be an interface or superclass. null is disallowed.
Returns:
an instance of the single bean matching the required type
Throws:
NoSuchBeanDefinitionException - if no bean of the given type was found
==> NoUniqueBeanDefinitionException - if more than one bean of the given type was found
BeansException - if the bean could not be created
So, bottom line, this happens before even trying to autowire your #Qualifier("fooDb") bean in the method, and I believe you have at lease these 2 choices, and in both cases your #Qualifier will be taken into account at the time when your JdbcTemplate is created:
if you need to execute some scripts to initialise your DB, then use #Primary to indicate which DataSource could be used for the task
otherwise, you can disable this implicit feature by adding spring.datasource.initialize=false in your application.properties (see here a list of common properties that can be configured)

This can be caused by a few different things. In my case, I had the following situation:
Two Datasource beans being configured in two Java classes, but both given specific Bean IDs
One place a Datasource was being injected, but correctly annotated with a Qualifier
A SpringBootApplication that was correctly excluding DataSourceAutoConfiguration
However, the bug turned out to be: a second class had been annotated as a SpringBootApplication and that was starting up...lost among the logs.
So, if everything else looks correct: check if some other, unexpected, SpringBootApplication is starting up.

Related

Transactions, Spring Boot Starter JDBC & R2DBC

I am trying to migrate a Spring Boot project, version 2.3.0.M3, that have used JDBC template to R2DBC. The project also uses Liquibase so I cannot get rid of JDBC altogether.
I have both the spring-boot-starter-data-r2dbc and the spring-boot-starter-jdbc dependencies in the project with which I get the following exception when trying to run one of my tests:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.transaction.TransactionManager' available: expected single matching bean but found 2: transactionManager,connectionFactoryTransactionManager
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1180)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:416)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:480)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:335)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
...
The bean connectionFactoryTransaction manager is defined like this in the Spring class R2dbcTransactionManagerAutoConfiguration:
#Bean
#ConditionalOnMissingBean(ReactiveTransactionManager.class)
public R2dbcTransactionManager connectionFactoryTransactionManager(ConnectionFactory connectionFactory) {
return new R2dbcTransactionManager(connectionFactory);
}
The bean transactionManager is defined like this in the Spring class DataSourceTransactionManagerAutoConfiguration:
#Bean
#ConditionalOnMissingBean(PlatformTransactionManager.class)
DataSourceTransactionManager transactionManager(DataSource dataSource,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
return transactionManager;
}
As can be seen, the #ConditionalOnMissingBean annotation contains different types which will cause an instance of both beans to be created.
However, in the Spring class TransactionAspectSupport there is this line of code in the determineTransactionManager method:
defaultTransactionManager = this.beanFactory.getBean(TransactionManager.class);
Since both of the transaction manager types, DataSourceTransactionManager and R2dbcTransactionManager, implement the TransactionManager interface, both the transaction manager beans as above will be matched and the error will occur.
I am now reaching out to hear if there is anyone who has managed to solve or work around this issue?
Thanks in advance!
With inspiration from M. Deinums answer (thanks!), I applied the following steps to my project and the test that failed earlier now runs successfully:
Remove the spring-boot-starter-jdbc dependency.
Add a dependency to spring-jdbc.
Add a dependency to HikariCP (com.zaxxer).
Add spring.liquibase user and password properties (I already had the url and change-log properties).
Remove all spring.datasource properties (I had url and drive-class-name).
I had the spring.r2dbc properties username, password and url defined which I did not need to change.
Update:
In addition, I used Testcontainers in the tests and could not assign a static port. In order to be able to configure the database port on Liquibase, I overrode a bean name liquibase of the type SpringLiquibase and created a DataSource (not exposed as a bean) in the liquibase bean creation method and set it on the liquibase bean.
It's possible to have spring-boot-starter-jdbc and spring-boot-starter-data-r2dbc co-exist. There is a class org.springframework.transaction.annotation.TransactionManagementConfigurer that can be used to resolve the conflict.
Spring Boot 2.3.0 seems to disable automatic datasource config when r2dbc is present. It's possible to manually import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration class to make both co-exist.
#Bean
TransactionManagementConfigurer transactionManagementConfigurer(ReactiveTransactionManager reactiveTransactionManager) {
return new TransactionManagementConfigurer() {
#Override
public TransactionManager annotationDrivenTransactionManager() {
return reactiveTransactionManager;
}
};
}

Spring Boot: How to make single externalize JDBC datasource configuration work in differnt DAOImpl classes

I have a requirement to fetch DB username and password from Vault. So I have removed the default implementation (spring.datasource.url,spring.datasource.username,spring.datasource.password)
and added the following code in DAOImpl class.
Code
#Autowired
private JdbcTemplate jdbcTemplate;
#Bean
#Primary
public DataSource dataSource()
{
return DataSourceBuilder.create().username("someusername").password("somepassword")
.url("someurl")
.driverClassName("oracle.jdbc.driver.OracleDriver").build();
}
It was working perfectly. But when I added a new DAOImpl class I got the following Exception. Is it necessay to add the above code snippet in all the DAOImpl
classes. Is there a way to configure dataSource in single class and use it in all the DAOImpl classes
Exception
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'dataSource': Requested bean is currently in creation: Is there an unresolvable circular reference?

BeanCurrentlyInCreationException when spring starts

I am getting the BeanCurrentlyInCreationException when I start my spring application.
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: com.datayes.bdb.rrp.business.service.impl.StockModelBaseService com.datayes.bdb.rrp.business.service.impl.StockModelV3ServiceImpl.stockModelBaseService; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'stockModelBaseService': Bean with name 'stockModelBaseService' has been injected into other beans [researchFrameworkCommonServiceImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:561)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331)
... 71 more
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'stockModelBaseService': Bean with name 'stockModelBaseService' has been injected into other beans [researchFrameworkCommonServiceImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:568)
And here are the code snippets I have found caused this issue:
#Component public class KafkaConsumerServer implements MessageListener<String, String> {
#Autowired StockModelV3Service stockModelV3Service;
#Autowired FinanceIndicatorService financeIndicatorService;
......
}
#Service public class FinanceIndicatorServiceImpl implements FinanceIndicatorService {
#Autowired StockModelV3Service stockModelV3Service;
#Autowired IndustryResearchFrameworkService industryResearchFrameworkService;
......
}
#Service public class IndustryResearchFrameworkServiceImpl implements IndustryResearchFrameworkService {
#Autowired ResearchFrameworkCommonService commonService;
......
}
#Service public class ResearchFrameworkCommonServiceImpl implements ResearchFrameworkCommonService {
#Autowired StockModelBaseService stockModelBaseService;
......
}
I found some thing interesting in the following article may have explained why.
https://blog.imaginea.com/spring-bean-creation-is-not-thread-safe/
As both my StockModelV3Service and FinanceIndicatorService depends on stockModelBaseService(FinanceIndicatorService -> industryResearchFrameworkService -> researchFrameworkCommonService -> stockModelBaseService), during spring bean creation, they have raced against each other. Which caused BeanCurrentlyInCreationException. As the above article noted, spring bean creation is not thread safe.
To solve this problem seems easy. I changed autowire order as below:
public class KafkaConsumerServer implements MessageListener<String, String> {
#Autowired FinanceIndicatorService financeIndicatorService;
#Autowired StockModelV3Service stockModelV3Service;
As FinanceIndicatorService also depends on StockModelV3Service, so when financeIndicatorService is loaded, it will wait till stockModelV3Service is loaded then load itself. Thus avoid BeanCurrentlyInCreationException.
After all, it has nothing to do with circular reference. As StockModelBaseService doesn't depends on other services.

Spring Boot voodoo required instantiating JPA with DataNucleus and Hikari

Any help getting this config to work would be welcome.
I am trying to take over the automatic connection pool, datasource and JPA configuration from Spring Boot to allow me to bring DataNucleus into the mix instead of Hibernate.
My approach is to code up the pieces Boot says are missing on a trial and error basis. I had to remove the Hibernate dependencies to allow DataNucleus to run.
Maybe I've now coded up too much or maybe not I'm not far enough.
Spring falls over with the error:
Exception encountered during context initialization - cancelling refresh attempt:
[huge SNIP]
nested exception is org.springframework.beans.BeanInstantiationException:
Failed to instantiate [org.springframework.data.repository.support.Repositories]:
Factory method 'repositories' threw exception;
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'symbolRepositoryImpl':
Unsatisfied dependency expressed through field 'entityManager';
nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type 'javax.persistence.EntityManager' available:
expected single matching bean but found 2:
org.springframework.orm.jpa.SharedEntityManagerCreator#0,
org.springframework.orm.jpa.SharedEntityManagerCreator#1
[SNIP]
2017-06-01 09:43:09.675 ERROR 9108 --- [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter
***************************
APPLICATION FAILED TO START
***************************
Description:
Field entityManager in com.bp.gis.tardis.repository.SymbolRepositoryImpl
required a single bean, but 2 were found:
- org.springframework.orm.jpa.SharedEntityManagerCreator#0: defined by method 'createSharedEntityManager' in null
- org.springframework.orm.jpa.SharedEntityManagerCreator#1: defined by method 'createSharedEntityManager' in null
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans,
or using #Qualifier to identify the bean that should be consumed
I could spend hours debugging this further but the breakpoint comes in the initialisation of one of the repositories which should have an entityManager injected.
This is what I'm manually instantiating:
#Configuration
#EnableJpaRepositories(
basePackages = {"org.adam.repository"}
)
public class DataSourceConfig {
#Bean
#ConfigurationProperties(prefix = "adam.datasource")
public AdamDataSourceProperties getDataSourceProperties() {
return new AdamDataSourceProperties();
}
#Bean
public DataSource getDataSource() {
AdamDataSourceProperties props = getDataSourceProperties();
return new HikariDataSource(props.getHikariConfig());
}
#Bean
public LocalContainerEntityManagerFactoryBean getEmfBean() {
LocalContainerEntityManagerFactoryBean emfBean =
new LocalContainerEntityManagerFactoryBean();
emfBean.setDataSource(getDataSource());
emfBean.setPersistenceUnitName("adam");
return emfBean;
}
#Bean
public EntityManagerFactory getEmf() {
LocalContainerEntityManagerFactoryBean emfBean = getEmfBean();
return emfBean.getNativeEntityManagerFactory();
}
}
My AdamDatasourceProperties is initialised by Spring using the "adam.datasource" prefixed values in application.properties, and it can then create a HikariConfig object to use to instantiate the HikariDataSource. That bit is actually fine, it's the entity manager factory that is probably causing issues - or something else.
I've got no evidence that my last method getEmf() is actually helping.
Also, I'm dubious that the error
Required a single bean, but 2 were found
or the suggested action are helpful - I don't fancy going into the Spring source code in order to annotate one of those methods on Spring's SharedEntityManagerCreator as #Primary.
UPDATE
DataNucleus won't run if it finds other JPA API classes on the classpath - it insists on its own version of the persistence API - hence removing the Hibernate packages was necessary.
Caused by: org.datanucleus.exceptions.NucleusUserException:
Found Meta-Data for class org.adam.entity.TimeSeriesEntity
but this class is either not enhanced or you have multiple copies
of the persistence API jar in your CLASSPATH!!
Make sure all persistable classes are enhanced before running
DataNucleus and/or the CLASSPATH is correct.
at org.datanucleus.metadata.MetaDataManagerImpl
.initialiseClassMetaData(MetaDataManagerImpl.java:2814)
so I have excluded Hibernate from spring-boot-starter-data-jpa and the error disappears.
I changed the LocalContainerEntityManagerFactoryBean method name to entityManagerFactory:
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean emfBean =
new LocalContainerEntityManagerFactoryBean();
emfBean.setDataSource(getDataSource());
emfBean.setPersistenceUnitName("adam");
return emfBean;
}
and to enable testing, I have to copy this #Configuration class and change the EMF method to accept Spring's test database:
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
#Qualifier("dataSource") DataSource dataSource) {
LocalContainerEntityManagerFactoryBean emfBean =
new LocalContainerEntityManagerFactoryBean();
emfBean.setDataSource(dataSource);
emfBean.setPersistenceUnitName("adam");
return emfBean;
}
That #Qualifier is for the sake of Intellij whose Spring facet complains about 2 candidates for injection here.
I also discovered that with this configuration, the repository/DTO dependency injection for the EntityManager doesn't work with #Autowired. It has to be the native-JPA annotation:
#PersistenceContext
private EntityManager entityManager;
With my previous Hibernate and OpenJPA configurations, Spring was happy to inject its own self-instantiated EntityManager in the presence of #Autowire.
This adds more fuel to my beef with Spring. It just too often doesn't do what it says on the tin. The Spring tests should find the #Configuration classes in the package hierarchy, but doesn't - I need to use #Import. Spring should also find dependency injection candidates based on type (EntityManager, DataSource etc) but it doesn't - in some cases they have to be produced by methods named a particular name or with #Bean annotations declaring a name.
Still, it's done.

Hibernate - Spring, SessionFactory nullPointerException (when calling getCurrentSession())

I try to save my PartageDomain in my database using hibernate session factory, the problem is that in Session session = sessionFactory.getCurrentSession(); a nullPointerException is thrown. My dataSource is well configured, and I can already save/persist other objects with exactly the same way in this project, so I don't khow where the problem comes from.
a snapshot from the console exception :
javax.faces.FacesException: /pages/indexx.xhtml #28,72 listener="#{userMB.saveUserRights}": java.lang.NullPointerException
at com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:85)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:97)
at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:114)
.....
javax.el.ELException: /pages/indexx.xhtml #28,72 listener="#{userMB.saveUserRights}": java.lang.NullPointerException
at com.sun.faces.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:108)
at com.sun.faces.facelets.tag.jsf.core.AjaxBehaviorListenerImpl.processAjaxBehavior(AjaxHandler.java:447)
at javax.faces.event.AjaxBehaviorEvent.processListener(AjaxBehaviorEvent.java:109)
at javax.faces.component.behavior.BehaviorBase.broadcast(BehaviorBase.java:98)
at javax.faces.component.UIComponentBase.broadcast(UIComponentBase.java:764)
at javax.faces.component.UIData.broadcast(UIData.java:911)
...
Caused by: java.lang.NullPointerException
at com.stage.dao.PartageDaoImpl.add(PartageDaoImpl.java:35)
at com.stage.beans.UserManagedBean.saveUserRights(UserManagedBean.java:224)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.sun.el.parser.AstValue.invoke(AstValue.java:234)
at com.sun.el.MethodExpressionImpl.invoke(MethodExpressionImpl.java:297)
at com.sun.faces.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:102)
... 23 more
here is part of code from the partageDomainImpl class :
#Repository
public class PartageDaoImpl implements PartageDao, Serializable {
#Resource(name = "sessionFactory")
private SessionFactory sessionFactory;
// sessionFactory getter and setter :)
public void add(PartageDomain partageDomain) { System.out.println(partageDomain.getPartageId().getUserDomain().getFirstName()); // I get this
Session session = sessionFactory.getCurrentSession();
// Save
try {
session.persist(partageDomain);
} catch (Exception e) {
session.saveOrUpdate(partageDomain);
}
}
Note that the exception is not caused by the EL langage, in fact I get the object correctly by printing it before calling the getsessionFactory method from which come the exception
in my PartageDomain class I have :
#Entity
public class PartageDomain implements Serializable {
// the PartageDomain properties, getters and setters ....
In fact I'm showing that to you to mention that I'm using annotation to manage dependencies and injections, concerning my session factory, I declared it in my configuration file as
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
p:dataSource-ref="dataSource"
p:configLocation="${hibernate.config}"
p:packagesToScan="com.stage"/>
I finally found the problem, in fact, it's my partageServiceImpl that has the sessionfactory property managed by spring-hibernate injection system.
In my userManagedBean, I was calling directly the the PartageDaoImpl.add method to add the partageDomain object, the correct thing is to call the PartageServiceImpl.add which has the sessionFactory property, in this whay the currentSession will not be null
I admit that it's a grave error that I made and It costs to me the whole day trying to solving it, so I hope this error will be faslty corrected by the others when seeying the exception that I have.
Just a guess -
It is possible that you have a conflict in your context files.
I think that your #Repository annotated bean is somehow being created in the servlet application context, while <tx:annotation-driven transaction-manager="transactionManager" /> is declared in the root web app context.
In other words the #Transactional works only in the context where tx:annotation-driven is declared.
I suppose that it's possible for some the beans to be autoscanned and created twice making them to exist both in the web application and servlet application contexts, if autoscanning is present in both context configurations.
So the solution may be to check that all beans are created once and in the right places - DAOs in the root context, Controllers in the servlet context etc.
update
Also please note that this code uses catch em all exception handling antipattern and possibly accesses hibernate session after it has raised an exception which is not supported by Hibernate as I know.

Resources