Java回顾之JDBC

开发 后端
将JDBC的各种细节操作整理打包,生成易于操作的ORM框架来进行数据库各项操作。但理解JDBC访问步骤,对于理解Java数据操作流程很有帮助。

 概述

  尽管在实际开发过程中,我们一般使用ORM框架来代替传统的JDBC,例如Hibernate或者iBatis,但JDBC是Java用来实现数据访问的基础,掌握它对于我们理解Java的数据操作流程很有帮助。

  JDBC的全称是Java Database Connectivity。

  JDBC对数据库进行操作的流程:

  • 连接数据库
  • 发送数据请求,即传统的CRUD指令
  • 返回操作结果集

  JDBC中常用的对象包括:

  • ConnectionManager
  • Connection
  • Statement
  • CallableStatement
  • PreparedStatement
  • ResultSet
  • SavePoint

  一个简单示例

  我们来看下面一个简单的示例,它使用JDK自带的Derby数据库,创建一张表,插入一些记录,然后将记录返回:

  1.  private static void test1() throws SQLException 
  2.  { 
  3.      String driver = "org.apache.derby.jdbc.EmbeddedDriver"
  4.      String dbURL = "jdbc:derby:EmbeddedDB;create=true"
  5.       
  6.      Connection con = null
  7.      Statement st = null
  8.      try 
  9.      { 
  10.          Class.forName(driver); 
  11.          con = DriverManager.getConnection(dbURL); 
  12.          st = con.createStatement(); 
  13.          st.execute("create table foo(ID INT NOT NULL, NAME VARCHAR(30))"); 
  14.          st.executeUpdate("insert into foo(ID,NAME) values(1, 'Zhang San')"); 
  15.           
  16.          ResultSet rs = st.executeQuery("select ID,NAME from foo"); 
  17.           
  18.          while(rs.next()) 
  19.          { 
  20.              int id = rs.getInt("ID"); 
  21.              String name = rs.getString("NAME"); 
  22.              System.out.println("ID=" + id + "; NAME=" + name); 
  23.          } 
  24.      } 
  25.      catch(Exception ex) 
  26.      { 
  27.          ex.printStackTrace(); 
  28.      } 
  29.      finally 
  30.      { 
  31.          if (st != null) st.close(); 
  32.          if (con != null) con.close(); 
  33.      } 
  34.  } 

  如何建立数据库连接

  上面的示例代码中,建立数据库连接的部分如下:

String driver = "org.apache.derby.jdbc.EmbeddedDriver"; String dbURL = "jdbc:derby:EmbeddedDB;create=true";  Class.forName(driver); con = DriverManager.getConnection(dbURL);

  建立数据库连接的过程,可以分为两步:

  1)加载数据库驱动,即上文中的driver以及Class.forName(dirver)

  2)定位数据库连接字符串, 即dbURL以及DriverManager.getConnection(dbURL)

  不同的数据库,对应的dirver和dbURL不同,但加载驱动和建立连接的方式是相同的,即只需要修改上面driver和dbURL的值就可以了。

  自动加载数据库驱动

  如果我们每次建立连接时,都要使用Class.forName(...)来手动加载数据库驱动,这样会很麻烦,我们可以通过配置文件的方式,来保存数据库驱动的信息。

  我们可以在classpath中,即编译出来的.class的存放路径,添加如下文件:

META-INF\services\java.sql.Driver

  对应的内容就是JDBC驱动的全路径,也就是上面driver变量的值:

org.apache.derby.jdbc.EmbeddedDriver

  接下来,我们在程序中,就不需要再显示的用Class.forName(...)来加载驱动了,它会被自动加载进来,当我们的数据库发生变化时,只需要修改这个文件就可以了,例如当我们的数据库由Derby变为MySQL时,只需要将上述的配置修改为:

com.mysql.jdbc.Driver

  但是,需要注意一点,这里只是配置了JDBC驱动的全路径,并没有包含jar文件的信息,因此,我们还是需要将包含该驱动的jar文件手动的放置到程序的classpath中。

  JDBC中的基本操作

  对于数据库操作来说,CRUD操作应该是最常见的操作了, 即我们常说的增、删、查、改。

  JDBC是使用Statement和ResultSet来完成这些操作的。

  如何实现CRUD

  下面是一个实现CRUD的示例:

