JDBC 简介

2022-05-05 19:40:24 浏览数 (1)

JDBC英文全称是Java Database Connectivity,也就是Java数据库连接。这是一个Java连接SQL数据库的标准,包含了常用的API,让我们能方便的连接盒管理SQL数据库。每个数据库厂商都会提供相应的JDBC驱动程序,实现相应的接口。这样我们就能以统一的方式,操作不同的数据库了。

建立连接

要使用JDBC,首先要做的事情就是建立一个数据库连接,这是一个java.sql.Connection对象,提供了很多功能。详细的使用方法可以参考JavaDoc。要创建一个Connection对象,我们需要使用以下语句:

代码语言:javascript复制
Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);

这个方法会抛出SQL异常,所以在使用的时候需要包裹在try-catch块中或者向上一级抛出异常。

DriverManager的getConnection方法会接受三个参数,URL代表JDBC连接字符串,还有两个参数是用户名和密码。每个数据库都有自己的连接字符串,这里列举几个常用的。DriverManager能够自动查找和加载驱动程序类,但是有时候(比如使用Hibernate)可能需要手动指定驱动程序类,这时候就需要知道驱动程序类的名称。

数据库

连接字符串

驱动程序类

MySQL

jdbc:mysql://HOST/DATABASE

com.mysql.jdbc.Driver

Postgresql

jdbc:postgresql://HOST/DATABASE

org.postgresql.Driver

SQL Server

jdbc:microsoft:sqlserver://HOST:1433;DatabaseName=DATABASE

com.microsoft.jdbc.sqlserver.SQLServerDriver

下面我们以MySQL数据库为例。默认情况下MySQL的连接字符串应该是这样:jdbc:mysql://localhost:3306/jdbclearn,jdbclearn为我们所使用数据库的名称。

使用Statement

有了数据库连接之后,我们就可以执行SQL语句了。执行SQL语句需要创建一个Statement对象。可以用以下语句创建Statement:

代码语言:javascript复制
Statement statement = connection.createStatement()

有了Statement对象,就可以调用它的方法来具体执行SQL语句了。根据功能可以将SQL语句分为两种,查询和更新。查询语句是对数据库的查询,不涉及数据的更改。更新语句包括插入、更新、删除等操作,会修改数据库的状态。

执行更新

执行更新需要调用Statement的executeUpdate方法,接受一个SQL更新字符串。这个方法实际上还会返回一个整数,表示受到影响的行数,不过一般情况下我们用不到。

下面的语句简单的执行了一条SQL插入语句。

代码语言:javascript复制
statement.executeUpdate("INSERT INTO user(username,password) VALUES('yitian','123456')");

执行查询

另一类语句就是查询语句了。执行查询语句需要调用Statement的executeQuery方法,这个方法接受一个查询字符串,会返回一个ResultSet对象,也就是查询的结果集。这个对象会包含所有的查询结果和一个游标。下面的例子执行一个SQL查询,将结果放到相应的实体类中,然后得到一个List。

代码语言:javascript复制
List<User> users = new ArrayList<>();
try (Statement statement = connection.createStatement()) {
    try (ResultSet rs = statement.executeQuery("SELECT *FROM user")) {

        while (rs.next()) {
            User user = new User();
            user.setId(rs.getInt(1));
            user.setUsername(rs.getString(2));
            user.setPassword(rs.getString(3));
            user.setNickname(rs.getString(4));
            user.setBirthday(rs.getDate(5));
            users.add(user);
        }
    }
}

next方法会将游标移动到下一个数据,如果没有下一个数据,就会返回false。在刚获取到结果集的时候,游标默认在第一个数据之前,所以我们可以将next方法放到循环中,来获取所有数据。结果集对象提供了一组get方法,用来获取结果。对于大多数数据库类型都有对应的Java类型,我们调用对应的方法就可以获取到数据了。这些get方法可以接受列名或者是列编号,如果使用编号的话性能会更好一点,因为不需要查询列名。需要注意列编号以1开始,不要和以零开始的数组相混淆

由于数据库连接、语句和结果集对象都实现了AutoCloseable接口,所以我们可以将其放入到自动资源清理语句中。

预编译的语句

普通的Statement虽然灵活,可以执行任意的SQL语句,但是它有几个缺点,第一,每次执行查询都需要将语句传入数据库中,不够高效;第二,如果要查询的语句很长,包含多个参数,需要拼接大量字符串,费时费力;第三,和第二点相关,如果用户输入的数据是virus';drop table user;这样的用户名,可能会扰乱SQL语句,甚至清除数据库,这就是所谓的SQL注入。要避免以上问题很简单,就是使用预编译的语句,也就是PreparedStatement对象。

