JDBC详细介绍

JDBC详细介绍

【摘要】JDBC详细介绍~

前言

hi~

JDBC

JDBC概念

JDBC就是为了让java语言有能力去操作数据库,通过它可以发送sql命令,并取得返回的结果。

JDBC, Java Database Connectivity, JAVA数据库互联。
ODBC, Open Database Connectivity, 开放式数据库互联。
这个标准是由MS公司主导的一个标准,所有的数据库厂商的产品都实现了ODBC规范,并且这个标准是采用c语言编写的。这样一来,程序员可以通过ODBC来访问/操作数据库产品。

java语言刚开始时,也是通过ODBC来访问数据库的,但是,java语言需要把java的调用转换成符合ODBC规范的c调用,这样一来,效率就会很低。

在java语言访问数据库的驱动发展历程中,经历了以下4代

A. JDBC-ODBC桥接
B. 本地部份java驱动
C. 基于网络的本地部份java驱动
D. 本地纯java驱动 [第四代驱动]:所有的DB产品的产品都实现了JDBC规范,使用 java语言写。这个实现也叫 驱动[driver]。

所以,JDBC的API 有两部份组成

  1. 是 SUN 公司提供的 JDBC标准接口 和工具类。
    java.sql 包[核心包]
    javax.sql 包[扩展包]
  2. JDBC的实现者,也就是各DB厂商的驱动程序。

JDBC有一个很大的优点,就是可以跨DB平台,一次编写,到处[数据库]运行。

JDBC核心包中的核心接口
java.sql.Driver 接口 驱动类接口,实现JDBC驱动的数据库产品必需要实现此接口java.sql.DriverManager 驱动管理类,它是连接的桥接器,它可以获取Connection。
注:在JDBC4.0规范中,做了修改,可以不需要事先注册驱动了。

1
2
3
4
5
java.sql.Connection  连接[可以想象成客户端程序与DB之间的网络通道]
java.sql.Statement 语句[负责发送和执行sql命令]
\- PreparedStatement 预处理语句[支持动态绑定参数的sql命令]
\- CallableStatement [负责发送和执行 plsql 命令]
java.sql.ResultSet 结果集[如果是查询命令,则由此对象负责带回查询的结果]

JDBC编程步骤

  1. 注册数据库驱动
    1
    2
    //DriverManager.registerDriver(具体的驱动类)
    Class.forName("驱动的全限定类名"); //它仅仅是一个字符串
  • 注:不同的数据库产品,驱动类名不一样,如:
    Oracle => oracle.jdbc.OracleDriver
    mysql => com.jdbc.mysql.Driver
    DB2 => i don’t know
    sqlserver => i don’t know
  • 注:在JDBC4.0规范中,此步可以省略[DriverManager会自动搜索项目的类路径,找到并加载目标驱动,可以有多个]
  1. 建立连接
    1
    2
    3
    4
    String url = "jdbc:oracle:thin:@127.0.0.1:1521:XE";
    String user = "jsd1707";
    String pwd = "jsd1707";
    Connection conn = DriverManager.getConnection(url, user, pwd);
  2. 创建Statement
    1
    Statement stmt = conn.createStatement(); //空参
  3. 发送并执行 sql命令
    1
    2
    3
    4
    String sql = "select id,first_name,salary,start_date from s_emp"; //准备的sql命令
    ResultSet rs = stmt.executeQuery(sql); //执行查询语句
    //boolean b = stmt.execute(sql); //执行任意SQL语句
    int count = stmt.executeUpdate(sql); //执行非查询语句
  4. 如果是查询命令,则处理结果集
    1
    2
    3
    4
    5
    6
    7
    8
    while(rs.next()) { //循 环读到结果集中的数据
    //取列值
    int id = rs.getInt(1); //取第一列
    String fn = rs.getString(2);
    double sal = rs.getDouble(3);
    Date sd = rs.getDate(4);
    //...
    }
  5. 释放资源
    1
    2
    3
    rs.close();
    stmt.close();
    conn.close();