JDBC实现基本的CRUD示例

  我们顺序调用上面的测试方法:

1 insertTest(); 2 deleteTest(); 3 updateTest();

  执行结果如下:

=====insert test===== ID:1; NAME=Zhang San ID:2; NAME=Li Si ID:3; NAME=Wang Wu =====delete test===== ID:1; NAME=Zhang San ID:2; NAME=Li Si =====update test===== ID:1; NAME=Zhang San ID:2; NAME=TEST

  上面代码中的showUser方法会把user表中的所有记录打印出来。

#p#

  如何调用存储过程

  存储过程是做数据库开发时经常使用的技术,它可以通过节省编译时间的方式来提升系统性能,我们这里的示例使用MySQL数据库。

  如何调用不带参数的存储过程

  假设我们现在有一个简单的存储过程,它只是返回user表中的所有记录,存储过程如下:

  1. CREATE DEFINER=`root`@`localhost` PROCEDURE `GetUser`() 
  2. BEGIN 
  3. select ID,NAME from user
  4. END 

  我们可以使用CallableStatement来调用存储过程:

调用存储过程示例一

  它的执行结果如下:

ID:1; NAME=Zhang San ID:2; NAME=TEST

  如何调用带参数的存储过程

  MySQL的存储过程中的参数分为三种:in/out/inout,我们可以把in看做入力参数,out看做出力参数,JDBC对这两种类型的参数设置方式不同:

  1)in, JDBC使用类似于cst.set(1, 10)的方式来设置

  2)out,JDBC使用类似于cst.registerOutParameter(2, Types.VARCHAR);的方式来设置

  我们来看一个in参数的示例,假设我们希望返回ID为特定值的user信息,存储过程如下:

  1. CREATE DEFINER=`root`@`localhost` PROCEDURE `GetUserByID`(in id int
  2. BEGIN 
  3. set @sqlstr=concat('select * from user where ID=', id); 
  4. prepare psmt from @sqlstr; 
  5. execute psmt; 
  6. END 

  Java的调用代码如下:

JDBC调用存储过程示例二

  我们执行下面的语句:

execStoredProcedureTest2(1);

  结果如下:

ID:1; NAME=Zhang San

  对于out类型的参数,调用方式类似,不再赘述。

  获取数据库以及结果集的metadata信息

  在JDBC中,我们不仅能够对数据进行操作,我们还能获取数据库以及结果集的元数据信息,例如数据库的名称、驱动信息、表信息;结果集的列信息等。

  获取数据库的metadata信息

  我们可以通过connection.getMetaData方法来获取数据库的元数据信息,它的类型是DatabaseMetaData。

获取数据库的元数据信息

  这里我们使用的数据库是MySQL中自带的默认数据库:mysql,它会记录整个数据库服务器中的一些信息。上述代码执行结果如下:

数据库:MySQL 5.5.28 驱动程序:MySQL-AB JDBC Driver mysql-connector-java-5.0.4 ( $Date: 2006-10-19 17:47:48 +0200 (Thu, 19 Oct 2006) $, $Revision: 5908 $ ) |表名称                      |表类别      |表类型      |表模式      |
|columns_priv                 |mysql     |TABLE     |null      |
|db                           |mysql     |TABLE     |null      |
|event                        |mysql     |TABLE     |null      |
|func                         |mysql     |TABLE     |null      | 。。。

  由于mysql中表比较多,上述结果只截取了一部分。

  获取结果集的元数据信息

  我们可以通过使用resultset.getMetaData方法来获取结果集的元数据信息,它的类型是ResultSetMetaData。

获取结果集的元数据信息

  它的执行结果如下:

Column Name:ID; Column Type:INTEGER UNSIGNED Column Name:NAME; Column Type:VARCHAR

  可以看到,它返回类结果集中每一列的名称和类型。

  基于ResultSet的操作

  当我们需要对数据库进行修改时,除了上述通过Statement完成操作外,我们也可以借助ResultSet来完成。

  需要注意的是,在这种情况下,我们定义Statement时,需要添加参数。

  Statement构造函数可以包含3个参数:

  • resultSetType,它的取值包括:ResultSet.TYPE_FORWARD_ONLYResultSet.TYPE_SCROLL_INSENSITIVEResultSet.TYPE_SCROLL_SENSITIVE,默认情况下,该参数的值是ResultSet.TYPE_FORWARD_ONLY
  • resultSetConcurrency,它的取值包括:ResultSet.CONCUR_READ_ONLYResultSet.CONCUR_UPDATABLE,默认情况下,该参数的值是ResultSet.CONCUR_READ_ONLY
  • resultSetHoldability,它的取值包括:ResultSet.HOLD_CURSORS_OVER_COMMITResultSet.CLOSE_CURSORS_AT_COMMIT

  为了使得ResultSet能够对数据进行操作我们需要:

  • 将resultSetType设置为ResultSet.TYPE_SCROLL_SENSITIVE
  • 将resultSetConcurrency设置为ResultSet.CONCUR_UPDATABLE

  在通过ResultSet对数据进行调整的过程中,下面方法可能会被调用:

  • resultset.last()
  • resultset.first()
  • resultset.moveToInsertRow()
  • resultset.absolute()
  • resultset.setxxx()
  • resultset.updateRow()
  • resultset.insertRow()

  下面是一个通过ResultSet对数据进行增、删、改的示例:

通过ResultSet对数据进行增、删、改

  分别调用上述方法:

1 getResultCount(); 2 insertDataToResultSet(); 3 updateDataToResultSet(); 4 delDataFromResultSet();

  执行结果如下:

=====Result Count===== 返回结果的条数:2
=====Insert===== ID:1; NAME=Zhang San ID:2; NAME=TEST ID:4; NAME=Xiao Ming =====Update===== ID:1; NAME=Zhang San ID:2; NAME=TEST ID:4; NAME=Xiao Qiang =====Delete===== ID:1; NAME=Zhang San ID:2; NAME=TEST

  可以看到我们对ID为4的记录进行了插入、更新和删除操作。

#p#

  预处理以及批处理

  预处理和批处理都是用来提升系统性能的方式,一种是利用数据库的缓存机制,一种是利用数据库一次执行多条语句的方式。

  预处理

  数据库服务器接收到Statement后,一般会解析Statement、分析是否有语法错误、定制最优的执行计划,这个过程可能会降低系统的性能。一般的数据库服务器都这对这种情况,设计了缓存机制,当数据库接收到指令时,如果缓存中已经存在,那么就不再解析,而是直接运行。

  这里相同的指令是指sql语句完全一样,包括大小写。

  JDBC使用PreparedStatement来完成预处理:

预处理示例

  执行结果如下:

=====Insert a single record by PreparedStatement===== ID:1; NAME=Zhang San ID:2; NAME=TEST ID:5; NAME=Lei Feng

  批处理

  批处理是利用数据库一次执行多条语句的机制来提升性能,这样可以避免多次建立连接带来的性能损失。

  批处理使用Statement的addBatch来添加指令,使用executeBatch方法来一次执行多条指令:

批处理示例

  执行结果如下:

=====Insert multiple records by Statement & Batch===== ID:1; NAME=Zhang San ID:2; NAME=TEST ID:5; NAME=Lei Feng ID:6; NAME=Xiao Zhang ID:7; NAME=Xiao Liu ID:8; NAME=Xiao Zhao

  预处理和批处理相结合

  我们可以把预处理和批处理结合起来,利用数据库的缓存机制,一次执行多条语句:

预处理和批处理相结合的示例

  执行结果如下:

=====Insert multiple records by PreparedStatement & Batch===== ID:1; NAME=Zhang San ID:2; NAME=TEST ID:5; NAME=Lei Feng ID:9; NAME=Xiao Zhang ID:10; NAME=Xiao Liu ID:11; NAME=Xiao Zhao

  数据库事务

  谈到数据库开发,事务是一个不可回避的话题,JDBC默认情况下,是每一步都自动提交的,我们可以通过设置 connection.setAutoCommit(false)的方式来强制关闭自动提交,然后通过connection.commit()和 connection.rollback()来实现事务提交和回滚。

  简单的数据库事务

  下面是一个简单的数据库事务的示例:

简单的数据库事务示例

  连续执行上述方法两次,我们可以得出下面的结果:

=====Simple Transaction test===== ID:1; NAME=Zhang San ID:2; NAME=TEST ID:5; NAME=Lei Feng ID:12; NAME=Xiao Li =====Simple Transaction test===== ID:1; NAME=Zhang San ID:2; NAME=TEST ID:5; NAME=Lei Feng ID:12; NAME=Xiao Li com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Duplicate entry '12' for key 'PRIMARY'     at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:931)     at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2870)     at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1573)     at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1665)     at com.mysql.jdbc.Connection.execSQL(Connection.java:3170)     at com.mysql.jdbc.Statement.executeUpdate(Statement.java:1316)     at com.mysql.jdbc.Statement.executeUpdate(Statement.java:1235)     at sample.jdbc.mysql.ResultSetSample.transactionTest1(ResultSetSample.java:154)     at sample.jdbc.mysql.ResultSetSample.main(ResultSetSample.java:17)

  可以看到,第一次调用时,操作成功,事务提交,向user表中插入了一条记录;第二次调用时,发生主键冲突异常,事务回滚。

  带有SavePoint的事务

  当我们的事务操作中包含多个处理,但我们有时希望一些操作完成后可以先提交,这样可以避免整个事务的回滚。JDBC使用SavePoint来实现这一点。

