Java连接Domino数据库完整实现指南
Java作为企业级后端开发的核心语言,广泛应用于大型信息系统中。Lotus Domino作为IBM推出的企业级协作平台,具备邮件、工作流、文档管理等综合能力,其内置的NoSQL型数据库支持复杂的结构化与非结构化数据存储。通过Java与其集成,开发者可以实现对企业历史数据的高效访问与业务逻辑扩展。本章将从Java访问Domino数据库的背景出发,探讨其在企业应用中的典型场景,如邮件系统集成、流程审批
简介:Java是一种广泛使用的编程语言,而Lotus Domino是企业级协同与信息管理平台。本资源包“Java访问Domino数据库.rar”深入讲解了Java通过Lotus Domino Java API和CORBA协议访问Domino数据库的技术实现。内容涵盖Session对象的创建方式、Domino核心对象(如数据库、文档、视图)的操作方法、安全认证机制、性能优化策略以及错误处理与日志记录等关键知识点,适用于Java开发者在Domino平台上构建高效、稳定的企业级应用系统。 
1. Java与Domino数据库集成概述
Java作为企业级后端开发的核心语言,广泛应用于大型信息系统中。Lotus Domino作为IBM推出的企业级协作平台,具备邮件、工作流、文档管理等综合能力,其内置的NoSQL型数据库支持复杂的结构化与非结构化数据存储。通过Java与其集成,开发者可以实现对企业历史数据的高效访问与业务逻辑扩展。
本章将从Java访问Domino数据库的背景出发,探讨其在企业应用中的典型场景,如邮件系统集成、流程审批引擎开发、数据迁移与报表生成等。同时,也将分析集成过程中面临的技术挑战,包括环境配置复杂、API学习曲线陡峭、线程安全控制等问题,为后续章节的技术实现打下基础。
2. Lotus Domino Java API使用详解
Lotus Domino 提供了丰富的 Java API,使开发者能够通过 Java 语言访问 Domino 数据库,实现企业级应用与 Domino 平台的无缝集成。本章将深入解析 Domino Java API 的使用方式,包括其版本演进、开发环境配置、核心类与接口、运行时依赖以及常见问题处理等关键内容。通过本章的学习,读者将掌握 Domino Java API 的基本结构与开发流程,为后续章节的高级操作打下坚实基础。
2.1 Java API的版本与开发环境搭建
2.1.1 Domino Java API的版本演进与兼容性分析
Domino Java API 的版本演进与其服务器版本密切相关。从早期的 Domino R5 到当前的 Domino 12.0.1,Java API 在功能支持、性能优化和兼容性方面经历了多次升级。
| Domino 版本 | Java API 版本 | 特性增强 | 兼容性说明 |
|---|---|---|---|
| R5 ~ R8.5 | 1.0 ~ 1.5 | 基础类支持,Session、Database、Document 等 | 仅支持 JDK 1.4 ~ 1.6 |
| Domino 9.0 | 1.6 | 支持 Java 8,增强线程处理能力 | 需要 Domino 9 及以上 |
| Domino 10.0 | 1.8 | 支持 Java 8,引入异步处理机制 | 需配置 Notes.jar 与 JRE 8 |
| Domino 11.0 | 1.9 | 增强 CORBA 支持,改进异常处理 | 推荐使用 Java 11 环境 |
| Domino 12.0+ | 2.0+ | 完全支持 Java 11+, 增强安全性、SSL 支持 | 推荐使用 Java 17 环境 |
兼容性建议:
- 开发环境与运行环境的 Java 版本应与 Domino 版本匹配。
- 使用较新版本的 Domino 时,建议采用 Java 11 或 17 以获得更好的性能与安全性。
- 使用旧版 Domino 时需注意 Notes.jar 的版本与 JRE 兼容性。
2.1.2 开发环境配置(Domino Designer、Notes客户端、Java SDK)
在开发 Java 与 Domino 集成应用前,需配置以下开发环境组件:
- Domino Designer :用于数据库设计与调试。
- Notes客户端 :用于本地测试 Domino Java API。
- Java SDK :推荐使用 Java 8、11 或 17。
开发环境配置步骤如下:
graph TD
A[安装 Domino Designer] --> B[配置 Notes 客户端]
B --> C[安装 Java SDK]
C --> D[设置系统环境变量]
D --> E[导入 Domino Java API 库]
E --> F[创建 Java 项目并配置构建路径]
环境变量设置示例(Windows):
# 设置 JAVA_HOME
set JAVA_HOME=C:\Program Files\Java\jdk-17.0.3
# 设置 Domino Notes.jar 路径
set CLASSPATH=%CLASSPATH%;C:\Program Files\IBM\Notes\jvm\lib\ext\Notes.jar
2.1.3 依赖库引入与项目结构设计
Domino Java API 的核心依赖库为 Notes.jar ,该库位于 Domino 安装目录下的 jvm/lib/ext/ 文件夹中。
Maven 项目结构示例:
my-domino-app/
├── src/
│ └── main/
│ └── java/
│ └── com/example/domino/
│ └── DominoTest.java
├── lib/
│ └── Notes.jar
├── pom.xml
└── README.md
手动引入 Notes.jar 到项目构建路径中(以 Eclipse 为例):
- 右键项目 → Build Path → Configure Build Path。
- 在 Libraries 标签页中点击 “Add External JARs”。
- 选择 Domino 安装目录下的
Notes.jar文件。 - 点击 Apply and Close。
依赖引入后,编写第一个测试类:
import lotus.domino.*;
public class DominoTest {
public static void main(String[] args) {
try {
Session session = NotesFactory.createSession();
System.out.println("成功创建 Session");
System.out.println("用户名:" + session.getUserName());
session.recycle();
} catch (Exception e) {
e.printStackTrace();
}
}
}
代码解释:
NotesFactory.createSession():创建一个本地 Notes 会话。session.getUserName():获取当前登录用户名称。session.recycle():释放资源,防止内存泄漏。
2.2 API核心类与接口介绍
2.2.1 NotesFactory与Session接口的作用与使用方式
NotesFactory 是 Domino Java API 的入口类,用于创建 Session 对象。它提供了多种创建方式,适用于不同的应用场景。
常用创建方法:
| 方法 | 说明 |
|---|---|
createSession() |
创建本地匿名会话(需 Notes 客户端已登录) |
createSessionWithID(String idPath, String password) |
使用指定的用户 ID 文件和密码创建会话 |
createSessionWithServer(String serverName) |
连接到远程 Domino 服务器 |
示例代码:
// 使用用户 ID 文件创建会话
Session session = NotesFactory.createSessionWithID("C:/NotesData/myid.id", "password123");
System.out.println("当前用户:" + session.getEffectiveUserName());
注意事项:
- ID 文件路径必须正确且具有访问权限。
- 使用完毕后必须调用 session.recycle() 回收资源。
2.2.2 NotesDatabase、NotesDocument、NotesView等核心对象的关系
Domino Java API 的核心对象包括:
Session:代表一个 Domino 会话。NotesDatabase:表示一个 Domino 数据库。NotesDocument:表示数据库中的一个文档。NotesView:表示数据库中的一个视图。
对象关系图:
classDiagram
Session --> NotesDatabase
NotesDatabase --> NotesView
NotesDatabase --> NotesDocument
NotesView --> NotesDocument
获取数据库对象示例:
Session session = NotesFactory.createSession();
NotesDatabase db = session.getDatabase("serverName", "mail\\user.nsf", false);
if (db.isOpen()) {
System.out.println("数据库标题:" + db.getTitle());
}
2.2.3 接口继承与异常处理机制解析
Domino Java API 采用接口继承的方式实现多态,所有核心类均实现 Base 接口,提供统一的 recycle() 方法用于资源释放。
异常处理机制:
- Domino Java API 使用
NotesException作为所有异常的基类。 - 所有方法调用都应使用
try-catch捕获异常。
示例代码:
try {
Session session = NotesFactory.createSession();
NotesDatabase db = session.getDatabase("", "nonexistent.nsf", false);
if (db == null) {
throw new NotesException(0, "数据库不存在");
}
} catch (NotesException e) {
System.err.println("NotesException: " + e.id + " - " + e.text);
} catch (Exception e) {
e.printStackTrace();
}
2.3 Java API的运行时依赖与注意事项
2.3.1 Domino运行时库(Notes.jar)的引用方式
Domino 的 Java API 依赖于 Notes.jar ,该文件必须被正确引入到项目的构建路径中。
运行时依赖说明:
- 本地开发需在 Notes 客户端或 Domino 服务器上运行。
- 使用远程访问时需启用 CORBA 支持。
Notes.jar依赖于本地的nlsxbe.dll(Windows)或libnotes.dylib(macOS)等原生库。
注意事项:
- 项目打包时需确保 Notes.jar 位于 classpath 中。
- 使用 Maven 或 Gradle 构建时需手动复制 Notes.jar 至 lib 目录并添加为依赖。
2.3.2 程序部署环境要求(本地与服务器端差异)
| 环境类型 | 要求 |
|---|---|
| 本地部署 | 需安装 Notes 客户端或 Domino 服务器,并配置环境变量 |
| 服务器端部署 | 需将应用部署在 Domino 服务器上,或使用远程 CORBA 访问 |
服务器端部署步骤:
- 将 Java 代码打包为
.jar文件。 - 使用 Domino Designer 将
.jar文件部署到数据库中。 - 配置服务器安全策略文件,允许加载外部类。
2.3.3 常见错误与解决方案(如NoClassDefFoundError)
常见错误类型:
| 错误类型 | 原因 | 解决方案 |
|---|---|---|
| NoClassDefFoundError | Notes.jar 未正确引入 | 检查项目构建路径是否包含 Notes.jar |
| UnsatisfiedLinkError | 缺少原生库文件(如 nlsxbe.dll) | 确保 Domino 安装路径已添加至系统路径 |
| NotesException: 4063 | 数据库路径错误或未打开 | 检查服务器名称和数据库路径是否正确 |
| NotesException: 4386 | 无权访问数据库 | 检查用户权限和 ACL 设置 |
调试建议:
- 使用 System.out.println() 输出关键对象状态。
- 使用 Domino Designer 的调试工具检查运行时环境。
- 启用 Notes 客户端日志( notes.ini 中设置 LOG_DEBUG=1 )。
本章深入讲解了 Domino Java API 的版本演进、开发环境配置、核心类与接口的使用方式,以及运行时依赖和常见错误处理方法。通过本章内容,读者应已具备搭建 Java 与 Domino 集成开发环境的能力,并能初步使用 NotesFactory、Session、NotesDatabase 等核心对象进行开发。下一章将重点讲解 Session 对象的创建方式与连接管理策略,帮助开发者构建高效、稳定的 Domino Java 应用。
3. Session对象创建方式与连接管理
3.1 Session对象概述
3.1.1 Session的作用与生命周期管理
在Lotus Domino的Java API中, Session 对象是与Domino服务器进行交互的起点。它不仅用于建立与Domino服务器的连接,还提供了访问数据库、视图、文档等核心对象的入口。 Session 对象的生命周期直接影响着程序的性能与资源消耗。通常情况下,一个 Session 实例应保持在应用程序运行期间尽可能长时间存活,以避免频繁创建和销毁带来的开销。
import lotus.domino.*;
public class DominoSessionExample {
public static void main(String[] args) {
Session session = null;
try {
session = NotesFactory.createSession();
System.out.println("Session created successfully.");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (session != null) {
try {
session.recycle(); // 释放Session资源
System.out.println("Session recycled.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
代码逻辑分析:
NotesFactory.createSession():创建一个匿名的Session对象,连接本地Domino服务器。session.recycle():回收Session对象,释放底层资源。这是Domino API中非常关键的操作,避免资源泄漏。- 在try-catch-finally结构中处理Session对象,确保异常情况下也能正确释放资源。
参数说明:
createSession():无参数,使用默认的用户上下文(通常是当前运行Notes客户端的用户)连接本地服务器。
3.1.2 不同创建方式的适用场景对比
在实际开发中,根据不同的使用场景,可以选择不同的Session创建方式。常见的三种方式包括:
| 创建方式 | 方法 | 适用场景 |
|---|---|---|
createSession() |
无参数 | 本地开发调试、当前用户上下文访问本地数据库 |
createSessionWithID(String idFilePath, String password) |
用户ID文件路径和密码 | 需要使用特定用户身份访问本地或远程服务器 |
createSessionWithServer(String serverName) |
服务器名称 | 直接连接指定服务器,通常用于无用户ID文件的场景 |
适用场景说明:
- 本地开发 :使用
createSession(),适合调试和快速验证逻辑。 - 服务端部署 :建议使用
createSessionWithID(),以便使用特定用户权限进行访问。 - 远程服务器访问 :结合CORBA协议使用
createSessionWithServer(),适用于需要跨网络访问的场景。
3.2 创建Session的三种方式详解
3.2.1 createSession:无参数创建方式
createSession() 是最基础的创建方式,适用于本地开发环境或已经登录的Notes客户端用户。
Session session = NotesFactory.createSession();
执行逻辑说明:
- 该方法尝试使用当前登录的Notes用户创建一个Session对象。
- 若当前没有运行Notes客户端,则会抛出异常。
- 无需提供用户名、密码或服务器地址。
适用场景:
- 开发人员在本地测试时使用。
- 服务器端部署在Notes服务器本地时使用。
3.2.2 createSessionWithID:使用用户ID文件创建
该方法允许使用特定的用户ID文件和密码创建Session,适合需要特定权限访问数据库的场景。
Session session = NotesFactory.createSessionWithID("C:/NotesData/user.id", "password123");
执行逻辑说明:
- 指定用户ID文件路径和密码。
- Domino会验证ID文件中的私钥和密码是否匹配。
- 成功后返回具有该用户权限的Session对象。
注意事项:
- 用户ID文件路径必须正确且可读。
- 密码应妥善保管,避免硬编码在代码中。
参数说明:
"C:/NotesData/user.id":用户ID文件的路径。"password123":用户ID文件的密码。
3.2.3 createSessionWithServer:指定服务器创建Session
此方法用于直接连接指定服务器,通常用于远程访问或无本地Notes客户端的环境。
Session session = NotesFactory.createSessionWithServer("server01/YourDomain");
执行逻辑说明:
- 指定服务器名称(格式为
serverName/domain)。 - Domino会尝试通过网络连接该服务器,并建立Session。
- 通常需要配合
createSessionWithID()使用以完成身份验证。
应用场景:
- 需要从非Domino服务器主机访问远程Domino数据库。
- 在Web应用中访问Domino服务。
3.3 Session连接池的设计与优化
3.3.1 多线程环境下Session的复用策略
在多线程环境下,频繁创建和销毁Session会导致性能下降,并可能引发资源泄漏。因此,应设计Session连接池来复用Session对象。
连接池设计原则:
- 线程安全 :确保多个线程访问连接池时不会出现并发问题。
- 资源复用 :Session对象一旦创建,尽量复用,避免重复连接。
- 自动回收 :闲置Session应被自动回收,避免资源浪费。
示例策略:
graph TD
A[请求获取Session] --> B{连接池中是否有可用Session?}
B -->|是| C[返回一个可用Session]
B -->|否| D[创建新Session并加入池中]
C --> E[使用Session进行操作]
E --> F[操作完成后归还Session到池]
D --> G[操作完成后归还Session到池]
3.3.2 连接池实现原理与示例代码
我们可以使用 ThreadLocal 来实现Session的线程隔离,确保每个线程持有独立的Session实例。
import lotus.domino.*;
import java.util.concurrent.ConcurrentHashMap;
public class SessionPool {
private static final ThreadLocal<Session> threadLocalSession = new ThreadLocal<>();
private static final ConcurrentHashMap<String, Session> sessionCache = new ConcurrentHashMap<>();
public static Session getSession() throws Exception {
Session session = threadLocalSession.get();
if (session == null) {
session = NotesFactory.createSessionWithID("C:/NotesData/user.id", "password123");
threadLocalSession.set(session);
sessionCache.put(Thread.currentThread().getName(), session);
}
return session;
}
public static void releaseSession() {
Session session = threadLocalSession.get();
if (session != null) {
try {
session.recycle();
} catch (Exception e) {
e.printStackTrace();
}
threadLocalSession.remove();
}
}
}
代码逻辑说明:
ThreadLocal<Session>:每个线程维护自己的Session对象,避免线程间竞争。ConcurrentHashMap:缓存Session对象,便于管理和监控。getSession():获取Session对象,若不存在则创建并缓存。releaseSession():回收当前线程的Session对象。
优点:
- 提高Session复用率,减少创建开销。
- 避免Session泄漏,提升系统稳定性。
3.3.3 连接泄漏检测与资源回收机制
在长期运行的系统中,Session资源泄漏是一个常见问题。可以通过以下方式检测和处理泄漏:
- 定时扫描Session缓存 :检查长时间未使用的Session并主动回收。
- 注册关闭钩子(Shutdown Hook) :在JVM关闭前回收所有Session。
- 使用AOP监控Session生命周期 :记录每个Session的创建和释放时间,用于日志分析。
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Shutting down. Releasing all Sessions...");
for (Session session : sessionCache.values()) {
try {
session.recycle();
} catch (Exception e) {
e.printStackTrace();
}
}
}));
参数说明:
addShutdownHook:在JVM关闭前执行资源回收操作。session.recycle():释放Session资源。
通过以上方式,我们可以有效地管理Session对象的生命周期,提高Java与Domino数据库集成的性能和稳定性。
4. CORBA协议访问远程Domino服务器
在现代企业级Java应用中,远程访问数据库系统是一项基础而关键的能力。Lotus Domino作为企业级协同平台,其支持通过CORBA(Common Object Request Broker Architecture)协议实现远程访问机制,为Java客户端提供了跨平台、跨语言的远程调用能力。本章将深入探讨如何利用CORBA协议访问远程Domino服务器,包括其架构原理、配置步骤、Java客户端的访问流程以及常见问题的排查方法。
4.1 CORBA协议与Domino远程访问机制
4.1.1 CORBA架构概述与Domino集成原理
CORBA是一种由OMA(对象管理组织)制定的分布式对象通信标准,允许不同平台和语言的对象通过网络进行互操作。其核心组件包括:
- ORB(对象请求代理) :负责处理对象间的通信。
- IDL(接口定义语言) :用于定义接口,确保跨语言兼容。
- Stub/Skeleton :客户端和服务器端的代理类,用于封装远程调用细节。
Domino服务器内置了CORBA支持模块,通过其CORBA服务接口,Java客户端可以远程访问Notes对象模型(如Session、Database、Document等)。其集成原理如下图所示:
graph TD
A[Java客户端] --> B(ORB)
B --> C[Domino CORBA服务]
C --> D{Domino服务器核心}
D --> E[Notes API]
D --> F[数据库引擎]
在该架构中,Java客户端通过ORB初始化连接到Domino的CORBA服务,并通过IDL接口定义的Stub类调用远程对象的方法,从而实现对Domino数据库的远程操作。
4.1.2 Domino CORBA服务的启用与配置
要在Domino服务器上启用CORBA服务,需执行以下步骤:
- 编辑Notes.ini文件
在Domino服务器的安装目录中找到notes.ini文件,添加以下参数以启用CORBA服务:
ini CORBAEnable=1 CORBAPort=1780
-
配置访问控制
在Domino管理控制台中,进入“服务器文档 > CORBA设置”,配置允许访问的IP地址和用户权限。 -
重启Domino服务
配置完成后,重启Domino服务器以使设置生效。 -
验证服务状态
使用nsd命令检查CORBA服务是否正常运行:
bash nsd -c "tell corba status"
4.1.3 安全通信与身份验证流程
Domino的CORBA服务支持SSL加密通信和基于用户凭据的身份验证机制。其认证流程如下:
- SSL握手 :客户端与服务器建立SSL连接,验证服务器证书。
- 登录认证 :客户端使用用户ID文件或用户名密码登录Domino服务器。
- 权限验证 :服务器根据用户角色判断其对数据库的访问权限。
SSL配置需在Domino服务器上导入CA证书,并在客户端信任该证书。Java客户端可通过以下方式启用SSL:
Properties props = new Properties();
props.put("com.ibm.CORBA.ORBEnableSSLIOP", "true");
props.put("com.ibm.CORBA.SSL.ClientAuthentication", "true");
props.put("javax.net.ssl.keyStore", "client_keystore.jks");
props.put("javax.net.ssl.keyStorePassword", "changeit");
4.2 Java客户端访问Domino CORBA服务
4.2.1 客户端环境配置与ORB初始化
Java客户端访问Domino的CORBA服务,需要引入Domino的Java SDK,并配置ORB环境。以下是初始化ORB的代码示例:
Properties props = new Properties();
props.put("org.omg.CORBA.ORBClass", "com.ibm.CORBA.iiop.ORB");
props.put("org.omg.CORBA.ORBSingletonClass", "com.ibm.CORBA.iiop.ORBSingleton");
props.put("com.ibm.iiop.ssl.configFile", "ssl.client.props");
ORB orb = ORB.init((String[]) null, props);
上述代码初始化了一个ORB实例,并指定了SSL配置文件路径。其中:
ORBClass指定ORB的实现类。ORBSingletonClass用于单例模式管理ORB实例。ssl.configFile用于指定SSL连接参数。
4.2.2 使用Stub与Skeleton进行远程调用
Domino的CORBA服务基于IDL接口提供远程调用能力。Java客户端通过IDL编译生成的Stub类与远程对象交互。例如,获取Session对象的代码如下:
// 获取根POA
org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
// 构造NameComponent路径
String name = "DominoSession";
NameComponent[] path = ncRef.to_name(name);
// 获取远程Session对象
Session session = SessionHelper.narrow(ncRef.resolve(path));
在上述代码中:
resolve_initial_references("NameService")用于获取命名服务。to_name将字符串名称转换为NameComponent数组。SessionHelper.narrow()用于窄化为具体的Session接口。
4.2.3 远程访问性能优化与延迟控制
远程访问CORBA服务时,可能会遇到网络延迟和性能瓶颈。以下是一些优化建议:
| 优化策略 | 说明 |
|---|---|
| 启用连接池 | 复用ORB连接,减少连接建立开销 |
| 启用压缩 | 通过IDL配置启用数据压缩,减少传输量 |
| 限制并发 | 控制客户端并发请求数,避免资源争用 |
| 启用缓存 | 对频繁访问的文档或视图进行本地缓存 |
示例:启用ORB连接池配置
props.put("com.ibm.CORBA.ORBConnectionCacheSize", "10");
props.put("com.ibm.CORBA.ORBMaxConnections", "50");
4.3 常见问题与故障排查
4.3.1 CORBA连接失败的典型原因分析
| 原因 | 描述 | 解决方案 |
|---|---|---|
| 端口未开放 | CORBA服务端口未在防火墙中开放 | 开放1780端口并检查网络连通性 |
| 服务未启动 | Domino CORBA服务未启动 | 执行 tell corba start 命令启动服务 |
| 类路径缺失 | 缺少Notes.jar或CORBA库 | 检查Java项目依赖是否完整 |
| 身份验证失败 | 用户凭证错误或ID文件路径不正确 | 核对用户名、密码或ID文件路径 |
4.3.2 安全策略配置与SSL通信问题
SSL通信失败通常与证书信任、密钥配置有关。以下是排查步骤:
- 检查客户端信任库 :确认客户端Java环境的
cacerts中已导入服务器CA证书。 - 查看SSL日志 :启用SSL调试日志:
bash java -Djavax.net.debug=ssl,handshake
- 验证证书路径 :确保证书路径正确且未过期。
- 配置SSL协议版本 :禁用旧版本SSL,启用TLS:
java props.put("com.ibm.CORBA.SSL.EnabledCipherSuites", "TLS_RSA_WITH_AES_128_CBC_SHA");
4.3.3 日志分析与调试工具推荐
Domino服务器和Java客户端的日志是排查问题的关键:
- Domino服务器日志 :位于
<domino_data>/logs目录下的console.log和corba.log。 - Java客户端日志 :可通过设置JVM参数输出ORB日志:
bash -Dcom.ibm.CORBA.Debug=true
推荐使用以下工具辅助调试:
| 工具 | 用途 |
|---|---|
| Wireshark | 抓取网络流量,分析CORBA通信 |
| IBM Support Assistant | Domino平台问题分析工具 |
| JConsole | 监控Java客户端资源使用情况 |
此外,可以使用以下代码片段输出ORB连接状态:
try {
ORB orb = ORB.init((String[]) null, props);
System.out.println("ORB initialized successfully.");
} catch (Exception e) {
e.printStackTrace();
System.err.println("Failed to initialize ORB: " + e.getMessage());
}
该代码可帮助快速判断ORB初始化阶段是否出错。
通过本章内容,读者可以全面掌握使用CORBA协议访问远程Domino服务器的核心技术,包括协议架构、配置流程、Java客户端访问方式以及常见问题的解决策略。下一章将继续深入讲解如何通过Java操作Domino数据库对象(NotesDatabase)。
5. Java操作Domino数据库对象(NotesDatabase)
Domino数据库是Lotus Notes/Domino平台的核心存储单元,Java开发者通过 NotesDatabase 类可以实现对数据库的连接、查询、管理、维护等操作。本章将深入讲解如何使用Java访问和操作Domino数据库对象,涵盖基本操作、文档集合与视图管理、以及事务与并发控制等内容。我们将通过代码示例、参数说明、流程图等多种形式,帮助读者掌握Java对Domino数据库对象的完整操作逻辑。
5.1 NotesDatabase对象的基本操作
NotesDatabase 是Java API中表示Domino数据库的核心类,它提供了与数据库连接、元信息获取、状态维护等相关的接口。理解其基本操作是进一步操作文档、视图和事务的基础。
5.1.1 数据库连接与打开操作
在Java中,要操作一个Domino数据库,首先需要通过 Session 对象打开数据库。以下是一个典型的数据库连接代码示例:
import lotus.domino.*;
public class DominoDatabaseExample {
public static void main(String[] args) {
Session session = null;
Database db = null;
try {
// 创建无参数Session(需要当前用户已登录)
session = NotesFactory.createSession();
// 打开本地数据库,路径为:"mail\user.nsf"
db = session.getDatabase("", "mail\\user.nsf", false);
if (db != null && db.isOpen()) {
System.out.println("数据库已成功打开");
} else {
System.out.println("数据库打开失败");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (db != null) db.recycle();
if (session != null) session.recycle();
} catch (NotesException e) {
e.printStackTrace();
}
}
}
}
代码解析:
NotesFactory.createSession():创建一个无参数的Session对象,适用于本地已登录用户。session.getDatabase():用于打开数据库。参数说明如下:- 第一个参数:服务器名(为空表示本地)。
- 第二个参数:数据库文件路径。
- 第三个参数:是否打开数据库副本(
false表示只打开主数据库)。 recycle():释放资源,避免内存泄漏。
注意事项:
- 如果数据库不存在或路径错误,
getDatabase()会返回null。 - 建议始终使用
isOpen()方法验证数据库是否成功打开。 - 操作完成后务必调用
recycle()释放 Domino 对象资源。
5.1.2 获取数据库元信息(标题、路径、创建时间等)
通过 NotesDatabase 对象可以获取数据库的元信息,如数据库标题、路径、创建时间等。这些信息常用于日志记录、监控或系统维护。
if (db.isOpen()) {
System.out.println("数据库标题: " + db.getTitle());
System.out.println("数据库路径: " + db.getFilePath());
System.out.println("创建时间: " + db.getCreated().toString());
System.out.println("是否为模板数据库: " + db.isTemplate());
System.out.println("当前副本ID: " + db.getReplicaID());
}
| 属性 | 方法 | 描述 |
|---|---|---|
| 标题 | getTitle() |
返回数据库的显示名称 |
| 路径 | getFilePath() |
返回数据库在文件系统中的路径 |
| 创建时间 | getCreated() |
返回 DateTime 对象,表示数据库创建时间 |
| 是否为模板 | isTemplate() |
判断是否为模板数据库 |
| 副本ID | getReplicaID() |
返回该数据库的唯一副本标识符 |
应用场景:
- 系统监控:记录数据库创建时间与路径,用于审计或备份策略。
- 用户界面展示:显示数据库标题与路径,供用户选择或查看。
- 模板判断:用于区分是否为模板数据库,决定是否允许修改。
5.1.3 数据库状态查询与维护操作
Domino数据库可能处于不同的状态,如只读、关闭、维护模式等。Java API提供了多种方法用于查询数据库状态并进行维护。
System.out.println("是否为只读数据库: " + db.isReadOnly());
System.out.println("是否正在维护中: " + db.isInMaintenance());
System.out.println("当前打开的文档数量: " + db.getOpenDocumentCount());
| 方法 | 返回类型 | 描述 |
|---|---|---|
isReadOnly() |
boolean | 判断数据库是否为只读 |
isInMaintenance() |
boolean | 判断是否处于维护状态 |
getOpenDocumentCount() |
int | 获取当前打开的文档数 |
compact() |
void | 压缩数据库以释放空间 |
fixup() |
void | 检查并修复数据库结构问题 |
维护操作示例:
if (!db.isReadOnly()) {
db.compact(); // 压缩数据库
System.out.println("数据库已压缩");
db.fixup(); // 修复数据库
System.out.println("数据库已修复");
}
注意事项:
- 压缩和修复操作应谨慎使用,最好在维护窗口执行。
- 这些操作可能会导致数据库短暂锁定,影响并发访问。
- 建议在执行前备份数据库。
5.2 数据库文档集合与视图操作
NotesDatabase 不仅管理数据库本身,还可以用于获取文档集合和视图集合,是操作文档和视图的起点。
5.2.1 获取文档集合(getAllDocuments)
通过 getAllDocuments() 方法可以获取数据库中所有文档的集合,返回一个 DocumentCollection 对象。
DocumentCollection docs = db.getAllDocuments();
int count = docs.getCount();
System.out.println("总文档数: " + count);
参数说明:
getCount():返回文档总数。getFirstDocument()/getNextDocument():遍历文档集合。
示例:遍历所有文档
Document doc = docs.getFirstDocument();
while (doc != null) {
System.out.println("文档UNID: " + doc.getUniversalID());
Document temp = doc;
doc = docs.getNextDocument(doc);
temp.recycle();
}
注意事项:
- 使用
DocumentCollection遍历时,每次获取文档后应调用recycle()释放资源。 - 不要对同一个
Document对象重复调用recycle(),否则会抛出异常。
5.2.2 视图集合与文档关系管理
视图是Domino数据库中用于组织文档的核心结构。Java API通过 getView() 方法获取视图对象。
View view = db.getView("($Inbox)");
if (view != null) {
Document inboxDoc = view.getFirstDocument();
while (inboxDoc != null) {
System.out.println("收件箱文档主题: " + inboxDoc.getItemValueString("Subject"));
Document nextDoc = view.getNextDocument(inboxDoc);
inboxDoc.recycle();
inboxDoc = nextDoc;
}
}
常用方法:
| 方法 | 描述 |
|---|---|
getView(String name) |
获取指定名称的视图 |
getFirstDocument() |
获取视图中第一条文档 |
getNextDocument(Document doc) |
获取当前文档的下一条 |
refresh() |
刷新视图内容 |
示例:视图与文档关系图(Mermaid)
graph TD
A[NotesDatabase] --> B[View]
B --> C[DocumentCollection]
C --> D[Document]
D --> E[Item]
应用场景:
- 邮件系统:通过视图获取收件箱、已发送等邮件列表。
- 文档分类:通过视图对文档按字段分类展示。
- 快速查询:视图可作为索引结构,提高查询效率。
5.2.3 数据库备份与复制操作
Domino支持数据库的复制功能,Java API可以通过 replicate() 方法实现远程数据库的同步。
try {
db.replicate("server1/mail\\user.nsf", true);
System.out.println("数据库已复制到 server1");
} catch (NotesException e) {
e.printStackTrace();
}
参数说明:
- 第一个参数:目标数据库路径(包括服务器名)。
- 第二个参数:是否为双向复制(
true表示双向)。
备份数据库示例:
db.createCopy("", "backup\\user_backup.nsf");
System.out.println("数据库备份成功");
| 方法 | 功能 |
|---|---|
replicate() |
复制数据库 |
createCopy() |
创建数据库副本 |
delete() |
删除数据库(需谨慎) |
注意事项:
- 复制操作可能涉及网络延迟,应设置超时机制。
- 删除数据库前应确保无依赖数据或文档。
- 建议在执行前进行权限验证。
5.3 数据库事务与并发控制
在多线程或高并发环境下,Java访问Domino数据库时需要考虑事务与并发控制,以确保数据一致性和系统稳定性。
5.3.1 Domino事务模型与Java接口支持
Domino数据库本身支持事务型操作,Java API通过 Transaction 类进行封装。开发者可以使用事务控制文档的创建、更新和删除操作。
Transaction tx = null;
try {
tx = session.createTransaction();
Document doc = db.createDocument();
doc.replaceItemValue("Form", "Memo");
doc.replaceItemValue("Subject", "测试事务");
doc.save(true, false, tx); // 在事务中保存
tx.commit(); // 提交事务
} catch (Exception e) {
if (tx != null) tx.rollback(); // 回滚事务
e.printStackTrace();
}
事务流程图(Mermaid):
sequenceDiagram
participant Java
participant Domino
Java->>Domino: 开始事务
Java->>Domino: 创建文档
Java->>Domino: 更新字段
Java->>Domino: 提交事务
alt 提交失败
Java->>Domino: 回滚事务
end
事务控制方法:
| 方法 | 描述 |
|---|---|
createTransaction() |
创建事务对象 |
commit() |
提交事务 |
rollback() |
回滚事务 |
save(true, false, tx) |
在事务中保存文档 |
注意事项:
- 事务操作应在try-catch块中进行,确保异常时能回滚。
- 事务提交前所有操作仅在本地生效,不影响其他用户。
- 事务对象也需调用
recycle()释放资源。
5.3.2 多线程并发访问控制策略
在多线程环境中,多个线程同时访问同一个 Session 或 Database 对象可能导致资源竞争或数据不一致。为此,建议采用以下策略:
- 线程局部Session(ThreadLocal)
private static ThreadLocal<Session> sessionLocal = new ThreadLocal<>();
public static Session getSession() {
Session session = sessionLocal.get();
if (session == null) {
session = NotesFactory.createSession();
sessionLocal.set(session);
}
return session;
}
- 使用连接池管理Session对象
参考第三章的Session连接池设计。
- 同步访问控制
对共享资源如 Database 对象进行同步访问:
synchronized (db) {
// 访问数据库操作
}
线程安全策略对比表:
| 策略 | 优点 | 缺点 |
|---|---|---|
| ThreadLocal Session | 线程独立,避免冲突 | 内存占用增加 |
| 同步块控制 | 简单有效 | 性能开销大 |
| Session连接池 | 复用资源,提高性能 | 实现复杂度高 |
5.3.3 数据一致性保障与回滚机制
在并发操作中,若发生异常,必须确保数据的一致性。Java API支持通过事务机制实现回滚。
try {
Transaction tx = session.createTransaction();
Document doc = db.getDocumentByUNID("1234567890ABCDEF");
doc.replaceItemValue("Status", "Processed");
doc.save(true, false, tx);
if (someConditionFailed()) {
tx.rollback();
System.out.println("事务回滚");
} else {
tx.commit();
System.out.println("事务提交");
}
} catch (Exception e) {
tx.rollback();
}
数据一致性保障流程(Mermaid):
graph LR
A[开始事务] --> B[执行文档更新]
B --> C{是否出错?}
C -->|是| D[事务回滚]
C -->|否| E[事务提交]
注意事项:
- 每次事务操作后必须明确提交或回滚。
- 避免在事务中执行耗时操作,防止阻塞其他线程。
- 回滚后应记录日志,便于问题排查。
至此,本章详细讲解了Java操作Domino数据库对象( NotesDatabase )的核心内容,包括基本操作、文档集合与视图管理、事务与并发控制等。通过代码示例、表格、流程图等多维度分析,帮助读者全面掌握Java访问Domino数据库的技术要点,为后续章节中操作文档对象( NotesDocument )打下坚实基础。
6. Java操作Domino文档对象(NotesDocument)
在Lotus Domino的Java开发中, NotesDocument 对象是操作数据库中具体数据的核心单元。它代表了数据库中的一个文档,开发者可以通过它来实现数据的创建、读取、更新和删除(CRUD)操作。本章将围绕 NotesDocument 对象的生命周期与操作方式进行详细解析,并通过代码示例展示如何高效地进行文档的字段处理、结构化内容操作以及查询与筛选。
6.1 NotesDocument对象的基本操作
6.1.1 文档的创建、打开与保存
NotesDocument 的创建可以通过调用 NotesDatabase.createDocument() 方法实现。创建后,开发者可以为文档添加字段(Item),并通过 save() 方法将其持久化到数据库中。
// 示例:创建并保存一个NotesDocument
Session session = NotesFactory.createSession();
NotesDatabase db = session.getDatabase("server", "path/to/db.nsf", false);
NotesDocument doc = db.createDocument();
doc.replaceItemValue("Form", "Memo"); // 设置表单字段
doc.replaceItemValue("Subject", "Hello Domino");
doc.save(); // 保存文档
逐行解读与逻辑分析:
NotesFactory.createSession():创建一个与Domino服务器的会话连接。session.getDatabase():打开指定路径的数据库。db.createDocument():创建一个新的文档对象。replaceItemValue():设置字段值。Form字段决定了文档在视图或表单中如何显示。doc.save():将文档保存到数据库中。
6.1.2 字段(Item)的读写与更新
字段是文档数据的基本单位。使用 getItemValue() 可以读取字段内容,而 replaceItemValue() 则用于更新字段。
// 示例:读取和更新字段
Vector<?> subject = doc.getItemValue("Subject");
System.out.println("Original Subject: " + subject);
doc.replaceItemValue("Subject", "Updated Subject");
doc.save();
参数说明:
getItemValue("Subject"):返回字段值的Vector集合,适用于多值字段。replaceItemValue("Subject", value):替换字段值,如果字段不存在则创建。
6.1.3 删除文档与回收站处理
删除文档可以通过 remove() 方法实现。若需进入回收站而非彻底删除,可传入 false 作为参数。
// 示例:删除文档
doc.remove(false); // false表示进入回收站
参数说明:
true:立即删除,不可恢复。false:移动到回收站,可通过Domino客户端恢复。
6.2 文档内容的结构化处理
6.2.1 文本、数字、日期字段的处理方式
Domino支持多种字段类型,包括文本、数字、日期等。Java API提供了相应的数据类型处理方式。
// 示例:处理不同类型的字段
doc.replaceItemValue("Body", "This is a memo body."); // 文本
doc.replaceItemValue("Priority", 3); // 数字
doc.replaceItemValue("DueDate", new DateTime("20250405")); // 日期
逻辑分析:
replaceItemValue()方法支持多种Java原生类型,如String、int、DateTime等。- Domino内部自动识别字段类型,开发者无需显式定义。
6.2.2 富文本字段与附件的读写操作
富文本字段(Rich Text)用于存储HTML内容、图片或附件。使用 RichTextItem 类可操作此类字段。
// 示例:添加富文本内容和附件
RichTextItem rtItem = doc.createRichTextItem("Body");
rtItem.appendText("This is rich text content.");
rtItem.embedObject(EmbeddedObject.EMBED_ATTACHMENT, null, "C:\\file.txt", null);
doc.save();
参数说明:
createRichTextItem("Body"):创建一个富文本字段。appendText():添加文本内容。embedObject():嵌入附件,参数包括附件类型、图标、路径等。
6.2.3 文档的复制与版本控制
Domino支持文档的复制和版本管理。开发者可以使用 copyToDatabase() 方法将文档复制到其他数据库,或使用 isConflict() 方法检测冲突版本。
// 示例:文档复制
NotesDatabase targetDb = session.getDatabase("server", "path/to/target.nsf", false);
doc.copyToDatabase(targetDb, true); // true表示复制后保留原文档
逻辑分析:
copyToDatabase():将当前文档复制到目标数据库。- 第二个参数为
true时,原文档保留在源数据库中。
6.3 文档的查询与筛选
6.3.1 使用NotesView和NotesDocumentCollection进行查询
NotesView 是Domino中用于组织文档的视图对象。通过视图可以快速检索文档集合。
// 示例:通过视图获取文档集合
NotesView view = db.getView("AllMemos");
NotesDocumentCollection docs = view.getAllDocumentsByKey("Important", true);
逻辑分析:
getView("AllMemos"):获取指定名称的视图。getAllDocumentsByKey("Important", true):查找键值为“Important”的所有文档。- 第二个参数为
true时,返回精确匹配的结果。
6.3.2 使用公式语言进行条件过滤
Domino支持使用公式语言(Formula Language)进行复杂查询。开发者可以使用 NotesDocumentCollection.FTSearch() 方法进行全文搜索。
// 示例:使用公式语言搜索
String formula = "Form = \"Memo\" & Priority > 2";
NotesDocumentCollection docs = db.search(formula, null, 0);
参数说明:
search(formula, null, 0):执行公式查询,null为可选的DateTime起始时间,0表示无限制返回数量。
6.3.3 文档排序与分页实现
对于大量文档的查询结果,开发者可以结合 NotesView 和 NotesDocumentCollection 实现排序与分页。
graph TD
A[开始查询] --> B[构建视图或执行公式查询]
B --> C{是否需要排序?}
C -->|是| D[使用NotesView.getColumnValues排序]
C -->|否| E[直接获取文档集合]
D --> F[使用getFirstDocument/getNextDocument实现分页]
E --> F
表格:分页实现方法对比
| 方法 | 适用场景 | 性能 | 灵活性 |
|---|---|---|---|
NotesView.getColumnValues() |
视图排序后分页 | 高 | 中等 |
NotesDocumentCollection.getFirstDocument() |
查询结果分页 | 中 | 高 |
FTSearch() + 分页 |
全文检索分页 | 中等 | 高 |
代码示例:分页获取文档
// 示例:分页获取文档
int pageSize = 10;
int pageNum = 2;
NotesDocument current = docs.getFirstDocument();
int count = 0;
while (current != null && count < pageSize * pageNum) {
if (count >= pageSize * (pageNum - 1)) {
System.out.println("Document: " + current.getItemValue("Subject"));
}
current = docs.getNextDocument(current);
count++;
}
逻辑分析:
getFirstDocument():获取集合中的第一个文档。getNextDocument():遍历集合,实现分页逻辑。
小结
本章围绕 NotesDocument 对象的操作展开,详细介绍了文档的创建、字段处理、富文本与附件管理、文档复制与删除,以及文档查询与分页等核心功能。通过丰富的代码示例和流程图、表格等形式,帮助开发者深入理解Domino文档操作的完整流程,并掌握实际开发中常见的问题解决方法。
下一章将结合Session初始化、数据库连接、文档操作等完整流程,展示一个完整的Java访问Domino数据库的实现案例。
7. Java访问Domino数据库完整实现流程
7.1 系统设计与接口定义
7.1.1 功能需求分析与模块划分
在实际的企业级应用中,Java访问Domino数据库通常用于实现文档的增删改查、数据同步、日志记录、邮件系统集成等功能。系统设计阶段应明确如下几个关键模块:
- 会话管理模块 :负责Session的创建、销毁与复用,支持本地或远程连接。
- 数据库连接模块 :封装NotesDatabase的打开、关闭及元数据获取。
- 文档操作模块 :实现NotesDocument的创建、更新、删除、查询等核心功能。
- 事务控制模块 :支持事务提交与回滚,确保数据一致性。
- 异常处理模块 :统一处理Java API抛出的异常,如NotesException。
- 安全与日志模块 :记录操作日志,实现权限控制和安全审计。
模块之间通过接口进行解耦,提升系统的可维护性与扩展性。
7.1.2 接口设计与调用流程图
graph TD
A[Java客户端] --> B(SessionFactory)
B --> C[SessionManager]
C --> D(DatabaseManager)
D --> E[DocumentManager]
E --> F[数据操作]
F --> G[事务提交]
G --> H[响应返回]
H --> A
如上图所示,客户端通过SessionFactory创建Session,交由SessionManager管理,再通过DatabaseManager连接目标数据库,最后由DocumentManager执行文档操作。
7.1.3 异常处理与安全机制设计
Java API在调用Domino服务时可能抛出 NotesException ,建议使用统一的异常处理类进行封装:
public class DominoException extends RuntimeException {
public DominoException(String message, Throwable cause) {
super(message, cause);
}
}
安全机制方面,应结合身份验证(如使用createSessionWithID方式)、SSL通信、访问控制列表(ACL)等策略,保障系统安全性。
7.2 实现步骤详解
7.2.1 初始化Session与连接数据库
初始化Session是访问Domino数据库的第一步。根据部署环境选择合适的创建方式,以下是使用createSessionWithID的示例代码:
import lotus.domino.*;
public class DominoSessionManager {
private Session session;
public void initSession(String idFilePath, String password) throws NotesException {
session = NotesFactory.createSessionWithFullAccess(idFilePath, password);
}
public Database openDatabase(String server, String filePath) throws NotesException {
return session.getDatabase(server, filePath, false);
}
public void closeSession() throws NotesException {
if (session != null) {
session.recycle();
}
}
}
参数说明 :
-idFilePath: 用户ID文件路径(如C:\notes\data\user.id)
-password: ID文件密码
-server: Domino服务器名称,若为空则连接本地
-filePath: 数据库文件路径(如mail\user.nsf)
7.2.2 查询并处理文档数据
使用 Database 对象获取视图,进而查询文档集合。以下代码演示了如何查询所有未读邮件文档:
public List<Document> getUnreadEmails(Database db) throws NotesException {
List<Document> result = new ArrayList<>();
View view = db.getView("($Inbox)");
Document doc = view.getFirstDocument();
while (doc != null) {
Item readFlag = doc.getFirstItem("$Read");
if (readFlag != null && readFlag.getText().equals("0")) {
result.add(doc);
} else {
doc.recycle();
}
Document nextDoc = view.getNextDocument(doc);
doc = nextDoc;
}
return result;
}
逻辑说明 :
-$Inbox为系统视图,包含收件箱文档
-$Read字段标识是否已读(1为已读,0为未读)
- 遍历文档时需调用recycle()释放资源
7.2.3 更新文档与事务提交
更新文档需先获取文档对象,修改字段内容后调用 save() 方法。以下代码展示如何更新文档的标题字段:
public void updateSubject(Document doc, String newSubject) throws NotesException {
Item subjectItem = doc.replaceItemValue("Subject", newSubject);
subjectItem.setSummary(true);
doc.save(true, false); // 第一个参数为是否创建副本,第二个为是否强制保存
}
事务控制说明 :
Domino的Java API默认不支持显式事务控制。若需事务性操作,建议使用Transaction对象或通过代理服务实现。
7.3 完整代码示例与部署说明
7.3.1 示例代码结构与关键方法说明
完整项目结构如下:
domino-java-demo/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ ├── com.example.domino/
│ │ │ │ ├── DominoSessionManager.java
│ │ │ │ ├── DominoDocumentManager.java
│ │ │ │ ├── DominoException.java
│ │ │ │ └── Main.java
│ │ └── resources/
│ └── test/
├── pom.xml
└── README.md
关键类说明:
DominoSessionManager:负责Session的创建与回收DominoDocumentManager:封装文档操作方法Main.java:程序入口,演示完整流程
7.3.2 项目打包与部署注意事项
-
依赖管理 :
- 确保项目中包含Notes.jar(位于Domino安装目录下)
- 使用Maven或Gradle进行依赖管理时,需手动安装本地jar包 -
部署环境要求 :
- 本地开发:需安装Notes客户端或Domino Designer
- 服务器部署:需安装Domino服务器并配置Java运行时 -
权限配置 :
- 确保应用程序使用的用户在目标数据库中有足够权限
- 启用“Java远程方法调用”选项(若涉及远程访问) -
日志与调试 :
- 启用Domino日志(notes.ini中配置LOG_SESSION=1)
- 使用Eclipse或IntelliJ IDEA进行远程调试
7.3.3 性能测试与调优建议
| 优化方向 | 建议措施 |
|---|---|
| Session复用 | 使用连接池机制管理Session对象 |
| 批量操作 | 尽量减少单个文档的频繁调用,使用文档集合进行批量处理 |
| 异步处理 | 对耗时操作(如邮件发送)采用多线程或异步任务 |
| 资源回收 | 每次操作后及时调用 recycle() 释放Domino对象 |
| 网络优化 | 启用压缩传输,配置合适的超时机制 |
性能测试工具推荐 :
- Apache JMeter:模拟多用户并发访问
- VisualVM:分析Java堆内存与线程状态
- Domino控制台命令:tell http show users查看当前连接情况
(本章节完)
简介:Java是一种广泛使用的编程语言,而Lotus Domino是企业级协同与信息管理平台。本资源包“Java访问Domino数据库.rar”深入讲解了Java通过Lotus Domino Java API和CORBA协议访问Domino数据库的技术实现。内容涵盖Session对象的创建方式、Domino核心对象(如数据库、文档、视图)的操作方法、安全认证机制、性能优化策略以及错误处理与日志记录等关键知识点,适用于Java开发者在Domino平台上构建高效、稳定的企业级应用系统。
更多推荐

所有评论(0)