Specify Oracle Schema SpringBoot 2.x/Apache Camel - Multiple Datasource - java

I am having a hard time changing the oracle datasource schema for my springboot app, that will eventually be used by my camel routes. I am logging in as user readonly, but all the data is in schema mydata. Readonly has read rights to the mydata schema.
I have tried calling ALTER SESSION SET CURRENT_SCHEMA=mydata against the datasource (by autowiring, and then getting the connection object from the datasource) and it doesn't work, I have no issue running selects from statement objects I create off the connection (see code below)
If I create a rest endpoint that executes ALTER SESSION SET CURRENT_SCHEMA=mydata and if I call that from postman or a browser, that will change my schema and my other endpoints will work, but I would prefer not to do it that way since I will have to call that endpoint. I guess I could call that endpoint in my springboot app when it loads but it just seems like the wrong way to do it.
I also do not want to hardcode/prefix all my tables with the schema name since different regions have different schema names, I'd like to configure the schema name in the properties file.
Here is my application.properties, I have tried various ways to set the schema in the properties file based on other stack overflow posts, and so far none of them work.
spring.datasource.first.url=jdbc:oracle:thin:#myserver:10100:db9
spring.datasource.first.username=readonly
spring.datasource.first.password=readonlypass
## DOESNT WORK ->spring.datasource.hikari.schema=mydata
## DOESNT WORK ->spring.datasource.hikari.first.schema=mydata
#sync database
spring.datasource.second.driverClassName=oracle.jdbc.OracleDriver
spring.datasource.second.url = jdbc:oracle:thin:myserver2:10100:db15
spring.datasource.second.username = eam
spring.datasource.second.password = eampass
Here is the code from my springboot application:
/**
* A spring-boot application that includes a Camel route builder to setup the Camel routes
*/
#SpringBootApplication
#ImportResource({"classpath:spring/camel-context.xml"})
public class Application extends RouteBuilder {
int workorderSyncFrequency = 5000;
//Autowired the first datasource in attempts to alter the session to set my schema name.
#Autowired
DataSource firstDataSource;
// must have a main method spring-boot can run
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
//setup first datasource
#Bean
#Primary
#ConfigurationProperties("spring.datasource.first")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
#ConfigurationProperties("spring.datasource.first.configuration")
public DataSource firstDataSource() {
return firstDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
//setup second data source
#Bean
#ConfigurationProperties("spring.datasource.second")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("spring.datasource.second.configuration")
public DataSource secondDataSource() {
return firstDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
#Override
public void configure() throws Exception {
Connection con = DataSourceUtils.getConnection(firstDataSource);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("select count(*) from mydata.ASSET");
rs.next();
//simply testing I am using the correct datasource and I can query from the second schema and this works.
System.out.println("++++++++++++++++++++++ASSET COUNT+++++++++++++++++++"+rs.getInt(1));
//Tried both of these statements, neither works.
//stmt.executeQuery("ALTER SESSION SET CURRENT_SCHEMA=mydata");
//stmt.executeUpdate("ALTER SESSION SET CURRENT_SCHEMA=mydata");
//Connection is defaulted to autocommit tried this just in case.
con.commit();
//ASSET table doesnt exist on the readonly schema, only on the mydata schema
//if I call test3 I will get a table or view does not exist, unless I first call the "schema"
//endpoint below.
rest()
.get("test3")
.produces(MediaType.APPLICATION_JSON_VALUE)
.route()
.to("sql:SELECT * FROM ASSET where rownum < 10"
+ "?dataSource=firstDataSource&outputType=SelectList");
//This works if I call this route, but its a weird way to make this work.
rest()
.get("schema")
.produces(MediaType.APPLICATION_JSON_VALUE)
.route()
.to("sql:ALTER SESSION SET CURRENT_SCHEMA=mydata"
+ "?dataSource=firstDataSource&outputType=SelectList");
}

Related

Cant resolve symbol SimpleNativeJdbcExtractor [duplicate]

My Project is on spring-boot-starter-parent - "1.5.9.RELEASE" and I'm migrating it to spring-boot-starter-parent - "2.3.1.RELEASE".
This is multi-tenant env application, where one database will have multiple schemas, and based on the tenant-id, execution switches between schemas.
I had achieved this schema switching using SimpleNativeJdbcExtractor but in the latest Springboot version NativeJdbcExtractor is no longer available.
Code snippet for the existing implementation:
#Bean
#Scope(
value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public JdbcTemplate jdbcTemplate() {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
SimpleNativeJdbcExtractor simpleNativeJdbcExtractor = new SimpleNativeJdbcExtractor() {
#Override
public Connection getNativeConnection(Connection con) throws SQLException {
LOGGER.debug("Set schema for getNativeConnection "+Utilities.getTenantId());
con.setSchema(Utilities.getTenantId());
return super.getNativeConnection(con);
}
#Override
public Connection getNativeConnectionFromStatement(Statement stmt) throws SQLException {
LOGGER.debug("Set schema for getNativeConnectionFromStatement "+Utilities.getTenantId());
Connection nativeConnectionFromStatement = super.getNativeConnectionFromStatement(stmt);
nativeConnectionFromStatement.setSchema(Utilities.getTenantId());
return nativeConnectionFromStatement;
}
};
simpleNativeJdbcExtractor.setNativeConnectionNecessaryForNativeStatements(true);
simpleNativeJdbcExtractor.setNativeConnectionNecessaryForNativePreparedStatements(true);
jdbcTemplate.setNativeJdbcExtractor(simpleNativeJdbcExtractor);
return jdbcTemplate;
}
Here Utilities.getTenantId() ( Stored value in ThreadLocal) would give the schema name based on the REST request.
Questions:
What are the alternates to NativeJdbcExtractor so that schema can be dynamically changed for JdbcTemplate?
Is there any other way, where while creating the JdbcTemplate bean I can set the schema based on the request.
Any help, code snippet, or guidance to solve this issue is deeply appreciated.
Thanks.
When I was running the application in debug mode I saw Spring was selecting Hikari Datasource.
I had to intercept getConnection call and update schema.
So I did something like below,
Created a Custom class which extends HikariDataSource
public class CustomHikariDataSource extends HikariDataSource {
#Override
public Connection getConnection() throws SQLException {
Connection connection = super.getConnection();
connection.setSchema(Utilities.getTenantId());
return connection;
}
}
Then in the config class, I created bean for my CustomHikariDataSource class.
#Bean
public DataSource customDataSource(DataSourceProperties properties) {
final CustomHikariDataSource dataSource = (CustomHikariDataSource) properties
.initializeDataSourceBuilder().type(CustomHikariDataSource.class).build();
if (properties.getName() != null) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
Which will be used by the JdbcTemplate bean.
#Bean
#Scope(
value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public JdbcTemplate jdbcTemplate() throws SQLException {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
return jdbcTemplate;
}
With this approach, I will have DataSource bean created only once and for every JdbcTemplate access, the proper schema will be updated during runtime.
There's no need to get rid of JdbcTemplate. NativeJdbcExtractor was removed in Spring Framework 5 as it isn't needed with JDBC 4.
You should replace your usage of NativeJdbcExtractor with calls to connection.unwrap(Class). The method is inherited by Connection from JDBC's Wrapper.
You may also want to consider using AbstractRoutingDataSource which is designed to route connection requests to different underlying data sources based on a lookup key.

Spring Boot app with Hibernate multi-tenant support

I have started converting an existing Spring Boot(1.5.4.RELEASE) application to support multi-tenant capabilities. So i am using MySQL as the database and Spring Data JPA as the data access mechanism. i am using the schema based multi-tenant approach. As Hibernate document suggests below
https://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch16.html
I have implemented MultiTenantConnectionProvider and CurrentTenantIdentifierResolver interfaces and I am using ThreadLocal variable to maintain the current tenant for the incoming request.
public class TenantContext {
final public static String DEFAULT_TENANT = "master";
private static ThreadLocal<Tenant> tenantConfig = new ThreadLocal<Tenant>() {
#Override
protected Tenant initialValue() {
Tenant tenant = new Tenant();
tenant.setSchemaName(DEFAULT_TENANT);
return tenant;
}
};
public static Tenant getTenant() {
return tenantConfig.get();
}
public static void setTenant(Tenant tenant) {
tenantConfig.set(tenant);
}
public static String getTenantSchema() {
return tenantConfig.get().getSchemaName();
}
public static void clear() {
tenantConfig.remove();
}
}
Then i have implemented a filter and there i set the tenant dynamically looking at a request header as below
String targetTenantName = request.getHeader(TENANT_HTTP_HEADER);
Tenant tenant = new Tenant();
tenant.setSchemaName(targetTenantName);
TenantContext.setTenant(tenant);
This works fine and now my application points to different schema based on the request header value.
However there is a master schema where i store the some global settings and i need to access that schema while in a middle of a request for a tenant. Therefore i tried to hard code the Threadlocal variable just before that database call in the code as below.
Tenant tenant = new Tenant();
tenant.setSchemaName("master");
TenantContext.setTenant(tenant);
However this does not point to the master schema and instead it tries to access the original schema set during the filter. What is the reason for this?
As per my understanding Hibernate invokes openSession() during the first database call to a tenant and after i try to invoke another database call for "master" it still use the previous tenant as CurrentTenantIdentifierResolver invokes only during the openSession(). However these different database calls does not invoke within a transaction.
Can you please help me to understand the issue with my approach and any suggestions to fix the issue
Thanks
Keth
#JonathanJohx actually i am trying to override the TenantContext set by the filter in one of the controllers. First i am loging in a tenant where TenantContext is set to that particular tenant. While the request is in that tenant i am requesting data from master. In order to do that i am simply hard code the tenant as below
#RestController
#RequestMapping("/jobTemplates")
public class JobTemplateController {
#Autowired
JobTemplateService jobTemplateService;
#GetMapping
public JobTemplateList list(Pageable pageable){
Tenant tenant = new Tenant();
tenant.setSchemaName(multitenantMasterDb);
TenantContext.setTenant(tenant);
return jobTemplateService.list(pageable);
}

AbstractRoutingDataSource change map in runtime

I have now 2 tables in database:
User
user_database
In user I store login, password,role
In user_database i store database driver,url,password and user.
Diagram database
I want user login to my page and next connection what he done will be sent to user database. Why i need what? I planing map popular e commerce and create android application where user login and see store data, can add and view product orders.
Now time to go practice, my knowledge in spring technology is small please explain me something when I doing wrong.
All examples on web for AbstractRoutingDataSource have declaration datasource in persistence file or create datasource bean and start using AbstractRoutingDataSource.
In my project I don't now user connection and i need get this from database. I was try get using repository and this example
https://stackoverflow.com/a/17575648/3037869
but i getting null on #Autowired in controller, i think connection for repository is null. How to set connection for this repository and set Route? Defect this method is when i add user i need restart server to refresh connection.
Next try what i using now is class User implement UserDetails after user login i can get user connection from getPrincipal() and add to map.
private void setDataSources() {
HashMap<Object, Object> targetDataSources = new HashMap<>();
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("org.h2.Driver");
dataSourceBuilder.url("jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
dataSourceBuilder.username("sa");
dataSourceBuilder.password("");
targetDataSources.put("auth", dataSourceBuilder.build());
setDefaultTargetDataSource(dataSourceBuilder.build());
if( SecurityContextHolder.getContext().getAuthentication()!=null) {
User user=(User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
System.out.println(user.getUserDatabase().getDriver());
dataSourceBuilder.driverClassName(user.getUserDatabase().getDriver());
dataSourceBuilder.url(user.getUserDatabase().getUrl());
dataSourceBuilder.username("3450_Menadzer");
dataSourceBuilder.password(user.getUserDatabase().getPassword());
targetDataSources.put("user", dataSourceBuilder.build());
}
setTargetDataSources(targetDataSources);
afterPropertiesSet(); //map is refresh when i add this
}
I run this method on constuctor and determineCurrentLookupKey
protected Object determineCurrentLookupKey() {
if( SecurityContextHolder.getContext().getAuthentication()!=null) {
setDataSources();
return "user";
}
return "auth";
}
This is working but when i refresh 3-4 times request for user database i getting
User 3450_Menadzer already has more than 'max_user_connections' active connections
Setting connection map manualy and not refreshing every method determineCurrentLookupKey run i don't have this problem. I think my method is not clossing connection. How i can clean this? This is possible to better method to route connection?
EDIT
#SergeBallesta i change some code from your examples
This is my class for map
#Component
#Scope(value = "singleton")
public class DataSourceMap {
private Map<Object,Object> dataSourceMap;
public DataSourceMap()
{
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("org.h2.Driver");
dataSourceBuilder.url("jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
dataSourceBuilder.username("sa");
dataSourceBuilder.password("");
dataSourceMap=new HashMap<Object,Object>();
dataSourceMap.put("auth",dataSourceBuilder.build());
}
public void addDataSource(String session,DataSource dataSource)
{
this.dataSourceMap.put(session,dataSource);
}
public Map<Object,Object> getDataSourceMap()
{
return dataSourceMap;
}
public void removeSource(String session)
{
dataSourceMap.remove(session);
}
}
For AbstractRoutingDataSource i done some changes, i was add afterPropertiesSet() beacuse datasource not refresh. I done some refresh and i not getting error i think this is working. I need test this for more databases in future
#Component
public class CustomRoutingDataSource extends AbstractRoutingDataSource{
#Autowired
DataSourceMap dataSources;
#Override
protected Object determineCurrentLookupKey() {
setDataSources(dataSources);
afterPropertiesSet();
System.out.println("test");
if( SecurityContextHolder.getContext().getAuthentication()!=null) {
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
return request.getSession().getId();
}
return "auth";
}
#Autowired
public void setDataSources(DataSourceMap dataSources) {
System.out.println("data adding");
setTargetDataSources(dataSources.getDataSourceMap());
}
}
First, per user database is a very uncommon design. If all those databases will end with same structure, please do not do that in a real world application, but just add user_id in your tables and queries.
Next, I found another (not full) example of a dynamic AbstractRoutingDataSource in another answer of mine.
And one big difference between my code (beware never tested) and your question is that I use a SessionListener to close the databases to avoid that the number of open database grows indefinitively.
If you to this to learn Spring, you could try the following pattern (bottom-up description) :
a session scoped bean that would hold the actual database connection for a user, the connection should be created on first request (to be sure that user id is present in session) and cached for subsequent uses. A destroy method (automaticaly called by Spring when session is closed) should close the connection.
an AbstractRoutingDataSource, that would be injected with a proxy to above holder, and that would ask actual datasource to the holder
As in the other answer, if same user is likely to have many simultaneous sessions, you could have a singleton been injected in session holders that would keep the actual database connections along with the number of active sessions. That way you would get one single connection per user, no matter how many concurrent sessions he could have.

How can I get a spring JdbcTemplate to read_uncommitted?

Firstly, I can't use the declarative #Transactional approach as the application has multiple JDBC data-sources, I don't want to bore with the details, but suffice it to say the DAO method is passed the correct data-source to perform the logic. All JDBC data sources have the same schema, they're separated as I'm exposing rest services for an ERP system.
Due to this legacy system there are a lot of long lived locked records which I do not have control over, so I want dirty reads.
Using JDBC I would perform the following:
private Customer getCustomer(DataSource ds, String id) {
Customer c = null;
PreparedStatement stmt = null;
Connection con = null;
try {
con = ds.getConnection();
con.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
stmt = con.prepareStatement(SELECT_CUSTOMER);
stmt.setString(1, id);
ResultSet res = stmt.executeQuery();
c = buildCustomer(res);
} catch (SQLException ex) {
// log errors
} finally {
// Close resources
}
return c;
}
Okay, lots' of boiler-plate, I know. So I've tried out JdbcTemplate since I'm using spring.
Use JdbcTemplate
private Customer getCustomer(JdbcTemplate t, String id) {
return t.queryForObject(SELECT_CUSTOMER, new CustomerRowMapper(), id);
}
Much nicer, but it's still using default transaction isolation. I need to somehow change this. So I thought about using a TransactionTemplate.
private Customer getCustomer(final TransactionTemplate tt,
final JdbcTemplate t,
final String id) {
return tt.execute(new TransactionCallback<Customer>() {
#Override
public Customer doInTransaction(TransactionStatus ts) {
return t.queryForObject(SELECT_CUSTOMER, new CustomerRowMapper(), id);
}
});
}
But how do I set the transaction isolation here? I can't find it anywhere on the callback or the TransactionTemplate to do this.
I'm reading Spring in Action, Third Edition which explains as far as I've done, though the chapter on transactions continues on to using declarative transactions with annotations, but as mentioned I can't use this as my DAO needs to determine at runtime which data-source to used based on provided arguments, in my case a country code.
Any help would be greatly appreciated.
I've currently solved this by using the DataSourceTransactionManager directly, though it seems like I'm not saving as much boiler-plate as I first hoped. Don't get me wrong, it's cleaner, though I still can't help but feel there must be a simpler way. I don't need a transaction for the read, I just want to set the isolation.
private Customer getCustomer(final DataSourceTransactionManager txMan,
final JdbcTemplate t,
final String id) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
TransactionStatus status = txMan.getTransaction(def);
Customer c = null;
try {
c = t.queryForObject(SELECT_CUSTOMER, new CustomerRowMapper(), id);
} catch (Exception ex) {
txMan.rollback(status);
throw ex;
}
txMan.commit(status);
return c;
}
I'm still going to keep this one unanswered for a while as I truly believe there must be a better way.
Refer to Spring 3.1.x Documentation - Chapter 11 - Transaction Management
Using the TransactionTemplate helps you here, you need to configure it appropriately. The transaction template also contains the transaction configuration. Actually the TransactionTemplate extends DefaultTransactionDefinition.
So somewhere in your configuration you should have something like this.
<bean id="txTemplate" class=" org.springframework.transaction.support.TransactionTemplate">
<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
<property name="readOnly" value="true" />
<property name="transactionManager" ref="transactionManager" />
</bean>
If you then inject this bean into your class you should be able to use the TransactionTemplate based code you posted/tried earlier.
However there might be a nicer solution which can clean up your code. For one of the projects I worked on, we had a similar setup as yours (single app multiple databases). For this we wrote some spring code which basically switches the datasource when needed. More information can be found here.
If that is to far fetched or overkill for your application you can also try and use Spring's AbstractRoutingDataSource, which based on a lookup key (country code in your case) selects the proper datasource to use.
By using either of those 2 solutions you can start using springs declarative transactionmanagement approach (which should clean up your code considerably).
Define a proxy data source, class being org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy and set the transaction isolation level. Inject actual data source either through setter or constructor.
<bean id="yourDataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
<constructor-arg index="0" ref="targetDataSource"/>
<property name="defaultTransactionIsolationName" value="TRANSACTION_READ_UNCOMMITTED"/>
</bean>
I'm not sure you can do it without working with at the 'Transactional' abstraction-level provided by Spring.
A more 'xml-free' to build your transactionTemplate could be something like this.
private TransactionTemplate getTransactionTemplate(String executionTenantCode, boolean readOnlyTransaction) {
TransactionTemplate tt = new TransactionTemplate(transactionManager);
tt.setReadOnly(readOnlyTransaction);
tt.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return tt;
}
In any case I would "leverage" the #Transactional annotation specifying the appropriate transaction manager, binded with a separate data-source. I've done this for a multi-tenant application.
The usage:
#Transactional(transactionManager = CATALOG_TRANSACTION_MANAGER,
isolation = Isolation.READ_UNCOMMITTED,
readOnly = true)
public void myMethod() {
//....
}
The bean(s) declaration:
public class CatalogDataSourceConfiguration {
#Bean(name = "catalogDataSource")
#ConfigurationProperties("catalog.datasource")
public DataSource catalogDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = ENTITY_MANAGER_FACTORY)
public EntityManagerFactory entityManagerFactory(
#Qualifier("catalogEntityManagerFactoryBean") LocalContainerEntityManagerFactoryBean emFactoryBean) {
return emFactoryBean.getObject();
}
#Bean(name= CATALOG_TRANSACTION_MANAGER)
public PlatformTransactionManager catalogTM(#Qualifier(ENTITY_MANAGER_FACTORY) EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public NamedParameterJdbcTemplate catalogJdbcTemplate() {
return new NamedParameterJdbcTemplate(catalogDataSource());
}
}

DbUnit - Warning: AbstractTableMetaData

I am using DbUnit in the latest version 2.4.8 and I get many warnings in my Unit tests with this message:
WARN : org.dbunit.dataset.AbstractTableMetaData -
Potential problem found: The configured data type factory
'class org.dbunit.dataset.datatype.DefaultDataTypeFactory'
might cause problems with the current database 'MySQL' (e.g. some datatypes may
not be supported properly). In rare cases you might see this message because the
list of supported database products is incomplete (list=[derby]). If so please
request a java-class update via the forums.If you are using your own
IDataTypeFactory extending DefaultDataTypeFactory, ensure that you override
getValidDbProducts() to specify the supported database products.
So I thought I add this (I use a MySQL database):
protected void setUpDatabaseConfig(DatabaseConfig config) {
config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new MySqlDataTypeFactory());
}
But this does not help to avoid these warnings. What's wrong here?
Thank you in advance & Best Regards Tim.
I solved this with info from the dbunit faq. Just setting the data type factory property made the warning go away.
Connection dbConn = template.getDataSource().getConnection();
IDatabaseConnection connection = new DatabaseConnection(dbConn, "UTEST", false);
DatabaseConfig dbConfig = connection.getConfig();
// added this line to get rid of the warning
dbConfig.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new OracleDataTypeFactory());
With Spring-Boot you can use such configuration bean
#Configuration
public class DbUnitConfiguration {
#Autowired
private DataSource dataSource;
#Bean
public DatabaseDataSourceConnectionFactoryBean dbUnitDatabaseConnection() {
DatabaseConfigBean bean = new DatabaseConfigBean();
bean.setDatatypeFactory(new MySqlDataTypeFactory());
DatabaseDataSourceConnectionFactoryBean dbConnectionFactory = new DatabaseDataSourceConnectionFactoryBean(dataSource);
dbConnectionFactory.setDatabaseConfig(bean);
return dbConnectionFactory;
}
}
I know this is an old thread but all the answers here are more complicated than they need to be.
The simplest way to accomplish setting the factory on every connection acquisition is to supply an OperationListener and implement its connectionRetrieved method to do what you want. No overriding needed; the listener will be invoked every time an IDatabaseConnection is acquired.
I was using JTDS driver and MS SQL 2008. In my DBUntiTest class override the following method. The waring message disappeared.
#Override
protected void setUpDatabaseConfig(DatabaseConfig config) {
config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new MsSqlDataTypeFactory());
}
#reassembler's answer is spot on. Just to add that I am testing against different database products, so I now set the DataType Factory according to the current connection:
private IDatabaseConnection getConnection(Connection jdbcConnection) throws Exception {
String databaseProductName = jdbcConnection.getMetaData().getDatabaseProductName();
DatabaseConnection databaseConnection = new DatabaseConnection(jdbcConnection);
DatabaseConfig dbConfig = databaseConnection.getConfig();
switch (databaseProductName) {
case "HSQL Database Engine":
dbConfig.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new HsqldbDataTypeFactory());
break;
case "MySQL":
dbConfig.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new MySqlDataTypeFactory());
break;
default:
log.warn("No matching database product found when setting DBUnit DATATYPE_FACTORY");
}
return databaseConnection;
}
You can obviously add any additionaly databases to this list.
I am using Dbunit version 2.7.0. In my case, just setting the data type factory property in the #Test doesn't suffice. The warning continues when calling dbunit JdbcDatabaseTester.onsetup() method.
I solved the problem implementing a MyJdbcDatabaseTester that extends JdbdDatabaseTester, and overriding the method getConnection(), configuring the datatype factory property:
public class MyJdbcDatabaseTester extends JdbcDatabaseTester {
public MyJdbcDatabaseTester(String driverClass, String connectionUrl, String username,
String password )
throws ClassNotFoundException {
super( driverClass, connectionUrl, username, password );
}
#Override
public IDatabaseConnection getConnection() throws Exception {
IDatabaseConnection result = super.getConnection();
DatabaseConfig dbConfig = result.getConfig();
//to supress warnings when accesing to database
dbConfig.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new MySqlDataTypeFactory());
return result;
}
}
Then I use MyJcbdDatabaseTester instead of JdbcDatabaseTester in my tests

Categories