JAVA SE学习笔记(十二)JDBC编程

1 JDBC基础

JDBC的全称是Java Database Connectivity,即Java数据库连接,是一种可以执行SQL语句的Java API。程序可以通过JDBC API连接到关系数据库,并使用结构化查询语言来完成对数据库的查询、更新。

1.1 JDBC常用接口和类

  • Drivermanager::用于管理JDBC驱动的服务类,程序中使用该类的主要功能是获取Connection对象,该类包含如下用法:public static synchronized Connection getConnection(String url, String user,String pass) throws SQLExpection;,该方法获得url对应数据库的连接
  • Connection:代表数据库连接对象,每个Connection代表一个物理连接对话,要想访问数据库,必须先获得数据库连接,常用方法有:
    • Statement createStatement() throws SQLException:该方法返回一个Statement对象
    • PreparedStatement prepareStatement(String sql) throws SQLException:该方法返回预编译的Statement对象,即将SQL语句提交到数据库进行预编译
    • CallableStatement prepareCall(String sql) throws SQLException:该方法返回CallableStatement对象,该对象用于调用存储过程
    • 注意:上述三个方法都返回用于执行SQL语句的Statement对象,只有获得了Statement或其子类之后才可执行SQL语句
    • Connection提供的控制事务的方法:
      • Savepoint setSavepoint():创建一个保存点
      • Savepoint setSavepoint(String name):以指定名字来创建一个保存点
      • void setTransactionIsolation(int level):设置事务的隔离级别
      • void rollback():回滚事务
      • void rollback(Savepoint savepoint):将事务回滚到指定的保存点
      • void setAutoCommit(bollean autoCommit):关闭自动提交,打开事务
      • void commit()::提交事务
  • Statement:用于执行SQL语句的工具接口,可执行DDL、DCL、DML,查询语句,常用方法如下:
    • ReaultSet executeQuery(String sql) throws SQLException:执行查询语句,并返回查询结果对应的ResultSet对象
    • Int executeUpdate(String sql) throws SQLException:该方法用于执行DML语句,并返回受影响的行数,也可以执行DDL语句,返回0
    • Boolean execute(String sql) throws SQLException:执行任何SQL语句,如果第一个结果是ResultSet的话,返回true,其他的返回false
  • PreparedStatement:预编译的Statement对象,是Statement的子接口,允许数据库预编译SQL语句(这些SQL语句通常带有参数),以后每次只改变SQL命令的参数即可,避免数据库每次都需要编译SQL语句,因此性能更好,相对于Statement而言,使用PreparedStatement执行SQL语句时,无须再传入SQL语句,只要为预编译的SQL语句传入参数值即可,所以它比Statement多了如下方法:void setXxx(int parameterIndex, Xxx value);该方法根据传入参数值的类型不同,需要使用不同的方法。PreparedStatement同样有executeUpdate()、executeQuery()和execute()三个方法,只是方法无须接收SQL字符串,因为PreparedStatement对象已经预编译了SQL命令,只要为这些命令传入参数即可。
  • ResultSet:结果集对象,包含访问查询结果的方法,可以通过列索引或列名获得列数据,它包含了如下常用方法来移动记录指针,当把记录指针移动到指定行之后,ResultSet可通过getXxx(int columnIndex)或getXxx(String columnLabel)方法来获取当前行、指定列的值。
    • void close():释放ResultSet对象
    • boolean absolute(int row):将结果集的指针移动到第row行,如果是负数的话,就是倒数第row行,如果指针指向有效记录,返回true
    • void beforeFirst():将ResultSet的记录指针定位到首行之前
    • boolean first():定位到首行
    • boolean previous():定位到上一行
    • boolean next():定位到下一行
    • boolean last():定位到最后一行
    • void afterLast():定位到最后一行之后