使用方法很简单,和普通的语句类似,只不过换成了PreparedStatement,然后在创建预编译语句的时候需要在创建时指定SQL字符串,参数使用问号?代替。然后用一组set方法将参数传入,然后才能执行语句。

代码语言:javascript复制
try (PreparedStatement statement
             = connection.prepareStatement("INSERT INTO user(username,password,nickname,birthday) VALUES(?,?,?,?)")) {
    statement.setString(1, "test2");
    statement.setString(2, "12345678");
    statement.setString(3, "张三");
    statement.setDate(4, new Date(new java.util.Date().getTime()));
    int rows = statement.executeUpdate();
    assertThat(rows, is(1));
}

上面普通的语句有什么缺点,预编译的语句就有什么优点。所以如果没有什么特殊要求,最好在项目中全部使用预编译的语句。

结果集

执行查询之后JDBC会返回一个结果集对象,结果集对象包含了我们获取查询结果的很多方法。最常用的方法就是前面的做法,在循环中调用结果集的next方法,然后获取每一行内容。

结果集的常用方法如下:

方法名

作用

absolute(int i)

将游标移动到结果集的第i行

afterLast()

将游标移动到结果集的最后一行的后面

beforeFirst()

将游标移动到结果集第一行的前面

first()

将游标移动到第一行

last()

将游标移动到最后一行

getXXX(int columnIndex)

一组get方法,按列序号获取当前行的数据

getXXX(String columnLabel)

一组get方法,按列名称获取当前行的数据

deleteRow()

删除当前行的数据,也会从地从数据库中删除

updateXXX

一组update方法,用来更新结果集的,和get方法一样,存在按照列名和列序号两种方式更新数据

updateRow()

将更新之后的行写入结果集和底层数据库

默认情况下结果集只支持一次遍历,也就是说游标在遍历到下一条数据之后,就无法后退了。我们也可以打开结果集的遍历和编辑功能。要打开结果集的遍历和编辑功能,需要在创建语句对象的时候同时指定结果集的标志。然后就可以使用上面列举出的各种方法对结果集进行遍历和编辑、删除操作了。

代码语言:javascript复制
try (Statement statement
             = connection.createStatement(
        ResultSet.TYPE_SCROLL_INSENSITIVE,
        ResultSet.CONCUR_UPDATABLE)) {
    statement.executeUpdate("INSERT INTO user(username,password) VALUES('test3','112233')");
    try (ResultSet rs = statement.executeQuery("SELECT *FROM user WHERE username='test3'")) {
        rs.absolute(1);
        rs.updateString("password", "987654321");
        rs.updateRow();
    }
    try (ResultSet rs = statement.executeQuery("SELECT *FROM user WHERE username='test3'")) {
        rs.absolute(1);
        assertThat(rs.getString("password"), equalTo("987654321"));
    }

}

元数据

利用元数据可以获取关于JDBC的更多信息我们可以在数据库连接、结果集等对象上调用getMetaData方法,获取相应的元数据对象。

下面是一个数据库元数据的例子,我们可以使用元数据获取数据库连接的详细属性。

代码语言:javascript复制
DatabaseMetaData metaData = connection.getMetaData();
logger.info("DriverName:{}", metaData.getDriverName());
logger.info("DriverVersion:{}", metaData.getDriverVersion());

利用结果集元数据,我们可以获取结果集的详细信息。下面利用元数据获取了结果集各列的列名。

代码语言:javascript复制
try (PreparedStatement statement
             = connection.prepareStatement("SELECT *FROM user")) {
    try (ResultSet rs = statement.executeQuery()) {
        ResultSetMetaData metaData = rs.getMetaData();
        for (int i = 0; i < metaData.getColumnCount();   i) {
            System.out.print(String.format("%st", metaData.getColumnName(i   1)));
        }
        System.out.println();
        while (rs.next()) {
            for (int i = 0; i < metaData.getColumnCount();   i) {
                System.out.print(String.format("%st", rs.getString(i   1)));
            }
            System.out.println();
        }

    }
}

列集

ResultSet是对查询结果的一个抽象,但是结果集有一些局限性。所以出现了一个功能更强的接口就是列集RowSet,它继承自结果集,所以具备结果集的所有特性,同时还增加了一些功能。

下面就是一个使用JdbcRowSet的小例子。更多列集的使用方法请参考相关文档和博客。

