【Java学习笔记】55:JDBC-MySQL基本使用,游标控制,CONCUR_UPDATABLE,更新/添加/删除

配置了这么久终于可以学习JDBC了,在这之前,给刚刚的表多插入一些表项:

mysql> USE newDB;
Database changed
mysql> INSERT INTO NewUsr
    -> (id,name,age)
    -> VALUES
    -> (1,'lzh',20),
    -> (3,'sb',17),
    -> (2,'奥特曼',9),
    -> (7,'起诉面包',3);
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM NewUsr;
+----+--------------+------+
| id | name         | age  |
+----+--------------+------+
|  6 | 红色青蛙     | NULL |
|  1 | lzh          |   20 |
|  3 | sb           |   17 |
|  2 | 奥特曼       |    9 |
|  7 | 起诉面包     |    3 |
+----+--------------+------+
5 rows in set (0.00 sec)

表项在表中逻辑上是没有顺序的,既不能认为按照id的大小,也不能认为是时间先后的顺序(虽然不使用ORDER BY时查询出来就是这个顺序)。

基本流程

画张图记录一下,竖线表示影响的范围:
【Java学习笔记】55:JDBC-MySQL基本使用,游标控制,CONCUR_UPDATABLE,更新/添加/删除_第1张图片

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

public class Main {

    public static void main(String[] args) {
        try {
            // 要求JVM查找并加载指定的类
            Class.forName("com.mysql.jdbc.Driver");
            // 声明一个sql连接对象
            java.sql.Connection con = null;
            // 连接信息字符串,MySQL数据库服务器的默认端口号是3306
            // jdbc:mysql://ip地址:端口号/要连接的数据库名?其它选项
            // useSSL参数指明数据通道是否要加密处理
            // characterEncoding参数指明连接编码,要和数据库编码,数据库表的编码,数据库系统的编码一致
            String uri = "jdbc:mysql://192.168.0.106:3306/newDB?useSSL=true&characterEncoding=utf8";
            String user = "root";// 用户名
            String password = "3838438"; // 密码
            // 和指定的数据库建立连接(连接信息字符串,用户名,密码)
            con = DriverManager.getConnection(uri, user, password);
            // 用Statement声明一个SQL语句对象,用连接对象的createStatement()方法创建之
            // SQL语句对象用来向数据库发送SQL语句,并返回结果
            java.sql.Statement sql = con.createStatement();
            // SQL语句对象调用executeQuery()方法对数据库进行查询,返回ResultSet对象
            ResultSet rs = sql.executeQuery("SELECT * FROM NewUsr;");// 在这里指定查询语句
            // 顺序查询方式
            // ResultSetd对象一次只能看到一个数据行,用next()方法移动到下一个数据行
            while (rs.next()) {
                // 在每个数据行上多次使用getXxx()方法获取这行上每个字段中的数据
                // 传入的参数可以是int列号,也可以是String列名
                // 而getXxx()中的Xxx就是获得这一字段值时采取的类型,也即返回的数据类型
                int id = rs.getInt(1);
                String name = rs.getString(2);// 实际上不论哪种类型的字段都可以使用getString(列号/列名)
                int age = rs.getInt(3);
                System.out.println(id + " " + name + " " + age);
            }
            // 最后关闭连接
            // 注意当Connection对象使用close()方法关闭连接后,ResultSet对象不再可用
            // 所以如果要使用ResultSet对象就要一直保持连接
            // 如果要关闭连接后还能使用数据,就要把ResultSet对象里的数据存到别的地方去
            con.close();
        } catch (SQLException e) {
            // SQLException异常可能发生在DriverManager.getConnection()方法获得Connection对象时
            // 还可能发生在Connection对象调用createStatement()方法创建SQL语句对象时
            // 还可能发生在SQL语句对象调用executeQuery()方法对数据库进行查询时
            // 还可能发生在Connection对象调用close()方法关闭连接时
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // ClassNotFoundException异常可能发生在lass.forName()时,即找不到要加载的类
            e.printStackTrace();
        }
    }
}

输出

6 红色青蛙 0
1 lzh 20
3 sb 17
2 奥特曼 9
7 起诉面包 3

游标控制

用next()方法让结果集的游标向下一行,如果成功时会返回true,在初始状态,结果集的游标总是在第一行的前面。

如果Connection对象用无参数的createStatement()方法获取SQL语句对象,那么只能进行向下的顺序查询。为它指定特定的参数以控制查询游标可以上下移动:

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

public class Main {

