JTA transaction with multiple data source in Spring - java

I have to develop an application which uses two datasources in Spring. The two datasources should be managed by a transaction so that if there are some exceptions happens in one datasource, the other datasource should also roll back. My issume now is that my current implantation with jta does not work. When I tested my code, if the one datasource has error, the other datasource did not roll back. It just commited.
Here is my configuration file:
<bean id="parentDataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource"
abstract="true">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="root"/>
</bean>
<bean id="firstDataSource" parent="parentDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
</bean>
<bean id="secondDataSource" parent="parentDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/test2"/>
</bean>
<bean id="jtaTransactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
</bean>
<bean id="customerDAO" class="springapp.datasource.CustomerDAO">
<property name="dataSource">
<ref local="firstDataSource"/>
</property>
<property name="dataSource2">
<ref local="secondDataSource"/>
</property>
<property name="jtaTransactionManager">
<ref local="jtaTransactionManager"/>
</property>
</bean>
The transaction code in the customerDAO is:
public class CustomerDAO {
private DataSource dataSource;
private DataSource dataSource2;
private JtaTransactionManager jtaTransactionManager;
public DataSource getDataSource2() {
return dataSource2;
}
public void setDataSource2(DataSource dataSource2) {
this.dataSource2 = dataSource2;
}
public JtaTransactionManager getJtaTransactionManager() {
return jtaTransactionManager;
}
public void setJtaTransactionManager(JtaTransactionManager jtaTransactionManager) {
this.jtaTransactionManager = jtaTransactionManager;
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void insertItem(Item item){
TransactionTemplate tt = new TransactionTemplate(jtaTransactionManager);
tt.execute(new TransactionCallback<Object>(){
#Override
public Object doInTransaction(TransactionStatus arg0) {
JdbcTemplate jt = new JdbcTemplate(dataSource);
String sql = "insert into item(name,price) values ('aaaa',11);";
jt.setDataSource(dataSource);
jt.update(sql);
sql = "insert into item(name,price) values ('aaaa',12);";
jt.setDataSource(dataSource2);
jt.update(sql);
return null;
}
});
}
}
Can anyone tell me where is the problem?

You have to use XA Data Sources. JDBC Transactions only span a single datasource. To extend the transaction scope to multiple datasources XA Datasources must be used.

You can implement AbstractRoutingDataSource for handling multiple datasource and datasource connection will be based on your internal setting.

Related

Spring utilizing Mongo implementation over JPA

I am fairly new to the Spring Framework and have been having some trouble setting up the project I am currently working on. I need to be able to connect to two different databases one being MongoDB and the other being MSSQL. I am using JPA to connect to the MSSQL.
The problem that I am encountering is that it appears to be trying to make calls to the Mongo database when I want it to make calls to the MSSQL and I'm not really sure how to tell it what to read from. I have seen the posts advising to use the #Qualifier annotation to direct it to the correct implementation, but I don't think that that will work for my case.
#RestController
#RequestMapping("/software")
public class SoftwareEndpoint {
#Autowired
SoftwareRepository repo;
/**********************************************************************************
********************************MSSQL calls****************************************
***********************************************************************************/
#RequestMapping(value="/all",method=RequestMethod.GET,produces=MediaType.APPLICATION_JSON)
String getAllSoftware(){
System.out.println("Here1");
List<Software> allSoftware = (List<Software>) repo.findAll();
System.out.println("Here2");
//rest of method and class
Above shows a snippet of my controller class that has an instance of my SoftwareRepository. I also print to the out stream before and after the db call.
The out stream only shows "Here1", goes on to print out this line:
2016-10-04 07:35:39.810 INFO 4236 --- [nio-8080-exec-2] org.mongodb.driver.cluster : No server chosen by ReadPreferenceServerSelector{readPreference=primary} from cluster description ClusterDescription{type=UNKNOWN, connectionMode=SINGLE, all=[ServerDescription{address=localhost:27017, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused: connect}}]}. Waiting for 30000 ms before timing out
and then throws an exception on timeout.
I do not have a mongo instance running locally, however there will be on where the application is being deployed, but I don't believe that this is the problem because on hitting that endpoint, it shouldn't be making a call to the Mongo database, it should be trying to reach out to MSSQL.
TLDR: How do I specify which database implementation for Spring to use for a specific repository or database call?
You can connect to different databases in spring based on the configuration in context.
The below code is for connecting to MySql and Mongo DB. You can substitute MySql with MSSQL provided you have the JDBC for it. Check http://jdbforms.sourceforge.net/UsersGuide/html/ch20s02.html for what the properties for JDBC connection mean.
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="mySqldataSource" /> <!-- Change the datasource to MSSQL-->
</bean>
<bean id="mySqldataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="removeAbandoned">
<value>true</value>
</property>
<property name="removeAbandonedTimeout">
<value>30</value>
</property>
<property name="driverClassName">
<value>MSSQL_DRIVER_CLASS_NAME</value>
</property>
<property name="url">
<value>MSSQL_DATABASE_URL</value>
</property>
<property name="username">
<value>MSSQL_DB_USER_NAME</value>
</property>
<property name="password">
<value>MSSQL_DB_PASSWORD</value>
</property>
<property name="maxIdle">
<value>10</value>
</property>
<property name="maxActive">
<value>10</value>
</property>
<property name="maxWait">
<value>100000</value>
</property>
<property name="testOnBorrow">
<value>false</value>
</property>
<property name="testWhileIdle">
<value>false</value>
</property>
<property name="timeBetweenEvictionRunsMillis">
<value>60000</value>
</property>
<property name="minEvictableIdleTimeMillis">
<value>60000</value>
</property>
<property name="numTestsPerEvictionRun">
<value>1</value>
</property>
<property name="defaultTransactionIsolation" value="1" />
<property name="poolPreparedStatements" value="true" />
<property name="maxOpenPreparedStatements" value="1" />
</bean>
<bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"></bean>
Below is for connecting to mongodb
<mongo:db-factory dbname="mongoDbName" host="mongoServer" port="mongoPort"/>
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
</bean>
<mongo:repositories base-package="com.test.repoPackage"/> <!-- Package containing the mongo repository interfaces -->
Now you can use the repositories provided by spring.
EDIT 1: Suppose name of config is springConfig.properties. In the above example for the properties dbname, host and port in mongo:db-factory, you would want the values to be configured in springConfig.properties. So lets name them below:
mongoServer = xxx.xx.xxx.xxx
mongoPort = 27017
mongoDb = testDb
Now the context file needs to be modified to import the springConfig.properties. this is done as below in the context file:
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" >
<property name="locations" >
<list>
<value>classpath:/log4j.properties</value>
<value>classpath:/springConfig.properties</value>
</list>
</property>
</bean>
The bean mongo:db-factory would now look like:
<mongo:db-factory dbname="${mongoDb}" host="${mongoServer}" port="${mongoPort}"/>
Notice that the "keys" from config (dbname, host and port) are represented insde ${}. This will replace with values in config for the keys.
You need to have two separated config for JPA. Don't forget to disable JPA auto configuration.
#SpringBootApplication(
exclude={
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class
}
)
Below is example for two different sql database. Could be easily adapted for your needs (when second datasource is mongo).
First one:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "sellitEntityManagerFactory",
transactionManagerRef = "sellitTransactionManager",
basePackages = { "co.sellit.core.api.repository.sellit" }
)
public class JpaSellitConfig {
#Bean
#ConfigurationProperties(prefix="spring.datasource.sellit")
public DataSourceProperties sellitDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties(prefix="spring.hikaricp.sellit")
public HikariConfig sellitHikariConfig() {
HikariConfig hikariConfig = new HikariConfig();
return hikariConfig;
}
#Bean
public DataSource sellitDataSource(
#Qualifier("sellitHikariConfig") HikariConfig sellitHikariConfig,
#Qualifier("sellitDataSourceProperties") DataSourceProperties sellitDataSourceProperties) {
sellitHikariConfig.setDriverClassName(sellitDataSourceProperties.getDriverClassName());
sellitHikariConfig.setJdbcUrl(sellitDataSourceProperties.getUrl());
sellitHikariConfig.setUsername(sellitDataSourceProperties.getUsername());
sellitHikariConfig.setPassword(sellitDataSourceProperties.getPassword());
sellitHikariConfig.setConnectionTestQuery("SELECT 1");
HikariDataSource hikariDataSource = new HikariDataSource(sellitHikariConfig);
return hikariDataSource;
}
#Bean
#ConfigurationProperties(prefix="spring.jpa.sellit")
public JpaVendorAdapter sellitJpaVendorAdapter() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
return jpaVendorAdapter;
}
#Bean
#Autowired
public EntityManagerFactory sellitEntityManagerFactory(
#Qualifier("sellitDataSource") DataSource sellitDataSource,
#Qualifier("sellitJpaVendorAdapter") JpaVendorAdapter sellitJpaVendorAdapter) {
LocalContainerEntityManagerFactoryBean lemf = new LocalContainerEntityManagerFactoryBean();
lemf.setDataSource(sellitDataSource);
lemf.setJpaVendorAdapter(sellitJpaVendorAdapter);
lemf.setPackagesToScan("co.sellit.core.api.entity.sellit");
lemf.setPersistenceUnitName("sellitPersistenceUnit");
lemf.afterPropertiesSet();
return lemf.getObject();
}
#Bean
#Autowired
public EntityManager sellitDataSourceEntityManager(
#Qualifier("sellitEntityManagerFactory") EntityManagerFactory sellitEntityManagerFactory) {
return sellitEntityManagerFactory.createEntityManager();
}
#Bean
#Autowired
#Qualifier("sellitTransactionManager")
public PlatformTransactionManager sellitTransactionManager(
#Qualifier("sellitEntityManagerFactory") EntityManagerFactory sellitEntityManagerFactory) {
return new JpaTransactionManager(sellitEntityManagerFactory);
}
}
Second one:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "ofEntityManagerFactory",
transactionManagerRef = "ofTransactionManager",
basePackages = { "co.sellit.core.api.repository.openfire" }
)
public class JpaOpenfireConfig {
#Bean
#ConfigurationProperties(prefix="spring.datasource.openfire")
public DataSourceProperties ofDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties(prefix="spring.hikaricp.openfire")
public HikariConfig ofHikariConfig() {
HikariConfig hikariConfig = new HikariConfig();
return hikariConfig;
}
#Bean
public DataSource ofDataSource(
#Qualifier("ofHikariConfig") HikariConfig ofHikariConfig,
#Qualifier("ofDataSourceProperties") DataSourceProperties ofDataSourceProperties) {
ofHikariConfig.setDriverClassName(ofDataSourceProperties.getDriverClassName());
ofHikariConfig.setJdbcUrl(ofDataSourceProperties.getUrl());
ofHikariConfig.setUsername(ofDataSourceProperties.getUsername());
ofHikariConfig.setPassword(ofDataSourceProperties.getPassword());
ofHikariConfig.setConnectionTestQuery("SELECT 1");
HikariDataSource hikariDataSource = new HikariDataSource(ofHikariConfig);
return hikariDataSource;
}
#Bean
#ConfigurationProperties(prefix="spring.jpa.openfire")
public JpaVendorAdapter ofJpaVendorAdapter() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
return jpaVendorAdapter;
}
#Bean
#Autowired
public EntityManagerFactory ofEntityManagerFactory(
#Qualifier("ofDataSource") DataSource ofDataSource,
#Qualifier("ofJpaVendorAdapter") JpaVendorAdapter ofJpaVendorAdapter) {
LocalContainerEntityManagerFactoryBean lemf = new LocalContainerEntityManagerFactoryBean();
lemf.setDataSource(ofDataSource);
lemf.setJpaVendorAdapter(ofJpaVendorAdapter);
lemf.setPackagesToScan("co.sellit.core.api.entity.openfire");
lemf.setPersistenceUnitName("ofPersistenceUnit");
lemf.afterPropertiesSet();
return lemf.getObject();
}
#Bean
#Autowired
public EntityManager ofDataSourceEntityManager(
#Qualifier("ofEntityManagerFactory") EntityManagerFactory ofEntityManagerFactory) {
return ofEntityManagerFactory.createEntityManager();
}
#Bean
#Autowired
#Qualifier("ofTransactionManager")
public PlatformTransactionManager ofTransactionManager(
#Qualifier("ofEntityManagerFactory") EntityManagerFactory ofEntityManagerFactory) {
return new JpaTransactionManager(ofEntityManagerFactory);
}
}
So repositories from package co.sellit.core.api.repository.sellit will use sellit db
But repositories from package co.sellit.core.api.repository.openfire will use openfire database.
UPDATE (xml config version)
<bean id="openfireDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/openfire?characterEncoding=UTF-8" />
</bean>
<bean id="sellitDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/sellit?characterEncoding=UTF-8" />
</bean>
<bean id="openfireSessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="openfireDataSource" />
<property name="packagesToScan" value="co.sellit.core.api.repository.openfire" />
<property name="hibernateProperties">
<props>
...
</props>
</property>
</bean>
<bean id="sellitSessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="sellitDataSource" />
<property name="packagesToScan" value="co.sellit.core.api.repository.sellit" />
<property name="hibernateProperties">
<props>
...
</props>
</property>
</bean>
<bean id="openfireTxnManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="openfireSessionFactory" />
</bean>
<bean id="sellitTxnManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sellitSessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="openfireTxnManager" />
<tx:annotation-driven transaction-manager="sellitTxnManager" />

