[疯狂Java]JDBC:ResultSet的升级RowSet、离线的CachedRowSet、离线分页查询

1. RowSet系列、创建RowSet实例:

    1) RowSet的出现最初是为了解决离线缓存的问题,因为在使用ResultSet的时候必须保证在线(即保持与数据库的连接),连接后必须立即处理,否则连接断开则ResultSet也将关闭,这就非常麻烦,再有些设计模式中,要求逻辑和视图分开,因此就必须自己先开一段内存暂存ResultSet中的内容(这样ResultSet关闭了里面的数据已经拿出来了),然后再交给视图层显示,而不能直接将ResultSet作为视图层的绘图数据使用;

    2) 但现在RowSet的标准已经更加全面和完善,与ResultSet相比RowSet默认就是可滚动、可更新、可序列话的,无需打开任何开关,并且操作上比ResultSet更加简单;

!!最重要的是RowSet直接被设计成JavaBean,因此可以方便地用于网络传输、MVC设计模式,而离线缓存更是让其如虎添翼;

    3) RowSet在Java中只是一个上层类(抽象类),实际中有很多RowSet的具体实现来适应不同环境中的应用:

         i. 首先是ResultSet的直接扩展——JdbcRowSet:直接继承于RowSet,仅仅就是ResultSet的扩展,和ResultSet一样不是离线的,使用时必须保持在线状态!

         ii. 另一大分支(离线RowSet)——CachedRowSet:名字就叫离线缓存RowSet,也直接继承于RowSet,比JdbcRowSet更进一步,支持离线缓存;

         iii. 从CachedRowSet派生出了应用于各种场景的离线RowSet:WebRowSet(支持网络数据,直接继承于CachedRowSet)、JoinRowSet(继承于WebRowSet)、FilteredRowSet(继承于WebRowSet);

    4) RowSet的实现仍然在时刻发展着:

         i. 现代IT环境迭代迅速,RowSet为了不断适应新的环境,JDK提供的实现也是时不时地更新(RowSet的标准也仍然在不断完善中),至今还没有一个彻底稳定、固定的实现版本;

         ii. 目前JDK也只是暂时给各种RowSet提供了一个实现类,类名就是相应RowSet名后加一个Impl后缀(即Implement的缩写),例如:JdbcRowSetImpl、CachedRowSetImpl等;

         iii. 这些实现类都是JDK未公开的API,在未来可能删除用新的代替(因此名称也许会彻底改变),虽然可以直接在程序中使用,但是使用后编译器会发出警告,如果这些API一旦发生更新,那程序就非常不好维护和升级;

    5) 因此Java提供了RowSetFactory和RowSetProvider使RowSet的实现和应用程序彻底分离,这样更有利于程序后期的升级和扩展:

         i. 要获取具体的RowSet实例就必须先通过RowSetProvider(即RowSet供应商)提供一个可以制造RowSet的工厂(RowSetFactory实例),然后再用工厂来制造具体的RowSet实例:

         ii. 上述过程中用到的方法:

             a. static RowSetFactory RowSetProvider.newFactory();  // 静态方法,创建一个工厂实例

             b. XxxRowSet RowSetFactory.createXxxRowSet();  // 对象方法,用工厂创建一个具体的RowSet实例

!!Xxx可以是Jdbc、Cached、Web、Join、Filtered的任意一种,都是具体的RowSet实现类;

         iii. 示例:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. RowSetFactory factory = RowSetProvider.newFactory();  
  2. CachedRowSet crs = factory.createCachedRowSet();  
         

2. 如何关联RowSet和数据库的连接:

    1) 从上面可以看到,只能通过RowSetProvider和RowSetFactory的方式创建RowSet实例,但这两个方法中并没有让RowSet和数据库建立连接!!

    2) 两种建立连接的方式:这些方法都是在上层抽象类RowSet中定义的,因此其所有派生类(Jdbc、Web、Cached等也都拥有)

         i. 直连:直接让RowSet连接数据库,不再需要用Connection连接了,这是最常用的,简化了很多步骤!!因此在这里可以看到RowSet的强大!

            a. 提供了setUrl、setUsername、setPassword方法设置数据库连接三要素;

            b. void setUrl(String url);

            c. void setUsername(String name);

            d. void setPassword(String password);

            e. 三要素设置好之后就完成连接了!

         ii. 被动地包装ResultSet:void populate(ResultSet data);

!!这种方式需要先用传统的Connection方法建立连接、获得SQL语句句柄并执行返回ResultSet后再用populate将其包装成可滚动、可更新、可序列化的同时也是JavaBean的RowSet;

!!可以看到过程非常麻烦,但还是很有用武之地的,这个后面会解释;


