生产问题分析
文章目录
- 生产问题分析
- 什么情况会导致java.sql.SQLException: No operations allowed after statement closed.
- 什么情况会导致java.net.SocketException: Connection reset
- SpringBoot+mysql8.0默认的连接池是什么?
- 当线程池的最大线程数设置过大,队列深度过深,当请求量暴增时,会对系统带来哪些影响
- 当线程池的最大线程数设置过大,队列深度过深,当请求量暴增时,对数据库会带来哪些影响
- 线程池中的参数
- BlockingQueue<Runnable>
- BlockingQueue<Runnable>存储的是什么信息
- QUEUE
- LinkedBlockingQueue
- 数据库连接池对JDBC的封装
- JDBC案例
什么情况会导致java.sql.SQLException: No operations allowed after statement closed.
java.sql.SQLException: No operations allowed after statement closed 异常通常在Java应用程序中使用JDBC与数据库交互时出现
,它表示尝试在已经关闭的 Statement
对象上执行某个操作。
以下几种情况可能会导致这种异常:
-
手动关闭:
开发者显式调用了 Statement.close() 方法后,如果继续尝试使用这个关闭的Statement执行查询或更新操作。
-
自动资源管理:
在使用 try-with-resources 或者 Connection 的 close() 方法时,如果 Statement 是在这个范围内的,当退出 try 块时,JVM会自动关闭所有关联的资源,包括 Statement。
-
数据库驱动超时
数据库连接或者Statement的超时设置可能导致连接被服务器端强制关闭,例如MySQL中的 wait_timeout 或 interactive_timeout 达到设定值后,未活动的连接会被关闭。
-
连接池复用:
如果应用使用连接池,从池中取出的连接可能因为某种原因(如长时间空闲、连接失效)而被隐式关闭,与之相关的Statement也就无效了。
-
事务结束:
在事务提交或回滚之后,若未重新获取新的Statement而在旧的已关闭的Statement上执行操作也会抛出此异常。
-
并发问题
多线程环境下,一个线程关闭了Statement对象,而另一个线程仍在尝试使用这个已关闭的对象。
总结
为了避免这个问题,应确保在执行任何数据库操作前,Statement对象是有效且未关闭的,并遵循最佳实践,如尽量使用预编译的PreparedStatement以减少资源消耗和潜在的并发问题,以及正确处理数据库连接和相关资源的生命周期。
生产导致的问题
排查原因发现数据库域名连接ip为上海ip,数据库服务器在北京,在连接时会出现中断的情况导致连接关闭
目前解决方案需要修改数据库域名解析到北京,然后重启应用
什么情况会导致java.net.SocketException: Connection reset
java.net.SocketException: Connection reset 这个异常在Java网络编程中通常发生于TCP/IP通讯过程中,它表示套接字连接已被对方重置(reset)。
这种情况通常是由于以下几个原因造成的:
-
远程主机关闭连接:
服务器端应用程序在完成任务后,或者遇到错误需要终止连接时,可能会主动关闭Socket连接,而客户端在不知道的情况下继续尝试读取或写入数据,此时会收到Connection reset的异常。
-
网络层面的问题:
网络设备(比如路由器或防火墙)可能中断了连接,或者网络波动导致数据包丢失,这也可能触发连接重置。
-
服务器崩溃或重启:
当服务器进程突然崩溃或无序退出时,与之建立连接的客户端所持有的Socket将无法继续使用,从而产生连接重置异常。
-
超时或连接限制:
如果服务器设置了短的超时时间,客户端在规定时间内没有完成响应,服务器可能认为连接已失效并关闭它。
另外,服务器可能存在连接数限制,当达到上限时,可能会关闭一些现有连接来释放资源。
-
数据传输不完整或格式错误:
如果客户端发送的数据在协议层面上不完整或格式有误,服务器可能会拒绝接收并重置连接。
-
安全软件或防火墙干扰:
防火墙规则可能会阻止某些连接或数据传输,导致连接被意外重置。
-
操作系统内核行为:
操作系统内核在检测到半开连接(即一方已经关闭连接,但另一方尚未感知到)时,也可能选择主动重置连接。
总结
总之,java.net.SocketException: Connection reset 通常是由于对端(无论是服务器还是客户端)主动或被动地关闭了网络连接,在这种情况下,客户端尝试继续使用这个已关闭的连接时就会抛出这个异常。处理这类异常时,通常需要检查网络连接稳定性、应用程序逻辑以及两端的配置和异常处理机制。
SpringBoot+mysql8.0默认的连接池是什么?
Spring Boot 2.x 版本默认的数据库连接池是 HikariCP。这意味着如果你在Spring Boot应用中配置了MySQL 8.0作为数据源,而没有明确指定连接池实现的话,Spring Boot将会自动配置并使用HikariCP作为数据库连接池。HikariCP以其高性能和低延迟特性而著称,是许多现代Java应用框架中首选的连接池实现。
HikariCp的默认配置项
HikariCP 是一款高性能的 JDBC 连接池实现,Spring Boot 默认集成并提供了合理的默认配置。以下是 HikariCP 中一些关键的配置项及其默认值(可能因 Spring Boot 版本不同略有差异):
connectionTimeout
::等待连接池分配连接的最大时长(单位毫秒),超过这个时间还没有可用连接,则抛出异常。默认值通常为 30000 毫秒(即30秒)idleTimeout
:连接在空闲状态下的存活时间(单位毫秒),达到这个时间点后,连接如果没有被使用,将被释放。默认值可能为 600000 毫秒(即10分钟)minimumIdle
:池中允许保持的最小空闲连接数,低于这个数量时,连接池会尝试创建新连接直到达到这个数量。默认值可能是10maximumPoolSize
:连接池允许的最大连接数。默认值可能根据机器资源动态计算,或者是物理核心数的两倍autoCommit
:控制连接的自动提交模式,默认为 true。connectionTestQuery
:用于验证连接是否有效的SQL查询语句,如 SELECT 1poolName
:连接池的名字,用于识别和调试
此外,还有其他诸如 leakDetectionThreshold(泄露检测阈值)、maxLifetime(连接最大生命周期)、registerMbeans(是否注册JMX管理接口)等配置项。
实际使用时,你可以在 Spring Boot 应用程序的 application.properties 或 application.yml 文件中覆盖这些默认配置,具体的默认值请参考相应版本的 Spring Boot 和 HikariCP 文档。例如:
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.connection-timeout=30000
springboot整合dynamic-datasource-spring-boot-starter使用默认的HikariCP应该如何配置多数据源情况下的连接池的相关参数
在Spring Boot整合dynamic-datasource-spring-boot-starter实现多数据源配置时,你需要为每个数据源分别配置其对应的HikariCP连接池参数。以下是一个多数据源场景下的YAML配置示例:
# application.yml 示例配置
spring:
datasource:
# 指定HikariCP作为数据源类型,Spring Boot 2.x默认就是HikariCP,一般无需额外指定
type: com.zaxxer.hikari.HikariDataSource
# 主数据源配置
dynamic:
primary: master # 指定哪个数据源作为默认或主数据源
datasource:
master:
# HikariCP连接池配置
hikari:
jdbc-url: jdbc:mysql://localhost:3306/master_db?useSSL=false&serverTimezone=UTC
username: master_user
password: master_password
maximum-pool-size: 10
# ...其他HikariCP配置项
# 从数据源配置
slave:
hikari:
jdbc-url: jdbc:mysql://localhost:3306/slave_db?useSSL=false&serverTimezone=UTC
username: slave_user
password: slave_password
maximum-pool-size: 5
# ...其他HikariCP配置项
# 如果有更多数据源,可以按类似方式添加
# ...
- 在上面的例子中,我们定义了两个数据源:master和slave。每个数据源内部都有一个hikari配置段,用于配置各自对应的HikariCP连接池参数。
- 当然,除了上述基本的数据库连接参数外,你还可以为每个数据源独立配置HikariCP的所有其他参数,如idle-timeout、connection-timeout、max-lifetime等。
- 确保每个数据源的连接池配置都是完备且符合你的业务需求。在实际使用中,可能还需要配合dynamic-datasource-spring-boot-starter提供的数据源路由策略来实现对不同数据源的动态切换。这部分内容通常涉及到在代码中注入DynamicDataSource并利用注解或API来指定使用哪个数据源。
当线程池的最大线程数设置过大,队列深度过深,当请求量暴增时,会对系统带来哪些影响
-
内存消耗增加:
每个线程都需要一定的内存空间来保存其堆栈信息和其他线程相关数据结构。过多的线程会导致JVM占用大量内存,特别是对于那些栈内存要求较大的线程而言,内存压力更为明显。
-
CPU上下文切换开销增大:
当线程数量远大于CPU核心数时,CPU会频繁地在各个线程间进行上下文切换,这个过程是需要消耗CPU资源的。过度的上下文切换会导致CPU利用率降低,整体性能下降,尤其是对于I/O密集型任务,即使线程大部分时间在等待I/O完成,但由于线程数量庞大,上下文切换的开销仍然不可忽视。
-
系统资源争抢加剧:
超过系统承受能力的线程数量不仅消耗内存,还会加重对CPU、磁盘I/O、网络带宽等系统资源的竞争,可能导致系统整体响应速度变慢,甚至可能出现死锁或活锁现象。
-
线程调度复杂度提升:
过多的线程使得操作系统线程调度算法的负担加重,可能导致更复杂的优先级竞争,进一步降低系统的稳定性和性能。
-
线程池拒绝策略启动:
即使线程池的大小很大,且配置了任务队列,但当任务队列填满且线程数目达到最大值时,线程池的拒绝策略将生效。这可能导致新来的任务被拒绝执行,进而影响服务功能的正常提供。
-
资源耗尽风险:
若线程池设计不合理,资源管理失控,可能会耗尽系统资源,导致整个应用或系统出现卡顿甚至崩溃。例如,大量的线程可能导致操作系统的线程资源枯竭,或是JVM自身的内存溢出(OOM)。
-
响应延迟增大:
因为线程池资源有限,而请求量剧增时,更多的请求可能需要等待线程可用或队列中有空位,这样会导致请求的响应时间延长,影响用户体验。
总结
综上所述,合理配置线程池的最大线程数和队列深度是非常关键的,应结合实际硬件资源和应用场景进行优化,以确保系统在高并发环境下的稳定性和效率。通常推荐的做法是先根据系统的并发负载模型和CPU核心数来估算一个相对合理的线程数,并结合实际情况观察调整,避免资源浪费和性能瓶颈。同时,合理的任务排队策略也能有效地缓解瞬间请求高峰带来的压力。
当线程池的最大线程数设置过大,队列深度过深,当请求量暴增时,对数据库会带来哪些影响
当线程池的最大线程数设置过大,队列深度过深,且在请求量暴增时,确实会对IO操作(包括文件IO和数据库IO)产生影响,并对数据库带来如下几个方面的挑战:
-
IO资源争抢:
当大量线程并发执行IO操作时,尤其是硬盘I/O,由于磁盘的读写速率相对于内存和CPU来说较低,且具有机械特性,多个线程同时访问磁盘时,会导致磁盘头频繁移动,从而降低I/O性能。线程过多会使磁盘I/O资源的争抢更为严重,最终影响整体I/O吞吐量。
-
数据库连接池压力:
对于数据库操作来说,如果线程池中的线程都在执行数据库操作,那么数据库连接池的压力会骤增。即使数据库连接池的大小足够大,但是当线程过多且每个线程都占据着数据库连接时,会造成连接资源的过度消耗,这可能导致新的数据库请求无法获得连接,从而影响数据库操作的执行效率。
-
数据库并发处理能力极限:
数据库自身也有其并发处理能力的上限,比如锁管理、事务处理、索引查找等操作都有一定的并发处理约束。过多的并发请求可能会使数据库达到并发处理极限,导致性能下降,事务冲突增加,甚至可能出现死锁等情况。
-
数据库缓存效率降低:
数据库系统内部往往有缓冲区和缓存机制,如InnoDB引擎的缓冲池。过大的并发量可能导致缓存命中率降低,因为不同线程不断对数据库进行读写操作,可能导致缓存数据被频繁替换,影响数据库的整体性能。
-
网络拥塞:
高并发下,众多线程发起的数据库请求会集中在网络通道上,可能导致网络带宽资源不足,网络通信效率降低,从而影响数据库操作的响应速度。
-
SQL执行计划的选择:
当并发请求过于频繁时,数据库在统计信息收集、SQL解析和执行计划生成等方面可能会受到影响。由于并发执行的SQL语句可能会改变表的数据分布,可能导致数据库为某条SQL选择的执行计划不再是最优,从而影响执行效率。
总结
总结起来,过大的线程池规模和过深的工作队列在请求量暴增时,不仅会加大对IO系统的压力,还会对数据库造成严重的资源竞争和并发处理难题,影响数据库的服务质量和性能表现。因此,在设计和配置线程池时,应结合实际硬件资源、数据库性能以及业务特点综合考虑,以实现最优的资源分配和并发处理策略。
线程池中的参数
线程池是一种多线程处理形式,它是预先创建并维护一定数量的工作线程,用于执行来自任务队列的任务。Java中,java.util.concurrent.ThreadPoolExecutor 是一个标准的线程池实现类,它提供了丰富的参数来配置线程池的行为,以便有效地管理和调度工作负载。以下是 ThreadPoolExecutor 的七个关键参数及其功能详解:
-
corePoolSize(核心线程数)
这是线程池中的最小线程数,即使这些线程处于空闲状态也不会被销毁。
当提交新任务时,若当前线程数少于核心线程数,则会创建新的线程来处理任务,直到达到核心线程数为止。
可以通过设置 allowCoreThreadTimeOut 参数为 true 来让核心线程在空闲时间超过指定阈值后也能终止。
-
maximumPoolSize(最大线程数)
线程池允许同时存在的最大线程数。
当线程数已经达到核心线程数,并且工作队列也满了的情况下,线程池才会尝试创建更多的线程来处理任务,最多不超过这个数值。
如果线程数已经达到最大线程数,且队列也满了,则按照拒绝策略处理新来的任务。
-
keepAliveTime(线程存活时间)
当线程池中的线程数量超过核心线程数时,多余的线程在空闲多长时间(不包含正在等待的任务)后会被终止。
此参数与 unit 参数一起定义了非核心线程的空闲存活时间。
-
unit(时间单位)
与 keepAliveTime 结合使用,用来指定存活时间的时间单位,例如 TimeUnit.SECONDS 或 TimeUnit.MILLISECONDS。
-
workQueue(工作队列)
用于存储待处理任务的队列,通常为 BlockingQueue 类型。
常见的队列类型有无界队列(如 LinkedBlockingQueue)、有界队列(如 ArrayBlockingQueue)和优先级队列(如 PriorityBlockingQueue),不同的队列类型会影响线程池的饱和策略
-
threadFactory(线程工厂)
默认情况下,使用 Executors.defaultThreadFactory() 创建线程,但可以根据需要提供自定义的 ThreadFactory 实现。
用于创建新线程的工厂,用户可以自定义线程的创建逻辑,比如设置线程名、优先级等。
-
handler(拒绝策略)
当线程池和工作队列均无法接受新任务时,如何处理新提交的任务的策略。
四种内置拒绝策略:
- AbortPolicy:抛出 RejectedExecutionException 异常。
- CallerRunsPolicy:由调用者所在的线程执行该任务。
- DiscardPolicy:直接丢弃任务,不抛出异常也不执行。
- DiscardOldestPolicy:丢弃工作队列中最旧的一个未处理的任务,然后重新尝试执行当前任务。
BlockingQueue
java.util.concurrent.BlockingQueue 是Java并发包中的一个重要接口,它扩展了 Queue 接口,增加了阻塞式的插入和移除方法。在Java线程池(如 ThreadPoolExecutor)中,BlockingQueue 被用来存储待执行的任务(通常是以 Runnable 对象的形式),以实现生产者消费者模式。
BlockingQueue的主要特性与方法:
-
阻塞插入(put方法):
void put(E e) 方法将元素添加到队列中,如果队列已满,则当前线程将被阻塞,直到有空间可用为止。
-
阻塞移除(take方法):
E take() 方法移除并返回队列头部的元素,如果队列为空,则当前线程将被阻塞,直到有元素可用为止。
-
限时插入(offer方法):
boolean offer(E e, long timeout, TimeUnit unit) 尝试在指定的等待时间内将元素加入队列,如果在指定时间内不能加入,则返回false。
-
限时移除(poll方法):
E poll(long timeout, TimeUnit unit) 尝试在指定的等待时间内移除并返回队列头部的元素,如果在指定时间内队列仍为空,则返回null。
-
容量控制:
不同的 BlockingQueue 实现类有不同的容量控制策略,有的支持固定容量(如 ArrayBlockingQueue),有的支持无界容量(如 LinkedBlockingQueue),还有的可以根据情况进行动态调整(如 SynchronousQueue)
在Java线程池中,当线程池的线程数达到核心线程数后,新提交的任务会被放入 BlockingQueue 中等待执行。如果 BlockingQueue 已满并且线程池的线程数达到最大线程数,那么线程池会根据配置的拒绝策略来处理新提交的任务。
常用的 BlockingQueue 实现类有:
- ArrayBlockingQueue:基于数组实现的有界阻塞队列,其容量在构造时就需要确定。
- LinkedBlockingQueue:基于链表实现的阻塞队列,可以指定容量或者默认为无界队列。
- PriorityBlockingQueue:优先级阻塞队列,元素必须实现 Comparable 接口以供排序,无界。
- SynchronousQueue:特殊的无容量队列,每一个put操作必须等待一个take操作,反之亦然,主要用于传递任务而不是存储任务。
通过使用 BlockingQueue,线程池能够有效地管理和调度任务,避免了不必要的线程创建和销毁,同时也解决了多线程环境中的同步和数据交换问题。
BlockingQueue存储的是什么信息
BlockingQueue 是一个泛型化的Java集合接口 java.util.concurrent.BlockingQueue 的实例化,其中 指定队列中元素的类型。在这种情况下,BlockingQueue 存储的信息是实现了 java.lang.Runnable 接口的对象。
Runnable 是Java中用于表示线程任务的基本接口,它只有一个方法 void run(),任何实现了 Runnable 接口的类都可以作为一个可执行任务。在多线程编程中,一个实现了 Runnable 接口的对象通常代表了一个线程需要执行的工作单元。
当 BlockingQueue 被用在Java线程池(如 ThreadPoolExecutor)中时,它主要用来存放待执行的任务。线程池中的工作线程会不断地从队列中取出 Runnable 对象并执行其 run() 方法,这样就能并发地执行多个任务。
举个例子,假设有一个实现了 Runnable 的类 Task,那么我们可以创建它的实例并将其放入 BlockingQueue 中:
class Task implements Runnable {
// 实现任务的具体逻辑
@Override
public void run() {
// 这里编写任务需要执行的操作
}
}
// 创建一个BlockingQueue
BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
// 创建并提交任务到队列
Task myTask = new Task();
taskQueue.put(myTask);
在上述场景中,BlockingQueue 存储的就是像 myTask 这样的 Runnable 对象,它们代表着线程需要执行的不同工作任务。线程池中的工作线程会通过调用 BlockingQueue 的 take() 方法从队列中取出并执行这些任务。
QUEUE
Queue 是Java集合框架中的一种数据结构接口,位于 java.util 包下,它继承自 Collection 接口,并且遵循先进先出(First In First Out, FIFO)的原则。Queue接口主要用于模拟现实世界中的队列概念,即元素只能在队列的一端添加(称为“入队”或“enqueue”),而在另一端移除(称为“出队”或“dequeue”)
以下是Queue接口的主要特性:
-
基础操作:
add(E element): 添加元素到队列尾部。如果队列已满并且不支持扩容,那么将抛出 IllegalStateException。
offer(E element): 尝试将元素添加到队列尾部,如果队列已满(对于有界队列),则返回false,否则返回true。
remove(): 移除并返回队列头部的元素。如果队列为空,将抛出 NoSuchElementException。
poll(): 尝试移除并返回队列头部的元素。如果队列为空,则返回null。
element(): 返回但不移除队列头部的元素。如果队列为空,将抛出 NoSuchElementException。
peek(): 尝试返回但不移除队列头部的元素。如果队列为空,则返回null
-
阻塞操作(在 BlockingQueue 中):
put(E e): 把元素添加到队列尾部,如果队列已满,当前线程会被阻塞,直到有空间可以插入。
take(): 移除并返回队列头部的元素,如果队列为空,当前线程会被阻塞,直到有元素可取。
-
容量与边界:
队列可以是有界的也可以是无界的。ArrayBlockingQueue 和 LinkedBlockingQueue 分别提供了有界和无界队列的实现。
无界队列在理论上可以无限容纳元素,而有界队列在达到其容量限制后将不允许再添加元素(除非已有元素被移除)。
-
线程安全:
Queue接口的设计旨在支持多线程环境,其大多数实现(如 LinkedList 的线程安全版本 ConcurrentLinkedQueue 或 BlockingQueue 的各种实现)都是线程安全的,这意味着多个线程可以安全地同时对队列进行操作。
-
排序与优先级:
一般的Queue实现遵循FIFO原则,但也有例外,比如 PriorityQueue 提供了基于元素自然顺序或定制比较器的优先级排序。
-
迭代:
虽然Queue接口并未直接提供迭代器,但其子接口如 Deque 提供了迭代能力,允许遍历队列中的所有元素。
总之,Queue作为一种重要的数据结构,在多线程编程中扮演了重要角色,它帮助开发者实现生产者消费者模式,管理异步任务队列,以及其他需要先进先出处理逻辑的场景。
LinkedBlockingQueue
java.util.concurrent.LinkedBlockingQueue 是Java并发包(java.util.concurrent)中一个基于链表结构的阻塞队列实现,它具备以下主要特性:
-
线程安全:
LinkedBlockingQueue是线程安全的,这意味着多个线程可以同时安全地进行插入和删除操作,不需要额外的外部同步控制。
-
先进先出(FIFO):
它遵循先进先出(FIFO)原则,即最早放入队列的元素将首先被移除。
-
无界/有界队列:
LinkedBlockingQueue既可以作为无界队列使用(默认构造函数),也可以作为有界队列使用(通过在构造函数中传入一个容量参数)。当作为有界队列时,如果队列已满并且有线程尝试向队列中插入元素,那么这个线程将会被阻塞,直到其他线程从队列中移除元素腾出空间;当作为无界队列时,它可以无限增长,不会因为队列满而导致插入操作阻塞。
-
阻塞操作:
提供了put(E e)方法用于插入元素,当队列满时调用此方法会阻塞,直到有空间可插入。
提供了take()方法用于移除并返回队列头部的元素,当队列空时调用此方法会阻塞,直到有元素可取。
-
链表实现:
内部使用链表(LinkedList)数据结构存储元素,插入和删除操作在链表的首尾进行,这使得队列头部元素的获取和尾部元素的添加都能达到O(1)的时间复杂度。
-
Condition对象:
使用了两个内部的 java.util.concurrent.locks.Condition 对象来实现线程的唤醒和阻塞,一个用于控制插入操作,一个用于控制移除操作,这使得队列能够在恰当的时候唤醒等待的线程。
-
内存效率:
相较于基于数组实现的阻塞队列(如ArrayBlockingQueue),LinkedBlockingQueue的内存效率更高,因为它不需要预先分配固定的容量大小,可以根据实际插入的元素数量动态扩展。
-
非空/非满信号:
内部使用条件变量(notEmpty和notEmpty)来控制线程的阻塞和唤醒,当队列为空时,试图从队列中获取元素的线程将被notempty条件阻塞,直到有元素入队;当队列满时,试图往队列中插入元素的线程将被notfull条件阻塞,直到有空间可用。
总结
总结来说,LinkedBlockingQueue是一个高效的、基于链表结构的阻塞队列,适用于多线程环境下生产和消费任务的协调,尤其适合处理生产者和消费者的速度不一致的情况。
数据库连接池对JDBC的封装
数据库连接池和JDBC(Java Database Connectivity)有着密切的关系,二者共同服务于Java应用程序与数据库之间的交互。简单来说,JDBC是Java语言访问数据库的标准API,而数据库连接池是对JDBC连接管理的一种优化方案。
JDBC: JDBC是Java平台的一部分,它提供了一组接口和类,允许Java应用程序与广泛的数据库系统进行交互。通过JDBC,程序员可以编写SQL语句,执行查询、更新操作,处理结果集等。JDBC API的核心组件包括DriverManager、Connection、Statement、PreparedStatement、ResultSet等。
- DriverManager:用于加载和管理数据库驱动,通过它可以获取到数据库的连接(Connection)
- Connection:代表到数据库的物理连接,通过它可以执行SQL语句、创建Statement对象等
- Statement、PreparedStatement:用于执行SQL命令的对象,PreparedStatement支持预编译SQL语句和参数化查询
- ResultSet:存储查询结果的对象,可以从ResultSet中读取数据
在传统的JDBC使用模式下,每当需要与数据库交互时,应用程序会通过DriverManager获取一个新的数据库连接,执行完操作后再关闭这个连接。这种方式在高并发场景下会带来一些问题,比如频繁的创建和销毁连接成本高昂,同时可能导致数据库连接资源耗尽。
数据库连接池
数据库连接池: 为了解决上述问题,引入了数据库连接池的概念。连接池是一种资源管理技术,它预先创建并维护一组数据库连接,当应用程序需要执行数据库操作时,从连接池中获取一个空闲的连接,而不是每次都创建新的连接。当操作完成后,连接并不会被关闭,而是返回到连接池中以便后续复用。
在Java中,有许多第三方库提供了数据库连接池的实现,如HikariCP、Apache Commons DBCP、C3P0、Druid等。这些连接池通常基于JDBC构建,封装了创建和管理连接的过程,并提供了额外的特性,如连接池大小管理、连接回收、超时处理、监控统计等功能。
例如,使用HikariCP作为连接池时,应用程序只需要配置好连接池参数,然后通过连接池获取数据库连接,执行完操作后只需将连接归还给连接池即可,大大提高了资源利用率和系统性能。
总结
总结来说,数据库连接池是基于JDBC之上的一种优化手段,它改进了传统JDBC逐次创建和销毁数据库连接的方式,提升了应用程序与数据库交互的性能和资源管理效率
JDBC案例
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class JDBCExample {
private static final String DB_URL = "jdbc:mysql://localhost:3306/testdb";
private static final String USER = "root";
private static final String PASS = "password";
// 加载数据库驱动
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 获取数据库连接
conn = DriverManager.getConnection(DB_URL, USER, PASS);
// 插入操作
String insertSql = "INSERT INTO employees (id, name, age) VALUES (?, ?, ?)";
pstmt = conn.prepareStatement(insertSql);
pstmt.setInt(1, 1);
pstmt.setString(2, "John Doe");
pstmt.setInt(3, 30);
pstmt.executeUpdate();
// 更新操作
String updateSql = "UPDATE employees SET name = ?, age = ? WHERE id = ?";
pstmt = conn.prepareStatement(updateSql);
pstmt.setString(1, "Jane Doe");
pstmt.setInt(2, 31);
pstmt.setInt(3, 1);
pstmt.executeUpdate();
// 删除操作
String deleteSql = "DELETE FROM employees WHERE id = ?";
pstmt = conn.prepareStatement(deleteSql);
pstmt.setInt(1, 1);
pstmt.executeUpdate();
// 查询操作
String selectSql = "SELECT * FROM employees";
stmt = conn.createStatement();
rs = stmt.executeQuery(selectSql);
while (rs.next()) {
System.out.println(rs.getInt("id") + ", " + rs.getString("name") + ", " + rs.getInt("age"));
}
// 批量插入操作
List<String[]> batchData = new ArrayList<>();
batchData.add(new String[]{"2", "Alice", "25"});
batchData.add(new String[]{"3", "Bob", "30"});
String batchInsertSql = "INSERT INTO employees (id, name, age) VALUES (?, ?, ?)";
pstmt = conn.prepareStatement(batchInsertSql);
for (String[] data : batchData) {
pstmt.setInt(1, Integer.parseInt(data[0]));
pstmt.setString(2, data[1]);
pstmt.setInt(3, Integer.parseInt(data[2]));
pstmt.addBatch();
}
pstmt.executeBatch();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (pstmt != null) {
pstmt.close();
}
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
注意事项
- 请确保你的数据库中存在一个名为employees的表,并且拥有id, name, age字段。
- 以上代码片段仅供参考,实际使用时需要根据你的数据库结构和需求进行适当修改。
- 在真实项目中,为了代码的健壮性和安全性,你应该使用连接池,并且做好SQL注入防护(例如通过PreparedStatement设置参数),同时妥善处理资源关闭等问题。