[疯狂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. 示例:

RowSetFactory factory = RowSetProvider.newFactory();
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) 批处理更新示例(在线状态下,离线状态重连后也必须关掉自动提交才能实现批处理):

conn.setAutoCommit(false); // 一定要在更新语句之前关闭!
多次updateXxx、updateRow;
rs.acceptChanges();


5. 示例:以CachedRowSet为例

public class Test {
	private String driver;
	private String url;
	private String user;
	private String pass;
	
	public void initParam() throws FileNotFoundException, IOException {
		Properties props = new Properties();
		props.load(new FileInputStream("mysql.ini"));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	
	public CachedRowSet query(String sql) throws ClassNotFoundException, SQLException {
		Class.forName(driver);
		Connection conn = DriverManager.getConnection(url, user, pass);
		Statement stmt = conn.createStatement();
		ResultSet rs = stmt.executeQuery(sql);
		
		RowSetFactory factory = RowSetProvider.newFactory();
		CachedRowSet crs = factory.createCachedRowSet();
		crs.populate(rs);
		
		// 显式关闭所有连接资源
		rs.close();
		stmt.close();
		conn.close();
		
		return crs; // 这样返回的RowSet仍然能用,说明被离线缓存了
	}
	
	public void init() throws FileNotFoundException, IOException, ClassNotFoundException, SQLException {
		initParam();
		CachedRowSet rs = query("select * from student_table"); // 虽然连接已关闭,但结果集被离线缓存下来了
		
		rs.afterLast(); // 可滚动
		while (rs.previous()) {
			System.out.println( // 解析
				rs.getString(1) + '\t' +
				rs.getString(2) + '\t' +
				rs.getString(3)
			);
			
			if (rs.getInt("student_id") == 3) { // 更新
				rs.updateString("student_name", "QQQ");
				rs.updateRow();
			}
		}
		
		// 重连,同步到真实数据库
		Connection conn = DriverManager.getConnection(url, user, pass);
		conn.setAutoCommit(false); // 先不管,这个跟事务管理有关
		rs.acceptChanges(conn); // 由于之前连接资源已断,因此要调用有参版本的
	}
	
	public static void main(String[] args) throws FileNotFoundException, ClassNotFoundException, IOException, SQLException {
		new Test().init();
	}

}


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) 示例:

public class Test {
	private String driver;
	private String url;
	private String user;
	private String pass;
	
	public void initParam() throws FileNotFoundException, IOException {
		Properties props = new Properties();
		props.load(new FileInputStream("mysql.ini"));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	
	// pageSize是页大小,page表示从第几页开始装载
	public CachedRowSet query(String sql, int pageSize, int page) throws ClassNotFoundException, SQLException {
		Class.forName(driver);
		Connection conn = DriverManager.getConnection(url, user, pass);
		Statement stmt = conn.createStatement();
		ResultSet rs = stmt.executeQuery(sql);
		
		RowSetFactory factory = RowSetProvider.newFactory();
		CachedRowSet crs = factory.createCachedRowSet();

		crs.setPageSize(pageSize);
		crs.populate(rs, (page - 1) * pageSize + 1);
		
		// 所有连接资源统统关闭
		rs.close();
		stmt.close();
		conn.close();
		
		return crs; // 这样返回的RowSet仍然能用,说明被离线缓存了
	}
	
	public void init() throws FileNotFoundException, IOException, ClassNotFoundException, SQLException {
		initParam();
		CachedRowSet rs = query("select * from student_table", 3, 2); // 每页3条记录,查询第2页的内容
		
		rs.afterLast(); // 可滚动
		while (rs.previous()) {
			System.out.println( // 解析
				rs.getString(1) + '\t' +
				rs.getString(2) + '\t' +
				rs.getString(3)
			);
		}
		
		// 重连,同步到真实数据库
		Connection conn = DriverManager.getConnection(url, user, pass);
		conn.setAutoCommit(false);
		rs.acceptChanges(conn); // 由于之前连接资源已断,因此要调用有参版本的
	}
	
	public static void main(String[] args) throws FileNotFoundException, ClassNotFoundException, IOException, SQLException {
		new Test().init();
	}

}




你可能感兴趣的:(疯狂Java笔记)