一、JDBC分类
JDBC驱动程序按照工作方式分为四类:
1、JDBC-ODBC bridge + ODBC 驱动
JDBC-ODBC bridge桥驱动将JDBC调用翻译成ODBC调用,再由ODBC驱动翻译成访问数据库命令。
优点:可以利用现存的ODBC数据源来访问数据库。
缺点:从效率和安全性的角度来说的比较差。不适合用于实际项目。
2、基于本地API的部分Java驱动
我们应用程序通过本地协议跟数据库打交道。然后将数据库执行的结果通过驱动程序中的Java部分返回给客户端程序。
优点:效率较高。
缺点:安全性较差。
3、纯Java的网络驱动
(中间协议) (本地协议)
app JDBC 纯Java 中间服务器 DB
缺点:两段通信,效率比较差
优点:安全性较好
4、纯Java本地协议:通过本地协议用纯Java直接访问数据库。
特点:效率高,安全性好。
二、JDBC 编程的步骤
1、加载和注册数据库驱动Driver
1.1 使用Class.forName(driverName)自动注册,需要把驱动的jar包导进来,需处理异常,driverName为各数据库厂商驱动名,如:
String driverName = "com.mysql.jdbc.Driver";//MySQL的
String driverName = "oracle.jdbc.driver.OracleDriver";//Oracle的
1.2 实例化具体的Driver驱动:
Driver driver = new com.mysql.jdbc.Driver();
DriverManager.registerDriver(driver);
这种写法一般不用,不能参数化驱动名,不够灵活,一般使用上面第一种方式。每个数据库厂商会实现Driver接口,并会调用DriverManager的静态registerDriver方法,如MySQL实现的:
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
2、连接数据库
conn = DriverManager.getConnection(url, username, password);//需处理异常
//Connection返回数据库连接,如:“com.mysql.jdbc.Connection@1ffb8dc”;连接不成功则返回 null
3、创建Statement对象
stmt = conn.createStatement();//需处理异常,返回其生成结果的对象
/*为了类型安全和批量更新的效率,建议用PreparedStatement
String sql = "insert into tableName values(?,?)"; // "?"占位符
stmt = conn.prepareStatement(sql); */
4、操作数据库,执行SQL语句
String sql = "select * from tableName";
rs = stmt.executeQuery(sql); //executeQuery(sqlString) 查询 返回查询结果集
String sql = "insert into tableName values(?,?)"; // "?"占位符
int number = stmt.executeUpdate(sql);//更新,再返回int(更新、修改影响的条数)
//executeUpdate(sql) 执行给定 SQL 语句,如 INSERT、UPDATE 或 DELETE 等不返回任何内容的语句
//用PreparedStatement时,已经有语句: rs = stmt.executeQuery();
5.处理数据(游标)
StringBuffer sb = new StringBuffer();
ResultSetMetaData md = rs.getMetaData(); //ResultSetMetaData可获取列的类型和属性信息
int col = md.getColumnCount(); //获取列的数目
while(rs.next()){ //rs.next()使游标下移一位,返回boolean,没有下一个结果则返回false
for(int i=1; i<=col;i++){ // index(JDBC 的下标从1开始)
sb.append(md.getColumnName(i)+"="+rs.getString(i)+" ");
}
sb.append("\n");
}
System.out.println(sb);
游标的初始位置在第一条记录的前面,使第一次调用next()后,刚好拿到第一个结果。
游标的最终位置在最后一条记录的后面(结果集的前面和后面留空,真正存在)
6、释放资源,断开与数据库的连接
先判断是否有引用资源,再释放,释放空资源会抛异常:
if (rs != null) {
try {
rs.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
注意顺序,要按先ResultSet结果集,后Statement,最后Connection的顺序关闭资源,因为 ResultSet需要Statement和Connection连接时才可以用的;Statement也需要Connection才可用;结束 Statement之后有可能其它的Statement还需要连接,因此不能先关闭Connection,ResultSet同理。
JDBC访问数据库例子:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
public class JdbcTest {
public static void main(String[] args) {
String driverName = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://192.168.0.248:3306/test?characterEncoding=UTF-8";
String username = "huisou";
String password = "huisou";
Connection con = null;
PreparedStatement stmt = null;
try {
Class.forName(driverName);
con = DriverManager.getConnection(url, username, password);
String query = "insert into student(age,name) values (?,?)";
stmt = con.prepareStatement(query);
stmt.setInt(1, 25);
stmt.setString(2, "lilei");
stmt.executeUpdate();
}
catch (Exception e) {
e.printStackTrace();
}
finally {
if (stmt != null)
try {
stmt.close();
}
catch (Exception e) {
e.printStackTrace();
}
if (con != null)
try {
con.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
三、java.sql和javax.sql
JDBC中包括了两个包:java.sql和javax.sql
1、java.sql 这个包中的类和接口主要针对基本的数据库编程服务,如生成连接、执行语句以及准备语句和运行批处理查询等。同时也有一些高级的处理,比如批处理更新、事务隔离和可滚动结果集等。
2、javax.sql 主要为数据库方面的高级操作提供了接口和类。如为连接管理、分布式事务和旧有的连接提供了更好的抽象,它引入了容器管理的连接池、分布式事务和行集等。
四、JDBC对象解释
1、Connection 数据库连接对象,与特定数据库的连接(会话),能够通过getMetaData方法获得数据库提供的信息、所支持的SQL语法、存储过程和此连接的功能等信息。
2、Driver 每个驱动程序类必需实现的接口,每个数据库驱动程序也都应该提供一个实现Driver接口的类。
3、DriverManager 工具类,管理一组JDBC驱动程序的基本服务。作为初始化的一部分,此接口会尝试加在”jdbc.drivers”系统属性中引用的驱动程序。只是一个辅助类。
4、Statement 用于执行静态SQL语句并返回其生成结果的对象,在向数据库发送相应的SQL语句时,都需要创建Statement接口或 PreparedStatement接口。在具体应用中,Statement主要用于操作不带参数(可以直接运行)的SQL语句,比如删除语句、添加或更 新。
5、PreparedStatement 继承Statement接口,表示预编译的SQL语句的对象,SQL语句被预编译并且存储在PreparedStatement对象中。使用PreparedStatement有下面三步:
一:通过连接获得PreparedStatement对象,用带占位符(?)的sql语句构造。
PreparedStatement pstm = con.preparedStatement(“insert into student(age,name) values (?,?)”);
二:设置参数
stmt.setInt(1, 25);
stmt.setString(2, "lilei");
三:执行sql语句
Rs = pstm.excuteQuery();
Statement发送完整的Sql语句到数据库不是直接执行而是由数据库先编译,再运行。每次都需要编译。而PreparedStatement是先发送带参数的Sql语句,由数据库先编译,再发送一组组参数值(同构时不需重复编译)。
同构:两个Sql语句可编译部分是相同的,只有参数值不同;
异构:整个sql语句的格式是不同的;
如果是同构的sql语句,PreparedStatement的效率要比statement高。而对于异构的sql则两者效率差不多。一般都用 PreparedStatement代替Statement,因为它是类型安全的。Statement对参数类型不作检查,故不够安全。使用时注意下面几 点:
1、使用预编译的Statement编译多条Sql语句一次执行
2、可以跨数据库使用,编写通用程序
3、能用预编译时尽量用预编译
4、如果第二个SQL语句与前一个是异构的,需要再次编译“ps = con.prepareStatement(sql);“
6、CallableStatement 用来访问数据库中的存储过程,它提供了一些方法来指定语句所使用的输入/输出参数。
7、ResultSet 查询结果集接口,它对返回的结果集进行处理。
8、 ResultSetMetaData 对元数据进行操作的接口,可以实现很多高级功能,Hibernate运行数据库的操作,大部分都是通过此接口。可以认为,此接口是SQL查询语言的一种反 射机制。ResultSetMetaData接口可以通过数组的形式,遍历数据库的各个字段的属性。
JDBC通过元数据(MetaData)来获得具体的表的相关信息,例如,可以查询数据库中有哪些表,表有哪些字段,以及字段的属性等。MetaData中通过一系列getXXX将这些信息返回给我们。
JDBC通过元数据(MetaData)来获得具体的表的相关信息,例如,可以查询数据库中有哪些表,表有哪些字段,以及字段的
数据库元数据 Database MetaData 用connection.getMetaData()获得;包含了关于数据库整体元数据信息。
结果集元数据 ResultSet MetaData 用resultSet.getMetaData()获得;比较重要的是获得表的列名,列数等信息。
结果集元数据对象:ResultSetMetaData meta = rs.getMetaData();
字段个数:meta.getColomnCount();
字段名字:meta.getColumnName();
字段JDBC类型:meta.getColumnType();
字段数据库类型:meta.getColumnTypeName();
数据库元数据对象:DatabaseMetaData dbmd = con.getMetaData();
数据库名:dbmd.getDatabaseProductName();
数据库版本号:dbmd.getDatabaseProductVersion();
数据库驱动名:dbmd.getDriverName();
数据库驱动版本号:dbmd.getDriverVersion();
数据库Url:dbmd.getURL();
该连接的登陆名:dbmd.getUserName();
五、JDBC中的事务编程
1、事务是具备以下特征(ACID)的工作单元:
原子性(Atomicity)—— 如果因故障而中断,则所有结果均被撤消;
一致性(Consistency)—— 事务的结果保留不变;
孤立性(Isolation)—— 中间状态对其它事务是不可见的;
持久性(Durability)—— 已完成的事务结果上持久的。
原子操作,也就是不可分割的操作,必须一起成功一起失败。
2、事务处理三步曲:(事务是一个边界)
① connection.setAutoCommit(false); //把自动提交关闭;在创建Statement对象之前。
② 正常的DB操作 //若有一条SQL语句失败了,自动回滚
③ connection.commit() //主动提交
和 connection.rollback() //主动回滚,一般写在catch语句里,而前三个都写在try语句里
try{
con.setAutoCommit(false); //step① 把自动提交关闭
Statement stm = con.createStatement(); //step② 正常的DB操作
stm.executeUpdate("insert into person(id, name, age) values(520, 'X-Man', 18)");
stm.executeUpdate("insert into Person(id, name, age) values(521, 'Super', 19)");
con.commit(); //step③ 成功主动提交
} catch(SQLException e){
try{con.rollback(); //如果中途发生异常,则roolback;这语句也会抛异常
}catch(Exception e){e.printStackTrace();} //step③ 失败则主动回滚
}
3、JDBC事务并发产生的问题和事务隔离级别
JDBC事务并发产生的问题:
① 脏读(Dirty Reads) 一个事务读取了另一个并行事务还未提交的数据。(产生原因:读-写)
② 不可重复读(UnPrpeatable Read)一个事务前后两次读取数据时,得到的数据不一致,被另一个已提交的事务修改。
③ 幻读(Phantom Read) 一个事务再次查询,记录中的量变化了。(仅对统计有影响)
为了避免以上三种情况的出现,则采用事务隔离级别:
TRANSACTION_NONE 不使用事务(不可能用,只是理论的)
TRANSACTION_READ_UNCOMMITTED 可以读取未提交数据(允许脏读,也不可能)
TRANSACTION_READ_COMMITTED 只读提交的数据:可防止脏读;大部分数据库的默认隔离级别
TRANSACTION_REPEATABLE_READ 重复读取;只可以避免脏读
TRANSACTION_SERIALIZABLE 事务串行化:可以避免脏读,重复读取和幻读,但会降低数据库效率(最常用)
以上的五个事务隔离级别都是在Connection类中定义的静态常量。隔离级别越高,数据越安全,并发能力越差。
使用setTransactionIsolation(int level) 方法可以设置事务隔离级别。
比如:con.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
六、Scrollability 结果集可滚动
可双向支持绝对与相对滚动,对结果集可进行多次迭代。
Con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
//上句的 SCROLL 再到 CONCUR;不可以写反,编译器无法检测到,因为他们都是int类型的。
TYPE_FORWARD_ONLY:(单向,一般不用)该常量指示指针只能向前移动的 ResultSet 对象的类型。
TYPE_SCROOL_INSENSITIVE:(双向、不敏感)可滚动但不受其他更改影响的 ResultSet 对象的类型。
TYPE_SCROLL_SENSITIVE:(双向、敏感)该常量指示可滚动并且通常受其他的更改影响的 ResultSet 对象的类型。
CONCUR_READ_ONLY:(只读)该常量指示只可以读取的 ResultSet 对象的并发模式。
CONCUR_UPDATABLE:(可更新)该常量指示可以更新的 ResultSet 对象的并发模式。
绝对定位:boolean absolute(int row)将游标移动到指定位置。(row指记录的序号,没有这位置则返回false)
void afterLast() 将游标指向最后一条记录的后一位(有这位置,但记录为空)。
void beforeFirst()将游标指向最前一条记录的前一位。
boolean first()将游标移动到结果集最前。
boolean last()将游标移动到结果集末尾。
相对定位:boolean next()指向下一个。
boolean previous()指向前一个。
boolean relative(int) 向next()方向移动 int 位(int 可负)。
判位函数:boolean isAfterLast() 是否在最后一条的后一位。
boolean isBeforeFirst() 是否最前一条记录的前一位。
boolean isFirst() 是否最前位置。
boolean isLast() 是否最后位置。
七、Updatability结果集可更新
rs.updateString(“name”,”Tony”);//前面一个是字段的名字或者序号
rs.updateInt(1,”122323”);修改
rs.deleteRow();删除
rs.updateRow();
只有在必要的时候(如桌面应用)才用结果集更新数据库,因为使用结果集更新数据库效率低下。可更新结果集还要看数据库驱动程序是否支持,如Oracle就 支持,MySql不支持。并且只能针对一张表做结果集更新(不能子查询),而且不能有join操作,必须有主健,必须把所有非空没有默认值的字段查出,处 理可更新结果集时不能用select *来查询语句,必须指出具体要查询的字段,不能使用通配符。
八、Batch updates 批量更新
将多组对数据库的更新操作发送到数据库统一执行(数据库支持并发执行操作),以提高效率。主要是通过减少数据(Sql语句或参数)在网络上传输的次数来节省时间。
1、对于Statement的批量更新处理:
stm.addBatch(Sql);
int[] ResultSet=stm.executeBatch();
2、对于PreparedStatement的批量更新处理
pstm.setInt(1,12);pstm.setString(2,”gaga”);
pstm.addBatch();
int[] ResultSet=pstm.executeBatch();
int[] 中每一个数表示该Sql语句影响到的记录条数,PreparedStatement的更新操作比Statement的更新操作多了一个设置参数的过程。
九、JNDI 命名目录服务器
JNDI(Java Naming and Directory Interface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口,类似JDBC都是构建在抽象层上。 JNDI的方法是在javax.naming包下,接口是Context实现类是InitialContext,是Java的命名目录服务器。而JDBC 是Java的数据库访问接口。
bind(String name, Object obj) 将名称绑定到对象资源,建立指定的字符串和对象资源的关联
lookup(String name) ,通过指定的字符串获得先前绑定的资源
JNDI与JDBC:
JNDI提供了一种统一的方式,可以用在网络上查找和访问服务。通过指定一个资源名称,该名称对应于数据库或命名服务中的一个纪录,同时返回数据库连接建立所必须的信息。
代码示例:
try {
Context cntxt = new InitialContext();
DataSource ds = (DataSource) cntxt.lookup("jdbc/dpt");
}
catch (NamingException ne) {
...
}
十、DataSourse 数据源
package javax.sql;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Wrapper;
public interface DataSource extends CommonDataSource,Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password) throws SQLException;
}
用于提供到此
DataSource
对象所表示的物理数据源的连接。作为
DriverManager
工具的替代项,
DataSource
对象是获取连接的首选方法。实现
DataSource
接口的对象通常在基于 Java
TM Naming and Directory Interface (JNDI) API 的命名服务中注册。
<!-- Generated by javadoc (build 1.6.0-beta2) on Mon Mar 19 18:32:23 CST 2007 -->
<noscript></noscript>DataSource
接口由驱动程序供应商实现。共有三种类型的实现:
-
- 基本实现 - 生成标准的
Connection
对象 - 连接池实现 - 生成自动参与连接池的
Connection
对象。此实现与中间层连接池管理器一起使用。 分布式事务实现 - 生成一个
- 基本实现 - 生成标准的
Connection
-
- <!-- Generated by javadoc (build 1.6.0-beta2) on Mon Mar 19 18:32:23 CST 2007 -->
- 对象,该对象可用于分布式事务,大多数情况下总是参与连接池。此实现与中间层事务管理器一起使用,大多数情况下总是与连接池管理器一起使用。
<!-- Generated by javadoc (build 1.6.0-beta2) on Mon Mar 19 18:32:23 CST 2007 -->
<noscript></noscript>通过 DataSource
对象访问的驱动程序本身不会向 DriverManager
注册。通过查找操作获取 DataSource
对象,然后使用该对象创建 Connection
对象。使用基本的实现,通过 DataSource
对象获取的连接与通过 DriverManager
设施获取的连接相同。
1、包含了连接数据库所需的信息,可以通过数据源获得数据库连接,有时由于某些连接数据库的信息会变更,所以经常使用包含数据库连接信息的数据源。
2、一个标准的数据库连接工厂,作为DriverManager的替代项,保存与数据库相关的信息, 可以将数据库的连接信息放在一个共享的空间进行提取,不用在本地安装。
支持JNDI的绑定,支持连接池,支持分布式服务,用getConnection方法可获得与数据库的连接。 数据源应该由管理员创建(目的是为了保证数据库的安全)。所以数据源对象一般放在JNDI服务器中。
通过JNDI获得绑定的资源:
public static Object lookup(String context) throws NamingException {
Properties pro = new Properties();
// Weblogic的JNDI服务器参数
pro.put(InitialContext.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
pro.put(InitialContext.PROVIDER_URL, "t3://localhost:7001");
Context ctx = new InitialContext(pro);
return ctx.lookup(context);// 查找;通过指定的字符串获得先前绑定的资源。
}
十一、连接池
在内存中用来保存一个个数据库连接的对象。
访问数据库时,建立连接和拆连接需要花费较长时间,通过以连接池直连的方式获取连接,不需要注册驱动程序,可以大量的节省销毁和创建连接的资源消耗,提高 访问数据库的效率。通过连接池获得的Connection,当执行con.close()时,不是关闭连接,而是表示将连接释放回连接池。
连接池是一个很复杂的算法,常见的连接池有C3P0、DBCP、BoneCP、Druid等。
十二、分布式的事务管理器JTA(Java Transaction API)
分布式事务是通过多个异地数据库执行一组相关的操作,要保证原子操作的不可分,也不用再自己写commit,和rollback,全部都交给中间服务器(TM)来处理。
如果计划用JTA 界定事务,那么就需要有一个实现 javax.sql.XADataSource 、 javax.sql.XAConnection 和
javax.sql.XAResource 接口的 JDBC 驱动程序。一个实现了这些接口的驱动程序将可以参与 JTA 事务。一个
XADataSource 对象就是一个 XAConnection 对象的工厂。 XAConnection s 是参与 JTA 事务的 JDBC 连接。