JDBC(Java数据库连接)基础学习笔记

JDBC(Java Data Base Connectivity)即Java数据库连接,它是由Sun官方提供的2个类库包java.sqljavax.sql所构成。这种技术遵循了Sun官方的接口,所以JDBC就能使用一套代码就能方便地连接和操作各种不同的数据库,这也正是面向接口编程的应用之一。

体验

每次学习新知识之前,写一些类似于HelloWorld之类的小demo是很有帮助的,所以这里也先来弄一个JDBC的demo,观摩一下JDBC这个技术是如何入手使用的。

准备数据

创建数据库、表,并且插入几条数据。这里创建了名为jdbc_practice的数据库,并在里面创建了一张user表然后再插入了两条数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
create database jdbc_practice;

create table user(
id int primary key,
name varchar(20),
age int
);

insert into user
values(1, 'jack', 17);

insert into user
values(2, 'kate', 20);

导入相关的jar包

在使用JDBC进行编程之前,我们必须要先导入这个jar包:mysql-connector-java-x.x.xx.jar,它包含了连接数据库时所需要的驱动程序。

如果使用maven,则使用以下依赖配置即可。

1
2
3
4
5
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>

使用JDBC编写程序

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
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

import com.mysql.jdbc.Driver;

public class Experience {
public static void main(String[] args) throws Exception {
// 创建驱动类的实例
Driver driver = (Driver) Class.forName("com.mysql.jdbc.Driver").newInstance();
// 注册驱动实例
DriverManager.registerDriver(driver);

// 准备连接数据库的信息
String url = "jdbc:mysql://localhost:3306/jdbc_practice";
String user = "root";
String password = "root";
// 获取数据库的连接
Connection conn = DriverManager.getConnection(url, user, password);

// 创建语句对象并执行sql
String sql = "select * from user";
Statement statement = conn.createStatement();
ResultSet set = statement.executeQuery(sql);
// 处理执行后所返回的结果
while (set.next()) {
int id = set.getInt(1);
String name = set.getString(2);
int age = set.getInt(3);
System.out.println("id = " + id + ", name = " + name + ", age = " + age);
}

// 释放资源
set.close();
statement.close();
conn.close();
}
}

这就是使用JDBC进行数据库查询的小demo,我们也可以发现这个代码步骤也十分简单:

  • 注册JDBC驱动程序
  • 通过连接信息创建数据库的连接
  • 执行SQL操作数据库
  • 处理执行后的结果
  • 释放连接等资源

大体上来说,使用JDBC进行编程就这4大步骤。

运行结果

id = 1, name = jack, age = 17
id = 2, name = kate, age = 20

注册JDBC驱动程序的原理

使用Class.forName注册数据库驱动类即可

在查看com.mysql.jdbc.Driver这个驱动类源码的时候我们会发现这一段代码。

1
2
3
4
5
6
7
8
9
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}

在静态块中,调用DriverManagerregisterDriver方法注册本驱动实例。所以,当我们第一次使用com.mysql.jdbc.Driver这个类,即类加载器把它的加载到JVM时,就会执行静态块中的注册驱动语句。

所以我们在开发的时候只需要使用一句话就可以加载驱动程序:Class.forName("com.mysql.jdbc.Driver")
所有的数据库驱动类都是这样统一实现的,我们想注册什么数据库驱动类,就直接这样做即可:Class.forName("driver_class_qualified_name")

至于上面体验时候所演示的那加载驱动程序的两行代码所做的工作就显得有点多余了,毕竟只是个刚入门的demo,纠结太多原理性的东西也没什么用。

隐秘于DriverManager中所做的注册工作

如果我们没有做任何手工注册驱动的工作,那会如何呢?
在调试DriverManager类的源码时,其实可以发现它一开始就帮我们注册好了一些默认的驱动类。

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
public class DriverManager {
// 存储注册驱动信息的容器
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

// 省略部分代码

// 静态块中的loadInitialDrivers方法是进行初始化工作(非常重要)
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}

public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {

registerDriver(driver, null);
}

// 真正执行注册驱动的方法
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {

if(driver != null) {
// 如果驱动已注册,那就不会再进行注册
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
throw new NullPointerException();
}

println("registerDriver: " + driver);
}
// 省略部分代码
}

