Reputation: 101
I have multiple webapps which use the same database. Until recently I've been using the JNDI datasource like so:
server.xml:
<Resource name="jdbc/dbPool" auth="Container" type="javax.sql.DataSource"
maxActive="100" minIdle="10" maxIdle="30" maxWait="1000"
username="username" password="password"
driverClassName="oracle.jdbc.OracleDriver"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
url="jdbc:oracle:thin:@//localhost:1521/XE"/>
context.xml:
<ResourceLink name="jdbc/service1DB" global="jdbc/dbPool" type="javax.sql.DataSource"/>
<ResourceLink name="jdbc/service2DB" global="jdbc/dbPool" type="javax.sql.DataSource"/>
<ResourceLink name="jdbc/service3DB" global="jdbc/dbPool" type="javax.sql.DataSource"/>
<ResourceLink name="jdbc/service4DB" global="jdbc/dbPool" type="javax.sql.DataSource"/>
Server configuration:
@Bean
public DataSource dataSource(String dataSourceJndiName) {
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
lookup.setResourceRef(true);
DataSource dataSource;
try {
dataSource = lookup.getDataSource(dataSourceJndiName);
} catch (DataSourceLookupFailureException e) {
log.error("Cannot establish database connection", e)
throw e;
}
return dataSource;
}
Now I need to start configuring the database dialect (which has been hardcoded so far) in the JNDI resource. My Tomcat and server configurations now look like this:
server.xml:
<Resource name="jdbc/dbPool" auth="Container" type="my.webapp.CustomDataSource"
maxActive="100" minIdle="10" maxIdle="30" maxWait="1000"
username="username" password="password"
driverClassName="oracle.jdbc.OracleDriver"
factory="my.webapp.CustomDataSourceFactory"
dialect="org.hibernate.dialect.Oracle10gDialect"
url="jdbc:oracle:thin:@//localhost:1521/XE"/>
context.xml:
<ResourceLink name="jdbc/service1DB" global="jdbc/dbPool" type="my.webapp.CustomDataSource"/>
<ResourceLink name="jdbc/service2DB" global="jdbc/dbPool" type="my.webapp.CustomDataSource"/>
<ResourceLink name="jdbc/service3DB" global="jdbc/dbPool" type="my.webapp.CustomDataSource"/>
<ResourceLink name="jdbc/service4DB" global="jdbc/dbPool" type="my.webapp.CustomDataSource"/>
CustomDataSourceFactory implementation (important stuff copied over from org.apache.tomcat.jdbc.pool.DataSourceFactory with the dialect squeezed in):
public class CustomDataSourceFactory extends DataSourceFactory {
private static final String PROP_DIALECT = "dialect";
private static final String[] CUSTOM_PROPERTIES = new String[]{PROP_DIALECT};
private static final String[] PROPERTIES = ArrayUtils.addAll(ALL_PROPERTIES, CUSTOM_PROPERTIES);
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
if (obj != null && obj instanceof Reference) {
Reference ref = (Reference) obj;
Properties properties = new Properties();
for (int i = 0; i < PROPERTIES.length; ++i) {
String propertyName = PROPERTIES[i];
RefAddr ra = ref.get(propertyName);
if (ra != null) {
String propertyValue = ra.getContent().toString();
properties.setProperty(propertyName, propertyValue);
}
}
return this.createDataSource(properties, nameCtx);
} else {
return null;
}
}
public javax.sql.DataSource createDataSource(Properties properties, Context context) throws Exception {
PoolConfiguration poolProperties = parsePoolProperties(properties);
if (poolProperties.getDataSourceJNDI() != null && poolProperties.getDataSource() == null) {
this.performJNDILookup(context, poolProperties);
}
String dialect = properties.getProperty(PROP_DIALECT);
if (dialect == null) {
log.error("Dialect is unspecified");
return null;
}
CustomDataSource dataSource = new CustomDataSource(dialect, poolProperties);
dataSource.createPool();
return dataSource;
}
}
CustomDataSource implementation (extends org.apache.tomcat.jdbc.pool.DataSource):
@NoArgsConstructor
public class CustomDataSource extends DataSource {
@Getter
@Setter
private String dialect;
public CustomDataSource(String dialect, PoolConfiguration poolProperties) {
super(poolProperties);
this.dialect = dialect;
}
}
This somewhat works - when I restart Tomcat, the first webapp (e.g. the one using jdbc/service1DB datasource) starts successfully and uses the configured dialect and operates normally, but all the other ones fail with an error during datasource lookup. Also, when I deploy the webapps without Tomcat restart, the same error occurs (even with the first webapp which previously successfully started after Tomcat restart):
"org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException":"Failed to look up JNDI DataSource with name 'java:comp/env/jdbc/service2DB'; nested exception is javax.naming.NamingException: The local resource link [service2DB] that refers to global resource [jdbc/dbPool] was expected to return an instance of [my.webapp.CustomDataSource] but returned an instance of [my.webapp.CustomDataSource]"
What could be the issue here?
Upvotes: 4
Views: 2033
Reputation: 101
Aahh, I figured it out.
I did notice that there was only ever one "instance" of CustomDataSource created - after restart there was a tiny indication in the logs stating the datasource was created and then the first webapp started, but no datasources were created again for any of the webapps until another restart.
I re-read the "Adding custom resource factories" chapter in the Tomcat's JNDI resources HOW-TO and noticed the comment about setting the singleton attribute to false (I had read it before but didn't think it was important for my case).
I added it to my datasource. Note the last attribute at bottom.
<Resource name = "jdbc/dbPool"
auth = "Container"
type = "my.webapp.CustomDataSource"
maxActive = "100"
minIdle = "10"
maxIdle = "30"
maxWait = "1000"
username = "username"
password = "password"
driverClassName = "oracle.jdbc.OracleDriver"
factory = "my.webapp.CustomDataSourceFactory"
dialect = "org.hibernate.dialect.Oracle10gDialect"
url = "jdbc:oracle:thin:@//localhost:1521/XE"
singleton = "false"
/>
And it works! All my webapps start and use the dialect I configured!
Upvotes: 3