JDBC学习笔记

JDBC学习笔记

五月 31, 2019

JDBC(Java DataBase Connectivity)的学习笔记

入门

这一节我们先来学习一下JDBC的运行流程:

  1. 注册JDBC Driver
  2. 获取与数据库的物理连接
  3. 创建不同类型的Statement
  4. 执行SQL命令
  5. 如果有结果集,处理结果集
  6. 释放资源

下面我们按照上面的流程来写一段示例程序,从代码的注释中,你可以清晰的看到每一步的执行流程。关于代码实现的细节,你可以先不必了解,我们在后面的小节中会一步一步来进行分析。

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
package com.study.jdbc;

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

import org.junit.Test;


import com.mysql.jdbc.Driver;

public class JdbcDemo1 {

@Test
public void demo1() {

try {
// 1. 加载驱动
DriverManager.registerDriver(new Driver());
// 2. 获得连接
Connection conn = DriverManager.getConnection("jdbc:mysql://locahost:3306/jdbctest", "123456", "123456");
// 3. 创建执行SQL语句的对象,并且执行SQL
String sql = "select * from user";
// 4 连接statement
Statement stmt = conn.createStatement();
// 5 执行sql
ResultSet resultSet = stmt.executeQuery(sql);
while(resultSet.next()) {
int id = resultSet.getInt("id");
System.out.println('id': id);
}
// 6 释放资源
resultSet.close();
stmt.close();
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

JDBC的API

在上一节中,我们了解到了JDBC整个流程。在本节中,我们将详细了解JDBC为我们提供的API

DriverManager

DriverManager(驱动管理类)主要负责两个功能

  1. 注册驱动
    在上一节中,我们使用DriverManger来注册了一个驱动。

    1
    DriverManager.registerDriver(new Driver());

    但是在实际的开发中,我们不通过这中方式来注册驱动。这种注册方式会导致驱动注册两次。我们来分析一下com.mysql.jdbc.Driver的源码
    line 60-66 的静态代码块

    1
    2
    3
    4
    5
    6
    7
    static {
    try {

    } catch (SQLException E) {
    throw new RuntimeException("Can't register driver!");
    }
    }

    在这个静态代码块中,已经注册了驱动,也就是Driver类被加载的时候,就会注册驱动。我们在程序中再次手动调用注册Driver类。所以导致驱动被注册了两次。
    注意:我们一般注册驱动的正确方式为:

    1
    Class.forName("com.mysql.jdbc.Driver");
  2. 获取连接
    获得连接的方法:Connection getConnection(String url, String username, String password);

    • url参数的写法:jdbc:mysql://localhost:3306/jdbc
      • jdbc: 是一个连接数据库的协议
      • mysql: 自协议
      • localhost: 主机名
      • 3306: mysql端口号
      • jdbc: 数据库名
    • 如果我们连接的是本机的url,可以简写为:jdbc:mysql:///jdbc

Connection

Connection(连接对象)的主要功能:

  1. 创建执行SQL语句的对象
    • Statement createStatement() : 执行SQL语句,有SQL注入漏洞存在
    • PreparedStatement preparedStatement(String sql) : 预编译SQL语句,解决了SQL注入的漏洞(推荐使用)
    • CallableStatement prepareCall(String sql) : 执行SQL中存储过程
  2. 进行事务的管理
    • setAutoCommit(boolean autoCommit) : 设置事务是否自动提交
    • commit() : 事务提交
    • rollback() : 事务回滚

Statement

Statement(执行SQL对象)的主要功能:

  1. 执行SQL语句
    • boolean execute(String sql) : 执行SQL,执行SELECT语句返回true,否则返回false
    • ResultSet executeQuery(String sql) : 执行SQL中的SELECT语句
    • int executeUpdate(String sql) : 执行SQL中的INSERT/UPDATE/DELETE语句,返回影响的行数
  2. 执行批处理操作
    • addBatch(String sql) : 添加到批处理
    • executeBatch() : 执行批处理
    • clearBatch() : 清空批处理

ResultSet

ResultSet(结果集对象)。其实就是查询语句(SELECT)语句查询的结果封装

  • 主要作用:
    • 获取查询到的结果
    • next() : 获取下以条数据,第一次调用指针从数据集的第一条的上一条开始
    • 针对不同类型的数据可以使用getXXX()获取数据,通用的获取数据的方法 getObject()

JDBC的资源释放

JDBC程序运行完成后,一定要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是Result,Statement和Connection对象。特别是Connection对象,他是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。

在入门一节,我们在try语句块中释放了资源,但是如果代码在还没有运行到释放资源的代码的过程中发生了异常,我们是无法正常释放资源的,所以这段释放资源的代码,应该放到finally语句块中来执行。

下面为改进后的代码:

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
Connection conn = null;
Statement stmt = null;
ResultSet resultSet = null;
try {
// 1. 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获得连接
conn = DriverManager.getConnection("jdbc:mysql://locahost:3306/jdbctest", "123456", "123456");
// 3. 创建执行SQL语句的对象,并且执行SQL
String sql = "select * from user";
// 3.1 连接statement
stmt = conn.createStatement();
// 3.2 执行sql
resultSet = stmt.executeQuery(sql);
while(resultSet.next()) {
int uid = resultSet.getInt("uid");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
String name = resultSet.getString("name");
System.out.println(uid + " " + " " + "username" + " " + password + " " + name);
}

} catch (Exception e) {
e.printStackTrace();
} finally {
// 4. 释放资源
if(resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
}
resultSet = null;
}
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
}
stmt = null;
}
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
}
conn = null; // 垃圾回收机机制会更快地回收对象
}
}