带有SavePoint的事务示例

  执行结果如下:

=====Simple Transaction test===== com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Duplicate entry '13' for key 'PRIMARY'     at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:931)     at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2870)     at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1573)     at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1665)     at com.mysql.jdbc.Connection.execSQL(Connection.java:3170)     at com.mysql.jdbc.Statement.executeUpdate(Statement.java:1316)     at com.mysql.jdbc.Statement.executeUpdate(Statement.java:1235)     at sample.jdbc.mysql.ResultSetSample.transactionTest2(ResultSetSample.java:185)     at sample.jdbc.mysql.ResultSetSample.main(ResultSetSample.java:18) ID:1; NAME=Zhang San ID:2; NAME=TEST ID:5; NAME=Lei Feng ID:13; NAME=Xiao Li ID:14; NAME=Xiao Wang

  可以看到最终事务报出了主键冲突异常,事务回滚,但是依然向数据库中插入了ID为13和14的记录。

  另外,在确定SavePoint后,ID为15的记录并没有被插入,它是通过事务进行了回滚。

原文链接:http://www.cnblogs.com/wing011203/archive/2013/05/12/3073838.html

责任编辑:陈四芳 来源: 博客园
相关推荐

2013-05-28 10:08:41

IO输出

2011-03-24 09:22:36

Java 7JDBC4

2018-07-24 09:38:35

JavaMySQLJDBC

2013-05-27 12:59:22

设计模式GoF

2009-07-22 16:07:11

Java JDBC编程

2012-04-05 10:27:49

GooglePR之赛

2015-09-09 10:34:58

底层网络技术网络技术

2013-01-29 09:43:33

JavaJVMJava社区

2021-10-11 08:51:50

JavaMailJDBCJava

2011-05-20 09:53:00

JDK7

2009-07-22 15:58:34

JDBC调用Oracl

2009-07-16 13:51:43

2009-07-07 15:59:51

2010-01-15 21:47:39

2010-01-14 20:49:08

2009-06-19 16:38:45

JDBC简介J2EE

2009-07-20 17:41:59

Java JDBC

2020-10-12 08:43:25

Java基础知识

2012-08-16 09:46:05

Debian

2009-10-27 10:45:24

Maven是什么
点赞
收藏

51CTO技术栈公众号