JDBC

来源:互联网 发布:安卓手机还原网络设置 编辑:程序博客网 时间:2024/05/22 14:28
利用JDBC获取新插入记录id的三种方法
在一对多的关联操作中,通常在一方插入记录后,需要获取到当前记录的id,以方便进行多方的插入操作。要解决这一问题,在JDBC中至少有三种不同的实现方法:
1.在插入语句后添加一条查询,利用resultset来得到id号
psmt = con.prepareStatement
    ("insertinto orders(receiver,address,telephone,total,detailnum,userid)values(?,?,?,?,?,?);select@@identity");
psmt.setString(1,order.getReceiver());
……
results= psmt.executeQuery();
if(results.next()){
    num= results.getInt(1);
}
@@identity是表示的是最近一次向具有identity属性(即自增列)的表插入数据时对应的自增列的值,是系统定义的全局变量。一般系统定义的全局变量都是以@@开头,用户自定义变量以@开头。比如有个表A,它的自增列是id,当向A表插入一行数据后,如果插入数据后自增列的值自动增加至101,则通过 select@@identity得到的值就是101。若插入了多个行,则会产生多个标识值,@@IDENTITY返回最后产生的标识值。使用 @@identity的前提是在进行insert操作后,执行select@@identity的时候连接没有关闭,否则得到的将是NULL值。
2. 利用generatedKey来获取id
psmt = con.prepareStatement
  ("insertinto orders (receiver,address,telephone,total,detailnum,userid)values(?,?,?,?,?,?)",Statement.RETURN_GENERATED_KEYS);
  psmt.setString(1,order.getReceiver());
   ……
  psmt.executeUpdate();
  results= psmt.getGeneratedKeys();
  if(results.next()){
        Ineger key = results.getInt(1);
  }
3. 调用存储过程来实现,当然这种情况下,插入对象的属性不宜过多,否则存储过程的输入参数太多。在存储过程中,将id作为输出参数返回
CallableStatement proc = conn.prepareCall("{callproc_insert(?,?,?)}");
proc.setString(1,cardname);
proc.setInt(2,money);
proc.registerOutParameter(3,Types.INTEGER);
proc.execute();
num= proc.getInt(3)

JDBC 的事务支持
JDBC对事务的支持体现在三个方面:
1.自动提交模式(Auto-commitmode)
Connection 提供了一个auto-commit的属性来指定事务何时结束。
a.当auto-commit为true时,当每个独立SQL操作的执行完毕,事务立即自动提交,也就是说每个SQL操作都是一个事务。
一个独立SQL操作什么时候算执行完毕,JDBC规范是这样规定的:
对数据操作语言(DML,如insert,update,delete)和数据定义语言(如create,drop),语句一执行完就视为执行完毕。
对 select语句,当与它关联的ResultSet对象关闭时,视为执行完毕。
对存储过程或其他返回多个结果的语句,当与它关联的所有 ResultSet对象全部关闭,所有updatecount(update,delete等语句操作影响的行数)和output parameter(存储过程的输出参数)都已经获取之后,视为执行完毕。
b. 当auto-commit为false时,每个事务都必须显示调用commit方法进行提交,或者显示调用rollback方法进行回滚。auto- commit默认为true。

2. 事务隔离级别(TransactionIsolation Levels)
JDBC提供了5种不同的事务隔离级别,在Connection中进行了定义。
JDBC定义了五种事务隔离级别:
TRANSACTION_NONE JDBC驱动不支持事务
TRANSACTION_READ_UNCOMMITTED 允许脏读、不可重复读和幻读。
TRANSACTION_READ_COMMITTED 禁止脏读,但允许不可重复读和幻读。
TRANSACTION_REPEATABLE_READ 禁止脏读和不可重复读,单运行幻读。
TRANSACTION_SERIALIZABLE 禁止脏读、不可重复读和幻读。
conn.setTransactionIsolation(Connection.TRANSACTION_XXX);

3. 保存点(SavePoint)
JDBC定义了SavePoint接口,提供在一个更细粒度的事务控制机制。当设置了一个保存点后,可以 rollback到该保存点处的状态,而不是rollback整个事务。
Connection接口的setSavepoint和 releaseSavepoint方法可以设置和释放保存点
举个简单的例子,一个客户从A银行转帐至B银行,要作的动作为从A银行的账户扣款、在B银行的账户加上转帐的金额,两个动作必须成功,如果有一个动作失败,则此次转帐失败。
在JDBC 中,可以操作Connection的setAutoCommit()方法,给定它false自变量,在下达一连串的SQL语句后,自行呼叫 Connection的commit()来送出变更,如果中间发生错误,则呼叫rollback()来撤消所有的执行,例如:
public class TransactionDemo {
  public static void main(String[] args) {
      Connectionconn = null;
      Statement stmt = null;
      try{
          conn= DriverManager.getConnection(  url, user,password);
          conn.setAutoCommit(false);
          stmt= conn.createStatement();
          stmt.execute("....");// SQL                        
          conn.commit();
      }
      catch(SQLExceptione) {}
      finally{}
  }
}
如果您在交易管理时,仅想要rollback回某个SQL执行点,则您可以设定savepoint,例如:
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
stmt.executeUpdate("....");
stmt.executeUpdate("....");
Savepoint savepoint = conn.setSavepoint(); // 设定savepoint
stmt.executeUpdate("....");
// 如果因故rollback
conn.rollback(savepoint);
. . .
conn.commit();
// 记得释放savepoint
stmt.releaseSavepoint(savepoint);

