Java基础回顾——JDBC

文章目录

  • 介绍
  • 使用
  • JDBC事务
  • JDBC Batch
  • JDBC连接池

介绍

Java为关系数据库定义了一套标准的访问接口:JDBC(Java Database Connectivity)

JDBC是Java程序访问数据库的标准接口

好处:

  • 各数据库厂商使用相同的接口,Java代码不需要针对数据库分别开发
  • Java程序编译期仅依赖java.sql包,不依赖具体数据库的jar包
  • 可随时替换底层数据库,访问数据库的Java代码基本不变

使用

maven导入依赖

<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>5.1.47version>
    <scope>runtimescope>
dependency>
import java.sql.*;

public class JDBCTest {
    public static void main(String[] args) throws SQLException {
        String jdbc_url="jdbc:mysql://localhost:3306/learnjdbc?useSSL=false&characterEncoding=utf8";
        String jdbc_user="root";
        String jdbc_password="123456";
        try (Connection conn = DriverManager.getConnection(jdbc_url, jdbc_user, jdbc_password)) {
            try (Statement stmt = conn.createStatement()) {
                try (ResultSet rs = stmt.executeQuery("SELECT id, grade, name, gender FROM students WHERE gender=1")) {
                    while (rs.next()) {
                        long id = rs.getLong(1); // 注意:索引从1开始
                        long grade = rs.getLong(2);
                        String name = rs.getString(3);
                        int gender = rs.getInt(4);
                        System.out.println(id+","+grade+","+name+","+gender);
                    }
                }
            }
        }
    }
}

使用Statement拼字符串非常容易引发SQL注入的问题,这是因为SQL参数往往是从方法参数传入的。

解决办法:
使用PreparedStatement。使用PreparedStatement可以完全避免SQL注入的问题,因为PreparedStatement始终使用?作为占位符,并且把数据连同SQL本身传给数据库,这样可以保证每次传给数据库的SQL语句是相同的,只是占位符的数据不同,还能高效利用数据库本身对查询的缓存。

        try (Connection conn = DriverManager.getConnection(jdbc_url, jdbc_user, jdbc_password)) {
            try (PreparedStatement ps = conn.prepareStatement("SELECT id, grade, name, gender FROM students WHERE gender=? AND grade=?")) {
                ps.setObject(1, "M"); // 注意:索引从1开始
                ps.setObject(2, 3);
                try (ResultSet rs = ps.executeQuery()) {
                    while (rs.next()) {
                        long id = rs.getLong("id");
                        long grade = rs.getLong("grade");
                        String name = rs.getString("name");
                        String gender = rs.getString("gender");
                        System.out.println(id+","+grade+","+name+","+gender);
                    }
                }
            }
        }

使用PreparedStatement,必须首先调用setObject()设置每个占位符?的值,最后获取的仍然是ResultSet对象

插入和删除操作

        try (Connection conn = DriverManager.getConnection(jdbc_url, jdbc_user, jdbc_password)) {
//            try (PreparedStatement ps = conn.prepareStatement(
//                    "INSERT INTO students (id, grade, name, gender,score) VALUES (?,?,?,?,?)")) {
//                ps.setObject(1, 999); // 注意:索引从1开始
//                ps.setObject(2, 1); // grade
//                ps.setObject(3, "Bob"); // name
//                ps.setObject(4, 0); // gender
//                ps.setObject(5, 80); // gender
//                int n = ps.executeUpdate(); // 1
//            }
            try (PreparedStatement ps = conn.prepareStatement("DELETE FROM students WHERE id=?")) {
                ps.setObject(1, 999); // 注意:索引从1开始
                int n = ps.executeUpdate(); // 删除的行数
            }
        }

JDBC事务

数据库系统保证在一个事务中的所有SQL要么全部执行成功,要么全部不执行,即数据库事务具有ACID特性:

  • Atomicity:原子性
  • Consistency:一致性
  • Isolation:隔离性
  • Durability:持久性

数据库系统从效率考虑,对事务定义了不同的隔离级别。SQL标准定义了4种隔离级别,分别对应可能出现的数据不一致的情况:
Java基础回顾——JDBC_第1张图片

要在JDBC中执行事务,本质上就是如何把多条SQL包裹在一个数据库事务中执行。,默认情况下,获取到Connection连接后,总是处于“自动提交”模式,也就是每执行一条SQL都是作为事务自动执行的。

