在Java Web开发中,Tomcat作为一款广泛使用的Web应用服务器,其与数据库的交互是构建动态应用的核心环节,一个健壮、高效的数据库连接方案,直接关系到应用的性能、稳定性和可维护性,本文将深入探讨Tomcat连接数据库的两种主要方式,并重点阐述业界推荐的最佳实践。

核心概念:JDBC与数据库驱动
在探讨具体连接方法之前,必须理解两个基础概念:JDBC(Java Database Connectivity)和JDBC驱动。
- JDBC:这是一套由Java定义的、用于执行SQL语句的API(应用程序编程接口),它为Java开发者提供了一套标准的、与数据库无关的操作接口,使得开发者无需关心底层数据库的具体实现差异。
 - JDBC驱动:这是由数据库厂商提供的,实现了JDBC接口的具体组件,MySQL有
mysql-connector-java.jar,PostgreSQL有postgresql.jar,应用程序通过加载相应的JDBC驱动,才能与特定的数据库进行通信,无论采用哪种连接方式,将正确的JDBC驱动JAR包放入项目的类路径中都是首要前提。 
应用程序内直接连接(不推荐)
这是最直观、最基础的连接方式,开发者直接在Java代码(如Servlet或DAO层)中加载驱动、创建连接、执行操作并关闭连接。
示例代码片段:
public class DirectConnectionExample {
    public Connection getConnection() throws SQLException {
        Connection conn = null;
        try {
            // 1. 加载JDBC驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 2. 定义数据库连接URL、用户名和密码
            String url = "jdbc:mysql://localhost:3306/mydatabase?useSSL=false&serverTimezone=UTC";
            String user = "dbuser";
            String password = "dbpassword";
            // 3. 通过DriverManager获取数据库连接
            conn = DriverManager.getConnection(url, user, password);
        } catch (ClassNotFoundException e) {
            System.err.println("MySQL JDBC Driver not found.");
            e.printStackTrace();
        }
        return conn;
    }
}
这种方式存在显著的弊端:
- 性能低下:每次用户请求都需要创建一个新的数据库连接,而数据库连接的创建是一个昂贵的操作,频繁地创建和销毁连接会严重消耗系统资源,导致应用性能瓶颈。
 - 管理困难:数据库的连接信息(URL、用户名、密码)硬编码在Java代码中,当需要更换数据库或修改密码时,必须重新编译、部署整个应用,维护成本极高。
 - 资源泄漏风险:如果开发者忘记在
finally块中关闭连接,很容易导致连接泄漏,最终耗尽数据库资源,使整个系统崩溃。 - 无法利用连接池:这种方式无法利用Tomcat等容器提供的连接池技术,错失了提升并发性能的关键机会。
 
由于以上缺点,直接连接方式通常仅用于学习、测试或非常简单的独立工具中,在生产环境中应严格避免。
使用JNDI配置数据源(推荐的最佳实践)
为了克服直接连接的种种问题,Tomcat引入了JNDI(Java Naming and Directory Interface)来配置和管理数据源,这是企业级应用开发中的标准做法。
核心思想:将数据库连接的创建和管理责任从应用程序代码中剥离,交给Tomcat容器来处理,应用程序通过一个全局名称(JNDI名称)向容器“请求”一个数据库连接,而容器则从一个预先配置好的连接池中分配一个可用的连接。
优势:

- 连接池管理:Tomcat负责维护一个数据库连接池,应用启动时,池中会预先创建一定数量的连接,当应用需要连接时,直接从池中获取,用完后归还,极大地减少了连接创建和销毁的开销,显著提升了高并发场景下的性能。
 - 配置集中化:所有数据库连接信息(URL、用户名、密码、连接池参数等)都配置在Tomcat的服务器配置文件中,与应用程序代码完全解耦,修改配置只需重启Tomcat,无需改动和重新部署应用。
 - 安全性增强:数据库凭证等敏感信息存储在服务器端,而非应用程序代码包中,降低了信息泄露的风险。
 - 可维护性好:数据库的迁移或配置变更对应用代码透明,符合“关注点分离”的设计原则。
 
配置步骤详解
以下是在Tomcat中配置JNDI数据源的详细步骤:
放置JDBC驱动
将对应数据库的JDBC驱动JAR包(例如mysql-connector-java-8.0.xx.jar)复制到Tomcat安装目录下的lib文件夹中,这一步至关重要,因为它使得驱动对于Tomcat容器本身是可见的,从而能够创建数据源。
在context.xml中配置资源
打开Tomcat的conf目录下的context.xml文件,在<Context>标签内添加<Resource>标签来定义数据源。
<Context>
    <!-- ... 其他配置 ... -->
    <Resource name="jdbc/MyAppDB" 
              auth="Container"
              type="javax.sql.DataSource"
              driverClassName="com.mysql.cj.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/mydatabase?useSSL=false&serverTimezone=UTC"
              username="dbuser"
              password="dbpassword"
              maxTotal="20"
              maxIdle="10"
              maxWaitMillis="10000"
              validationQuery="SELECT 1"
              testOnBorrow="true"/>