    public static void main(String[] args) {
        try {
            // 要求JVM查找并加载指定的类
            Class.forName("com.mysql.jdbc.Driver");
            // 声明一个sql连接对象
            java.sql.Connection con = null;
            // 连接信息字符串,MySQL数据库服务器的默认端口号是3306
            // jdbc:mysql://ip地址:端口号/要连接的数据库名?其它选项
            // useSSL参数指明数据通道是否要加密处理
            // characterEncoding参数指明连接编码,要和数据库编码,数据库表的编码,数据库系统的编码一致
            String uri = "jdbc:mysql://192.168.0.106:3306/newDB?useSSL=true&characterEncoding=utf8";
            String user = "root";// 用户名
            String password = "3838438"; // 密码
            // 和指定的数据库建立连接(连接信息字符串,用户名,密码)

            // 用Statement声明一个SQL语句对象,用连接对象的createStatement()方法创建之
            // SQL语句对象用来向数据库发送SQL语句,并返回结果
            // ResultSet.TYPE_SCROLL_SENSITIVE使结果集可以滚动
            // ResultSet.CONCUR_READ_ONLY不能用结果集更新数据库中的表
            java.sql.Statement sql = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
            // SQL语句对象调用executeQuery()方法对数据库进行查询,返回ResultSet对象
            ResultSet rs = sql.executeQuery("SELECT * FROM NewUsr;");// 在这里指定查询语句
            /* 滚动查询方式(即可以控制游标) */
            rs.next();// 向下移动到第一行
            System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getInt(3));
            int lnth = rs.getRow();// 获取当前行号
            System.out.println("当前行号:" + lnth);
            rs.last();// 移动到最后一行
            System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getInt(3));
            rs.previous();// 向上移动一行
            System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getInt(3));
            System.out.println("是否在最后一行之后" + rs.isAfterLast());// 是否在最后一行之后
            System.out.println("是否在第一行之前" + rs.isBeforeFirst());// 是否在第一行之前
            rs.absolute(3);// 绝对移动:到第3行
            System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getInt(3));
            rs.absolute(-2);// 绝对移动:到倒数第2行
            System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getInt(3));

            // 最后关闭连接
            // 注意当Connection对象使用close()方法关闭连接后,ResultSet对象不再可用
            // 所以如果要使用ResultSet对象就要一直保持连接
            // 如果要关闭连接后还能使用数据,就要把ResultSet对象里的数据存到别的地方去
            con.close();
        } catch (SQLException e) {
            // SQLException异常可能发生在DriverManager.getConnection()方法获得Connection对象时
            // 还可能发生在Connection对象调用createStatement()方法创建SQL语句对象时
            // 还可能发生在SQL语句对象调用executeQuery()方法对数据库进行查询时
            // 还可能发生在Connection对象调用close()方法关闭连接时
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // ClassNotFoundException异常可能发生在lass.forName()时,即找不到要加载的类
            e.printStackTrace();
        }
    }
}

输出

6 红色青蛙 0
当前行号:1
7 起诉面包 3
2 奥特曼 9
是否在最后一行之后false
是否在第一行之前false
3 sb 17
2 奥特曼 9

CONCUR_UPDATABLE用结果集操作数据库表

Connection对象调用createStatement()方法创建SQL语句对象时,传入的第二个参数,取值为ResultSet.CONCUR_READ_ONLY时不能用结果集更新数据库中的表项。教材书这样写感觉很模糊,而且没有具体说明。学习它的对立参数ResultSet.CONCUR_UPDATABLE就能明白什么是”用结果集更新数据库中的表”了。

在该参数下使用ResultSet.TYPE_SCROLL_SENSITIVE参数

Connection对象调用createStatement()方法创建SQL语句对象时,传入的第一个参数可以有多种取值,当取ResultSet.TYPE_SCROLL_SENSITIVE时,不仅表示结果集可滚动,对数据库表的更新会更新ResultSet结果集对象。

