Mysql Java驱动代码阅读笔记及JDBC规范笔记

简介: 一前言:以前刚开始用java连接mysql时,都是连猜带蒙的。比如:一个Statement,Connection何时关闭?Connection能不能先于Statement关闭?ResultSet是怎样存放数据的?...

一前言:

以前刚开始用java连接mysql时,都是连猜带蒙的。比如:

一个Statement,Connection何时关闭?

Connection能不能先于Statement关闭?

ResultSet是怎样存放数据的?怎样才能高效操纵ResultSet?

PrepareStatement到底是怎样回事?

连接池是怎样工作的?

二、从JDBC driver代码分析:

在性能要求高的地方,应当使用ResultSet.get**(int)系列函数

如ResultSet.getBytes(String columnName),

则会先会调用findColumn(columnName)去查找到columnName对应的index是什么,而在findColumn(columnName)函数中,

会检查索引有没有构建好了,如果还没有则要构建columnName对应的索引。

所以,如果对性能要求,则应该使用ResultSet.getBytes(int column)函数。


PreparedStatement的缓存及重用

对于PreparedStatement,会有一个LRUCache来存放,会先到里面去取,拿不到再创建一个新的。

这个LRUCache的默认大小是25(太小了吧。。)。对于sql长度,如果大于256的,貌似则不缓存这个PreparedStatement。

LRUCache很简单,代码:

    public class LRUCache extends LinkedHashMap {
    protected int maxElements;
    public LRUCache(int maxSize) {
        super(maxSize);
        this.maxElements = maxSize;
    }
    protected boolean removeEldestEntry(Entry eldest) {
        return (size() > this.maxElements);
    }
}

LinkedHashMap在每次put和putAll后,会调用removeEldestEntry来判断是否要移除最老的Entry。

LinkedHashMap的实现也比较简单,里面用双向链表把所有的Entry串在一起,当调用get时,把get所在的key移到链表的最前面。

PreparedStatement是如何实现重用的:

    pStmt = (com.mysql.jdbc.ServerPreparedStatement)this.serverSideStatementCache.remove(sql);
                                                                                                                                                                                                                                                                                                                                                                                                                                                     
if (pStmt != null) {
    ((com.mysql.jdbc.ServerPreparedStatement)pStmt).setClosed(false);
    pStmt.clearParameters();
}

可见只是设置未关闭,再清除Parameters。所以从代码上来说,我们得到一个PreparedStatement在使用后,可以调用clearParameters,再接着使用。但是最好不要这样做。

如果是想要执行多次,可以用addBatch和executeBatch函数来多次执行。


关于CallableStatement和ServerPreparedStatement

CallableStatement,ServerPreparedStatement继承自PreparedStatement,实际上prepareStatement(String sql)函数返回的就是ServerPreparedStatement,LRUCache中放的也是。

CallableStatement也有一个LRUcache。

实际上当PreparedStatement调用close时,并没有真正释放掉资源,


Statement、Connection、ResultSet何时close

查看Statement的close函数代码,可以发现当close时,这个Statement中所有的ResultSet也会被close。

查看Connection的close函数,当close时,这个Connection的所有Statement都会被close。

但是据JDBC4的规范,有可能当Statement关闭时,ResultSet中的资源未被完全释放,当GC再次运行时才会回收。

所以最好就是顺序关闭ResultSet,Statement,Connection。


异常处理

SQLException是可以迭代的,应该用以下的代码来处理所有的异常:

    catch(SQLException ex) {
   while(ex != null) {
      System.out.println("SQLState:" + ex.getSQLState());
      System.out.println("Error Code:" + ex.getErrorCode());
      System.out.println("Message:" + ex.getMessage());
      Throwable t = ex.getCause();
      while(t != null) {
         System.out.println("Cause:" + t);
         t = t.getCause();
      }
      ex = ex.getNextException();
   }
}
//或者
catch(SQLException ex) {
   for(Throwable e : ex ) {
      System.out.println("Error encountered: " + e);
   }
}


