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()
:创建一个默认的CachedRowSetFilteredRowSet createFilteredRowSet()
:创建一个默认的FilteredRowSetJdbcRowSet createJdbcRowSet()
:创建一个默认的JdbcRowSetJoinRowSet createJoinRowSet()
:创建一个默认的JoinRowSetWebRowSet createWebRowSet()
:创建一个默认的WebRowSet
通过使用RowSetFactory就可以把应用程序与RowSet实现类分离,避免直接使用JdbcRow SetImpl等非公开的API,更便于升级和扩展
RowSet接口中定义了几个常用方法:
setUrl(String url)
:设置该RowSet要访问的数据库URLsetUsername(String name)
:设置该RowSet要访问的数据库的用户名setPassword(String password)
:设置该RowSet要访问的数据库的密码setCommand(String sql)
:设置使用该sql语句的查询结果来装填该RowSetexecute()
:执行查询
代码示例
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));
}
}
}