个性化的Tomcat JNDI数据源

4

我有多个使用相同数据库的web应用程序。 直到最近,我一直使用JNDI数据源,如下所示:

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"/>

服务器配置:

@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;
}

现在,我需要开始在JNDI资源中配置数据库方言(到目前为止已经是硬编码的)。我的Tomcat和服务器配置现在如下:

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实现(从org.apache.tomcat.jdbc.pool.DataSourceFactory复制过来的重要内容,并加入方言):

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;
    }
}

自定义数据源实现(扩展 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;
    }
}

这个方法有一定效果——当我重新启动Tomcat时,第一个webapp(例如使用jdbc/service1DB数据源的那个)成功启动,并使用了配置的方言,正常操作;但所有其他webapp在数据源查找期间都会失败并出现错误。此外,即使没有重新启动Tomcat,在部署webapps时也会出现相同的错误(即使先前成功启动过的第一个webapp):

"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]"

这里可能存在什么问题?

你的 CustomDataSource 存放在哪里?这是 Tomcat 处理类加载器的典型问题,尝试将你的类放在一个 Jar 包内,并将该 Jar 包放在 tomcat/lib 目录下。 - morgano
它位于我配置DataSource查找的类所在的同一包中(以及CustomDataSourceFactory)。因此,它包含在每个Web应用程序的WAR中。我将尝试这个方法,只是为了看看是否有效,但如果可能的话,我更倾向于避免使用这种方法。 - Willy
1个回答

3

singleton="false"

啊哈,我想通了。

我注意到只有一个“CustomDataSource”实例被创建 - 重新启动后,在日志中有一个小的提示说明数据源已经创建,然后第一个webapp开始运行,但在另一个重新启动之前,任何webapp都不会再次创建数据源。

我重新阅读了Tomcat的JNDI资源HOW-TO中的“添加自定义资源工厂”章节,并注意到了将singleton属性设置为false的注释(我以前已经读过它,但认为它对我的情况不重要)。

我向我的数据源中添加了它。请注意底部的最后一个属性。

<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"
/>

它能够正常工作!所有我的web应用程序都启动并使用我配置的方言!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接