前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JDBC 简介

JDBC 简介

作者头像
乐百川
发布2022-05-05 19:40:24
3690
发布2022-05-05 19:40:24
举报

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("%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的小例子。更多列集的使用方法请参考相关文档和博客。

代码语言: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("%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存储过程为例。下面是两个存储过程。

代码语言: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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-01-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 建立连接
  • 使用Statement
    • 执行更新
      • 执行查询
      • 预编译的语句
      • 结果集
      • 元数据
      • 列集
      • 存储过程
      • 数据源
      • 事务管理
      • 参考资料
      相关产品与服务
      云数据库 SQL Server
      腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档