mybatis源码分析:阅读JDBC相关资料笔记

Scroll Down

1、JDBC API简介

JDBC(java database connectivity)是Java语言中提供的访问关系型数据的接口。在Java编写的应用中,使用JDBC API可以执行SQL语句、检索SQL执行结果以及将数据更改回写到底层数据源。JDBC API也可以用于分布式、异构环境中与多个数据源交互。
JDBC API基于X/Open SQL CLI,是ODBC的基础。JDBC提供了一种自然的、易于使用的Java语言与数据库交互的接口。自1997年1月Java语言引入JDBC规范后,JDBC API被广泛接受、并且广大数据库厂商开始提供JDBC驱动的实现。
使用JDBC操作数据源大致需要以下几个步骤。
(1)、与数据源建立连接。
(2)、执行SQL语句。
(3)、检索SQL执行结果。
(4)、关闭连接。

1.1、建立数据源连接

JDBC API中定义了Connection接口,用来表示与底层的数据源的连接。JDBC应用程序可以使用以下两种方式获取Connection对象。
(1)、DriverManager:这是一个在JDBC1.0规范中就已经存在,完全由JDBC API实现的驱动管理类。当应用程序第一次尝试通过URL连接数据库时,DriverManager会自动加载CLASSPATH下所有的JDBC驱动。DriverManager类提供了一系列重载的getConnection方法,用来获取Connection对象。
(2)、DataSource:这个接口是在JDBC2.0规范可选包中引入的API。它比DriverManager更受欢迎,因为它提供了更多底层数据源相关的细节,而且对应用来说,不需要关注JDBC驱动的实现。一个DataSource对象的属性被设置后,它就代表一个特定的数据源。当DataSource实例的getConnection()方法被调用后,DataSource实例就会返回一个与数据源建立连接的Connection对象。在应用程序修改DataSource对象的属性后,就可以通过DataSource对象获取指向不同数据源的Connection对象。同样,数据源的具体实现修改后,不需要修改程序代码。
需要注意的是,JDBC API中只提供了DataSource接口,没有提供DataSource的具体实现,DataSource具体的实现由JDBC驱动程序提供。另外,目前的一些主流的数据库连接池(例如DBCP、C3P0、Druid等),也提供了DataSource接口的具体实现。
mybatis框架中提供了DataSource接口的实现。下面提供了一个通过DataSource获取Connection实例的例子:

public Connection buildJdbcConnection() {
    try {
        // 创建DataSourceFactory实例。
        DataSourceFactory dataSourceFactory = new UnpooledDataSourceFactory();
        // 创建Properties实例。
        Properties properties = new Properties();
        // 读取类路径下的database.properties文件内容。
        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("database.properties");
        properties.load(inputStream);
        // 将文件属性内容设置到DataSourceFactory中。
        dataSourceFactory.setProperties(properties);
        // 通过工厂模式获取DataSource实例。
        DataSource dataSource = dataSourceFactory.getDataSource();
        // 通过DataSource实例获取Connection实例。
        return dataSource.getConnection();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

1.2、执行SQL语句

JDBC API提供了访问SQL:2003规范中常用的实现特性,因为不同的JDBC厂商对这些特性的支持程度各不相同,所以JDBC API中提供了一个DatabaseMetadata接口,应用程序可以使用DatabaseMetadata的实例来确定目前使用的数据源是否支持某一特性。JDBC API中还定义了转义语法,使用JDBC应用程序能够访问JDBC厂商提供的非标准的特性。
获取到JDBC中的Connection对象之后,可以通过Connection对象设置事务属性,并且可以通过Connection接口中提供的方法创建Statement、PreparedStatement或者CallableStatement对象。
Statement接口可以理解为JDBC API中提供的SQL语句的执行器,可以调用Statement接口中定义的executeQuery()方法执行查询操作,调用executeUpdate()方法执行更新操作,调用execute()方法进行统一的操作,然后通过execute()方法的返回值来判断SQL语句类型。最后可以通过Statement接口提供的getResultSet()方法来获取查询结果集,或者通过getUpdateCount()方法来获取更新操作影响的行数。

public ResultSet executeQuery(Connection connection) throws SQLException{
    // 获取Statement对象
    Statement statement = connection.createStatement();
    // 执行查询操作
    return statement.executeQuery("select * from user");
}

2、JDBC API中的类与接口

Java开发人员需要掌握的API,主要包括下面几个接口:
java.sql.Wrapper
java.sql.Connection
java.sql.Statement
java.sql.CallableStatement
java.sql.PreparedStatement
java.sql.DatabaseMetaData
java.sql.ParameterMetaData
java.sql.ResultSet
java.sql.ResultSetMetaData
这些接口都继承了java.sql.Wrapper接口。
JDBC API中的Connection、Statement、ResultSet等接口都继承自Wrapper接口,这些接口都提供了对JDBC驱动原始类型的访问能力。
java_sql1.png
JDBC1.0中使用DriverManager类来产生一个与数据源连接的Connection对象。相对于DriverManager,JDBC2.0提供的DataSource接口是一个更好的连接数据源的方式。
首先,应用程序不需要像使用DriverManager一样对加载的数据库驱动程序信息进行硬编码。开发人员可以选择通过JDNI注册这个数据源对象,然后在程序中使用一个逻辑名称来引用它,JNDI会自动根据给出的名称找到与这个名称绑定的DataSource对象。然后就可以使用这个DataSource对象来建立和数据库的连接了。
其次,使用DataSource接口的第二个优势体现在连接池和分布式事务上。连接池通过对连接的复用,而不是每次需要操作数据源时都新建一个物理连接来显著地提高程序的效率,适用于任务繁忙、负担繁重的企业级应用。
java_sql2.png
javax.sql包下还提供了一个PooledConnection接口。PooledConnection和Connection的不同之处在于,它提供了连接池管理的句柄。一个PooledConnection表示与数据源建立的物理连接,该连接在应用程序使用完后可以回收而不用关闭它,从而减少了与数据库建立连接的次数。
应用程序开发人员一般不会直接使用PooledConnection接口,而是通过一个管理连接池的中间层基础设施使用。当应用程序调用DataSource对象的getConnection方法时,它返回一个Connection对象。但是当我们使用数据库连接池时,该Connection对象实际上是到PooledConnection对象的句柄,这是一个物理连接。连接池管理器维护所有的PooledConnection对象资源。如果在池中存在可用的PooledConnection对象,则连接池管理器返回作为到该物理连接的句柄Connection对象。如果不存在可用的PooledConnection对象,则连接池管理器调用ConnectionPoolDataSource对象的getConnection方法创建新的物理连接。
连接池实现模块可以调用PooledConnection对象的addConnectionEventListener将自己注册成为一个PooledConnection对象的监听者,当数据库连接需要重用或关闭的时候会产生一个ConnectionEvent对象,它表示一个连接事件,连接池实现模块将会得到通知。
java_sql3.png
javax.sql包中还提供了一个RowSet接口,该接口继承自java.sql包下的ResultSet接口。RowSet用于为数据源和应用程序在内容中建立一个映射。RowSet对象可以建立一个与数据源的连接并在其整个生命周期中维持该连接,在这种情况下,该对象被称为连接的RowSet。RowSet对象还可以建立一个与数据源的连接,从其获取数据,然后关闭它,这种RowSet被称为非连接RowSet。非连接RowSet可以在断开时更改其数据,然后将这些更改回写底层数据源,不过它必须重新建立连接才能完成此操作。
相较于java.sql.ResultSet而言,RowSet的离线操作能够有效地利用计算机越来越充足的内存减轻数据库服务的负担。由于数据操作都是在内存中进行的,然后批量提交到数据源,因此灵活性和性能都有了很大的提高。
RowSet默认是一个可滚动、可更新、可序列化的结果集,而且它作为一个JavaBean组件,可以方便地在网络间传输,用于两端的数据同步。通俗来讲,RowSet就相当于数据库表数据在应用程序内存中的映射,我们所有的操作都可以直接与RowSet对象交互。RowSet与数据库之间的数据同步,作为开发人员不需要关注。
java_sql4.png

3、Connection详解

3.1、Connection介绍

一个Connection对象表示通过JDBC驱动与数据源建立的连接。这里的数据源可以是关系型数据库管理系统(DBMS)、文件系统或者其他通过JDBC驱动访问的数据。使用JDBC API的应用程序需要维护多个Connection对象,一个Connection对象可能访问多个数据源,也可以访问单个数据源。
从JDBC驱动的角度来看,Connection对象表示客户端会话,因此它需要一些相关的状态信息,例如用户ID、一组SQL语句和会话中使用的结果集以及事务隔离级别等信息。
一般可以通过两种方式获取JDBC中的Connection对象:
(1)、通过JDBC API中提供的DriverManager类获取。
(2)、通过DataSource接口的实现类获取。
JDBC的驱动:
最常见的驱动类型是Native Protocol Driver,在开发中使用的驱动包基本都是属于此类,通常由数据库厂商直接提供。驱动程序把JDBC调用转换为数据库特定的网络通信协议。

3.2、java.sql.Driver接口

所有的JDBC驱动都必须实现Driver接口,而且实现类都必须包含一个静态初始化代码块。驱动实现类需要在静态初始化代码块中向DriverManager注册一个自己的实例。
例如mysql的驱动代码:

package com.mysql.jdbc;

import java.sql.SQLException;

/**
 * The Java SQL framework allows for multiple database drivers. Each driver should supply a class that implements the Driver interface
 * 
 * <p>
 * The DriverManager will try to load as many drivers as it can find and then for any given connection request, it will ask each driver in turn to try to
 * connect to the target URL.
 * 
 * <p>
 * It is strongly recommended that each Driver class should be small and standalone so that the Driver class can be loaded and queried without bringing in vast
 * quantities of supporting code.
 * 
 * <p>
 * When a Driver class is loaded, it should create an instance of itself and register it with the DriverManager. This means that a user can load and register a
 * driver by doing Class.forName("foo.bah.Driver")
 */
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    /**
     * Construct a new driver and register it with DriverManager
     * 
     * @throws SQLException
     *             if a database error occurs.
     */
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

为了确保驱动程序可以使用这种机制加载,Driver实现类需要提供一个无参数的构造方法。
DriverManager类与注册的驱动程序进行交互时会调用Driver接口中提供的方法。Driver接口中提供了一个acceptsURL()方法,DriverManager类可以通过Driver实现类的acceptsURL()来判断一个给定的URL是否能与数据库成功建立连接。当试图使用DriverManager与数据库建立连接时,会调用Driver接口中提供的connect()方法,
代码如下:

/**
     * Try to make a database connection to the given URL. The driver should return "null" if it realizes it is the wrong kind of driver to connect to the given
     * URL. This will be common, as when the JDBC driverManager is asked to connect to a given URL, it passes the URL to each loaded driver in turn.
     * 
     * <p>
     * The driver should raise an SQLException if the URL is null or if it is the right driver to connect to the given URL, but has trouble connecting to the
     * database.
     * </p>
     * 
     * <p>
     * The java.util.Properties argument can be used to pass arbitrary string tag/value pairs as connection arguments. These properties take precedence over any
     * properties sent in the URL.
     * </p>
     * 
     * <p>
     * MySQL protocol takes the form:
     * 
     * <PRE>
     * jdbc:mysql://host:port/database
     * </PRE>
     * 
     * </p>
     * 
     * @param url
     *            the URL of the database to connect to
     * @param info
     *            a list of arbitrary tag/value pairs as connection arguments
     * 
     * @return a connection to the URL or null if it isn't us
     * 
     * @exception SQLException
     *                if a database access error occurs or the url is null
     * 
     * @see java.sql.Driver#connect
     */
    public java.sql.Connection connect(String url, Properties info) throws SQLException {
        if (url == null) {
            throw SQLError.createSQLException(Messages.getString("NonRegisteringDriver.1"), SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, null);
        }

        if (StringUtils.startsWithIgnoreCase(url, LOADBALANCE_URL_PREFIX)) {
            return connectLoadBalanced(url, info);
        } else if (StringUtils.startsWithIgnoreCase(url, REPLICATION_URL_PREFIX)) {
            return connectReplicationConnection(url, info);
        }

        Properties props = null;

        if ((props = parseURL(url, info)) == null) {
            return null;
        }

        if (!"1".equals(props.getProperty(NUM_HOSTS_PROPERTY_KEY))) {
            return connectFailover(url, info);
        }

        try {
            Connection newConn = com.mysql.jdbc.ConnectionImpl.getInstance(host(props), port(props), props, database(props), url);

            return newConn;
        } catch (SQLException sqlEx) {
            // Don't wrap SQLExceptions, throw
            // them un-changed.
            throw sqlEx;
        } catch (Exception ex) {
            SQLException sqlEx = SQLError.createSQLException(
                    Messages.getString("NonRegisteringDriver.17") + ex.toString() + Messages.getString("NonRegisteringDriver.18"),
                    SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, null);

            sqlEx.initCause(ex);

            throw sqlEx;
        }
    }

该方法有两个参数:第一个参数为驱动能够识别的URL;第二个参数为与数据库建立连接需要的额外参数,例如用户名、密码等。
当Driver实现类能够与数据库建立连接时,就会返回一个Connection对象,当Driver实现类无法识别URL时则会返回null。
JDBC 4.0以上的版本对DriverManager类的getConnection()方法做了增强,可以通过Java的SPI机制加载驱动。符合JDBC 4.0以上版本的驱动程序的JAR包中必须存在一个META-INF/services/java.sql.Driver文件,在java.sql.Driver文件中必须指定Driver接口的实现类。

3.3、java.sql.DriverManager类

DriverManager类通过Driver接口为JDBC客户端管理一组可用的驱动实现,当客户端通过DriverManager类和数据库建立连接时,DriverManager类会根据getConnection()方法参数中的URL找到对应的驱动实现类,然后使用具体的驱动实现连接到对应的数据库。
DriverManager类提供了两个关键的静态方法:
(1)、registerDriver():该方法用于将驱动的实现类注册到DriverManager类中,这个方法会在驱动加载时隐式地调用,而且通常在每个驱动实现类的静态初始化代码块中调用。
(2)、getConnection():这个方法是提供给JDBC客户端调用的,可以接收一个JDBC URL作为参数,DriverManager类会对所有注册驱动进行遍历,调用Driver实现的connect()方法找到能够识别到JDBC URL的驱动实现后,会与数据库建立连接,然后返回Connection对象。
JDBC URL的格式如下:

jdbc:<subprotocol>:<subname>

subprotocol用于指定数据库连接机制由一个或者多个驱动程序提供支持,subname的内容取决于subprotocol。

3.4、javax.sql.DataSource接口

javax.sql.DataSource接口最早是由JDBC2.0版本扩展包提供的,它是比较推荐的获取数据源连接的一种方式,JDBC驱动程序都会实现DataSource接口,通过DataSource实现类的实例,返回一个Connection接口的实现类的实例。
DataSource对象用于表示能够提供数据库连接的数据源对象。如果数据库相关的信息发生了变化,则可以简单地修改DataSource对象的属性来反映这种变化,而不用修改应用程序的任何代码。
DataSource的属性如下:

/** Log stream */
protected transient PrintWriter logWriter = null;

/** Database Name */
protected String databaseName = null;

/** Character Encoding */
protected String encoding = null;

/** Hostname */
protected String hostName = null;

/** Password */
protected String password = null;

/** The profileSql property */
protected String profileSql = "false";

/** The JDBC URL */
protected String url = null;

/** User name */
protected String user = null;

/** Should we construct the URL, or has it been set explicitly */
protected boolean explicitUrl = false;

/** Port number */
protected int port = 3306;

3.5、关闭Connection对象

Connection提供如下方法来关闭连接:

// 当应用程序使用完Connection对象后,应该显示地调用java.sql.Connection对象的close()方法。
// 调用该方法后,由该Connection对象创建的所有Statement对象都会被关闭,一旦关闭Connection对象后,调用Connection的常用方法将会抛出SQLException异常
void close() throws SQLException;

// Connection接口中提供的isClose()方法用于判断应用程序中是否调用了close()方法关闭该Connection对象,
// 这个方法不能用于判断数据库连接是否有效。
boolean isClosed() throws SQLException;

4、Statement详解

Statement接口及其它的子接口PreparedStatement和CallableStatement。Statement接口中定义了执行SQL语句的方法,这些方法不支持参数输入,PreparedStatement接口中增加了设置SQL参数的方法,CallableStatement接口继承自PreparedStatement,在此基础上增加了调用存储过程以及检索存储过程调用结果的方法。

4.1、java.sql.Statement接口

Statement是JDBC API操作数据库的核心接口,具体的实现由JDBC驱动来完成。Statement对象的创建比较简单,需要调用Connection对象的createStatement()方法。

// 获取Statement对象
Statement statement = connection.createStatement();

Statement的主要作用是与数据库进行交互,该接口中定义了一些数据库操作及检索SQL执行结果相关的方法。

// 批量执行SQL
void addBatch(String sql) throws SQLException;
void clearBatch() throws SQLException;
int[] executeBatch() throws SQLException;
// 执行未知SQL语句
boolean execute(String sql) throws SQLException;
boolean execute(String sql, int autoGeneratedKeys) throws SQLException;
boolean execute(String sql, int columnIndexes[]) throws SQLException;
boolean execute(String sql, String columnNames[]) throws SQLException;
// 执行查询语句
ResultSet executeQuery(String sql) throws SQLException;
// 执行更新语句,包括UPDATE、DELETE、INSERT
int executeUpdate(String sql) throws SQLException;
int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException;
int executeUpdate(String sql, int columnIndexes[]) throws SQLException;
int executeUpdate(String sql, String columnNames[]) throws SQLException;
// 执行结果处理
long getLargeUpdateCount() throws SQLException {
        throw new UnsupportedOperationException("getLargeUpdateCount not implemented");
    }
ResultSet getResultSet() throws SQLException;
int getUpdateCount() throws SQLException;
boolean getMoreResults() throws SQLException;
boolean getMoreResults(int current) throws SQLException;
ResultSet getGeneratedKeys() throws SQLException;
// JDBC 4.2新增,数据量大于Integer.MAX_VALUE时使用
long[] executeLargeBatch() throws SQLException {
        throw new UnsupportedOperationException("executeLargeBatch not implemented");
    }
ong executeLargeUpdate(String sql) throws SQLException {
        throw new UnsupportedOperationException("executeLargeUpdate not implemented");
    }
long executeLargeUpdate(String sql, int autoGeneratedKeys)
            throws SQLException {
        throw new SQLFeatureNotSupportedException("executeLargeUpdate not implemented");
    }
long executeLargeUpdate(String sql, int columnIndexes[]) throws SQLException {
        throw new SQLFeatureNotSupportedException("executeLargeUpdate not implemented");
    }
long executeLargeUpdate(String sql, String columnNames[])
            throws SQLException {
        throw new SQLFeatureNotSupportedException("executeLargeUpdate not implemented");
    }
// 取消SQL执行,需要数据库和驱动支持
void cancel() throws SQLException;
// 关闭Statement对象
void close() throws SQLException;
public void closeOnCompletion() throws SQLException;

Statement接口中提供的与数据库交互的方法比较多,具体调用哪个方法取决于SQL语句的类型。
如果使用Statement执行一条查询语句,并返回一个结果集(ResultSet对象),则可以调用executeQuery()方法。
如果SQL语句是一个返回更新数量的DML语句,则需要调用executeUpdate()方法,该方法有几个重载方法。
(1)、int executeUpdate(String sql):执行一个UPDATE、INSERT或者DELETE语句,返回更新数量。
(2)、int executeUpdate(String sql, int autoGeneratedKeys):执行一个UPDATE、INSERT或者DELETE语句。当SQL语句是INSERT语句时,autoGeneratedKeys参数用于指定自动生成的键是否能够被检索,取值为Statement.RETURN_GENERATED_KEYS或Statement.NO_GENERATED_KEYS。当参数值为Statement.RETURN_GENERATED_KEYS时,INSERT语句自动生成的键能够被检索。
(3)、int executeUpdate(String sql, int[] columnIndexes):执行一个UPDATE、INSERT或者DELETE语句,通过columnIndexes参数告诉驱动程序哪些列中自动生成的键可以用于检索。columnIndexes数组用于指定目标表中列的索引,这些列中自动生成的键必须能够被检索。如果SQL语句不是INSERT语句,columnIndexes参数将被忽略。
(4)、int executeUpdate(String sql, String[] columnNames):这个方法的作用和executeUpdate(String sql, int[] columnIndexes)相同,不同的是columnNames参数是一个String数组,通过字段名的方式指定哪些字段中自动生成的键能够被检索。如果SQL语句不是INSERT语句,columnNames参数就会被忽略。
**注意:如果数据库支持返回的更新数量大于Integer.MAX_VALUE,则需要调用executeLargeUpdate()方法。
**
当在执行数据库操作之前,若不确定SQL语句的类型,则可以调用execute()方法。

// 执行一个SQL语句,通过返回值判断SQL类型,当返回值为true时,说明SQL语句为SELECT语句,可以通过Statement接口中的getResultSet()方法获取查询结果集;
// 否则为UPDATE、INSERT或者DELETE语句,可以通过Statement接口中的getUpdateCount()方法获取影响的行数。
boolean execute(String sql) throws SQLException;


// 该方法通过autoGeneratedKeys参数指定INSERT语句自动生成的键是否能够被检索。
boolean execute(String sql, int autoGeneratedKeys) throws SQLException;

// 通过columnIndexes参数告诉驱动程序哪些列中自动生成的键可以用于检索。
boolean execute(String sql, int columnIndexes[]) throws SQLException;

// columnNames参数是一个String数组,通过字段名的方式指定哪些字段中自动生成的键能够被检索。如果SQL语句不是INSERT语句,则columnNames参数会被忽略。
boolean execute(String sql, String columnNames[]) throws SQLException;

另外execute()方法可能返回多个结果。可以通过Statement对象的getMoreResults()方法获取下一个结果,当getMoreResults()方法的返回值为true时,说明下一个结果为ResultSet对象;当返回值为false时,说明下一个结果为影响行数,或者没有更多结果。
默认情况下,每次调用getMoreResults()方法都会关闭上一次调用getResultSet()方法返回的ResultSet对象。但是,可以通过重载参数指定是否关闭ResultSet对象。
Statement接口中定义了3个常量可以用作getMoreResults()的参数,具体如下:
(1)、CLOSE_CURRENT_RESULT:表明当返回下一个ResultSet对象时,当前ResultSet对象应该关闭。
(2)、KEEP_CURRENT_RESULT:表明当返回下一个ResultSet对象时,当前的ResultSet对象不关闭。
(3)、CLOSE_ALL_RESULT:表明当返回下一个ResultSet对象时,当前所有未关闭ResultSet对象都关闭。
如果当前结果是影响行数,而不是ResultSet对象,则getMoreResults()方法的参数将会被忽略,为了确定JDBC驱动是否支持通过getMoreResults()方法获取下一个结果,可以调用DatabaseMetaData接口提供的supportsMultipleOpenResults()方法。

4.2、java.sql.PreparedStatement接口

PreparedStatement接口继承自Statement接口,在Statement接口基础上增加了参数占位符功能。PreparedStatement接口中增加了一些方法,可以为占位符设置值。PreparedStatement的实例表示可以被预编译的SQL语句,执行一次后,后续多次执行时效率会比较高。使用PreparedStatement实例执行SQL语句时,可以使用“?”作为参数占位符,然后使用PreparedStatement接口中提供的方法为占位符设置参数值。
PreparedStatement接口中定义了一系列的Setter方法,用于为SQL语句中的占位符赋值,这些Setter方法名称遵循set格式,其中Type为数据类型。需要注意的是,在使用PreparedStatement对象执行SQL语句之前必须为每个参数占位符设置对应的值,否则调用executeQuery()、executeUpdate()或execute()等方法时会抛出SQLException异常。
PreparedStatement对象设置的参数在执行后不能被重置,需要显式地调用clearParameters()方法清除先前设置的值,再为参数重新设置值即可。
但是当程序执行SQL之后再次调用setXxx()方法覆盖先前设置的值。

4.3、java.sql.CallableStatement接口

CallableStatement接口继承自PreparedStatement接口,在PreparedStatement的基础上增加了调用存储过程并检索调用结果的功能。
CallableStatement对象可以使用3种类型参数:IN、OUT和INOUT。可以将参数指定为序数参数或命名参数,必须为IN或INOUT参数的每个参数占位符设置一个值,必须为OUT或INOUT参数中的每个参数占位符调用registerOutParameter方法。

5、ResultSet详解

ResultSet接口提供了检索和操作SQL执行结果相关的方法。

5.1、ResultSet类型

ResultSet类型主要体现在两个方面:
(1)、游标可操作的方式。
(2)、ResultSet对象的修改对数据库的影响。
后者成为ResultSet对象的敏感性。ResultSet有三种不同的类型,分别说明如下:
(1)、TYPE_FORWARD_ONLY:这种类型的ResultSet不可滚动,游标只能向前移动,从第一行到最后一行,不允许向后移动,即只能使用ResultSet接口的next()方法,而不能使用previous()方法,否则会产生错误。
(2)、TYPE_SCROLL_INSENSITIVE:这种类型的ResultSet是可滚动的,它的游标可以相对于当前位置向前或向后移动,也可以移动到绝对位置。
当ResultSet没有关闭时,ResultSet的修改对数据库不敏感,也就是说对ResultSet对象的修改不会影响对应的数据库中的记录。
(3)、TYPE_SCROLL_SENSITIVE:这种类型的ResultSet是可滚动的,它的游标可以相对于当前位置向前或向后移动,也可以移动到绝对位置。
当ResultSet没有关闭时,对ResultSet对象的修改会直接影响到数据库中的记录。
默认情况下,ResultSet的类型为TYPE_FORWARD_ONLY。DatabaseMetaData接口中提供了一个supportResultSetType()方法,用于判断数据库驱动是否支持某种类型的ResultSet对象。

5.2、ResultSet并行性

ResultSet对象的并行性决定了它支持更新的级别,目前JDBC中支持两个级别,分别如下:
(1)、CONCUR_READ_ONLY:为ResultSet对象设置这种属性后,只能从ResultSet对象中读取数据,但是不能更新ResultSet对象中的数据。
(2)、CONCUR_UPDATABLE:该属性表明,既可以从ResultSet对象中读取数据,又能更新ResultSet中的数据。
ResultSet对象默认的并行性为CONCUR_READ_ONLY,DatabaseMetaData接口中提供了supportsResultSetConcurrency()方法,用于判断JDBC驱动是否支持某一级别的并行性,如果支持就返回true,否则就返回false。

5.3、ResultSet可保持性

调用Connection对象的commit()方法能够关闭当前事务中创建的ResultSet对象。ResultSet对象的holdability属性使得应用程序能够在Connection对象的commit()方法调用后控制ResultSet对象是否关闭。

5.4、ResultSet属性设置

ResultSet的类型、并行性和可保持性等属性可以在调用Connection对象的createStatement()、preparedStatement()或prepareCall()方法创建Statement对象时设置。

Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
                ResultSet.CONCUR_READ_ONLY,
                ResultSet.CLOSE_CURSORS_AT_COMMIT);

5.5、ResultSet游标游动

ResultSet对象中维护了一个游标,游标指向当前数据行。当ResultSet对象第一次创建时,游标指向数据的第一行。ResultSet接口中提供了一系列的方法,用于操作ResultSet对象中的游标:

// 游标向前移动一行,如果游标定位到上一行,则返回true;
// 如果游标位于最后一行之后,则返回false
boolean next() throws SQLException;
// 游标向后移动一行,如果游标定位到上一行,则返回true;
// 如果游标位于第一行之前,则返回false
boolean previous() throws SQLException;
// 游标移动到第一行,如果游标定位到第一行,则返回true;
// 如果ResultSet对象中一行数据都没有,则返回false
boolean first() throws SQLException;
// 移动游标到最后一行,如果游标定位到最后一行,则返回true;
// 如果ResultSet不包含任何数据行,则返回false
boolean last() throws SQLException;
// 移动游标到ResultSet对象的第一行之前,如果ResultSet对象不包含任何数据行,则该方法不生效。
void beforeFirst() throws SQLException;
// 移动游标到ResultSet对象的最后一行之后,如果ResultSet对象不包含任何数据行,则该方法不生效。
void afterLast() throws SQLException;
// 相对于当前位置移动游标,如果参数为0,则游标不移动,如果偏移量超出范围则会移动到第一行或最后一行
boolean relative( int rows ) throws SQLException;
// 游标定位到ResultSet对象中的第row行。
boolean absolute( int row ) throws SQLException;

注意:当ResultSet对象的类型为TYPE_FORWARD_ONLY时,游标只能向前移动,调用其他方法操作游标向后移动时将会抛出SQLException。

5.6、修改ResultSet对象

并行性为CONCUR_UPDATABLE的ResultSet对象可以使用ResultSet接口中提供的方法对其进行更新,包括删除行、更新行、在JDBC驱动支持的情况下,还可以插入新的行。

// 获取Connection对象
Connection connection = testJdbc.buildJdbcConnection();
// 获取Statement对象
Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
                ResultSet.CONCUR_UPDATABLE);
// 执行查询操作
ResultSet resultSet = statement.executeQuery("select * from user");
resultSet.next();
// update
resultSet.updateString("name", "mowenti");
resultSet.updateRow();
// delete
resultSet.absolute(3);
resultSet.deleteRow();
// insert
resultSet.moveToInsertRow();
resultSet.updateInt(1, 4);
resultSet.updateString(2, "jack");
resultSet.updateString(3,"111");
resultSet.updateString(4,"111");
resultSet.updateString(5, "jack");
resultSet.updateDate(6, new Date(123456789));
resultSet.insertRow();
resultSet.moveToCurrentRow();
connection.close();

5.7、关闭ResultSet对象

ResultSet对象在下面两种情况下会显示地关闭:
(1)、调用ResultSet对象的close()方法。
(2)、创建ResultSet对象的Statement或者Connection对象被显式地关闭。
在下面两种情况下ResultSet会被隐式地关闭:
(1)、相关联的Statement对象重复执行时。
(2)、可保持性为CLOSE_CURSORS_AT_COMMIT的ResultSet对象在当前事务提交后会被关闭。