1.2 JDBC编程步骤

  1. 加载数据库驱动,通常使用Class类的forName()静态方法来加载驱动:Class.forName(driverClass),加载驱动时并不是真正使用数据库的驱动,只是使用数据库驱动程序类名的字符串。
    附:
    MySQL:com.mysql.jdbc.Driver
    Oracle:oracle.jdbc.driver.OracleDriver
  2. 通过DriverManager获取数据库连接:DriverManager.getConnection(String url, String user, String pass)
    数据库URL的写法:jdbc:subprotocol:other stuff
    MySQL:jdbc:mysql://hostname:port/databasename
    Oracle:jdbc:oracle:thin:@hostname:port:databasename
  3. 通过Connection对象创建Statement对象
    • createStatement():创建基本的Statement对象
    • prepareStatement(String sql):根据传入的SQL语句创建预编译的Statement对象
    • prepareCall(String sql):根据传入的SQL语句创建CallableStatement对象
  4. 使用Statement执行SQL语句,所有的Statement都有如下三个方法来执行SQL语句
    • execute():可以执行任何SQL语句,但比较麻烦
    • executeUpdate():主要用于执行DML和DDL语句,返回受SQL影响的行数
    • executeQuery():只能执行查询语句,执行后返回代表查询结果的ResultSet语句
  5. 操作结果集ResultSet
  6. 回收数据库资源,包括关闭ResultSet、Statement和Connection对象
public class ConnMySql {
    public static void main(String[] args) throws Exception {
        // 1.加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        try (
                // 2.使用DriverManager获取数据库连接,其中返回的Connection就代表了Java程序和数据库的连接
                Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/select_test", "root", "root");
                // 3.使用Connection来创建一个Statment对象
                Statement stmt = conn.createStatement();
                // 4.执行SQL语句
            /*
            Statement有三种执行sql语句的方法:
            1 execute 可执行任何SQL语句。- 返回一个boolean值,如果执行后第一个结果是ResultSet,则返回true,否则返回false
            2 executeQuery 执行Select语句 - 返回查询到的结果集
            3 executeUpdate 用于执行DML语句。- 返回一个整数,代表被SQL语句影响的记录条数
            */
                ResultSet rs = stmt.executeQuery("select s.* , teacher_name"
                        + " from student_table s , teacher_table t"
                        + " where t.teacher_id = s.java_teacher")) {
            // ResultSet有系列的getXxx(列索引 | 列名),用于获取记录指针
            // 指向行、特定列的值,不断地使用next()将记录指针下移一行,
            // 如果移动之后记录指针依然指向有效行,则next()方法返回true。
            while (rs.next()) {
                System.out.println(rs.getInt(1) + "\t"
                        + rs.getString(2) + "\t"
                        + rs.getString(3) + "\t"
                        + rs.getString(4));
            }
        }
    }
}

1.3 执行SQL语句

1.3.1 Statement

  • Statement的execute()方法几乎可以执行任何SQL语句,但它执行SQL语句时比较麻烦,通常没有必要使用execute()方法来执行SQL语句,使用executeUpdate()或executeQuery()方法更简单。但是如果不清楚SQL语句的类型,则只能使用execute()方法来执行SQL语句了。使用execute()方法执行SQL语句的返回值只是boolean值,表明执行该SQL语句是否返回了ResultSet对象,Statement提供了如下两个方法来获取执行结果:
    • getResultSet():获取该Statement执行查询语句所返回的ResultSet对象
    • getUpdateCount():获取该Statement()执行DML语句所影响的记录行数

1.3.3 PreparedStatement

  • PreparedStatement是Statement接口的子接口,可以预编译SQL语句,预编译之后的SQL语句被存储在PreparedStatement对象中,然后可以使用该对象多次高效地执行该语句,简而言之,使用PreparedStatement比使用Statement的效率更高
  • 创建PreparedStatement对象使用Connection的preparedStatement()方法,该方法需要传入一个SQL字符串,该字符串可以包含占位符参数,在执行SQL语句之前必须为这些参数传入参数值,PreparedStatement提供了一系列的setXxx(int index, Xxx value)方法来传入参数。
  • PreparedStatement也提供了execute()、executeUpdate()、executeQuery()三个方法来执行SQL语句,不过这三个方法无须参数,因为PreparedStatement已存储了预编译的SQL语句
public class PreparedStatementTest {
    private String driver;
    private String url;
    private String user;
    private String pass;

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

    public void insertUseStatement() throws Exception {
        long start = System.currentTimeMillis();
        try (
                // 获取数据库连接
                Connection conn = DriverManager.getConnection(url, user, pass);
                // 使用Connection来创建一个Statment对象
                Statement stmt = conn.createStatement()) {
            // 需要使用100条SQL语句来插入100条记录
            for (int i = 0; i < 100; i++) {
                stmt.executeUpdate("insert into student_table values(" + " null ,'姓名" + i + "' , 1)");
            }
            System.out.println("使用Statement费时:" + (System.currentTimeMillis() - start));
        }
    }

