JDBC连接数据库
JDBC
在前面的学习,通过CMD命令窗口或者图形界面工具连接并使用MySQL数据库,在一款软件的开发过程中,我们一般使用命令窗口或者图形界面工具调试或者测试数据,用不同的语言去连接数据库,进行数据操作,以Java为例,Java为我们提供并封装了一系列操作数据库的接口(这里的数据库不单单指MySQL,也包括其他关系型数据库例如:SqlServer或者oracle等)。在本章节内,我们将详细学习如果使用。
JDBC简介
JDBC 的全称是 Java Database Connectivity,即 Java 数据库连接,它是一种可以执行 SQL 语句的 Java API。程序可通过 JDBC API 连接到关系数据库,并使用结构化查询语言(SOL,数据库标准的查询语言)来完成对数据库的查询、更新。 与其他数据库编程环境相比,JDBC 为数据库开发提供了标准的 API,所以使用 JDBC 开发的数据库应用可以跨平台运行,而且可以跨数据库(如果全部使用标准的 SQL)。也就是说,如果使用 JDBC开发一个数据库应用,则该应用既可以在 Windows 平台上运行,也可以在 UNIX 等其他平台上运行;既可以使用 MySQL 数据库,也可以使用 Oracle 等数据库,而程序无须进行任何修改。
Java 语言的各种跨平台特性,都采用相似的结构,因为它们都需要让相同的程序在不同的平台上运行,所以都需要中间的转换程序(为了实现 Java 程序的跨平台性,Java为不同的操作系统提供了不同的虚拟机)。同样,为了使JDBC程序可以跨平台,则需要不同的数据库厂商提供相应的驱动程序。
上图显示了JDBC驱动示意图。 正是通过 JDBC驱动的转换,才使得使用相同 JDBC API 编写的程序,在不同的数据库系统上运行良好。
Sun 提供的 JDBC 可以完成以下三个基本工作。
- 建立与数据库的连接
- 执行SQL语句
- 获取SQL语句的执行结果
JDBC常用接口和类
学习JDBC之前需要先了解常用的接口和类,了解完这些接口和类以后就可以使用JDBC连接并操作数据库。
DriverManager类
该接口用于管理JDBC驱动的服务类,该类的主要作用是获取Java与数据库连接的Connection对象,Connection对象可以想象成Java与数据库通信的桥梁。DriverManager的主要方法如下:
- public static Connection getConnection(String url,String user, String password) throws SQLException:该方法用于获取url对应的数据库连接,user指数据库的用户名,password指数据库的用户密码
Connection接口
该接口代表数据库的连接对象,每个Connection代表一个物理连接会话,要想访问数据库,必须先获得数据库连接。该接口的常用方法如下:
- Statement createStatement() throws SQLExcetpion∶ 该方法返回一个 Statement 对象。
- PreparedStatement prepareStatement(String sql)throws SQLExcetpion∶ 该方法返回预编译的 Statement 对象,即将 SOL 语句提交到数据库进行预编译。
- CallableStatement prepareCall(String sql) throws SQLExcetpion∶ 该方法返回 CallableStatement 对象,该对象用于调用存储过程。
上面三个方法都返回用于执行 SQL 语句的 Statement 对象,PreparedStatement、CallableStatement是 Statement 的子类,只有获得了 Statement 之后才可执行 SQL 语句。
除此之外,Connection 还有如下几个用于控制事务的方法。
- Savepoint setSavepoint()∶创建一个保存点。
- Savepoint setSavepoint(String name)∶ 以指定名字来创建一个保存点。
- void setTransactionIsolation(int level)∶ 设置事务的隔离级别。
- void rollback()∶ 回滚事务。 > void rollback(Savepoint savepoint)∶ 将事务回滚到指定的保存点。
- void setAutoCommit(boolean autoCommit)∶关闭自动提交,打开事务。> void commit()∶提交事务。
Statement接口
Statement∶用于执行 SQL 语句的工具接口。该对象既可用于执行DDL、DCL 语句,也可用于 执行 DML 语句,还可用于执行 SQL 查询。当执行 SQL 查询时,返回查询到的结果集。它的常用方法如下。
- ResultSet executeQuery(String sql)throws SQLException∶该方法用于执行查询语句,并返回查 询结果对应的 ResultSet 对象。该方法只能用于执行查询语句。
- int executeUpdate(String sql)throws SQLExcetion∶ 该方法用于执行 DML 语句,并返回受影响 的行数; 该方法也可用于执行 DDL 语句,执行 DDL 语句将返回 0。
- boolean execute(String sql)throws SOLException∶ 该方法可执行任何 SOL 语句。如果执行后 第一个结果为 ResultSet 对象,则返回 true; 如果执行后第一个结果为受影响的行数或没有任何结果,则返回 false。
PreparedStatement:预编译的 Statement 对象。PreparedStatement 是 Statement 的子接口,它允 许数据库预编译 SOL 语句(这些 SOL 语句通常带有参数),以后每次只改变 SOL 命令的参数,避免数据库每次都需要编译 SQL 语句,因此性能更好。相对于 Statement 而言,使用 PreparedStatement 执行 SOL 语句时,无须再传入 SOL 语句,只要为预编译的 SOL 语句传入参数值即可。所以它比 Statement 多了如下方法。
- void setXxx(int parameterlndex,Xxx value)∶该方法根据传入参数值的类型不同,需要使用不 同的方法。传入的值根据索引传给 SQL 语句中指定位置的参数。
ResultSet接口
ResultSet:结果集对象。该对象包含访问查询结果的方法,ResultSet 可以通过列索引或列名获 得列数据。它包含了如下常用方法来移动记录指针。
- void close():释放 ResultSet 对象。
- boolean absolute(int row):将结果集的记录指针移动到第 row 行,如果 row 是负数,则移动 到倒数第 row 行。如果移动后的记录指针指向一条有效记录,则该方法返回 true。
- void beforeFirst():将 ResultSet 的记录指针定位到首行之前,这是 ResultSet 结果集记录指针 的初始状态——记录指针的起始位置位于第一行之前。
- boolean first():将 ResultSet 的记录指针定位到首行。如果移动后的记录指针指向一条有效记 录,则该方法返回 true。
- boolean previous():将 ResultSet 的记录指针定位到上一行。如果移动后的记录指针指向一条 有效记录,则该方法返回 true。
- boolean next():将 ResultSet 的记录指针定位到下一行,如果移动后的记录指针指向一条有效 记录,则该方法返回 true。
- boolean last():将 ResultSet 的记录指针定位到最后一行,如果移动后的记录指针指向一条有 效记录,则该方法返回 true。 ·
- void afterLast(): 将 ResultSet 的记录指针定位到最后一行之后。
当把记录指针移动到指定行之后,ResultSet 可通过 getXxx(int columnIndex)或 getXxx(String columnLabel)方法来获取当前行、指定列的值,前者根据列索引获取值,后者根据列名获取值。Java 7新增了
JDBC编程步骤
大致了解了JDBC API的相关接口和类之后,下面就可以进行 JDBC 编程了,JDBC编程大致按如下步骤进行。
- 加载驱动
1 | //加载MySQL驱动 |
- 获取连接
1 | //获取连接 |
当使用 DriverManager 获取数据库连接时,通常需要传入三个参数;数据库 URL、登录数据库的用户名和密码。这三个参数中用户名和密码通常由 DBA(数据库管理员)分配,而且该用户还应该具有相应的权限,才可以执行相应的 SQL 语句。 数据库 URL通常遵循如下写法∶
1 | jdbc:subprotoclo:othre stuff |
上面 URL 写法中的 jdbc 是固定的,而 subprotocol 指定连接到特定数据库的驱动,而后面的 other和 stuff也是不固定的——也没有较强的规律,不同数据库的 URL 写法可能存在较大差异。例如,MySQL数据库的 URL写法如下∶
1 | jdbc:mysql://hostname:port/databasename |
在上面的链接中hostname指的是数据库所在主机的IP或者域名,如果要连接本机则可以写作“localhost”或者“127.0.0.1”,port则是指数据库的端口号,MySQL的端口号默认是3306,database则是只要连接数据库的名称。
Oracle数据库的URL写法如下:
1 | jdbc:oracle:thin: :port:databasename |
- 通过Connection获取Statement对象
1 | Statement statement = connection.createStatemen(); |
- 使用Statement对象执行SQL语句
1 | String sql = "INSERT INTO STUDENT VALUES(1,'张三',18,'男')"; |
- 获取结果,此处使用的是Statement对象的executeUpdate方法,该方法返回的是影响的行数,如果要调用executeQuery方法,则返回值是一个ResultSet对象。
1 | ResultSet rs = statement.executeQuery(sql); |
- 释放资源
下面,将上述步骤进行整合,示范JDBC编程步骤:
首先创建数据库和数据表:
1 | --创建数据库 |
运行上面的程序,结果如下图:
使用PrepareStatement执行SQL语句
如果经常需要反复执行一条结构相似的 SQL 语句,例如如下两条 SQL 语句∶
1 | INSERT INTO STUDENT VALUES (1,"张三",18,'男'); |
对于这两条 SOL 语句而言,它们的结构基本相似,只是执行插入时插入的值不同而已。对于这种情况,可以使用带占位符(?)参数的 SOL语句来代替它;
1 | INSERT INTO STUDENT VALUES (?,?,?,?); |
但 Statement 执行 SOL 语句时不允许使用问号占位符参数,而且这个问号占位符参数必须获得值后才可以执行。为了满足这种功能,JDBC提供了PreparedStatement接口,它是 Statement接口的子接口,它可以预编译 SQL 语句,预编译后的 SQL 语句被存储在 PreparedStatement 对象中,然后可以使用该对象多次高效地执行该语句。简而言之,使用 PreparedStatement 比使用 Statement 的效率要高。 创建 PreparedStatement 对象使用 Connection 的 prepareStatement()方法,该方法需要传入一个 SQL字符串,该 SQL 字符串可以包含占位符参数。如下代码所示∶
1 | String sql = "INSERT INTO STUDENT (?,?,?,?)"; |
PreparedStatement 也提供了execute()、executeUpdate()、executeQuery()三个方法来执行 SQL 语句,不过这三个方法无须参数,因为 PreparedStatement 已存储了预编译的 SOL 语句。 使用 PreparedStatement 预编译 SOL 语句时,该 SOL 语句可以带占位符参数,因此在执行 SOL 语句之前必须为这些参数传入参数值,PreparedStatement 提供了一系列的 setXxx(int index,Xxx value)方法来传入参数值。
下面通过示例学习PrepareStatement的用法:
1 | package cn.bytecollege; |
需要注意的是使用PrepareStatement的setXxx()方法时,第一个参数是占位符的索引,第二个参数是占位符的真实值,占位符的索引是从1开始。
总体来看,使用 PreparedStatement 比使用 Statement 多了如下三个好处。
- PreparedStatement 预编译 SQL 语句,性能更好。
- PreparedStatement 无须拼接SQL 语句,编程更简单。
- PreparedStatement 可以防止 SQL 注入,安全性更好。
基于以上三点,通常推荐避免使用 Statement 来执行 SQL 语句,改为使用 PreparedStatement 执行 SQL语句。
可滚动的结果集
前面提到,ResultSet 定位记录指针的方法有 absolute(O)、previous(等方法,但前面程序自始至终都只用了 next()方法来移动记录指针,实际上也可以使用 absoluteO、previousO、lastO)等方法来移动记录 指针。可以使用 absolute()、previous(、afterLast()等方法自由移动记录指针的 ResultSet 被称为可滚动的结果集。
在 JDK 1.4 以前,默认打开的 ResultSet 是不可滚动的,必须在创建 Statement 或 PreparedStatement 时传入额外的参数。从 Java 5.0 以后,默认打开的 ResultSet 就是可滚动的,无须传入额外的参数。
以默认方式打开的 ResultSet 是不可更新的,如果希望创建可更新的 ResultSet,则必须在创建 Statement 或 PreparedStatement 时传入额外的参数。Connection 在创建 Statement 或 PreparedStatement 时还可额外传入如下两个参数。
- resultSetType∶ 控制 ResultSet 的类型,该参数可以取如下三个值:
- ResultSet.TYPE FORWARD ONLY∶该常量控制记录指针只能向前移动。这是 JDK1.4 以前 的默认值。
- ResultSet.TYPE SCROLL INSENSITIVE∶该常量控制记录指针可以自由移动(可滚动结果 集),但底层数据的改变不会影响 ResultSet 的内容。
- ResultSet.TYPE SCROLL SENSITIVE∶该常量控制记录指针可以自由移动(可滚动结果集), 而且底层数据的改变会影响 ResultSet 的内容。
- resultSetConcurrency∶控制 ResultSet 的并发类型,该参数可以接收如下两个值 ·:
- ResultSet.CONCUR_READ_ONLY∶该常量指示 ResultSet 是只读的并发模式(默认)。·
- ResultSet.CONCUR UPDATABLE∶该常量指示 ResultSet是可更新的并发模式。
注意:TYPE SCROLL INSENSITIVE、TYPE SCROLL SENSITIVE 两个常量的作用需要底层数据库驱动的支持,对于有些数据库驱动来说,这两个常量并没有太大的区别。
需要指出的是,可更新的结果集还需要满足如下两个条件。
- 所有数据都应该来自一个表。
- 选出的数据集必须包含主键列。
通过该 PreparedStatement 创建的 ResultSet 就是可滚动、可更新的,程序可调用 ResultSet 的 updateXxx(int columnIndex,Xxx value)方法来修改记录指针所指记录、特定列的值,最后调用 ResultSet的 updateRow()方法来提交修改。
下面通过示例来学习可滚动的结果集。
1 | package cn.bytecollege; |
运行上面的程序结果如下图:
从运算结果可以看出输入数据时是倒序输出的,这是因为在程序中先将游标定位到了最后一行,然后倒序循环,通过absolute()定位到每一行数据并读取。读取完数据后,将数据表中的第2列数据进行了修改,此时查询数据库如下图:
使用ResultSetMetaData分析结果集
当执行 SOL 查询后可以通过移动记录指针来遍历 ResultSet 的每条记录,但程序可能不清楚该 ResultSet 里包含哪些数据列,以及每个数据列的数据类型,那么可以通过 ResultSetMetaData 来获取关于 ResultSet 的描述信息。
ResultSet 里包含一个 getMetaData()方法,该方法返回该 ResultSet 对应的 ResultSetMetaData 对象。一旦获得了 ResultSetMetaData 对象,就可通过 ResultSetMetaData 提供的大量方法来返回 ResultSet 的描述信息。常用的方法有如下三个。
- int getColumnCount()∶ 返回该 ResultSet 的列数量。
- String getColumnName(int column)∶返回指定索引的列名。
- String getColumnClassName(int column)∶返回指定索引的列类型。
下面通过示例来学习ResultSetMetaData的用法
1 | package cn.bytecollege; |
运行上面的程序,结果如下