I'm new to Spring's transaction management having troubles to tackle the following scenario of nested transactions while integrating Spring (3.2) and Hibernate (3.6).
I've declared two appContext files as following.
File1) applicationContext-student.xml
<bean id="studentProjSessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
...**dataSource1_on_Machine1**...
</bean>
<bean id="studentProjTransactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="studentProjSessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="studentProjTransactionManager" />
<bean id="studentDao" class="com.my.univ.employee.dao.studentHibDao" scope="singleton" />
<bean id="studentService" class="com.my.univ.student.service.studentServiceImpl" scope="singleton">
<property name="studentDao" ref="studentDao" />
</bean>
File2) applicationContext-employee.xml
<bean id="employeeProjSessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
........**dataSource2_on_Machine2**...
</bean>
<bean id="employeeProjTransactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="employeeProjSessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="employeeProjTransactionManager" />
<bean id="employeeDao" class="com.my.univ.employee.dao.EmployeeHibDao" scope="singleton" />
<bean id="employeeService" class="com.my.univ.employee.service.EmployeeServiceImpl" scope="singleton">
<property name="employeeDao" ref="employeeDao" />
</bean>
Imported above two files in the following file.
File3) applicationContext-university.xml
<import resource="applicationContext-student.xml" />
<import resource="applicationContext-employee.xml" />
<bean id="personService" class="com.my.univ.person.service.PersonServiceImpl" scope="singleton">
<property name="studentService" ref="studentService" />
<property name="employeeService" ref="employeeService" />
</bean>
Questions
Let's assume that method level #Transactional annotations are provided with the right txManager names in studentService and employeeService but not in personService.
Q1) If I declare a method in personService as #Transactional, which txManager gets picked?
Q2) How does the nested txManager scenario work if the txManagers in the hierarchy are different from each other?
Ex: If a #Transactional method in personService invokes a #Transactional method in studentService and then another #Transactional method in employeeService (with in the same method of personService).
Q3) How does the commit, rollback elements work in above scenario.
Q4) Readonly operations vs Read/Write operations in above scenario.
It'd be great if anybody could clarify the above.
Thanks.
The #Transactional annotation takes as a value the bean name of the transaction manager. From the documentation:
public abstract String value
A qualifier value for the specified transaction.
May be used to determine the target transaction manager, matching the qualifier value (or the bean name) of a specific PlatformTransactionManager bean definition.
Default:
""
So in your case, explicitly defining:
#Transactional("studentProjTransactionManager")
#Transactional("employeeProjTransactionManager")
should wrap transactionally using the right transaction manager.
Besides Spring 3.1 and ehcache-spring-annotations, do we have other alternatives that are other than EhCache and Spring?
Have you looked at Spring-AOP based solutions? You can create an auto-proxy object in your Spring applicationContext using the org.springframework.aop.support.RegexpMethodPointcutAdvisor and then make an object that implements AfterReturningAdvice, ThrowsAdvice and MethodBeforeAdvice.
Use that object to monitor function calls and exits and cache what information you want.
<bean name="cacheHandler" class="org.yourname.CachingInterceptor" />
<bean id="cacheAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="cacheHandler" />
<property name="pattern" value="org.yourname.regex.of.stuff.you.want.cached.*" />
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
I'm developing a web application using Struts2 + Spring, and now I'm trying to add a scheduled task. I'm using Spring's task scheduling to do so. In my applicationContext I have:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
...
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="MYSQL" />
</bean>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
And then I have my DAO that uses this entityManagerFactory:
<bean id="dao" class="data.GenericDAO" />
So this works flawlessly within the web application. But now I have a problem when creating the scheduled task:
<task:scheduled-tasks scheduler="notifier">
<task:scheduled ref="emailService" method="sendMail" fixed-rate="30000" />
</task:scheduled-tasks>
<task:scheduler id="notifier" pool-size="10" />
<bean id="emailService" class="services.emailService" >
<property name="dao" ref="dao" />
</bean>
This executes the method sendMail on my emailService class every 30 seconds. And my emailService has the DAO injected correctly. The thing is that I can fetch objects with my DAO using the findById named queries, but when I try to access any property mapped by Hibernate, such as related collections or entities, I get an "LazyInitializationException: could not initialize proxy - no Session ". I don't know what's wrong, since I believe the scheduled task is being managed by Spring, so it should have no problem using a Spring managed DAO. I must say that I'm using the openSessionInView filter on my struts actions, so maybe I need something similar for this scheduled task.
Any help or suggestion will be appreciated, thanks!
Edit: Finally I found a way to fix this. I changed my regular Dao with one where I can decide when to start and commit the transaction. So before doing anything I start a transaction and then everything works OK. So I still don't know exactly what causes the problem and if someday I'll be able to use my regular DAO, for the moment I'm staying with this solution.
OpenSessionInView won't help you, because you don't have a web context. You need Spring's Declarative Transaction Management.
In most cases, what you need to do is just this XML:
<!-- JPA, not hibernate -->
<bean id="myTxManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven transaction-manager="myTxManager" />
<!-- without backing interfaces you probably also need this: -->
<aop:config proxy-target-class="true">
(Annotate your EmailService class as #Transactional to enable this)
<bean id="cObject" scope="request" class="x.y.z.CClass"/>
<bean id="bObject" scope="request" class="x.y.z.BClass"/>
<bean id="aObject" scope="request" class="x.y.z.AClass">
<constructor-arg ref="bObject" />
<property name="cRef" ref="cObject" />
</bean>
aObject.cRef is not getting set for some reason. Note that constructor-arg and property are used in the same definition. I have not seen an example / post with similar feature.
On same sources my colleague discover:
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'service.MenuService#0'
defined in class path resource [spring-beans/integrator.xml]:
Could not resolve matching constructor (hint: specify index/type/name
arguments for simple parameters to avoid type ambiguities)
while my host, test and production servers have no such error.
With:
<bean class="service.MenuService">
<constructor-arg index="0" type="java.lang.String" value="#{user}"/>
<constructor-arg index="1" type="java.lang.String" value="#{password}"/>
<constructor-arg index="2" type="java.lang.String" value="#{uri}"/>
<property name="system" value="OPRT"/>
<property name="client" value="OPRT"/>
</bean>
while there are only one 3-args constructor in bean.
The reason to use constructor - it perform some additional actions on non-Spring library by invoking init() method. And set args as fields.
So I change spring-beans.xml to:
<bean class="service.MenuService" init-method="init">
<property name="login" value="#{user}"/>
<property name="password" value="#{password}"/>
<property name="httpsUrl" value="#{uri}"/>
<property name="system" value="OPRT" />
<property name="client" value="OPRT" />
</bean>
Take attention to init-method= part.
UPDATE After all I wrote simple XML config and step through Spring source code in debugger. Seems that with Spring 3.x it's possible to combine constructor-arg and property in XML bean definition (check doCreateBean in AbstractAutowireCapableBeanFactory.java, which call createBeanInstance and populateBean next).
See also https://softwareengineering.stackexchange.com/questions/149378/both-constructor-and-setter-injection-together-in-spring/
Mixing <constructor-arg> and <property> is generally a bad idea.
There is only one good reason for using <constructor-arg>, and that is to create immutable objects.
However, your objects are not immutable if you can set their properties. Don't use <constructor-arg>. Redesign the class, use an initializer method annotated with #PostConstruct if you need to apply some logic at bean creation time.
How to read the system environment variable in the application context?
I want something like :
<util:properties id="dbProperties"
location="classpath:config_DEV/db.properties" />
or
<util:properties id="dbProperties"
location="classpath:config_QA/db.properties" />
depending on the environement.
Can I have something like this in my application Context?
<util:properties id="dbProperties"
location="classpath:config_${systemProperties.env}/db.properties" />
where the actual val is set based on the SYSTEM ENVIRONMENT VARIABLE
I'm using Spring 3.0
You are close :o)
Spring 3.0 adds Spring Expression Language.
You can use
<util:properties id="dbProperties"
location="classpath:config_#{systemProperties['env']}/db.properties" />
Combined with java ... -Denv=QA should solve your problem.
Note also a comment by #yiling:
In order to access system environment variable, that is OS level
variables as amoe commented, we can simply use "systemEnvironment"
instead of "systemProperties" in that EL. Like
#{systemEnvironment['ENV_VARIABLE_NAME']}
Nowadays you can put
#Autowired
private Environment environment;
in your #Component, #Bean, etc., and then access the properties through the Environment class:
environment.getProperty("myProp");
For a single property in a #Bean
#Value("${my.another.property:123}") // value after ':' is the default
Integer property;
Another way are the handy #ConfigurationProperties beans:
#ConfigurationProperties(prefix="my.properties.prefix")
public class MyProperties {
// value from my.properties.prefix.myProperty will be bound to this variable
String myProperty;
// and this will even throw a startup exception if the property is not found
#javax.validation.constraints.NotNull
String myRequiredProperty;
//getters
}
#Component
public class MyOtherBean {
#Autowired
MyProperties myProperties;
}
Note: Just remember to restart eclipse after setting a new environment variable
Check this article. It gives you several ways to do this, via the PropertyPlaceholderConfigurer which supports external properties (via the systemPropertiesMode property).
Yes, you can do <property name="defaultLocale" value="#{ systemProperties['user.region']}"/> for instance.
The variable systemProperties is predefined, see 6.4.1 XML based configuration.
In your bean definition, make sure to include "searchSystemEnvironment" and set it to "true". And if you're using it to build a path to a file, specify it as a file:/// url.
So for example, if you have a config file located in
/testapp/config/my.app.config.properties
then set an environment variable like so:
MY_ENV_VAR_PATH=/testapp/config
and your app can load the file using a bean definition like this:
e.g.
<bean class="org.springframework.web.context.support.ServletContextPropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="searchSystemEnvironment" value="true" />
<property name="searchContextAttributes" value="true" />
<property name="contextOverride" value="true" />
<property name="ignoreResourceNotFound" value="true" />
<property name="locations">
<list>
<value>file:///${MY_ENV_VAR_PATH}/my.app.config.properties</value>
</list>
</property>
</bean>
Using Spring EL you can eis example write as follows
<bean id="myBean" class="path.to.my.BeanClass">
<!-- can be overridden with -Dtest.target.host=http://whatever.com -->
<constructor-arg value="#{systemProperties['test.target.host'] ?: 'http://localhost:18888'}"/>
</bean>
For my use case, I needed to access just the system properties, but provide default values in case they are undefined.
This is how you do it:
<bean id="propertyPlaceholderConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="searchSystemEnvironment" value="true" />
</bean>
<bean id="myBean" class="path.to.my.BeanClass">
<!-- can be overridden with -Dtest.target.host=http://whatever.com -->
<constructor-arg value="${test.target.host:http://localhost:18888}"/>
</bean>
Declare the property place holder as follows
<bean id="propertyPlaceholderConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="locations">
<list>
<value>file:///path.to.your.app.config.properties</value>
</list>
</property>
</bean>
Then lets say you want to read System.property("java.io.tmpdir") for your Tomcat bean or any bean then add following in your properties file:
tomcat.tmp.dir=${java.io.tmpdir}
This is how you do it:
<bean id="systemPrereqs" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" scope="prototype">
<property name="targetObject" value="#{#systemProperties}" />
<property name="targetMethod" value="putAll" />
<property name="arguments">
<util:properties>
<prop key="deployment.env">dev</prop>
</util:properties>
</property>
</bean>
But remember spring gets loaded first and then it will load this bean MethodInvokingFactoryBean. So if you are trying to use this for your test case then make sure that you use depends-on. For e.g. in this case
In case you are using it for your main class better to set this property using your pom.xml as
<systemProperty>
<name>deployment.env</name>
<value>dev</value>
</systemProperty>
You can mention your variable attributes in a property file and define environment specific property files like local.properties, production.propertied etc.
Now based on the environment, one of these property file can be read in one the listeners invoked at startup, like the ServletContextListener.
The property file will contain the the environment specific values for various keys.
Sample "local.propeties"
db.logsDataSource.url=jdbc:mysql://localhost:3306/logs
db.logsDataSource.username=root
db.logsDataSource.password=root
db.dataSource.url=jdbc:mysql://localhost:3306/main
db.dataSource.username=root
db.dataSource.password=root
Sample "production.properties"
db.logsDataSource.url=jdbc:mariadb://111.111.111.111:3306/logs
db.logsDataSource.username=admin
db.logsDataSource.password=xyzqer
db.dataSource.url=jdbc:mysql://111.111.111.111:3306/carsinfo
db.dataSource.username=admin
db.dataSource.password=safasf#mn
For using these properties file, you can make use of REsource as mentioned below
PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource("classpath:"+System.getenv("SERVER_TYPE")+"DB.properties");
configurer.setLocation(resource);
configurer.postProcessBeanFactory(beanFactory);
SERVER_TYPE can be defined as the environment variable with appropriate values for local and production environment.
With these changes the appplicationContext.xml will have the following changes
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="${db.dataSource.url}" />
<property name="username" value="${db.dataSource.username}" />
<property name="password" value="${db.dataSource.password}" />
Hope this helps .
Thanks to #Yiling. That was a hint.
<bean id="propertyConfigurer"
class="org.springframework.web.context.support.ServletContextPropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="searchSystemEnvironment" value="true" />
<property name="locations">
<list>
<value>file:#{systemEnvironment['FILE_PATH']}/first.properties</value>
<value>file:#{systemEnvironment['FILE_PATH']}/second.properties</value>
<value>file:#{systemEnvironment['FILE_PATH']}/third.properties</value>
</list>
</property>
</bean>
After this, you should have one environment variable named 'FILE_PATH'. Make sure you restart your terminal/IDE after creating that environment variable.
Updated version (2020).
Use System.getenv("ENV_VARIABLE")