• Java回顾之JDBC


      第一篇:Java回顾之I/O

      第二篇:Java回顾之网络通信

      第三篇:Java回顾之多线程

      第四篇:Java回顾之多线程同步

      第五篇:Java回顾之集合

      第六篇:Java回顾之序列化

      第七篇:Java回顾之反射

      第八篇:Java回顾之一些基础概念

      这篇文章里,我们来讨论一些和JDBC相关的话题。

      概述

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

      JDBC的全称是Java Database Connectivity。

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

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

      JDBC中常用的对象包括:

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

      一个简单示例

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

    一个简单的JDBC示例
     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 private static void insertTest() throws SQLException
     2 {
     3     String dbURL = "jdbc:mysql://localhost/test";
     4     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     5     Statement st = con.createStatement();
     6     st.execute("insert into user(ID,NAME) values(1, 'Zhang San')");
     7     st.execute("insert into user(ID,NAME) values(2, 'Li Si')");
     8     st.execute("insert into user(ID,NAME) values(3, 'Wang Wu')");
     9     System.out.println("=====insert test=====");
    10     showUser(st);
    11     st.close();
    12     con.close();
    13 }
    14 
    15 private static void deleteTest() throws SQLException
    16 {
    17     String dbURL = "jdbc:mysql://localhost/test";
    18     Connection con = DriverManager.getConnection(dbURL, "root", "123");
    19     Statement st = con.createStatement();
    20     st.execute("delete from user where ID=3");
    21     System.out.println("=====delete test=====");
    22     showUser(st);
    23     st.close();
    24     con.close();
    25 }
    26 
    27 private static void updateTest() throws SQLException
    28 {
    29     String dbURL = "jdbc:mysql://localhost/test";
    30     Connection con = DriverManager.getConnection(dbURL, "root", "123");
    31     Statement st = con.createStatement();
    32     st.executeUpdate("update user set NAME='TEST' where ID=2");
    33     System.out.println("=====update test=====");
    34     showUser(st);
    35     st.close();
    36     con.close();
    37 }
    38 
    39 private static void showUser(Statement st) throws SQLException
    40 {
    41     ResultSet rs = st.executeQuery("select ID, NAME from user");
    42     while(rs.next())
    43     {
    44         int id = rs.getInt("ID");
    45         String name = rs.getString("NAME");
    46         System.out.println("ID:" + id + "; NAME=" + name);
    47     }
    48     rs.close();
    49 }

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

    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表中的所有记录打印出来。

      如何调用存储过程

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

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

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

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

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

    调用存储过程示例一
     1 private static void execStoredProcedureTest() throws SQLException
     2 {
     3     String dbURL = "jdbc:mysql://localhost/test";
     4     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     5     CallableStatement cst = con.prepareCall("call GetUser()");
     6     ResultSet rs = cst.executeQuery();
     7     while(rs.next())
     8     {
     9         int id = rs.getInt("ID");
    10         String name = rs.getString("NAME");
    11         System.out.println("ID:" + id + "; NAME=" + name);
    12     }
    13     rs.close();
    14     cst.close();
    15     con.close();
    16 }

      它的执行结果如下:

    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调用存储过程示例二
     1 private static void execStoredProcedureTest2(int id) throws SQLException
     2 {
     3     String dbURL = "jdbc:mysql://localhost/test";
     4     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     5     CallableStatement cst = con.prepareCall("call GetUserByID(?)");
     6     cst.setInt(1, id);
     7     ResultSet rs = cst.executeQuery();
     8     while(rs.next())
     9     {
    10         String name = rs.getString("NAME");
    11         System.out.println("ID:" + id + "; NAME=" + name);
    12     }
    13     rs.close();
    14     cst.close();
    15     con.close();
    16 }

      我们执行下面的语句:

    execStoredProcedureTest2(1);

      结果如下:

    ID:1; NAME=Zhang San

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

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

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

      获取数据库的metadata信息

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

    获取数据库的元数据信息
     1 private static void test1() throws SQLException
     2 {
     3     String dbURL = "jdbc:mysql://localhost/mysql";
     4     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     5     
     6     DatabaseMetaData dbmd = con.getMetaData();
     7     
     8     System.out.println("数据库:" + dbmd.getDatabaseProductName() + " " + dbmd.getDatabaseProductVersion());
     9     System.out.println("驱动程序:" + dbmd.getDriverName() + " " + dbmd.getDriverVersion());
    10 
    11     ResultSet rs = dbmd.getTables(null, null, null, null);
    12     System.out.println(String.format("|%-26s|%-9s|%-9s|%-9s|", "表名称","表类别","表类型","表模式"));        
    13     while(rs.next())
    14     {
    15         System.out.println(String.format("|%-25s|%-10s|%-10s|%-10s|", 
    16                 rs.getString("TABLE_NAME"),rs.getString("TABLE_CAT"),
    17                 rs.getString("TABLE_TYPE"), rs.getString("TABLE_SCHEM")));
    18     }
    19 }

      这里我们使用的数据库是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。

    获取结果集的元数据信息
     1 private static void test2() throws SQLException
     2 {
     3     String dbURL = "jdbc:mysql://localhost/test";
     4     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     5     Statement st = con.createStatement();
     6     ResultSet rs = st.executeQuery("select ID, NAME from user");
     7     ResultSetMetaData rsmd = rs.getMetaData();
     8     for (int i = 1; i <= rsmd.getColumnCount(); i++)
     9     {
    10         System.out.println("Column Name:" + rsmd.getColumnName(i) + "; Column Type:" + rsmd.getColumnTypeName(i));
    11     }
    12 }

      它的执行结果如下:

    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 private static void getResultCount() throws SQLException
     2 {
     3     System.out.println("=====Result Count=====");
     4     String dbURL = "jdbc:mysql://localhost/test";
     5     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     6     Statement st = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
     7     ResultSet rs = st.executeQuery("select * from user");
     8     rs.last();
     9     System.out.println("返回结果的条数:"+ rs.getRow());
    10     rs.first();
    11     
    12     rs.close();
    13     st.close();
    14     con.close();
    15 }
    16 
    17 private static void insertDataToResultSet() throws SQLException
    18 {
    19     System.out.println("=====Insert=====");
    20     String dbURL = "jdbc:mysql://localhost/test";
    21     Connection con = DriverManager.getConnection(dbURL, "root", "123");
    22     Statement st = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
    23     ResultSet rs = st.executeQuery("select ID,NAME from user");
    24     rs.moveToInsertRow();
    25     rs.updateInt(1, 4);
    26     rs.updateString(2, "Xiao Ming");
    27     rs.insertRow();
    28     showUser(st);
    29     
    30     rs.close();
    31     st.close();
    32     con.close();
    33 }
    34 
    35 private static void updateDataToResultSet() throws SQLException
    36 {
    37     System.out.println("=====Update=====");
    38     String dbURL = "jdbc:mysql://localhost/test";
    39     Connection con = DriverManager.getConnection(dbURL, "root", "123");
    40     Statement st = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
    41     ResultSet rs = st.executeQuery("select * from user");
    42     rs.last();
    43     int count = rs.getRow();
    44     rs.first();
    45     rs.absolute(count);
    46     rs.updateString(2, "Xiao Qiang");
    47     rs.updateRow();
    48     showUser(st);
    49     
    50     rs.close();
    51     st.close();
    52     con.close();
    53 }
    54 
    55 private static void delDataFromResultSet() throws SQLException
    56 {
    57     System.out.println("=====Delete=====");
    58     String dbURL = "jdbc:mysql://localhost/test";
    59     Connection con = DriverManager.getConnection(dbURL, "root", "123");
    60     Statement st = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE, ResultSet.CLOSE_CURSORS_AT_COMMIT);
    61     ResultSet rs = st.executeQuery("select * from user");
    62     rs.last();
    63     int count = rs.getRow();
    64     rs.first();
    65     rs.absolute(count);
    66     rs.deleteRow();
    67     showUser(st);
    68     
    69     rs.close();
    70     st.close();
    71     con.close();
    72 }

      分别调用上述方法:

    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的记录进行了插入、更新和删除操作。

      预处理以及批处理

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

      预处理

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

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

      JDBC使用PreparedStatement来完成预处理:

    预处理示例
     1 private static void test1() throws SQLException
     2 {
     3     System.out.println("=====Insert a single record by PreparedStatement=====");
     4     String dbURL = "jdbc:mysql://localhost/test";
     5     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     6     PreparedStatement pst = con.prepareStatement("insert into user(id,name) values(?,?)");
     7     pst.setInt(1, 5);
     8     pst.setString(2, "Lei Feng");
     9     pst.executeUpdate();
    10     showUser(pst);
    11     pst.close();
    12     con.close();
    13 }

      执行结果如下:

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

      批处理

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

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

    批处理示例
     1 private static void test2() throws SQLException
     2 {
     3     System.out.println("=====Insert multiple records by Statement & Batch=====");
     4     String dbURL = "jdbc:mysql://localhost/test";
     5     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     6     Statement st = con.createStatement();
     7     st.addBatch("insert into user(id,name) values(6,'Xiao Zhang')");
     8     st.addBatch("insert into user(id,name) values(7,'Xiao Liu')");
     9     st.addBatch("insert into user(id,name) values(8,'Xiao Zhao')");
    10     st.executeBatch();
    11     showUser(st);
    12     st.close();
    13     con.close();
    14 }

      执行结果如下:

    =====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

      预处理和批处理相结合

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

    预处理和批处理相结合的示例
     1 private static void test3() throws SQLException
     2 {
     3     System.out.println("=====Insert multiple records by PreparedStatement & Batch=====");
     4     String dbURL = "jdbc:mysql://localhost/test";
     5     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     6     PreparedStatement pst = con.prepareStatement("insert into user(id,name) values(?,?)");
     7     pst.setInt(1, 9);
     8     pst.setString(2, "Xiao Zhang");
     9     pst.addBatch();
    10     pst.setInt(1, 10);
    11     pst.setString(2, "Xiao Liu");
    12     pst.addBatch();
    13     pst.setInt(1, 11);
    14     pst.setString(2, "Xiao Zhao");
    15     pst.addBatch();
    16     pst.executeBatch();
    17     showUser(pst);
    18     pst.close();
    19     con.close();
    20 }

      执行结果如下:

    =====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()来实现事务提交和回滚。

      简单的数据库事务

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

    简单的数据库事务示例
     1 private static void transactionTest1() throws SQLException
     2 {
     3     System.out.println("=====Simple Transaction test=====");
     4     String dbURL = "jdbc:mysql://localhost/test";
     5     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     6     Statement st = con.createStatement();
     7     try
     8     {
     9         con.setAutoCommit(false);
    10         st.executeUpdate("insert into user(id,name) values(12, 'Xiao Li')");
    11         con.commit();
    12     }
    13     catch(Exception ex)
    14     {
    15         ex.printStackTrace();
    16         con.rollback();
    17     }
    18     finally
    19     {
    20         con.setAutoCommit(true);
    21         showUser(st);
    22         if (st != null) st.close();
    23         if (con != null) con.close();
    24     }
    25 }

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

    =====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的事务示例
     1 private static void transactionTest2() throws SQLException
     2 {
     3     System.out.println("=====Simple Transaction test=====");
     4     String dbURL = "jdbc:mysql://localhost/test";
     5     Connection con = DriverManager.getConnection(dbURL, "root", "123");
     6     Statement st = con.createStatement();
     7     Savepoint svpt = null;
     8     try
     9     {
    10         con.setAutoCommit(false);
    11         st.executeUpdate("insert into user(id,name) values(13, 'Xiao Li')");
    12         st.executeUpdate("insert into user(id,name) values(14, 'Xiao Wang')");
    13         svpt = con.setSavepoint("roll back to here");
    14         st.executeUpdate("insert into user(id,name) values(15, 'Xiao Zhao')");
    15         st.executeUpdate("insert into user(id,name) values(13, 'Xiao Li')");
    16         con.commit();
    17     }
    18     catch(Exception ex)
    19     {
    20         ex.printStackTrace();
    21         con.rollback(svpt);
    22     }
    23     finally
    24     {
    25         con.setAutoCommit(true);
    26         showUser(st);
    27         if (st != null) st.close();
    28         if (con != null) con.close();
    29     }
    30 }

      执行结果如下:

    =====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的记录并没有被插入,它是通过事务进行了回滚。

  • 相关阅读:
    老王学jsp之javabean与表单
    老王学jsp之http500错误
    老王学jsp之session
    转 Fiddler抓取HTTPS
    PHP 大型网站优化 大数据大并发大流量
    大流量、高并发的网站的底层系统架构
    大流量、高并发Web系统搭建(单机到分布式集群)
    Failed to start Load Kernel Modules
    ubuntu 16.04 我的任务栏呢 怎么只剩下桌面了
    跳坑日志之腾讯服务器不见了
  • 原文地址:https://www.cnblogs.com/wing011203/p/3073838.html
Copyright © 2020-2023  润新知