JDBC主要功能及职责
官方文档对JDBC的解释:Java数据库连接 (JDBC,Java Database Connectivity) ,是针对Java编程语言与各种数据库、SQL数据库和其他表格数据源(如电子表格或平面文件)之间独立于数据库的连接的行业标准。其中,JDBC API 是基于 SQL 的数据库访问提供调用级 API。
JDBC是Java语言中提供的访问关系型数据库的接口。
JDBC的对数据源的操作基本如下图:
建立数据源连接(Connection)
-
DataSource (官方推荐方式):调用JDBC 2.0提供的DataSource的getConnection()方法后,DataSource实例会返回一个与数据源建立连接的Connection对象。 UnpooledDataSource dataSource = new UnpooledDataSource(driverClassLoader, "com.mysql.jdbc.Driver", "jdbc:mysql://127.0.0.1/test", "root", "123");
Connection connection = dataSource.getConnection();
JDBC API中只提供了DataSource接口,没有DataSource的具体实现。DataSource具体的实现由JDBC驱动程序提供,如JDBCDataSource,MysqlDataSource等。目前主流的数据库连接池(例如DBCP、C3P0、Druid等)也都实现了DataSource接口
-
DriverManager: JDBC 1.0使用DriverManager类生成一个与数据源连接的Connection对象,通过重载的getConnection()方法,用来获取Connection对象。当应用程序第一次通过URL连接数据源时,DriverManager会自动加载CLASSPATH下所有的JDBC驱动。 Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:association_nested", "SA", "");
DriverManager类会尝试加载“jdbc.drivers”系统属性中引用的驱动程序类。
执行sql语句
数据源Connection建立后,通过Connection接口提供的方法来创建Statement、PreparedStatement或者CallableStatement对象,就可以对数据源进行查询和修改操作。
Connection realConn = conn.getRealConnection();
Statement statement = realConn.createStatement());
statement.executeQuery("select * from t");
Statement接口实际是JDBC API中提供的SQL语句的执行器,Statement接口定义了executeQuery()查询操作、executeUpdate()更新操作、executeBatch()批量操作、execute()查询/更新操作、getResultSet()查询结果集、getUpdateCount()更新操作影响的行数等等执行和返回方法。
a. 如果数据库返回的更新数量大于Integer.MAX_VALUE,则需要调用executeLargeXXX()方法
b.int executeXXX(String sql, int autoGeneratedKeys)等方法,第二个参数(autoGeneratedKeys)可以为int,int[],String[]等类型,通过autoGeneratedKeys/columnIndexes/columnNames参数告诉驱动程序哪些列是自动生成的键可以用于检索。如果SQL语句不是INSERT语句,columnNames参数就会被忽略。
处理SQL执行结果
通过Statement接口执行了sql语句,根据返回的ResultSet获取sql执行后的结果,遍历获取最终的查询结果。ResultSet提供相关的getString(int columnIndex),getBoolean(int columnIndex)等各种方法,根据JDBCType等各种枚举最终将每列查询结果的数据类型转换为Java对应的类型。
关闭连接
当所有Sql语句执行完成,并获取结果集后,通过对应的close()方法,正常关闭Statement和Connection。
JDBC 主要内容
Wrapper
Wrapper接口为使用JDBC的应用程序提供访问原始类型的功能,从而使用JDBC驱动中一些非标准的特性。 Connection,DataSource,Statement,ResultSet,DatabaseMetaData等基础接口继承Wrapper,Wrapper包含unwrap()和isWrapperFor()两个方法:
- unwrap()方法用于返回未经过包装的JDBC驱动原始类型实例,可以通过该实例调用JDBC驱动中提供的非标准的方法。
- isWrapperFor()方法用于判断当前实例是否是JDBC驱动中某一类型的包装类型。
DataSource
DataSource优点:
- 可以通过JNDI注册数据源对象,然后在程序中用一个逻辑名称来引用,JNDI会自动根据这个名称找到与这个名称绑定的DataSource对象。这样就可以使用这个DataSource对象来建立和具体数据库的连接。
- DataSource接口支持连接池和分布式事务上。连接池通过对连接的复用,不需要每次操作数据源时都新建一个物理连接,可以显著地提高程序的效率。
PooledConnection
当应用程序调用方法DataSource.getConnection时,返回一个Connection对象。但是当使用数据库连接池时(例如Druid),Connection对象实际上是到PooledConnection对象的句柄,是一个物理连接。
PooledConnection提供了连接池管理的句柄。PooledConnection对象表示到数据源的物理连接。当应用程序完成连接时,连接可以被回收而不是关闭,从而减少了需要建立的连接数。开发人员一般不直接使用PooledConnection接口,它由管理连接池的中间层基础设施使用,即通过一个管理连接池的中间层基础设施使用。
- 建立连接
连接池管理器(通常是应用程序服务器)维护一个 PooledConnection的对象池。如果池中有可用的 PooledConnection 对象,则连接池管理器返回一个Connection对象,该对象是该物理连接的句柄。如果没有PooledConnection对象可用,连接池管理器调用ConnectionPoolDataSource的(PooledConnection对象工厂)getPoolConnection方法来创建新的物理连接。一般对应实现 ConnectionPoolDataSource 的 JDBC 驱动程序,创建一个新的 PooledConnection 对象并返回一个句柄给它。如MysqlConnectionPoolDataSource实现如下:
public synchronized PooledConnection getPooledConnection() throws SQLException {
try {
Connection connection = this.getConnection();
MysqlPooledConnection mysqlPooledConnection = MysqlPooledConnection.getInstance((JdbcConnection)connection);
return mysqlPooledConnection;
} catch (CJException var4) {
throw SQLExceptionsMapping.translateException(var4);
}
}
- 关闭连接
当应用程序关闭连接时,调用Connection的close方法。当连接池完成时,连接池管理器会收到通知(使用ConnectionPool的addConnectionEventListener方法,将自己注册为ConnectionEventListener对象)。连接池管理器停用PooledConnection对象的句柄并将PooledConnection对象返回到连接池,以便它可以再次使用。因此,当应用程序关闭其连接时,底层物理连接将被回收而不是被关闭。 在连接池管理器调用PooledConnection的close方法之前,物理连接不会关闭。通常调用close方法来有序关闭服务器。
调用Connection对象的commit()方法能够关闭当前事务中创建的ResultSet对象。
- 分布式连接
- XAConnection继承PooledConnection,为分布式事务提供支持的对象。XAConnection 对象可以通过XAResource对象加入分布式事务。事务管理器,通常是中间层服务器的一部分,通过XAResource对象管理XAConnection。应用不直接使用这个接口;它由在中间层服务器中工作的事务管理器使用。
- XAConnection接口继承了PooledConnection接口,具有所有PooledConnection的特性,我们可以调用XAConnection实例的getConnection()方法获取java.sql.Connection对象
RowSet 和 ResultSet
- RowSet接口继承java.sql包下的ResultSet接口,提供了一组 JavaBeans 属性,允许将RowSet实例配置为连接到 JDBC 数据源并从数据源读取一些数据(RowSet用于为数据源和应用程序在内容中建立一个映射)。一组 setter 方法(setInt、setBytes、setString等)提供了一种将输入参数传递给行集的命令属性的方法。此命令是行集在从关系数据库获取数据时使用的 SQL 查询。RowSet接口支持 JavaBeans 事件,允许在行集上发生事件时通知应用程序中的其他组件,例如其值的更改。
相较于java.sql.ResultSet,RowSet的离线操作能够有效地利用计算机内存减轻数据库的负担。由于数据操作都是在内存中进行,然后批量提交到数据源,因此灵活性和性能有很大的提高。RowSet默认是一个可滚动、可更新、可序列化的结果集,而且它作为一个JavaBean组件,可以方便地在网络间传输,用于两端的数据同步。通俗来讲,RowSet就相当于数据库表数据在应用程序内存中的映射,我们所有的操作都可以直接与RowSet对象交互。RowSet与数据库之间的数据同步,开发人员不需要关心。
- ResultSet的类型、并行性和可保持性等属性可以在调用Connection对象的createStatement()、prepareStatement()或prepareCall()方法创建Statement对象时设置,例如:
Connection connection = DriverManager.getConnection("abc");
Statement statement = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE, ResultSet.CLOSE_CURSORS_AT_COMMIT);
ResultSet类型
TYPE_FORWARD_ONLY(默认):游标只能向前移动,从第一行到最后一行
TYPE_SCROLL_INSENSITIVE:游标可以向前/向后滚动(相对于当前位置),也可以滚动到相对位置,ResultSet对象的修改不会影响对应的数据库中的记录
TYPE_SCROLL_SENSITIVE:游标可以向前/向后移动(相对于当前位置),也可以移动到绝对位置。ResultSet对象的修改会直接影响数据库中的记录
ResultSet并行性
CONCUR_READ_ONLY(默认):为ResultSet对象设置这种属性后,只能从ResulSet对象中读取数据,但是不能更新ResultSet对象中的数据。
CONCUR_UPDATABLE:该属性表明,既可以从ResulSet对象中读取数据,又能更新ResultSet中的数据。
ResultSet可保持性
HOLD_CURSORS_OVER_COMMIT:当调用Connection对象的commit()方法时,不关闭当前事务创建的ResultSet对象。
CLOSE_CURSORS_AT_COMMIT:当前事务创建的ResultSet对象在事务提交后会被关闭,这样能够提升系统性能。
ResultSet对象关闭后,不会关闭由ResultSet对象创建的Blob、Clob、NClob或SQLXML对象,除非调用这些对象的free()方法进行清除
事务
Connection接口中提供了一个setTransactionIsolation()方法,允许JDBC客户端设置Connection对象的事务隔离级别。 Connection对象的autoCommit属性决定什么时候结束事务。启用自动提交后,会在每个SQL语句执行完毕后自动提交事务。当Connection对象创建时,默认情况下,事务自动提交是开启的。Connection接口中setAutoCommit()方法,可以禁用事务自动提交。这种情况下,需要调用Connection接口的commit()方法进行显式提交事务,或者调用rollback()方法回滚事务。禁用事务自动提交适用于需要将多个SQL语句作为一个事务提交或者事务由应用服务器管理。
“老生常谈”的事务隔离级别:
- 脏读、幻读、不可重复读
- 脏读,读取未提交的数据导致的。例如,A事务修改了一条数据,但是未提交修改,此时A事务对数据的修改对其他事务是可见的,B事务中能够读取A事务未提交的修改。如果A事务回滚,B事务中读取的就是不正确的数据。
- 不可重复读,(1)A事务中读取一行数据。(2)B事务中修改了该行数据。(3)A事务中再次读取该行数据将得到不同的结果。
- 幻读,(1)A事务中通过WHERE条件读取若干行。(2)B事务中插入了符合条件的若干条数据。(3)A事务中通过相同的条件再次读取数据时将会读取到B事务中插入的数据。
- 几种事务隔离级别如下:
- TRANSACTION_NONE:表示驱动不支持事务,不兼容JDBC规范的驱动程序。
- TRANSACTION_READ_UNCOMMITTED:允许事务读取未提交的数据,可能会出现脏读、不可重复读、幻读等现象。
- TRANSACTION_READ_COMMITTED:在事务中进行的任何数据更改,在提交之前对其他事务是不可见的。可以防止脏读,不能解决不可重复读和幻读。
- TRANSACTION_REPEATABLE_READ:能够解决脏读和不可重复读,但是不能解决幻读。
- TRANSACTION_SERIALIZABLE:事务串行执行,能够有效解决脏读、不可重复读和幻读题,但是并发效率较低
参考资料
JDBC 4.2规范文档:https://download.oracle.com/otndocs/jcp/jdbc-4_2-mrel2-spec/index.html。
JDBC在Java 8中相关的功能:https://docs.oracle.com/javase/8/docs/technotes/guides/jdbc/jdbc_42.html
JTA规范文档:http://download.oracle.com/otndocs/jcp/jta-1.1-spec-oth-JSpec/?submit=Download
|