当使用CONCUR_UPDATABLE用结果集更新数据库表时,需要先移动到要操作的行,调用ResultSet对象的updateXxx(列号,新值)方法(Xxx是类型),再调用updateRow()方法完成更新:

            // 用Statement声明一个SQL语句对象,用连接对象的createStatement()方法创建之
            // SQL语句对象用来向数据库发送SQL语句,并返回结果
            // ResultSet.TYPE_SCROLL_SENSITIVE使结果集可以滚动,当数据变化时,结果集同步改变
            // ResultSet.CONCUR_UPDATABLE能用结果集更新数据库中的表
            java.sql.Statement sql = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
            // SQL语句对象调用executeQuery()方法对数据库进行查询,返回ResultSet对象
            ResultSet rs = sql.executeQuery("SELECT * FROM NewUsr;");// 在这里指定查询语句
            /* 滚动查询方式(即可以控制游标) */
            rs.absolute(3);// 绝对移动:到第3行
            System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getInt(3));
            // 用ResultSet结果集去更新数据库表项
            rs.updateInt(1, 11);
            rs.updateString(2, "皮卡丘");
            rs.updateInt(3, 6);
            rs.updateRow();// 切记要做了这一步才能完成更新!
            // 在ResultSet.TYPE_SCROLL_SENSITIVE参数下,对数据库表项的操作会同时更新结果集
            System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getInt(3));

输出

3 sb 17
11 皮卡丘 6

这时候数据库表项已经更新了。

如果要取消更新,注意这个取消不是把上面已经更新数据库表项这件事取消,而是把前面的updateXxx(列号,新值)方法们做的更新取消,可以在updateRow()前调用ResultSet对象的cancelRowUpdates()方法取消:

            // 用Statement声明一个SQL语句对象,用连接对象的createStatement()方法创建之
            // SQL语句对象用来向数据库发送SQL语句,并返回结果
            // ResultSet.TYPE_SCROLL_SENSITIVE使结果集可以滚动,当数据变化时,结果集同步改变
            // ResultSet.CONCUR_UPDATABLE能用结果集更新数据库中的表
            java.sql.Statement sql = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
            // SQL语句对象调用executeQuery()方法对数据库进行查询,返回ResultSet对象
            ResultSet rs = sql.executeQuery("SELECT * FROM NewUsr;");// 在这里指定查询语句
            /* 滚动查询方式(即可以控制游标) */
            System.out.println("[更新前]");
            rs.absolute(3);// 绝对移动:到第3行
            System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getInt(3));
            // 用ResultSet结果集去更新数据库表项
            rs.updateInt(1, 18);
            rs.updateString(2, "暗夜猎手");
            rs.updateInt(3, 9);
            rs.updateRow();// 尝试更新
            rs.absolute(4);// 绝对移动:到第4行
            System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getInt(3));
            // 用ResultSet结果集去更新数据库表项
            rs.updateInt(1, 12);
            rs.updateString(2, "777");
            rs.updateInt(3, 19);
            rs.cancelRowUpdates();// 取消更新
            rs.updateRow();// 尝试更新
            rs.absolute(1);// 绝对移动:到第1行
            System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getInt(3));
            // 用ResultSet结果集去更新数据库表项
            rs.updateInt(1, 15);
            rs.updateString(2, "SBSB");
            rs.updateInt(3, 21);
            rs.updateRow();// 尝试更新
            // 在ResultSet.TYPE_SCROLL_SENSITIVE参数下,对数据库表项的操作会同时更新结果集
            System.out.println("[更新后]");
            rs.absolute(3);// 绝对移动:到第3行
            System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getInt(3));
            rs.absolute(4);// 绝对移动:到第4行
            System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getInt(3));
            rs.absolute(1);// 绝对移动:到第1行
            System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getInt(3));

输出

[更新前]
13 雷丘 7
2 奥特曼 9
6 红色青蛙 0
[更新后]
18 暗夜猎手 9
2 奥特曼 9
15 SBSB 21

从输出可以看出,updateRow()方法和cancelRowUpdates()方法也仅仅是针对当前数据行的。这样的好处在于可以把各个数据行间的移动、更新写在前面,而把这两个方法写在最后,只要用绝对移动到各个数据行,再去抉择是否要对这行应用更新即可了。

在该参数下使用ResultSet.TYPE_SCROLL_INSENSITIVE参数

Connection对象调用createStatement()方法创建SQL语句对象时,传入的第一个参数可以有多种取值,当取ResultSet.TYPE_SCROLL_INSENSITIVE时,表示结果集可滚动,但对数据库表的更新不会更新ResultSet结果集对象。

