I'm building a web application, which uses the Spring Framework 4.1.3 and using Jersey for the RESTful web service.
I want to connect to a Redis server and I'm using Spring Data Redis with the Jedis driver.
This is how my Beans.xml file looks like:
<!-- Jedis ConnectionFactory -->
<bean id="jedisConnFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="server" p:port="6379" p:use-pool="true"
/>
<!-- redis template definition -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnFactory"
/>
This is the Servlet with the access to the Redis server:
import javax.annotation.Resource;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
#Path("/")
public class RoutesServlet {
#Autowired
private RedisTemplate<String, String> template;
// inject the template as ListOperations
#Resource(name="redisTemplate")
private ListOperations<String, String> listOps;
#GET
#Produces("text/plain")
public String index() {
listOps.leftPush("user", "name");
...
At this point I'm getting a NullPointerException. I'm guessing the #Resource annotation is not working properly for some reason. Any ideas?
UPDATE:
This is my full Beans.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
xmlns:p="http://www.springframework.org/schema/p"
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd ">
<!-- Initialization for data source -->
<!--bean id="dataSource" class="db.mysql.DBConn"> </bean -->
<bean id="dataSource" class="org.apache.tomcat.jdbc.pool.DataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/insider" />
<property name="username" value="root" />
<property name="password" value="" />
<property name="jmxEnabled" value="true" />
<property name="testWhileIdle" value="false" />
<property name="testOnBorrow" value="true" />
<property name="validationQuery" value="SELECT 1" />
<property name="testOnReturn" value="false" />
<property name="validationInterval" value="30000" />
<property name="timeBetweenEvictionRunsMillis" value="30000" />
<property name="maxActive" value="100" />
<property name="initialSize" value="10" />
<property name="maxWait" value="10000" />
<property name="removeAbandonedTimeout" value="60" />
<property name="minEvictableIdleTimeMillis" value="30000" />
<property name="minIdle" value="10" />
<property name="logAbandoned" value="true" />
<property name="removeAbandoned" value="true" />
<property name="jdbcInterceptors"
value="org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer" />
</bean>
<!-- Definition for categoryJDBCTemplate bean -->
<bean id="categoryJDBCTemplate" class="db.mysql.CategoryJDBCTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- Definition for itemJDBCTemplate bean -->
<bean id="itemJDBCTemplate" class="db.mysql.ItemJDBCTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- Definition for userJDBCTemplate bean -->
<bean id="userJDBCTemplate" class="db.mysql.UserJDBCTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- Definition for reviewJDBCTemplate bean -->
<bean id="reviewJDBCTemplate" class="db.mysql.ReviewJDBCTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- Jedis ConnectionFactory -->
<bean id="jedisConnFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="server" p:port="6379" p:use-pool="true"
/>
<!-- redis template definition -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnFactory"
/>
</beans>
I believe it is simply so that the bean with the name redisTemplate is of type RedisTemplate. One way to retrieve the ListOperations is to do the following:
public class RedisExample {
// Just use the RedisTemplate - don't inject the ListOperations
private final RedisTemplate<String, String> redisTemplate;
// Use constructor injection (preferred over field injection)
#Autowired
public RedisExample(final RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void addLink(String userId, URL url) {
// Here is the trick:
// You can either retrieve the ListOperations this way
ListOperations<String, String> listOps = redisTemplate.opsForList();
listOps.leftPush(userId, url.toExternalForm());
// or, you can retrieve it this way
redisTemplate.boundListOps(userId).leftPush(url.toExternalForm());
}
}
The example shows that you should inject a ListOperations bean with the name redisTemplate. Since there is no such bean the injection fails. Simply remove the #Resource annotation (and the field) and use the code as described above.
Related
I have a small project that I’m using to learn Spring Batch. I want to read data from Oracle Database and write to an XML file, but I got an error:
Error creating bean with name 'step1': Cannot resolve reference to bean 'jobRepository' while setting bean property 'jobRepository'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jobRepository' defined in class path resource [Spring/batch/config/spring-batch-contextOriginal.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'dataSource' of bean class [org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean]: Bean property 'dataSource' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
I think I put everything but I think the most important things for this error are: spring-batch-context.xml and spring-datasource.xml. Am I missing something or is something wrong? Let me know if you need more details. Thank you. (I tried the example without using database and it has worked good.)
spring-batch-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<import resource="../jobs/jobPerson.xml"/>
<import resource="../config/spring-datasource.xml" />
<!-- <context:annotation-config />
<tx:annotation-driven transaction-manager="transactionManager"/>a PlatformTransactionManager is still required
-->
<!-- JobRepository and JobLauncher are configuration/setup classes -->
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean" >
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
<property name="databaseType" value="oracle" />
</bean>
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<!-- Optional ItemProcessor to perform business logic/filtering on the input records -->
<bean id="itemProcessor" class="springBatch.ExamResultItemProcessor" />
<!-- Optional JobExecutionListener to perform business logic before and after the job -->
<bean id="jobListener" class="springBatch.ExamResultJobListener" />
<!-- Step will need a transaction manager -->
<bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
</beans>
spring-datasource.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd">
<!-- Info to connect To Database -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:#*********:1552/******" />
<property name="username" value="*******" />
<property name="password" value="*******" />
</bean>
<bean id="transactionManager"
class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
<!-- create job-meta tables automatically -->
<jdbc:initialize-database data-source="dataSource">
<jdbc:script location="org/springframework/batch/core/schema-drop-oracle10g.sql" />
<jdbc:script location="org/springframework/batch/core/schema-oracle10g.sql" />
</jdbc:initialize-database>
</beans>
jobPerson.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:batch="http://www.springframework.org/schema/batch"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc">
<!-- <import resource="../config/spring-batch-contextOriginal.xml"/>
-->
<bean id="itemReader" class="org.springframework.batch.item.database.JdbcCursorItemReader" scope="step">
<property name="dataSource" ref="dataSource"/>
<property name="sql" value="SELECT internal_Id,individual_Id FROM Person" />
<property name="rowMapper">
<bean class="sb.dbToXml.PersonRowMapper"></bean>
</property>
</bean>
<bean id="itemWriter" class="org.springframework.batch.item.xml.StaxEventItemWriter">
<property name="resource" value="file:xml/persons.xml"/>
<property name="marshaller" ref="personMarshaller"/>
<property name="rootTagName" value="persons"/>
</bean>
<bean id="personMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<value>sb.dbToxml.Person </value>
</property>
</bean>
<batch:job id="personJob">
<batch:step id="step1" >
<batch:tasklet transaction-manager="transactionManager">
<batch:chunk reader="itemReader" writer="itemWriter" commit-interval="10" />
</batch:tasklet>
</batch:step>
<batch:listeners>
<batch:listener ref="jobListener" />
</batch:listeners>
</batch:job>
</beans>
Main class:
package sb.dbToxml;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainDbToXml
{
public static void main(String[] args)
{
ApplicationContext context=new ClassPathXmlApplicationContext("Spring/batch/config/spring-batch-context.xml");
JobLauncher jobLauncher= (JobLauncher) context.getBean("jobLauncher");
Job job=(Job) context.getBean("personJob");
try
{
JobExecution execution=jobLauncher.run(job, new JobParameters());
System.out.println("Main/try :Job Person Exit Status "+execution.getStatus());
}
catch (JobExecutionException e)
{
System.out.println("Main /catch :Job Person failed");
e.printStackTrace();
}
}
}
Person class:
package sb.dbToxml;
import javax.xml.bind.annotation.XmlAccessOrder;
import javax.xml.bind.annotation.XmlAccessorOrder;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name="Person")
//#XmlAccessorOrder(XmlAccessOrder.UNDEFINED)
public class Person
{
Long internal_id;
Long Individual_id;
#XmlElement(name="int_id")
public Long getInternal_id()
{
return internal_id;
}
public void setInternal_id(Long internal_id)
{
this.internal_id = internal_id;
}
#XmlElement(name="indv_id")
public Long getIndividual_id()
{
return Individual_id;
}
public void setIndividual_id(Long individual_id)
{
Individual_id = individual_id;
}
}
PersonRawMapper:
package sb.dbToxml;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
public class PersonRowMapper implements RowMapper <Person>
{
#Override
public Person mapRow(ResultSet rs, int rowNum) throws SQLException
{
Person person=new Person();
person.setIndividual_id(rs.getLong("individual_id"));
person.setInternal_id(rs.getLong("internal_id"));
return person;
}
}
The MapJobRepositoryFactoryBean does not have the properties dataSource, transactionManager and databaseType. You should use the JobRepositoryFactoryBean instead of the MapJobRepositoryFactoryBean:
So replace this:
<!-- JobRepository and JobLauncher are configuration/setup classes -->
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean" >
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
<property name="databaseType" value="oracle" />
</bean>
with this:
<!-- JobRepository and JobLauncher are configuration/setup classes -->
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean" >
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
<property name="databaseType" value="oracle" />
</bean>
and it should work.
I have the following convertor:
#Component
public class EventConverter implements MessageConverter {
#Override
public Object fromMessage(Message message) throws JMSException, MessageConversionException {
// ...DO SOMETHING HERE
}
#Override
public Message toMessage(Object eventObject, Session session) throws JMSException, MessageConversionException {
// ... DO ANOTHER THING HERE
}
My beans definition looks like this:
<!-- Message converter - to convert between out Event class and JMS message -->
<bean id="eventConverter"
class="com.shared.events.common.handlers.EventConverter" />
<bean id="redeliveryConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="${activemq_url}" />
<property name="redeliveryPolicy" ref="redeliveryPolicy" />
<property name="nonBlockingRedelivery" value="true" />
</bean>
<bean id="redeliveryCachingConnectionFactory"
class="org.springframework.jms.connection.CachingConnectionFactory"
p:targetConnectionFactory-ref="redeliveryConnectionFactory"
p:sessionCacheSize="10" />
<!-- Redelivery: retry after 3sec, 6sec,9sec,12sec,15sec finally put in
DLQ -->
<bean id="redeliveryPolicy" class="org.apache.activemq.RedeliveryPolicy">
<property name="queue" value="*" />
<property name="initialRedeliveryDelay" value="0" />
<property name="redeliveryDelay" value="3000" />
<property name="maximumRedeliveryDelay" value="3600000" />
<property name="maximumRedeliveries" value="5" />
<property name="useExponentialBackOff" value="true" />
<property name="backOffMultiplier" value="1" />
</bean>
<!-- A JmsTemplate instance that uses the cached connection and destination -->
<bean id="redeliveryJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="redeliveryCachingConnectionFactory" />
<property name="messageConverter" ref="eventConverter" />
<property name="sessionTransacted" value="true" />
</bean>
<!-- EDIT EDIT EDIT -->
<!-- The Spring message listener container configuration -->
<jms:listener-container container-type="default"
destination-type="queue" connection-factory="redeliveryConnectionFactory"
acknowledge="transacted" concurrency="5" cache="consumer"> <!-- remove prefetch on production -->
<jms:listener destination="accountStatsQueue" ref="accountStatsService"
method="onMessage" />
<!-- Listeners -->
<bean id="accountStatsService" class="com.service.AccountStatsService" />
<!-- Destinations -->
<bean id="accountStatsQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="AccountStatsQueue" />
the toMessage() function is being called straight from my EventConverter which is great.
The problem is that the fromMessage() is not being called from my EventConvertor.
(btw: org.springframework.jms.support.converter.SimpleMessageConverter does get called on fromMessage() , Its like my EventConverter is not overriding it in the specific function - fromMessage().)
Any ideas?
I migrating my spring batch processes from a command line application to a spring boot webapp including the spring-batch-admin-manager (version 1.3.0).
My old command-line app worked with two JPA databases and two TransactionManager instances. I remember it has been a hell of configuration to get that running. I expected things to get more comfortable, when starting with Spring Boot, but now things seem to be even worse:
Spring is unable to choose the right TransactionManager instance for transactions.
On application startup Spring is visiting one of my classes to execute a #PostConstruct annotated code block, from where a transactional method should be called.
#PostConstruct
public void init() {
eventType = eventTypeBo.findByLabel("Sport"); // <-- Calling transactional method
//...
}
From here the error is thrown. Have a look at the stacktrace:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type [org.springframework.transaction.PlatformTransactionManager] is defined:
expected single matching bean but found 2: transactionManager,osm.transactionManager
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:313)
at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:337)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:252)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy89.findByLabel(Unknown Source)
at com.company.model.service.impl.EventTypeBo.findByLabel(EventTypeBo.java:43)
at com.company.batch.article.utils.converter.SomeConverter.init(SomeConverter.java:83)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
//...
As you can see from the error log my TransactionManagers are named "transactionManager" and "osm.transactionManager". Transactions are configured accordingly:
<!-- DATABASE 1 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- DATABASE 2 -->
<bean id="osm.transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="osm.entityManagerFactory" />
</bean>
<tx:advice id="txAdvice" transaction-manager="osm.transactionManager">
<tx:attributes>
<tx:method name="*" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="osmServiceOperation"
expression="execution(* com.qompa.osm.service.spec..*Service.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="osmServiceOperation" />
</aop:config>
I am using two different GenericDao implementions to distinguish between PersistenceContexts when accessing data:
public class OsmGenericDao<T> implements IGenericDao<T> {
#PersistenceContext(unitName = "osm.entityManagerFactory")
protected EntityManager em;
//...
}
public class GenericDao<T> implements IGenericDao<T> {
#PersistenceContext(unitName = "entityManagerFactory")
protected EntityManager em;
//...
}
Everything seems to be fproperly configured: But still it fails!
EDIT: As far as I can follow Spring's TransactionAspectSupport tries to determine the transactionmanager via a qualifier (determineTransactionManager). Because the findByLabel's #Transactional annotation has no qualifier it tries to determine the correct bean with help of DefaultListableBeanFactory"s getBean method, where two beans of the same type are found that can't be further distinguished.
There must be a way to tell Spring to choose the transactionManager according to the persistence context!
Any ideas?
Here's my working configuration with 2 persistence contexts:
<!-- METADATA -->
<!-- Metadata connection pool -->
<bean id="scprMetadataDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass"
value="..." />
<property name="jdbcUrl"
value="..." />
<property name="user"
value="..." />
<property name="password"
value="..." />
<property name="maxPoolSize"
value="..." />
<property name="minPoolSize"
value="..." />
</bean>
<!-- Metadata entity manager factory -->
<bean id="scprMetadataEntityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="jpaVendorAdapter">
<bean
class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
<property name="database" value="H2" />
</bean>
</property>
</bean>
<!-- Metadata transaction manager -->
<bean id="scprMetadataTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="scprMetadataEntityManagerFactory" />
</bean>
<!-- DOMAIN -->
<!-- Domain connection pool -->
<bean id="scprDomainDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass"
value="..." />
<property name="jdbcUrl"
value="..." />
<property name="user"
value="..." />
<property name="password"
value="..." />
<property name="maxPoolSize"
value="..." />
<property name="minPoolSize"
value="..." />
</bean>
<!-- Domain entity manager factory -->
<bean id="scprDomainEntityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="jpaVendorAdapter">
<bean
class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
<property name="database" value="SQL_SERVER" />
</bean>
</property>
</bean>
<!-- Domain transaction manager -->
<bean id="scprDomainTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="scprDomainEntityManagerFactory" />
</bean>
Other config (missing in your question):
<persistence-unit name="scpr_metadata" transaction-type="RESOURCE_LOCAL">
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<class>...</class>
<class>...</class>
<class>...</class>
</persistence-unit>
<persistence-unit name="scpr_domain" transaction-type="RESOURCE_LOCAL">
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<class>...</class>
<class>...</class>
<class>...</class>
</persistence-unit>
And in my java class:
#Repository
public final class MetadataRepositoryImpl implements MetadataRepository {
#PersistenceContext(unitName = "scpr_metadata")
private EntityManager em;
And:
#Repository
public final class DomainRepositoryImpl implements DomainRepository {
#PersistenceContext(unitName = "scpr_domain")
private EntityManager em;
Hope this helps you.
I have a Maven project with a Hibernate JPA persistence layer that I would like to incorporate into some Spring applications (one a web application, and one a command line tool), but I am unsure how to configure this. The persistence module includes model classes, DAO classes, and the persistence.xml file. I have configured Spring's EntityManagerFactory to use the existing persistence.xml, but instantiating the DAO classes always results in a NullPointerException, as they seem to be unable to locate the persistence unit by name. Is it possible to use Hibernate JPA classes created outside Spring's context in a Spring application?
persistence.xml
<persistence-unit name="myapp" transaction-type="RESOURCE_LOCAL">
<properties>
<property name="hibernate.archive.autodetection" value="class, hbm" />
<property name="hibernate.hbm2ddl.auto" value="validate" />
<property name="hibernate.show_sql" value="false" />
</properties>
</persistence-unit>
application-context.xml
<bean id="allProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="singleton" value="true" />
<property name="ignoreResourceNotFound" value="true" />
<property name="locations">
<list>
<value>classpath:config/db.properties</value>
<value>classpath:config/log4j.properties</value>
</list>
</property>
</bean>
<context:component-scan base-package="com.company.app" />
<context:annotation-config />
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceXmlLocation" value="classpath:META-INF/persistence.xml" />
<property name="persistenceUnitName" value="myapp"/>
<property name="dataSource" ref="datasource" />
<property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
<property name="jpaDialect" ref="jpaDialect"/>
</bean>
<bean id="jpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="MYSQL" />
<property name="databasePlatform" value="${hibernate.dialect}" />
</bean>
<bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
<property name="dataSource" ref="datasource" />
<property name="jpaDialect" ref="jpaDialect" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="${hibernate.connection.url}" />
<property name="username" value="${hibernate.connection.username}" />
<property name="password" value="${hibernate.connection.password}" />
</bean>
UserDao.java
public class UserDao extends AbstractDao<User> {
public UserDao() {
super("myapp");
}
// CRUD methods
AbstractDao.java
public abstract class AbstractDao<T extends DataObject> {
private EntityManagerFactory emf;
private final static Logger log = LoggerFactory.getLogger(AbstractDao.class);
private final Map<String, Map<String, String>> connectionDictionaries =
new HashMap<String, Map<String, String>>();
public AbstractDao(String dataSourceName) {
try {
setUpDataSource(dataSourceName);
} catch (IOException e) {
log.error("Unable to load: ", AbstractDataConstants.DB_PROPERTIES_FILE);
e.printStackTrace();
}
}
...
// Other methods
I have a Spring application, i want to change the data source dynamically,ie. when input a DS URL, the Spring beans and all dependency will get updated automatically.I know this is somewhat strange, but anyway i want to achieve that.
My Spring configuration as following:
<bean id="majorDataSource" class="org.postgresql.ds.PGSimpleDataSource">
<property name="serverName" value="${jdbc.serverName}" />
<property name="portNumber" value="${jdbc.portNumber}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="databaseName" value="${jdbc.databaseName}" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="majorDataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="majorDataSource"/>
<property name="configLocation" value="classpath:sqlmap-config.xml"/>
</bean>
The questions are:
The JDBC URL is stored in properties, which could be changed runtime.
Once the URL is changed, i need to re-create the data source and maybe the dependent objects. I could not figure out how to do it elegantly in Spring?
I have known that Spring did could dynamically route data source based on one key, but the data source URL is predefined in Spring and will not change runtime. It is not my case.
You can use spring's AbstractRoutingDataSource by extending it and overriding the method determineCurrentLookupKey() that should return the key referencing the datasource's spring bean to be used.
Take a look at this blog article on spring source's blog which will show you an example of how to use that feature.
Basically to answer your questions, what you will need to do is to define the two datasources as different spring bean in your XML config. There is no need to create one dynamically, spring will load both, and use one or the other dynamically depending on your criteria in the determineCurrentLookupKey() method.
This would lead to something like:
XML config
<!-- first data source -->
<bean id="majorDataSource" class="org.postgresql.ds.PGSimpleDataSource">
<property name="serverName" value="${jdbc.major.serverName}" />
<property name="portNumber" value="${jdbc.major.portNumber}" />
<property name="user" value="${jdbc.major.username}" />
<property name="password" value="${jdbc.major.password}" />
<property name="databaseName" value="${jdbc.major.databaseName}" />
</bean>
<!-- second data source -->
<bean id="minorDataSource" class="org.postgresql.ds.PGSimpleDataSource">
<property name="serverName" value="${jdbc.minor.serverName}" />
<property name="portNumber" value="${jdbc.minor.portNumber}" />
<property name="user" value="${jdbc.minor.username}" />
<property name="password" value="${jdbc.minor.password}" />
<property name="databaseName" value="${jdbc.minor.databaseName}" />
</bean>
<!-- facade data source -->
<bean id="dataSource" class="blog.datasource.CustomerRoutingDataSource">
<property name="targetDataSources">
<map>
<entry key="MINOR" value-ref="minorDataSource"/>
<entry key="MAJOR" value-ref="majorDataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="majorDataSource"/>
</bean>
<!-- wiring up -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:sqlmap-config.xml"/>
</bean>
Java
public class MyRoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
// get the current url
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
if (request.getRequestURL().toString().endsWith("/minor"))
return "MINOR";
else
return "MAJOR";
}
}
I am not sure if this is the correct Way of doing this but what you can do is something like this.
<bean id="majorDataSource" class="org.postgresql.ds.PGSimpleDataSource">
<property name="serverName" value="dummydata" />
<property name="portNumber" value="dummydata" />
<property name="user" value="dummydata" />
<property name="password" value="dummydata" />
<property name="databaseName" value="dummydata" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="majorDataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
and in Java Class
public class TestTransaction {
#Autowired
private DataSourceTransactionManager manager;
private PlatformTransactionManager transactionManager;
public testExecution(DataSource ds) {
manager.setDataSource(ds);
transactionManager = manager;
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
jdbcTemplate.update();
transactionManager.commit(status);
} catch (Exception ex) {
transactionManager.rollback(status);
}
}
}
Please suggest if this Approach could Work as i am Still new to Spring