使用PreparedStatement 来执行sql命令

  1. 它的好处:

    A.可以动态绑定参数,避免了SQL字符串的拼接,更加安全。
    B.支持预编译,事先把sql送到数据库中编译、优化,然后,给定参数后再执行,如果执行多次话,只需要给出不同的参数即可,SQL本身无需再次编译、优化。

  2. 缺点:

    一个PreparedStatement只能绑定1条sql命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//
String sql = "select id,name from s_dept where id = ? and name like ?";
PreparedStatement pstmt = conn.prepareStatement(sql); //初始化时要传入sql
//在执行之前,要设定参数
pstmt.setInt(1,10); //把id的值设为 10
pstmt.setString(2, "hehe");

//
String sql = "select id,name from s_dept";
Statement stmt = conn.createStatement();
//
rs = stmt.executeQuery(sql);
//
stmt.executeUpdate("another sql command");

JDBC事务 [transaction]

Why use?

注:当删除主表记录时,如果这个记录有子记录存在,则删除是会失败的。

如果你想在删除主记录时,一起把子记录删除,有如下两种方式

  1. 编程式
    在JDBC代码中,先删除子记录,再删除主记录,如:

    1
    2
    3
    4
    delete from tbl_emp where dept_id = ?;

    delete from tbl_dept where id = ?
    --这两个?处的值是一样的。

    注:这种方式有一个很大的缺点,就是 牵一发而动全身。

  2. 就在建表时,指定外键在删除时的处理方式
    当我们在添加外键约束时,可以指定如下两个选项:

    A. ON DELETE CASCADE
    B. ON DELETE SET NULL
    如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    create table tbl_dept(
    id number primary key,
    name varchar2(255)
    );
    create table tbl_emp(
    id number primary key,
    ...,
    dept_id number,
    --添加外键
    foreign key(dept_id) references tbl_dept(id) ON DELETE CASCADE
    );

    JDBC事务概念

    JDBC事务 [transaction]为了让JDBC跨多个数据库平台,JDBC规范中必然要制定出它自己的事务规范,因为不同的数据库对事务的粒度控制是不一样的。

比如:ORACLE中不允许有脏读,但是,在mysql中可以【需要设置】,它们的级别也不一样,这对于我们编写JDBC程序来说,就比较麻烦。

所以,我们程序人员就针对JDBC事务做出处理,而不是去编写与数据库产品相关的事务代码,在JDBC规范中,提供了事务的隔离级别的控制。

在Connection接口中,定义了5个常量来分别表示不同的JDBC事务隔离级别:

1
2
3
4
5
TRANSACTION_NONE                0
TRANSACTION_READ_UNCOMMITTED 1
TRANSACTION_READ_COMMITTED 2
TRANSACTION_REPEATABLE_READ 4
TRANSACTION_SERIALIZABLE 8

其中,它们由数据库产品进行实现。
注:设置连接的事务隔离级别不一定有效果。

在Connection中,提供了方法:setTransactionIsolation(int level) 来设置级别。

另外,在JDBC中,默认情况下,事务是自动提交的[Auto Commit]。也就是执行一次sql命令,就自动提交事务一次。

如果你想手动控制事务,则需要:

1
2
3
4
conn.setAutoCommit(false); //

conn.commit(); //手动提交
conn.rollback(); //手动回滚

JDBC批处理[Batch]

JDBC从2.0规范开始,就支持批处理了,它的目的是为了提高数据插入的效率。利用它,可以高效地插入海量的数据到表中。
1.利用Statement执行批处理

1
2
3
4
5
6
7
8
9
10
11
//Code:
String sql1 = "";
String sql2 = "";
String sql3 = "";
//...
//我们可以把这些sql命令通过 addBatch方法添加到批处理命令列表中。
stmt.addBatch(sql1);
stmt.addBatch(sql2);
stmt.addBatch(sql3);
//...
int[] results = stmt.executeBatch();

所以,Statement的批处理适合用来加载并执行sql脚本文件中的命令。

2.利用PreparedStatement 来做批处理

1
2
3
4
5
6
7
8
9
10
11
//Code
String sql = "insert into tbl_dept(id,name) values(?,?)";
//
PreparedStatement pstmt = conn.prepareStatement(sql);
//绑定参数
pstmt.setString(2,xxx);
//把这组参数添加到 批处理中
pstmt.addBatch();
//...
//执行批处理
int[] result = pstmt.executeBatch();