</Context>
关键属性说明:
| 属性名 | 说明 | 示例 | 
|---|---|---|
name | 
JNDI名称,应用将通过此名称查找数据源,通常以jdbc/开头。 | 
jdbc/MyAppDB | 
auth | 
指定资源的管理者,通常为Container。 | 
Container | 
type | 
资源的Java类型,对于数据源固定为javax.sql.DataSource。 | 
javax.sql.DataSource | 
driverClassName | 
JDBC驱动的完整类名。 | com.mysql.cj.jdbc.Driver | 
url | 
数据库连接URL。 | jdbc:mysql://... | 
username / password | 
数据库登录凭证。 | dbuser / dbpassword | 
maxTotal | 
连接池中允许的最大连接数。 | 20 | 
maxIdle | 
连接池中保持空闲的最大连接数。 | 10 | 
maxWaitMillis | 
当连接池耗尽时,一个请求等待获取连接的最长毫秒数。 | 10000 | 
validationQuery | 
用于验证连接是否有效的SQL语句。 | SELECT 1 | 
在web.xml中引用资源(可选但推荐)
在Web应用的WEB-INF/web.xml文件中,添加<resource-ref>标签,声明应用将要使用的外部资源,这起到了一个文档说明的作用,让应用部署者知道该应用依赖哪些外部资源。

<web-app ...>
    <!-- ... 其他配置 ... -->
    <resource-ref>
        <description>DB Connection Pool</description>
        <res-ref-name>jdbc/MyAppDB</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>
</web-app>
在Java代码中获取并使用连接
可以在Servlet或其他Java类中通过JNDI查找来获取数据源,并从中获取连接。
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class JndiConnectionExample {
    public Connection getConnection() throws SQLException {
        DataSource dataSource = null;
        try {
            // 1. 获取JNDI初始上下文
            InitialContext ctx = new InitialContext();
            // 2. 通过JNDI名称查找数据源
            // 注意:"java:comp/env/"是JNDI查找的标准环境命名上下文前缀
            dataSource = (DataSource) ctx.lookup("java:comp/env/jdbc/MyAppDB");
            // 3. 从数据源获取连接
            return dataSource.getConnection();
        } catch (NamingException e) {
            throw new SQLException("JNDI lookup failed", e);
        }
    }
}
通过这种方式,数据库连接的管理被优雅地交给了Tomcat,应用代码变得非常干净,只关注业务逻辑,而无需关心连接的创建、销毁和池化细节。
相关问答FAQs
我已经把JDBC驱动放在了项目的WEB-INF/lib目录下,为什么使用JNDI数据源时还是提示ClassNotFoundException?
解答:这是一个常见的困惑,当使用JNDI数据源时,是由Tomcat容器本身来创建和管理数据源实例的,而不是由你的Web应用程序,Tomcat容器必须能够“看到”JDBC驱动类,将驱动放在WEB-INF/lib目录下,它只对你的Web应用可见,对Tomcat容器是不可见的,正确的做法是:将数据库的JDBC驱动JAR包复制到Tomcat安装目录的lib文件夹中,这样,当Tomcat启动并解析context.xml中的<Resource>配置时,它的类加载器就能成功加载指定的driverClassName。
为什么我需要使用连接池?为每个数据库操作都创建一个新连接,然后立即关闭它,这样有什么问题吗?
解答:为每个操作都创建新连接会带来严重的性能和资源问题,建立数据库连接是一个涉及网络通信、协议握手、身份验证等多个步骤的“重”操作,耗时远超普通的SQL查询,在高并发场景下,频繁创建和销毁连接会迅速耗尽应用服务器的CPU和内存资源,并给数据库服务器带来巨大压力,导致响应时间急剧增加,数据库能同时维持的连接数是有限的,无节制的连接创建很快会耗尽数据库的连接资源,导致新的连接请求被拒绝,使整个应用不可用,连接池通过复用一组预先建立好的连接,从根本上解决了这些问题,应用从池中获取连接几乎无延迟,用完后归还,使得有限的数据库连接资源可以被高效、安全地共享,从而大幅提升应用的吞吐量和稳定性。