JDBC 快速入门

本文涉及的产品
云数据库 RDS SQL Server,独享型 2核4GB
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: JDBC英文全称是Java Database Connectivity,也就是Java数据库连接。这是一个Java连接SQL数据库的标准,包含了常用的API,让我们能方便的连接盒管理SQL数据库。

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

建立连接

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

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:

Statement statement = connection.createStatement()

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

执行更新

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

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

statement.executeUpdate("INSERT INTO user(username,password) VALUES('yitian','123456')");

执行查询

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

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方法将参数传入,然后才能执行语句。

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() 将更新之后的行写入结果集和底层数据库

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

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方法,获取相应的元数据对象。

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

DatabaseMetaData metaData = connection.getMetaData();
logger.info("DriverName:{}", metaData.getDriverName());
logger.info("DriverVersion:{}", metaData.getDriverVersion());

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

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("%s\t", metaData.getColumnName(i + 1)));
        }
        System.out.println();
        while (rs.next()) {
            for (int i = 0; i < metaData.getColumnCount(); ++i) {
                System.out.print(String.format("%s\t", rs.getString(i + 1)));
            }
            System.out.println();
        }

    }
}

列集

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

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

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("%s\t", metaData.getColumnName(i + 1)));
    }
    System.out.println();
    while (rs.next()) {
        for (int i = 0; i < metaData.getColumnCount(); ++i) {
            System.out.print(String.format("%s\t", rs.getString(i + 1)));
        }
        System.out.println();
    }
}

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

存储过程

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

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这样的方法设置即可。如果存储过程是查询数据的,可以直接使用结果集返回。

CallableStatement statement = connection.prepareCall("CALL find_all_blogs_of(?)");
statement.setInt(1, 1);
ResultSet rs = statement.executeQuery();

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

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数据源。

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提交或回滚事务。

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

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
3月前
|
SQL Java 关系型数据库
JDBC快速入门
JDBC快速入门
48 0
|
8月前
|
Java 数据库连接 数据库
JDBC快速入门(四)
JDBC快速入门(四)
|
6月前
|
SQL Java 关系型数据库
JDBC知识【JDBC快速入门】第二章
JDBC知识【JDBC快速入门】第二章
|
7月前
|
SQL Java 关系型数据库
JDBC快速入门
JDBC快速入门
28 0
|
8月前
|
SQL Java 数据库连接
JDBC快速入门(五)--PreparedStatement用法
JDBC快速入门(五)--PreparedStatement用法
|
8月前
|
Java 数据库连接 数据库
JDBC快速入门(三) 代码再优化
JDBC快速入门(三) 代码再优化
|
8月前
|
SQL Java 关系型数据库
JDBC快速入门(二) JDBC各个类详解以及代码规范
JDBC快速入门(二) JDBC各个类详解以及代码规范
|
8月前
|
SQL Java 数据库连接
JDBC快速入门(一)
JDBC快速入门(一)
|
11月前
|
Java 数据库连接 数据库
|
11月前
|
SQL 存储 druid