代码语言:javascript复制
RowSetFactory factory = RowSetProvider.newFactory();
try (RowSet rs = factory.createJdbcRowSet()) {
    rs.setUrl(JdbcUtil.URL);
    rs.setUsername(JdbcUtil.USERNAME);
    rs.setPassword(JdbcUtil.PASSWORD);
    rs.setCommand("select *from user");
    rs.execute();

    ResultSetMetaData metaData = rs.getMetaData();
    for (int i = 0; i < metaData.getColumnCount();   i) {
        System.out.print(String.format("%st", metaData.getColumnName(i   1)));
    }
    System.out.println();
    while (rs.next()) {
        for (int i = 0; i < metaData.getColumnCount();   i) {
            System.out.print(String.format("%st", rs.getString(i   1)));
        }
        System.out.println();
    }
}

相应的我写了一个小项目来演示上面的这些例子。项目托管在Github上,地址在这里。有兴趣的同学可以看看。

存储过程

在JDBC中也可以执行存储过程。我们以MySQL存储过程为例。下面是两个存储过程。

代码语言:javascript复制
CREATE PROCEDURE find_all_blogs_of(IN user_id INT)
  BEGIN
    SELECT
      id,
      username,
      password,
      nickname,
      birthday
    FROM user
    WHERE id = user_id;
  END;

CREATE PROCEDURE get_total_user_count(OUT count INT)
  BEGIN
    SELECT count(id)
    FROM user
    INTO count;
  END;

执行存储过程需要使用CallableStatement。当存储过程需要IN参数的时候,像普通查询参数那样使用setInt这样的方法设置即可。如果存储过程是查询数据的,可以直接使用结果集返回。

代码语言:javascript复制
CallableStatement statement = connection.prepareCall("CALL find_all_blogs_of(?)");
statement.setInt(1, 1);
ResultSet rs = statement.executeQuery();

如果存储过程使用OUT参数返回结果,那么情况稍微有些复杂。我们需要使用registerOutParameter方法注册一个输出参数。然后在存储过程执行之后获取该参数的值。

代码语言:javascript复制
CallableStatement statement = connection.prepareCall("CALL get_total_user_count(?)");
statement.registerOutParameter(1, Types.INTEGER);
statement.execute();
int count = statement.getInt(1);

数据源

前面我们使用DriverManager来获取连接对象。但是在实际环境中最好使用数据原来实现相同的功能。JDBC定义了一个DataSource接口,所有的JDBC驱动都实现了该接口。除了JDBC驱动之外,还有一些类库页实现了该接口,提供了方便的数据源功能。以MySQL为例,我们来设置一个MysqlConnectionPoolDataSource数据源。

代码语言:javascript复制
MysqlConnectionPoolDataSource dataSource = new MysqlConnectionPoolDataSource();
dataSource.setUrl(JdbcUtil.URL);
dataSource.setUser(JdbcUtil.USERNAME);
dataSource.setPassword(JdbcUtil.PASSWORD);
dataSource.setUseSSL(false);

有了数据源,我们就可以调用数据源的getConnection方法获取连接对象了。如果查看MySQL的源代码或者文档会发现,MysqlConnectionPoolDataSource还提供了大量set方法设置数据源的各种属性,因此数据源应该是创建数据库连接的首选方式。

事务管理

前面我们都是在执行了SQL语句之后,立刻获得了结果。我们还可以使用JDBC的事务管理功能。首先需要调用Connection.setAutoCommit(false)将自动提交关闭,然后使用Connection.commit和Connection.rollback提交或回滚事务。

代码语言:javascript复制
DataSource dataSource = DataSourceUtils.getDataSource();
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false);
PreparedStatement selectOne = conn.prepareStatement("SELECT count(id) FROM user WHERE username =?");
PreparedStatement insertOne = conn.prepareStatement("INSERT INTO user(username,password) VALUES(?,?)");
//成功插入
String username1 = "zhang3";
insertOne.setString(1, username1);
insertOne.setString(2, "123456");
insertOne.executeUpdate();
conn.commit();

selectOne.setString(1, username1);
ResultSet rs = selectOne.executeQuery();
rs.first();
assertThat(rs.getInt(1), is(1));

//插入失败
String username2 = "li4";
insertOne.setString(1, username2);
insertOne.setString(2, "123456");
insertOne.executeUpdate();
conn.rollback();

selectOne.setString(1, username2);
rs = selectOne.executeQuery();
rs.first();
assertThat(rs.getInt(1), is(0));

参考资料

http://alvinalexander.com/java/jdbc-connection-string-mysql-postgresql-sqlserver

0 人点赞