在Statement 批处理中批次执行SQL,批处理前设定autocommit为false,如果中间有个SQL执行错误,则应该rollback整个批处理。

建立Statement或PreparedStatement,使用Connection无参数的createStatement()与 preparedStatement(),这样取得的Statement其执行SQL后得到的ResultSet,将只能使用next()方法逐笔取得查询结果。
可以在建立Statement对象时指定resultSetType,可指定的参数有 ResultSet.TYPE_FORWARD_ONLY(1003)、 ResultSet.TYPE_SCROLL_INSENSITIVE(1004)与 ResultSet.TYPE_SCROLL_SENSITIVE(1005),在不指定的情况下,预设是第一个,也就是只能使用next()来逐笔取得数据,指定第二个或第三个,则可以使用ResultSet的afterLast()、previous()、absolute()、relative() 等方法。
ResultSet.TYPE_SCROLL_INSENSITIVE与 ResultSet.TYPE_SCROLL_SENSITIVE的差别在于及时跟踪数据库的更新,以便更改ResultSet中的数据,另外您还必须指定resultSetConcurrency,有ResultSet.CONCUR_READ_ONLY(1007)与 ResultSet.CONCUR_UPDATABLE(1008)两个参数可以设定,前者表示只能读取ResultSet的资料,后者表示可以直接使用 ResultSet来操作数据库,
createStatement()不给定参数时,预设是 ResultSet.TYPE_FORWARD_ONLY、ResultSet.CONCUR_READ_ONLY。
这边先示范如何控制ResultSet的读取光标,在建立Statement时,您使用ResultSet.TYPE_SCROLL_INSENSITIVE 及ResultSet.CONCUR_READ_ONLY即可,下面这个例子示范从 查询到的数据最后一笔开始往前读取:

Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,   ResultSet.CONCUR_READ_ONLY);
ResultSet result = stmt.executeQuery("SELECT * FROM message");
result.afterLast();
while(result.previous()) {
  System.out.print(result.getString("name")+ "\t");
  System.out.print(result.getString("email")+ "\t");
}
stmt.close();
conn.close();

afterLast() 会将ResultSet的读取光标移至最后一笔数据之后,您使用previous()方法往前移动读取光标。
您也可以使用 absolute()方法指定查询到的数据之位置,例如absolute(4)表示第四笔资料,absoulte(10)则是第十笔数据,如果指定负数,则从最后往前数,例如absolute(-1)则是最后一笔数据,若有100笔数据,absoulte(-4)则是第97笔资料。
relative() 方法则从目前光标处指定相对位置,例如若目前在第25笔资料,则relative(-2)则表示第23笔资料,而relative(4)则表示第29笔资料。
另外还有beforeFirst(),可以将光标移至数据的第一笔之前,first()可以将光标移至第一笔数据,而 last()可以将光标移至最后一笔数据。
之前要进行新增、更新或删除数据,都必须要撰写SQL,然后使用executeUpdate() 来执SQL,将SQL写在executeUpdate()之中,其实是一件麻烦又容易出错的动作,如果您只是想要针对查询到的数据进行一些简单的新增、更新或删除资料,您可以藉由ResultSet的一些方法 来执行,而不一定要撰写SQL并执行。
想要使用 ResultSet直接进行新增、更新或删除数据,在建立Statement时必须在createStatement()上指定 ResultSet.TYPE_SCROLL_SENSITIVE(或ResultSet.TYPE_SCROLL_INSENSITIVE,如果不想取得数据库更新后的数据的话)与ResultSet.CONCUR_UPDATABLE,例如:
Statement stmt =conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
假如我们想要针对查询到的数据进行更新的动作,我们先移动光标至想要更新的数据位置,然后使用updateXXX()等对应的方法即可,最后记得使用 updateRow()让更新生效,例如:
ResultSet result = stmt.executeQuery("SELECT * FROM message WHERE name='caterpillar'");
result.last();
result.updateString("name","justin");
result.updateString("email","justin@mail.com");
result.updateRow();