JDBC的CRUD操作

JDBC的CRUD操作比较简单,只要你会使用SQL指令,了解JDBC中的几个常用对象以及其常用方法(在上一节已经介绍)。就可以很容易地理解并运用CRUD。为了减少冗长的代码,本节中我们只将try块下的代码放在下面。

Create操作

新增操作可以直接调用Statement对象下的executeUpdate方法,在方法中传入SELECT语句作为参数即可

1
2
3
4
5
6
7
8
9
10
// 一顿前置(参考demo2代码)操作,获取到Statement对象
stmt = conn.createStatement();
// 编写SQL语句
String sql = "INSERT INTO user VALUES(null, 'eee', '123', 'Lee')";
int line = stmt.executeUpdate(sql);
if(line > 0) {
System.out.println("新增成功");
} else {
System.out.println("新增失败");
}

Update操作

相同的,更新操作也可以调用Statement对象下的executeUpdate方法,直接上代码:

1
2
3
4
5
6
// 编写SQL
String sql = "UPDATE `user` SET `username` = 'qqqq', `password`='456' WHERE uid = 4";
int i = stmt.executeUpdate(sql);
if(i > 0) {
System.out.println("修改成功");
}

Delete操作

删除操作同样简单,使用executeUpdate方法执行SQL语句即可。

1
2
3
4
5
6
7
String sql = "DELETE FROM user WHERE uid = 1";
int res = stmt.executeUpdate(sql);
if(res > 0) {
System.out.println("删除成功");
} else {
System.out.println("删除失败");
}

Retrieve操作

了解了增、改、删操作,我们来看一下相对麻烦的查询操作,不同前三个操作,由于查询需要额外的一个ResultSet对象来接收结果集,所以我们在声明对象和释放对象的时候,一定不要忘记对ResultSet对象的释放。另外不同的一点,就是前三个操作是使用Statement对象下的executeUpdate方法来执行的,而对于查的操作,我们要使用Statement对象下的executeQuery方法。这一点一定要结合前三者来区分。可以根据方法的返回内容来区分,executeQuery是到得到一些资源,所以必须要有一个叫做ResultSet的对象来接收,而增、改、删三个操作是对数据资源的操作,不需要接收数据资源,所以,只需要返回一个整型的影响行数即可。

编写一个JDBC工具类

在上述我们已经了解了JDBC,理解上面的内容,你就可以进行实际的开发了。但是有很多代码是重复的,为了减少冗长的代码,本节中我们来抽象出一个JDBC的工具类。

配置文件

在开始编写工具类之前,我们在工程的src目录下新建一个名为jdbc.properties文件。这个.properties为一个数据库配置相关的属性文件,其内容为Key-Value的形式。这样做可以在不修改工具类内部代码的前提下方便的配置你的工具类。可以抽离出的几项配置如下所示,相信你看到Key的命名就可以理解他的含义了。

1
2
3
4
driverClass = com.mysql.jdbc.Driver
url = jdbc:mysql:///database-name
username = root
password = 123456

实现工具类

我们在 com.study.jdbc.utils 包下面新建一个类:JDBCUtils,非常简单,实际上就是对公共代码的一个抽离。
不需要过多的解释,具体的每一步操作都在代码中有详细的注释。

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package com.study.jdbc.utils;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
* JDBC工具类
* @author Colorful
*
*/
public class JDBCUtils {

private static final String driverClass;
private static final String url;
private static final String username;
private static final String password;

static {
// 加载属性文件并解析
Properties props = new Properties();
// 使用类的加载器的方式获取的属性文件的输入流
InputStream inStream = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
props.load(inStream);
} catch (IOException e) {
e.printStackTrace();
}

driverClass = props.getProperty("driverClass");
url = props.getProperty("url");
username = props.getProperty("username");
password = props.getProperty("password");
}

/**
* 注册驱动
* @throws ClassNotFoundException
*/
public static void loadDriver() throws ClassNotFoundException {
Class.forName(driverClass);
}

/**
* 获得连接
* @throws SQLException
* @throws ClassNotFoundException
*/
public static Connection getConnection() throws Exception {
loadDriver();
return DriverManager.getConnection(url, username, password);
}

/**
* 释放资源
* @param rs 结果集对象
* @param stmt Statement对象
*/
public static void release(Statement stmt, Connection conn) {
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}

/**
* 释放资源
* @param rs 结果集对象
* @param stmt Statement对象
* @param conn Connection对象
*/
public static void release(ResultSet rs, Statement stmt, Connection conn) {
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}