loadInitialDrivers方法就做一件事情:加载初始驱动类,但在里面使用了两种方式进行加载

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
public class DriverManager {
// 省略部分代码

private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}

AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();

try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});

println("DriverManager.initialize: jdbc.drivers = " + drivers);

if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}

// 省略部分代码
}

加载方式一:
第一种加载驱动类的方式是最直观的,从loadInitialDrivers方法这几行代码直接得知。去除无关代码后,逻辑就显而易见了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String drivers;

// 通过jdbc.drivers这个键获取对应的驱动类名字符串
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});

// 分割驱动类名字符串,得出每个驱动类名,并把它们加载到JVM,即注册它们
String[] driversList = drivers.split(":");
for (String aDriver : driversList) {
Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
}

加载方式二:
第二种加载驱动类的方式隐藏得比较深,从loadInitialDrivers方法中这段代码也没看出什么,但这块主体代码所做的工作确实就是加载驱动类。

1
2
3
4
5
6
7
8
9
10
11
12
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// load中的参数就是java.sql.Driver.class
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();

while(driversIterator.hasNext()) {
driversIterator.next();
}
return null;
}
});

所以,要继续深入查看ServiceLoader类的源码,我们才能了解到其中具体的加载步骤是如何的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public final class ServiceLoader<S> implements Iterable<S> {
private static final String PREFIX = "META-INF/services/";

// The class or interface representing the service being loaded
private final Class<S> service;

// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// The current lazy-lookup iterator
private LazyIterator lookupIterator;

// 省略部分代码
}

PREFIX以及service这两个域对于后面理解加载过程是十分重要的,这里列出来留个印象。而接下来所展示的源码片段都是ServiceLoader类中的。

我们首先从ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);load方法开始查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl); // 进入
}

public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
return new ServiceLoader<>(service, loader); // 进入
}

private ServiceLoader(Class<S> svc, ClassLoader cl) {
// service被初始化为java.sql.Driver.class
service = Objects.requireNonNull(svc, "Service interface cannot be null");

loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload(); // 进入
}

public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader); // 进入
}

源码片段到这里,ServiceLoader类的service域被初始化为java.sql.Driver.class
最终,ServiceLoader类中service域的值传递给了成员内部类LazyIterator

那么接下来我们要重点关注的就是ServiceLoader类中的成员内部类LazyIterator的源码。

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
private class LazyIterator implements Iterator<S> {
Class<S> service;

ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;

// 下一个要进行加载到JVM中的全类名
String nextName = null;

private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}

private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}

private S nextService() {
String cn = nextName;
nextName = null;
Class<?> c = null;

// 在这里,驱动类就会被注册
c = Class.forName(cn, false, loader);
S p = service.cast(c.newInstance());

providers.put(cn, p);
return p;
}

public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}

public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
}

从这个迭代器LazyIterator的代码首先可得知3件事:

  • ServiceLoader外部类service域的值会传递给这个迭代器类的service
  • hasNext方法必定会调用hexNextService方法
  • next方法必定会调用nextService方法

我们最终重点关注的就是迭代器类中那两个实现加载工作的私有方法。

16~35行hasNextService方法中最重要的一个语句

1
String fullName = PREFIX + service.getName();

其中,PREFIXservice的值已经知道了,而fullName的值自然而然就是META-INF/services/java.sql.Driver。而下面所做的工作就是在找到fullName所指定的资源文件,并读取它。资源文件里面就有初始驱动类的全限定驱动类名列表。

不信的话,你可以自己展开mysql-connector-java-x.x.xx.jar这个jar包,根据fullName值所表示的目录就可以找到java.sql.Driver这个文件,
里面就有初始的驱动类的全类名列表,比如我这里使用的是mysql-connector-java-5.1.37.jar,里面就有:

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
1
nextName = pending.next();

最后,nextName就会被赋予上一个驱动类名以供调用nextService方法时使用。

最后来看37~48行nextService方法的具体实现

nextName将它存储的驱动类名赋值给cn,并重置为空。那接下来的两个语句就起到的作用就是加载驱动类了。

1
2
c = Class.forName(cn, false, loader);
S p = service.cast(c.newInstance());

第一行forName方法并不会触发cn对应驱动类的静态块进行加载。而第二行newInstance这个调用就会触发驱动类加载注册了。

