1. 为什么JNDI数据源在国产化部署里绕不开把SpringMVC应用从开发机的直连数据库搬到TONGWEB或TOMCAT上很多人第一反应是继续用applicationContext.xml里那套DriverManagerDataSource或者Druid直连配置。本地跑没问题一上中间件就出幺蛾子连接池被应用和容器各管一套、数据库密码明文躺在war包里、运维改个库地址还得重新打包。JNDI数据源就是来解决这类问题的——把数据源的创建权交给中间件应用只通过一个全局名字去容器里取现成的连接池。这个思路在国产化环境里尤其重要。达梦DM数据库作为常见的国产关系库在TONGWEB、TOMCAT这类中间件下通过JNDI暴露数据源是很多政企项目、内网系统的标准做法。它带来的直接好处有三个连接池统一由容器管理避免应用层重复造池子配置与代码解耦改数据库地址只动中间件配置不动war包密码不落代码安全审计时少一堆麻烦。但真动手配的时候坑比想象中多。TONGWEB和TOMCAT虽然都遵循JNDI规范但配置文件的写法、驱动加载方式、resource-ref的声明细节都有差异达梦的JDBC驱动在JNDI场景下还有自己的一些脾气。我前后在几个项目里踩过完整的坑这篇就把SpringMVC JNDI DM数据源在两种中间件下的完整配置链路拆开讲清楚包括每一步为什么这么写、哪里最容易翻车、怎么验证。适合谁看正在做国产化适配的后端开发、需要把SpringMVC应用部署到TONGWEB/TOMCAT的运维、以及被NameNotFoundException折磨过的同行。下面所有配置我都实测过参数含义和踩坑点会一并说明。2. 先搞清楚JNDI取数据源到底发生了什么2.1 从一次getConnection说起应用里写ctx.lookup(java:comp/env/jdbc/dmDS)这行代码时背后其实发生了好几件事。容器启动阶段它会读取自己的数据源配置文件TOMCAT是context.xmlTONGWEB是tongweb.xml或对应的资源描述文件根据配置里的driverClassName、url、username、password等参数实例化一个连接池对象并把它绑定到JNDI树上某个名字下。这个名字通常是java:comp/env/前缀加你自定义的jdbc/xxx。应用运行时调用lookup容器从JNDI树里把这个已经建好的连接池对象返回给你。注意返回的是连接池本身不是单个Connection。Spring的JndiObjectFactoryBean拿到它之后会把它包装成一个DataSource注入到你的DAO层。所以整个链路是容器建池 → JNDI绑定 → Spring查找 → 注入使用。理解这一点很关键因为后面所有的配置错误本质都是这条链路上某一环断了要么容器没建池配置没生效要么名字对不上lookup找不到要么驱动没加载建池时报ClassNotFound。2.2 为什么不能直接把Druid配置搬过来有人会想我在Spring里配Druid配得好好的为什么不能直接让容器也用Druid技术上可以但要注意容器管理的数据源其生命周期归容器。你在context.xml里配的factory、driverClassName这些是给容器的资源工厂读的不是给Spring读的。Spring这边只负责查找不负责创建。这就带来一个常见误区在applicationContext.xml里既配了JNDI查找又配了一堆连接池参数结果两边打架。正确做法是Spring侧只保留jee:jndi-lookup或JndiObjectFactoryBean连接池参数全部挪到中间件配置里。达梦的连接池参数如initialSize、maxActive在TOMCAT的Resource标签里通过maxTotal、maxIdle这类通用名配置具体映射关系后面会讲。2.3 达梦驱动在JNDI场景的特殊性达梦的JDBC驱动类名是dm.jdbc.driver.DmDriverURL格式通常是jdbc:dm://host:port。在JNDI场景下有两个点要特别注意一是驱动jar必须放在中间件的lib目录下而不是应用的WEB-INF/lib因为建池发生在容器层容器的类加载器得能加载到驱动二是达梦某些版本的驱动对validationQuery有要求不配的话连接池可能拿不到有效连接。这两点后面会展开。3. TOMCAT下的完整配置链路3.1 context.xml里Resource标签怎么写TOMCAT的数据源配置放在conf/context.xml全局或应用的META-INF/context.xml应用级。推荐用应用级避免影响其他应用。一个能跑通的达梦配置长这样Context Resource namejdbc/dmDS authContainer typejavax.sql.DataSource driverClassNamedm.jdbc.driver.DmDriver urljdbc:dm://127.0.0.1:5236/TESTDB usernameSYSDBA passwordSYSDBA001 maxTotal20 maxIdle10 minIdle5 maxWaitMillis10000 validationQuerySELECT 1 FROM DUAL testOnBorrowtrue factoryorg.apache.tomcat.jdbc.pool.DataSourceFactory/ /Context逐项说明name是JNDI名应用里lookup时要用java:comp/env/jdbc/dmDSauthContainer表示由容器认证type固定javax.sql.DataSourcefactory指定TOMCAT自带的连接池工厂不写的话TOMCAT会用默认的DBCP行为有差异。validationQuery对达梦写SELECT 1 FROM DUAL达梦支持DUAL伪表这条能正常返回。注意maxTotal、maxIdle这些是TOMCAT JDBC Pool的参数名不是Druid的maxActive。写错参数名不会报错但会静默失效连接池按默认值跑这是很隐蔽的坑。3.2 web.xml里的resource-ref声明光配context.xml还不够应用得在web.xml里声明自己要用这个资源否则容器不会把它暴露给应用上下文resource-ref descriptionDM DataSource/description res-ref-namejdbc/dmDS/res-ref-name res-typejavax.sql.DataSource/res-type res-authContainer/res-auth /resource-refres-ref-name必须和context.xml里的name完全一致大小写敏感。我见过有人这里写成jdbc/DMDS结果lookup一直报NameNotFoundException排查半天。res-type和res-auth也要和Resource标签对应。3.3 Spring侧JNDI查找的两种写法Spring里查找JNDI数据源老项目常用JndiObjectFactoryBeanbean iddataSource classorg.springframework.jndi.JndiObjectFactoryBean property namejndiName valuejava:comp/env/jdbc/dmDS/ property nameresourceRef valuetrue/ /bean新一点的写法用jee命名空间更简洁jee:jndi-lookup iddataSource jndi-namejava:comp/env/jdbc/dmDS/两种等价。resourceReftrue的作用是自动补全java:comp/env/前缀如果你jndiName已经写全了这个属性可省。我建议写全路径避免歧义。3.4 驱动jar放哪、怎么验证达梦驱动jar如DmJdbcDriver18.jar必须放到TOMCAT_HOME/lib/下。放WEB-INF/lib的话容器建池时用自己的类加载器加载DmDriver会失败报ClassNotFoundException。放好之后重启TOMCAT看日志里有没有数据源初始化相关的报错。验证是否配通最直接的办法是写个测试Servlet或Controller注入DataSource后调getConnection()能拿到连接就说明链路通了。也可以看TOMCAT启动日志成功建池一般会有连接池初始化的INFO日志。4. TONGWEB下的差异与适配要点4.1 TONGWEB的数据源配置位置TONGWEB作为国产中间件配置方式和TOMCAT不完全一样。它的数据源通常在管理控制台里配或者改conf/tongweb.xml及对应的资源文件。控制台配置的好处是可视化、能热改改文件的好处是便于版本管理和批量部署。具体路径各版本略有差异核心是找到JDBC资源或数据源配置项填入达梦的驱动类、URL、账号密码。TONGWEB底层也支持类似TOMCAT的资源工厂机制但它的Resource定义可能放在conf/server.xml的GlobalNamingResources里或者通过context.xml的应用级配置。我建议先在控制台配一遍看它生成/修改了哪个文件然后照着那个格式做版本化配置这样最稳。4.2 JNDI名映射的坑TONGWEB对JNDI名的处理有时比TOMCAT更严格。有些版本要求resource-ref里的名字和实际绑定名做显式映射否则java:comp/env/jdbc/dmDS找不到。如果lookup报错先确认三处名字一致中间件数据源配置里的name、web.xml的res-ref-name、Spring里的jndiName。三处必须字字相同。另一个坑是TONGWEB可能默认不开启resource-ref的自动映射需要在配置里显式声明resource-ref到实际资源的绑定关系。这个在TOMCAT里通常自动完成TONGWEB下要留意。4.3 达梦连接池参数在TONGWEB下的映射TONGWEB的数据源参数名和TOMCAT不同它可能用initialPoolSize、maxPoolSize、minPoolSize这类命名。配置时不要照搬TOMCAT的maxTotal要看TONGWEB文档里对应的参数名。填错同样不报错只是不生效。达梦的validationQuery在TONGWEB下一般也支持写SELECT 1 FROM DUAL即可。4.4 两种中间件的配置对照配置项TOMCATTONGWEB配置位置context.xml控制台或tongweb.xml资源标签Resource数据源配置项池参数名maxTotal/maxIdlemaxPoolSize/minPoolSize驱动位置TOMCAT_HOME/libTONGWEB对应lib目录JNDI前缀java:comp/env/通常相同注意映射这张表是我实际对比后的总结具体版本可能有出入配之前最好翻一下对应版本的文档确认参数名。5. 那些让我熬夜的报错和排查思路5.1 NameNotFoundException的三种成因这个异常是JNDI场景下出现频率最高的。成因无非三类名字不一致配置名、web.xml名、Spring名三者对不上、resource-ref没声明web.xml漏了、容器没建池context.xml配置没生效或驱动加载失败。排查顺序建议从外到内先确认web.xml声明了再确认三处名字一致最后看容器日志里数据源有没有初始化成功。我遇到过一次特别隐蔽的context.xml放在META-INF下但打包时被maven的resource过滤规则排除了war包里根本没有这个文件容器自然没建池。后来在pom里显式配置了META-INF/context.xml的打包规则才解决。所以配完一定要解压war包确认文件在不在。5.2 ClassNotFoundException与驱动加载如果容器日志报ClassNotFoundException: dm.jdbc.driver.DmDriver基本就是驱动jar位置不对。TOMCAT下必须放libTONGWEB下放对应容器的lib目录。放应用WEB-INF/lib在容器建池场景下无效。另外注意驱动jar版本和达梦服务端版本的兼容性版本差太多可能连不上或报协议错误。5.3 连接池拿不到有效连接的排查配好了能lookup到DataSource但getConnection()超时或报无效连接通常是validationQuery没配或配错。达梦下写SELECT 1 FROM DUAL。另外检查testOnBorrow是否开启不开的话池子可能把失效连接给你。还有maxWaitMillis设太小高并发下拿不到连接直接抛异常适当调大。5.4 一个完整的排查链路示例假设应用启动后访问接口报NameNotFoundException。我的排查步骤是第一步解压war包确认META-INF/context.xml和WEB-INF/web.xml都在且内容正确第二步看TOMCAT启动日志有没有数据源初始化记录没有说明context.xml没被读第三步确认驱动jar在TOMCAT_HOME/lib第四步核对三处JNDI名第五步写个最小测试Servlet单独lookup排除Spring配置干扰。这套流程走下来九成问题能定位。6. 几个能省半天时间的实操经验第一先在中间件层面把数据源配通再动Spring。写个不依赖Spring的测试Servlet直接InitialContext().lookup()能拿到连接说明中间件侧没问题再去配Spring。这样能把问题范围一刀切开避免两头同时排查。第二JNDI名统一用全路径。java:comp/env/jdbc/xxx写全别依赖resourceRef自动补前缀减少歧义。第三达梦的validationQuery一定要配。不配的话连接池空闲一段时间后可能给你失效连接表现为偶发的查询失败很难复现和定位。第四参数名以对应中间件文档为准。TOMCAT和TONGWEB的池参数名不同别互相照搬填错不报错但静默失效。第五war包打包后必查META-INF/context.xml。maven资源过滤、打包插件配置都可能把它弄丢这是最容易被忽略的一环。第六生产环境密码不要明文。TOMCAT支持在Resource里用加密后的密码配合自定义factory解密TONGWEB控制台一般也有密码加密选项。安全审计时明文密码是硬伤提前处理掉。这套配置我在几个国产化项目里反复用过TOMCAT和TONGWEB的差异主要就在配置位置和参数命名上核心链路是一致的容器建池、JNDI绑定、Spring查找、注入使用。把这条链路每一环都验证到位剩下的就是耐心核对了。