使用updateXXX()等方法之后,并不会马上对数据库生效,而必须执行完updateRow()方法才会对数据库进行操作,如果在 updateRow()前想要取消之前的updateXXX()方法,则可以使用cancelRowUpdates()方法取消。
如果想要新增数据,则先使用moveToInsertRow()移至新增数据处,执行相对的updateXXX()方法,然后再执行insertRow() 即可新增数据,例如:
ResultSet result = stmt.executeQuery("SELECT * FROMmessage WHERE name='caterpillar'");
result.moveToInsertRow();
result.updateString("name","caterpillar");
result.updateString("email","caterpillar@mail.com");
result.updateString("subject","test");
result.updateString("memo", "This is atest!");
result.insertRow();

如果想要删除查询到的某笔数据,则可以将光标移至该笔数据,然后执行deleteRow()方法即可:
ResultSet result = stmt.executeQuery("SELECT * FROMmessage WHERE name='caterpillar'");
result.last();
result.deleteRow();

交易是一组原子(Atomic)操作(一组SQL执行)的工作单元,这个工作单元中的所有原子操作在进行期间,与其它交易隔离,免于数据来源的交相更新而 发生混乱,交易中的所有原子操作,要嘛全部执行成功,要嘛全部失败(即使只有一个失败,所有的原子操作也要全部撤消)。

Meta Data即「数据的数据」(Data about data),ResultSet用来表示查询到的数据,而ResultSet数据的数据,即描述查询到的数据背后的数据描述,即用来表示表格名称、字段名称、字段型态等等,这些讯息您可以透过ResultSetMetaData来取得。
下面这个范例直接示范如何取得查询到的数据域位数、表格名称、字段名称与字段数据型态:
package onlyfun.caterpillar;
import java.sql.*;
public class ResultSetMetaDataDemo {
  public static void main(String[] args) {
    try{
        conn= DriverManager.getConnection(url, user, password);
        stmt= conn.createStatement();
        ResultSetresult = stmt.executeQuery("SELECT * FROM message");
        ResultSetMetaData metadata =  result.getMetaData();
        for(int i = 1;i <= metadata.getColumnCount();i++) {
            System.out.print(metadata.getTableName(i)+ ".");
            System.out.print(metadata.getColumnName(i)+ "\t|\t");
            System.out.println(metadata.getColumnTypeName(i));
        }
    }
    catch(Exceptione) {}                     
    finally{}
  }
}

对于Oracle9i前,要储存数据至 Blob/Clob字段前,JDBC必须先获得Blob/Clob光标,在实际存入数据前,我们必须先建立空的Blob/Clob字段,然后从空字段中取得Blob/Clob光标。
您可以使用Oracle的EMPTY_BLOB()与EMPTY_CLOB()来存入空的 Blob/Clob字段,例如:
pstmt = conn.prepareStatement( "INSERT INTO files(id, des, image) VALUES(?, EMPTY_CLOB(), EMPTY_BLOB())");
pstmt.setInt(1, 1);
pstmt.executeUpdate();
或者是使用oracle.sql.Blob、 oracle.sql.Clob来存入Blob/Clob空字段,例如:
pstmt = conn.prepareStatement( "INSERT INTOfiles(id, des, image) VALUES(?, ?, ?)");
pstmt.setInt(1, 1);
pstmt.setClob(2, CLOB.empty_lob());
pstmt.setBlob(3, BLOB.empty_lob());
pstmt.executeUpdate();
接下来重新查询字段,取得Blob/Clob字段光标,然后用更新的方式将Blob/Clob数据更新至数据库
blob.getBinaryStream() getBytes(long pos, int length)setBytes(long pos, byte[] bytes)
clob.getCharacterStream()    getSubString(long pos, int length)setString(long pos, String str)

检测数据库是否支持batch
DatabaseMetaData.supportsBatchUpdates()

然后就是三个比较有用的方法:
addBatch: 将Statement, PreparedStatement, CallableStatement添加进batch里面
executeBatch: 返回各个语句的执行结果
clearBatch: 将batch里面的sql语句清除掉
在这个里面有一个值得注意的是要设置connection的事务提交类型
setAutoCommit(false) 为手动提交

Statement stmt = conn.createStatement();
// Set auto-commit to false
conn.setAutoCommit(false);
// Create SQL statement
String SQL = "INSERT INTO Employees (id, first,last, age) VALUES(200,'Zia', 'Ali', 30)";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);
// Create one more SQL statement
String SQL = "INSERT INTO Employees (id, first,last, age) VALUES(201,'Raj', 'Kumar', 35)";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);
// Create one more SQL statement
String SQL = "UPDATE Employees SET age = 35 WHERE id= 100";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);
// Create an int[] to hold returned values
int[] count = stmt.executeBatch();
//Explicitly commit statements to apply changes

conn.commit(); 

executeBatch()不限制批次的语言类型,比如你第一句insert,第二局update,第三句delete也没问题。
但是如果用PreparedStatement的就要限制语句种类了,因为PreparedStatement只有参数列表,语句则只有一个预编译语句句柄,也就是一句sql应用于一批参数。


原创粉丝点击