学习目标:了解JDBC驱动的安装和使用,以及使用JDBC连接MySQL数据库,执行SQL语句并获取查询结果。
认识JDBC
在实际开发中,大量的开发都是基于数据库的,使用数据库可以方便地实现数据的存储与查询。
当前主流的数据库基本都是关系数据库,也就是使用SQL语言对数据库进行查询和操作。程序主要使用的关系数据库有MySQL、SQL Server、Oracle等数据库管理系统。
开发Java语言的团队为了方便程序员开发数据库程序,减少数据库开发的工作量,尽量做到同一数据库程序代码能够操作不同的数据库管理系统。他们开发了JDBC数据库驱动程序,JDBC是一种标准的执行SQL语句的Java API接口,它可以方便的实现对多种关系数据库管理系统的统一操作。
JDBC接口的具体实现由生产数据库的厂商来实现,当前主流的关系数据库管理系统(如MySQL、SQL Server、Oracle等),都提供了对JDBC接口的支持。
按照JDBC的实现方式,JDBC驱动程序分为以下四类:
JDBC—ODBC桥驱动
在这种实现方式中,JDBC并不直接操作数据库,而是通过Windows系统提供的ODBC(开放数据库连接)来操作数据库,ODBC可以看作是JDBC和数据库的连接桥梁,因此称作JDBC—ODBC桥驱动。

由于JDBC利用ODBC作为操作数据库的桥梁,限制了Java程序的跨平台性,Java程序只能在Windows平台下运行,另外在性能上也比较低效。因此这种JDBC实现方式在数据库应用开发中用的比较少。
JDBC本地驱动
在这种实现方式中,JDBC驱动程序通过数据库管理系统的API接口来直接操作数据库,减少了通过ODBC操作数据库的环节,提高了数据库的访问效率。这种实现方式只能支持特定的数据库系统,如果要支持多个数据库系统,需要接入多个JDBC本地驱动。

JDBC本地驱动要求在运行Java数据库程序的每台机器上,同时要安装数据库、JDBC驱动以及数据库系统的API。
JDBC网络驱动
在这种实现方式中,本地客户端Java程序可以直接通过约定的网络通信协议访问数据库,JDBC驱动程序会把数据库的访问操作转换为约定的网络协议,发送到数据库应用服务器,数据库应用服务器再把网络协议转换为数据库的API。

本地协议JDBC驱动
在这种实现方式中,将JDBC调用直接转换为数据库特定的网络通信协议。它是最常见的驱动程序类型,Java数据库程序开发多采用这类JDBC驱动程序,网上下载的JDBC驱动jar包基本都属于这类驱动,通常是由数据库厂商直接提供,例:mysql-connector-java.jar。因为是使用网络通信,驱动程序可以完全用java编写,支持跨平台部署,性能也较好。

JDBC提供了JDBC API类库,让开发者能够连接数据库、执行SQL语句、数据库表记录的添加、插入、更新、删除等操作。在JDBC的基本操作中,最常用的类和接口有DriverManager、Connection、Statement、Result、Prepared Statement。开发人员可以利用这些类和接口来开发数据库应用程序。
在后面的数据库编程课程中,将采用MySQL8.0数据库作为课程案例的操作数据库,读者需要在本地安装MySQL8.0数据库。
在项目中添加JDBC驱动
开发数据库程序,需要在项目中添加JDBC驱动程序,MySQL8.0数据库的JDBC驱动程序文件是mysql-connector-java-8.0.18.jar。
jar文件也称为jar包,它是一种java归档文件,该文件把具有同类功能且已编译的类calss文件、相关的程序资源(如文本、图片等)放置到一个归档文件中,作为类库来使用,这个归档文件就是jar文件。
jar文件的基础格式就是zip文件,将jar文件的扩展名修改为zip后,可以使用zip程序解压jar文件,查看jar包里面的内容。
新建项目PUnit15,在PUnit15项目中添加mysql-connector-java-8.0.18.jar包。添加步骤如下:
(1)在项目目录下,建立lib目录,将mysql-connector-java-8.0.18.jar包复制到lib目录。
(2)单击【File】菜单下的“Project Structure…”命令,该命令的快捷键为(Ctrl + Alt + Shift + s),IDEA弹出Project Structure对话框,选择Dependencies标签,添加已复制到项目目录下的mysql-connector-java-8.0.18.jar包,单击【OK】按钮即可。

3、 连接MySQL数据库
连接MySQL数据库,需要在程序中加载JDBC驱动程序,然后使用DriverManager类的getConnection()方法来连接MySQL数据库。连接步骤如下图所示:

(1)加载JDBC驱动程序
所有的JDBC驱动程序都实现了java.sql.driver接口,加载JDBC驱动就是加载JDBC实现该接口的类,一般使用Class类的forName()方法加载该类,forName()方法要求传入包含加载类路径的文本串,也就是JDBC实现java.sql.driver接口的类所在jar包的路径。
基于MySQL8.0数据库的JDBC实现java.sql.driver接口的类路径为:
com.mysql.cj.jdbc.Driver
加载类文件的语句为:
Class.forName(“com.mysql.cj.jdbc.Driver”);
使用winrar软件直接打开mysql-connector-java-8.0.18.jar文件,即可发现JDBC实现java.sql.driver接口的类所在jar包的路径。
(2)连接数据库
加载JDBC驱动程序成功后,下一步就可以连接数据库。连接数据库要用到DriverManager类,DriverManager类在java.sql包内。它负责JDBC驱动程序的管理,作用于程序和JDBC驱动程序之间,跟踪可用的驱动程序,并在数据库的驱动程序之间建立连接。
它提供的主要方法就是得到一个数据库的连接,getConnection()方法用于连接数据库,并返回一个Connection对象。该方法说明如下:
● static Connection getConnection(String url)
该方法用于建立和数据库的连接,数据库的连接路径由参数url给出。它会从已加载的JDBC驱动程序集合中选择适当的驱动程序。
参数url为数据库的连接路径,连接MySQL数据库的路径格式为:
jdbc:mysql://IP地址:端口号/数据库名称?key1=value1&……& keyn=valuen
其中“jdbc”为连接协议,“mysql”为连接的子协议,该子协议指定是连接mysql数据库,“IP地址:端口号”指定数据库所在主机的IP地址和数据库的监听端口,在实际连接中,使用主机的IP地址和数据库的监听端口号来代替。“数据库名称”指定要连接的数据库名称。在“数据库名称”后面的key和value键值对可以有多个,也可以没有,这些键值对为驱动程序提供附加的连接信息。
● static Connection getConnection(String url, String user, String password)
该方法用于建立和数据库的连接,数据库的连接路径由参数url给出,数据库的登录用户由参数user给出,数据库用户的登录密码由参数password给出。
如果待连接的数据库需要指定用户名和密码才能访问时,就需要使用该方法来连接数据库。
(3)操作数据库
数据库连接成功后,就可以进行数据库的查询、更新和写入操作。对数据库的所有操作都在Connection接口内,关于Connection接口的内容在后面的课程会陆续介绍。
(4)关闭数据库的连接
完成数据库的操作后,需要关闭数据库的连接,以释放数据库连接资源。Connection类的close()方法用于关闭与数据库的连接。
本课案例中涉及的数据库可以在课程资源下载,下载的数据库是SQL文件,可以使用MySQL命令导入下载的数据库。
案例1:建立ConnectionMySQLTest类,连接数据库。
在PUnit15项目新建demo包,在demo包下新建ConnectionMySQLTest类。代码如下:
package demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConnectionMySQLTest {
// 定义JDBC加载路径
static String jdbc = "com.mysql.cj.jdbc.Driver";
// 定义MySQL数据库的连接地址
static String mysqlurl = "jdbc:mysql://localhost:3306/shop?serverTimezone=UTC";
// 定义MySQL数据库的用户名
static String username = "root";
// 定义MySQL数据库的用户名登录密码
static String password = "~123456q";
public static void main(String[] args) {
Connection conn = null;
try {
// 加载JDBC驱动
Class.forName(jdbc);
// 连接数据库
conn = DriverManager.getConnection(mysqlurl, username, password);
if( null != conn )
{
System.out.println(conn);
conn.close();
}
else
{
System.out.println("数据库连接失败");
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
代码中mysqlurl数据库路径说明如下:
(1) localhost 是指本地IP地址;
(2) MySQL数据库的默认端口号是3306;
(3) shop是连接数据库的名称,shop数据库可以在课程资源区下载,下载后可导入到MySQL数据库;
(4) serverTimezone键值对是用来设置时区,可以将serverTimezone设置为UTC,UTC表示的是全球标准时间。如果程序运行是在中国,一般将该值设置为GMT%2B8,即北京时间东八区。
代码中的username和password分别是登录数据库的用户名和登录密码。
程序执行结果如下图所示:

程序输出以上信息,表示数据库连接成功。
4、 执行SQL语句
MySQL数据库连接成功后,就可以执行SQL语句了,使用SQL语句可以对数据库记录进行查询、更新、添加及删除等操作。执行SQL语句的操作由Statement接口完成,Statement接口在java.sql包内,该接口可以使用Connection接口提供的createStatement()方法实例化。
MySQL数据库连接成功后,就可以执行SQL语句了,使用SQL语句可以对数据库记录进行查询、更新、添加及删除等操作。执行SQL语句的操作由Statement接口完成,Statement接口在java.sql包内,该接口可以使用Connection接口提供的createStatement()方法实例化。
Connection接口的createStatement()方法是重载方法,具体说明如下:
● Statement createStatement()
该方法用于创建向数据库发送SQL语句的Statement对象。
● Statement createStatement(int resultSetType, int resultSetConcurrency)
该方法用于创建具有给定类型和并发性的ResultSet对象的Statement对象。此方法与上面的createStatement方法相同,但它允许重写默认的结果集类型和并发性。
参数resultSetType设置结果集类型,结果集类型有如下常量定义:
ResultSet.TYPE_FORWARD_ONLY;
说明:结果集的游标只能向下滚动。
ResultSet.TYPE_SCROLL_INSENSITIVE;
说明:结果集的游标可以上下移动,当数据库变化时,当前结果集不变。
ResultSet.TYPE_SCROLL_INSENSITIVE;
说明:返回可滚动的结果集,当数据库变化时,当前结果集同步改变。
参数resultSetConcurrency设置并发类型,并发类型有如下常量定义:
ResultSet.CONCUR_READ_ONLY;
说明:不能用结果集更新数据库中的表。
ResultSet.CONCUR_UPDATETABLE;
说明:能用结果集更新数据库中的表。
● Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
此方法与上面的方法相同。不同之处是多了resultSetHoldability参数,该参数设置ResultSet的保持类型。该保持类型有如下常量定义:
ResultSet.HOLD_CURSORS_OVER_COMMIT;
说明:表示提交当前事务时, 具有此可保存性的打开的 resultset 对象不被关闭。
ResultSet.CLOSE_CURSORS_AT_COMMIT;
说明:表示提交当前事务时,具有此可保存性的打开的 resultset 对象将被关闭
Statement接口的主要方法说明如下:
该方法立即释放此语句对象的数据库和JDBC资源。
案例2:查询shop数据库shoper表的记录数。
在demo包下新建SqlDemoTest1类。代码如下:
package demo;
import java.sql.*;
public class SqlDemoTest1 {
// 定义JDBC加载路径
static String jdbc = "com.mysql.cj.jdbc.Driver";
// 定义MySQL数据库的连接地址
static String mysqlurl = "jdbc:mysql://localhost:3306/shop?serverTimezone=GMT%2B8";
// 定义MySQL数据库的用户名
static String username = "root";
// 定义MySQL数据库的用户名登录密码
static String password = "~123456q";
public static void main(String[] args) {
Connection conn = null;
Statement statement = null;
try {
// 加载JDBC驱动
Class.forName(jdbc);
// 连接数据库
conn = DriverManager.getConnection(mysqlurl, username, password);
if( null != conn )
{
System.out.println(conn);
// 获取表记录数的SQL语句
String sql = "select * from shoper";
// 实例化Statement对象
statement = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
// 执行SQL语句
boolean bSucess = statement.execute(sql);
if( bSucess )
{
// 获取记录数
ResultSet resultset =statement.getResultSet();
// 将结果集的光标移动到最后一条记录
resultset.last();
System.out.println("shoper表记录数:" + resultset.getRow());
resultset.close();
}
else
{
System.out.println("SQL语句执行失败");
}
statement.close();
conn.close();
}
else
{
System.out.println("数据库连接失败");
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
SqlDemoTest1程序的sql语句用于获取shoper表的全部记录,statement对象的execute()方法执行指定的sql语句,如果sql语句执行成功,该方法会返回true。statement对象的getResultSet()方法会返回execute()方法执行后的结果集。程序获取结果集后,调用resultset对象的last()方法将结果集光标移动到结果集的最后一条记录,然后resultset对象的getRow()方法获取当前结果集光标所在记录行数,该记录行数即为结果集的全部记录数。
5、 ResultSet与SQL查询
Statement接口执行SQL查询语句后,会以结果集的方式返回,查询到的数据库所有记录都会存储在结果集中,操作该结果集的接口就是ResultSet接口。
ResultSet接口的常用方法说明如下:
● boolean first()
该方法将光标移动到此ResultSet结果集中的第1行。
● boolean next()
该方法将光标从当前记录位置向后移动一行。
● boolean last()
该方法将光标移动到此ResultSet结果集中的最后一行。
● int getRow()
该方法返回当前光标所在ResultSet结果集中的行号。
● int getInt(int columnIndex)
该方法获取指定ResultSet结果集当前光标所在行中列号的内容,内容以int类型返回。
● int getInt(String columnLabel)
该方法获取指定ResultSet结果集当前光标所在行中列名称的内容,内容以int类型返回。
● float getFloat(int columnIndex)
该方法获取指定ResultSet结果集当前光标所在行中列号的内容,内容以float类型返回。
● float getFloat(String columnLabel)
该方法获取指定ResultSet结果集当前光标所在行中列名称的内容,内容以float类型返回。
● double getDouble(int columnIndex)
该方法获取指定ResultSet结果集当前光标所在行中列号的内容,内容以double类型返回。
● double getDouble(String columnLabel)
该方法获取指定ResultSet结果集当前光标所在行中列名称的内容,内容以double类型返回。
● String getString(int columnIndex)
该方法获取指定ResultSet结果集当前光标所在行中列号的内容,内容以String类型返回。
● String getString(String columnLabel)
该方法获取指定ResultSet结果集当前光标所在行中列名称的内容,内容以String类型返回。
● Date getDate(int columnIndex)
该方法获取指定ResultSet结果集当前光标所在行中列号的内容,内容以Date类型返回。
● Date getDate(String columnLabel)
该方法获取指定ResultSet结果集当前光标所在行中列名称的内容,内容以Date类型返回。
● void close()
该方法立即释放此结果集ResultSet对象占用的资源
案例3:查询shoper表的所有记录,并将记录内容输出到控制台。
在demo包下新建SqlDemoTest2类。代码如下:
package demo;
import java.math.BigDecimal;
import java.sql.*;
public class SqlDemoTest2 {
// 定义JDBC加载路径
static String jdbc = "com.mysql.cj.jdbc.Driver";
// 定义MySQL数据库的连接地址
static String mysqlurl = "jdbc:mysql://localhost:3306/shop?serverTimezone=GMT%2B8";
// 定义MySQL数据库的用户名
static String username = "root";
// 定义MySQL数据库的用户名登录密码
static String password = "~123456q";
public static void main(String[] args) {
Connection conn = null;
Statement statement = null;
try {
// 加载JDBC驱动
Class.forName(jdbc);
// 连接数据库
conn = DriverManager.getConnection(mysqlurl, username, password);
if (null != conn) {
System.out.println(conn);
// 获取表记录数的SQL语句
String sql = "select * from shoper";
// 实例化Statement对象
statement = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
// 执行SQL查询语句
ResultSet resultset = statement.executeQuery(sql);
if (null != resultset) {
while( resultset.next() )
{
// 获取id字段内容
String id = resultset.getString("id");
// 获取userId字段内容
String userId = resultset.getString("userId");
// 获取name字段内容
String name = resultset.getString("name");
// 获取brief字段内容
String brief = resultset.getString("brief");
// 获取log字段内容
String log = resultset.getString("log");
// 获取shopMoney字段内容
BigDecimal shopMoney = resultset.getBigDecimal("shopMoney");
// 获取status字段内容
int status = resultset.getInt("status");
// 获取createDate字段内容
Date createDate = resultset.getDate("createDate");
System.out.printf("-----输出第%d条记录-----\n",resultset.getRow());
System.out.println("id:" + id + ";");
System.out.println("userId:" + userId + ";");
System.out.println("name:" + name + ";");
System.out.println("brief:" + brief + ";");
System.out.println("log:" + log + ";");
System.out.println("shopMoney:" + shopMoney.toString() + ";");
System.out.println("status:" + status + ";");
System.out.println("createDate:" + createDate.toString() + ";");
System.out.println("-------------------------------");
}
resultset.close();
} else {
System.out.println("SQL语句执行失败");
}
statement.close();
conn.close();
} else {
System.out.println("数据库连接失败");
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
statement对象执行SQL查询语句后,会返回ResultSet结果集,ResultSet结果集存储了已查询到的数据库记录。遍历ResultSet结果集可以循环调用ResultSet接口的next()方法,next()方法将当前结果集的光标移动到下一行。因为ResultSet结果集的初始光标是在第1行记录的前面,因此在循环中第一次调用next()方法时,会将光标移动到结果集的第1行,当结果集的记录遍历完毕,next()方法返回false。
结果集当前记录的列内容可以调用ResultSet接口getXxx()方法获取,如果获取的是String类型的内容,当内容含有中文并且出现中文乱码时,需要检eclipse的字符编码和数据库的字符编码是否一致。
程序执行结果如下图所示:
