ResultSet

简介

已经很久没有去自己处理数据库连接和ResultSet了,基本都使用数据库连接池和ORM工具了,最近看MyBatis源码,发现各种关于数据库接口的操作啊。之前在学校了解了一些,现在从新再回头来学习一下。没有研究数据库驱动的代码,所以一下底层的还是有一些疑问,例如ResultSet的TYPE_SCROLL_SENSITIVE与TYPE_SCROLL_INSENSITIVE的处理方式,虽然知道一些结果,但是处理的原理还是不清除,如:缓存是在在数据库端还是在jvm端?是怎样实现这样缓存的?

这一篇文章文章注意介绍ResultSet相关的接口和配置常量。下图中是ResultSet中的常量。

ResultSet常量

FETCH_FORWARD 与 CONCUR_READ_ONLY

FETCH_FORWARD和CONCUR_READ_ONLY都是ResultSet的常量,FETCH_FORWARD是控制ResultSet游标只能向前移动,不能向后移动。CONCUR_READ_ONLY是控制ResultSet是不能不修改的。一般connection.createStatement()接口是默认的ResultSet的模式,例如我们使用的官方的启动的实现,我们使用的官方听提供的驱动的实现就是。

private static final int DEFAULT_RESULT_SET_TYPE = ResultSet.TYPE_FORWARD_ONLY;

private static final int DEFAULT_RESULT_SET_CONCURRENCY = ResultSet.CONCUR_READ_ONLY;

public java.sql.Statement createStatement() throws SQLException {
        return createStatement(DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY);
    }

在FETCH_FORWARD模式下是可以使用absolute,relative等接口(参考下面的常用游标接口)的。但是游标不能向后移动,只能向前移动。

这种模式常见的就是如下的操作:

@Test
    public void testBaseResultSet(){
        try {
            statement = connection.createStatement();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            rs = statement.executeQuery("select * from user limit 1,3");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
//            rs.absolute(2);
            rs.relative(1);
            while(rs.next()){
                int id = rs.getInt(1);//columnIndex从1开始
                System.out.println(id);
            }
            rs.absolute(2);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

TYPE_SCROLL_INSENSITIVE 与 CONCUR_READ_ONLY

TYPE_SCROLL_INSENSITIVE和CONCUR_READ_ONLY可以组合出一个游标可以前后移动,数据集不能修改的ResultSet。可以参考一下org.apache.ibatis.executor.resultset.DefaultResultSetHandler#skipRows方法对于游标的使用。

常用的使用方式:

  @Test
    public void testScrollResultSet(){
        try {
            statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            rs = statement.executeQuery("select * from user");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            while(rs.next()){
                int id = rs.getInt(1);//从1开始
                System.out.println(id);
            }
            rs.absolute(0);//定位到第一行之前
//            rs.relative(-3);//把游标位置向后移动3个位置
            while(rs.next()){
                int id = rs.getInt(1);//从1开始
                System.out.println(id);
            }
            
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

TYPE_SCROLL_INSENSITIVE 与 CONCUR_UPDATABLE

TYPE_SCROLL_INSENSITIVE和CONCUR_UPDATABLE可以组合出一个既可以前后移动游标,又可以修改数据的ResultSet。 我们可以通过下面的方式创建一个的Statement获取这样的ResultSet connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);

更新的方法是,把ResultSet的游标移动到你要更新的行,然后调用updateXXX(),这个方法XXX的含义和getXXX()是相同的。updateXXX()方法有两个参数,第一个是要更新的列,可以是列名或者序号。第二个是要更新的数据,这个数据类型要和XXX相同。每完成对一行的update要调用updateRow()完成对数据库的写入,而且是在ResultSet的游标没有离开该修改行之前,否则修改将不会被提交。下面是一些修改的实例:

@Test
    public void testUpdateableResultSet(){
        try {
            statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            rs = statement.executeQuery("select * from user");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            rs.absolute(2);//定位到第2行
            rs.updateString("name","第二行");
//            rs.cancelRowUpdates();回滚,updateRow之前
            rs.updateRow();
            
            //插入一行
            rs.moveToInsertRow(); 
            rs.updateString(2, "insert");
            rs.updateString(3,"8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92");
            rs.insertRow();
            rs.moveToCurrentRow();
            
            //删除第4行
            rs.absolute(4);
            rs.deleteRow();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

修改前:

修改前

修改之后:

修改之后

注意:这样的ResultSet对象可以完成对数据库中表的修改,但是我知道ResultSet只是相当于数据库中表的视图,所以并不是所有的ResultSet只要设置了可更新就能够完成更新的,能够完成更新的ResultSet的SQL语句必须要具备如下的属性:

  1. 只引用了单个表。

  2. 不含有join或者group by子句。

  3. 那些列中要包含主关键字。

常用游标接口

public boolean absolute(int row) 该方法的作用是将记录集中的某一行设定为当前行,亦即将数据库游标移动到指定的行,参数row指定了目标行的行号,这是绝对的行号,由记录集的第一行开始计算不是相对的行号

public boolean relative(int rows) 该方法的作用也是将记录集中的某一行设定为当前行,但是它的参数rows表示目标行相对于当前行的行号

public boolean first() 该方法的作用是将当前行定位到数据库记录集的第一行。

public boolean last() 该方法的作用刚好和first()方法相反。

public boolean isFirst()该方法的作用是检查当前行是否记录集的第一行,如果是返回true, 否则返回false

public boolean isLast() 该方法的作用是检查当前行是否记录集的最后一行,如果是返回true ,否则返回false。

public void afterLast() 该方法的作用是将数据库游标移到记录集的最后,位于记录集最后一行的后面,如果该记录集不包含任何的行该方法不产生作用

public void beforeFirst() 该方法的作用是将数据库游标移到记录集的最前面,位于记录集第一行的前面,如果记录集不包含任何的行该方法不产生作用。

public boolean isAfterLast()该方法检查数据库游标是否处于记录集的最后面,如果是返回true ,否则返回false。

public boolean isBeforeFirst()该方法检查数据库游标是否处于记录集的最前面,如果是返回true ,否则返回false。

public boolean next() 该方法的作用是将数据库游标向前移动一位,使得下一行成为当前行,当刚刚打开记录集对象时,数据库游标的位置在记录集的最前面,第一次使用next()方 法将会使数据库游标定位到记录集的第一行,第二次使用next()方法将会使数据库游标定位到记录集的第二行,以此类推。

public boolean previous()该方法的作用是将数据库游标向后移动一位,使得上一行成为当前行

常用更新接口

public boolean rowDeleted()如果当前记录集的某行被删除了,那么记录集中将会留出一个空位;调用rowDeleted()方法,如果探测到空位的存在,那么就返回true如果没有探测到空位的存在,就返回false 值

public boolean rowInserted() 如果当前记录集中插入了一个新行,该方法将返回true ,否则返回false

public boolean rowUpdated() 如果当前记录集的当前行的数据被更新,该方法返回true ,否则返回false

public void insertRow() 该方法将执行插入一个新行到当前记录集的操作

public void updateRow() 该方法将更新当前记录集当前行的数据

public void deleteRow() 该方法将删除当前记录集的当前行

public void updateString(int columnIndex ,String x)该方法更新当前记录集当前行某列的值,该列的数据类型是String(指Java 数据类型是String,与之对应的JDBC 数据类型是VARCHAR 或NVARCHAR 等数据类型) 。该方法的参数columnIndex 指定所要更新的列的列索引,第一列的列索引是1 ,以此类推,第二个参数x代表新的值,这个方法并不执行数据库操作,需要执行insertRow()方法或者updateRow()方法以后,记录集和数据库中的数据才能够真正更新。

public void updateString(String columnName ,String x) 该方法和上面介绍的同名方法差不多,不过该方法的第一个参数是columnName ,代表需要更新的列的列名,而不是columnIndex。

ResultSet插入

往数据库当前记录集插入新行的操作流程如下

1 调用moveToInsertRow()方法

2 调用updateXXX()方法指定插入行各列的值

3 调用insertRow()方法往数据库中插入新的行

ResultSet修改

更新数据库中某个记录的值(某行的值)的流程如下

1 定位到需要修改的行(使用absolute()relative()等方法定位)

2 使用相应updateXXX()方法设定某行某列的新值XXX所代表的Java数据类型,必须可以映射为某列的JDBC数据类型,如果希望rollback该项操作,请在调用updateRow()方法以前,使用cancelRowUpdates()方法,这个方法可以将某行某列的值复原

3 使用updateRow()方法完成UPDATE的操作

ResultSet删除

删除记录集中某行(亦即删除某个记录)的流程如下

1 定位到需要删除的行(使用absolute()relative()等方法定位)

2 使用deleteRow()删除记录集中某行(亦即删除某个记录)的方法

总结

除了上面介绍的对于TYPE_SCROLL_INSENSITIVE还有TYPE_SCROLL_SENSITIVE,这2者之间的区别是数据库的变化会不会映射到结果集上。前者不会后者会,其实TYPE_SCROLL_SENSITIVE也只能更新操作敏感,其它的插入操作和删除操作不会及时地反映到结果集中。如果感兴趣可以研究一些数据库驱动的实现。

附录

测试代码

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;


public class ResultSetTest {
    
    private Connection connection;
    
    private Statement statement;
    
    private ResultSet rs;
    
    private final static String url = "jdbc:mysql://127.0.0.1:3306/design?useUnicode=true&characterEncoding=utf-8";
    
    private final static String user = "tim";
    
    private final static String password = "123456";
    
    @Before
    public void setUp(){
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        try {
            connection = DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    @Test
    public void testConnection(){
        Assert.assertNotNull(connection);
    }
    
    @Test
    public void testBaseResultSet(){
        try {
            statement = connection.createStatement();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            rs = statement.executeQuery("select * from user limit 1,3");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
//            rs.absolute(2);
            rs.relative(1);
            while(rs.next()){
                int id = rs.getInt(1);//从1开始
                System.out.println(id);
            }
            rs.absolute(2);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    @Test
    public void testScrollResultSet(){
        try {
            statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            rs = statement.executeQuery("select * from user");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            while(rs.next()){
                int id = rs.getInt(1);//从1开始
                System.out.println(id);
            }
            rs.absolute(0);//定位到第一行之前
//            rs.relative(-3);//把游标位置向后移动3个位置
            while(rs.next()){
                int id = rs.getInt(1);//从1开始
                System.out.println(id);
            }
            
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    @Test
    public void testUpdateableResultSet(){
        try {
            statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            rs = statement.executeQuery("select * from user");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            rs.absolute(2);//定位到第2行
            rs.updateString("name","第二行");
//            rs.cancelRowUpdates();回滚,updateRow之前
            rs.updateRow();
            
            //插入一行
            rs.moveToInsertRow(); 
            rs.updateString(2, "insert");
            rs.updateString(3,"8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92");
            rs.insertRow();
            rs.moveToCurrentRow();
            
            //删除第4行
            rs.absolute(4);
            rs.deleteRow();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    
    protected void printRs(ResultSet rs) throws SQLException{
        rs.absolute(0);
        rs.relative(-1);
        rs.first();
        rs.last();
        rs.isFirst();
        rs.isLast();
        rs.afterLast();
        rs.beforeFirst();
        rs.isAfterLast();
        rs.isBeforeFirst();
        rs.next();
        rs.previous();
    }
    

}

测试表SQL

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL COMMENT '用户名,字母数字中文',
  `password` char(64) NOT NULL COMMENT '密码,sha256加密',
  `nick_name` varchar(20) DEFAULT '' COMMENT '昵称',
  `portrait` varchar(30) DEFAULT '' COMMENT '头像,使用相对路径',
  `status` enum('valid','invalid') DEFAULT 'valid' COMMENT 'valid有效,invalid无效',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COMMENT='用户表';

转载于:https://my.oschina.net/u/2474629/blog/1489992

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值