Java面向对象系列[v1.0.0][RowSet]

RowSet

RowSet接口继承了ResultSet,并包含JdbcRowSet、CachedRowSet、FilteredRowSet、JoinRowSet和WebRowSet,除JdbcRowSet外其余4个都是离线的RowSet,与ResultSet相比,RowSet默认可滚动、可更新、可序列化,且作为JavaBean使用,因此方便在网络上传输,用于同步两端数据,而对于离线RowSet而言,程序在创建RowSet时已把数据从底层数据库读取到了内存,因此可充分利用计算机内存,降低数据库服务器的负载提高性能

C#提供了DataSet将底层数据读取到内存中进行离线操作,操作完成后在同步到底层数据源,Java则提供了类似的RowSet

在这里插入图片描述
如UML所示,CachedRowSet及其子接口都是离线RowSet,都不需要底层数据库连接

理解并掌握RowSet、RowSetFactory

Java7新增了RowSetProvider类和RowSetFactory接口,其中RowSetProvider负责创建RowSetFactory,而RowSetFactory则提供了如下方法来创建RowSet实例

  • CachedRowSet createCachedRowSet():创建一个默认的CachedRowSet
  • FilteredRowSet createFilteredRowSet():创建一个默认的FilteredRowSet
  • JdbcRowSet createJdbcRowSet():创建一个默认的JdbcRowSet
  • JoinRowSet createJoinRowSet():创建一个默认的JoinRowSet
  • WebRowSet createWebRowSet():创建一个默认的WebRowSet

通过使用RowSetFactory就可以把应用程序与RowSet实现类分离,避免直接使用JdbcRow SetImpl等非公开的API,更便于升级和扩展

RowSet接口中定义了几个常用方法:

  • setUrl(String url):设置该RowSet要访问的数据库URL
  • setUsername(String name):设置该RowSet要访问的数据库的用户名
  • setPassword(String password):设置该RowSet要访问的数据库的密码
  • setCommand(String sql):设置使用该sql语句的查询结果来装填该RowSet
  • execute():执行查询
代码示例
import java.util.*;
import java.io.*;
import java.sql.*;
import javax.sql.rowset.*;

public class RowSetFactoryTest
{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile) throws Exception
	{
		// 使用Properties类来加载属性文件
		var props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	public void update(String sql) throws Exception
	{
		// 加载驱动
		Class.forName(driver);
		// 使用RowSetProvider创建RowSetFactory
		RowSetFactory factory = RowSetProvider.newFactory();
		try (
			// 使用RowSetFactory创建默认的JdbcRowSet实例
			JdbcRowSet jdbcRs = factory.createJdbcRowSet())
		{
			// 设置必要的连接信息
			jdbcRs.setUrl(url);
			jdbcRs.setUsername(user);
			jdbcRs.setPassword(pass);
			// 设置SQL查询语句
			jdbcRs.setCommand(sql);
			// 执行查询
			jdbcRs.execute();
			jdbcRs.afterLast();
			// 向前滚动结果集
			while (jdbcRs.previous())
			{
				System.out.println(jdbcRs.getString(1)
					+ "\t" + jdbcRs.getString(2)
					+ "\t" + jdbcRs.getString(3));
				if (jdbcRs.getInt("student_id") == 3)
				{
					// 修改指定记录行
					jdbcRs.updateString("student_name", "孙悟空");
					jdbcRs.updateRow();
				}
			}
		}
	}
	public static void main(String[] args) throws Exception
	{
		var jt = new RowSetFactoryTest();
		jt.initParam("mysql.ini");
		jt.update("select * from student_table");
	}
}

这段代码示范了使用JdbcRowSet的可滚动可修改性,使用RowSetFactory来创建JdbcRowSet对象,从而避免了与JdbcRowImpl实现类耦合,通过这种方式创建的JdbcRowSet还没有传入Connection参数,因此程序还需要调用setUrl()、setUsername()、setPassword()等方法来设置数据库连接信息

JdbcRowSet是一个可滚动、可修改的结果集,因此底层数据表中相应的记录也被修改了

CMD
set CLASSPATH=%CLASSPATH%;../mysql-connector-java-8.0.13.jar
java RowSetFactoryTest
cmd

离线RowSet

在使用ResultSet的时,程序查询得到ResultSet之后必须尽快读取或者处理它对应的记录,否则一旦Connection关闭,再通过ResultSet读取记录就会引发异常了

例如应用程序架构被分为两层:数据访问层和视图显示层,当应用程序在数据访问层查询得到ResultSet之后,对ResultSet的处理有两种方式

  • 使用迭代访问ResultSet里的记录,并将这些记录转换成JavaBean,再将多个JavaBean封装成一个List集合,也就是完成ResultSet到JavaBean集合的转换,转换完成可以关闭Connection等资源,然后再将JavaBean集合传到视图显示层展示数据
  • 直接将ResultSet传到视图显示曾,这要求当视图显示层显示数据时,底层Connection必须一直处于打开状态,否则ResultSet无法读取记录