在代码Connection类的很多地方,比如void closeAllOpenStatements()函数,可以看到这样的代码:

    for (int i = 0; i < numStmts; i++) {
                Statement stmt = (Statement) currentlyOpenStatements.get(i);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
                try {
                    stmt.realClose(false, true);
                } catch (SQLException sqlEx) {
                    postponedException = sqlEx; // throw it later, cleanup all
                    // statements first
                }
            }

感觉这个是有问题的,因为把一些异常信息给丢掉了,实际上是可以迭代的,应该调用setNextException函数把异常都加到一起。


统计数量count:

    stmt.execute(sql);
ResultSet resultSet = stmt.getResultSet();
resultSet.next();
int count = resultSet.getInt("count(*)");//or  getInt(1);


代码中其它一些有意思的地方:

在代码中有大量的StringBuffer,而没有用StringBuilder,可能是要兼容JDK5的原因

配置都用一个ConnectionProperty类来表示,从这里派生出子类,IntegerConnectionProperty,BooleanConnectionProperty什么的。

每一个配置有都默认值,描述信息,版本等。貌似大部分在ConnectionProperties类中实现。

发现了代码中的一个bug,没有找到找交的地方,不是很重要就算了:)

com.mysql.jdbc.ConnectionProperties类initializeFrom(String extractedValue)函数中:

    if (extractedValue.endsWith("k")
        || extractedValue.endsWith("K")
        || extractedValue.endsWith("kb")
        || extractedValue.endsWith("Kb")
        || extractedValue.endsWith("kB")) {
    multiplier = 1024;
    int indexOfK = StringUtils.indexOfIgnoreCase(
            extractedValue, "k");
    extractedValue = extractedValue.substring(0, indexOfK);
} else if (extractedValue.endsWith("m")
        || extractedValue.endsWith("M")
        || extractedValue.endsWith("G")  //这行明显是多出来的
        || extractedValue.endsWith("mb")
        || extractedValue.endsWith("Mb")
        || extractedValue.endsWith("mB")) {
    multiplier = 1024 * 1024;


三、从JDBC规范来看:

类型对照表:

Java类型

SQL类型

boolean

BIT

byte

TINYINT

short

SMALLINT

int

INTEGER

long

BIGINT

float

FLOAT

double

DOUBLE

byte[]

BINARYVARBINARYLONGBINARY

java.lang.String

CHARVARCHARLONGVARCHAR

java.math.BigDecimal

NUMERICDECIMAL

java.sql.Date

DATE

java.sql.Time

TIME

java.sql.Timestamp

TIMESTAMP

注意:

在JDBC中要表示日期,是使用java.sql.Date,其日期格式是"年、月、日";

要表示时间的话则是使用java.sql.Time,其时间格式为"时、分、秒";

如果要表示"时、分、秒、微秒"的格式,则是使用java.sql.Timestamp


连接池可能会自动关闭之前的connection!

要注意使用连接池时,据JDBC规范:

A single physical PooledConnection object may generate many logical

Connection objects during its lifetime. For a given PooledConnection object,

only the most recently produced logical Connection object will be valid. Any

previously existing Connection object is automatically closed when the associated

PooledConnection.getConnection method is called. Listeners (connection

pool managers) are not notified in this case.

This gives the application server a way to take a connection away from a client.

This is an unlikely scenario but may be useful if the application server is trying

to force an orderly shutdown.

所以之前得到的Connection有可能会失效!!

但是实际上我估计没人会按这个方案来实现,因为太不友好,怎么能别人用着你就把它悄悄地关闭掉了。

测试了proxool,当设置最大Connection数为1时,在获取第二个Connection时,会抛出个异常。


物理连接和逻辑连接

连接池中分为物理连接和逻辑连接,对应PooledConnection类和Connection类。

PooledConnection不对用户暴露,当PooledConnection调用close时才关闭物理连接。

Connection调用close时,并不真正关闭物理连接,只是把它放入池中。


Statement

Statement也分两种logical statement和physical statement。

可以实现PreparedStatement池,当Connection调用close时,并不把PreparedStatement关闭,有可能是放入到池中。

A pool of statements is associated with a PooledConnection object.

所以说一个物理连接都有一个Statement池。至于池的大小,可以通过ConnectionPoolDataSource的Properties来设置。


ConnectionPoolDataSource的属性

ConnectionPoolDataSource 有以下的标准属性:

maxStatements,initialPoolSize,minPoolSize,maxPoolSize,maxIdleTime,propertyCycle。



JDBC连接池的架构图和PreparedStatement池的架构图:








相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
负载均衡 算法 关系型数据库
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
本文聚焦 MySQL 集群架构中的负载均衡算法,阐述其重要性。详细介绍轮询、加权轮询、最少连接、加权最少连接、随机、源地址哈希等常用算法,分析各自优缺点及适用场景。并提供 Java 语言代码实现示例,助力直观理解。文章结构清晰,语言通俗易懂,对理解和应用负载均衡算法具有实用价值和参考价值。
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
|
存储 Java 关系型数据库
java调用mysql存储过程
在 Java 中调用 MySQL 存储过程主要借助 JDBC(Java Database Connectivity)。其核心原理是通过 JDBC 与 MySQL 建立连接,调用存储过程并处理结果。具体步骤包括:加载 JDBC 驱动、建立数据库连接、创建 CallableStatement 对象、设置存储过程参数并执行调用。此过程实现了 Java 程序与 MySQL 数据库的高效交互。
|
11月前
|
SQL Java 关系型数据库
Java连接MySQL数据库环境设置指南
请注意,在实际部署时应该避免将敏感信息(如用户名和密码)硬编码在源码文件里面;应该使用配置文件或者环境变量等更为安全可靠地方式管理这些信息。此外,在处理大量数据时考虑使用PreparedStatement而不是Statement可以提高性能并防止SQL注入攻击;同时也要注意正确处理异常情况,并且确保所有打开过得资源都被正确关闭释放掉以防止内存泄漏等问题发生。
476 13
|
人工智能 Java 关系型数据库
Java的时间处理与Mysql的时间查询
本文总结了Java中时间与日历的常用操作,包括时间的转换、格式化、日期加减及比较,并介绍了MySQL中按天、周、月、季度和年进行时间范围查询的方法,适用于日常开发中的时间处理需求。
235 0
|
人工智能 JavaScript 关系型数据库
【02】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-ui设计图figmaUI设计准备-figma汉化插件-mysql数据库设计-优雅草卓伊凡商业项目实战
【02】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-ui设计图figmaUI设计准备-figma汉化插件-mysql数据库设计-优雅草卓伊凡商业项目实战
583 14
【02】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-ui设计图figmaUI设计准备-figma汉化插件-mysql数据库设计-优雅草卓伊凡商业项目实战
|
存储 Java 开发者
【潜意识Java】深入详细理解分析Java中的toString()方法重写完整笔记总结,超级详细。
本文详细介绍了 Java 中 `toString()` 方法的重写技巧及其重要
1002 10
【潜意识Java】深入详细理解分析Java中的toString()方法重写完整笔记总结,超级详细。
|
前端开发 JavaScript Java
Java构建工具-maven的复习笔记【适用于复习】
这篇文档由「潜意识Java」创作,主要介绍Maven的相关知识。内容涵盖Maven的基本概念、作用、项目导入步骤、依赖管理(包括依赖配置、代码示例、总结)、依赖传递、依赖范围以及依赖的生命周期等七个方面。作者擅长前端开发,秉持“得之坦然,失之淡然”的座右铭。期待您的点赞、关注和收藏,这将是作者持续创作的动力! [个人主页](https://blog.csdn.net/weixin_73355603?spm=1000.2115.3001.5343)
511 3
|
SQL druid Java
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)(下)
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)
363 3
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)(下)
|
SQL Java 关系型数据库
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)(上)
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)
776 3
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)(上)

推荐镜像

更多