但是,CONCUR_UPDATABLE选择的方式就是用结果集去更新数据库表项,所以在这种方式下不论用的是ResultSet.TYPE_SCROLL_INSENSITIVE还是ResultSet.TYPE_SCROLL_SENSITIVE,结果集都会改变:

            // 用Statement声明一个SQL语句对象,用连接对象的createStatement()方法创建之
            // SQL语句对象用来向数据库发送SQL语句,并返回结果
            // ResultSet.TYPE_SCROLL_INSENSITIVE使结果集可以滚动,当数据变化时,结果集不改变!
            // 但是,注意在CONCUR_UPDATABLE参数下的情况!
            // ResultSet.CONCUR_UPDATABLE能用结果集更新数据库中的表
            java.sql.Statement sql = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
            // SQL语句对象调用executeQuery()方法对数据库进行查询,返回ResultSet对象
            ResultSet rs = sql.executeQuery("SELECT * FROM NewUsr;");// 在这里指定查询语句
            /* 滚动查询方式(即可以控制游标) */
            System.out.println("[更新前,结果集对象中的这行]");
            rs.absolute(4);// 绝对移动:到第4行
            System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getInt(3));
            // 用ResultSet结果集去更新数据库表项
            rs.updateInt(1, 12);
            rs.updateString(2, "777");
            rs.updateInt(3, 19);
            rs.updateRow();// 更新这行
            // 在ResultSet.TYPE_SCROLL_INSENSITIVE参数下,对数据库表项的操作不会同时更新结果集
            // 但是,注意在CONCUR_UPDATABLE参数下的情况!
            System.out.println("[数据项更新后,当前结果集对象中的这行]");
            rs.absolute(4);// 绝对移动:到第4行(其实已经在第4行了)
            System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getInt(3));

输出

[更新前,结果集对象中的这行]
2 奥特曼 9
[数据项更新后,当前结果集对象中的这行]
12 777 19

那么如何去看这两个参数的区别之处呢,总不能把SELECT语句前面加上UPDATE语句来看吧,这样得到的已经不是同一个结果集了,我猜想ResultSet.TYPE_SCROLL_INSENSITIVE参数提供的是多线程情形下的对数据库访问结果的保护,以后的学习中再来验证吧。

更新/添加/删除操作

除了可以用结果集去操作数据库表项,还可以直接用SQL语句去做这些操作。但是注意更新、添加、删除操作使用的是SQL语句对象的.executeUpdate()方法且返回的是一个int型的受影响的行数,而不是.executeQuery()方法返回结果集:

            // 用Statement声明一个SQL语句对象,用连接对象的createStatement()方法创建之
            // SQL语句对象用来向数据库发送SQL语句,并返回结果
            // ResultSet.TYPE_SCROLL_INSENSITIVE使结果集可以滚动,当数据变化时,结果集不改变!
            // ResultSet.CONCUR_UPDATABLE能用结果集更新数据库中的表
            java.sql.Statement sql = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
            // SQL语句对象调用executeQuery()方法对数据库进行查询,返回ResultSet对象
            ResultSet rs = sql.executeQuery("SELECT * FROM NewUsr;");// 在这里指定查询语句
            System.out.println("[更新,添加,删除之前]");
            // 顺序查看这张表
            while (rs.next()) {
                int id = rs.getInt(1);
                String name = rs.getString(2);
                int age = rs.getInt(3);
                System.out.println(id + " " + name + " " + age);
            }

            System.out.println("[更新,添加,删除操作]");
            int num = sql.executeUpdate("UPDATE NewUsr SET name='刘知昊' WHERE id=12;");// 更新数据库表项
            System.out.println(num + "行受影响");
            num = sql.executeUpdate("DELETE FROM NewUsr WHERE name='SBSB';");// 删除数据库表项
            System.out.println(num + "行受影响");
            num = sql.executeUpdate("INSERT INTO NewUsr VALUES(22,'啊啊啊',23);");// 添加数据库表项
            System.out.println(num + "行受影响");

            // SQL语句对象调用executeQuery()方法对数据库进行查询,返回ResultSet对象
            rs = sql.executeQuery("SELECT * FROM NewUsr;");// 在这里指定查询语句
            System.out.println("[更新,添加,删除之后]");
            // 顺序查看这张表
            while (rs.next()) {
                int id = rs.getInt(1);
                String name = rs.getString(2);
                int age = rs.getInt(3);
                System.out.println(id + " " + name + " " + age);
            }

输出

[更新,添加,删除之前]
15 SBSB 21
1 lzh 20
18 暗夜猎手 9
12 777 19
7 起诉面包 3
[更新,添加,删除操作]
1行受影响
1行受影响
1行受影响
[更新,添加,删除之后]
22 啊啊啊 23
1 lzh 20
18 暗夜猎手 9
12 刘知昊 19
7 起诉面包 3

你可能感兴趣的:(Java)