所以,PreparedStatement 适合用来批量插入同一种结构的数据.

注:select命令是不能使用批处理的。

针对PreparedStatement

1
2
3
4
5
6
7
8
9
//Code
Connection conn = ....;
String sql = "select id,name from tbl_dept";
PreparedStatement pstmt = conn.prepareStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE,
sql);
//通过此PreparedStatement 执行的查询结果集是可以滚动、可以更新的。
//...

JDBC元数据

元数据的定义: 用来描述其它数据的数据。它的API有:

1
2
3
4
5
6
7
8
9
10
11
12
DatabaseMetaData    数据库元数据
它描述了数据库相关的数据

ResultSetMetaData 结果集元数据
它描述了结果集相关的数据。
getColumnCount() 得到此结果集中的总列数
getColumnName(int index)
getColumnType(int index) => int 详见API: Types
...

ParameterMetaData 参数元数据
与存储过程【PLSQL】调用有关的元数据。

sql异常

sql异常概述

java.sql.SQLException 【已检查异常】它是JDBC规范中顶级的异常,所有的JDBC异常都是继承于此类,如:

1
2
3
4
5
6
7
8
9
10
11
    SQLSyntaxErrorException     执行sql的语法有错误
SQLIntegrityConstraintViolationException
SQLFeatureNotSupportedException
SQLInvalidAuthorizationSpecException
SQLTimeoutException
BatchUpdateException
...

SQLException的其它方法
getErrorCode();
getNextException();

1
2
3
4
5
6
7
8
9
10
11
12
try {
...
...
} catch (SQLException e) {
//获取此sql异常的错误码
int code = e.getErrorCode();
//如果想把整个异常链都显示出来
while(e != null) {
e.printStackTrace();
e = e.getNextException();
}
}

有关批处理的异常

1
2
BatchUpdateException
int[] getUpdateCounts();

连接池 [connection pools]

概念

它是一种维护多个数据库连接的对象,也就是一种数据源的实现方式。在实际的开发中,连接池是比较常用的,因为它能够大大提高连接的使用效率。
正是因为普通的数据库连接使用效率太低,所以,JDBC规范中提出了连接池,它通过javax.sql.DataSource这个对象来做为获取连接的标准。

DataSource 数据源,它是一个规范[接口],它有三种实现:

1.普通实现,也就是 DriverManager获取连接的方式
2.连接池实现
3.分布式事务实现

获取连接的方式

我们现在获取连接的方式:

1
2
3
Connection conn = DriverManager.getConnection(url,user,pwd);
//...
conn.close(); //用完后,就关闭【也就是与RDBMS真正的断开连接】

连接池的实现

一般我们第三方的实现,如:

Apache组织的 commons-dbcp 组件就一个开源的连接池实现。
或者是一些轻量级的中间件产品,如:Tomcat/Jetty/Resin…

有了数据源,我们获取连接的方式:

1
2
数据源 数据源 = ...;
Connection conn = 数据源.getConnection();

所以,问题变成了我们在代码中如何得到 数据源对象?

主流的方式: 通过 JNDI 来获取[要有中间件、容器的支持]
备选方式:下载第三方的实现者,并通过API来创建.

有关commons-dbcp 组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
核心类:
BasicDataSource
//代码片断
BasicDataSource bds = new BasicDataSource();
//给这个数据源设置相关的属性
bds.设置驱动类
bds.设置URL
bds.设置用户名
bds.设置密码
//与池有关的属性
bds.设置初始池的大小
bds.设置池的最大连接数
bds.设置池的最小连接数
bds.设置获取连接的最大等待时长
bds.设置连接的最大空闲时间
//...

分布式事务的实现

它是基于连接池的,它更加复杂,一般也是由第三方提供,而且免费的产品不多,大多都是商业化的、收费的产品,如:

1
2
3
4
5
IBM  Webshere
ORACLE weblogic
JBOSS JBOSS AS
oracle glassfish [免费的]
...

它的两个核概念

1.中央事务管理器
2.二阶段提交 [two phase commit]

结束语

bye~

评论