Appearance
JDBC(Java数据库连接)概述
JDBC(Java DataBase Connectivity, java数据库连接)是一种用于执行 SQL 语句的 Java API。JDBC 是 Java访问数据库的标准规范,作用是可以为不同的关系型数据库提供统一访问方式,它由一组用Java语言编写的接口和类组成。具体的实现类由各大数据库厂商来编写。
JDBC 需要连接驱动,驱动是两个设备要进行通信,满足一定通信数据格式,数据格式由设备提供商规定,设备提供商为设备提供驱动软件,通过软件可以与该设备进行通信。

MySQL 数据库 JDBC 相关资源
MySQL 驱动官网下载地址:https://dev.mysql.com/downloads/connector/j/
相关文件夹说明:
- src 文件夹是源代码
- docs 文件夹是 API
JDBC 开发
JDBC 执行流程

使用 JDBC 连接数据库的四个参数:
- 用户名
- 密码
- 连接字符串:jdbc:mysql://localhost:3306/数据库?参数名=参数值
- 数据库驱动类,如 MySQL数据库驱动是
com.mysql.jdbc.Driver,8.0版本后驱动类是com.mysql.cj.jdbc.Driver
JDBC 开发步骤(以 MySQL 为例)
- 加载并注册数据库驱动,即告知JVM使用的是哪一个数据库的驱动,通常使用反射技术
Class.forName()方法将驱动类加入到内存中。
java
Class.forName("com.mysql.jdbc.Driver")- 通过
DriverManager连接数据库并获得连接对象Connection
java
Connection conn = DriverManager.getConnection(url, user, password);使用JDBC中的类,完成对MySQL数据库的连接
- 获得语句执行平台,通过
Connection对象的createStatement()方法获得Statement对象。Statement对象就是对SQL语句的执行者对象。 - 调用
Statement对象的excuteXxx(String sql)方法发送要执行的SQL语句,并获得执行后数据库返回结果。 - 处理结果,通过
ResultSet的next()和getXxx(索引/字段名)方法获得数据库返回的结果集。 - 必须关闭释放数据库资源,使用
close()方法。关闭原则:后开的先关(ResultSet->Statement->Connection)
注:一般开发会使用
PreparedStatement来代替父类Statement,使用效率和安全性都更高。JDBC 访问数据库的基础包,在 JavaSE 中的包。导包注意要选择java.sql包
项目(程序)引入驱动 jar 包(MySQL 的驱动)
- MySQL 相关jar包:mysql-connector-java-x.x.xx-bin.jar
- MySQL驱动相关文件夹mysql-connector-java-x.x.xx
- src文件夹是源代码,docs文件夹是API
- MySQL 官方驱动下载地址:https://dev.mysql.com/downloads/connector/j/
- MySQL maven 依赖xml
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>x.x.xx</version> </dependency>
JDBC 的核心 API 使用
核心 API 概述
Driver接口:数据库驱动的接口,由数据库的厂商实现。DriverManager类:用来管理和注册数据库驱动的类Connection接口:数据库连接对象。Statement/PrepareStatement接口:操作数据库 SQL 语句的对象ResultSet接口:用来封装数据库返回符合查询条件的结果集
Driver 接口
每个驱动程序都应该提供一个实现 Driver 接口的类。DriverManager 会试着加载尽可能多的它可以找到的驱动程序,这意味着用户可以通过调用以下程序加载和注册一个驱动程序类对象。
java
Class.forName("foo.bah.Driver");DriverManager 类
类作用
java.sql.DriverManager 类用于管理和注册一组 JDBC 驱动程序的基本服务。
DriverManager 是一个工厂类,通过它来创建数据库连接。当 JDBC 的 Driver 类被加载进来时,它会自己注册到 DriverManager 类里面,然后会把数据库配置信息传成 DriverManager.getConnection() 方法,DriverManager 会使用注册到它里面的驱动来获取数据库连接,并返回给调用的程序。
注册驱动程序方法
java
public static void registerDriver(new Driver());注册数据库驱动的静态方法
java
DriverManager.registerDriver(new com.mysql.jdbc.Driver());使用该方法注册驱动会触发驱动注册两次,一般不推荐使用
正确的注册驱动使用下面的方式:一般使用 Class 类的 forName("类全名") 注册驱动,可以解决注册两次的问题
java
Class.forName("com.mysql.jdbc.Driver");注:从JDK1.6开始,JDBC的版本是4.0版本后,可以不用写注册驱动的代码了,但是为了兼容之前的版本,注册驱动的代码一般都会保留。
类常用方法
获取连接对象方式1:通过指定 url(需要连接的数据库地址),用户名和密码得到一个 Connection 接口的实现类。用于连接数据库并获得连接对象。
java
public static Connection getConnection(String url, String user, String password) throws SQLException;参数说明:
url: 连接数据库字符串 urluser: 用户名,比如:rootpassword: 密码,注册时候的密码。比如:123456
获取连接对象方式2:通过一个属性集和 URL,得到一个数据库的连接。实际开发中比较常用
java
public static Connection getConnection(String url, Properties info) throws SQLException参数说明:
url: 连接数据库字符串urlinfo: 属性集合,用来存储用户名和密码
一般 properties 属性文件存放在 src 文件夹中,先获取当前类的
Class对象,再使用Class类的getResourceAsStream("/文件名")得到输入流对象,再使用load()读取文件
Code Demo: 使用 Properties 属性文件获取 Connection 对象
java
// 创建Properties集合
Properties info = new Properties();
// 加载文件数据到info中
info.load(ConnectionDemo.class.getResourceAsStream("/jdbc.properties"));
// 连接数据库获得连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://10.211.55.3:3306/tempDb", info);连接数据库的 URL 地址格式
URL 用于标识数据库的位置,程序员通过 URL 地址告诉 JDBC 程序连接哪个数据库,
url 地址格式
格式:
jdbc协议名:子协议://数据库服务器地址或 IP 地址:端口号/数据库名示例:
java
jdbc:mysql://localhost:3306/test?参数名:参数值
// MySQL写法:
jdbc:mysql://localhost:3306/temp
// 简写格式(前提:数据库服务器地址是本机,端口号默认是3306)
jdbc:mysql:///temp各类数据库连接字符串
JDBC 的 URL = 协议名 + 子协议名 + 数据源名
- 协议名总是
jdbc。 - 子协议名由 JDBC 驱动程序的编写者决定。
- 数据源名也可能包含用户与口令等信息;这些信息也可单独提供。
几种常见的数据库连接
Oracle
- 驱动:
oracle.jdbc.driver.OracleDriver - URL:
jdbc:oracle:thin:@machine_name:port:dbnamemachine_name:数据库所在的机器的名称;port:端口号,默认是1521
MySQL
- 驱动:
com.mysql.jdbc.Driver - URL:
jdbc:mysql://machine_name:port/dbnamemachine_name:数据库所在的机器的名称;port:端口号,默认3306
SQL Server
- 驱动:
com.microsoft.jdbc.sqlserver.SQLServerDriver - URL:
jdbc:microsoft:sqlserver://<machine_name><:port>;DatabaseName=<dbname>machine_name:数据库所在的机器的名称;port:端口号,默认是1433
DB2
- 驱动:
com.ibm.db2.jdbc.app.DB2Driver - URL:
jdbc:db2://<machine_name><:port>/dbnamemachine_name:数据库所在的机器的名称;port:端口号,默认是5000
解决 JDBC 无法连接 MySQL 数据库的问题
MySQL 数据连接时只能使用 localhost 连接,但不能用 IP 连接问题的解决方案
- 打开 cmd 窗口,进入M ySQL 安装的 bin 目录
- 执行命令登录数据库,之后会出现一行要你输入密码的
mysql -u root -p - 执行以下命令分配新用户:
sql
# (%) 表示所有ip
grant all privileges on *.* to 'root'@'%' identified by 'root';- 执行完上述命令后用下面的命令刷新权限
sql
flush privileges;- 之后关闭mysql服务,然后启动mysql服务,大功告成
mysql 8.0+以上版本驱动连接失败
使用 mysql 8.0+ 版本后,一些项目连接数据会报以下的错误:
java.sql.SQLNonTransientConnectionException:Public key Retrieval is not allowed…原因(参考官网给出的连接选项):如果用户使用了 sha256_password 认证,密码在传输过程中必须使用 TLS 协议保护,但是如果 RSA 公钥不可用,可以使用服务器提供的公钥。可以在连接中通过 ServerRSAPublicKeyFile 指定服务器的 RSA 公钥,或者 allowPublicKeyRetrieval=true 参数以允许客户端从服务器获取公钥。但是需要注意的是,该参数可能会导致恶意的代理通过中间人攻击(MITM)获取到明文密码,所以默认是关闭的,必须显式开启。所以可以用 mysql_native_password,不要用 sha256_password 方式,就不会有问题了。
此时需要修改数据库的连接,增加如下配置:
useSSL=falseMySQL 8.0 以上版本不需要建立 SSL 连接的,需要显示关闭allowPublicKeyRetrieval=true允许客户端从服务器获取公钥。serverTimezone=UTC设置时区,mysql驱动8.0+也要指定时区,不然也会报一些错
yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/test_db?characterEncoding=utf-8&useSSL=false&useUnicode=true&allowPublicKeyRetrieval=true&serverTimezone=UTC
driverClassName: com.mysql.cj.jdbc.Driver
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource # 设置使用的数据源类型Notes: 如果使用的 mysql 是 8.0+,建议最好把以上三个参数设置下,避免发生一些莫名错误。
Connection 接口
接口作用
用来和数据库建立连接,和获取 Statement 对象
常用方法
java
Statement createStatement() throws SQLException;- 创建一个
Statement接口现实类对象来将 SQL 语句发送到数据库。
java
PreparedStatement prepareStatement(String sql);- 创建一个
PreparedStatement对象来将参数化的 SQL 语句发送到数据库。sql:需要执行的SQL语句,获取PreparedStatement对象后直接使用方法executeUpdate()或executeQuery()进行操作。
java
void close() throws SQLException- 立即释放此
Connection对象的数据库和 JDBC 资源,建议最好在调用close方法之前,应用程序显式提交或回滚一个活动事务。
Statement 接口(操作数据库)
接口概述
用来发送SQL语句给数据库执行,对数据库进行增删改查操作。通过 Connection 对象的 createStatement() 方法获取 Statement 对象。
java
public interface Statement extends Wrapper, AutoCloseableCRUD(增删改查操作):
- C:
Create - R:
Read - U:
Update - D:
Delete
常用方法
java
boolean execute(String sql) throws SQLException;
boolean execute(String sql, int autoGeneratedKeys) throws SQLException;
boolean execute(String sql, int columnIndexes[]) throws SQLException;
boolean execute(String sql, String columnNames[]) throws SQLException;- 可以执行任意的 SQL 语句。如果查询的结果是一个
ResultSet对象,此方法返回 true;如果结果不是ResultSet(比如 insert 或者 update 等语句),此方法则会返回 false。可以通过接口的getResultSet()方法来获取 ResultSet 结结果对象,或者通过getUpdateCount()方法来获取更新的记录条数。
java
int executeUpdate(String sql) throws SQLException;- 只能执行数据库的SQL语句中的
insert、delete、update、drop、create用于数据库的增删改的操作等 DML、DDL 语句。方法参数与返回值解析如下:sql:表示语句,返回执行成功的数据表行数。- 可以执行数据定义语言『DDL(Data Definition Language)』。用于创建,修改,删除数据库、表、列等。如:
create,alter,drop等。使用DDL语句不影响行数,即返回值为0。 - 可以数据操作语言『DML(Data Manipulation Language)』。用来修改、删除、添加数据。如:
insert、delete、update。使用DML语句会影响行数,会返回影响行数
建议:一般使用操作数据库的语句,先在图形化客户端运行判断是否有错,再写到 IDE
java
ResultSet executeQuery(String sql) throws SQLException;- 用于数据库SQL语句中的
select查询操作,返回一个符合条件的结果集,是ResultSet接口的现实类。即使查询不到记录返回的ResultSet也不会为 null。如果方法传入参数是 insert 或者 update 语句的话,它会抛出错误信息为 “executeQuery method can not be used for update” 的java.util.SQLException。
java
void close() throws SQLException;- 立即释放此
Statement对象的数据库和 JDBC 资源,而不是等待该对象自动关闭时发生此操作。一般来说,使用完后立即释放资源是一个好习惯,这样可以避免对数据库资源的占用。
注:关闭
Statement对象时,还将同时关闭其当前的ResultSet对象(如果有)。
ResultSet 接口(查询/处理结果)
接口概述
java
public interface ResultSet extends Wrapper, AutoCloseable通过 Statement 的 executeQuery() 方法查询数据库后会返回一个 ResultSet 对象。java.sql.ResultSet 是一个接口,其作用是用来封装符合查询条件的记录,提供用于从当前行获取列值的方法,可以指定列的索引编号或列的名称。
ResultSet 对象维护了一个游标,指向当前的数据行。可以将 ResultSet 理解成一根指针,开始的时候这个游标默认指向位置在执行查询记录的第一行之前,如果调用了 ResultSet 的 next() 方法游标会下移一行;如果没有更多的数据时,next() 方法会返回 false。因此可以在 for 循环中用它来遍历数据集。
默认的 ResultSet 是不能更新的,游标也只能往下移,即只能从第一行到最后一行遍历一遍。不过也可以创建可以回滚或者可更新的 ResultSet。
值得注意的是,当生成 ResultSet 的 Statement 对象要关闭或者重新执行或是获取下一个 ResultSet 的时候,之前的 ResultSet 对象也会自动关闭。
常用方法
java
boolean next() throws SQLException;ResultSet光标最初位于第一行之前。第一次调用next方法使第一行成为当前行;第二次调用使第二行成为当前行,依此类推。如果新的当前行有效,则返回true;如果不存在下一行,则返回false。
java
Xxx getXxx(列索引号或列字段名称);- 可以指定列的索引编号或列的名称,XXX 表示数据类型。列号默认从1开始。如:
getString(1);、getInt("name");
java
void close() throws SQLException;- 即释放此
ResultSet对象的数据库和 JDBC 资源
getXxx 方法注意事项
- 如果指针在指向结果集第一行前面时调用
resultSet.getXX()获取列值,会抛出异常:Before start of result set - 如果指针在指向结果集最后一行后面时,调用
resultSet.getXX()获取列值,会抛出异常:After end of result set - 如果查询结果集为空,还调用
resultSet.getXX()获取列值,会抛出异常:Illegal operation on empty result set - 使用完毕以后要关闭结果集
ResultSet,再关闭Statement,再关闭Connection
常用数据类型转换表
| SQL 类型 | JDBC 对应方法 | 返回类型 |
|---|---|---|
| BIT(1) bit(n) | getBoolean getBytes() | Boolean byte[] |
| TINYINT | getByte() | Byte |
| SMALLINT | getShort() | Short |
| Int | getInt() | Int |
| BIGINT | getLong() | Long |
| CHAR,VARCHAR,LONGVARCHAR | getString() | String |
| Text(clob) Blob | getClob getBlob() | Clob Blob |
| DATE | getDate() | java.sql.Date |
| TIME | getTime() | java.sql.Time |
| TIMESTAMP | getTimestamp() | java.sql.Timestamp |
查询乱码的问题:如果汉字查询出现乱码,要将数据库的字符集设置成 UTF-8,因为多数 IDE 默认使用 utf-8 的编码
PreparedStatement 接口
SQL 注入的概念
SQL 注入是指,用户输入的内容作为了 SQL 语法的一部分,改变了原有 SQL 语句的含义。
接口概述
用于 SQL 语句的发送,是继承 Statement 的子接口,拥有父类所有的功能。可以防止 SQL 注入的问题,比父类更安全。然后可以使用此对象多次高效地执行该语句。
在使用 Connection 的方法得到 PreparedStatement 对象时,已经定义数据库操作的语句。
实际开发时使用
PreparedStatement比较多
常用方法
java
int executeUpdate() throws SQLException;用于增删改的操作,返回影响的行数 在此 PreparedStatement 对象中执行 SQL 语句
java
ResultSet executeQuery() throws SQLException;用于查询,返回结果集。在此 PreparedStatement 对象中执行 SQL 查询,并返回该查询结果集封装成的 ResultSet 对象。
java
void setXxx(int index, Xxx xxx);将指定参数值 xxx 赋值给第 index 个占位符 ? 。再将此值发送到数据库时,驱动程序将它转换成一个 SQL Xxx类型值。
预编译
数据库在接收到 SQL 语句后,需要对词法和语义解析,优化 SQL 语句,制定执行计划等一系列处理,这需要花费一些时间。如果一条 SQL 语句需要反复执行,每次都进行语法检查和优化,显然会造成性能与时间的浪费。
SQL 预编译,指的是数据库驱动在发送 SQL 语句和参数给 DBMS 之前对 SQL 语句进行编译,这样 DBMS 执行 SQL 时,就不需要重新编译,一次编译、多次运行,省去解析优化等过程。
JDBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译。具体做法是将 SQl 语句中的值用占位符替代,即SQL 语句模板化。预编译阶段可以优化 SQL 的执行,预编译之后的 SQL 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的 SQL,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作;同时预编译语句对象可以重复利用,把一个 SQL 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个SQL,可以直接使用这个缓存的对象。
预编译的作用小结:
- 预编译阶段可以优化 sql 的执行。预编译之后的 sql 多数情况下可以直接执行,数据库服务器不需要再次编译,可以提升性能。
- 预编译语句对象可以重复利用。将一个 sql 预编译后产生的
PreparedStatement对象缓存下来,下次对于同一个 sql,可以直接使用这个缓存的对象。 - 防止 SQL 注入。使用预编译,对注入的参数将不会再进行 SQL 编译。即后面注入进来的参数系统将不会认为它会是一条 SQL 语句,而默认其是一个参数。
Statement 和 PreparedStatement 的区别
- 安全性
PreparedStatement可以防止 SQL 注入问题,安全Statement有 SQL注入 的隐患问题,不安全
- 预编译功能
PreparedStatement有预编译的功能,在创建对象的时候就提供了 SQL 语句并存储在PreparedStatement对象中。在真正执行前,才把参数传递给 SQL 语句,并且可以反复执行。Statement没有预编译的功能,创建的时候没有 SQL 语句,执行的时候才提供 SQL 语句
- 缓存
PreparedStatement有缓存功能,使用缓存可以提升运行速度。Statement没有缓存功能
- 效率
PreparedStatement效率高,因为数据库系统对相同的 SQL 语句不会再次编译Statement效率低
PreparedStatement 使用步骤
- 准备要执行的SQL语句,使用
?临时代替真实的参数 - 调用
Connection对象的preparedStatement()方法创建PreparedStatement对象,并传递SQL语句 - 调用
PreparedStatement对象的setXxx(int index, Xxx value)给占位符?使用真实的参数赋值,?参数序号,从 1 开始 - 调用
PreparedStatement对象的executeUpdate()/executeQuery()方法,执行SQL语句,不需要再次传递SQL语句。
注:要先使用 setXxx() 方法给占位符 ? 赋值后再调用 executeXxx() 方法
释放资源
- 需要释放的对象和顺序:
ResultSet->Statement->Connection Connection对象释放原则:尽可能晚的打开,尽可能早的释放。因为Connection资源比较占用内存的,而且使用频率高,是公共的,稀缺的资源。- 释放资源的操作放在
finally语句块中
API 示例
使用 executeUpdate 执行 DML 操作数据库练习代码
Code Demo: 给数据库insert、update、delete create、drop等操作
java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
/*
* 3.1 使用 JDBC 的开发步骤:
* 1. 注册并加载数据库驱动 Class.forName()
* 2. 连接数据库,获取连接对象 Connection con = DriverManager.getConnection();
* 3. 创建 Statement对象 Statement stmt = conn.createStatement();
* 4. 使用 Statement对象发送 SQL
* 5. 处理结果
* 6. 关闭连接,后开的先关。
*/
public class MoonZero {
public static void main(String[] args) throws Exception {
// 注册数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 准备连接数据库字符串
String url = "jdbc:mysql://localhost:3306/tempDb";
String uName = "root";
String pwd = "123456";
// 连接数据库,获取Connection对象
Connection con = DriverManager.getConnection(url, uName, pwd);
// 获取Statement对象
Statement sta = con.createStatement();
// 使用Statement对象进行DDL和DML操作,定义一个sql操作语句字符串
String sql;
// 创建表
// sql = "create table testss(id int primary key,tname varchar(20) not null);";
// 执行后line=0
// 删除数据表
// sql = "drop table testss;";
// 执行后line=0
// 进行insert操作
// sql = "insert into testss values(1,'试试');";
// 执行后line=1
// sql = "insert into testss values(2,'再试试'), (3,'最后一插');";
// 执行后line=2
// 进行delete操作
// sql = "delete from testss where id = 1;";
// 执行后line=1
// 进行update操作
sql = "update testss set tname='用eclipse改一下' where id = 3;";
// 执行后line=1
operateTable(sta, sql);
// 关闭资源,先开后关
sta.close();
con.close();
}
// 测试创建数据表方法
public static void operateTable(Statement sta, String sql) throws SQLException {
// 执行后line=0
int line = sta.executeUpdate(sql);
System.out.println(line);
}
}解析 executeQuery 返回的数据库结果集练习代码
Code Demo: 解析 executeQuery 返回的数据库结果集(执行 DQL 操作)
java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class MoonZero {
public static void main(String[] args) throws Exception {
// 数据库注册
Class.forName("com.mysql.jdbc.Driver");
// 获取数据库Connetion对象
String url = "jdbc:mysql://localhost:3306/tempDb";
String user = "root";
String pwd = "123456";
Connection conn = DriverManager.getConnection(url, user, pwd);
// 获得语句执行平台,通过数据库连接对象,获取到SQL语句执行者对象
Statement stat = conn.createStatement();
// 准备sql语句
String sql = "select * from dept";
// 获取查询返回的select查询结果集
ResultSet rs = stat.executeQuery(sql);
// 使用循环解析返回的所有内容
while (rs.next()) {
System.out.print(rs.getInt("id") + " ");
System.out.println(rs.getString("name"));
}
// 关闭资源
rs.close();
stat.close();
conn.close();
}
}自定义数据库工具类练习
Code Demo: 自定义原生JDBC数据库工具类,用来创建连接和关闭资源,用户名和密码使用读取 properties 属性文件的方式
java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* 获取数据连接、关闭等操作的工具类
*/
public class MooNJDBCUtils {
// 数据库驱动字符串
private static final String DRIVER_CLASS = "com.mysql.jdbc.Driver";
// 使用静态代码块,用来加载类时就运行注册的动作
static {
try {
Class.forName(DRIVER_CLASS);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取数据库连接对象
*
* @param url 需要连接的数据库地址url字符串
* @return 连接数据库对象
*/
public static Connection getConnection(String url) {
// 读取jdbc.properties配置文件,获取用户名和密码
Properties pro = new Properties();
try {
pro.load(MooNJDBCUtils.class.getResourceAsStream("/jdbc.properties"));
Connection conn = DriverManager.getConnection(url, pro);
return conn;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void close(ResultSet rs, Statement stmt, Connection conn) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
close(stmt, conn);
}
/**
* 关闭数据库资源方法
*
* @param stmt 数据库操作对象(子类PreparedStatement也可以关闭)
* @param conn 数据库的连接对象
*/
public static void close(Statement stmt, Connection conn) {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
close(conn);
}
/**
* 关闭数据库连接对象方法
*
* @param conn 数据库的连接对象
*/
public static void close(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}保存用户名和密码的 properties 属性文件 jdbc.properties:
properties
user=root
password=123456JDBC 事务操作
JDBC 事务处理的分类
事务处理方式主要分为两种:自动处理和手动处理。
自动处理:每条SQL语句执行后自动提交事务,无法通过回滚撤消操作。
手动处理:
- 先调用
setAutoCommit(false)开启事务,取消自动提交 - 在 SQL 执行完后调用
commit()提交事务;如果出现异常则调用rollback()回滚事务。
Connection 接口与事务处理相关的方法
java
void setAutoCommit(boolean autoCommit) throws SQLException;- 将此连接的自动提交模式设置为给定状态。如果连接处于自动提交模式下,则它的所有 SQL 语句将被执行并作为单个事务提交。否则,它的 SQL 语句将聚集到事务中,直到调用 commit 方法或 rollback 方法为止。默认情况下,新连接处于自动提交模式。参数
boolean autoCommit的取值说明如下:- autoCommit 设置为 false,则代表开启事务,后面的 SQL 语句将算成一个事务,直到调用 commit 方法或 rollback 方法为止。
- 默认情况下,autoCommit 值为 true,是自动提交模式,代表后面每一个 SQL 语句算成一个事务,自动提交事务。
java
void commit() throws SQLException;- 提交事务
java
void rollback() throws SQLException;- 回滚事务
JDBC 事务操作基础示例
java
import java.sql.Connection;
import java.sql.PreparedStatement;
import org.apache.commons.dbutils.DbUtils;
import jdbc.C3P0Utils;
public class MoonZero {
public static void main(String[] args) {
// 声明连接对象
Connection conn = null;
// 声明操作数据库对象
PreparedStatement ps = null;
try {
// 定义操作sql语句
String sql = "update users set gender='改' where id=2;";
// 使用工具类读取配置文件获取连接
conn = C3P0Utils.getConnection();
// 关闭事务自动提交
conn.setAutoCommit(false);
// 获取操作对象,对数据库进行更新
ps = conn.prepareStatement(sql);
ps.executeUpdate();
DbUtils.closeQuietly(ps);
// 手动制造异常
// System.out.println(5 / 0);
ps = conn.prepareStatement("update users set gender='改' where id=4;");
ps.executeUpdate();
System.out.println("修改成功");
// 提交事务
DbUtils.commitAndCloseQuietly(conn);
} catch (Exception e) {
// 事务回滚
DbUtils.rollbackAndCloseQuietly(conn);
System.out.println("修改失败");
e.printStackTrace();
} finally {
// 使用DbUtils工具类关闭资源
DbUtils.closeQuietly(ps);
}
}
}JDBC 连接池
连接池概述
连接池是一个用来创建和管理数据连接对象的容器。
连接池的核心思想:连接复用,为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个连接,使用完毕之后再放回池中。可以通过设定连接池最大连接数来防止系统无限制地与数据库连接,更为重要的是可以通过连接池的管理机制监视数据库的连接的数量、使用情况,为系统开发,测试及性能调整提供依据。
JDBC 中连接数据的问题
获取连接对象需要消耗比较多的资源,而每次操作都要重新获取新的连接对象,执行一次操作就把连接关闭,这样连接对象的使用率低。而数据库创建连接通常需要消耗相对较多的资源,创建时间也较长。
使用连接池技术可以避免频繁创建数据库连接对象和销毁连接对象带来的开销。
连接池的使用步骤
- 创建:程序启动时创建连接池(容器)并初始化连接对象。放在一块内存中,这块内存称为连接池。
- 获取(使用):直接从连接池中获得一个已经创建好的连接对象来操作数据库
- 关闭:关闭的时候不是真正关闭连接,而是将连接对象再次放回到连接池中,等待复用。
DataSource 数据库连接池 API
数据源(连接池)接口
javax.sql.DataSource 接口表示数据源。只要是实现类实现了该接口的类,就是一个连接池类。
java
public interface DataSource extends CommonDataSource, Wrapper连接池接口常用方法
java
Connection getConnection() throws SQLException;从数据源(连接池)中获取一个连接对象
连接池相关参数
- 初始连接数:一开始连接池中创建多少个连接对象。
- 最大连接数:连接池中最多可以有多少个连接对象。
- 最长等待时间:当一个会话要从连接池中得到连接对象的时候,最长等待多久。
- 最长空闲时间:当一个连接对象在指定时间内没有被使用时,则回收该连接对象。
常用数据库连接池(第三方工具)
- C3P0 连接池
- DBCP 连接池
C3P0 连接池技术
C3P0 连接池概述
C3P0 是一个开源的第三方 JDBC 连接池工具,它实现了数据源和 JNDI 绑定,支持 JDBC3 规范和 JDBC2 的标准扩展。目前使用它的开源项目有 Hibernate,Spring 等。
C3P0 连接池的特点
- 免费开源的连接池技术
- 很多主流的第三方框架都是使用该连接池技术。比如:Spring 和 Hibernate 框架,默认推荐使用 C3P0 作为连接池实现
c3p0 与 DBCP 区别
- dbcp 没有自动回收空闲连接的功能
- c3p0 有自动回收空闲连接功能
C3P0 的使用步骤
- 导入 jar 库 c3p0-0.9.5.2.jar 和 mchange-commons-java-0.2.11.jar。(注:数据库驱动mysql-connector-java-5.1.37-bin.jar也不能少)
- 创建连接池对象 ComboPooledDataSource 对象java
ComboPooledDataSource ds = new ComboPooledDataSource(); - 设置数据库连接参数(jdbcUrl,user,password,driverClass)
- 数据库连接url字符串:
ds.setJdbcUrl("jdbc:mysql://localhost:3306/tempDb"); - 用户名:
ds.setUser("root"); - 密码:
ds.setPassword("123456"); - 驱动类全名:
ds.setDriverClass("com.mysql.jdbc.Driver");
- 数据库连接url字符串:
- 设置连接池参数
- 初始连接数:一开始获得多少个数据连接对象(默认值:3),如果设置值少于3,也是默认是3。
javasetInitialPoolSize(int num)- 最大连接数:连接池中最多有多少个连接对象可以使用(默认值:15)
javasetMaxPoolSize(int num)- 最大等待时间:当某个纯种要获得连接对象时,如果没有可用的连接对象时需要等待的时间,如果超过时间还没有获得,则抛出异常。(默认值:0,就是无限等待)
javasetCheckoutTimeout(毫秒值)- 最大空闲回收时间:连接对象空间时长,如果在指定的时长内没有被再次使用,会被真正关闭。(默认值:0,就是无限等待)
javasetMaxIdleTime(秒值) - 调用
getConnection()方法,获取连接对象。javaConnection conn = ds.getConnection(); - 关闭连接对象。不是真正关闭,而是将连接对象放回连接池中。
Code Demo: C3P0创建连接池步骤练习
java
import java.sql.Connection;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class MoonZero {
public static void main(String[] args) {
// 创建连接池对象
ComboPooledDataSource ds = new ComboPooledDataSource();
try {
// 设置连接参数
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/tempDb");
ds.setUser("root");
ds.setPassword("123456");
// 设置连接池参数
// 初始化连接数
ds.setInitialPoolSize(5);
// 最大连接数
ds.setMaxPoolSize(10);
// 最大等待时间
ds.setCheckoutTimeout(3000);
// 最大空余等待时间
ds.setMaxIdleTime(3);
// 获取连接
for (int i = 1; i <= 11; i++) {
Connection conn = ds.getConnection();
System.out.println(conn);
if (i == 5) {
// 不是真正关闭,将连接对象放回连接池
conn.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}C3P0 连接池(使用 xml 配置文件加载)
使用配置文件的好处
- 配置信息和操作数据库代码分离,降低了程序的耦合性。
- 配置信息不是硬编码到 Java 源码中,后期维护更加方便。
- 可以使用不同的连接池参数。如:
maxPoolSize=10,maxPoolSize=8 - 可以连接不同的数据库。如:db1,db2
- 可以连接不同厂商的数据库。如:Oracle 或 MySQL
配置文件的要求
- 文件名命名要求:
c3p0-config.xml - 位置要求:放在类路径下,源代码即 src 目录下。
C3P0 配置文件的使用方式
方式1: 使用默认配置(default-config)
java
ComboPooledDataSource ds = new ComboPooledDataSource();方式2: 使用命名配置(named-config:name="配置名")
java
ComboPooledDataSource ds = new ComboPooledDataSource("配置名");配置文件的各个属性名,都是以设置方法去掉set后的名字。
Code Demo : C3P0 配置文件
xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!-- 默认配置 -->
<default-config>
<!-- 数据库连接信息 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/tempDb</property>
<property name="user">root</property>
<property name="password">123456</property>
<!-- 连接池的配置 -->
<!-- 连接池初始化连接数 -->
<property name="initialPoolSize">5</property>
<!-- 连接池最大连接数 -->
<property name="maxPoolSize">10</property>
<!-- 连接池最大等待时间 -->
<property name="checkoutTimeout">3000</property>
<!-- 连接池最大空闲时间 -->
<property name="maxIdleTime">3</property>
</default-config>
<!-- 其它的命名配置 -->
<named-config name="MoonZero-config">
<!-- 数据库连接信息 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/tempDb</property>
<property name="user">root</property>
<property name="password">123456</property>
<!-- 连接池的配置 -->
<!-- 连接池初始化连接数 -->
<property name="initialPoolSize">5</property>
<!-- 连接池最大连接数 -->
<property name="maxPoolSize">9</property>
<!-- 连接池最大等待时间 -->
<property name="checkoutTimeout">3000</property>
<!-- 连接池最大空闲时间 -->
<property name="maxIdleTime">3</property>
</named-config>
</c3p0-config>Code Demo : C3P0 使用配置文件创建连接池
java
import java.sql.Connection;
import java.sql.SQLException;
import com.mchange.v2.c3p0.ComboPooledDataSource;
/*
* 关卡1训练案例2
* 1.C3P0 连接池的使用
* 要求:连接池参数和初始化连接数通过配置文件配置。
* 使用 C3P0连接池获得 10个连接对象。
* 要求:分别使用默认配置和命名配置创建连接池对象。再通过连接池对象获得连接对象。
*/
public class MoonZero {
public static void main(String[] args) {
// 使用配置文件创建连接池对象(方式1:使用默认配置(default-config))
// ComboPooledDataSource ds = new ComboPooledDataSource();
// 使用配置文件创建连接池对象(方式2:使用命名配置(named-config:MoonZero-config))
ComboPooledDataSource ds = new ComboPooledDataSource("MoonZero-config");
// 使用循环获取连接对象
for (int i = 1; i <= 11; i++) {
try {
Connection conn = ds.getConnection();
System.out.println(i + " = " + conn);
// 测试最大连接数,不是真正的关闭,只是放回去连接池
if (i == 5 || i == 9) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}自定义 C3P0 连接池工具类
Code Demo: 自定义C3P0工具类
java
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3P0Utils {
// 创建私有静态数据源(连接池对象)成员变量
// src文件夹下有xml配置文件,所以不需要进行数据库设置与连接池设置
private static DataSource ds = new ComboPooledDataSource();
// 创建公有的得到数据源(连接池对象)的方法
public static DataSource getDataSource() {
return ds;
}
// 创建共有的得到连接对象的方法
public static Connection getConnection() {
try {
return ds.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}DBCP 连接池技术
DBCP 连接池概述
DBCP: DataBase Connection Pool 数据库连接池。
DBCP 是 Apache 旗下组织开发的一款产品,免费开源的,也是 Tomcat 服务器的默认使用连接池
DBCP 使用步骤
- 导入 dbcp 相关的 jar 包 目前使用版本 commons-dbcp-1.4.jar 核心包 commons-pool-1.6.jar 支持包 注:DBCP 2 compiles and runs under Java 7 only (JDBC 4.1) DBCP 1.4 compiles and runs under Java 6 only (JDBC 4)
- 创建连接池对象
BasicDataSource对象javaBasicDataSource ds = new BasicDataSource(); - 设置连接参数(url,username,password,driverClassName)
- 数据库连接url字符串:
ds.setUrl("jdbc:mysql://localhost:3306/tempDb"); - 用户名:
ds.setUsername("root"); - 密码:
ds.setPassword("xxx"); - 驱动类全名:
ds.setDriverClassName("com.mysql.jdbc.Driver");
注:与C3P0设置连接参数的方法名有不同
- 数据库连接url字符串:
- 设置连接池参数(初始连接数,最大连接数,最大等待时间,最大空闲数)
- 初始化连接数:
setInitialSize(int num) - 最大连接数:
setMaxActive(int num) - 超过最大连接数时,最大等待时间:
setMaxWait(毫秒值) - 最大空闲数:
setMaxIdle(int num)连接池中最多只有指定个数的连接对象空闲。
注:与C3P0设置连接池参数的方法名有不同
- 初始化连接数:
- 调用
getConnection()方法,获取连接对象javaConnection conn = ds.getConnection(); - 关闭连接对象。不是真正关闭,而是将连接对象放回连接池中
Code Demo: DBCP连接池技术
java
import java.sql.Connection;
import org.apache.commons.dbcp.BasicDataSource;
public class MoonZero {
public static void main(String[] args) {
try {
// 创建连接池对象
BasicDataSource ds = new BasicDataSource();
// 设置连接参数
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/tempDb");
ds.setUsername("root");
ds.setPassword("123456");
// 设置连接池参数
// 初始化连接数
ds.setInitialSize(5);
// 最大连接数
ds.setMaxActive(10);
// 超过最大连接数时,最大等待时间
ds.setMaxWait(3000);
// 最大空闲连接数
ds.setMaxIdle(3);
// 获取连接
for (int i = 1; i <= 10; i++) {
Connection conn = ds.getConnection();
// 因为 DBCP 返回的 Connection 对象已经重写了 toString()方法,
// 为了看到不同的对象,输出 hashCode()方法。
System.out.println(i + " : " + conn.hashCode());
if (i == 5) {
// 释放连接(不是真正的关闭连接对象,而是把连接对象放回连接池)
conn.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}输出结果:
1 : 1327763628
2 : 1915503092
3 : 1535128843
4 : 2027961269
5 : 458209687
6 : 458209687
7 : 254413710
8 : 1789447862
9 : 1688376486
10 : 445884362使用 Properties 配置文件加载 DBCP 连接池
DBCP配置文件要求
- 文件名命名要求:xxx.properties,一般使用 dbcp.properties
- 位置要求:放在类路径下,源代码即 src 目录下。
- 键名:配置文件的键与方法名去掉 set 相同,首字母小写。
注:写键值对时,在"="左右尽量不要有空格
dbcp.properties : DBCP配置文件示例
properties
# 数据库的连接信息
url=jdbc:mysql://localhost:3306/tempDb
username=root
password=123456
driverClassName=com.mysql.jdbc.Driver
#连接池的配置信息
initailSize=5
maxActive=10
maxWait=3000
maxIdle=7DBCP 使用配置文件加载连接池步骤
- 创建
Properties属性文件,配置相关参数 - 通过类对象的
getResourceAsStream("/dbcp.properties")方法,从类路径下加载文件,以字节流的方式加载。 - 通过
properties.load(InputStream in)加载属性文件 - 通过
BasicDataSourceFactory.createDataSource(Properties prop),得到DataSource连接池对象 - 通过
DataSource类对象调用getConnection()方法得到连接对象 - 关闭连接对象
Code Demo: DBCP使用配置文件加载连接池
java
import java.sql.Connection;
import java.util.Properties;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
public class MoonZero {
public static void main(String[] args) {
try {
// 创建Properties类对象读取属性文件
Properties pro = new Properties();
pro.load(MoonZero.class.getResourceAsStream("/dbcp.properties"));
// 创建连接池对象
DataSource ds = BasicDataSourceFactory.createDataSource(pro);
// 获取连接
for (int i = 1; i <= 11; i++) {
Connection conn = ds.getConnection();
// 因为 DBCP 返回的 Connection 对象已经重写了 toString()方法,
// 为了看到不同的对象,输出 hashCode()方法。
System.out.println(i + " : " + conn.hashCode());
if (i == 5) {
// 释放连接(不是真正的关闭连接对象,而是把连接对象放回连接池)
conn.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}JPA(Java Persistence API)
概述
JPA 是 Java Persistence API 的简称,中文名 Java 持久层 API,是 JDK 5.0 注解或 XML 描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
Persistence 类
Persistence 类主要是用于读取配置文件,获得实体管理工厂。常用方法如下所示:
java
public static EntityManagerFactory createEntityManagerFactory(String persistenceUnitName)- 获得实体管理工厂。参数
String persistenceUnitName:配置文件中的persistenceUnitName
示例:
java
EntityManagerFactory emf = Persistence.createEntityManagerFactory("crm");EntityManagerFactory 接口
用于管理数据库的连接,获得操作对象实体管理类 EntityManager。EntityManagerFactory 是一个线程安全的对象,并且其创建极其浪费资源,所以编程的时候要保持它是单例的。常用方法如下所示:
java
public EntityManager createEntityManager();- 获取操作对象实体管理类
示例:
java
EntityManagerFactory emf = Persistence.createEntityManagerFactory("crm");
EntityManager entityManager = emf.createEntityManager();EntityManager 接口
在 JPA 规范中,EntityManager 实体管理类是操作数据库的重要 API,它是线程不安全的,需要保持线程独有。常用方法如下所示:
java
public EntityTransaction getTransaction();- 获取事务对象,但没有开启事务
java
public void persist(Object entity);- 插入数据
java
public void remove(Object entity);- 根据对象的主键id删除数据。注意:使用JPA删除数据也必须使用持久化对象,即要先查询数据,再删除
java
public <T> T merge(T entity);- 如果数据库没有记录就保存,如果有记录就更新,重要的判断是 OID 是否相同,OID(Object ID)就是在配置文件配置为
<id>属性。
java
public <T> T find(Class<T> entityClass, Object primaryKey);- 用于通过OID,获得一条记录,有延迟。相当于
get()
java
public <T> T getReference(Class<T> entityClass, Object primaryKey);- 用于通过OID,获得一条记录,无延迟。相当于
load()
java
public Query createQuery(String qlString);- 获取 JPQL 操作对象,参数是操作 HQL 语句,用于删除和更新
java
public <T> TypedQuery<T> createQuery(String qlString, Class<T> resultClass);- 获取 JPQL 操作对象,用于查询操作
EntityTransaction 接口
EntityTransaction 接口用于管理事务(开始,提交,回滚)。获取事务(没有开启事务):
获取实例
java
EntityManagerFactory emf = Persistence.createEntityManagerFactory("crm");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();常用方法
java
public void begin();- 开启事务
java
public void commit();- 提交事务
java
public void rollback();- 回滚事务
TypedQuery 接口
TypedQuery 接口继承 Query 接口。用于操作 JPQL 的查询的。JPQL 和 HQL 一样。为什么 JPA 的标准,查询需要指定类型,目的就是为了让返回的数据没有没有警告。
获取实例
java
EntityManager em = xxx;
TypedQuery<Xxx> query = em.createQuery("xxx", Xxx.class);常用方法
java
int executeUpdate();- 执行查询操作(好像不用)。此方法继承于
Query接口
java
TypedQuery<X> setParameter(int position, Object value);- 设置
?参数的值。int position:是?占位符后面指定的下标Object value:占位符的值
java
TypedQuery<X> setParameter(String name, Object value);- 设置命名参数的值。
String name:命名参数的名字,不带:号Object value:命名参数的值
java
TypedQuery<X> setFirstResult(int startPosition);- 设置分页查询的起始数
int startPosition:查询语句limit的起始数
java
TypedQuery<X> setMaxResults(int maxResult);- 设置分页查询的数量
int maxResult:分页查询每页大小
java
List<X> getResultList();- 返回查询的结果List集合(查询所有的数据)
java
X getSingleResult();- 返回查询的结果是一条数据,常用聚合函数
count(),相当于uniqueResult()
Query 接口
用于操作SQL的查询接口,执行没有返回数据的JPQL(增删改),用于删除和更新
获取实例
java
EntityManager em = xxx;
Query query = em.createQuery("xxx");常用方法
java
int executeUpdate();- 执行删除和修改的操作
java
Query setParameter(int position, Object value);- 设置
?参数的值。int position:是?占位符后面指定的下标Object value:占位符的值
java
Query setParameter(String name, Object value);- 设置命名参数的值。
String name:命名参数的名字,不带:号Object value:命名参数的值
CriteriaBuilder 接口
用户使用标准查询接口 Criteria 查询接口
常用数据库编程工具类库
- Apache Commons DBUtils,详见《Apache Commons 工具类库》笔记的『Commons DBUtils』章节