目录:
一、RowSetFactory与RowSet
二、离线RowSet
三、离线RowSet的查询分页
RowSet规范的接口示意图:
除了JdbcRowSet需要保持与数据库保持连接之外,其余4个子接口都是离线的RowSet,无须保持与数据库的连接。
与RowSet相比,RowSet默认是可滚动的、可更新的、可序列化的结果集,而且作为JavaBean使用,因此能方便在网络上传输,用于同步两端的数据。对于离线的RowSet而言,程序在创建RowSet时,已经把数据从底层数据库读取到内存,因此可以充分利用计算机内存,从而降低数据库服务器的负载,提高程序性能。
一、RowSetFactory与RowSet
Java 7新增RowSetProvider类和RowSetFactory接口,其中RowSetProvider负载创建RowSetFactory,而RowSetFactory则提供了如下方法来创建RowSet实例。
(1)CachedRowSet createCachedRowSet():创建一个默认的CachedRowSet。
(2)FilteredRowSet createFilteredRowSet():创建一个默认的FilteredRowSet。
(3)JdbcRowSet createJdbcRowSet():创建一个默认的JdbcRowSet。
(4)JoinRowSet createJoinRowSet():创建一个默认JoinRowSet。
(5)WebRowSet createWebRowSet():创建一个默认的WebRowSet。
通过RowSetFactory,就可以把应用程序与RowSet实现类分离开,避免直接使用JdbcRowSet SetImpl等非公开的API,也利于后期的升级、扩展。
使用RowSetFactory创建的RowSet并没有创建装填数据。为了让RowSet能够抓取到数据库的数,需要为RowSet设置数据库的URL、用户名、密码等连接信息。因此,RowSet接口中定义了如下方法:
(1)setUrl(String url):设置RowSet要访问的数据库的URL。
(2)setUsername(String name):设置该RowSet要访问数据库的用户名。
(3)setPassword(String password):设置该RowSet要访问数据库的密码。
(4)setCommand(String sql):设置使用该sql语句查询结果来装填RowSet。
(5)excute():执行查询。
下面程序通过了RowSetFactory示范了JdbcRowSet的可滚动性、可修改性。
1 package section6; 2 3 import javax.sql.rowset.JdbcRowSet; 4 import javax.sql.rowset.RowSetFactory; 5 import javax.sql.rowset.RowSetProvider; 6 import java.io.FileInputStream; 7 import java.util.Properties; 8 9 public class RowSetFactoryTest 10 { 11 private String driver; 12 private String url; 13 private String user; 14 private String pass; 15 16 public void initParam(String fileName) 17 throws Exception 18 { 19 //使用Properties类加载属性 20 Properties props=new Properties(); 21 props.load(new FileInputStream(fileName)); 22 driver=props.getProperty("driver"); 23 url=props.getProperty("url"); 24 user=props.getProperty("user"); 25 pass=props.getProperty("pass"); 26 } 27 28 public void updata(String sql) 29 throws Exception 30 { 31 //加载驱动 32 Class.forName(driver); 33 //使用RowSetProvider创建RowSetFactory 34 RowSetFactory factory= RowSetProvider.newFactory(); 35 try( 36 //使用RowSetFactory创建默认的JdbcRowSet实例 37 JdbcRowSet jdbcRs=factory.createJdbcRowSet() 38 ) 39 { 40 //设置必要连接信息 41 jdbcRs.setUrl(url); 42 jdbcRs.setUsername(user); 43 jdbcRs.setPassword(pass); 44 //设置SQL查询语句 45 jdbcRs.setCommand(sql); 46 //执行结果 47 jdbcRs.execute(); 48 jdbcRs.afterLast(); 49 //先前滚动结果集 50 while(jdbcRs.previous()) 51 { 52 System.out.println(jdbcRs.getString(1)+" "+ 53 jdbcRs.getString(2)+" "+ 54 jdbcRs.getString(3)); 55 if(jdbcRs.getInt("student_id")==3) 56 { 57 //修改指定记录行 58 jdbcRs.updateString("student_name","孙悟空"); 59 jdbcRs.updateRow(); 60 } 61 } 62 } 63 } 64 public static void main(String[] args) 65 throws Exception 66 { 67 var fac=new RowSetFactoryTest(); 68 fac.initParam("src\mysql.ini"); 69 fac.updata("select *from student_table"); 70 71 } 72 }
编译运行该程序,一切正常。JdbcRowSet是一个可滚动、可修改的结果集,因此底层的数据表中的记录也被修改了。
二、离线RowSet
在使用ResultSet时代,程序查询的得到ResultSet之后必须立即读取或处理它对应的记录,否则一旦Connection关闭,再通过ResultSet读取记录就会引发异常。假设应用程序架构分为两层:数据访问层和视图显示层,当应用程序在数据访问层查询得到ResultSet之后,对ResultSet的处理有如下两种常见方式:
·1、使用迭代访问ResultSet里的记录,并将这些记录转换成Java Bean,再将多个Java Bean封装成一个List集合,也就是完成“ResultSet—>Java Bean集合”的转换。转换完成后就可以关闭Connection等资源,然后将Java Bean集合传到视图显示层,视图显示层就可以显示查询得到的数据。
2、直接将ResultSet传到视图显示层——这就要求当视图显示层显示数据时,底层Connection必须一直处于打开状态,否则ResultSet无法读取记录。
第一种方式比较安全,但编程复杂;第二种方式则需要Connection一直处在打开状态,这不仅不安全,而且对程序性能影响大。
离线RowSet会直接将底层数据读入内存中,封装成RowSet对象,而RowSet对象则完全可以当成Java Bean来使用。因此不仅编安全,而且编程简单。CachedRowSet是所有离线RowSet的父接口,因此下面以CachedRowSet为例进行介绍:
1 package section6; 2 3 import javax.sql.rowset.CachedRowSet; 4 import javax.sql.rowset.RowSetFactory; 5 import javax.sql.rowset.RowSetProvider; 6 import java.io.FileInputStream; 7 import java.sql.*; 8 import java.util.Properties; 9 10 public class CachedRowSetTest 11 { 12 private static String driver; 13 private static String url; 14 private static String user; 15 private static String pass; 16 public void initParam(String fileName) 17 throws Exception 18 { 19 //使用Properties类来加载属性 20 Properties props=new Properties(); 21 props.load(new FileInputStream(fileName)); 22 driver=props.getProperty("driver"); 23 url=props.getProperty("url"); 24 user=props.getProperty("user"); 25 pass=props.getProperty("pass"); 26 } 27 public CachedRowSet query(String sql) 28 throws Exception 29 { 30 //加载驱动 31 Class.forName(driver); 32 //获取数据库连接 33 Connection conn= DriverManager.getConnection(url,user,pass); 34 Statement stmt=conn.createStatement(); 35 ResultSet rs=stmt.executeQuery(sql); 36 //使用RowSetProvider创建RowSetFactory 37 RowSetFactory factory= RowSetProvider.newFactory(); 38 //创建默认的CachedRowSet实例 39 CachedRowSet cachedRs=factory.createCachedRowSet(); 40 //使用ResultSet装填CachedRowSet 41 cachedRs.populate(rs); 42 //关闭资源 43 rs.close(); 44 stmt.close(); 45 conn.close(); 46 return cachedRs; 47 } 48 public static void main(String[] args) 49 throws Exception 50 { 51 var ct=new CachedRowSetTest(); 52 ct.initParam("src\mysql.ini"); 53 CachedRowSet rs=ct.query("select *from student_table;"); 54 rs.afterLast(); 55 //向前滚动查询结果集 56 while(rs.previous()) 57 { 58 System.out.println(rs.getString(1)+" "+ 59 rs.getString(2)+" "+ 60 rs.getString(3)); 61 if(rs.getInt("student_id")==3) 62 { 63 //修改指定记录行 64 rs.updateString("student_name","孙悟空"); 65 rs.updateRow(); 66 } 67 } 68 //重新获取数据库连接 69 Connection conn= DriverManager.getConnection(url,user,pass); 70 conn.setAutoCommit(false); 71 //把RowSet所做的修改同步到底层数据库 72 rs.acceptChanges(conn); 73 } 74 }
上面代码调用了RowSet的populate(ResultSet rs)方法来包装给指定的ResultSet,接下来的关闭了ResultSet、Statement、Connection等数据库资源。如果程序直接返回ResultSet,那么这个ResultSet将无法使用——因为底层的Connection已经关闭:但程序返回的是CachedRowSet,它是一个离线的RowSet,因此程序依然可以读取、修改RowSet中的记录。
三、离线RowSet的查询分页
由于CachedRowSet会将数据记录直接载入到内存中,因此如果SQL语句查询返回的记录过大,CachedRowSet会占用大量的内存,中某些极端情况下,它甚至会导致内存溢出。
为了解决上面的问题,CachedRowSet提供了分页功能——一次只装载ResultSet里的某几条记录,这样可以避免CachedRowSet占用内存过大的问题。
CachedRowSet提供如下方法控制分页:
1、populate(ResultSet rs,int starRow):使用给定ResultSet装填RowSet,从ResultSet里的第starRow条记录开始装填。
2、setPageSize(int pageSize):设置CachedRowSet每次返回多少条记录。
3、previousPage():在底层ResultSet可以的情况下,让CachedRowSet读取上一页记录。
4、nextPage():在底层ResultSet可以的情况下,让CachedRowSet读取下一页记录。
1 package section6; 2 3 import javax.sql.rowset.CachedRowSet; 4 import javax.sql.rowset.RowSetFactory; 5 import javax.sql.rowset.RowSetProvider; 6 import java.io.FileInputStream; 7 import java.sql.Connection; 8 import java.sql.DriverManager; 9 import java.sql.ResultSet; 10 import java.sql.Statement; 11 import java.util.Properties; 12 13 public class CachedRowSetPage 14 { 15 private static String driver; 16 private static String url; 17 private static String user; 18 private static String pass; 19 20 public void initParam(String fileName) 21 throws Exception 22 { 23 //使用Properties类来加载属性 24 Properties props=new Properties(); 25 props.load(new FileInputStream(fileName)); 26 driver=props.getProperty("driver"); 27 url=props.getProperty("url"); 28 user=props.getProperty("user"); 29 pass=props.getProperty("pass"); 30 } 31 public CachedRowSet query(String sql,int pageSize,int page) 32 throws Exception 33 { 34 //加载驱动 35 Class.forName(driver); 36 try( 37 //获取数据库连接 38 Connection conn= DriverManager.getConnection(url,user,pass); 39 Statement stmt=conn.createStatement(); 40 ResultSet rs=stmt.executeQuery(sql) 41 ) 42 { 43 // 使用RowSetProvider创建RowSetFactory 44 RowSetFactory factory=RowSetProvider.newFactory(); 45 //创建CachedRowSet实例 46 CachedRowSet cachedRs=factory.createCachedRowSet(); 47 //设置每页显示pageSize条记录 48 cachedRs.setPageSize(pageSize); 49 //使用ResultSet装填RowSet,设置从第几条记录开始 50 cachedRs.populate(rs,(page-1)*pageSize+1); 51 return cachedRs; 52 } 53 } 54 public static void main(String[] args) 55 throws Exception 56 { 57 var cp=new CachedRowSetPage(); 58 cp.initParam("src\mysql.ini"); 59 CachedRowSet rs=cp.query("select *from student_table;",3,2);//① 60 //向后滚动结果集 61 while(rs.next()) 62 { 63 System.out.println(rs.getString(1)+" " 64 +rs.getString(2)+" " 65 +rs.getString(3)); 66 } 67 } 68 }
运行结果:
1 4 学生名4 2 2 5 学生名5 2 3 6 学生名6 2
程序①号带啊吗显示要查询第2页记录,每页显示3条记录。运行上面程序,可以看到程序只会显示第4行到第6行的记录,这就实现了分页。