3. 如果采用直连法让RowSet连接数据库,那么就需要让其执行SQL语句(这样才能得到想要的查询结果呀!):

    1) 首先需要设置RowSet要执行的SQL语句:void setCommand(String cmd); // cmd就是要执行的SQL语句,既然是和ResultSet是一脉相承的,那必然也只能执行Query语句!

    2) 接着就是执行查询了:void execute();  // 很简单,就是直接(提交)执行

!!注意:RowSet直接执行SQL语句是不支持预编译的,都是直接提交执行的!!

!!因此需要对查询预编译还是得走ResultSet的老路,然后用populate包装成RowSet;

!!因此有预编译需求的还是得先走ResultSet的老路;


4. 结果集的分析和修改:

    1) 完全和ResultSet一模一样!!!

    2) 记录指针定位方法一模一样(next、previous、afterlast等);

    3) 获取列值的方法一模一样(getXxx(可以用列索引指定也可以用列名指定));

    4) 修改结果集并更新到数据库的方法也一模一样(updateXxx在结果集中临时修改、updateRow将修改更新到数据库中);


5. 离线状态下如何将修改更新到真实数据库?

    1) 如果处于在线状态,那么调用完updateRow之后会立即更新到数据库,但是如果RowSet已经离线了,那么及时updateRow也无法更新到数据库中,因为此时连接已经断开;

    2) 因此首先必须得重新连接数据库,还是使用DriverManager.getConnection的那一套重新连接;

    3) 然后调用RowSet的acceptChanges方法(将重新连接的连接句柄conn作为参数)接受RowSet的修改(将更新同步到数据库):

         i. void RowSet.acceptChanges(Connection con);  // 将RowSet中对记录的更改同步到conn所连接的数据库中

!!还有一种是无参版本的:

         ii. void RowSet.acceptChanges();

             a. 此版本在没有离线的状态下使用,因此无需指定连接句柄;

             b. 但是在线状态下直接updateRow不就自动同步到数据库了吗?为啥还需要再调用acceptChanges呢?

             c. 设想,每次updateRow就要同步一遍数据库(底层其实是构造了一个SQL查询并提交执行),那如果这样的修改非常非常多呢?(特别是在段时间内要执行很多次)那岂不是效率太低了吗?能不能一次将所有的更改一步到位批量送入数据库进行更新呢?

             d. 答案是肯定的,你可以先通过Connection的setAutoCommit方法关闭自动提交的功能:void Connection.setAutoCommit(boolean autoCommit);  // true开启(默认),false关闭

!!关闭后每次调用updateRow就不会立即同步到数据库,而是把此次更新送入批处理队列中,知道调用无参版的accpetChanges之后才会一次性将批处理队列中的所有更新全部提交到数据库运行!

    4) 批处理更新示例(在线状态下,离线状态重连后也必须关掉自动提交才能实现批处理):

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. conn.setAutoCommit(false); // 一定要在更新语句之前关闭!  
  2. 多次updateXxx、updateRow;  
  3. rs.acceptChanges();  


5. 示例:以CachedRowSet为例

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class Test {  
  2.     private String driver;  
  3.     private String url;  
  4.     private String user;  
  5.     private String pass;  
  6.       
  7.     public void initParam() throws FileNotFoundException, IOException {  
  8.         Properties props = new Properties();  
  9.         props.load(new FileInputStream("mysql.ini"));  
  10.         driver = props.getProperty("driver");  
  11.         url = props.getProperty("url");  
  12.         user = props.getProperty("user");  
  13.         pass = props.getProperty("pass");  
  14.     }  
  15.       
  16.     public CachedRowSet query(String sql) throws ClassNotFoundException, SQLException {  
  17.         Class.forName(driver);  
  18.         Connection conn = DriverManager.getConnection(url, user, pass);  
  19.         Statement stmt = conn.createStatement();  
  20.         ResultSet rs = stmt.executeQuery(sql);  
  21.           
  22.         RowSetFactory factory = RowSetProvider.newFactory();  
  23.         CachedRowSet crs = factory.createCachedRowSet();  
  24.         crs.populate(rs);  
  25.           
  26.         // 显式关闭所有连接资源  
  27.         rs.close();  
  28.         stmt.close();  
  29.         conn.close();  
  30.           
  31.         return crs; // 这样返回的RowSet仍然能用,说明被离线缓存了  
  32.     }  
  33.       
  34.     public void init() throws FileNotFoundException, IOException, ClassNotFoundException, SQLException {  
  35.         initParam();  
  36.         CachedRowSet rs = query("select * from student_table"); // 虽然连接已关闭,但结果集被离线缓存下来了  
  37.           
  38.         rs.afterLast(); // 可滚动  
  39.         while (rs.previous()) {  
  40.             System.out.println( // 解析  
  41.                 rs.getString(1) + '\t' +  
  42.                 rs.getString(2) + '\t' +  
  43.                 rs.getString(3)  
  44.             );  
  45.               
  46.             if (rs.getInt("student_id") == 3) { // 更新  
  47.                 rs.updateString("student_name""QQQ");  
  48.                 rs.updateRow();  
  49.             }  
  50.         }  
  51.           
  52.         // 重连,同步到真实数据库  
  53.         Connection conn = DriverManager.getConnection(url, user, pass);  
  54.         conn.setAutoCommit(false); // 先不管,这个跟事务管理有关  
  55.         rs.acceptChanges(conn); // 由于之前连接资源已断,因此要调用有参版本的  
  56.     }  
  57.       
  58.     public static void main(String[] args) throws FileNotFoundException, ClassNotFoundException, IOException, SQLException {  
  59.         new Test().init();  
  60.     }  
  61.   
  62. }  


