Java 核心技术 卷II (4章)--数据库编程

Java 核心技术 卷II  (4章)

---数据库编程

 

1、jdbc设计

1)Jdbc是java能够通过SQL访问数据库的一套javaAPI;

2)Jdbc是一套能与多种关系数据库提供统一访Java接口方法;

 

2、核心API方法介绍

创建执行对象:

Stattement stat = conn.createStatement();

stat.executeUpdate():返回受SQL命令影响的行数,或者对不返回行数的语句(DDL语句)返回0

ResultSet rs = stat.executeQuery(String SQL)

结果集使用:

while(rs.next()){ . . . . . .}

如何取值?不同的参数类型有不同的访问器,可以通过下标和类名获取

rs.getDouble (1);
rs.getDouble(“price”)

说明:

1)数据库索引与数组的索引不同,数据库的列序号从1开始计算的。

2)使用get方法的类型与列的数据类型不一致,get方法会尝试合理的类型转换,如rs.getString(“price”),会将price列的浮点值转换为字符串。

3)做通用话数据封装时,可以使用getObject进行通用取。

如何管理连接??

使用完ResultSet、Statement或Connection对象,应立即使用close方法关闭,调用顺序从里到外(ResultSet、Statement、Connection)

如何解析SQL异常

SQLWarning w = stat.getWarning();
while(w != null){
            do something with w
            w = w.nextWarning();
}

说明:还有其他的很多方法,见书!!

 

相关其他方法:

Connection :
            Statement createStatement()
             voidcolse()
Statement
            ResultSetexecuteQuery(String sql)
            intexecuteUpdate(String sql)
            booleanexecute(String sql)
            ResultSetgetResultSet()
            intgetUpdateCount()
            voidclose()
            booleanisColsed()
            void coseOncompleion()
ResultSet
            booleannext()
            XxxgetXxx(int columnNumber)
            XxxgetXxx(String columnName)
            <T> TgetObject(int columnNumber,Class<T> type)
            <T> T getObject(intcolumnName,Class<T> type)
            int findColumn(String columnName)
            void close()
             boolean isClosed();

3、不同的执行对象

3.1预备语句(预编译语句)

如何进行参数化查询??

PreparedStatement stat = conn.prepareStatement(“select *from user where id = ?”);
stat.serInt(1,12);
ResultSet rs = stat.executeQuery();

        许多数据库通常都会有自动缓存预备语句,如果相同的查询被预备两次,数据库通常会直接重用查询策略。

不使用Statement:

1)使用Statement,若是进行参数化查询,每次查询都会建立新的查询语句,消耗查询性能;

2)使用Statement,如果进行参数化查询,需要拼接SQL,容易造成SQL注入。

使用statement:

如果进行的查询是不带参数的,使用Statement比PrepredStatement更高校,

 

相关其他方法:

Connection
            PreparedStatementprepareStatement(String sql)
PreparedStatement
            voidsetXxx(int n, Xxx x);
            voidclearParameters();
            ResultSetexecuteQuery()
            intexecuteUpdate()                 【如果执行的是数据定义语句(DDL),如CREATE TABLE,则返回0】

3.2 CallableStatement执行存储过程

CALL pro_findById2(5,@NAME);

(1)存储过程

mysql> DELIMITER $
mysql> CREATE PROCEDURE pro_findById2(IN eidINT,OUT vname VARCHAR(20))
-> BEGIN
->      SELECT empname INTO vname FROM employeeWHERE id = eid;
-> END $;

(2)测试

String sql = "CALL pro_findById2(?,?)"; //第一个?是输入参数,第二个?是输出参数
cstmt =conn.prepareCall(sql);
//设置输入参数
cstmt.setInt(1, 6);
//设置输出参数(注册输出参数)
cstmt.registerOutParameter(2,java.sql.Types.VARCHAR);
//返回结果到输出参数中
cstmt.executeQuery();
//从输出参数的索引中,获取结果
String result = cstmt.getString(2);            

4、其他

4.1读写Lob

