Tomcat JDBC 连接池

目录

简介

JDBC 连接池 org.apache.tomcat.jdbc.poolApache Commons DBCP 连接池的替代方案。

那么为什么我们需要一个新的连接池呢?

以下是一些原因

  1. Commons DBCP 1.x 是单线程的。为了实现线程安全,Commons 在对象分配和对象返回期间都会短时间锁定整个连接池。请注意,这不适用于 Commons DBCP 2.x。
  2. Commons DBCP 1.x 可能会很慢。随着逻辑 CPU 数量的增长以及尝试借用或返回对象的并发线程数量的增加,性能会下降。对于高并发系统,影响可能很大。请注意,这不适用于 Commons DBCP 2.x。
  3. Commons DBCP 包含超过 60 个类。而 tomcat-jdbc-pool 核心只有 8 个类,因此对未来需求的修改将需要更少的更改。这只是运行连接池本身所需的全部,其余都是附加功能。
  4. Commons DBCP 使用静态接口。这意味着您必须为给定的 JRE 版本使用正确的版本,否则可能会看到 NoSuchMethodException 异常。
  5. 当一个连接池可以通过更简单的实现来完成时,重写 60 多个类是不值得的。
  6. Tomcat JDBC 连接池实现了异步检索连接的能力,而无需向库本身添加额外的线程。
  7. Tomcat JDBC 连接池是一个 Tomcat 模块,它依赖于 Tomcat JULI,一个用于 Tomcat 的简化日志框架。
  8. 使用 javax.sql.PooledConnection 接口检索底层连接。
  9. 无饥饿。如果连接池为空,并且线程正在等待连接,当连接返回时,连接池将唤醒正确的等待线程。大多数连接池会简单地导致饥饿。

相较于其他连接池实现的额外功能

  1. 支持高并发环境和多核/多 CPU 系统。
  2. 接口的动态实现,将支持您运行时环境的 java.sqljavax.sql 接口(只要您的 JDBC 驱动程序也这样做),即使使用较低版本的 JDK 编译。
  3. 验证间隔 - 我们不必在每次使用连接时都进行验证,我们可以在借用或归还连接时进行此操作,但频率不超过我们可配置的间隔。
  4. 一次性查询,一个可配置的查询,在数据库连接建立时只运行一次。对于设置您希望在连接建立的整个过程中存在的会话设置非常有用。
  5. 配置自定义拦截器的能力。这允许您编写自定义拦截器来增强功能。您可以使用拦截器收集查询统计信息、缓存会话状态、在失败时重新连接、重试查询、缓存查询结果等等。您的选择是无限的,并且拦截器是动态的,不绑定到 java.sql/javax.sql 接口的 JDK 版本。
  6. 高性能 - 我们稍后将展示一些性能差异
  7. 极其简单,由于其非常简化的实现,代码行数和源文件数量都非常少,与 c3p0 拥有超过 200 个源文件(我们上次检查时)相比,Tomcat JDBC 的核心只有 8 个文件,连接池本身大约是其一半。如果发生错误,它们将更容易追踪和修复。从一开始,减少复杂性就是重点。
  8. 异步连接检索 - 您可以排队请求连接并接收一个 Future<Connection> 返回。
  9. 更好的空闲连接处理。它不是直接关闭连接,而是仍然可以将连接放入池中,并使用更智能的算法调整空闲连接池的大小。
  10. 您可以决定何时将连接视为已废弃,是在连接池满时,还是通过指定连接池使用阈值在超时时直接废弃。
  11. 废弃连接计时器会在语句/查询活动时重置。这允许长时间使用的连接不会超时。这是通过使用 ResetAbandonedTimer 实现的。
  12. 在连接建立一定时间后关闭连接。根据连接寿命在返回连接池时关闭。
  13. 当连接被怀疑已废弃时,获取 JMX 通知和日志条目。这类似于 removeAbandonedTimeout,但它不采取任何行动,只报告信息。这是通过使用 suspectTimeout 属性实现的。
  14. 连接可以从 java.sql.Driverjavax.sql.DataSourcejavax.sql.XADataSource 中检索。这是通过使用 dataSourcedataSourceJNDI 属性实现的。
  15. XA 连接支持

如何使用

Tomcat 连接池的使用已经尽可能简化,对于熟悉 commons-dbcp 的用户来说,迁移将非常简单。从其他连接池迁移也相当直接。