总结

在我们手工注册指定的数据库驱动之前,DriverManager会做这个工作:

  • 首先通过系统属性中jdbc.drivers这个键获取对应的数据库驱动类名,并注册它们
  • 然后再通过读取数据库驱动jar包的配置文件META-INF/services/java.sql.Driver,并注册里面所存储好的数据库驱动类类名

JDBC中相关的类与接口

学习JDBC,我们只要把其中相关的类与接口都熟悉一遍,就能轻松地它们它们了。

DriverManager类

java.sql.DriverManager类是工具类,它主要负责数据库的驱动注册和连接获取。

其中这个类常用的方法有:

  • public static Driver getDriver(String url) 根据指定的数据库URL地址获取JDBC驱动
  • public static Enumeration<Driver> getDrivers() 获取驱动管理器中所有已经加载好的JDBC驱动
  • public static void registerDriver(Driver driver) 注册指定的数据库驱动
  • public static Connection getConnection(String url, String user, String password) 获取数据库连接

连接数据库的URL路径格式:主协议:子协议://主机地址:端口/数据库名,如果连接本地数据库,那么可以省略主机地址和端口,斜杠是绝对不能省略。

Connection接口

java.sql.Connection是SUN定义的顶级接口,一般的数据库厂商所创建的接口都会继承该接口。

Connection类型层次

获取到数据库的连接,我们就可以对数据库进行操作了,而对数据库的操作可以分为两大部分:

执行SQL语句

要执行SQL语句,则必须使用Connection接口中的方法创建相应的语句对象才行。

  • Statement createStatement() - Statement对象执行静态SQL语句,它不能接收参数
  • PreparedStatement prepareStatement(String sql) - PreparedStatement对象执行预编译SQL语句,它可以接收参数
  • CallableStatement prepareCall(String sql) - CallableStatement对象执行存储过程,并且它也可以接收参数

执行事务操作

有关事务操作的常用方法比较多的,这些方法同样是在Connection接口中。

  • void setAutoCommit(boolean autoCommit) - 设置事务是否自动提交,一般设置为false
  • void rollback() - 回滚事务
  • Savepoint setSavepoint() - 创建一个匿名的回滚点
  • Savepoint setSavepoint(String name) - 根据指定的名称创建一个带名称的回滚点
  • void rollback(Savepoint savepoint) - 回滚到指定的回滚点
  • void commit() - 提交事务
  • void setTransactionIsolation(int level) - 设置事务隔离级别

Statement接口

Statement类型层次

正如前面所讲,java.sql.Statement类型的对象用于执行静态SQL语句,并且我们不能传递参数给它。其中常用于执行SQL的方法有:

  • boolean execute(String sql) - 执行指定的SQL,返回true表示执行结果是一个ResultSetfalse表示执行结果是更新统计数或没有结果。
  • ResultSet executeQuery(String sql) - 执行SQL进行查询并返回结果集ResultSet对象。
  • int executeUpdate(String sql) - 执行SQL进行更新并返回影响的记录数

ResultSet接口

java.sql.ResultSet接口代表的是查询结果。

其中,查询后得出这个类型的对象后,我们常用以下方法来处理查询结果:

  • boolean next() - 将游标从当前位置移动到下一行
  • boolean previous() - 将游标从当前位置移动到上一行
  • boolean last() - 将游标移动到最后一行
  • boolean first() - 将游标移动到第一行
  • boolean absolute(int row) - 将游标移动到指定的行数

除了这些方法,还有的就是各种getXXX方法用于获取数据。

这里,使用JDBC技术进行普通的SQL查询。

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
public class JDBC {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;

try {
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 定义连接数据库的信息
String url = "jdbc:mysql:///jdbc_practice";
String user = "root";
String password = "root";
// 获取数据库连接
connection = DriverManager.getConnection(url, user, password);
// 创建语句对象
String sql = "select * from user";
statement = connection.createStatement();
// 执行SQL语句
resultSet = statement.executeQuery(sql);
// next遍历
while (resultSet.next()) {
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
int age = resultSet.getInt(3);
System.out.println(id + ", " + name + ", " + age);
}
System.out.println("-------------------------------");
// previous遍历
while (resultSet.previous()) {
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
int age = resultSet.getInt(3);
System.out.println(id + ", " + name + ", " + age);
}
System.out.println("-------------------------------");
// 取第一条记录
resultSet.first();
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
int age = resultSet.getInt(3);
System.out.println(id + ", " + name + ", " + age);
System.out.println("-------------------------------");
// 取最后一条记录
resultSet.last();
id = resultSet.getInt(1);
name = resultSet.getString(2);
age = resultSet.getInt(3);
System.out.println(id + ", " + name + ", " + age);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
resultSet = null;
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
statement = null;
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
connection = null;
e.printStackTrace();
}
}
}
}
}