    public void insertUsePrepare() throws Exception {
        long start = System.currentTimeMillis();
        try (
                // 获取数据库连接
                Connection conn = DriverManager.getConnection(url, user, pass);
                // 使用Connection来创建一个PreparedStatement对象
                PreparedStatement pstmt = conn.prepareStatement("insert into student_table values(null,?,1)")
        ) {
            // 100次为PreparedStatement的参数设值,就可以插入100条记录
            for (int i = 0; i < 100; i++) {
                pstmt.setString(1, "姓名" + i);
                pstmt.executeUpdate();
            }
            System.out.println("使用PreparedStatement费时:" + (System.currentTimeMillis() - start));
        }
    }

    public static void main(String[] args) throws Exception {
        PreparedStatementTest pt = new PreparedStatementTest();
        pt.initParam("mysql.ini");
        pt.insertUseStatement();
        pt.insertUsePrepare();
    }
}
  • 使用PreparedStatement的好处:
    • PreparedStatement预编译SQL语句,性能更好
    • PreparedStatement无须“拼接”SQL语句,编程更简单
    • PreparedStatement可以防止SQL注入,安全性更好
  • 注意:使用PreparedStatement执行带占位符参数的SQL语句时,SQL语句中的占位符参数只能代替普通值,不要使用占位符参数代替参数名、列名等数据库对象,更不要用占位符参数来代替SQL语句中的insert、select等关键字。

1.3.3 CallableStatement

  • 调用存储过程使用CallableStatement,可以通过Connection的prepareCall()方法来创建CallableStatement对象,创建该对象时需要传入调用存储过程的SQL语句。调用存储过程的SQL语句总是这种格式:{call 过程名(?,?,?……)},其中问号作为存储过程参数的占位符,存储过程的参数既有传入参数,也有传出参数,传入参数指的是在使用前需要传入参数值,可以通过调用CallableStatement的setXxx()方法为传入参数设置值;所谓传出参数就是指可以通过该参数获取存储过程里的值;CallableStatement需要调用registerOutParameter()方法来注册该参数
  • 经过上面的步骤之后,就可以调用CallableStatement的execute()方法来执行存储过程了,执行结束后通过CallableStatement对象的getXxx(int index)方法来获取指定传出参数的值,
public class CallableStatementTest {
    private String driver;
    private String url;
    private String user;
    private String pass;

    public void initParam(String paramFile) throws Exception {
        // 使用Properties类来加载属性文件
        Properties 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 callProcedure() throws Exception {
        // 加载驱动
        Class.forName(driver);
        try (
                // 获取数据库连接
                Connection conn = DriverManager.getConnection(url, user, pass);
                // 使用Connection来创建一个CallableStatment对象
                CallableStatement cstmt = conn.prepareCall("{call add_pro(?,?,?)}")) {
            cstmt.setInt(1, 4);
            cstmt.setInt(2, 5);
            // 注册CallableStatement的第三个参数(存放的是返回值)是int类型
            cstmt.registerOutParameter(3, Types.INTEGER);
            // 执行存储过程
            cstmt.execute();
            // 获取,并输出存储过程传出参数的值。
            System.out.println("执行结果是: " + cstmt.getInt(3));
        }
    }

    public static void main(String[] args) throws Exception {
        CallableStatementTest ct = new CallableStatementTest();
        ct.initParam("mysql.ini");
        ct.callProcedure();
    }
}

2 ResultSet和RowSet

  JDBC使用ResultSet来封装执行查询得到的查询结果,然后通过一佛那个ResultSet的记录指针来取出结果集的内容,除此之外,JDBC还允许通过ResultSet来更新记录,并提供了ResultSetMetaData来获得ResultSet对象的相关信息。

2.1 结果集

2.1.1 可滚动、可更新的结果集