Lob分两种:一种字符型大对象Clob,二进制大对象Blob

Blob:
存入数据库:

String sql = " insert into test(img)values(?)";
// 连接
con = JdbcUtil.getConnection();
// pstmt 对象
pstmt = con.prepareStatement(sql);
// 获取图片流
InputStream in = App_text.class.getResourceAsStream("7.jpg");
pstmt.setBinaryStream(1, in);            
// 执行保存图片
pstmt.execute(); 

从数据库读取:

String sql = "select img from  test where id=2;";
// 连接
con = JdbcUtil.getConnection();
pstmt = con.prepareStatement(sql);
rs = pstmt.executeQuery();
if (rs.next()) {
      // 获取图片流
      InputStream in =rs.getBinaryStream("img");
      // 图片输出流
      FileOutputStream out = new FileOutputStream(new File("c://1.jpg"));
      int len = -1;
      byte b[] = new byte[1024];
      while ((len = in.read(b)) != -1) {
            out.write(b, 0,len);
      }
}

Clob:

存入数据库:

String sql = "insert into test(content)values(?)";
con = JdbcUtil.getConnection();
pstmt = con.prepareStatement(sql);
// 设置参数
// 先获取文件路径
String path = App_text.class.getResource("tips.txt").getPath();
FileReader reader = new FileReader(new File(path));
pstmt.setCharacterStream(1,reader);
// 执行sql
pstmt.executeUpdate();

从数据库读取:

String sql = "select * from  test;";
con = JdbcUtil.getConnection();
pstmt = con.prepareStatement(sql);
rs = pstmt.executeQuery();
if (rs.next()) {
      // 获取长文本数据,方式1:
      //Reader r = rs.getCharacterStream("content");
                       
      // 获取长文本数据,方式2:
      System.out.print(rs.getString("content"));
}
 

4.2获取主键

String sql = "insert into articlevalues(null,?,now(),?)";
//执行语句的同时返回一个主键集合
PreparedStatement pst =DB.prepareStmt(conn, sql,Statement.RETURN_GENERATED_KEYS);
pst.setInt(1,0);
pst.setInt(2, 0);
pst.executeUpdate();
      
//得到主键集合
ResultSet rsKey =pst.getGeneratedKeys();
rsKey.next();
rootId = rsKey.getInt(1);

4.3多结果集

若是执行存储过程或者在单个查询中提交了多个select语句,那么一个数据库能返回多个结果集。

 

关键点:重复调用getMoreResult方法移动到下一项结果集。

CREATE PROCEDURE proc_test()
BEGIN
select * from person;
select * from person;
END;

Connection conn = getConn();
String sql = "{call proc_test()}";   
CallableStatement ctmt = conn.prepareCall(sql);
boolean hadResults = ctmt.execute();   
int i=0;   
ResultSet rs = null; 
while (hadResults) {   
    System.out.println("result No:----"+(++i));   
    rs = ctmt.getResultSet();   
    while (rs !=null && rs.next()){   
        int id1 = rs.getInt(1);   
        String name1 = rs.getString(2);   
        System.out.println(id1 +":" + name1);   
    }   
    hadResults = ctmt.getMoreResults(); //检查是否存在更多结果集   
} 

4.4可滚动&可更新结果集

可滚动结果集:

Connection conn = getConn();
/*
 *conn.createStatement(type,concurrency)
 */
// TYPE_SCROLL_SENSITIVE:结果集能滚动,对数据库变化敏感
// CONCUR_UPDATABLE:结果集可以用于更新数据库
Statement stat = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stat.executeQuery("select * from user");
// 移动游标方法
rs.next();
rs.relative(3);
rs.previous();
rs.relative(-1);
rs.absolute(5);
 
// 特殊位置
rs.first();
rs.last();
rs.beforeFirst();
rs.afterLast();

ps:因为在操作过程中,一直连接着数据库,可滚动结果集,消耗数据库资源巨大。

 

可更新结果集:

Connection conn = getConn();
// TYPE_SCROLL_SENSITIVE:结果集能滚动,对数据库变化敏感
// CONCUR_UPDATABLE:结果集可以用于更新数据库
Statement stat = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stat.executeQuery("select * from user");
rs.next();
rs.updateString("name","u-2");
rs.updateInt("age", 22);
rs.updateRow();
 
 
rs.insertRow();
rs.deleteRow();

ps:

①更新数据库行,必须要updateRow(),这样可以将变化同步到数据库;插入数据库一行,使用updateXxx,之后要使用insertRow(),rs.moveToCurrentRow();deleteRow()直接将当前行删除。

②如果查询语句涉及到多个表的连接操作,那么它所产生的结果集将是不可更新的;若是利用主键连接表的,那么它还是可更新结果集。

4.5行集

         RowSet接口扩展自ResultSet接口,却无需始终保持与数据库连接,可以完美解决可滚动结果集的,数据库资源消耗问题。

行集分几种:

CachedRowSet:缓存的行集。

WebRowSet:缓存的行集,可保存为XML文件,该文件可移动到Web应用。

FilteredRowSet和JoinRowSet:支持对行集的轻量级操作。

JdbcRowSet:是ResultSet接口的瘦包装器。

 

行集中的数据来源??

使用JDBC驱动程序从数据库检索的数据

从其他数据源获得的数据,如文件数据

 

行集(Row  Set)的优点??

1)可以断开数据库连接操作数据;

2)可以在分布式系统中的不同组件之间传递;

3)默认可更新,可滚动,可序列化,可以方便的在网络间传输;

4)可以方便的使数据在行集与JavaBean对象之间进行转换。行集中的一行数据可以封装为一个JavaBean对象。

Connection conn = getConn();
PreparedStatement pst=conn.prepareStatement("select * from user"); 
//必须设置非自动提交 
conn.setAutoCommit(false); 
ResultSet rs=pst.executeQuery(); 
//创建行集实例 
CachedRowSetImpl rowset=newCachedRowSetImpl(); 
//填充 
rowset.populate(rs); 
 
//这里已经关闭结果集了!!!
rs.close(); 
pst.close(); 
 
// 在关闭连接之前进行更新操作
rowset.absolute(5); 
rowset.updateInt("English", 55); 
rowset.updateRow();  //更新 
rowset.acceptChanges(conn); //提交 
 
//输出结果集之前,关闭连接 
conn.close(); 
 
//输出行集数据 
while(rowset.next()){ 
    System.out.print(rowset.getInt("id")+"\t"); 
    System.out.print(rowset.getInt("Chinese")+"\t"); 
    System.out.print(rowset.getInt("English")+"\t"); 
    System.out.println(rowset.getInt("history")); 
}    

5、元数据

元数据:描述数据库或者其组成部分的数据。

 

元数据分为:

关于数据库的元数据(DatabaseMetaData)

关于结果集的元数据(ResultSetMetaData)

关于预备语句参数的元数据(ParameterMetaData)

 

数据库元数据

// 获取连接
Connection conn = JdbcUtil.getConnection();
// 获取数据库元数据
DatabaseMetaData metaData = conn.getMetaData();//alt + shift + L 快速获取方法返回值
     
System.out.println(metaData.getUserName());
System.out.println(metaData.getURL());
System.out.println(metaData.getDatabaseProductName());

参数元数据

// 获取连接
Connection conn = JdbcUtil.getConnection();
// SQL
String sql = "select * from dept wheredeptid=? and deptName=?";
// Object[] values = {"tom","888"};
           
PreparedStatement pstmt = conn.prepareStatement(sql);
// 参数元数据
ParameterMetaData p_metaDate = pstmt.getParameterMetaData();
// 获取参数的个数
int count = p_metaDate.getParameterCount();
// 测试
System.out.println(count); 

结果集元数据

