什么是JDBC
JDBC,Java DataBase Connectivity,Java数据库连接。
是Java官方定义的一套操作所有关系型数据库的规则(接口)。
各个数据库厂商去实现这套接口,提供数据库驱动JAR包。
另一个角度,在《MySQL从入门到实践:4.结构》 ,我们提到了Connectors
(客户端),JDBC属于MySQL的Connectors
(客户端)的一部分(但这个描述没有体现JDBC的特点)。
用法
快速入门
步骤
注册驱动,即指定需要连接哪一个客户端,示例代码:1 Class.forName("com.mysql.cj.jdbc.Driver" );
在这里,我们使用了反射,将一个类加载进内存。
获取连接,客户端与MySQL服务端建立连接,示例代码:1 Connection conn = DriverManager.getConnection(url, username, password);
编写SQL语句,示例代码:
获取执行SQL的对象,示例代码:1 Statement stmt = conn.createStatement();
执行SQL的对象,不是说这个对象被SQL执行,而是说这个对象主动的去执行SQL。
执行SQL,即发送给数据库服务端,示例代码:1 stmt.executeUpdate(sql);
处理返回结果
释放资源1 2 stmt.close(); conn.close();
例子
接下来,我们来看一个具体的例子。
准备工作
新建项目 我们创建一个新的空项目,并为该项目指定名称、目录以及JDK版本。
新建模块
点击右上角,选择Project Structure
,新建模块。
导入驱动包
在新建模块之后,在模块上点击右键,新建一个Dictionary。
用于存放第三方的JAR包的目录,一般取名为lib
或者libs
。 访问地址:https://mvnrepository.com/artifact/mysql/mysql-connector-java
点击箭头所指处,下载JAR包:
将下载的JAR包,复制到lib目录中,右键点击JAR包,Add a Library
。
此时有三个选项,分别是:
Global Library
:全局有效Project Library
:项目有效Module Library
:模块有效在这里,我们选择Module Library
。
在本文,我们的JDBC版本是8.0.29
。
题外话,如果我们想从Library中移除某个包: 右键,选择Open Module Setting
、Dependencies
,然后移除。
编写代码
接下来,就是编写代码了,在src
目录下,点击新建Java Class
。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.kakawanyifan;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import java.sql.Statement;public class JdbcTest { public static void main (String[] args) throws ClassNotFoundException, SQLException { Class.forName("com.mysql.cj.jdbc.Driver" ); String url = "jdbc:mysql://127.0.0.1:3306/test_jdbc" ; String username = "root" ; String password = "MySQL@2022" ; Connection conn = DriverManager.getConnection(url, username, password); String sql = "update a set a2 = 22 where a1 = 2;" ; Statement stmt = conn.createStatement(); int count = stmt.executeUpdate(sql); System.out.println(count); stmt.close(); conn.close(); } }
运行结果:
在注册驱动的代码中,我们写的是Class.forName("com.mysql.cj.jdbc.Driver");
,如果将com.mysql.cj.jdbc.Driver
改成com.mysql.jdbc.Driver
的话,还会有如下的提示:
1 Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
注意:The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
,驱动不用自动注册。
接下来,我们依次介绍上述例子中的各种组件。
DriverManager
DriverManager,驱动管理,主要作用:
注册驱动
获取数据库连接
注册驱动
注册驱动有四种方式:
Class.forName("com.mysql.cj.jdbc.Driver");
我们上文采用的是就是这种方法。
System.setProperty("jdbc.drivers","com.mysql.cj.jdbc.Driver");
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
自动注册,在5版本之后的JAR包中,JDBC可以根据数据库连接的URL,自动注册驱动了,所以上述我们注册驱动的代码也可以不写。
获取数据库连接
在上文中,我们获取数据库连接的代码是:
1 2 3 4 String url = "jdbc:mysql://127.0.0.1:3306/test_jdbc" ; String username = "root" ; String password = "MySQL@2022" ; Connection conn = DriverManager.getConnection(url, username, password);
即,我们是通过连接字符串,用户名,密码来获取数据库的连接对象的,方法如下:
1 Connection getConnection (String url, String user, String password)
解释一下URL
部分,格式如下:
1 jdbc:mysql://【IP或域名】:【端口号】/【数据库名称】?【参数键值对1】&【参数键值对2】...
如果数据库出现乱码,可以指定参数characterEncoding=utf8
,表示让数据库以UTF-8
编码来处理数据。例如:
1 jdbc:mysql://localhost:3306/test_jdbc?characterEncoding=utf8
除了上述方法,我们还可以通过连接字符串,属性对象来得到连接对象,方法如下:
1 Connection getConnection (String url, Properties info)
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.kakawanyifan;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import java.util.Properties;public class JdbcTest { public static void main (String[] args) throws SQLException { String url = "jdbc:mysql://127.0.0.1:3306/test_jdbc" ; String username = "root" ; String password = "MySQL@2022" ; Properties info = new Properties(); info.setProperty("user" ,username); info.setProperty("password" ,password); Connection conn = DriverManager.getConnection(url, info); System.out.println(conn); conn.close(); } }
运行结果:
1 com.mysql.cj.jdbc.ConnectionImpl@53ca01a2
Connection
Connection,数据库连接对象,主要作用:
获取执行SQL的对象
管理事务
获取执行SQL对象
有三种执行SQL的对象:
普通执行SQL对象,方法如下:1 Statement createStatement ()
在上文,我们用的就是这种。
预编译SQL的执行SQL对象1 PreparedStatement prepareStatement (sql)
通过这种方法,可以防止SQL注入,我们会在下文做更多的讨论。
执行存储过程的对象1 CallableStatement prepareCall (sql)
管理事务
在《MySQL从入门到实践:6.事务》 ,我们讨论过事务,最基本的操作有;
开启事务:BEGIN;
或者START TRANSACTION;
提交事务:COMMIT;
回滚事务:ROLLBACK;
在Connection
中,有对应的方法:
开启事务:1 setAutoCommit(boolean autoCommit)
autoCommit
为true
表示自动提交事务,false
表示手动提交事务,开启事务的方法为将该参数设为false
。
提交事务
回滚事务
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package com.kakawanyifan;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import java.sql.Statement;public class JdbcTest { public static void main (String[] args) throws SQLException { String url = "jdbc:mysql://127.0.0.1:3306/test_jdbc" ; String username = "root" ; String password = "MySQL@2022" ; Connection conn = DriverManager.getConnection(url, username, password); String sql1 = "update a set a2 = 100 where a1 = 1" ; String sql2 = "update a set a2 = 200 where a1 = 2" ; Statement stmt = conn.createStatement(); try { conn.setAutoCommit(false ); int count1 = stmt.executeUpdate(sql1); System.out.println(count1); int i = 3 /0 ; int count2 = stmt.executeUpdate(sql2); System.out.println(count2); conn.commit(); } catch (Exception e) { conn.rollback(); e.printStackTrace(); } stmt.close(); conn.close(); } }
运行结果:
1 2 3 1 java.lang.ArithmeticException: / by zero at com.kakawanyifan.JdbcTest.main(JdbcTest.java:30)
Statement
Statement对象,用来执行SQL语句,对于不同类型的SQL语句,执行方法也不一样。
执行DDL、DML语句:1 statement.executeUpdate(【SQL】)
返回内容是int
类型,表示受影响条数。
执行DQL语句:1 statement.executeQuery(【SQL】)
返回内容是ResultSet
类型。
ResultSet
ResultSet,结果集对象,主要方法有:
boolean next()
,将"光标"移动到下一行,并判断是否为有效行:true
,有效行,即当前行有数据;false
,无效行,当前行没有数据。
close()
,关闭,释放资源
getXxx()
,获取数据,有两种类型的入参:int
,列的编号(从1开始
);String
,列的名称。
SQL类型
JDBC对应方法
返回类型
BIT(1)
getBoolean()
boolean
TINYINT
getByte()
byte
SMALLINT
getShort()
short
INT
getInt()
int
BIGINT
getLong()
long
CHAR
、VARCHAR
getString()
String
Text
、Clob
getClob()
Clob
Blob
getBlob()
Blob
DATE
getDate()
java.sql.Date
(只代表日期)
TIME
getTime()
java.sql.Time
(只表示时间)
TIMESTAMP
getTimestamp()
java.sql.Timestamp
(同时有日期和时间)
java.sql.Date
、java.sql.Time
、java.sql.Timestamp
,三个共同父类是java.util.Date
。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package com.kakawanyifan;import java.sql.*;public class JdbcTest { public static void main (String[] args) throws SQLException { String url = "jdbc:mysql://127.0.0.1:3306/test_jdbc" ; String username = "root" ; String password = "MySQL@2022" ; Connection conn = DriverManager.getConnection(url, username, password); String sql = "select * from a" ; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); while (rs.next()){ int a1 = rs.getInt(1 ); System.out.println("int a1 = rs.getInt(1); " + a1); int a2 = rs.getInt(2 ); System.out.println("int a2 = rs.getInt(2); " + a2); String a1Str = rs.getString(1 ); System.out.println("String a1Str = rs.getString(1); " + a1Str); String a2Str = rs.getString(2 ); System.out.println("String a2Str = rs.getString(2); " + a2Str); String a3 = rs.getString(3 ); System.out.println("String a3 = rs.getString(3); " + a3); double a4 = rs.getDouble(4 ); System.out.println("double a4 = rs.getDouble(4); " + a4); a1 = rs.getInt("a1" ); System.out.println("a1 = rs.getInt(\"a1\"); " + a1); a2 = rs.getInt("a2" ); System.out.println("a2 = rs.getInt(\"a2\"); " + a2); a1Str = rs.getString("a1" ); System.out.println("a1Str = rs.getString(\"a1\"); " + a1Str); a2Str = rs.getString("a2" ); System.out.println("a2Str = rs.getString(\"a2\"); " + a2Str); a3 = rs.getString("a3" ); System.out.println("a3 = rs.getString(\"a3\"); " + a3); a4 = rs.getDouble("a4" ); System.out.println("a4 = rs.getDouble(\"a4\"); " + a4); } rs.close(); stmt.close(); conn.close(); } }
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int a1 = rs.getInt(1); 1 int a2 = rs.getInt(2); 11 String a1Str = rs.getString(1); 1 String a2Str = rs.getString(2); 11 String a3 = rs.getString(3); abc double a4 = rs.getDouble(4); 2.71 a1 = rs.getInt("a1"); 1 a2 = rs.getInt("a2"); 11 a1Str = rs.getString("a1"); 1 a2Str = rs.getString("a2"); 11 a3 = rs.getString("a3"); abc a4 = rs.getDouble("a4"); 2.71 int a1 = rs.getInt(1); 2 int a2 = rs.getInt(2); 22 String a1Str = rs.getString(1); 2 String a2Str = rs.getString(2); 22 String a3 = rs.getString(3); xyz double a4 = rs.getDouble(4); 3.14 a1 = rs.getInt("a1"); 2 a2 = rs.getInt("a2"); 22 a1Str = rs.getString("a1"); 2 a2Str = rs.getString("a2"); 22 a3 = rs.getString("a3"); xyz a4 = rs.getDouble("a4"); 3.14
在上文的代码的释放资源中,有三个行:
rs.close()
,关闭ResultSet。
stmt.close()
,关闭Statement。
conn.close()
,关闭Connection。
解释一下:
关闭Statement(PreparedStatement,CallableStatement)对象时,该Statement(PreparedStatement,CallableStatement)对象所创建的ResultSet对象,也会被关闭;关闭Connection对象时,该Connection对象所创建的Statement对象,也会被关闭。
建议在一个对象不再使用的时候,主动关闭。
PreparedStatement
作用:
什么是SQL注入
SQL注入,一种攻击方法。
例如,用户登录校验,我们需要去数据库的表中,比对一下用户名和密码是否匹配,所以在程序中,可能会有类似于如下的SQL,示例代码:
1 SELECT * FROM account WHERE name = '【用户名】' AND password = '【密码】'
但,如果用户输入的密码,以'OR '1' = '1
结尾的话,此时的SQL如下:
1 SELECT * FROM account WHERE name = '【用户名】' AND password = '【密码】' OR '1' = '1'
或者,用户输入的密码以';DROP TABLE account;
结尾的,表都可能会被删除。
例如,密码是随便'or '1' = '1
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.kakawanyifan;import java.sql.*;public class JdbcTest { public static void main (String[] args) throws SQLException { String url = "jdbc:mysql://127.0.0.1:3306/test_jdbc" ; String username = "root" ; String password = "MySQL@2022" ; Connection conn = DriverManager.getConnection(url, username, password); String name = "q" ; String pwd = "随便'or '1' = '1" ; String sql = "select * from account where name = '" + name + "' and password = '" + pwd + "'" ; Statement statement = conn.createStatement(); ResultSet rs = statement.executeQuery(sql); if (rs.next()){ System.out.println("登录:成功" ); }else { System.out.println("登录:失败" ); } rs.close(); statement.close(); conn.close(); } }
运行结果:
防止SQL注入的操作
获取PreparedStatement对象,SQL语句中的参数值,使用?
占位符替代。
1 2 3 4 String sql = "select * from account where name = ? and password = ?" ; PreparedStatement pstmt = conn.prepareStatement(sql);
设置参数值
参数1
,位置编号,从1
开始;参数2
:值。
相关方法有:setDouble
、setFloat
、setInt
、setLong
、setObject
、setString
。
执行SQL
executeUpdate()
:执行DDL和DML
executeQuery()
:执行DQL
注意:调用这两个方法时不需要传参SQL语句。
同样,密码是随便'or '1' = '1
,再试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.kakawanyifan;import java.sql.*;public class JdbcTest { public static void main (String[] args) throws SQLException { String url = "jdbc:mysql://127.0.0.1:3306/test_jdbc" ; String username = "root" ; String password = "MySQL@2022" ; Connection conn = DriverManager.getConnection(url, username, password); String name = "q" ; String pwd = "随便'or '1' = '1" ; String sql = "select * from account where name = ? and password = ?" ; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1 ,name); pstmt.setString(2 ,pwd); ResultSet rs = pstmt.executeQuery(); if (rs.next()){ System.out.println("登录:成功" ); }else { System.out.println("登录:失败" ); } rs.close(); pstmt.close(); conn.close(); } }
运行结果:
防止SQL注入的原理
为了查看PreparedStatement的原理,我们需要开启MySQL的general_log(普通日志)。
查看general_log(普通日志)是否已经被开启,示例代码:
1 show variables like '%general%' ;
运行结果:
1 2 3 4 5 6 +----------------+-------------------------------------------+ |Variable_name |Value | +----------------+-------------------------------------------+ |general_log |OFF | |general_log_file|/usr/local/mysql/data/Kakas-MacBook-Pro.log| +----------------+-------------------------------------------+
开启general_log:set global general_log=on;
关闭general_log:set global general_log=off;
这种方法修改的general_log
,会在重启后失效,我们还可以通过修改配置文件的方式持久化。
我们先试试"Statement",观察日志,如下:
1 2 3 4 5 6 2022-06-07T15:48:41.480522Z 15 Connect root@localhost on test_jdbc using SSL/TLS 2022-06-07T15:48:41.483873Z 15 Query /* mysql-connector-java-8.0.29 (Revision: dd61577595edad45c398af508cf91ad26fc4144f) 【部分日志内容略】 2022-06-07T15:48:41.493203Z 15 Query SET character_set_results = NULL 2022-06-07T15:48:41.494117Z 15 Query SET autocommit=1 2022-06-07T15:48:41.505015Z 15 Query select * from account where name = 'q' and password = '随便'or '1' = '1' 2022-06-07T15:48:41.516186Z 15 Quit
再用"PreparedStatement",当输入随便'or '1' = '1
时:
1 2 3 4 5 6 2022-06-07T15:50:06.345176Z 16 Connect root@localhost on test_jdbc using SSL/TLS 2022-06-07T15:50:06.349145Z 16 Query /* mysql-connector-java-8.0.29 (Revision: dd61577595edad45c398af508cf91ad26fc4144f) 【部分日志内容略】 2022-06-07T15:50:06.357397Z 16 Query SET character_set_results = NULL 2022-06-07T15:50:06.357864Z 16 Query SET autocommit=1 2022-06-07T15:50:06.372828Z 16 Query select * from account where name = 'q' and password = '随便''or ''1'' = ''1' 2022-06-07T15:50:06.384482Z 16 Quit
用"PreparedStatement",当输入随便';DROP TABLE account;
时:
1 2 3 4 5 6 2022-06-07T23:29:22.429634Z 26 Connect root@localhost on test_jdbc using SSL/TLS 2022-06-07T23:29:22.432921Z 26 Query /* mysql-connector-java-8.0.29 (Revision: dd61577595edad45c398af508cf91ad26fc4144f) 【部分日志内容略】 2022-06-07T23:29:22.442774Z 26 Query SET character_set_results = NULL 2022-06-07T23:29:22.443217Z 26 Query SET autocommit=1 2022-06-07T23:29:22.459962Z 26 Query select * from account where name = 'q' and password = '随便'';DROP TABLE account;' 2022-06-07T23:29:22.470754Z 26 Quit
解释说明:被转义了。
这就是防止SQL注入的原理:对特殊字符进行转义 。
预编译
PreparedStatement的另一个作用,预编译SQL,这样效率能更高。
需要在URL中加上如下参数,以开启预编译功能
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package com.kakawanyifan;import java.sql.*;public class JdbcTest { public static void main (String[] args) throws SQLException { String url = "jdbc:mysql://127.0.0.1:3306/test_jdbc?useServerPrepStmts=true" ; String username = "root" ; String password = "MySQL@2022" ; Connection conn = DriverManager.getConnection(url, username, password); String sql = "select * from account where name = ? and password = ?" ; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1 ,"1" ); pstmt.setString(2 ,"2" ); ResultSet rs = pstmt.executeQuery(); pstmt.setString(1 ,"11" ); pstmt.setString(2 ,"22" ); rs = pstmt.executeQuery(); pstmt.setString(1 ,"111" ); pstmt.setString(2 ,"222" ); rs = pstmt.executeQuery(); pstmt.setString(1 ,"1111" ); pstmt.setString(2 ,"2222" ); rs = pstmt.executeQuery(); rs.close(); pstmt.close(); conn.close(); } }
运行结果:
1 2 3 4 5 6 7 8 9 10 11 2022-06-07T23:37:01.245012Z 27 Connect root@localhost on test_jdbc using SSL/TLS 2022-06-07T23:37:01.249296Z 27 Query /* mysql-connector-java-8.0.29 (Revision: dd61577595edad45c398af508cf91ad26fc4144f) */SELECT @@session.auto_increment_increment AS auto_increment_increment, @@character_set_client AS character_set_client, @@character_set_connection AS character_set_connection, @@character_set_results AS character_set_results, @@character_set_server AS character_set_server, @@collation_server AS collation_server, @@collation_connection AS collation_connection, @@init_connect AS init_connect, @@interactive_timeout AS interactive_timeout, @@license AS license, @@lower_case_table_names AS lower_case_table_names, @@max_allowed_packet AS max_allowed_packet, @@net_write_timeout AS net_write_timeout, @@performance_schema AS performance_schema, @@sql_mode AS sql_mode, @@system_time_zone AS system_time_zone, @@time_zone AS time_zone, @@transaction_isolation AS transaction_isolation, @@wait_timeout AS wait_timeout 2022-06-07T23:37:01.257040Z 27 Query SET character_set_results = NULL 2022-06-07T23:37:01.257493Z 27 Query SET autocommit=1 2022-06-07T23:37:01.272136Z 27 Prepare select * from account where name = ? and password = ? 2022-06-07T23:37:01.276161Z 27 Execute select * from account where name = '1' and password = '2' 2022-06-07T23:37:01.287837Z 27 Execute select * from account where name = '11' and password = '22' 2022-06-07T23:37:01.288373Z 27 Execute select * from account where name = '111' and password = '222' 2022-06-07T23:37:01.288779Z 27 Execute select * from account where name = '1111' and password = '2222' 2022-06-07T23:37:01.289232Z 27 Close stmt 2022-06-07T23:37:01.289321Z 27 Quit
连接池
概述
结合我们在本章以及《MySQL从入门到实践:4.结构》 的讨论,我们梳理一下SQL执行过程中的各种"通信"。
我们看到,各种连接和认证,占据了大多数的通信,真正在执行SQL和返回结果的,却是少数。如果能连上之后,可以多次执行SQL,是不是可以节约资源?
这就是连接池。
连接池:分配和管理数据库连接。
通常当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。
标准接口:DataSource
,在javax.sql
下的。(之前JDBC相关的都在java.sql
下)。
方法:
获取连接:getConnection()
归还连接:Connection.close()
,如果Connection对象是从连接池中获取的,那么调用Connection.close()
方法,不会关闭连接,而是归还连接。
当然,一般我们不会去实现这些接口,有开源的连接池,例如:
DBCP
C3P0
Druid
HikariCP
其中DBCP
现在不常用了;C3P0
性能不佳;Druid
是最常用的,而且支持监控功能;HikariCP
是后起之秀,性能强劲。
在本文,我们只讨论Druid
和HikariCP
。
Druid
Druid,由阿里巴巴提供的,使用方法如下:
下载并导入JAR包:druid-1.2.10.jar
(下载和导入方法与上文MySQL的驱动包类似,不赘述)
定义配置文件
properties形式
,可以叫任意名称,可以放在任意目录下。
加载配置文件
获取数据库连接池对象DruidDataSourceFactory
。
获取连接getConnection
首先,我们定义配置文件。
在这里,我们将配置文件放在src
目录下,src
目录是类的加载目录,在下文我们通过DruidDemo.class.getClassLoader()
方法定位到这个目录。
配置文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 url =jdbc:mysql://127.0.0.1:3306/test_jdbc?useServerPrepStmts=true username =root password =MySQL@2022 initialSize =5 maxActive =10 maxWait =3000
部分配置参数解释:
url
,连接数据库的URL,例如
username
,连接数据库的用户名
password
,连接数据库的密码
driverClassName
:驱动名称,默认值会根据URL自动识别
minIdle
:最小连接池数量
initialSize
:初始化时建立连接的个数,默认值0
maxActive
:最大连接数量,默认值8
maxWait
:获取连接时最大等待时间,单位毫秒。配置了maxWait
之后,会默认启用公平锁,这样并发效率会有所下降,但能保证整体性能稳定,可以通过配置useUnfairLock
为true
使用非公平锁。
minEvictableIdleTimeMillis
:连接保持空闲而不被驱逐的最小时间,默认是30分钟,在生产环境中,如果我们想尽快释放,可以将该值调小。
更多的参数配置,可以参考Druid的官方文档:https://github.com/alibaba/druid/wiki/DruidDataSource配置属性列表
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.kakawanyifan;import com.alibaba.druid.pool.DruidDataSourceFactory;import javax.sql.DataSource;import java.io.InputStream;import java.sql.Connection;import java.util.Properties;public class DruidDemo { public static void main (String[] args) throws Exception { Properties prop = new Properties(); InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties"); prop.load(is); DataSource dataSource = DruidDataSourceFactory.createDataSource(prop); Connection connection = dataSource.getConnection(); System.out.println(connection); } }
运行结果:
1 com.mysql.cj.jdbc.ConnectionImpl@20deea7f
特别的,我们还可以建立一个工具类,示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 package com.kakawanyifan;import com.alibaba.druid.pool.DruidDataSourceFactory;import javax.sql.DataSource;import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;import java.util.Properties;public class DruidPoolUtil { private static DataSource ds ; static { try { Properties pro = new Properties(); pro.load(DruidPoolUtil.class.getClassLoader().getResourceAsStream("druid.properties")); ds = DruidDataSourceFactory.createDataSource(pro); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection () throws SQLException { return ds.getConnection(); } public static void close (Statement stmt, Connection conn) { close(null ,stmt,conn); } public static void close (ResultSet rs , Statement stmt, Connection conn) { if (null != rs) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (null != stmt) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (null != conn) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } public static DataSource getDataSource () { return ds; } }
工具类的使用方法,示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.kakawanyifan;import java.sql.Connection;import java.sql.SQLException;import java.sql.Statement;public class DruidPoolDemo { public static void main (String[] args) throws SQLException { Connection connection = DruidPoolUtil.getConnection(); System.out.println(connection); Statement statement = connection.createStatement(); System.out.println(statement); DruidPoolUtil.close(statement,connection); } }
运行结果:
1 2 com.mysql.cj.jdbc.ConnectionImpl@20deea7f com.mysql.cj.jdbc.StatementImpl@3835c46
HikariCP
需要JAR包HikariCP-4.0.3.jar
。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.kakawanyifan;import com.zaxxer.hikari.HikariConfig;import com.zaxxer.hikari.HikariDataSource;import java.sql.Connection;import java.sql.SQLException;public class HikariCPDemo { public static void main (String[] args) throws SQLException { HikariConfig hikariConfig = new HikariConfig(); hikariConfig.setJdbcUrl("jdbc:mysql://8.130.9.218:3306/j" ); hikariConfig.setUsername("root" ); hikariConfig.setPassword("MySQL@2022" ); HikariDataSource ds = new HikariDataSource(hikariConfig); Connection connection = ds.getConnection(); System.out.println(connection); connection.close(); } }
运行结果:
1 HikariProxyConnection@1014166943 wrapping com.mysql.cj.jdbc.ConnectionImpl@60dcc9fe
除了上述的配置方法,还可以直接利用Properties
、propertyFileName
。
如果我们得到如下的报错:
1 2 3 4 java: cannot access com.zaxxer.hikari.HikariConfig bad class file: /Users/kaka/Documents/j/jdc/jdbc-demo/lib/HikariCP-5.0.1.jar!/com/zaxxer/hikari/HikariConfig.class class file has wrong version 55.0, should be 52.0 Please remove or make sure it appears in the correct subdirectory of the classpath.
是因为JDK的版本不对,Java 8 is version 52.0 and Java 11 is version 55.0。
Java 11+ maven artifact:
1 2 3 4 5 <dependency > <groupId > com.zaxxer</groupId > <artifactId > HikariCP</artifactId > <version > 5.0.1</version > </dependency >
Java 8 maven artifact (maintenance mode):
1 2 3 4 5 <dependency > <groupId > com.zaxxer</groupId > <artifactId > HikariCP</artifactId > <version > 4.0.3</version > </dependency >
更多的"HikariCP
的不同JDK
版本",在如下的地址有描述
https://github.com/brettwooldridge/HikariCP
此外,关于更多的配置及其作用,同样可以参考上述的地址。
Spring JDBC
Spring JDBC
,Spring框架对JDBC的简单封装,提供了一个JDBCTemplate对象简化JDBC的开发,我们不用再关注资源的获取、释放和归还,只需要专注于业务。
步骤:
导入JAR包:spring-core-5.3.20.jar
、spring-beans-5.3.20.jar
、spring-jdbc-5.3.20.jar
、spring-tx-5.3.20.jar
以及commons-logging-1.2.jar
。
创建JdbcTemplate对象,入参是DataSource对象1 JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
方法:
update()
:执行DML语句,增、删、改。
queryForMap()
:查询结果,将结果集封装为一个Map集合,列名作为Key,值作为Value。(结果集长度只能是1)。
queryForList()
:查询结果,将结果集封装为List集合,将每一条记录封装为一个Map集合,再将Map集合装载到List集合中。
query()
:查询结果,将结果封装为List集合,集合的元素是JavaBean对象
我们可以通过重写RowMapper
的方法进行封装,也可以通过利用new BeanPropertyRowMapper<类型>(类型.class)
完成自动封装。
queryForObject
:查询结果,将结果封装为一个JavaBean对象。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 package com.kakawanyifan;import org.junit.Test;import org.springframework.jdbc.core.BeanPropertyRowMapper;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.RowMapper;import java.sql.ResultSet;import java.sql.SQLException;import java.util.List;import java.util.Map;public class JdbcTemplateDemo { JdbcTemplate jdbcTemplate = new JdbcTemplate(DruidPoolUtil.getDataSource()); @Test public void test_update_1 () { String sql = "update a set a2 = ? where a1 = ?" ; int count = jdbcTemplate.update(sql,"101" ,1 ); System.out.println(count); } @Test public void test_update_2 () { String sql = "insert into a(a1,a2,a3,a4) values(?,?,?,?)" ; int count = jdbcTemplate.update(sql, 3 ,33 , "三十三" , 3.33 ); System.out.println(count); } @Test public void test_queryForMap () { String sql = "select * from a where a1 = ?" ; Map<String, Object> map = jdbcTemplate.queryForMap(sql, 1 ); System.out.println(map); } @Test public void test_queryForList () { String sql = "select * from a" ; List<Map<String, Object>> list = jdbcTemplate.queryForList(sql); System.out.println(list); } @Test public void test_query_mapRow () { String sql = "select * from a" ; List<A> list = jdbcTemplate.query(sql, new RowMapper<A>() { public A mapRow (ResultSet rs, int i) throws SQLException { A a = new A(); a.setA1(rs.getInt("a1" )); a.setA2(rs.getInt("a2" )); a.setA3(rs.getString("a3" )); a.setA4(rs.getDouble("a4" )); return a; } }); System.out.println(list); } @Test public void test_query_BeanPropertyRowMapper () { String sql = "select * from a" ; List<A> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<A>(A.class )) ; System.out.println(list); } @Test public void test_queryForObject () { String sql = "select count(*) from a" ; Long total = jdbcTemplate.queryForObject(sql, Long.class ) ; System.out.println(total); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.kakawanyifan;public class A { int a1; int a2; String a3; double a4; 【Getter和Setter代码略】 @Override public String toString () { return "A{" + "a1=" + a1 + ", a2=" + a2 + ", a3='" + a3 + '\'' + ", a4=" + a4 + '}' ; } }
关于BeanPropertyRowMapper
的自动绑定,再讨论一下:
SQL中的user
会被自动绑定到JavaBean的user
。
SQL中的user_name
会被自动绑定到JavaBean的userName
。
SQL中的列名不区分大小写。