  • 可滚动的结果集:可以使用absolute()、previous()、afterLast()等方法自由移动记录指针的ResultSet,默认可滚动,无须指定额外的参数
  • 默认方式打开的Result是不可更新的,如果希望创建可更新的ResultSet,则必须在创建Statement或PreparedStatement时传入额外的参数,Connection在创建Statement或PreparedStatement时还可额外传入如下两个参数:
    • resultSetType:控制ResultSet的类型,该参数可以取如下三个值:
      • ResultSet.TYPE_FORWARD_ONLY:该常量控制记录指针只能向前移动
      • ResultSet.TYPE_SCROLL_INSENSITIVE:该常量控制记录指针可以自由移动,但底层数据的改变不会影响ResultSet的内容
      • ResultSet.TYPE_SCROLL_SENSITIVE:该常量控制记录指针可以自由移动,而且底层数据的改变会影响ResultSet的内容
    • resultSetConcurrency:控制ResultSet的并发类型
      • ResultSet.CONCUR_READ_ONLY:该常量指示ResultSet是只读的并发模式
      • ResultSet.CONCUR_UPDATABLE:该常量指示ResultSet是可更新的并发模式
  • 可更新的结果集还需要满足如下两个条件:
    • 所有数据都应该来自一个表
    • 选出的数据集必须包含主键列
  • 通过调用ResultSet的updateXxx(int columnindex, Xxx value)方法来修改记录指针所指记录、特定列的值,最后调用ResultSet的updateRow()方法来提交修改
public class ResultSetTest {
    private String driver;
    private String url;
    private String user;
    private String pass;

