You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免Liferay重新部署模块时关闭外部JNDI HikariCP连接池?

解决Liferay 7.1热部署时HikariCP连接池被意外关闭的问题

问题场景

我们基于Liferay 7.1.3-ga4开发Service Builder模块,通过Tomcat JNDI配置的HikariCP连接池访问外部数据库。服务器首次启动时连接正常,但热部署模块后,Liferay会在卸载阶段调用HikariDataSource#close关闭连接池,导致后续数据库操作报错“HikariDataSource已关闭”,只能重启Tomcat才能恢复,严重影响开发效率和CI部署流程。

已按官方文档完成的配置:

  • 将hikaricp.jar和slf4j-api.jar放置到Tomcat的lib/ext目录
  • 在server.xml中配置HikariJNDIFactory的Resource
  • 在context.xml中添加ResourceLink关联JNDI资源
  • 模块通过ext-spring.xml配置DataSource Bean,service.xml关联实体与该数据源

问题根源

热部署时,Liferay的com.liferay.portal.dao.jdbc.DataSourceFactoryImpl会检查数据源类型:如果是HikariDataSource,则直接调用其close方法关闭连接池;而Tomcat DBCP的数据源因类型不匹配,不会触发该逻辑。由于我们的HikariCP是Tomcat全局管理的JNDI资源,被Liferay模块意外关闭后,后续无法重新初始化。

解决方案

方案一:自定义数据源包装类(推荐)

通过包装类隐藏HikariDataSource的真实类型,让Liferay无法识别并触发关闭逻辑。

  1. 编写包装类,实现javax.sql.DataSource接口,所有方法委托给内部的HikariDataSource实例:
package com.yourcompany.util;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class NonClosableHikariWrapper implements DataSource {
    private final DataSource delegate;

    public NonClosableHikariWrapper(DataSource delegate) {
        this.delegate = delegate;
    }

    @Override
    public Connection getConnection() throws SQLException {
        return delegate.getConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return delegate.getConnection(username, password);
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return delegate.unwrap(iface);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return delegate.isWrapperFor(iface);
    }

    // 重写close方法(若Liferay通过反射调用,空实现即可)
    public void close() {}
}
  1. 修改模块的ext-spring.xml,将JNDI获取的数据源包装成该类的实例:
<bean id="externalDataSource" class="com.yourcompany.util.NonClosableHikariWrapper">
    <constructor-arg>
        <jee:jndi-lookup jndi-name="java:comp/env/jdbc/YourExternalDB" />
    </constructor-arg>
</bean>

方案二:覆盖Liferay核心DataSourceFactory类

通过OSGi fragment模块修改DataSourceFactoryImpl的逻辑,跳过对外部JNDI数据源的关闭操作。

  1. 自定义DataSourceFactoryImpl,修改destroyDataSource方法:
package com.yourcompany.override;

import com.liferay.portal.dao.jdbc.DataSourceFactoryImpl;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;

public class CustomDataSourceFactoryImpl extends DataSourceFactoryImpl {
    @Override
    public void destroyDataSource(DataSource dataSource) {
        if (dataSource instanceof HikariDataSource) {
            // 在这里添加判断逻辑,识别是否为外部JNDI数据源
            // 例如通过数据源名称、自定义属性等方式判断
            boolean isExternalJndiDS = isExternalJndiDataSource(dataSource);
            if (!isExternalJndiDS) {
                super.destroyDataSource(dataSource);
            }
        } else {
            super.destroyDataSource(dataSource);
        }
    }

    private boolean isExternalJndiDataSource(DataSource dataSource) {
        // 实现自定义判断逻辑,比如获取数据源名称匹配外部DB标识
        HikariDataSource hikariDS = (HikariDataSource) dataSource;
        return hikariDS.getPoolName().startsWith("ExternalDB-");
    }
}
  1. 编写bnd.bnd文件,将该类作为fragment覆盖Liferay核心模块:
Bundle-SymbolicName: com.yourcompany.datasource.factory.override
Bundle-Version: 1.0.0
Fragment-Host: com.liferay.portal.dao.jdbc;bundle-version="[7.1.3,7.2)"
Liferay-Require-SchemaVersion: 1

方案三:使用Tomcat数据源代理

在Tomcat中配置DataSourceProxy包装HikariCP数据源,让Liferay识别到的是代理类而非HikariDataSource。

修改Tomcat的context.xml,添加代理后的资源链接:

<ResourceLink name="jdbc/ExternalDBProxy" global="jdbc/YourExternalDB" type="javax.sql.DataSource" />

然后在模块的ext-spring.xml中引用这个代理后的JNDI名称:

<jee:jndi-lookup id="externalDataSource" jndi-name="java:comp/env/jdbc/ExternalDBProxy" />

总结

推荐使用方案一,无需修改Liferay核心代码,兼容性强,后续升级Liferay版本时改动成本低。

内容的提问来源于stack exchange,提问作者shawmanz32na

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 12:10:58