I'm working on a import module, where I'm reading from an excel file, fill the entities and save them.
Now my trouble is that I'm working with many entities and then i split the save on DB in three distinct methods.
This is my import method:
#Transactional(propagation = Propagation.REQUIRES_NEW)
private void readRowFromExcel(Row row){
for(Row row : sheet){
Customer customer = readCustomerFromExcel(row)
CustomerDeatails customerDetails = readCustomerDetailsFromExcel(row)
customerService.save(customer,customerDetails);
MyEntity myEntity = readMyEntityFromExcel(row);
myEntityService.save(myEntity)
}
}
This is the save method in the customer service
#Transactional(propagation = Propagation.REQUIRED)
public void save(Customer customer, CustomerDetails customerDetails){
customerRepository.save(customer);
customerDatailsRepository.save(customer);
}
This is the save method in the myEntity service:
#Transactional(propagation = Propagation.REQUIRED)
public void save(MyEntity myEntity){
myEntityRepository.save(myEntity)
}
Now my trouble is that if i get an exception on the following code line
MyEntity myEntity = readMyEntityFromExcel(row);
the entities customer and customerDetails are already persistent, while i should save only if everything works
My spring-context.xml
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${db.driverClass}" />
<property name="url" value="${db.connectionURL}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory">
<property name="persistenceUnitName" value="myUnit" />
</bean>
<!--
<bean id="hibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<description>Allow spring to configure hibernate specific settings</description>
</bean>
-->
<bean id="eclipseLinkJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:dataSource-ref="dataSource"
p:persistenceUnitName="myUnit"
p:jpaVendorAdapter-ref="eclipseLinkJpaVendorAdapter">
<property name="jpaPropertyMap">
<map>
<entry key="eclipselink.cache.shared.default" value="false" />
<entry key="eclipselink.weaving" value="false" />
</map>
</property>
<property name="loadTimeWeaver">
<bean
class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
How can i do to solve it?
The solution is simple: you move the lines that read from excel before any DB persisting is made:
#Transactional(propagation = Propagation.REQUIRES_NEW)
private void readRowFromExcel(Row row){
for(Row row : sheet){
//Reading from Excel
Customer customer = readCustomerFromExcel(row)
CustomerDeatails customerDetails = readCustomerDetailsFromExcel(row);
MyEntity myEntity = readMyEntityFromExcel(row);
//DB persisting
customerService.save(customer,customerDetails);
myEntityService.save(myEntity)
}
}
Related
Introduction
In order to extract some data from a database, I am trying to setup a basic hibernate and spring batch project. The goal is to provide one query (HQL) and based on this query the spring batch application extracts all the data to a flat file.
One of the requirements of the application is that the user should not have to configure the mappings of the columns. As such I am trying to create a DynamicRecordProcessor that evaluates the input and passes the input (a table for example Address) to the writer in such a way that the flat file item writer can use a PassThroughFieldExtractor.
Below the reader-processor-writer xml configuration:
<!-- Standard Spring Hibernate Reader -->
<bean id="hibernateItemReader" class="org.springframework.batch.item.database.HibernateCursorItemReader">
<property name="sessionFactory" ref="sessionFactory" />
<property name="queryString" value="from Address" />
</bean>
<!-- Custom Processor -->
<bean id="dynamicRecordProcessor" class="nl.sander.mieras.processor.DynamicRecordProcessor"/>
<!-- Standard Spring Writer -->
<bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter">
<property name="resource" value="file:target/extract/output.txt" />
<property name="lineAggregator">
<bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
<property name="delimiter" value="|"/>
<property name="fieldExtractor">
<bean class="org.springframework.batch.item.file.transform.PassThroughFieldExtractor"/>
</property>
</bean>
</property>
</bean>
EDIT:
And the job configuration:
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml"/>
<property name="cacheableMappingLocations" value="classpath*:META-INF/mappings/*.hbm.xml"/>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager" lazy-init="true">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
Problem
My processor looks as following:
public class DynamicRecordProcessor<Input,Output> implements ItemProcessor<Input,Output> {
private static final String DELIMITER = "|";
private boolean areNamesSetup = false;
private List<String> names = new ArrayList<String>();
private Input item;
#SuppressWarnings("unchecked")
#Override
public Output process(Input item) throws Exception {
this.item = item;
initMapping();
return (Output) extract();
}
private void initMapping() {
if (!areNamesSetup) {
mapColumns();
}
areNamesSetup = true;
}
private void mapColumns() {
Field[] allFields = item.getClass().getDeclaredFields();
for (Field field : allFields) {
if (!field.getType().equals(Set.class) && Modifier.isPrivate(field.getModifiers())) {
names.add(field.getName());
}
}
}
private Object extract() {
List<Object> values = new ArrayList<Object>();
BeanWrapper bw = new BeanWrapperImpl(item);
for (String propertyName : this.names) {
values.add(bw.getPropertyValue(propertyName));
}
return StringUtils.collectionToDelimitedString(values, DELIMITER);
}
}
The table Address has the following field:
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="city_id", nullable=false)
public City getCity() {
return this.city;
}
And the corresponding column in city:
#Column(name="city_id", unique=true, nullable=false)
public Short getCityId() {
return this.cityId;
}
When using values.add(bw.getPropertyValue(propertyName)); with propertyName being "city" the following exception occurs:
org.hibernate.SessionException: proxies cannot be fetched by a stateless session
at org.hibernate.internal.StatelessSessionImpl.immediateLoad(StatelessSessionImpl.java:292)
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:156)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:260)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:68)
at nl.sander.mieras.localhost.sakila.City_$$_jvstc2c_d.toString(City_$$_jvstc2c_d.java)
at java.lang.String.valueOf(String.java:2982)
at java.lang.StringBuilder.append(StringBuilder.java:131)
at org.springframework.util.StringUtils.collectionToDelimitedString(StringUtils.java:1132)
at org.springframework.util.StringUtils.collectionToDelimitedString(StringUtils.java:1148)
at nl.sander.mieras.processor.DynamicRecordProcessor.extract(DynamicRecordProcessor.java:52)
at nl.sander.mieras.processor.DynamicRecordProcessor.process(DynamicRecordProcessor.java:27)
The value, however, is availabe as shown in the screenshot below.
Concrete question: How can I get the value 300?
I have tried getting the value using reflection API, but I couldn't reach the actual value I want to get...
Reproduce
I have setup a public repo. However, you still need a local database to be able to reproduce exactly the issue. https://github.com/Weirdfishees/hibernate-batch-example. Any suggestions how isolate this issue further are more then welcome.
Just flip your reader to be state-full instead of stateless with the useStatelessSession property:
<!-- Standard Spring Hibernate Reader -->
<bean id="hibernateItemReader" class="org.springframework.batch.item.database.HibernateCursorItemReader">
<property name="sessionFactory" ref="sessionFactory" />
<property name="queryString" value="from Address" />
<property name="useStatelessSession" value="false" />
</bean>
I am new to Spring batch and I am trying to use Spring Batch with restartable feature. I am using MapJobRepositoryFactoryBean as Job repository. Everything looks fine. But when I run the same job multiple times I could see the execution time increasing considerably. I guess some memory leak is happening.If no job is running, I am cleaning the repository as well. But no luck. How do I know whats happening exactly. After running the same job for 4-5 times, the execution time is going to 2-3 times of the first execution.
<jpa:repositories base-package=".reference.data.repository"/>
<context:annotation-config />
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
<property name="url"
value="jdbc:sqlserver://${reference-data-manager.database.hostname}:
${reference-data-manager.database.port};
database=${reference-data-manager.database.name};
user=${reference-data-manager.database.username};
password=${reference-data-manager.database.password}" />
</bean>
<bean id="simpleJobConfiguration" class="reference.job.SimpleJobConfiguration">
</bean>
<bean id="emf"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="emf" />
</bean>
<bean id="importJob" class="org.springframework.batch.core.job.SimpleJob" scope="prototype">
<property name="jobRepository" ref="jobRepository"></property>
</bean>
<batch:step id="importCodesStep">
<batch:tasklet allow-start-if-complete="true">
<batch:chunk reader="codeMappingReader" writer="codeMappingWriter"
processor="codeMappingProcessor" commit-interval="${reference-data-manager.batch.size}"
skip-policy="reasonRemarkAssnSkipPolicy" skip-limit="${reference-data-manager.skip.limit}">
<batch:skippable-exception-classes>
<batch:include class="org.springframework.batch.item.ParseException"/>
</batch:skippable-exception-classes>
</batch:chunk>
</batch:tasklet>
<batch:listeners>
<batch:listener ref="reasonRemarkAssnStepListener"/>
<batch:listener ref="reasonRemarkAssnSkipListener"/>
</batch:listeners>
</batch:step>
<bean id="reasonRemarkAssnStepListener" class="reference.listeners.ReasonRemarkAssnStepListener">
</bean>
<bean id="reasonRemarkAssnSkipListener" class="reference.listeners.ReasonRemarkAssnSkipListener">
</bean>
<bean id="reasonRemarkAssnSkipPolicy" class="reference.listeners.ReasonRemarkAssnSkipPolicy">
<!-- <property name="skipLimit" value="5"/> -->
</bean>
<bean id="codeMappingWriter" class="reference.writer.ReasonRemarkAssnWriter" scope="step">
<property name="entityManagerFactory" ref="emf" />
</bean>
<bean id="codeMappingProcessor" class="reference.processors.ReasonRemarkAssnProcessor" scope="step">
<property name="userId" value="#{jobParameters['USER_ID']}" />
<property name="clientMnemonic" value="#{jobParameters['CLIENT_MENOMONIC']}" />
</bean>
<bean id="codeMappingReader" class="reference.readers.ReasonRemarkAssnReader" scope="step">
<property name="org" value="#{jobParameters['ORG']}"/>
<property name="resource" value="" />
<property name="linesToSkip" value="1" />
<property name="lineMapper">
<bean class="reference.mapper.ReasonRemarkAassnLineMapper">
<property name="lineTokenizer">
<bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="names" value="Reason Code,Remark Code,Category,Category Priority,Ignore Insight Processing,Active,Comment" />
</bean>
</property>
<property name="fieldSetMapper" ref="reasonRemarkAssnMapper"/>
</bean>
</property>
</bean>
<bean id="reasonRemarkAssnMapper" class="reference.mapper.ReasonRemarkAssnMapper">
<property name="codeGroups" value="${reference-data-manager.code.groups}"></property>
</bean>
<bean id="jobRepository"
class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
</bean>
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean class="CryptoPropertyPlaceholderConfigurer">
<property name="configUtil" ref="configUtil" />
<property name="location"
value="file:config/reference-data-manager.conf" />
<property name="ignoreResourceNotFound" value="true" />
<property name="ignoreUnresolvablePlaceholders" value="true" />
</bean>
<bean name="configUtil" class="CoreConfigUtil">
<property name="crypto" ref="crypto" />
</bean>
<bean id="referenceDataManager" class="reference.service.impl.ReferenceDataManagerImpl">
<property name="step" ref="importCodesStep"></property>
</bean>
Here is my job invocation...
#RestfulServiceAddress("/reference")
public class ReferenceDataManagerImpl implements ReferenceDataManager {
#Autowired
private JobLauncher jobLauncher;
#Autowired
IDataAccessService dataAccessService;
#Autowired
private IConfigurationServiceFactory configurationServiceFactory;
#Autowired
private SimpleJob job;
#Autowired
private TaskletStep step;
#Autowired
private SimpleJobConfiguration jobConfiguration;
#Autowired
private MapJobRepositoryFactoryBean jobRepository;
#Override
public Response importData(MultipartBody input, String org,
String userId, MessageContext mc) throws ServiceFault {
Response.ResponseBuilder builder = null;
ReasonRemarkAssnResponse responseObj = null;
boolean isSuccess = false;
UserInfo userInfo= dataAccessService.userInfoFindByUserId(userId);
JobParametersBuilder jobParametersBuilder = new JobParametersBuilder();
List<Attachment> attachments = input.getAllAttachments();
DataHandler dataHandler = attachments.get(0).getDataHandler();
byte[] bFile = null;
if(null != dataHandler){
try {
InputStream is = dataHandler.getInputStream();
bFile = new byte[is.available()];
is.read(bFile);
is.close();
} catch (IOException e) {
//TODO
}
}
SimpleJobConfiguration.customStorage.put(org, bFile);
jobParametersBuilder.addLong(ReferenceConstants.JOB_PARAMS_USERID, userInfo.getId());
jobParametersBuilder.addString(ReferenceConstants.JOB_PARAMS_ORG, org);
jobParametersBuilder.addString(ReferenceConstants.JOB_PARAMS_TIME, Calendar.getInstance().getTime().toString());
String response = "{error:Error occured while importing the file}";
job.setName(ReferenceConstants.JOB_PARAMS_JOB_NAME_PREFIX+org.toUpperCase());
job.addStep(step);
job.setRestartable(true);
JobExecution execution = null;
try {
execution = jobLauncher.run(job, jobParametersBuilder.toJobParameters());
isSuccess = true;
} catch (JobExecutionAlreadyRunningException e) {
//TODO
}catch (JobRestartException e) {
//TODO
}catch (JobInstanceAlreadyCompleteException e) {
//TODO
}catch (JobParametersInvalidException e) {
//TODO
}
response = prepareResponse(responseObj);
synchronized (SimpleJobConfiguration.customStorage){
if(null != SimpleJobConfiguration.customStorage.get(org)){
SimpleJobConfiguration.customStorage.remove(org);
}
if(SimpleJobConfiguration.customStorage.isEmpty()){
jobRepository.clear();
}
}
builder = Response.status(Response.Status.OK);
builder.type(MediaType.APPLICATION_JSON);
builder.entity(response);
return builder.build();
}
}
Don't use the MapJobRepositoryFactoryBean unless it's for testing. That's all it's intended for.
You shouldn't be building a new Job instance with every call to the controller. If you have stateful components within a step, declare them as step scoped so that you get a new instance per step execution.
The Map based JobRepository keeps everything in memory. That's the idea. So as you execute more and more jobs, the repository is going to grow...eating up your space in memory.
For launching a job in a controller, give something like this a try:
#RestController
public class JobLaunchingController {
#Autowired
private JobLauncher jobLauncher;
#Autowired
private Job job;
#RequestMapping(value = "/", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.ACCEPTED)
public void launch(#RequestParam("name") String name) throws Exception {
JobParameters jobParameters =
new JobParametersBuilder()
.addString("name", name)
.toJobParameters();
this.jobLauncher.run(job, jobParameters);
}
}
When use spring data and jdbctemplate in same function
spring data work fine but jdbc update not work .
this is service class.
jdbc update then not error occurred but database not updated
#Service
public class SampleService {
private static final Logger logger = LoggerFactory.getLogger(SampleService.class);
#Inject
private SampleRepository sampleRepository;
#Inject
private JpaTransactionManager transactionManager;
private JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(transactionManager.getDataSource());
}
#Transactional(propagation=Propagation.REQUIRED)
public void execute(){
sampleRepository.deleteAll();
sampleRepository.save(new Sample("sample"));
logger.info("data count (after jpa insert) : {}", sampleRepository.count());//1
int count = jdbcTemplate().update("insert into Sample (sample) values ( ? )", "sample");
logger.info("jdbc insert count: {}", count);//1
logger.info("data count (after jdbc insert) : {}", sampleRepository.count());//expected: 2 , actual:1
count = jdbcTemplate().queryForObject("select count(id) from Sample", Integer.class);
logger.info("data count (after jdbc insert) : {}", count); //jdbc count query result also return 1
}
when not use #Transactional annotation then work fine.
public void execute2(){
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("testTx");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = transactionManager.getTransaction(def);
... this method work fine ...
transactionManager.commit(status);
}
}
and this is data access configuration.
<context:annotation-config />
<tx:jta-transaction-manager />
<tx:annotation-driven transaction-manager="transactionManager" />
<jpa:repositories base-package="example" />
<context:property-placeholder location="classpath:config.properties" />
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="persistenceUnitName" value="DefaultUnit" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="${db.showsql}" />
<property name="generateDdl" value="${db.generateDDL}" />
<property name="database" value="${db.database}" /><!-- H2 -->
</bean>
</property>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
<constructor-arg ref="basicDataSource" />
</bean>
<bean id="basicDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${db.driver}" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.user}" />
<property name="password" value="${db.pass}" />
<property name="defaultAutoCommit" value="false" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
I have this situation:
class User
#Entity
#Configurable(preConstruction=true)
public class User extends AbstractBussinessObject implements Serializable {
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Warrior> warriors;
...
class UserDto
public class UserDto extends AbstractDto implements Serializable{
private List<WarriorDto> warriors;
private String name;
private String password;
public UserDto() {}
public UserDto(Long id, List<WarriorDto> warriors, String name, String password) {
this.warriors = warriors;
...
class Warrior
#Entity
public class Warrior extends AbstractBussinessObject implements Serializable{
#JoinColumn(name = "user_id")
#ManyToOne
private User user;
...
class WarriorDto
public class WarriorDto extends AbstractDto implements Serializable{
private User user;
...
method in WarriorServiceImpl
#Transactional(readOnly = true)
public List<WarriorDto> getAllWarriors() {
List<Warrior> wars = null;
List<WarriorDto> warsDto = new ArrayList<WarriorDto>();
try {
wars = genericDao.getAll(Warrior.class);
if (wars != null) {
for (Warrior war : wars) {
warsDto.add(createWarriorDto(war));
}
}
} catch (Exception e) {}
return warsDto;
}
method in DAO
#SuppressWarnings("unchecked")
#Override
public <ENTITY> List<ENTITY> getAll(Class<ENTITY> clazz) {
return getEntityManager().createQuery(
"SELECT e FROM " + clazz.getSimpleName() + " e")
.getResultList();
}
applicationContext.xml
<!-- Connection pool -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="initialSize" value="2" />
<property name="minIdle" value="2" />
</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="databasePlatform" value="${jpa.platform}" />
<property name="generateDdl" value="true" />
<property name="showSql" value="true" />
</bean>
</property>
<property name="packagesToScan" value="cz.sutak.game" />
</bean>
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<!-- Podpora deklarativni demarkace transakci - anotace #Transactional -->
<tx:annotation-driven transaction-manager="txManager" mode="aspectj" />
<bean id="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager">
<ref bean="txManager" />
</property>
</bean>
complete code
https://github.com/sutakjakub/game
And if I want call WarriorService.Util.getInstance().getAllWarriors(
new AsyncCallback<List<WarriorDto>>() in widgets it will failure with this message: com.google.gwt.user.client.rpc.StatusCodeException: 500 The call failed on the server; see server log for details.
In Jetty is only this message: [ERROR] 500 - POST /game/warrior (127.0.0.1) 57 bytes
No error message more.
Is problem in serialization (util.List)? Do u have some idea?
Thank you very much.
EDIT:
This is error message in terminal (sorry i didnt look that):
ERROR LazyInitializationException - failed to lazily initialize a collection of role: cz.sutak.game.client.bo.User.warriors, no session or session was closed
The problem is that on the client side Hibernate has no way of fetching the attributes you marked with fetch = FetchType.LAZY. You either need to fetch them eagerly on the server side og null the Hibernate proxies before passing the object graph over the wire.
See my answer in another thread for tips on nulling Hibernate proxies.
Cheers,
Here As you said there is nothing to do with GWT,because its server side code.Even on clien side GWT supports Util.List.
And coming to the original problem.Its with the line wars = genericDao.getAll(Warrior.class);
while accessing this line there is no hibernate session opened.
I have a default database and sometimes I have to make a select in another database.
I've searched many blogs and questions here about this, but couldn't make it work.
Tried the http://blog.springsource.org/2007/01/23/dynamic-datasource-routing/ way. Nothing.
Code for RouterDataSource class:
public class RouterDataSource extends AbstractRoutingDataSource {
#Override
protected DataSourceEnum determineCurrentLookupKey() {
return DataSourceContextHolder.getTargetDataSource();
}
}
Code for DataSourceContextHolder class:
public class DataSourceContextHolder {
private static final ThreadLocal<DataSourceEnum> contextHolder = new ThreadLocal<DataSourceEnum>();
public static void setTargetDataSource(DataSourceEnum targetDataSource) {
Assert.notNull(targetDataSource, "Target data source cannot be null");
contextHolder.set(targetDataSource);
}
public static DataSourceEnum getTargetDataSource() {
if (contextHolder.get() != null)
return (DataSourceEnum) contextHolder.get();
else
return DataSourceEnum.DB1;
}
public static void resetDefaultDataSource() {
contextHolder.remove();
}
}
Code for the method calling to change the database:
#Override
public CodeHD getCategoryByCode(String code) throws BusinessException {
DataSourceContextHolder.setTargetDataSource(DataSourceEnum.DATABASE2);
return (CodeHD) persistency.getObject(GETOBJECT_BY_CODE, code);
}
Code for DatasourceEnum class:
public enum DataSourceEnum {
DB1,
DB2;
}
And finally the configuration on my applicationContext.xml:
<bean id="parentDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" abstract="true">
<property name="driverClass" value="oracle.jdbc.pool.OracleDataSource" />
<property name="acquireIncrement" value="10" />
<property name="idleConnectionTestPeriod" value="60" />
<property name="maxStatements" value="50" />
<property name="minPoolSize" value="5" />
<property name="maxPoolSize" value="15" />
</bean>
<bean id="database1DS" parent="parentDataSource">
<property name="jdbcUrl" value="jdbc:oracle:thin:#database1:1521:xe" />
<property name="user" value="user" />
<property name="password" value="password" />
</bean>
<bean id="database2DS" parent="parentDataSource">
<property name="jdbcUrl" value="jdbc:oracle:thin:#database2:1521:xe" />
<property name="user" value="user" />
<property name="password" value="password" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="package.RouterDataSource">
<property name="defaultTargetDataSource" ref="database1DS"/>
<property name="targetDataSources">
<map key-type="package.DataSourceEnum">
<entry key="DB1" value-ref="database1DS"/>
<entry key="DB2" value-ref="database2DS"/>
</map>
</property>
</bean>
The problem is that when I set it to DB2 it won't change.
Can anyone help me?
Try to make both static method as non static and pass R reference if the context holder.
First make sure that database2DS is working correctly. Make the defaultTargetDatasource database2DS and verify that it is not using DB1 still and there are no other errors using database2DS as the default. If the AbstractRoutingDataSource fails to resolve a DataSource in the targetDataSources you cannot switch to it.
AbstractRoutingDataSource will only change the DataSource when getConnection is called. Whatever persistence framework you're using is probably caching the Connection and not calling getConnection in between persistency.getObject(). However you are getting your persistency object, try getting a new persistency object after you change the datasource in DataSourceContextHolder. If this solves your problem, try creating a class that maintains your persistency object and handles changing the datasource. That way when you change the datasource you can modify your persistency manager object in one spot.