    public void initParam(String paramFile) throws Exception {
        // 使用Properties类来加载属性文件
        Properties 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 query(String sql) throws Exception {
        // 加载驱动
        Class.forName(driver);
        try (
                // 获取数据库连接
                Connection conn = DriverManager.getConnection(url, user, pass);
                // 使用Connection来创建一个PreparedStatement对象
                // 传入控制结果集可滚动,可更新的参数。
                PreparedStatement pstmt = conn.prepareStatement(sql
                        , ResultSet.TYPE_SCROLL_INSENSITIVE
                        , ResultSet.CONCUR_UPDATABLE);
                ResultSet rs = pstmt.executeQuery()) {
            rs.last();
            int rowCount = rs.getRow();
            for (int i = rowCount; i > 0; i--) {
                rs.absolute(i);
                System.out.println(rs.getString(1) + "\t" + rs.getString(2) + "\t" + rs.getString(3));
                // 修改记录指针所有记录、第2列的值
                rs.updateString(2, "学生名" + i);
                // 提交修改
                rs.updateRow();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        ResultSetTest rt = new ResultSetTest();
        rt.initParam("mysql.ini");
        rt.query("select * from student_table");
    }
}

2.1.2 处理Blob类型数据

  • Blob(Binary Long Object, 二进制长对象),通常用于存储大文件,例如:图片、音乐、视频,因为Blob常量无法表示,因此,将Blob数据插入数据库需要使用PreparedStatement,该对象有一个方法setBinaryStream(int parameterIndex, InputStream x)为指定参数传入二进制输入流,从而实现将Blob数据保存到数据库的功能
  • 当需要从ResultSet里取出Blob数据时,可以调用ResultSet的getBlob(int columnIndex)方法,该方法将返回一个Blob对象,Blob对象提供了getBinaryStream()方法来获取该Blob数据的输入流,也可以使用Blob对象提供的getByte()方法直接取出该Blob对象封装的二进制数据

2.1.3 使用ResultSetMetaData分析结果集

  • ResultSet里包含一个getMetaData()方法,该方法返回该ResultSet对应的ResultSetMetaData对象,一旦获得了ResultSetMetaData对象,就可以通过其提供的方法来返回ResultSet的描述信息,常用的方法有:
    • int getColumnCount():返回该ResultSet的列数量
    • String getColumnName(int column):返回指定索引的列名
    • int getColumnType(int column):返回指定索引的列类型

2.2 行集

RowSet继承了ResultSet接口,下含JdbcRowSet、CachedRowSet、FilteredRowSet、JoinRowSet和WebRowSet常用子接口,除了JdbcRowset需要保持与数据库的连接外,其余的都是离线RowSet接口,RowSet默认是可滚动、可更新、可序列化的结果集

2.2.1 RowSet

  • JdbcRowSetImpl提供的构造器:
    • JdbcRowSetImpl():创建一个默认的JdbcRowSetImpl对象
    • JdbcRowSetImpl(Connection conn):以给定的Connection对象作为数据库连接来创建JdbcRowSetImpl对象
    • JdbcRowSetImpl(ResultSet rs):创建一个包装ResultSet对象的JdbcRowSetImpl对象
  • RowSet接口定义的常用方法:
    • setUrl(String url):设置该RowSet要访问的数据库URL
    • setUsername(String name):设置该RowSet要访问的数据库的用户名
    • setPassword(String password):设置该RowSet要访问的数据库的密码
    • setCommand(String sql):设置使用该sql语句的查询结果来装填该RowSet
    • execute():执行查询
    • populate(ResultSet rs):让该RowSet直接包装给定的ResultSet对象
  • 为JdbcRowSet装填数据的两种方式:
    • 创建JdbcRowSetImpl时,直接传入ResultSet对象
    • 使用execute()方法来执行SQL查询,用查询返回的数据来装填RowSet
public class JdbcRowSetTest {
    private String driver;
    private String url;
    private String user;
    private String pass;

    public void initParam(String paramFile) throws Exception {
        // 使用Properties类来加载属性文件
        Properties 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);
        try (
                // 获取数据库连接
                Connection conn = DriverManager.getConnection(url, user, pass);
                // 创建JdbcRowSetImpl对象
                JdbcRowSet jdbcRs = new JdbcRowSetImpl(conn))   // ①
        {
            // 设置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 {
        JdbcRowSetTest jt = new JdbcRowSetTest();
        jt.initParam("mysql.ini");
        jt.update("select * from student_table");
    }
}

2.2.2 RowSetFactory

  • 使用RowSetProvider创建RowSetFactory,使用RowSetFactory创建RowSet实例,提供了如下方法:
    • CachedRowSet createCachedRowSet():创建一个默认的CachedRowSet
    • FilteredRowSet createFilteredRowSet():创建一个默认的FilteredRowSet
    • JdbcRowSet createJdbcRowSet():创建一个默认的JdbcRowSet
    • JoinRowSet createJoinRowSet():创建一个默认的JoinRowSet
    • WebRowSet createWebRowSet():创建一个默认的WebRowSet
public class RowSetFactoryTest {
    private String driver;
    private String url;
    private String user;
    private String pass;

    public void initParam(String paramFile) throws Exception {
        // 使用Properties类来加载属性文件
        Properties 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 {
        RowSetFactoryTest jt = new RowSetFactoryTest();
        jt.initParam("mysql.ini");
        jt.update("select * from student_table");
    }
}

2.2.3 CachedRowSet

  • 离线RowSet用来处理在连接关闭后如何访问ResultSet数据的问题,它直接将底层数据读入到内存中,封装成RowSet对象
  • 在连接关闭的条件下,程序依然可以读取、修改RowSet里的记录,为了将修改后的数据同步到底层数据库,需要调用RowSet的acceptChanges(Connection conn)方法
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类来加载属性文件
        Properties 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 {
        CachedRowSetTest 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);
    }
}
  • 离线RowSet可以实现分页功能,每次只装载ResultSet的某几条记录,避免占用内存过大问题,CachedRowSet提供了如下方法来控制分页:
    • popular(ResultSet rs, int startRow):使用给定的ResultSet装填RowSet,从ResultSet的第startRow条记录开始装填
    • setPageSize(int pageSize):设置CachedRowSet每次返回多少条记录
    • previousPage():在底层ResultSet可用的情况下,让CachedRowSet读取上一页记录
    • nextPage():在底层ResultSet可用的情况下,让CachedRowSet读取下一页记录
public class CachedRowSetPage {
    private String driver;
    private String url;
    private String user;
    private String pass;

    public void initParam(String paramFile) throws Exception {
        // 使用Properties类来加载属性文件
        Properties 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 {
        CachedRowSetPage cp = new CachedRowSetPage();
        cp.initParam("mysql.ini");
        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));
        }
    }
}

3 JDBC的事务支持

  • JDBC连接也提供了事务支持,JDBC连接的事务支持由Connection提供,Connection默认打开自动提交,即关闭事务,在这种情况下,每条SQL语句一旦执行,变回立即提交到数据库,永久生效,无法对其进行回滚操作,可以调用Connection的setAutoCommit()方法来关闭自动提交,开启事务
  • 一旦事务开始之后,程序可以像平常一样创建Statement对象,创建了Statement对象之后,可以执行任意多条DML语句,虽然语句执行了,但是并不会生效,需要调用Connection对象的commit()方法来提交事务,这样才会将修改结果同步到底层数据库
  • 如果SQL语句执行失败,则应该使用Connection对象的rollback()方法来回滚事务。
  • Connection也提供了设置中间点的方法:setSavePoint(),配和rollback(Savepoint savepoint)方法来回滚到指定中间点
public class TransactionTest {
    private String driver;
    private String url;
    private String user;
    private String pass;

