Using one DataSource object for multiple DB connections - using only one connection at a time - spring

Can we define only one DataSource object and wire it dynamically at runtime connecting to different databases ? I need to connect to only one database at a time.
I will be passing the name of the Database as argument. I will lookup the DB URL and other details from a property file and then I need to connect to the DB using the DB URL.
In Short - I do not know the number of databases I need to connect to. I will have all possible database connection details configured in the database.properties file following a certain syntax (like prefixed with DB01 etc.). The name of the DB will be passed as argument and I need to execute the query against that database.
database.properties file
DB01.driver=com.ibm.db2.jcc.DB2Driver
DB01.url=jdbc:db2://localhost:50000/SAMPLE
DB01.username=db2admin
DB01.password=db2admin
DAO class
#Autowired
#Qualifier("DB01") // how do I make this dynamic ?
private DataSource datasource;
private JdbcTemplate jdbcTemplate;
// some more code
public SqlRowSet executeQuery(String sqlQuery)
{
// can I pass the DB name here (the database.properties file will have the DB details
// with this name as given above) and set the DataSource Object accordingly ?
// so that the query will be executed against that DB ?
setJdbcTemplate(new JdbcTemplate(this.datasource));
return getJdbcTemplate().queryForRowSet(sqlQuery);
}
Using Spring v4.1.4 RELEASE. Thanks !!

You can define a Routing DataSource that redirects the getConnection method to one datasource or another based on a key, in your case, it seems to be the database name.
For instance, the spring xml:
....
<bean id="DB01DataSource" parent="parentDatasource" p:url="${DB01.url}" ... />
<bean id="DB02DataSource" parent="parentDatasource" p:url="${DB02.url}" .../>
<bean id="dataSource" class="DBRoutingDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="DB01" value-ref="DB01DataSource"/>
<entry key="DB02" value-ref="DB02DataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="DB01DataSource"/>
</bean>
....
The DBRoutingDataSource class:
public class DBRoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return DBContextHolder.getDB();
}
}
And the DBContextHolder class:
public final class DBContextHolder {
private static final ThreadLocal<String> CONTEXT = new ThreadLocal<String>();
private DBContextHolder() {
// empty
}
public static void setDB(final String db) {
CONTEXT.set(db);
}
public static String getDB() {
return CONTEXT.get();
}
public static void clearDB() {
CONTEXT.remove();
}
}
In your service class before calling your DAO you set the key that will enable the Routing DataSource to get the right connection:
DBContextHolder.setDB("DB01");
try{
dao.executeQuery(sqlSentence);
}finally{
DBContextHolder.clearDB();
}

Related

Spring bean how to access server resource only once?

I have a Spring bean which refers connection factory to access J2C resource. This code is deployed in webSphere server in 2 jvms load balanced. I had the readAuth method as init-method in bean xml but it was called multiple times during a load test. Since Spring singleton is per bean per container, I assumed there are multiple containers causing it to load multiple times. So I removed the init-method and changed the username and password to static and added null check in get methods. But now also, readAuth method is being called multiple times. I want to ensure this method is called only once per jvm since this method access server resource and connection is timing out during load test.
Please suggest a best approach on how to write this class. Thanks in advance.
<bean id="J2CUtils"
class="test.J2CUtils">
<property name="connectionFactory" ref="connectionFactory" />
</bean>
<jee:jndi-lookup id="connectionFactory" jndi-name="eis/J2CAuth" />
public class J2Ctils {
private ConnectionFactory connectionFactory;
private static String userName;
private static String password;
private void readAuth() throws ResourceException {
System.out.println("Auth loaded");
Connection conn = connectionFactory.getConnection();
Interaction interaction = (Interaction) conn.createInteraction();
Config config = interaction.getConfig();
userName = config.getUserName();
password = config.getPassword();
}
public String getUserName() {
if(null == userName) {
readAuth();
}
return userName;
}
public String getPassword() {
if(null == password) {
readAuth();
}
return password;
}
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
}
}
Singletons or static methods don't help if you want to access a resource exactly once because every member of the cluster has it's own instance of the class.
Take in mind that neither the Java EE specification the Spring specification do define any kind of cluster behavior. You need to search for specific vendor solution to achieve this kind of requirement. Just as an example see have a look at #ApplicationScped in a cluster.
Other approaches to archive the expected behaviour are using a distributed lock like Zookeeper or maybe even a Queue with duplicate message filter.

Spring PropertyPlaceholderConfigurer and passing multiple queries at startup

