2025/09/05 17:49:05 - Java 代码.0 - 使用 JNDI 名称: BASE_BIMS_122 2025/09/05 17:49:05 - Java 代码.0 - ERROR (version 8.2.0.0-342, build 8.2.0.0-342 from 2025-05-20 09.56.04 by yuqiang) : 数据库连接失败: Invalid subcontext 'java:' in context '' 2025/09/05 17:49:05 - Java 代码.0 - ERROR (version 8.2.0.0-342, build 8.2.0.0-342 from 2025-05-20 09.56.04 by yuqiang) : javax.naming.NamingException: Invalid subcontext 'java:' in context '' 2025/09/05 17:49:05 - Java 代码.0 - at org.osjava.sj.jndi.AbstractContext.lookup(AbstractContext.java:271) 2025/09/05 17:49:05 - Java 代码.0 - at org.osjava.sj.jndi.AbstractContext.lookup(AbstractContext.java:302) 2025/09/05 17:49:05 - Java 代码.0 - at javax.naming.InitialContext.lookup(InitialContext.java:417) 2025/09/05 17:49:05 - Java 代码.0 - at org.osjava.sj.jndi.DelegatingContext.lookup(DelegatingContext.java:60) 2025/09/05 17:49:05 - Java 代码.0 - at javax.naming.InitialContext.lookup(InitialContext.java:417) 2025/09/05 17:49:05 - Java 代码.0 - at Processor.processRow(Processor.java:42) 2025/09/05 17:49:05 - Java 代码.0 - at org.pentaho.di.trans.steps.userdefinedjavaclass.UserDefinedJavaClass.processRow(UserDefinedJavaClass.java:1019) 2025/09/05 17:49:05 - Java 代码.0 - at org.pentaho.di.trans.step.RunThread.run(RunThread.java:62) 2025/09/05 17:49:05 - Java 代码.0 - at java.lang.Thread.run(Thread.java:748)
时间: 2025-09-05 10:48:33 AIGC 浏览: 19
<think>
我们面临的问题是:在 Java 代码中使用 JNDI 连接数据库时出现 `Invalid subcontext 'java:' in context ''` 错误。
### 问题分析
根据错误信息 `Invalid subcontext 'java:' in context ''`,这通常是由于在非容器环境下(如独立的 Java 应用程序中)使用容器相关的 JNDI 命名(如 `java:comp/env/`)导致的。因为 `java:` 命名空间是 Java EE 容器(如 Tomcat、JBoss 等)提供的,在普通的 Java SE 应用程序中,JNDI 上下文不包含这个命名空间。
### 解决方案
#### 1. 确认运行环境
- **容器环境**(如 Tomcat、WebLogic 等):需要正确配置容器的 JNDI 资源,并在代码中使用容器提供的 JNDI 名称(通常以 `java:comp/env/` 开头)。
- **非容器环境**(如独立 Java 应用程序):不能使用 `java:` 开头的 JNDI 名称,应该使用其他方式(如直接使用 JDBC 连接,或者使用文件系统上的 JNDI 配置文件)。
#### 2. 针对非容器环境(如 Kettle 转换中的 Java 代码步骤)
在 Kettle 转换中,我们通常是在一个独立的 Java 环境中运行,而不是在 Java EE 容器中。因此,我们不应该使用 `java:comp/env/` 前缀。而是应该:
- 使用无前缀的 JNDI 名称(如果配置了 JNDI 资源)。
- 或者,更常见的是,在非容器环境中,我们需要自己创建一个 JNDI 上下文,并绑定数据源。
然而,在 Kettle 中,通常建议使用其内置的数据库连接方式,而不是通过 JNDI。但是,如果必须使用 JNDI,我们可以尝试以下方法:
##### 方法一:使用简单的 JNDI(在独立应用程序中)
我们可以使用 `SimpleJNDI` 这样的轻量级 JNDI 实现。步骤如下:
1. **添加依赖**:将 `simple-jndi` 库添加到 Kettle 的 `lib` 目录中(例如下载 [simple-jndi-0.11.4.1.jar](https://mvnrepository.com/artifact/simple-jndi/simple-jndi))。
2. **配置 JNDI 资源文件**:在类路径下(例如在 Kettle 的启动目录或 `simple-jndi` 的配置目录)创建一个名为 `jndi.properties` 的文件,或者创建一个 `.properties` 文件(如 `bims.properties`),并在其中定义数据源。
例如,在 `bims.properties` 文件中:
```
BASE_BIMS/type=javax.sql.DataSource
BASE_BIMS/driver=com.mysql.cj.jdbc.Driver
BASE_BIMS/url=jdbc:mysql://localhost:3306/BASE_BIMS
BASE_BIMS/user=username
BASE_BIMS/password=password
```
注意:这里没有使用 `java:comp/env/` 前缀。
3. **在代码中初始化 JNDI 上下文**:
```java
// 设置 JNDI 配置的位置(假设我们将配置文件放在 Kettle 的 lib 目录下的 simple-jndi 文件夹中)
System.setProperty("java.naming.factory.initial", "org.osjava.sj.SimpleContextFactory");
System.setProperty("org.osjava.sj.root", "path/to/simple-jndi"); // 配置文件所在目录
System.setProperty("org.osjava.sj.delimiter", "/"); // 属性分隔符,我们使用斜杠
Context ctx = new InitialContext();
// 注意:这里直接使用我们在配置文件中定义的名称(如 "BASE_BIMS"),没有 "java:comp/env/" 前缀
DataSource ds = (DataSource) ctx.lookup("BASE_BIMS");
```
##### 方法二:避免使用 `java:` 前缀(在容器外)
在独立应用程序中,我们不应该使用 `java:` 前缀,所以代码中应该直接使用配置的 JNDI 名称,不加前缀:
```java
// 在独立环境中,使用不带前缀的 JNDI 名称
DataSource ds = (DataSource) ctx.lookup(jndiName); // jndiName 应该是 "BASE_BIMS",而不是 "java:comp/env/BASE_BIMS"
```
#### 3. 针对容器环境(但在容器外调用)
如果我们的代码是在容器内运行的(例如在一个 Web 应用中),那么使用 `java:comp/env/` 前缀是正确的。但是,如果我们是在容器外(比如在 Kettle 中)调用这段代码,那么就会失败。因此,我们必须确保代码运行在容器内。
### 修改后的代码示例(非容器环境)
假设我们在 Kettle 的 Java 代码步骤中,使用 SimpleJNDI 来模拟 JNDI 环境:
```java
import java.sql.*;
import java.util.*;
import javax.naming.*;
import javax.sql.DataSource;
import org.pentaho.di.core.KettleEnvironment;
String jndiName = "BASE_BIMS"; // JNDI 数据源名称
public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException {
Object[] r = getRow();
if (r == null) {
setOutputDone();
return false;
}
r = createOutputRow(r, data.outputRowMeta.size());
if (first) {
first = false;
try {
String inputJndiName = get(Fields.In, "jndi_name").getString(r);
if (inputJndiName != null && !inputJndiName.trim().isEmpty()) {
jndiName = inputJndiName;
}
logBasic("使用 JNDI 名称: " + jndiName);
} catch (KettleException e) {
logBasic("使用默认 JNDI 名称: " + jndiName);
}
// 初始化 SimpleJNDI 环境(只需要一次)
System.setProperty("java.naming.factory.initial", "org.osjava.sj.SimpleContextFactory");
System.setProperty("org.osjava.sj.root", "file:///" + System.getProperty("java.io.tmpdir") + "/simple-jndi");
System.setProperty("org.osjava.sj.delimiter", "/");
// 注意:这里我们假设在临时目录下的 simple-jndi 文件夹存放配置文件
// 实际应用中,我们可以指定一个固定的路径,并确保配置文件存在
}
try {
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup(jndiName);
// 如果上面找不到,可以尝试加上子上下文(视配置文件中的层次结构而定)
// 例如:DataSource ds = (DataSource) ctx.lookup("jdbc/" + jndiName);
Connection dbconConnection = ds.getConnection();
String sql = "select case when cvalue is not null then 'Y' else 'N' end as feedbackTest from t_bims_system_config where ccode ='StationNo'";
PreparedStatement pst = dbconConnection.prepareStatement(sql);
ResultSet rs = pst.executeQuery();
String feedbackTest = "N";
if (rs.next()) {
feedbackTest = rs.getString("feedbackTest");
}
rs.close();
pst.close();
dbconConnection.close();
get(Fields.Out, "feedbackTest").setValue(r, feedbackTest);
} catch (Exception e) {
get(Fields.Out, "feedbackTest").setValue(r, "FAIL");
logError("数据库连接失败: " + e.getMessage(), e);
}
putRow(data.outputRowMeta, r);
return true;
}
```
### 配置步骤
1. **下载 Simple JNDI**:从 Maven 仓库下载 [simple-jndi](https://mvnrepository.com/artifact/org.osjava/simple-jndi) 的 JAR 文件,将其放入 Kettle 的 `lib` 目录。
2. **创建配置文件**:在临时目录(例如 `/tmp/simple-jndi` 或 `C:\Temp\simple-jndi`)下创建一个名为 `BASE_BIMS.properties` 的文件(注意:文件名必须与 JNDI 名称一致),内容为:
```
type=javax.sql.DataSource
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://your-mysql-host:3306/your_database
user=your_username
password=your_password
```
注意:如果 JNDI 名称包含路径(如 `jdbc/BASE_BIMS`),则创建目录 `jdbc`,并在其中创建文件 `BASE_BIMS.properties`。
### 注意事项
- 在非容器环境中,JNDI 的配置需要我们自己管理,因此维护多个数据源会稍微复杂。
- 对于 Kettle 转换,更推荐使用其内置的“数据库连接”组件,通过变量配置连接信息,这样更易于管理。
### 相关问题
1. 如何在 Tomcat 中配置 MySQL 的 JNDI 数据源?[^1][^2]
2. 如何在独立 Java 应用程序中使用 JNDI 连接数据库?[^3]
3. Kettle 中如何通过内置的数据库连接组件配置动态数据源?
4. Simple JNDI 的配置文件有哪些注意事项?
[^1]: 在 Tomcat 的 `context.xml` 中配置 JNDI 资源
[^2]: 在 Web 应用程序中通过 `java:comp/env/` 前缀查找 JNDI 资源
[^3]: 使用 Simple JNDI 在独立应用程序中模拟 JNDI 服务
</think>
### 解决 "Invalid subcontext 'java:' in context" 的 JNDI 连接错误
#### 问题原因分析
该错误表明在非容器环境下(如独立 Java 应用/Kettle 转换中)使用了容器特定的 JNDI 命名空间 `java:`[^1][^2]。常见原因包括:
1. **环境不匹配**:在非容器环境(如独立 Java 程序)中使用容器专属的 `java:comp/env/` 前缀
2. **上下文初始化错误**:InitialContext 未正确配置容器依赖
3. **命名空间冲突**:尝试访问容器未注册的 JNDI 子树
4. **Kettle 特殊性**:Kettle 转换运行时本质是独立 Java 进程,非 Web 容器
#### 解决方案
##### 1. 容器环境适配方案(推荐)
如果运行在 Tomcat/WebLogic 等容器中,使用标准容器前缀:
```java
// 使用容器标准前缀
String fullJndiName = "java:comp/env/" + jndiName;
DataSource ds = (DataSource) ctx.lookup(fullJndiName);
```
##### 2. 独立环境适配方案(Kettle专用)
在 Kettle 的 Java 代码步骤中,移除容器前缀直接使用 JNDI 名称:
```java
// Kettle 独立环境直接使用名称
try {
Context ctx = new InitialContext();
// 直接使用配置的 JNDI 名称
DataSource ds = (DataSource) ctx.lookup("BASE_BIMS");
Connection conn = ds.getConnection();
} catch (NamingException ne) {
logError("请检查 JNDI 配置: " + ne.getMessage());
}
```
##### 3. 通用兼容方案(自动检测环境)
```java
try {
Context ctx = new InitialContext();
DataSource ds = null;
// 尝试容器环境查找
try {
ds = (DataSource) ctx.lookup("java:comp/env/" + jndiName);
} catch (NamingException e1) {
// 尝试独立环境查找
try {
ds = (DataSource) ctx.lookup(jndiName);
} catch (NamingException e2) {
logError("JNDI 名称无效: " + jndiName);
}
}
if (ds != null) {
Connection conn = ds.getConnection();
// ... 数据库操作 ...
}
}
```
##### 4. Kettle 配置文件修正
在 `context.xml` 中移除 `java:` 前缀:
```xml
<!-- 修正前 -->
<ResourceLink name="java:comp/env/jdbc/BASE_BIMS" ... />
<!-- 修正后 -->
<ResourceLink name="jdbc/BASE_BIMS" ... />
```
#### 验证步骤
1. **环境检测**:
```java
System.out.println("Naming Context: " + new InitialContext().getNameInNamespace());
```
输出应为空(独立环境)或 `java:comp/env`(容器环境)
2. **列出可用 JNDI**:
```java
NamingEnumeration<NameClassPair> list = ctx.list("");
while (list.hasMore()) {
System.out.println("JNDI Entry: " + list.next().getName());
}
```
3. **Kettle 日志分析**:
```bash
# 设置详细日志
./kitchen.sh -level=Detailed ...
```
#### 配置示例
`context.xml` 正确配置:
```xml
<Context>
<Resource name="BASE_BIMS"
auth="Container"
type="javax.sql.DataSource"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://localhost:3306/bimsdb"
username="admin"
password="encrypted_password"
maxTotal="20"
maxIdle="10"/>
</Context>
```
> **关键原则**:在独立 Java 环境中避免使用 `java:comp/env/` 前缀,此命名空间仅适用于 Java EE 容器[^1][^2]。
---
### 相关问题
1. **Tomcat 中如何正确配置 MySQL 的 JNDI 数据源?**[^1][^3]
2. **Kettle 转换中如何使用内置数据库连接替代 JNDI?**
3. **如何在 Java 中动态检测当前运行环境(容器/独立)?**
4. **Kettle 连接 Oracle 数据库时的特殊配置有哪些?**
[^1]: Tomcat 项目的 `META-INF/context.xml` 中的 JNDI 资源配置
[^2]: Java 应用中使用 `java:comp/env/` 前缀的容器规范
[^3]: Kettle 中使用 JNDI 时的 MySQL 连接参数配置格式
阅读全文
相关推荐



