    public void initParam(String paramFile) throws Exception {
        // 使用Properties类来加载属性文件
        Properties 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 insertInTransaction(String[] sqls) throws Exception {
        // 加载驱动
        Class.forName(driver);
        try (
                Connection conn = DriverManager.getConnection(url, user, pass)) {
            // 关闭自动提交,开启事务
            conn.setAutoCommit(false);
            try (
                    // 使用Connection来创建一个Statment对象
                    Statement stmt = conn.createStatement()) {
                // 循环多次执行SQL语句
                for (String sql : sqls) {
                    stmt.executeUpdate(sql);
                }
            }
            // 提交事务
            conn.commit();
        }
    }

    public static void main(String[] args) throws Exception {
        TransactionTest tt = new TransactionTest();
        tt.initParam("mysql.ini");
        String[] sqls = new String[]{
                "insert into student_table values(null , 'aaa' ,1)",
                "insert into student_table values(null , 'bbb' ,1)",
                "insert into student_table values(null , 'ccc' ,1)",
                // 下面这条SQL语句将会违反外键约束,
                // 因为teacher_table中没有ID为5的记录。
                "insert into student_table values(null , 'ccc' ,5)" //①
        };
        tt.insertInTransaction(sqls);
    }
}
  • 批量更新:多条SQL语句将被作为一批操作同时提交,使用Statement对象的addBatch()方法收集SQL语句,再使用executeBatch()方法同时执行这些语句。

4 连接池管理连接

  • 数据库连接池的解决方案是:当应用程序启动时,系统主动创建足够多的数据库连接,并将这些链接组成一个连接池。每次应用程序请求数据库连接时,无须重新打开连接(打开连接极耗费系统资源),而是从连接池中取出已有的连接使用,使用完后不再关闭数据库连接,而是直接将连接归还给连接池
  • 数据库连接池是Connection对象的工厂,常用参数如下:
    • 数据库的初始连接数
    • 连接池的最大连接数
    • 连接池的最小连接数
    • 连接池每次增加的容量
  • JDBC的数据库连接池使用Java.sql.DataSource来表示

4.1 DBCP数据源

  • DBCP数据源依赖于common-pool,如果使用该连接池实现,需要引入两个jar包:
    • Commons-dbcp.jar:连接池的实现
    • Commons-pool.jar:连接池实现的依赖库
  • DBCP获得数据库连接的方式:
//创建数据源对象
BasicDataSource ds = new BasicDataSource();
//设置连接池所需的驱动
ds.setDriverClassName(“com.mysql.jdbc.Driver”);
//设置连接数据库的URL
ds.setUrl(“jdbc:mysql://localhost:3306/javaee”);
//设置连接数据库的用户名
ds.setUsername(“root”);
//设置连接数据库的密码
ds.setPassword(“pass”);
//设置连接池的初始连接数
ds.setInitialSize(5);
//设置连接池最多可有多少个活动连接数
ds.setMaxActive(20);
//设置连接池种最少有2个空闲的连接
ds.setMinIdle(2);
获取连接:
Connection conn = ds.getConnection();
释放连接:
Conn.close()

4.2 C3P0数据源

  • C3P0连接池可以自动清理不再使用的Connection,还可以自动清理Statement和ResultSet,如果需要使用C3P0连接池,则需要引入:c3p0-0.9.1.2.jar:c3p0连接池的实现
  • 通过C3P0获得数据库连接的方式:
//创建连接池实例
ComboPooledDataSource ds = new ComboPooledDataSource ();
//设置连接池所需的驱动
ds.setDriverClass (“com.mysql.jdbc.Driver”);
//设置连接数据库的URL
ds.setJdbcUrl(“jdbc:mysql://localhost:3306/javaee”);
//设置连接数据库的用户名
ds.setUser (“root”);
//设置连接数据库的密码
ds.setPassword(“pass”);
//设置连接池的最大连接数
ds.setMaxPoolSize(40);
//设置连接池的最小连接数
ds.setMinPoolSize(2);
//设置连接池的初始连接数
ds.setInitialPoolSize(10);
//设置连接池的缓存Statement的最大数
ds.setMaxStatements(180);
获得数据库连接:
Connection = ds.getConnection();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值