PreparedStatement接口

使用java.sql.Statement接口类型对象只能执行普通的SQL语句,而且我们不能通过它来给SQL语句指定参数,想要指定参数,我们必须将参数一个一个且准确无误地拼接到SQL语句中。所以当我们所要执行的SQL需要传参时,使用它是十分麻烦且容易出错的。

但使用扩展java.sql.Statement接口的子接口java.sql.PreparedStatement接口来做这个工作就方便得多了。

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
public class PreparedStatementTest {
public static void main(String[] args) {
Connection con = null;
PreparedStatement pStmt = null;
ResultSet rs = null;

try {
// 定义连接信息
String url = "jdbc:mysql:///jdbc_practice";
String user = "root";
String password = "root";
// 注册驱动并获取连接
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection(url, user, password);
// 创建预编译语句对象
String sql = "select * from user where id = ? and name = ? and age = ?";
pStmt = con.prepareStatement(sql);

// 设置参数
pStmt.setInt(1, 2);
pStmt.setString(2, "may");
pStmt.setInt(3, 18);

// 执行
rs = pStmt.executeQuery();
while (rs.next()) {
int id = rs.getInt(1);
String name = rs.getString(2);
int age = rs.getInt(3);
System.out.println(id + ", " + name + ", " + age);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
try {
if (rs != null)
rs.close();
} catch (SQLException e) {
rs = null;
e.printStackTrace();
}
try {
if (pStmt != null)
pStmt.close();
} catch (SQLException e) {
pStmt = null;
e.printStackTrace();
}
try {
if (con != null)
con.close();
} catch (SQLException e) {
con = null;
e.printStackTrace();
}
}
}
}

在SQL语句中,在需要使用参数的地方插入?作为参数占位符,在这之后调用预编译语句对象的setXXX方法设置参数即可。

CallableStatement接口

如果要使用JDBC技术执行存储过程或函数,这就必须使用java.sql.CallableStatement接口才行。

我们要使用这个接口之前,首先要知道调用存储过程或函数的语法,这个语法可以在API文档中找到:

  • 调用函数 - {? = call <function-name>[(<arg1>,<arg2>, ...)]}
  • 调用存储过程 - {call <procedure-name>[(<arg1>,<arg2>, ...)]}
细节:
1.使用大括号{}包围中间的语句对于调用函数来说是必须的,省略它,调用函数时就会出错。
2.存储过程和函数的每个参数都要用问号?作为占位符。
3.如果是调用函数,第一个参数就是结果参数,必须使用CallableStatement接口中的registerOutParameter方法将它注册为输出参数。
4.CallableStatement接口中的对应各种类型的set方法是传递参数值给存储过程或函数,而get方法是获取输出参数值或函数返回值。
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
public class CallableStatementTest {
// 连接数据库所要的驱动和信息
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String URL = "jdbc:mysql:///jdbc_practice";
static final String USER = "root";
static final String PASSWORD = "root";

public static void main(String[] args) {
Connection con = null;
Statement stmt = null;
CallableStatement cStmt1 = null;
CallableStatement cStmt2 = null;

try {
// 注册驱动, 获取数据库连接
Class.forName(JDBC_DRIVER);
con = DriverManager.getConnection(URL, USER, PASSWORD);

// 1.存储过程get_user_name
String sql1 = "drop procedure if exists `get_user_name`";
String sql2 = "create procedure get_user_name"
+ "(in u_id int, in u_age int, out u_name varchar(20)) "
+ "begin"
+ " select name into u_name"
+ " from user"
+ " where id = u_id and age = u_age;"
+ "end";
stmt = con.createStatement();
stmt.execute(sql1);
stmt.execute(sql2);

// 创建对应于存储过程的CallableStatement对象
String proc1 = "{call get_user_name(?, ?, ?)}";
cStmt1 = con.prepareCall(proc1);

// 设置参数
cStmt1.setInt("u_id", 2);
cStmt1.setInt("u_age", 18);

// 执行存储过程
cStmt1.execute();
String name = cStmt1.getString("u_name");
System.out.println(name);

// 2.函数compare
String sql3 = "drop function if exists `compare`";
String sql4 = "create function compare(a int, b int) "
+ "returns int"
+ " if a > b then return 1;"
+ " elseif a < b then return -1;"
+ " else return 0;"
+ " end if;";
stmt.execute(sql3);
stmt.execute(sql4);

// 创建对应于函数的CallableStatement对象
String proc2 = "{? = call compare(?, ?)}";
cStmt2 = con.prepareCall(proc2);

// 设置参数
cStmt2.setInt(2, 1);
cStmt2.setInt(3, 2);
// 注册输出参数
cStmt2.registerOutParameter(1, Types.INTEGER);

// 执行函数
cStmt2.execute();
int compare = cStmt2.getInt(1);
System.out.println(compare);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (cStmt2 != null)
cStmt2.close();
} catch (SQLException e) {
cStmt2 = null;
e.printStackTrace();
}
try {
if (cStmt1 != null)
cStmt1.close();
} catch (SQLException e) {
cStmt1 = null;
e.printStackTrace();
}
try {
if (stmt != null)
stmt.close();
} catch (SQLException e) {
stmt = null;
e.printStackTrace();
}
try {
if (con != null)
con.close();
} catch (SQLException e) {
con = null;
e.printStackTrace();
}
}
}
}

代码优化

我们可以发现上面的代码例子是有问题的。如果说单独用作学习例子确实没什么问题,但这种代码应用于实际的项目中是十分不好的。

问题:

  • 连接数据库所要用的参数信息都写死在代码中
  • 释放资源这种重复代码每次都要写

所以,现在我们可以写一个工具类。

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
public class DBUtils {
private static String driverClass;
private static String url;
private static String user;
private static String password;

static {
// 加载配置文件
Properties p = new Properties();
InputStream in = DBUtils.class.getResourceAsStream("db.properties");
try {
p.load(in);
driverClass = p.getProperty("driverClass");
url = p.getProperty("url");
user = p.getProperty("user");
password = p.getProperty("password");

if (driverClass != null)
Class.forName(driverClass);
} catch (Exception e) {
System.out.println("加载配置文件失败");
e.printStackTrace();
}
}

/**
* 获取数据库连接
*
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, password);
}

/**
* 释放数据库连接等资源
*
* @param con
* @param stmt
* @param set
*/
public static void release(Connection con, Statement stmt, ResultSet set) {
if (con != null) {
try {
con.close();
} catch (SQLException e) {
con = null;
e.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
stmt = null;
e.printStackTrace();
}
}
if (set != null) {
try {
set.close();
} catch (SQLException e) {
set = null;
e.printStackTrace();
}
}
}

public static void main(String[] args) throws Exception {
Connection connection = DBUtils.getConnection();
System.out.println(connection);
}
}

这样,我们在项目中就能应用这个数据库工具类。

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
public class TestDao {
@Test
public void update() {
Connection con = null;
Statement stmt = null;

try {
// 获取数据库连接并创建语句对象
con = DBUtils.getConnection();
stmt = con.createStatement();
// 执行SQL语句
String update = "update user set age = 19 where name = 'kate'";
int count = stmt.executeUpdate(update);
if (count > 0) {
System.out.println("有" + count + "条记录被修改");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtils.release(con, stmt, null);
}
}

@Test
public void query() {
Connection con = null;
Statement stmt = null;
ResultSet set = null;

try {
// 获取数据库连接并创建语句对象
con = DBUtils.getConnection();
stmt = con.createStatement();
// 执行SQL语句
String query = "select * from user";
set = stmt.executeQuery(query);

while (set.next()) {
int id = set.getInt(1);
String name = set.getString(2);
int age = set.getInt(3);
System.out.println(id + ", " + name + ", " + age);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtils.release(con, stmt, set);
}
}
}