前言
最近在研究Mybatis框架,由于该框架基于JDBC,想要很好地理解和学习Mybatis,必须要对JDBC有较深入的了解。所以便把JDBC 这个东东翻出来,老调重弹,好好总结一番,作为自己的笔记,也是给读者一个参考~~~本篇博文是我的上篇博文 老调重弹:JDBC系列 之 <驱动加载原理全面解析> 的续文,主要梳理一下JDBC的层次结构和基本构成。以下是本文的组织内容(用户可以点击上面的目录栏查看):
JDBC的层次结构
总体而言,JDBC包含以下几大角色 : Driver、DriverManager、Connection、Statement、ResultSet。这几大角色之间的层次关系如下图所示:
其中,DriverManager 和 Driver 这两个角色已经在我的上一篇文章:老调重弹:JDBC系列 之 <驱动加载原理全面解析>阐述过了,读者可以点击查看。
Connection:Driver 或者 DriverManager根据连接的url 和参数信息创建Connection实例,用来维持和数据库的数据通信,如果没有销毁或者调用close()对象,此对象和数据库的对象会一直保持连接;
Statement:Connection创建Statement对象,表示需要执行的sql语句或者存储过程;
ResultSet: 表示Statement执行完SQL语句后返回的结果集。
基本构成分析
Connection角色
Connection表示与特定数据库的连接,可以获取到数据库的一些信息,这些信息包括:其表信息,应该支持的SQL语法,数据库内有什么存储过程,此链接功能的信息等等。
在一般实际使用情况下,我们关注的Connection的功能有以下几点:
1.创建可以执行sql语句或者存储过程的对象statement,用来和数据库进行交互;
比如,以下代码创建了几种不同类型的Statement:
//加载Oracle数据库驱动 Class.forName("oracle.jdbc.driver.OracleDriver"); //根据特定的URL,返回可以接受此URL的数据库驱动对象 Driver driver = DriverManager.getDriver(URL); //使用数据库驱动创建数据库连接Connection会话 Connection connection = driver.connect(URL, props); //创建静态的sql语句 Statement 对象来将 SQL 语句发送到数据库。 Statement staticStatement= connection.createStatement(); //创建CallableStatement 对象来调用数据库存储过程。 CallableStatement callableStatement = connection.prepareCall(sqlString); //创建参数化的Statement对象 PreparedStatement preparedStatement = connection.prepareStatement(sqlString);2. 控制sql语句的事务;
Connection默认情况下,对于创建的statement执行的sql语句都是自动提交的,即在statement语句执行完后,自动执行commit操作,将结果影响到物理数据库。为了满足更好地事务控制需求,我们也可以手动地控制事务,手动地对statement 的sql语句执行进行提交(commit)或者回滚(rollback)。
具体事务控制,请关注我的 后续博文老调重弹:JDBC系列
下面通过一个简单的例子演示connection的事务控制:
String sqlString="insert into tableName(column1,column2) values(value1,value2)"; //加载Oracle数据库驱动 Class.forName("oracle.jdbc.driver.OracleDriver"); //根据特定的URL,返回可以接受此URL的数据库驱动对象 Driver driver = DriverManager.getDriver(URL); //使用数据库驱动创建数据库连接Connection会话 connection = driver.connect(URL, props); //使用自定义的事务,要设置connection不自动提交 connection.setAutoCommit(false); //创建静态的sql语句 Statement 对象来将 SQL 语句发送到数据库。 Statement staticStatement= connection.createStatement(); try{ //执行插入操作 staticStatement.execute(sqlString); staticStatement.getConnection().commit();//和上面的connection等价,statement只有一个创建自身的connection的引用 }catch(Exception e) { //有异常,则rollback staticStatement.getConnection().rollback(); }
3.获取数据库连接的元数据,即数据库的整体综合信息。连接的数据库整体信息被封装在了一个 DatabaseMetaData类型的对象上,可以通过以下代码获得:
DatabaseMetaData databaseMetaData = connection.getMetaData();具体DatabaseMetaData内包含了什么信息,请查看 JDK 的API对DatabaseMetaData的描述。Statement角色
Statement 的功能在于根据传入的sql语句,将传入sql经过整理组合成数据库能够识别的sql语句(对于静态的sql语句,不需要整理组合;而对于预编译sql语句和批量语句,则需要整理),然后传递sql请求,之后会得到返回的结果。对于查询sql,结果会以ResultSet的形式返回。
SQL语句可以分为增删改查(CRUD,Create,Read,Update,Delete)四种形式,JDBC 从对数据更新与否的角度上看,将上面的四种形式分为两类:查询类别和更新类别。即:
查询类别:select 语句
更新类别:Insert 、update、delete语句
对应地,Statement执行sql的几种形式:
1. 对sql语句类型不进行区分,执行sql语句的方法
statement提供了execute(String sql)方法支持此种形式,定义如下:
如果是执行的sql是查询类型的select语句,此方法会返回true,需要自己再调用 statement.getResultSet() 方法来获取 Resultset结果集;
boolean
execute(String sql)
执行给定的 SQL 语句,该语句可能返回多个结果。如果是执行的更新类的sql语句如 update,delete,insert语句,此方法会返回false,自己调用statement.getUpdateCount() 返回sql语句影响的行数。
2. 对查询类型的sql语句的执行方法
statement提供了executeQuery(String sql)方法支持此形式,定义如下:
ResultSet
executeQuery(String sql)
执行给定的 SQL 语句,该语句返回单个ResultSet
对象。
3. 对更新类的sql语句 的执行方法
statement提供了executeQuery(String sql)方法支持此形式,定义如下:
int
executeUpdate(String sql)
执行给定 SQL 语句,该语句可能为INSERT
、UPDATE
或DELETE
语句,或者不返回任何内容的 SQL 语句(如 SQL DDL 语句)。
4.批量sql的执行方法
有时候需要将一些sql语句一起提交给数据库,批量执行,statement提供了一些方法,对批量sql的支持:
void
addBatch(String sql)
将给定的 SQL 命令添加到此Statement
对象的当前命令列表中。
int[]
executeBatch()
将一批命令提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组。这里只讨论一般性的Statement,不包含其子接口PreparedStatement和CallableStatement,这两个类型的Statement将会在后续的 老调重弹:JDBC系列 中继续讨论。
ResultSet角色
当Statement查询sql执行后,会得到ResultSet对象,ResultSet对象是sql语句查询的结果,作为数据库结果的映射,其映射关系如下图所示。ResultSet对从数据库返回的结果进行了封装,使用迭代器的模式逐条取出结果集中的记录。其遍历结果集的基本形式如下:
while(resultSet.next()) { //传入列明或者列索引获取记录中对应列的值 resultSet.getXXX(param); }
ResultSet游标的移动和定位
Resultset 提供了很多游标定位的方法,部分方法已经在下面列出:
boolean
absolute(int row)
将光标移动到此ResultSet
对象的给定行编号。void
afterLast()
将光标移动到此ResultSet
对象的末尾,正好位于最后一行之后。void
beforeFirst()
将光标移动到此ResultSet
对象的开头,正好位于第一行之前。boolean
first()
将光标移动到此ResultSet
对象的第一行。int
getRow()
获取当前行编号。boolean
isAfterLast()
获取光标是否位于此ResultSet
对象的最后一行之后。boolean
isBeforeFirst()
获取光标是否位于此ResultSet
对象的第一行之前。boolean
isFirst()
获取光标是否位于此ResultSet
对象的第一行。boolean
isLast()
获取光标是否位于此ResultSet
对象的最后一行。boolean
last()
将光标移动到此ResultSet
对象的最后一行。boolean
next()
将光标从当前位置向前移一行。boolean
previous()
将光标移动到此ResultSet
对象的上一行。boolean
relative(int rows)
按相对行数(或正或负)移动光标。ResultSet结果集的元数据信息
元信息是指关于
ResultSet
对象中列的类型和属性信息的对象。可以通过以下方法获取:
ResultSetMetaData
getMetaData()
获取此ResultSet
对象的列的编号、类型和属性。
ResultSet.getXXX(param) 、ResultSet.updateXXX()的XXX问题
JDBC中定义了数据库中的数据类型和java数据类型的映射,用于数据库和Java数据类型之间的转换。在使用ResultSet去记录中的某一列值的时候,用户要根据数据库对应列的数据类型地应的java数据类型,否则的话有可能抛出异常。下图定义了数据库和Java类型之间的映射:
SQL JDBC/Java setXXX updateXXX VARCHAR java.lang.String setString updateString CHAR java.lang.String setString updateString LONGVARCHAR java.lang.String setString updateString BIT boolean setBoolean updateBoolean NUMERIC java.math.BigDecimal setBigDecimal updateBigDecimal TINYINT byte setByte updateByte SMALLINT short setShort updateShort INTEGER int setInt updateInt BIGINT long setLong updateLong REAL float setFloat updateFloat FLOAT float setFloat updateFloat DOUBLE double setDouble updateDouble VARBINARY byte[ ] setBytes updateBytes BINARY byte[ ] setBytes updateBytes DATE java.sql.Date setDate updateDate TIME java.sql.Time setTime updateTime TIMESTAMP java.sql.Timestamp setTimestamp updateTimestamp CLOB java.sql.Clob setClob updateClob BLOB java.sql.Blob setBlob updateBlob ARRAY java.sql.Array setARRAY updateARRAY REF java.sql.Ref SetRef updateRef STRUCT java.sql.Struct SetStruct updateStruct
JDBC工作的基本流程
一个基本的JDBC工作流程,分为以下几步:
1.加载特定数据库驱动器实现类,并注册驱动器(Driver会注册到DriverManager中);
2. 根据特定的URL,返回可以接受此URL的数据库驱动对象Driver;
3.使用数据库驱动 Driver 创建数据库连接Connection会话;
4. 使用 Connection对象创建 用于操作sql的Statement对象;
5. statement对象 .执行 sql语句,返回结果ResultSet 对象;
6. 处理ResultSet中的结果;
7. 关闭连接,释放资源。以下是一个简单的案例:
public class DBConnection { static final String URL = "jdbc:oracle:thin:@127.0.0.1:1521:xe"; static final String USER_NAME ="louluan"; static final String PASSWORD = "123456"; public static void main(String[] args) { connectionTest(); } public static void connectionTest(){ Connection connection = null; Statement statement = null; ResultSet resultSet = null; try { //1.加载类,并注册驱动器(Driver会注册到DriverManager中) //加载Oracle数据库驱动 Class.forName("oracle.jdbc.driver.OracleDriver").newInstance(); //2.根据特定的URL,返回可以接受此URL的数据库驱动对象 Driver driver = DriverManager.getDriver(URL); Properties props = new Properties(); props.put("user", USER_NAME); props.put("password", PASSWORD); //3.使用数据库驱动创建数据库连接Connection会话 connection = driver.connect(URL, props); //4.获得Statement对象 statement = connection.createStatement(); //5.执行 sql语句,返回结果 resultSet = statement.executeQuery("select * from hr.employees"); //6.处理结果,取出数据 while(resultSet.next()) { System.out.println(resultSet.getString(2)); } //7.关闭链接,释放资源 } catch (ClassNotFoundException e) { System.out.println("加载Oracle类失败!"); e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ //使用完成后管理链接,释放资源,释放顺序应该是: ResultSet ->Statement ->Connection try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
JDBC工作时序图
上述几个对象之间:DriverManager或者Driver创建Connection、Connection创建Statement、Statement又获得ResultSet,它们之间的交互序列图如下所示:
-------------------------------------------------------------------------------------------------------------------------------------------------------------
以上是本文 老调重弹:JDBC系列 之 <JDBC层次结构和基本构成> 的全部内容,以上是自己心得,并非权威,如有不妥或者此错误之处,欢迎读者批评和斧正! 欢迎关注我的下一篇博文: 老调重弹:JDBC系列 之 <JDBC 事务>