I am working on some existing application and it loads single DB query at server startup .
Now I want to pass more (may 3-4 queries) in same code instead one query.
i.e.How I can pass multiple queries in spring
Here is code-
myspring.xml
<property name="mypropertyfiles">
<list>
<value>test1.properties</value>
<value>test2.properties</value>
</list>
</property>
<bean id="mybean" class="com.test.MyBean">
<constructor-arg><ref-bean="mypropertyfiles"/><const-arg>
<constructor-arg><ref-bean="dataSource"/><const-arg>
<constructor-arg><value>select pcode, pname from PRODUCT1</value>
// Here only one query. **but I want to pass more queries like**
//select pcode, pname from PRODUCT2,
//select ocode, oname from ORDER1,
//select ocode, oname from ORDER2
<const-arg> <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
</bean>
MyBean.java
public class MyBean extends PropertyPlaceholderConfigurer
{
private DataSource dSource;
private String dbQuery;
// this is existing map to store PRODUCT1 table details
Map product1Map = new ConcurrentHashMap();
//lly, I will create more product2Map,order1Map , order2Map to store my
//queries data from PRODUCT2,ORDER1,ORDER2 tables
// here it is taking only one query.But I want more queries ie list
MyBean (Resource[] resources , DataSource dSource,String dbQuery){
super();
setLocations(resources);
this.dSource=dSource;
this.dbQuery=dbQuery;
}
#override
public String resolvePlaceholder(String p,Peroperties p){
//some code.....
loadQuery();
}
loadQuery(){
JdbcTemplate j;
j.execute(dbQuery,.......) // exsiting code has one query only{
public Map doInPreparestament(Preaprestatement ps){
rs =ps.executeQuery..
while(rs.next){
product1Map.put(rs.getString("pcode"),rs.getString("pname")); // only one map
}
}
}
}
Questions -
How I can pass multiple queries from "myspring.xml" file
I want to use existing loadQuery method to load all my queries and put into other Maps i.e. product2Map,order1Map ,order2Map

How to choose spring configuration in runtime based on a tenant?

I would like to be able to choose specific Spring (or Grails) context configuration based on the tenant that user belongs to in runtime. Let's say I use Spring Security and I retrieve tenantId during login.
Imagine now I have a two tenants and they pay different commission. How to inject specific service into a controller without too much plumbing? Here are two different contexts. So, I should inject different ExchangeService based on tenant.
#Configuration
public class FooTenant{
#Bean
public ExchangeService bar() {
return new ZeroCommisionExchangeService ();
}
}
#Configuration
public class BarTenant{
#Bean
public ExchangeService bar() {
return new StandardCommisionExchangeService ();
}
}
Edit:
I am aware I can obtain reference to Spring context and ask for service "manually", but I am looking for a more generic solution where this problematic is solved by IoC framework.
A couple of years ago we needed somthing like this but only for DataSources and ViewResolvers. We developed a solution using spring' TargetSource solution. (Initially we used a HotswappableTargetSource but that wasn't adequate for our use-case.
The code we developed is availabe here in the multi-tenant directory.
It is fully configurable and flexible.
Basically what you do is you configura a ContextSwappableTargetSource and tell it what type of interface/class it needs to return.
<bean id="yourTentantBasedServiceId" class="biz.deinum.multitenant.aop.target.ContextSwappableTargetSource">
<constructor-arg value="ExchangeService" />
</bean>
The default is to lookup beans in the ApplicationContext based on the tenantId (see the BeanFactoryTargetRegistry for this). However you can specify one or more of those (we used a JndiLookupTargetRegistry to dynamically lookup datasource, which allowed use to add tenants on the fly without restarting the application).
If you explicitly configure a BeanFactoryTargetRegistry you can add a prefix and suffix.
<bean id="exchangeService" class="biz.deinum.multitenant.aop.target.ContextSwappableTargetSource">
<constructor-arg value="ExchangeService" />
<property name="targetRegistry>
<bean class="biz.deinum.multitenant.aop.target.registry.impl.BeanFactoryTargetRegistry">
<property suffix="ExchangeService"/>
</bean>
</property>
</bean>
Now for foo it would lookup a bean named fooExchangeService and for bar barExchangeService.
The tenantId is stored in a ThreadLocal which is wrapped inside the ContextHolder. You need to find a way to fill and clear this thread local (in general a servlet Filter does that trick.
In your code you can now simply use the interface ExchangeService and at runtime based on the tenantId the correct implemenation will be looked up.
Also see http://mdeinum.wordpress.com/2007/01/05/one-application-per-client-database/
Assuming you have different services already defined, you can get their bean from the context and use it. In my example, all the services have implementation of serviceMethod and based on some criteria pick your proper service. The only thing I am not sure is how Multitenancy might impact this.
import org.springframework.context.ApplicationContext
class ServiceManagerController {
def serviceManager
def index() {
ApplicationContext ctx = grails.util.Holders.grailsApplication.mainContext
serviceManager = ctx.getBean(params.serviceName); //firstService or secondService
render serviceManager.serviceMethod()
}
}
FirstService
class FirstService {
def serviceMethod() {
return "first"
}
}
SecondService:
class SecondService {
def serviceMethod() {
return "second"
}
}
While it is possible to swap beans instantiated in a spring context at runtime (HotswappableTargetSource), it is not meant for use cases such as yours.
Remember there is one Spring Context for your application, all threads use the same instances (in most cases), this implies when you swap out a bean implementation, you are affectively doing this for all your application's users. To prevent this, you run into issues of ensuring Thread Safety, employing Thread Locals, as listed in another answer.
While it is possible to continue this approach and arrive at an implementation that gets the job done, it would definitely be a very contrived way of solving this problem.
You should take a step back and look at your problem in a more wholesome, system wide design point of view. Bust out your patterns books and look at how this can be resolved, regardless of whether you use Spring or an other framework. Service Locator, Factory bean etc described in some of the answers above is a step in the correct direction.
Your Use Case is pretty common for multi-tenant applications. You need to narrow down things that are likely to change based on a tenantId versus things that are constant across.
For instance as mentioned in the question, each Tenant might have a different commission amount or even different algorithm for commission calculation. A simple solution to this would be to implement a CommissionCalculationService which accepts a tenantId, and any other domain object based on which commission is to be calculated, I would imagine this would be something like Order or Sale, whatever makes sense in your application.
You now need a CommissionServiceFactory or a ServiceLocator which will contain tenant specific implementations of the CommissionCalculationService. The Service Locator is instantiated when the Spring context loads, and is injected with implementation classes also at application startup.
When you want to calculate commission for a tenant, you basically obtain the tenantId from the user's login, pass the tenant id to your service locator, based on the tenantId passed, the service locator returns the appropriate instance of a Service Implementation. In your calling class, use this instance to calculate the commission for the tenant.
Another pattern to consider is the Strategy Pattern, or even Template Pattern.
Bottom line, even if you want tenant specific logic implemented cleanly, don't thing about changing the beans loaded in the context. Have classes in your context that can handle all your tenant specific logic. Rely on design patterns to use the correct bean from the context based on the tenant id.
I apologize if the answer was a little verbose, I felt it was needed to explain why I think updating beans in a loaded Spring Context is not the appropriate solution.
I use the following code:
public class ConfigurableProxyFactoryBean implements FactoryBean<Object>, BeanNameAware {
#Autowired
private ApplicationContextProvider applicationContextProvider;
private Class<?> proxyType;
private String beanName;
private Object object;
private Object fallbackObject;
private Object monitor = new Object();
private ConfigurableProxy proxy;
public ConfigurableProxyFactoryBean(Class<?> proxyType) {
this.proxyType = proxyType;
}
public Object getFallbackObject() {
return fallbackObject;
}
public void setFallbackObject(Object fallbackObject) {
synchronized (monitor) {
this.fallbackObject = fallbackObject;
if (proxy != null) {
proxy.setFallbackObject(fallbackObject);
}
}
}
#Override
public void setBeanName(String name) {
beanName = name;
}
#Override
public Object getObject() throws Exception {
synchronized (monitor) {
if (object == null) {
#SuppressWarnings("unchecked")
Class<Object> type = (Class<Object>)proxyType;
proxy = new ConfigurableProxy(applicationContextProvider, beanName);
proxy.setFallbackObject(fallbackObject);
object = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] { type }, proxy);
}
return object;
}
}
#Override
public Class<?> getObjectType() {
return proxyType;
}
#Override
public boolean isSingleton() {
return true;
}
}
class ConfigurableProxy implements InvocationHandler {
public ConfigurableProxy(ApplicationContextProvider appContextProvider, String beanName) {
this.appContextProvider = appContextProvider;
this.beanName = beanName;
}
private ApplicationContextProvider appContextProvider;
private String beanName;
private Object fallbackObject;
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
ApplicationContext appContext = appContextProvider.getApplicationContext();
String name = "$&&#" + beanName;
Object bean = appContext.containsBean(name) ? appContext.getBean(name) : fallbackObject;
return method.invoke(bean, args);
}
public void setFallbackObject(Object fallbackObject) {
this.fallbackObject = fallbackObject;
}
}
ApplicationContextProvider has implementation, that chooses ApplicationContext according to current tennant.
In XML configuration it is used like this:
<bean class="my.package.infrastructure.ConfigurableProxyFactoryBean" name="beanName">
<constructor-arg>
<value type="java.lang.Class">my.package.model.ServiceInterface</value>
</constructor-arg>
<property name="fallbackObject">
<bean class="my.package.service.DefaultServiceImplementation"/>
</property>
</bean>
And in tennant configuration that way:
<bean class="my.package.service.ServiceImplementationA" name="$&&#beanName"/>
To inject this service somewhere you just write:
public class MyController {
#Autowired
private ServiceInterface service;
}
Also you are to implement ApplicationContextProvider, I won't share mine. It is not very hard to implement. For example, your implementation can just store context in ThreadLocal. And you create your own ServletContextListener, which for every query gets the current tennant and stores it into your ApplicationContextProvider implementation.
The new tenant scope and a servicelocator can helps
Tenant scope will guarantee than service is created one time for a tenant
Sample code:
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="tenant" value="foo.TenantScope"/>
</map>
</property>
</bean>
<bean id="service" class="foo.Service" factory-bean="tenantServiceLocator" factory-method="createInstance" scope="tenant"/>
<bean id="fooService" class="FooService">
<bean id="barService" class="BarService">
<bean id="tenantServiceLocator" class="foo.TenantServiceLocator">
<property name="services">
<map>
<entry key="foo" value-ref="fooService"/>
<entry key="bar" value-ref="barService"/>
</map>
</property>
</bean>
TenantServiceLocator should know the user tenantId
public class TenantServiceLocator {
private Map<String, Service> services;
public String getTenantId() {
return "foo"; // get it from user in session
}
public Map<String, Service> getServices() {
return services;
}
public void setServices(Map<String, Service> services) {
this.services = services;
}
public Service createInstance(){
return services.get(tenantId);
}
}
public class FooController{
#Autowired
private Service service;
}
A sample TenantScope implementation
public class TenantScope implements Scope {
private static Map<String, Map<String, Object>> scopeMap = new HashMap<String, Map<String, Object>>();
#Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> scope = getTenantScope(getTenantId());
Object object = scope.get(name);
if(object == null){
object = objectFactory.getObject();
scope.put(name, object);
}
return object;
}
private Map<String, Object> getTenantScope(String tenantId) {
if (!scopeMap.containsKey(tenantId)) {
scopeMap.put(tenantId, new HashMap<String, Object>());
}
return scopeMap.get(tenantId);
}
private String getTenantId() {
return "foo"; // load you tenantId
}
#Override
public Object remove(String name) {
Map<String, Object> scope = getTenantScope(getTenantId());
return scope.remove(name);
}
#Override
public void registerDestructionCallback(String name, Runnable callback) {
}
#Override
public Object resolveContextualObject(String key) {
return null;
}
#Override
public String getConversationId() {
return null;
}
}
Transforming my comment in an answer, one possible solution is to create a spring factory bean, that receive all he needs to decide which service needs to be returned when creating the instance.
Translating to Grails:
public interface ChoosableServiceIntf {
String getName();
}
class NormalService implements ChoosableServiceIntf {
public String getName() {
return getClass().name;
}
}
class ExtendedService implements ChoosableServiceIntf {
public String getName() {
return getClass().name
}
}
class ChoosableServiceFactory {
static ChoosableServiceIntf getInstance(String decisionParam) {
if(decisionParam == 'X') {
return applicationContext.getBean('extendedService')
}
return applicationContext.getBean('normalService')
}
static ApplicationContext getApplicationContext() {
return Holders.grailsApplication.mainContext
}
}
Here we have two services and ChoosableServiceFactory is responsible to know witch is the correct one.
Then you will need to use the method ApplicationContext#getBean(String, Object[]) to return the correct instance and will also make the factory prototyped scope because of the runtime params.
A controller to test it:
class MyController {
def grailsApplication
def index() {
ChoosableServiceIntf service = grailsApplication.mainContext.getBean('choosableServiceFactory', ["X"] as Object[])
ChoosableServiceIntf serviceNormal = grailsApplication.mainContext.getBean('choosableServiceFactory', ["N"] as Object[])
render text: "#1 - ${service.class.name} , #2 - ${serviceNormal.class.name}"
}
}
This will print #1 - dummy.ExtendedService , #2 - dummy.NormalService
The declaration of the beans will be:
choosableServiceFactory(ChoosableServiceFactory) { bean ->
bean.scope = 'prototype'
bean.factoryMethod = 'getInstance'
}
normalService(NormalService)
extendedService(ExtendedService)

Spring Data Solr multiple cores and repository

I have apache solr with multiple cores e.g. currency, country etc... So using Spring Data Solr I can retrieve information from one core. I have got this XML configuration right now queries against 'currency' core. If I wanted to query against 'country' core how can I set this up?
<!-- Enable Solr repositories and configure repository base package -->
<solr:repositories base-package="com.acme.repository" solr-template-ref="solrCurrencyTemplate"/>
<solr:solr-server id="solrCurrencyServer" url="http://localhost:8983/solr/currency"/>
<bean id="solrCurrencyTemplate" class="org.springframework.data.solr.core.SolrTemplate">
<constructor-arg ref="solrCurrencyServer" />
</bean>
and have the repository defined as
#Repository
public interface CurrencyRepository extends SolrCrudRepository<Currency, String> {
}
and from my service I can do this
#Override
public List<Currency> getCurrencies() {
Page<Currency> currencies = (Page<Currency>) currencyRepository.findAll();
return currencies.getContent();
}
I have also tried using #SolrDocument(solrCoreName = "currency") but this din't work.
#SolrDocument(solrCoreName = "currency")
public class Currency {
public static final String FIELD_CURRENCY_NAME = "currency_name";
public static final String FIELD_CURRENCY_CODE = "currency_code";
public static final String FIELD_DECIMALS = "decimals";
#Id
#Field(value = FIELD_CURRENCY_CODE)
private String currencyCode;
//currency_name,decimals
#Field(value = FIELD_CURRENCY_NAME)
private String currencyName;
#Field(value = FIELD_DECIMALS)
private String decimals;
...
...
...
}
I need help on this asap... otherwise I will have to go back to the RestTemplate Solution :-(
Hope someone can help.
Thanks
GM
Thought I would share, We spend lot of time recently configuring multiple cores. We did in java, not xml.
As part of spring #configuration add following.
#Bean(name="solrCore1Template")
public SolrTemplate solrCore1Template() throws Exception {
EmbeddedSolrServer embeddedSolrServer = new EmbeddedSolrServer(getCoreContainer(), "core1");
return new SolrTemplate(embeddedSolrServer);
}
#Bean(name="solrCore2Template")
public SolrTemplate solrCore2Template() throws Exception {
EmbeddedSolrServer embeddedSolrServer = new EmbeddedSolrServer(getCoreContainer(), "core2");
return new SolrTemplate(embeddedSolrServer);
}
#Bean
#Scope
public CoreContainer getCoreContainer() throws FileNotFoundException{
String dir = <path_to_solr_home>;
System.setProperty("solr.solr.home", dir);
CoreContainer.Initializer initializer = new CoreContainer.Initializer();
return initializer.initialize();
}
And to use each template use like below in service classes.
#Resource
private SolrTemplate solrCore1Template;
Embedded server can be relaced with HTTP using below code.
HttpSolrServer httpSolrServer = new HttpSolrServer(getSolrURL());
return new SolrTemplate(httpSolrServer, "core1");
Hope this helps. I know it's a very late reply for the question asked.
multicore support via namespace config is unfortunately an open issue. You'll need to have a separate SolrTemplate for each core and create repositories manually.
#Autowired
#Qualifier("solrCurrencyTemplate")
private SolrTemplate solrCurrencyTemplate;
#Autowired
#Qualifier("solrCountryTemplate")
private SolrTemplate solrCountryTemplate;
//...
CurrencyRepository currencyRepo = new SolrRepositoryFactory(this.solrCurrencyTemplate)
.getRepository(CurrencyRepository.class);
CountryRepository countryRepo = new SolrRepositoryFactory(this.solrCountryTemplate)
.getRepository(CountryRepository.class);
Spring Data now supports multiple cores with their respective repositories.
The multicoreSupport flag needs to be true in #EnableSolrRepositories annotation and the corresponding document needs to be told what core they belong to. Like:
#SolrDocument(solrCoreName = "currency")
public class Currency
{
// attributes
}
the other class should be
#SolrDocument(solrCoreName = "country")
public class Country
{
// attributes
}
The respective repositories should know what pojo they are working with.
public interface CurrencyRepository extends SolrCrudRepository<Currency,String>
{
}
and
public interface CountryRepository extends SolrCrudRepository<Country,String>
{
}
and configuration should be
#Configuration
#EnableSolrRepositories(value = "com.package.name",multicoreSupport = true)
public class SolrConfig
{
#Bean
public SolrServer solrServer() throws Exception
{
HttpSolrServerFactoryBean f = new HttpSolrServerFactoryBean();
f.setUrl("http://localhost:8983/solr");
f.afterPropertiesSet();
return f.getSolrServer();
}
#Bean
public SolrTemplate solrTemplate(SolrServer solrServer) throws Exception
{
return new SolrTemplate(solrServer());
}
}
With Spring Data Solr 1.1.0.RC1 multiple cores works as described by Christoph Strobl with #EnableSolrRepositories. It works also with an XML configuration by set multicore-support="true".
<solr:repositories base-package="your.solr.repo.package" repository-impl-postfix="Impl" multicore-support="true"/>
<solr:solr-server id="solrServer" url="${solr.server.base.connection.url}" />
<bean id="solrTemplate" class="org.springframework.data.solr.core.SolrTemplate">
<constructor-arg index="0" ref="solrServer" />
</bean>
<solr:solr-server id="solrServer" timeout="1000" maxConnections="1000" url="${solr.server.1},${solr.server.2}"/>
<bean id="solrServerFactory" class="org.springframework.data.solr.server.support.MulticoreSolrServerFactory">
<constructor-arg ref="solrServer" />
<constructor-arg name="cores">
<list>
<value>${solr.index.customer}</value>
<value>${solr.index.task}</value>
</list>
</constructor-arg>
</bean>
<bean id="solrTemplate" class="org.springframework.data.solr.core.SolrTemplate">
<constructor-arg ref="solrServerFactory" />
</bean>
<solr:repositories base-package="com.deve.pig.solr" multicore-support="true" solr-template-ref="solrTemplate" />

Spring 3.1 and Oracle Audit Trail: Providing application data to triggers

Problem Parameters:
Spring 3.1
Oracle 11.2.0.3
Glassfish 2.1 Application Server, providing a JDBC connection pool.
Problem Description:
I am retrofitting user auditing in an existing set of administrative applications for adding, editing and deleting customer users. I need to store the ID of the administrative user in audit records created by Oracle triggers associated with a number of tables. I want to make the administrative user Id accessible to the triggers by setting the Oracle CLIENT_IDENTIFIER attribute on each a connection retrieved from the connection pool before a database operation and then clear the attribute after the database operation. I have something that works, but I don't really like the way it is done.
The Question:
Is there a way to access connections so an Oracle context attribute can be set before and after a database operation? Maybe some kind of listener responding to an event?
I have looked at:
A million web pages (OK maybe that's an exaggeration, but I've googled for a three or four hours).
Using DataSourceUtils to get connections. This would work, but I really don't want to manage the connections, I just want to intercept them on the way in and out of the pool to set the CLIENT_IDENTIFIER attribute value.
Overriding the getConnection method of the datasource. Since this gets called somewhere inside the JdbcTemplate, I can't get the application data to the method.
I'm hoping that the Spring and/or Oracle Gurus will say "Well it's obvious and the answer is ... " without having to hack through my implementation, but here it is anyway. If nothing else, this does work in case someone is looking for an idea.
My Implementation:
All database operations are done using a JdbcOperations reference to a JdbcTemplate object injected into a Dao. The add, edit and delete operation use the JdbcOperations query method, passing either a PreparedStatementCreator or a BatchPreparedStatementSetter. I access the java.sql.Connection object provided by the application server connection pool in the callback methods for these objects (createPreparedStatement or setValues) to set the CLIENT_IDENTIFIER attribute.
applicationContext.xml datasource configuration:
<!-- Setup the datasource -->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/IpOneDatabasePool"/>
</bean>
<!-- Setup the transaction manager -->
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- Associate the transaction manager with objects that must be managed. -->
<aop:config>
<aop:pointcut id="userDaoOperation" expression="execution(* com.myCompany.IpOne.dao.UserDaoImpl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="userDaoOperation"/>
</aop:config>
<!-- Bean providing access to the various prepared statement objects -->
<bean id="daoHelperFactory" class="com.myCompany.IpOne.dao.DaoHelperFactoryImpl" />
<!-- Bean that allows setting of the client identifier for the audit trail -->
<bean id="databaseContextEditor" class="com.myCompany.IpOne.dao.OracleDatabaseContextEditor" />
<!-- Dao that manages persistence of User objects -->
<bean id="userDao" class="com.myCompany.IpOne.dao.UserDaoImpl" >
<property name="dataSource" ref="dataSource"/>
<property name="licenseDao" ref="licenseDao"/>
<property name="appPropertyManager" ref="appPropertyManager"/>
<property name="maximumLicensesPerUserKey" value="max_licences_per_user"/>
<property name="daoHelperFactory" ref="daoHelperFactory"/>
</bean>
This is the user Dao interface
public interface UserDao {
void addUser(User newUser,String adminUserId);
}
This is the user Dao class
public class UserDaoImpl implements UserDao{
private JdbcOperations jdbcOperations;
public void setDataSource(DataSource dataSource) {
this.jdbcOperations = new JdbcTemplate(dataSource);
}
public void addUser(User newUser,String adminUserId) {
PreparedStatementCreator insertUserStatement =
this.daoHelperFactory.getInsertUserStatement(newUser,adminUserId);
KeyHolder keyHolder = this.daoHelperFactory.getKeyHolder();
this.jdbcOperations.update(insertUserStatement, keyHolder);
newUser.setUserId(keyHolder.getKey().intValue());
}
}
This class provides access to the application context.
public class ApplicationContextProvider implements ApplicationContextAware{
private static ApplicationContext ctx = null;
public static ApplicationContext getApplicationContext() {
return ctx;
}
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
this.ctx = ctx;
}
}
Interface for classes that provides various objects used by the Dao.
public interface DaoHelperFactory {
PreparedStatementCreator getInsertUserStatement(User user,String adminUserId);
KeyHolder getKeyHolder();
}
This class is just a factory for PreparedStatementCreator and BatchPreparedStatementSetter objects and other objects used by the Dao. I've changed it to provide the object that actually sets the database context attribute to the various objects being returned.
public class DaoHelperFactoryImpl implements DaoHelperFactory{
private DatabaseContextEditor getDatabaseContextEditor(){
ApplicationContext appContext = ApplicationContextProvider.getApplicationContext();
DatabaseContextEditor databaseContextEditor = (DatabaseContextEditor) appContext.getBean("databaseContextEditor");
return databaseContextEditor;
}
public KeyHolder getKeyHolder(){
return new GeneratedKeyHolder();
}
public PreparedStatementCreator getInsertUserStatement(User user,String adminUserId){
InsertUser insertUser = new InsertUser(user,adminUserId);
insertUser.setDatabaseContextEditor(getDatabaseContextEditor());
return insertUser;
}
}
This is the interface for classes that set the database context
public interface DatabaseContextEditor {
public DatabaseContextEditor getInstance();
public void setClientIdentifier(Connection connection,String clientIdentifier) throws SQLException;
}
This is class which does that for Oracle
public class OracleDatabaseContextEditor implements DatabaseContextEditor{
public void setClientIdentifier(Connection connection,String clientIdentifier) throws SQLException{
OracleJdbc4NativeJdbcExtractor extractor = new OracleJdbc4NativeJdbcExtractor();
oracle.jdbc.OracleConnection oracleConnection = null;
if(!(connection instanceof oracle.jdbc.OracleConnection))
oracleConnection = (oracle.jdbc.OracleConnection) extractor.getNativeConnection(connection);
else
oracleConnection = (oracle.jdbc.OracleConnection)connection;
String[] metrics = new String[OracleConnection.END_TO_END_STATE_INDEX_MAX];
metrics[OracleConnection.END_TO_END_CLIENTID_INDEX]=clientIdentifier;
oracleConnection.setEndToEndMetrics(metrics,(short)0);
}
public DatabaseContextEditor getInstance(){
return new OracleDatabaseContextEditor();
}
}
This class is the PreparedStatementCreator for adding a User
public class InsertUser implements PreparedStatementCreator {
User insertUser;
/** This is the admin user Id I need to store */
String adminUserId;
private final String SQL = "INSERT INTO SC_USR (" +
"USR_ID, USR_SSO_NAME, USR_PH_NO, USR_SIP_NAME," +
"USR_SIP_PSWD, USR_SIP_DISP_NAME, USR_SIP_DOMAIN, USR_SIP_PROXY," +
" USR_CREATED_BY, USR_CREATED_DATETIME) " +
"VALUES (SEQ_SC_USR_ID.NEXTVAL, ?, ?, ?, ?, ?, ?, ?, ?, SYSTIMESTAMP)";
private final String GENERATED_COLUMNS[] = {"USR_ID"};
/** Object that provides functionality for setting values in the database context */
private DatabaseContextEditor databaseContextEditor;
public InsertUser(User user,String adminUserId){
this.insertUser = user;
this.adminUserId = adminUserId;
}
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
this.databaseContextEditor.setClientIdentifier(connection, adminUserId);
PreparedStatement preparedStatement = connection.prepareStatement(SQL,GENERATED_COLUMNS);
int i=1;
preparedStatement.setString(i++,this.insertUser.getSsoName());
preparedStatement.setString(i++,this.insertUser.getPhoneNumber());
preparedStatement.setString(i++,this.insertUser.getSipName());
preparedStatement.setString(i++,this.insertUser.getSipPassword());
preparedStatement.setString(i++,this.insertUser.getSipDisplayName());
preparedStatement.setString(i++,this.insertUser.getSipDomain());
preparedStatement.setString(i++,this.insertUser.getSipProxy());
preparedStatement.setString(i++,this.insertUser.getCreatedBy().name());
return preparedStatement;
}
public void setDatabaseContextEditor(DatabaseContextEditor databaseContextEditor) {
this.databaseContextEditor = databaseContextEditor;
}
}
There are "AFTER DELETE OR INSERT OR UPDATE" triggers on each table I want to audit. Each table has a corresponding audit table. They extract the CLIENT_IDENTIFIER from the context and insert a row in the appropriate audit table. This is a sample.
CREATE OR REPLACE TRIGGER IPONE_DEV_USER.SC_USR$AUDTRG
AFTER DELETE OR INSERT OR UPDATE
ON IPONE_DEV_USER.SC_USR
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE
v_operation VARCHAR2(10) := NULL;
v_admin_user_id VARCHAR2(30);
BEGIN
v_admin_user_id := SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER');
IF INSERTING THEN
v_operation := 'INS';
ELSIF UPDATING THEN
v_operation := 'UPD';
ELSE
v_operation := 'DEL';
END IF;
IF INSERTING OR UPDATING THEN
INSERT INTO SC_USR$AUD (
USR_ID,
USR_SSO_NAME,
USR_PH_NO,
USR_SOME_VALUE1,
USR_SOME_VALUE2,
USR_SOME_VALUE3,
USR_SOME_VALUE4,
USR_CREATED_BY,
USR_SOME_VALUE5,
USR_SOME_VALUE6,
aud_action,aud_timestamp,aud_user) VALUES (
:new.USR_ID,
:new.USR_SSO_NAME,
:new.USR_PH_NO,
:new.USR_SOME_VALUE1,
:new.USR_SOME_VALUE2,
:new.USR_SOME_VALUE3,
:new.USR_CREATED_DATETIME,
:new.USR_CREATED_BY,
:new.USR_SOME_VALUE4,
:new.USR_SOME_VALUE5,
v_operation,SYSDATE,v_admin_user_id);
ELSE
INSERT INTO SC_USR$AUD (
USR_ID,
USR_SSO_NAME,
USR_PH_NO,
USR_SIP_NAME,
USR_SIP_PSWD,
USR_SIP_DISP_NAME,
USR_CREATED_DATETIME,
USR_CREATED_BY,
USR_SIP_DOMAIN,
USR_SIP_PROXY,
aud_action,aud_timestamp,aud_user) VALUES (
:old.USR_ID,
:old.USR_SSO_NAME,
:old.USR_PH_NO,
:old.USR_SIP_NAME,
:old.USR_SIP_PSWD,
:old.USR_SIP_DISP_NAME,
:old.USR_CREATED_DATETIME,
:old.USR_CREATED_BY,
:old.USR_SIP_DOMAIN,
:old.USR_SIP_PROXY,
v_operation,SYSDATE,v_admin_user_id);
END IF;
END;
As I say this works, but I don't like it for the following reasons.
I have to modify the connection in methods that are intended for setting up the prepared statements.
I have to add this code to every PreparedStatementCreator or a BatchPreparedStatementSetter object I want to audit.
I don't have access to the connection after the database operation so I can clear the attribute.
What I really want is a single point where I can set the attribute on the connection, before and after.
Any input or ideas would be appreciated.
Spring have an elegant way of doing this. The example is pretty much what you want:
Spring Data docs
Uses AOP to set the CLIENT_IDENTIFIER when a connection is got from the Datasource.
Could use another pointcut for when the connection is closed. But not a problem is the connection pool is used soley by your app.
A better way is to use connection labeling. Have a look at the oracle.ucp.jdbc.LabelableConnection interface.

Resources