如何创建自定义 c3p0 ComboPooledDataSource 并在 tomcat context.xml 中引用

问题描述

我有一个 tomcat 应用服务器,我的数据库连接是在 context.xml 中定义的,并将数据源作为 JNDI 获取

<Context>

    <!-- Default set of monitored resources. If one of these changes,the    -->
    <!-- web application will be reloaded.                                   -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>

    <!-- Uncomment this to disable session persistence across Tomcat restarts -->
    <!--
    <Manager pathname="" />
    -->

<Resource name="datasource/test" auth="Container"
                type="com.mchange.v2.c3p0.ComboPooledDataSource"
                    factory="org.apache.naming.factory.beanfactory"
                        user="abc"
                        password="abc123"
                        jdbcUrl="jdbc:MysqL://localhost:3306/jacplus"
                        driverClass="com.MysqL.jdbc.Driver"
                        minPoolSize="2"
                        initialPoolSize="30"
                        maxPoolSize="50"
                        idleConnectionTestPeriod="600"
                        acquireRetryAttempts="30"/>
                        
 </Context>

而不是在 context.xml 中硬编码用户名密码。我想将数据库凭据存储在 aws 机密管理器中,并使用从 aws 机密管理器检索到的数据库凭据创建数据源。

为此,我创建了以下自定义 ComboPooledDataSource 类。

import com.mchange.v2.c3p0.AbstractComboPooledDataSource;

import javax.naming.Referenceable;
import java.io.Serializable;

    public final class CustomComboPoolDataSource extends AbstractComboPooledDataSource implements Serializable,Referenceable {
    
    
    }



    import com.mchange.v2.c3p0.PoolConfig;
    import org.apache.tomcat.jdbc.pool.DataSourceFactory;
    
    import java.sql.sqlException;
    import java.util.Properties;
    import javax.naming.Context;
    import javax.sql.DataSource;
    
    import org.apache.tomcat.jdbc.pool.PoolConfiguration;
    
    public class SecuretomcatDataSourceImpl extends DataSourceFactory {
    
        public SecuretomcatDataSourceImpl() {
    
        }
    
        @Override
        public DataSource createDataSource(Properties properties,Context context,boolean XA) throws sqlException {

String userName = getFromAWSSecretManager("username");
String password = getFromAWSSecretManager("password");
    
            PoolConfiguration poolProperties = SecuretomcatDataSourceImpl.parsePoolProperties(properties);
            PoolConfig poolConfig = new PoolConfig(properties);
    
            CustomComboPoolDataSource customDataSource = new CustomComboPoolDataSource();
            customDataSource.setProperties(properties);
            customDataSource.setUser(userName );
            customDataSource.setPassword(password);
    
            // The rest of the code is copied from Tomcat's DataSourceFactory.
            if (poolProperties.getDataSourceJNDI() != null && poolProperties.getDataSource() == null) {
               performJNDILookup(context,poolProperties);
            }
    
           
    
    
            return customDataSource;
        }
    
    }

之后,我从上面的实现中创建了一个 jar 文件并将其放入 tomcat /lib 文件夹中。

并且我在 tomcat/conf 文件夹中的 Context.xml 文件中做了以下修改

<Resource name="datasource/test" auth="Container"
                    type="com.mchange.v2.c3p0.ComboPooledDataSource"
                        **factory="com.aws.rds.SecuretomcatDataSourceImpl"
                            user=""
                            password=""**
                            jdbcUrl="jdbc:MysqL://localhost:3306/jacplus"
                            driverClass="com.MysqL.jdbc.Driver"
                            minPoolSize="2"
                            initialPoolSize="30"
                            maxPoolSize="50"
                            idleConnectionTestPeriod="600"
                            acquireRetryAttempts="30"/>

但是当我启动 tomcat 时,我遇到了以下异常。

失败了;嵌套异常是 org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException: 无法使用名称查找 JNDI 数据源 'java:comp/env/datasource/test';嵌套异常是 javax.naming.NameNotFoundException: JNDI 对象 [java:comp/env/datasource/test] 未找到:JNDI 实现 返回空值 org.springframework.beans.factory.support.AbstractAutowireCapablebeanfactory.initializeBean(AbstractAutowireCapablebeanfactory.java:1745) 在 org.springframework.beans.factory.support.AbstractAutowireCapablebeanfactory.doCreateBean(AbstractAutowireCapablebeanfactory.java:576) 在 org.springframework.beans.factory.support.AbstractAutowireCapablebeanfactory.createBean(AbstractAutowireCapablebeanfactory.java:498) 在 org.springframework.beans.factory.support.Abstractbeanfactory.lambda$doGetBean$0(Abstractbeanfactory.java:320) 在 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) 在 org.springframework.beans.factory.support.Abstractbeanfactory.doGetBean(Abstractbeanfactory.java:318) 在 org.springframework.beans.factory.support.Abstractbeanfactory.getBean(Abstractbeanfactory.java:199) 在 org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1083) 在 org.springframework.context.support.AbstractApplicationContext.finishbeanfactoryInitialization(AbstractApplicationContext.java:853) 在 org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546) 在 org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:400) 在 org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:291) 在 org.springframework.web.context.ContextLoaderListener.contextinitialized(ContextLoaderListener.java:103) 在 org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4770) 在 org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5236) 在 org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) 在 org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:754) 在 org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:730) 在 org.apache.catalina.core.StandardHost.addChild(StandardHost.java:744) 在 org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:980) 在 org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1851) 在 java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) 在 java.util.concurrent.FutureTask.run(FutureTask.java:266) 在 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) 在 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) 在 java.lang.Thread.run(Thread.java:748)

Caused by: javax.naming.NameNotFoundException: JNDI object with [java:comp/env/datasource/test] 未找到:JNDI 实现 返回空值 org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:158) 在 org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:178) 在 org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:96) 在 org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup.getDataSource(JndiDataSourceLookup.java:45)

我验证了我的数据库凭据和数据库配置正确。

解决方法

您正在扩展 Tomcat JDBCObjectFactory 以创建您的数据源,只要类型属性不是 nulljavax.sql.DataSource 或 {{ 1}} 并将问题记录到警告级别(参见source code)。

如果您设置 javax.sql.XADataSource,它应该可以工作,但您的解决方案依赖于Tomcat JDBC 和 C3P0。

我宁愿检索用户名和密码,并在 org.apache.tomcat.jdbc.pool.DataSource 的默认构造函数中调用 setUsersetPassword,并使用泛型 type="javax.sql.DataSource" 在 Tomcat 中对其进行配置。>