附加特性

Tomcat 连接池提供了一些比大多数其他连接池更多的额外功能

  • initSQL - 在连接创建时只运行一次 SQL 语句的能力
  • validationInterval - 除了对连接进行验证外,避免过于频繁地运行验证。
  • jdbcInterceptors - 灵活且可插拔的拦截器,用于创建连接池、查询执行和结果集处理方面的任何自定义。更多信息请参阅高级部分。
  • fairQueue - 将公平标志设置为 true 以实现线程公平性或使用异步连接检索

在 Apache Tomcat 容器内部

Tomcat 连接池被配置为一个资源,详见 Tomcat JDBC 文档。唯一的区别是您必须指定 factory 属性并将其值设置为 org.apache.tomcat.jdbc.pool.DataSourceFactory

独立使用

该连接池仅有一个额外的依赖,即 tomcat-juli.jar。要在独立项目中使用 Bean 实例化来配置连接池,要实例化的 Bean 是 org.apache.tomcat.jdbc.pool.DataSource。您用于将连接池配置为 JNDI 资源的相同属性(如下所述)也用于将数据源配置为 Bean。

JMX

连接池对象公开了一个可以注册的 MBean。为了使连接池对象创建 MBean,必须将 jmxEnabled 标志设置为 true。这并不意味着连接池将注册到 MBean 服务器,而仅仅是 MBean 已被创建。在像 Tomcat 这样的容器中,Tomcat 本身会将 DataSource 注册到 MBean 服务器,然后 org.apache.tomcat.jdbc.pool.DataSource 对象将注册实际的连接池 MBean。如果您在容器外部运行,您可以将 DataSource 注册到您指定的任何对象名称下,它会将注册传播到底层连接池。要做到这一点,您需要调用 mBeanServer.registerMBean(dataSource.getPool().getJmxPool(),objectname)。在此调用之前,请确保已通过调用 dataSource.createPool() 创建了连接池。

属性

为了提供从 commons-dbcp 到 tomcat-jdbc-pool 的非常简单的切换,大多数属性都是相同的,并且具有相同的含义。

JNDI 工厂和类型

属性描述
factory

factory 是必需的,其值应为 org.apache.tomcat.jdbc.pool.DataSourceFactory

type

类型应始终为 javax.sql.DataSourcejavax.sql.XADataSource

根据类型,将创建 org.apache.tomcat.jdbc.pool.DataSourceorg.apache.tomcat.jdbc.pool.XADataSource

系统属性

系统属性是 JVM 全局的,影响在 JVM 中创建的所有连接池。

属性描述
org.apache.tomcat.jdbc.pool.onlyAttemptCurrentClassLoader

(boolean) 控制动态类(如 JDBC 驱动程序、拦截器和验证器)的类加载。如果设置为 false(默认值),连接池将首先尝试使用当前加载器(即加载连接池类的类加载器)进行加载,如果类加载失败,则尝试使用线程上下文加载器进行加载。如果您希望与 Apache Tomcat 8.0.8 及更早版本保持向后兼容,并且只尝试使用当前加载器,请将此值设置为 true。如果未设置,则默认值为 false

通用属性

这些属性在 commons-dbcp 和 tomcat-jdbc-pool 之间共享,在某些情况下默认值可能不同。

属性描述
defaultAutoCommit

(boolean) 此连接池创建的连接的默认自动提交状态。如果未设置,则默认为 JDBC 驱动程序默认值(如果未设置,则不会调用 setAutoCommit 方法)。

defaultReadOnly

(boolean) 此连接池创建的连接的默认只读状态。如果未设置,则不会调用 setReadOnly 方法。(某些驱动程序不支持只读模式,例如:Informix)

defaultTransactionIsolation

(String) 此连接池创建的连接的默认事务隔离状态。以下之一:(参见 javadoc)

  • NONE
  • READ_COMMITTED
  • READ_UNCOMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE

如果未设置,则不会调用该方法,并默认为 JDBC 驱动程序。

defaultCatalog

(String) 此连接池创建的连接的默认目录。

driverClassName

(String) 要使用的 JDBC 驱动程序的完全限定 Java 类名。该驱动程序必须与 tomcat-jdbc.jar 位于相同的类加载器中。

username