Connection conn = openConnection();
try {
    // 关闭自动提交:
    conn.setAutoCommit(false);
    // 执行多条SQL语句:
    insert(); update(); delete();
    // 提交事务:
    conn.commit();
} catch (SQLException e) {
    // 回滚事务:
    conn.rollback();
} finally {
    conn.setAutoCommit(true);
    conn.close();
}

如果要设定事务的隔离级别,可以使用如下代码:

// 设定隔离级别为READ COMMITTED:
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

如果没有调用上述方法,那么会使用数据库的默认隔离级别。MySQL的默认隔离级别是REPEATABLE_READ。

JDBC Batch

批量操作

通过一个循环来执行每个PreparedStatement虽然可行,但是性能很低。SQL数据库对SQL语句相同,但只有参数不同的若干语句可以作为batch执行,即批量执行,这种操作有特别优化,速度远远快于循环执行每个SQL。

try (PreparedStatement ps = conn.prepareStatement("INSERT INTO students (name, gender, grade, score) VALUES (?, ?, ?, ?)")) {
    // 对同一个PreparedStatement反复设置参数并调用addBatch():
    for (Student s : students) {
        ps.setString(1, s.name);
        ps.setBoolean(2, s.gender);
        ps.setInt(3, s.grade);
        ps.setInt(4, s.score);
        ps.addBatch(); // 添加到batch
    }
    // 执行batch:
    int[] ns = ps.executeBatch();
    for (int n : ns) {
        System.out.println(n + " inserted."); // batch中每个SQL执行的结果数量
    }
}

执行batch和执行一个SQL不同点在于,需要对同一个PreparedStatement反复设置参数并调用addBatch(),这样就相当于给一个SQL加上了多组参数,相当于变成了“多行”SQL。

第二个不同点是调用的不是executeUpdate(),而是executeBatch(),因为设置了多组参数,相应地,返回结果也是多个int值,因此返回类型是int[],循环int[]数组即可获取每组参数执行后影响的结果数量。

JDBC连接池

执行JDBC的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,创建和销毁JDBC连接的开销就太大了。为了避免频繁地创建和销毁JDBC连接,可以通过连接池(Connection Pool)复用已经创建好的连接

JDBC连接池有一个标准的接口javax.sql.DataSource,注意这个类位于Java标准库中,但仅仅是接口。要使用JDBC连接池,必须选择一个JDBC连接池的实现。常用的JDBC连接池有:

  • HikariCP
  • C3P0
  • BoneCP
  • Druid

依赖:com.zaxxer:HikariCP:2.7.1

        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/learnjdbc");
        config.setUsername("root");
        config.setPassword("123456");
        config.addDataSourceProperty("connectionTimeout", "1000"); // 连接超时:1秒
        config.addDataSourceProperty("idleTimeout", "60000"); // 空闲超时:60秒
        config.addDataSourceProperty("maximumPoolSize", "10"); // 最大连接数:10
        DataSource ds = new HikariDataSource(config);

//有了连接池以后,获取Connection时,把DriverManage.getConnection()改为ds.getConnection()
        try (Connection conn = ds.getConnection()) { // 在此获取连接
            try (PreparedStatement ps = conn.prepareStatement("SELECT id, grade, name, gender FROM students WHERE gender=? AND grade=?")) {
                ps.setObject(1, "M"); // 注意:索引从1开始
                ps.setObject(2, 3);
                try (ResultSet rs = ps.executeQuery()) {
                    while (rs.next()) {
                        long id = rs.getLong("id");
                        long grade = rs.getLong("grade");
                        String name = rs.getString("name");
                        String gender = rs.getString("gender");
                        System.out.println(id+","+grade+","+name+","+gender);
                    }
                }
            }
        } // 在此“关闭”连接

通过连接池获取连接时,并不需要指定JDBC的相关URL、用户名、口令等信息,因为这些信息已经存储在连接池内部了(创建HikariDataSource时传入的HikariConfig持有这些信息)。一开始,连接池内部并没有连接,所以,第一次调用ds.getConnection(),会迫使连接池内部先创建一个Connection,再返回给客户端使用。当调用conn.close()方法时(在try(resource){…}结束处),不是真正“关闭”连接,而是释放到连接池中,以便下次获取连接时能直接返回。

你可能感兴趣的:(java,开发语言)