6. 离线分页查询:

    1) 首先补充装填的概念:

         i. RowSet调用populate包装ResultSet的过程就叫装填,即直接将ResultSet连接数据库返回的全部结果集装填到RowSet中;

         ii. 如果是可离线缓存的RowSet,那么这种装填会将全部结果一次性全部装入内存缓冲区中;

         iii. 设想,如果结果的量非常庞大,那么RowSet将会占用庞大的内存,不仅降低效率甚至容易导致内存溢出!!

    2) 因此RowSet提供了分页控制方法来限定你每次可装填的内容的大小,让你多次分页装填记录并分析;

    3) 首先你需要设置每页的大小(即每次可以装填多少条记录):void RowSet.setPageSize(int size);  // size即页的大小(单位是条记录),默认状态下是无限条(即有多少装多少)

    4) 接着就是装填了:

         i. void populate(ResultSet data);  // 默认从第一条记录开始装填一页的大小

         ii. void populate(ResultSet rs, int startRow);  // 指定从第startRow条记录开始装填一页的大小

!!要现调用setPageSize设定的页的大小,否则就默认从指定记录开始装填到整个ResultSet的最后一条记录

    5) 定位页:定位到要访问的页,其实就是将要访问的页自动装填到当前RowSet中

         i. boolean RowSet.nextPage();  // 将当前页的下一页装填到RowSet中,如果当前已经是最后一页了则返回false

         ii. boolean previousPage();  // 装载上一页,如果已经是第一页了则返回false

    6) 示例:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class Test {  
  2.     private String driver;  
  3.     private String url;  
  4.     private String user;  
  5.     private String pass;  
  6.       
  7.     public void initParam() throws FileNotFoundException, IOException {  
  8.         Properties props = new Properties();  
  9.         props.load(new FileInputStream("mysql.ini"));  
  10.         driver = props.getProperty("driver");  
  11.         url = props.getProperty("url");  
  12.         user = props.getProperty("user");  
  13.         pass = props.getProperty("pass");  
  14.     }  
  15.       
  16.     // pageSize是页大小,page表示从第几页开始装载  
  17.     public CachedRowSet query(String sql, int pageSize, int page) throws ClassNotFoundException, SQLException {  
  18.         Class.forName(driver);  
  19.         Connection conn = DriverManager.getConnection(url, user, pass);  
  20.         Statement stmt = conn.createStatement();  
  21.         ResultSet rs = stmt.executeQuery(sql);  
  22.           
  23.         RowSetFactory factory = RowSetProvider.newFactory();  
  24.         CachedRowSet crs = factory.createCachedRowSet();  
  25.   
  26.         crs.setPageSize(pageSize);  
  27.         crs.populate(rs, (page - 1) * pageSize + 1);  
  28.           
  29.         // 所有连接资源统统关闭  
  30.         rs.close();  
  31.         stmt.close();  
  32.         conn.close();  
  33.           
  34.         return crs; // 这样返回的RowSet仍然能用,说明被离线缓存了  
  35.     }  
  36.       
  37.     public void init() throws FileNotFoundException, IOException, ClassNotFoundException, SQLException {  
  38.         initParam();  
  39.         CachedRowSet rs = query("select * from student_table"32); // 每页3条记录,查询第2页的内容  
  40.           
  41.         rs.afterLast(); // 可滚动  
  42.         while (rs.previous()) {  
  43.             System.out.println( // 解析  
  44.                 rs.getString(1) + '\t' +  
  45.                 rs.getString(2) + '\t' +  
  46.                 rs.getString(3)  
  47.             );  
  48.         }  
  49.           
  50.         // 重连,同步到真实数据库  
  51.         Connection conn = DriverManager.getConnection(url, user, pass);  
  52.         conn.setAutoCommit(false);  
  53.         rs.acceptChanges(conn); // 由于之前连接资源已断,因此要调用有参版本的  
  54.     }  
  55.       
  56.     public static void main(String[] args) throws FileNotFoundException, ClassNotFoundException, IOException, SQLException {  
  57.         new Test().init();  
  58.     }  
  59.   
  60. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值