第一种方式比较安全,但变成非常繁琐,第二种方式则许愿哦Connection一直处于打开状态,不仅不安全,而且对程序性能影响很大

这种时候,离线RowSet就非常恰当的可以出现了,离线RowSet会直接将底层数据读入内存,封装成RowSet对象,而RowSet对象则完全可以当成JavaBean来使用,不仅安全且编程简单;CachedRowSet是所有离线RowSet的父接口

代码示例
import java.util.*;
import java.io.*;
import java.sql.*;
import javax.sql.*;
import javax.sql.rowset.*;

public class CachedRowSetTest
{
	private static String driver;
	private static String url;
	private static String user;
	private static String pass;
	public void initParam(String paramFile) throws Exception
	{
		// 使用Properties类来加载属性文件
		var props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}

	public CachedRowSet query(String sql) throws Exception
	{
		// 加载驱动
		Class.forName(driver);
		// 获取数据库连接
		Connection conn = DriverManager.getConnection(url, user, pass);
		Statement stmt = conn.createStatement();
		ResultSet rs = stmt.executeQuery(sql);
		// 使用RowSetProvider创建RowSetFactory
		RowSetFactory factory = RowSetProvider.newFactory();
		// 创建默认的CachedRowSet实例
		CachedRowSet cachedRs = factory.createCachedRowSet();
		// 使用ResultSet装填RowSet
		cachedRs.populate(rs);    
		// 关闭资源
		rs.close();
		stmt.close();
		conn.close();
		return cachedRs;
	}
	public static void main(String[] args) throws Exception
	{
		var ct = new CachedRowSetTest();
		ct.initParam("mysql.ini");
		CachedRowSet rs = ct.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", "孙悟空");
				rs.updateRow();
			}
		}
		// 重新获取数据库连接
		Connection conn = DriverManager.getConnection(url, user, pass);
		conn.setAutoCommit(false);
		// 把对RowSet所做的修改同步到底层数据库
		rs.acceptChanges(conn);
	}
}

代码中cachedRs.populate(rs); 包装了给定的ResultSet,然后关闭了ResultSet、Statement、Connection等数据库资源,如果程序返回的是ResultSet那么这个ResultSet将无法使用,因为Connection已经关闭,但程序返回的是CachedRowSet,因此依然可以读取、修改RowSet中的记录


为了将程序对离线RowSet所做的修改同步到底层数据库,程序在调用RowSet的acceptChanges()方法时候必须传入Connection、


实际项目中强烈建议使用try语句来自动关闭资源,而非代码中所示的显示关闭各种资源

CMD
set CLASSPATH=%CLASSPATH%;../mysql-connector-java-8.0.13.jar
java CachedRowSetTest
cmd

使用RowSet控制分页

由于CachedRowSet会将数据记录直接装载到内存中,因此如果SQL查询返回的记录过大,CachedRowSet将占大量的内存,极端情况下会内存溢出,因此CachedRowSet提供了分页功能,所谓分页功能就是一次只装载ResultSet里的某几条数据,如此避免CachedRowSet占用内存过大,为此CachedRowSet提供了如下方法来控制分页:

  • populate(ResultSet rs, int startRow):使用给定的ResultSet装填RowSet,从ResultSet的第startRow条记录开始装填
  • setPageSize(int pageSize):设置CachedRowSet每次返回多少条记录
  • previousPage():在底层ResultSet可用的情况下,让CachedRowSet读取上一页记录
  • nextPage():在底层ResultSet可用的情况下,让CachedRowSet读取下一页记录
代码示例
import java.util.*;
import java.io.*;
import java.sql.*;
import javax.sql.*;
import javax.sql.rowset.*;

public class CachedRowSetPage
{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile) throws Exception
	{
		// 使用Properties类来加载属性文件
		var props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}

	public CachedRowSet query(String sql, int pageSize,
		int page) throws Exception
	{
		// 加载驱动
		Class.forName(driver);
		try (
			// 获取数据库连接
			Connection conn = DriverManager.getConnection(url, user, pass);
			Statement stmt = conn.createStatement();
			ResultSet rs = stmt.executeQuery(sql))
		{
			// 使用RowSetProvider创建RowSetFactory
			RowSetFactory factory = RowSetProvider.newFactory();
			// 创建默认的CachedRowSet实例
			CachedRowSet cachedRs = factory.createCachedRowSet();
			// 设置每页显示pageSize条记录
			cachedRs.setPageSize(pageSize);
			// 使用ResultSet装填RowSet,设置从第几条记录开始
			cachedRs.populate(rs, (page - 1) * pageSize + 1);
			return cachedRs;
		}
	}
	public static void main(String[] args) throws Exception
	{
		var cp = new CachedRowSetPage();
		cp.initParam("mysql.ini");
		// 查询第2页的记录,每页显示3条记录
		CachedRowSet rs = cp.query("select * from student_table", 3, 2);   
		// 向后滚动结果集
		while (rs.next())
		{
			System.out.println(rs.getString(1)
				+ "\t" + rs.getString(2)
				+ "\t" + rs.getString(3));
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Davieyang.D.Y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值