setup spring bean configuration for dbcp2 gives "PoolableConnectionFactory not linked to pool. Calling setPool()"

I need to convert dbcp2 java setup code into spring beans
The below code works as expected:
protected void setupDriver(String connectURI, String username, String password) throws ClassNotFoundException, SQLException{
//
// First, we'll create a ConnectionFactory that the
// pool will use to create Connections.
// We'll use the DriverManagerConnectionFactory,
// using the connect string passed in the command line
// arguments.
//
ConnectionFactory connectionFactory =
new DriverManagerConnectionFactory(connectURI, username, password);
//
// Next we'll create the PoolableConnectionFactory, which wraps
// the "real" Connections created by the ConnectionFactory with
// the classes that implement the pooling functionality.
//
poolableConnectionFactory =
new PoolableConnectionFactory(connectionFactory, null);
logger.info("poolableConnectionFactory created");
//
// Now we'll need a ObjectPool that serves as the
// actual pool of connections.
//
// We'll use a GenericObjectPool instance, although
// any ObjectPool implementation will suffice.
//
connectionPool =
new GenericObjectPool<PoolableConnection>(poolableConnectionFactory,getPoolConfig());
logger.info("connectionPool created");
// Set the factory's pool property to the owning pool
poolableConnectionFactory.setPool(connectionPool);
logger.info("connectionPool is set to poolableConnectionFactory");
//
// Finally, we create the PoolingDriver itself...
//
Class.forName("org.apache.commons.dbcp2.PoolingDriver");
driver = (PoolingDriver) DriverManager.getDriver("jdbc:apache:commons:dbcp:");
logger.info("dbcp2 driver is created");
//
// ...and register our pool with it.
//
driver.registerPool(poolName,connectionPool);
logger.info("driver is registered");
//
// Now, we create the PoolingDriver itself,
// passing in the object pool we created.
//
dataSource = new PoolingDataSource<PoolableConnection>(connectionPool);
logger.info("dataSource is created");
//
//Finally we create the JdbcTemplate for sql
//operations in DbDAO class
//
jdbcTemplate = new JdbcTemplate(dataSource);
logger.info("jdbcTemplate is setup");
logger.info("Finally dbcp2 driver setup is completed!");
}
//Pool initial setup values
private GenericObjectPoolConfig getPoolConfig(){
logger.info("Let's create the pool config values");
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(Integer.parseInt(config.getMaxtotal())); // set number of max connections i.e 25
poolConfig.setMaxWaitMillis(Long.parseLong(config.getMaxwaitmillis())); //ie. wait for a minute = 60000
poolConfig.setMaxIdle(Integer.parseInt(config.getMaxidle())); // set max number of idle connections
/*poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(true);*/
//poolConfig.setTestWhileIdle(true);
//poolConfig.setTimeBetweenEvictionRunsMillis(10000L);
//poolConfig.setNumTestsPerEvictionRun(5);
//poolConfig.setMinEvictableIdleTimeMillis(5000L);
return poolConfig;
}
And this is the beans.xml
<!-- ============ Trauma Database Connection Pooling Beans Settings ================== -->
<bean id="connectionFactory" class="org.apache.commons.dbcp2.DriverManagerConnectionFactory">
<constructor-arg index="0" value="${tir.jdbc.url}" />
<constructor-arg index="1" value="${tir.jdbc.username}" />
<constructor-arg index="2" value="${tir.jdbc.password}" />
</bean>
<!-- Connection Factory -->
<bean id="poolableConnectionFactory" class="org.apache.commons.dbcp2.PoolableConnectionFactory">
<constructor-arg index="0" ref="connectionFactory"/>
<constructor-arg index="1" > <null/> </constructor-arg>
</bean>
<!-- Pool Configs -->
<bean id="poolConfig" class="org.apache.commons.pool2.impl.GenericObjectPoolConfig">
<property name="maxTotal" value="${pool.maxtotal}"/>
<property name="maxIdle" value="${pool.maxidle}"/>
<property name="minIdle" value="${pool.minidle}"/>
<property name="maxWaitMillis" value="${pool.maxwaitmillis}"/>
</bean>
<!-- Connection Pool -->
<bean id="connectionPool" class="org.apache.commons.pool2.impl.GenericObjectPool">
<constructor-arg index="0" ref="poolableConnectionFactory"/>
<constructor-arg index="1" ref="poolConfig"/>
</bean>
<!-- Datasource gets connection pool -->
<bean id="dataSource" class="org.apache.commons.dbcp2.PoolingDataSource">
<constructor-arg ref="connectionPool"/>
</bean>
<!-- JdbcTemplate bean gets the datasource -->
<bean id="jdbcTemplateTIR" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>
<!-- Finally, we create our Database object bean -->
<bean id="dbdao" class="edu.uams.dao.impl.DBDAO">
<property name="jdbcTemplate" ref="jdbcTemplateTIR" />
</bean>
<!-- ============= END OF Trauma Database Connection Pooling Settings =========== -->
In both situations I can use the jDBCTemplate objects, however it gives the following warning:
"WARN 0[main] - org.apache.commons.dbcp2.PoolingDataSource.(PoolingDataSource.java:65) PoolableConnectionFactory not linked to pool. Calling setPool() to fix the configuration"
The reason is obvious: In my java code, I setup
poolableConnectionFactory.setPool(connectionPool);
How can I call setPool method from beans?
And how can I also setup this java code in my beans? The constructor of DriverManagerConnectionFactory does not get any constructor of DriverClassName?
// Finally, we create the PoolingDriver itself...
//
Class.forName("org.apache.commons.dbcp2.PoolingDriver");
driver = (PoolingDriver) DriverManager.getDriver("jdbc:apache:commons:dbcp:");
logger.info("dbcp2 driver is created");
//
// ...and register our pool with it.
//
driver.registerPool(poolName,connectionPool);
Wouldn't it be easier if you just used org.apache.commons.dbcp2.BasicDataSource? According to the documentation it provides a "one stop shopping" solution for basic connection pooling.
<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="${db.driver}"/>
<property name="url" value="${db.jdbcurl}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
<property name="initialSize" value="3"/>
</bean>
I was searching for a solution to exactly this problem given in the question with these specific versions of the libraries. I used the Spring configuration you gave and tweaked it a little bit, and I was able to get it working. Here is how I am using it...
<bean id="poolingDataSourceBean" class="org.apache.commons.dbcp2.PoolingDataSource">
<constructor-arg>
<bean id="genericObjectPoolBean" class="org.apache.commons.pool2.impl.GenericObjectPool">
<constructor-arg>
<bean id="poolableConnectionFactoryBean" class="org.apache.commons.dbcp2.PoolableConnectionFactory">
<constructor-arg index="0">
<bean id="dataSourceConnectionFactoryBean" class="org.apache.commons.dbcp2.DataSourceConnectionFactory">
<constructor-arg>
<bean id="dataSourceBean" class="net.sourceforge.jtds.jdbcx.JtdsDataSource">
<property name="serverName" value="${database.server}"/>
<property name="portNumber" value="${database.port}"/>
<property name="databaseName" value="${database.name}"/>
<property name="user" value="${database.user}"/>
<property name="password" value="${database.password}"/>
</bean>
</constructor-arg>
</bean>
</constructor-arg>
<constructor-arg index="1"><null/></constructor-arg>
</bean>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
You are getting PoolableConnectionFactory not linked to pool. Calling setPool() to fix the configuration" warning because PoolingDataSource has pooling defined while PoolableConnectionFactory does not. With spring you run into circular dependency: PoolableConnectionFactory needs GenericObjectPool that needs PoolableConnectionFactory in constructor.
One way to get around it is to use MethodInvokingFactoryBean (or MethodInvokingBean in spring 4.0+):
<bean id="connectionPoolSetter" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" >
<property name="targetObject" ref="poolableConnectionFactory" />
<property name="targetMethod" value="setPool"/>
<property name="arguments" ref="connectionPool" />
</bean>
then add depends-on attribute to dataSource to make sure connectionPoolSetter is initialized:
<bean id="dataSource" class="org.apache.commons.dbcp2.PoolingDataSource" depends-on="connectionPoolSetter">
<constructor-arg ref="connectionPool"/>
</bean>
Check Pooling Data Source example code:
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/doc/PoolingDataSourceExample.java?view=markup
By the way, I also have a DBPoolManager which I can manage the pool itself, even printing the stats.. even I haven't find any solution yet, I hope this code will be useful for you..
import java.sql.Connection;
import java.sql.SQLException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
public class DBPoolManager {
private static final Logger logger = LoggerFactory.getLogger(DBPoolManager.class);
private static DBPoolManager dbPoolManager = null;
private static final String POOLNAME = "dbpool";
private DBPoolDriver poolDriver = null;
private String url = null;
private String username = null;
private String password = null;
private static DriverStats stats = null;
protected DBPoolManager(){}
public void init(String url, String username, String password ){
this.url = url;
this.username = username;
this.password = password;
try{
this.poolDriver = new DBPoolDriver(POOLNAME);
}catch(Exception ex){
logger.error(ex.getMessage());
}
}
public static DBPoolManager getInstance(){
if(dbPoolManager == null){
synchronized(DBPoolManager.class){
if(dbPoolManager == null){
dbPoolManager = new DBPoolManager();
logger.info("Created a new singleton of DBPoolManager: "+dbPoolManager);
}
}
}
else{
logger.info("Created the same singleton of DBPoolManager: "+dbPoolManager);
}
return dbPoolManager;
}
public JdbcTemplate getJdbcTemplate(){
return poolDriver.getJdbcTemplate();
}
public Connection getConnection() throws Exception{
return poolDriver.getConnection();
}
public void setupPool() throws ClassNotFoundException, SQLException {
try{
poolDriver.setupDriver(url, username, password);
}
catch(Exception e){
logger.error(e.getMessage());
}
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getMaxTotal() {
return Integer.parseInt(poolDriver.getConfig().getMaxtotal());
}
public void setMaxTotal(int maxTotal) {
poolDriver.getConfig().setMaxtotal(String.valueOf(maxTotal));
}
protected long getMaxWaitMillis() {
return Long.parseLong(poolDriver.getConfig().getMaxwaitmillis());
}
public void setMaxWaitMillis(long maxWaitMillis) {
poolDriver.getConfig().setMaxwaitmillis(String.valueOf(maxWaitMillis));
}
public int getMaxIdle() {
return Integer.parseInt(poolDriver.getConfig().getMaxidle());
}
public void setMaxIdle(int maxIdle) {
poolDriver.getConfig().setMaxidle(String.valueOf(maxIdle));
}
public int getNumActive() throws SQLException{
return poolDriver.getConnectionPool().getNumActive();
}
public int getNumIdle() throws SQLException{
return poolDriver.getConnectionPool().getNumIdle();
}
public void shutDownPool() throws SQLException{
poolDriver.close();
}
public void printDriverStats() throws Exception {
logger.info(Thread.currentThread().getName()+ " NumActive: " + dbPoolManager.getNumActive());
logger.info(Thread.currentThread().getName()+ " NumIdle: " + dbPoolManager.getNumIdle());
}

When to create a new SessionFactory?

In my app I've been passing my SessionFatory in my method parameters when I need to access my database within those methods. It's instantiated in my Controller using:
#Autowired
private SessionFactory sessionFactory;
And when I need to access my database I use lines like this:
sessionFactory.getCurrentSession().save()
// or
List products = sessionFactory.getCurrentSession().createSQLQuery("SELECT * FROM PRODUCTS").list();
Should I be creating new sessionFactories to get my current session or should is it okay to pass it as a parameter?
edit [added for clarity]:
From HomeController.java:
#Controller
public class HomeController {
#Autowired
private SessionFactory sessionFactory;
//Correlations Insert Handler
//DOCUMENT CREATE
#RequestMapping(value="/documents", method = RequestMethod.PUT)
public #ResponseBody String insertDocument(HttpServletRequest request, HttpServletResponse response){
DocumentControl documentControl = new DocumentControl();
documentControl.insertDocument(request, response, sessionFactory);
return "went through just find";
}
//DOCUMENT READ
#RequestMapping(value="/documents", method = RequestMethod.GET)
public #ResponseBody List<Documents> getAllDocuments(){
//List documents = sessionFactory.getCurrentSession().createQuery("from Documents").list();
DocumentControl documentControl = new DocumentControl();
List documents = documentControl.getAllDocuments(sessionFactory);
return documents;
}
From DocumentControl.java:
public class DocumentControl {
private static Logger logger = Logger.getLogger(DocumentControl.class.getName());
public DocumentControl(){};
//CREATE
public void insertDocument(HttpServletRequest request, HttpServletResponse response, SessionFactory sessionFactory){
Documents document = new Documents(request)
sessionFactory.getCurrentSession().save(document);
}
//READ
public List<DocumentReturn> getAllDocuments(SessionFactory sessionFactory){
List documents = sessionFactory.getCurrentSession().createQuery("from Documents").list();
return documents;
}
private boolean ifProductExists (String productCode, SessionFactory sessionFactory) {
boolean exists = false;
List<Products> productList = sessionFactory.getCurrentSession().createCriteria(Products.class).add( Restrictions.eq("productCode", productCode)).list();
if ( !productList.isEmpty() && productList.size() > 0 ) {
exists = true;
}
return exists;
}
private boolean ifStandardExists (String standardCode, SessionFactory sessionFactory) {
boolean exists = false;
List<Standards> standardList = sessionFactory.getCurrentSession().createCriteria(Standards.class).add( Restrictions.eq("standardCode", standardCode)).list();
if ( !standardList.isEmpty() && standardList.size() > 0 ) {
exists = true;
}
return exists;
}
}
hibernate.cfg.xml:
hibernate-configuration>
<session-factory>
<property name="hibernate.c3p0.acquire_increment">1</property>
<property name="hibernate.c3p0.idle_test_period">100</property>
<property name="hibernate.c3p0.max_size">10</property>
<property name="hibernate.c3p0.max_statements">10</property>
<property name="hibernate.c3p0.min_size">10</property>
<property name="hibernate.c3p0.timeout">100</property>
<mapping class="***.*****.********.model.Documents"/>
</session-factory>
</hibernate-configuration>
persistence-context.xml:
<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}" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key=" hibernate.use_sql_comments">true</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<
property name="sessionFactory" ref="sessionFactory" />
</bean>
SessionFactory is a Singleton. Therefore, you should get it from the ApplicationContext (i.e. using the #Autowired annotation). Passing it as a parameter will only complicate your API.
I think that you should leave creation of SessionFactory to the framework you are using and inject it as a resource just as you are doing by now. Besides you are not creating it in your code at all. Good practice is to create separate session for every chunk of data manipulation, if it is not done by a framework ofc. Session should be your main unit of work.

Initialize database schema and add in test data

I am switching from XML to Java based Spring configuration. Following is my xml configuration to setup and initialize my H2 database.
<beans profile="test-h2">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="org.h2.Driver"/>
<property name="url" value="jdbc:h2:target/h2/pps;AUTO_SERVER=TRUE"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="entityManagerFactory" parent="entityManagerFactoryCommonParent">
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">create-drop</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
</beans>
Following is my java based configuration to setup my H2 database in Server Mode.
private static final String H2_JDBC_URL_TEMPLATE = "jdbc:h2:%s/db/merchant;AUTO_SERVER=TRUE";
private DataSource createH2DataSource() {
String jdbcUrl = String.format(H2_JDBC_URL_TEMPLATE, System.getProperty("user.home"));
JdbcDataSource ds = new JdbcDataSource();
ds.setURL(jdbcUrl);
ds.setUser("sa");
ds.setPassword("");
return ds;
}
How can I run scripts to initialize schema and add in some test data? Any ideas?
I have found a way to do it.
#Value("classpath:seed-data.sql")
private Resource H2_SCHEMA_SCRIPT;
#Value("classpath:test-data.sql")
private Resource H2_DATA_SCRIPT;
#Value("classpath:drop-data.sql")
private Resource H2_CLEANER_SCRIPT;
#Bean
public DataSource dataSource(Environment env) throws Exception {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
}
#Autowired
#Bean
public DataSourceInitializer dataSourceInitializer(final DataSource dataSource) {
final DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(databasePopulator());
return initializer;
}
private DatabasePopulator databasePopulator() {
final ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(H2_SCHEMA_SCRIPT);
populator.addScript(H2_DATA_SCRIPT);
return populator;
}

Java, Spring - Logging Bean Properties

Say I have the following bean definition:
<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="maxActive" value="50"/>
<property name="maxIdle" value="5"/>
Is there a way to get Spring to log all of the properties that it has set (without enabling verbose logging for Spring in log4j). I was thinking of something along the lines of... verbose="true"
<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource" verbose="true">
Update
I used the following, as suggested in the answer:
public class SpringBeanLoggingPostProcessor implements BeanPostProcessor {
private static final Logger log = Logger.getLogger(SpringBeanLoggingPostProcessor.class);
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean.getClass() == org.apache.commons.dbcp.BasicDataSource.class) {
BasicDataSource ds = (BasicDataSource) bean;
log.info("url="+ds.getUrl());
log.info("username="+ds.getUsername());
}
return bean;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String arg1) throws BeansException {
return bean;
}
}
No, there is no such a facility. You can create a dedicated bean post processor that logs target bean properties for that.

Categories

Resources