(String) 传递给 JDBC 驱动程序以建立连接的用户名。请注意,DataSource.getConnection(username,password) 方法默认不会使用传入方法的凭据,而是使用此处配置的凭据。有关详细信息,请参阅 alternateUsernameAllowed 属性。

password

(String) 传递给 JDBC 驱动程序以建立连接的密码。请注意,DataSource.getConnection(username,password) 方法默认不会使用传入方法的凭据,而是使用此处配置的凭据。有关详细信息,请参阅 alternateUsernameAllowed 属性。

maxActive

(int) 此连接池中可同时分配的最大活动连接数。默认值为 100

maxIdle

(int) 连接池中应始终保持的最大连接数。默认值为 maxActive:100。空闲连接会定期检查(如果启用),并且空闲时间超过 minEvictableIdleTimeMillis 的连接将被释放。(另请参阅 testWhileIdle

minIdle

(int) 连接池中应始终保持的最小已建立连接数。如果验证查询失败,连接池可能会缩小到低于此数量。默认值源自 initialSize:10。(另请参阅 testWhileIdle

initialSize

(int) 连接池启动时创建的初始连接数。默认值为 10

maxWait

(int) 当没有可用连接时,连接池在抛出异常之前等待连接返回的最大毫秒数。默认值为 30000 (30 秒)。

testOnBorrow

(boolean) 指示对象在从连接池借用之前是否会被验证。如果对象未能通过验证,它将从连接池中移除,我们将尝试借用另一个。为了获得更高效的验证,请参阅 validationInterval。默认值为 false

testOnConnect

(boolean) 指示对象在连接首次创建时是否会被验证。如果对象未能通过验证,将抛出 SQLException。默认值为 false

testOnReturn

(boolean) 指示对象在返回连接池之前是否会被验证。默认值为 false

testWhileIdle

(boolean) 指示对象是否将由空闲对象驱逐器(如果有)进行验证。如果对象未能通过验证,它将从连接池中移除。默认值为 false,并且必须设置此属性才能使连接池清理/测试线程运行(另请参阅 timeBetweenEvictionRunsMillis)。

validationQuery

(String) 用于在将连接返回给调用者之前验证此连接池中连接的 SQL 查询。如果指定,此查询不必返回任何数据,它只不能抛出 SQLException。默认值为 null。如果未指定,连接将通过 isValid() 方法进行验证。示例值为 SELECT 1(MySQL)、select 1 from dual(Oracle)、SELECT 1(MS SQL Server)。

validationQueryTimeout

(int) 连接验证查询失败前的超时时间(秒)。这是通过在执行 validationQuery 的语句上调用 java.sql.Statement.setQueryTimeout(seconds) 来实现的。连接池本身不会使查询超时,它仍然取决于 JDBC 驱动程序来强制执行查询超时。小于或等于零的值将禁用此功能。默认值为 -1

validatorClassName

(String) 实现 org.apache.tomcat.jdbc.pool.Validator 接口并提供无参构造函数(可能是隐式的)的类名。如果指定,该类将用于创建 Validator 实例,然后使用该实例而不是任何验证查询来验证连接。默认值为 null。示例值为 com.mycompany.project.SimpleValidator

timeBetweenEvictionRunsMillis

(int) 空闲连接验证/清理线程两次运行之间休眠的毫秒数。此值不应低于 1 秒。它决定了我们检查空闲、废弃连接的频率,以及我们验证空闲连接的频率。如果 maxAge 非零且更小,此值将被覆盖。默认值为 5000(5 秒)。

numTestsPerEvictionRun

(int) 在 tomcat-jdbc-pool 中未使用的属性。

minEvictableIdleTimeMillis

(int) 对象在池中空闲多久才符合被驱逐的最低时间量。默认值为 60000(60 秒)。

accessToUnderlyingConnectionAllowed

(boolean) 未使用的属性。可以通过在池化连接上调用 unwrap 来实现访问。参见 javax.sql.DataSource 接口,或通过反射调用 getConnection 或将对象转换为 javax.sql.PooledConnection

removeAbandoned

(boolean) 标志,用于在废弃连接超过 removeAbandonedTimeout 时将其移除。如果设置为 true,则连接在使用时间超过 removeAbandonedTimeout 时被视为废弃并符合移除条件。将其设置为 true 可以从未能关闭连接的应用程序中恢复数据库连接。另请参阅 logAbandoned。默认值为 false

removeAbandonedTimeout

(int) 废弃(使用中)连接在被移除之前的超时时间(秒)。默认值为 60(60 秒)。该值应设置为您的应用程序可能拥有的最长运行查询时间。

logAbandoned

(boolean) 标志,用于记录废弃连接的应用程序代码的堆栈跟踪。记录废弃连接会增加每次借用连接的开销,因为必须生成堆栈跟踪。默认值为 false

connectionProperties

(String) 建立新连接时将发送到 JDBC 驱动程序的连接属性。字符串格式必须为 [propertyName=property;]* 注意 - "user" 和 "password" 属性将显式传递,因此无需包含在此处。默认值为 null

poolPreparedStatements

(boolean) 未使用的属性。

maxOpenPreparedStatements

(int) 未使用的属性。

Tomcat JDBC 增强属性

属性描述
initSQL

(String) 连接首次创建时要运行的自定义查询。默认值为 null

jdbcInterceptors

(String) 一个以分号分隔的类名列表,这些类名扩展了 org.apache.tomcat.jdbc.pool.JdbcInterceptor 类。有关语法和示例的更详细说明,请参阅下面的配置 JDBC 拦截器

这些拦截器将作为拦截器插入到 java.sql.Connection 对象的操作链中。默认值为 null

预定义拦截器
org.apache.tomcat.jdbc.pool.interceptor.
ConnectionState
- 跟踪自动提交、只读、目录和事务隔离级别。
org.apache.tomcat.jdbc.pool.interceptor.
StatementFinalizer
- 跟踪所有使用 createStatementprepareStatementprepareCall 创建的语句,并在连接返回连接池时关闭这些语句。

更多预定义拦截器在JDBC 拦截器部分中有详细描述。

validationInterval

(long) 避免过度验证,最多以这个频率(毫秒)运行验证。如果连接需要验证,但在此间隔内已进行过验证,则不会再次验证。默认值为 3000(3 秒)。

jmxEnabled

(boolean) 是否向 JMX 注册连接池。默认值为 true

fairQueue

(boolean) 如果您希望对 getConnection 的调用以真正的 FIFO 方式公平处理,请设置为 true。这将使用 org.apache.tomcat.jdbc.pool.FairBlockingQueue 实现来管理空闲连接列表。默认值为 true。当您想要使用异步连接检索时,此标志是必需的。
设置此标志可确保线程按照它们到达的顺序接收连接。
在性能测试中,锁和锁等待的实现方式存在很大差异。当 fairQueue=true 时,会根据系统运行的操作系统进行决策。如果系统运行在 Linux 上(属性 os.name=Linux。要禁用此 Linux 特有的行为并仍然使用公平队列,只需在加载连接池类之前将属性 org.apache.tomcat.jdbc.pool.FairBlockingQueue.ignoreOS=true 添加到您的系统属性中。

abandonWhenPercentageFull

(int) 已废弃(超时)的连接不会被关闭和报告,除非正在使用的连接数高于 abandonWhenPercentageFull 定义的百分比。该值应在 0-100 之间。默认值为 0,这意味着连接在达到 removeAbandonedTimeout 后立即符合关闭条件。

maxAge

(long) 在重新创建连接之前保持连接的毫秒数。当从连接池借用连接时,连接池将检查是否已达到 当前时间 - 连接建立时间 > maxAge,如果达到,则在借用之前重新连接。当连接返回连接池时,连接池将检查是否已达到 当前时间 - 连接建立时间 > maxAge,如果达到,则尝试重新连接。当连接空闲且 timeBetweenEvictionRunsMillis 大于零时,连接池将定期检查是否已达到 当前时间 - 连接建立时间 > maxAge,如果达到,则尝试重新连接。将 maxAge 设置为低于 timeBetweenEvictionRunsMillis 的值将覆盖后者(因此空闲连接验证/清理将更频繁地运行)。默认值为 0,这意味着连接将保持打开状态,并且在从连接池借用、将连接返回连接池或检查空闲连接时不会进行年龄检查。

useEquals

(boolean) 如果您希望 ProxyConnection 类使用 String.equals,请将其设置为 true;如果您希望在比较方法名时使用 ==,请将其设置为 false。此属性不适用于添加的拦截器,因为它们是单独配置的。默认值为 true

suspectTimeout

(int) 超时值(秒)。默认值为 0
类似于 removeAbandonedTimeout 值,但不是将连接视为废弃并可能关闭连接,而是在 logAbandoned 设置为 true 时简单地记录警告。如果此值小于或等于 0,则不会执行可疑检查。可疑检查仅在超时值大于 0 且连接未废弃或废弃检查被禁用时进行。如果连接可疑,将记录一条 WARN 消息并发送一次 JMX 通知。

rollbackOnReturn

(boolean) 如果 autoCommit==false,则连接池可以在连接返回连接池时通过调用回滚来终止事务。默认值为 false

commitOnReturn

(boolean) 如果 autoCommit==false,则连接池可以在连接返回连接池时通过调用提交来完成事务。如果 rollbackOnReturn==true,则此属性将被忽略。默认值为 false

alternateUsernameAllowed

(boolean) 默认情况下,出于性能原因,jdbc-pool 将忽略 DataSource.getConnection(username,password) 调用,并简单地返回在全局配置的 usernamepassword 属性下先前池化的连接。

然而,连接池可以配置为允许每次请求连接时使用不同的凭据。要启用 DataSource.getConnection(username,password) 调用中描述的功能,只需将属性 alternateUsernameAllowed 设置为 true
如果您使用凭据 user1/password1 请求连接,并且该连接之前是使用不同的 user2/password2 连接的,则连接将被关闭,并使用请求的凭据重新打开。这样,连接池大小仍然在全局级别管理,而不是每个模式级别。
默认值为 false
此属性作为对 bug 50025 的增强而添加。

dataSource

(javax.sql.DataSource) 将数据源注入连接池,连接池将使用该数据源检索连接,而不是使用 java.sql.Driver 接口建立连接。当您希望池化 XA 连接或使用数据源而不是连接字符串建立的连接时,此功能非常有用。默认值为 null

dataSourceJNDI

(String) 数据源在 JNDI 中查找并用于建立数据库连接的 JNDI 名称。请参阅 dataSource 属性。默认值为 null

useDisposableConnectionFacade

(boolean) 如果您希望在连接上设置一个门面,使其在关闭后不能被重用,请将其设置为 true。这可以防止线程持有已关闭连接的引用,并对其执行查询。默认值为 true

logValidationErrors

(boolean) 将此设置为 true 可将验证阶段的错误记录到日志文件中。如果设置为 true,错误将以 SEVERE 级别记录。为了向后兼容,默认值为 false

propagateInterruptState

(boolean) 将此设置为 true 可传播已中断线程的中断状态(不清除中断状态)。为了向后兼容,默认值为 false

ignoreExceptionOnPreLoad

(boolean) 在初始化连接池时是否忽略连接创建错误的标志。如果您想在初始化连接池时忽略连接创建错误,请设置为 true。如果您想通过抛出异常来使连接池初始化失败,请设置为 false。默认值为 false

useStatementFacade

(boolean) 如果您希望包装语句以使 equals()hashCode() 方法能够在已关闭的语句上调用(如果设置了任何语句代理),请将其设置为 true。默认值为 true

高级用法

JDBC 拦截器

要查看如何使用拦截器的示例,请查看 org.apache.tomcat.jdbc.pool.interceptor.ConnectionState。这个简单的拦截器是一个包含事务隔离级别、自动提交和只读状态这三个属性的缓存,以便系统避免不必要的数据库往返。

未来将根据需要向连接池核心添加更多拦截器。欢迎贡献!

拦截器当然不仅限于 java.sql.Connection,还可以用于包装方法调用的任何结果。您可以构建查询性能分析器,在查询运行时间超过预期时提供 JMX 通知。

配置 JDBC 拦截器

JDBC 拦截器的配置通过 jdbcInterceptors 属性完成。该属性包含一个以分号分隔的类名列表。如果类名不是完全限定的,它将以 org.apache.tomcat.jdbc.pool.interceptor. 前缀。

示例
jdbcInterceptors="org.apache.tomcat.jdbc.pool.interceptor.ConnectionState; org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer"
等同于
jdbcInterceptors="ConnectionState;StatementFinalizer"

拦截器也可以有属性。拦截器的属性在类名后的括号内指定。多个属性用逗号分隔。

示例
jdbcInterceptors="ConnectionState;StatementFinalizer(useEquals=true)"

类名、属性名和值周围的额外空格字符将被忽略。

org.apache.tomcat.jdbc.pool.JdbcInterceptor

所有拦截器的抽象基类,不能被实例化。

属性描述
useEquals

(boolean) 如果您希望 ProxyConnection 类使用 String.equals,请将其设置为 true;如果您希望在比较方法名时使用 ==,请将其设置为 false。默认值为 true

org.apache.tomcat.jdbc.pool.interceptor.ConnectionState

缓存连接的以下属性:autoCommitreadOnlytransactionIsolationcatalog。这是一种性能增强,旨在避免在调用 getter 或使用已设置的值调用 setter 时与数据库进行往返。

属性描述

org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer

跟踪所有使用 createStatementprepareStatementprepareCall 创建的语句,并在连接返回连接池时关闭这些语句。

属性描述
trace

(boolean as String) 启用未关闭语句的跟踪。当启用且连接关闭但语句未关闭时,拦截器将记录所有堆栈跟踪。默认值为 false

org.apache.tomcat.jdbc.pool.interceptor.StatementCache

缓存连接上的 PreparedStatement 和/或 CallableStatement 实例。

语句按连接缓存。计数限制是针对属于同一连接池的所有连接全局计数的。一旦计数达到 max,后续语句将不会返回到缓存中,而是立即关闭。

属性描述
prepared

(boolean as String) 启用对使用 prepareStatement 调用创建的 PreparedStatement 实例的缓存。默认值为 true

callable

(boolean as String) 启用对使用 prepareCall 调用创建的 CallableStatement 实例的缓存。默认值为 false

max

(int as String) 连接池中缓存语句数量的限制。默认值为 50

org.apache.tomcat.jdbc.pool.interceptor.StatementDecoratorInterceptor

参见 48392。用于包装语句和结果集的拦截器,以防止使用 ResultSet.getStatement().getConnection()Statement.getConnection() 方法访问实际连接。

属性描述

org.apache.tomcat.jdbc.pool.interceptor.QueryTimeoutInterceptor

当创建新语句时,自动调用 java.sql.Statement.setQueryTimeout(seconds)。连接池本身不会使查询超时,它仍然取决于 JDBC 驱动程序来强制执行查询超时。

属性描述
queryTimeout

(int as String) 查询超时设置的秒数。小于或等于零的值将禁用此功能。默认值为 1 秒。

org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReport

跟踪查询性能,并在查询超过时间阈值或失败时发出日志条目。使用的日志级别是 WARN

属性描述
threshold

(int as String) 查询在发出日志警报之前必须超过的毫秒数。默认值为 1000 毫秒。

maxQueries

(int as String) 为节省内存空间而跟踪的最大查询数量。小于或等于 0 的值将禁用此功能。默认值为 1000

logSlow

(boolean as String) 如果您希望记录慢查询,请设置为 true。默认值为 true

logFailed

(boolean as String) 如果您希望记录失败的查询,请设置为 true。默认值为 false

org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReportJmx

扩展 SlowQueryReport,除了日志条目外,它还发出 JMX 通知供监控工具响应。继承其父类的所有属性。此类使用 Tomcat 的 JMX 引擎,因此它在 Tomcat 容器外部无法工作。默认情况下,如果启用,JMX 通知通过 ConnectionPool mbean 发送。如果 notifyPool=falseSlowQueryReportJmx 也可以注册 MBean。

属性描述
notifyPool

(boolean as String) 如果您希望 JMX 通知发送到 SlowQueryReportJmx MBean,请设置为 false。默认值为 true

objectName

(String) 定义一个有效的 javax.management.ObjectName 字符串,该字符串将用于向平台 MBean 服务器注册此对象。默认值为 null,对象将使用 tomcat.jdbc:type=org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReportJmx,name=the-name-of-the-pool 进行注册。

org.apache.tomcat.jdbc.pool.interceptor.ResetAbandonedTimer

废弃计时器在连接从连接池中取出时启动。这意味着,如果您有一个 30 秒的超时时间,并且使用该连接运行 10 个 10 秒的查询,则该连接将被标记为废弃,并可能根据 abandonWhenPercentageFull 属性被回收。使用此拦截器,您每次对连接执行操作或成功执行查询时,都会重置取出计时器。

属性描述

代码示例

Tomcat 配置 JDBC 用法的其他示例可以在 Tomcat 文档中找到。

普通 Java

这是一个创建和使用数据源的简单示例。

  import java.sql.Connection;
  import java.sql.ResultSet;
  import java.sql.Statement;

  import org.apache.tomcat.jdbc.pool.DataSource;
  import org.apache.tomcat.jdbc.pool.PoolProperties;

  public class SimplePOJOExample {

      public static void main(String[] args) throws Exception {
          PoolProperties p = new PoolProperties();
          p.setUrl("jdbc:mysql://localhost:3306/mysql");
          p.setDriverClassName("com.mysql.jdbc.Driver");
          p.setUsername("root");
          p.setPassword("password");
          p.setJmxEnabled(true);
          p.setTestWhileIdle(false);
          p.setTestOnBorrow(true);
          p.setValidationQuery("SELECT 1");
          p.setTestOnReturn(false);
          p.setValidationInterval(30000);
          p.setTimeBetweenEvictionRunsMillis(30000);
          p.setMaxActive(100);
          p.setInitialSize(10);
          p.setMaxWait(10000);
          p.setRemoveAbandonedTimeout(60);
          p.setMinEvictableIdleTimeMillis(30000);
          p.setMinIdle(10);
          p.setLogAbandoned(true);
          p.setRemoveAbandoned(true);
          p.setJdbcInterceptors(
            "org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;"+
            "org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer");
          DataSource datasource = new DataSource();
          datasource.setPoolProperties(p);

          Connection con = null;
          try {
            con = datasource.getConnection();
            Statement st = con.createStatement();
            ResultSet rs = st.executeQuery("select * from user");
            int cnt = 1;
            while (rs.next()) {
                System.out.println((cnt++)+". Host:" +rs.getString("Host")+
                  " User:"+rs.getString("User")+" Password:"+rs.getString("Password"));
            }
            rs.close();
            st.close();
          } finally {
            if (con!=null) try {con.close();}catch (Exception ignore) {}
          }
      }

  }

作为资源

这是一个如何为 JNDI 查找配置资源的示例。

<Resource name="jdbc/TestDB"
          auth="Container"
          type="javax.sql.DataSource"
          factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
          testWhileIdle="true"
          testOnBorrow="true"
          testOnReturn="false"
          validationQuery="SELECT 1"
          validationInterval="30000"
          timeBetweenEvictionRunsMillis="30000"
          maxActive="100"
          minIdle="10"
          maxWait="10000"
          initialSize="10"
          removeAbandonedTimeout="60"
          removeAbandoned="true"
          logAbandoned="true"
          minEvictableIdleTimeMillis="30000"
          jmxEnabled="true"
          jdbcInterceptors="org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;
            org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer"
          username="root"
          password="password"
          driverClassName="com.mysql.jdbc.Driver"
          url="jdbc:mysql://localhost:3306/mysql"/>

异步连接检索

Tomcat JDBC 连接池支持异步连接检索,而无需向连接池库添加额外的线程。它通过向数据源添加一个名为 Future<Connection> getConnectionAsync() 的方法来实现。为了使用异步检索,必须满足两个条件:

  1. 您必须将 fairQueue 属性配置为 true
  2. 您必须将数据源转换为 org.apache.tomcat.jdbc.pool.DataSource
使用异步功能的示例如下所示。
  Connection con = null;
  try {
    Future<Connection> future = datasource.getConnectionAsync();
    while (!future.isDone()) {
      System.out.println("Connection is not yet available. Do some background work");
      try {
        Thread.sleep(100); //simulate work
      }catch (InterruptedException x) {
        Thread.currentThread().interrupt();
      }
    }
    con = future.get(); //should return instantly
    Statement st = con.createStatement();
    ResultSet rs = st.executeQuery("select * from user");

拦截器

拦截器是一种强大的方式,可以启用、禁用或修改特定连接或其子组件上的功能。拦截器有许多不同的用例。默认情况下,出于性能原因,连接池是无状态的。连接池本身插入的唯一状态是 defaultAutoCommitdefaultReadOnlydefaultTransactionIsolationdefaultCatalog(如果设置)。这 4 个属性仅在连接创建时设置。如果这些属性在连接使用期间被修改,连接池本身不会重置它们。

拦截器必须扩展 org.apache.tomcat.jdbc.pool.JdbcInterceptor 类。这个类相当简单,您需要有一个无参构造函数。

  public JdbcInterceptor() {
  }

当连接从连接池中借用时,拦截器可以通过实现以下方法来初始化或以其他方式响应此事件:

  public abstract void reset(ConnectionPool parent, PooledConnection con);

方法。此方法在调用时带有两个参数,一个是连接池本身的引用 ConnectionPool parent,另一个是底层连接的引用 PooledConnection con

当调用 java.sql.Connection 对象上的方法时,它将导致

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

方法被调用。Method method 是实际调用的方法,Object[] args 是参数。来看一个非常简单的例子,我们演示如何使对 java.sql.Connection.close() 的调用在连接已关闭时变为无操作:

  if (CLOSE_VAL==method.getName()) {
      if (isClosed()) return null; //noop for already closed.
  }
  return super.invoke(proxy,method,args);

这里有一个观察。它是方法名的比较。一种方法是使用 "close".equals(method.getName())。上面我们看到方法名和 static final String 引用之间的直接引用比较。根据 JVM 规范,方法名和静态最终字符串最终位于共享常量池中,因此引用比较应该有效。当然,也可以这样做:

  if (compare(CLOSE_VAL,method)) {
      if (isClosed()) return null; //noop for already closed.
  }
  return super.invoke(proxy,method,args);

compare(String,Method) 将使用拦截器上的 useEquals 标志,并在设置 useEquals=true 标志时进行引用比较或字符串值比较。

连接池启动/停止
当连接池启动或关闭时,您可以收到通知。您每个拦截器类只会收到一次通知,即使它是一个实例方法。您将使用当前未附加到连接池的拦截器收到通知。

  public void poolStarted(ConnectionPool pool) {
  }

  public void poolClosed(ConnectionPool pool) {
  }

覆盖这些方法时,如果您扩展的类不是 JdbcInterceptor,请不要忘记调用 super。

配置拦截器
拦截器通过 jdbcInterceptors 属性或 setJdbcInterceptors 方法进行配置。拦截器可以有属性,并且会像这样进行配置:

  String jdbcInterceptors=
    "org.apache.tomcat.jdbc.pool.interceptor.ConnectionState(useEquals=true,fast=yes)"

拦截器属性
由于拦截器可以有属性,您需要在拦截器内部读取这些属性的值。以上述示例为例,您可以覆盖 setProperties 方法。

  public void setProperties(Map<String, InterceptorProperty> properties) {
     super.setProperties(properties);
     final String myprop = "myprop";
     InterceptorProperty p1 = properties.get(myprop);
     if (p1!=null) {
         setMyprop(Long.parseLong(p1.getValue()));
     }
  }

获取实际的 JDBC 连接

连接池围绕实际连接创建包装器,以便正确地对其进行池化。我们还在这些包装器中创建拦截器,以便能够执行某些功能。如果需要检索实际连接,可以使用 javax.sql.PooledConnection 接口来实现。

  Connection con = datasource.getConnection();
  Connection actual = ((javax.sql.PooledConnection)con).getConnection();

构建

我们使用 1.6 构建 JDBC 连接池代码,但它在运行时环境上向后兼容到 1.5。对于单元测试,我们使用 1.6 及更高版本。

Tomcat 配置 JDBC 用法的其他示例可以在 Tomcat 文档中找到。

从源代码构建

构建非常简单。连接池依赖于 tomcat-juli.jar,如果您需要 SlowQueryReportJmx

  javac -classpath tomcat-juli.jar \
        -d . \
        org/apache/tomcat/jdbc/pool/*.java \
        org/apache/tomcat/jdbc/pool/interceptor/*.java \
        org/apache/tomcat/jdbc/pool/jmx/*.java

构建文件可以在 Tomcat 源代码仓库中找到。

为方便起见,还包含了一个构建文件,其中一个简单的构建命令将生成所有需要的文件。

  ant download  (downloads dependencies)
  ant build     (compiles and generates .jar files)
  ant dist      (creates a release package)
  ant test      (runs tests, expects a test database to be setup)

该系统是为 Maven 构建而设计的,但确实会生成发布工件。仅仅是库本身。