Connection conn = JdbcUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement("select * from dept ");
ResultSet rs = pstmt.executeQuery();
// 得到结果集元数据(目标:通过结果集元数据,得到列的名称)
ResultSetMetaData rs_metaData = rs.getMetaData();          
// 迭代每一行结果
while (rs.next()) {
      // 1. 获取列的个数
      int count = rs_metaData.getColumnCount();
      // 2. 遍历,获取每一列的列的名称
      for (int i=0; i<count; i++) {
            // 得到列的名称
            String columnName= rs_metaData.getColumnName(i + 1);
            // 获取每一行的每一列的值
            ObjectcolumnValue = rs.getObject(columnName);
            // 测试
            System.out.print(columnName +"=" + columnValue + ",");
      }
      System.out.println();
}

说明:getObject可以拿到任意类型行的值

6、事务

            事务:将一组语句构建成一个事务,当所有语句都顺利执行,事务可以被提交。否则,如果其中某个语句遇到错误,那么事务将被回滚。

            事务:确保数据库万整性。

 

默认情况下,数据库连接处于自动提交模式。

Connection con;
PreparedStatement pstmt;
String sql_zs = "UPDATE account SETmoney=money-1000 WHERE accountName='张三';";
String sql_ls = "UPDATE1 account SETmoney=money+1000 WHERE accountName='李四';";
try {
      con = JdbcUtil.getConnection();// 默认开启的隐士事务
      // 一、设置事务为手动提交
      con.setAutoCommit(false);
 
      /*** 第一次执行SQL ***/
      pstmt = con.prepareStatement(sql_zs);
      pstmt.executeUpdate();
 
      /*** 第二次执行SQL ***/
      pstmt = con.prepareStatement(sql_ls);
      pstmt.executeUpdate();
} catch(Exception e) {
      try {
            // 二、出现异常,需要回滚事务
            con.rollback();
      } catch (SQLException e1) {
            e.printStackTrace();
}
} finally {
      try {
            // 三、所有的操作执行成功,提交事务
            con.commit();
            JdbcUtil.closeAll(con,pstmt, null);
      } catch (SQLException e) {
      }
}


保存点:使用保存点可以更细粒度的控制回滚操作

. . .
try {
      con = JdbcUtil.getConnection();// 默认开启的隐士事务
      // 一、设置事务为手动提交
      con.setAutoCommit(false);
 
      /*** 第一次执行SQL ***/
      pstmt = con.prepareStatement(sql_zs);
      pstmt.executeUpdate();
 
      Savepoint svpt = pstmt.setSavepoint();
 
      /*** 第二次执行SQL ***/
      pstmt = con.prepareStatement(sql_ls);
      pstmt.executeUpdate();
} catch(Exception e) {
      // 二、出现异常,需要回滚到回滚点
      con.rollback(svpt);
      . . .
} finally {
      . . .
}

Ps:当不需要回滚点时,可以释放回滚点:使用conn.releaseSavepoint(svpt)。

7、批量操作

         当程序需要执行多条语句时,可以使用批量操作,如执行上万条insert语句,可以使用批量更新来提高程序性能。

// SQL
String sql = "INSERT INTOadmin(userName,pwd) values(?,?)";
try {
      // 获取连接
      con = JdbcUtil.getConnection();
      // 创建stmt
      pstmt = con.prepareStatement(sql);             // 【预编译SQL语句】
      for (int i=0; i<list.size(); i++) {
            Admin admin =list.get(i);
            // 设置参数
            pstmt.setString(1,admin.getUserName());
            pstmt.setString(2, admin.getPwd());            
            // 添加批处理
            pstmt.addBatch();                              // 【不需要传入SQL】
                       
            // 测试:每5条执行一次批处理
            if (i % 5 == 0) {
                  // 批量执行
                  pstmt.executeBatch();
                  // 清空批处理
                  pstmt.clearBatch();
            }
      }
                 
      // 批量执行
      pstmt.executeBatch();
      // 清空批处理
      pstmt.clearBatch();
} catch(Exception e) {
      e.printStackTrace();
} finally {
      JdbcUtil.closeAll(con, pstmt, rs);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章