Java
Java Web基础

使用JDBC操作数据库

简介:JDBC全称Java Database Connectivity,是SUN公司提供的一套操作数据库的标准规范。

1. JDBC概述

JDBC全称Java Database Connectivity,是SUN公司提供的一套操作数据库的标准规范。JDBC规范主要有四个核心对象:

  • DriverManager:用于注册驱动。
  • Connection:表示与数据库创建的连接。
  • Statement:操作数据库sql语句的对象。
  • ResultSet:结果集或一张虚拟表。

对于不同的数据库,只需要提供实现了JDBC规范的实现类,就可以使用Java来驱动数据库的各种操作。

注:JDBC规范位于JDK中java.sql.*javax.sql.*

2. 一个简单的JDBC程序示例

在开发之前,需要做一些准备工作,在数据库中创建测试表,这里使用MySQL数据库,创建一个users表并添加测试数据,SQL语句如下:

Sql
  • create database jdbc_test;
  • use jdbc_test;
  • create table users(
  • id int primary key auto_increment,
  • name varchar(40),
  • gender varchar(10),
  • email varchar(60),
  • birthday date
  • ) character set utf8 collate utf8_general_ci;
  • insert into users(name, gender, email, birthday) values('Tom', 'Male', 'Tom@coderap.com', '1993-12-04');
  • insert into users(name, gender, email, birthday) values('Jack', 'Male', 'Jack@coderap.com', '1991-12-04');
  • insert into users(name, gender, email, birthday) values('Marry', 'Female', 'Marry@coderap.com', '1999-12-04');

完成后,users表数据如下:

  • mysql> select * from users;
  • +----+-------+--------+-------------------+------------+
  • | id | name | gender | email | birthday |
  • +----+-------+--------+-------------------+------------+
  • | 1 | Tom | Male | Tom@coderap.com | 1993-12-04 |
  • | 2 | Jack | Male | Jack@coderap.com | 1991-12-04 |
  • | 3 | Marry | Female | Marry@coderap.com | 1999-12-04 |
  • +----+-------+--------+-------------------+------------+
  • 3 rows in set (0.00 sec)

接下来用代码实现JDBC操作。在操作之前,需要先导入MySQL的驱动Jar包,这里使用的是mysql-connector-java-5.0.8-bin.jar,然后编写下面的代码:

Java
  • package com.coderap.jdbc;
  • import java.sql.*;
  • public class JDBCTest {
  • static {
  • try {
  • // 1. 注册驱动
  • Class.forName("com.mysql.jdbc.Driver");
  • } catch (ClassNotFoundException e) {
  • e.printStackTrace();
  • }
  • }
  • public static void main(String[] args) {
  • testQuery();
  • }
  • private static void testQuery() {
  • Connection connection = null;
  • PreparedStatement pstmt = null;
  • ResultSet resultSet = null;
  • try {
  • // 2. 创建连接
  • connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test", "root", "12345678");
  • // 3. 得到执行sql语句的Statement对象
  • String sql = "select name, gender, email, birthday from users;";
  • pstmt = connection.prepareStatement(sql);
  • // 4. 执行sql语句,并返回结果
  • resultSet = pstmt.executeQuery();
  • // 5. 处理结果
  • while (resultSet.next()) {
  • String name = resultSet.getString(1);
  • String gender = resultSet.getString(2);
  • String email = resultSet.getString(3);
  • Date birthday = resultSet.getDate(4);
  • System.out.println("name: " + name + ", gender: " + gender + ", email: " + email + ", brithday: " + birthday);
  • }
  • } catch (Exception e) {
  • e.printStackTrace();
  • } finally {
  • // 6. 关闭资源
  • if (connection != null) {
  • try {
  • connection.close();
  • } catch (SQLException e) {
  • e.printStackTrace();
  • }
  • }
  • if (pstmt != null) {
  • try {
  • pstmt.close();
  • } catch (SQLException e) {
  • e.printStackTrace();
  • }
  • }
  • if (resultSet != null) {
  • try {
  • resultSet.close();
  • } catch (SQLException e) {
  • e.printStackTrace();
  • }
  • }
  • }
  • }
  • }

注:oracle可使用jdbc:oracle:thin:@localhost:1521:jdbc_test

运行代码,得到以下结果:

  • name: Tom, gender: Male, email: Tom@coderap.com, brithday: 1993-12-04
  • name: Jack, gender: Male, email: Jack@coderap.com, brithday: 1991-12-04
  • name: Marry, gender: Female, email: Marry@coderap.com, brithday: 1999-12-04

3. JDBC常用的类和接口

  1. java.sql.Drivermanager类:创建连接

这个类一般用于创建数据库连接。在创建数据库连接之前,一般需要先注册驱动,有两种方式:

  • DriverManager.registerDriver(new com.mysql.jdbc.Driver());:不建议使用这种方式,它会导致驱动被注册2次,同时强烈依赖数据库的驱动Jar包。
  • Class.forName("com.mysql.jdbc.Driver");:这种方式是桥接模式的典型应用。Class.forName(String className)的作用有两个,第一是将CLASSPATH下指定名字的.class文件加载到Java虚拟机内存中,第二是初始化这个类。Class.forName("com.mysql.jdbc.Driver");的作用就是初始化com.mysql.jdbc.Drviercom.mysql.jdbc.Driver类的源码如下(给静态资源赋值以及执行静态代码块):
Java
  • package com.mysql.jdbc;
  • import java.sql.SQLException;
  • /**
  • * The Java SQL framework allows for multiple database drivers. Each driver
  • * should supply a class that implements the Driver interface
  • *
  • * <p>
  • * The DriverManager will try to load as many drivers as it can find and then
  • * for any given connection request, it will ask each driver in turn to try to
  • * connect to the target URL.
  • *
  • * <p>
  • * It is strongly recommended that each Driver class should be small and
  • * standalone so that the Driver class can be loaded and queried without
  • * bringing in vast quantities of supporting code.
  • *
  • * <p>
  • * When a Driver class is loaded, it should create an instance of itself and
  • * register it with the DriverManager. This means that a user can load and
  • * register a driver by doing Class.forName("foo.bah.Driver")
  • *
  • * @see org.gjt.mm.mysql.Connection
  • * @see java.sql.Driver
  • * @author Mark Matthews
  • * @version $Id: Driver.java 3726 2005-05-19 15:52:24Z mmatthews $
  • */
  • public class Driver extends NonRegisteringDriver implements java.sql.Driver {
  • // ~ Static fields/initializers
  • // ---------------------------------------------
  • //
  • // Register ourselves with the DriverManager
  • //
  • static {
  • try {
  • java.sql.DriverManager.registerDriver(new Driver());
  • } catch (SQLException E) {
  • throw new RuntimeException("Can't register driver!");
  • }
  • }
  • // ~ Constructors
  • // -----------------------------------------------------------
  • /**
  • * Construct a new driver and register it with DriverManager
  • *
  • * @throws SQLException
  • * if a database error occurs.
  • */
  • public Driver() throws SQLException {
  • // Required for Class.forName().newInstance()
  • }
  • }

同时在这个类源码的注释中有一句解释:

When a Driver class is loaded, it should create an instance of itself and register it with the DriverManager. This means that a user can load and register a driver by doing Class.forName(“foo.bah.Driver”).

这句注释表明,当使用Class.forName("com.mysql.jdbc.Driver")时会创建一个Driver实例,并且使用DriverManager注册它,源码中的具体表现是java.sql.DriverManager.registerDriver(new Driver());这一句,java.sql.DriverManager.registerDriver(java.sql.Driver driver);关键代码如下:

Java
  • // List of registered JDBC drivers
  • private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();
  • public static synchronized void registerDriver(java.sql.Driver driver)
  • throws SQLException {
  • /* Register the driver if it has not already been added to our list */
  • if (driver != null) {
  • registeredDrivers.addIfAbsent(new DriverInfo(driver));
  • } else {
  • // This is for compatibility with the original DriverManager
  • throw new NullPointerException();
  • }
  • println("registerDriver: " + driver);
  • }
  • // Worker method called by the public getConnection() methods.
  • private static Connection getConnection(
  • String url, java.util.Properties info, ClassLoader callerCL) throws SQLException {
  • /*
  • * When callerCl is null, we should check the application's
  • * (which is invoking this class indirectly)
  • * classloader, so that the JDBC driver class outside rt.jar
  • * can be loaded from here.
  • */
  • synchronized (DriverManager.class) {
  • // synchronize loading of the correct classloader.
  • if (callerCL == null) {
  • callerCL = Thread.currentThread().getContextClassLoader();
  • }
  • }
  • if (url == null) {
  • throw new SQLException("The url cannot be null", "08001");
  • }
  • println("DriverManager.getConnection(\"" + url + "\")");
  • // Walk through the loaded registeredDrivers attempting to make a connection.
  • // Remember the first exception that gets raised so we can reraise it.
  • SQLException reason = null;
  • for (DriverInfo aDriver : registeredDrivers) {
  • // If the caller does not have permission to load the driver then
  • // skip it.
  • if (isDriverAllowed(aDriver.driver, callerCL)) {
  • try {
  • println(" trying " + aDriver.driver.getClass().getName());
  • Connection con = aDriver.driver.connect(url, info);
  • if (con != null) {
  • // Success!
  • println("getConnection returning " + aDriver.driver.getClass().getName());
  • return (con);
  • }
  • } catch (SQLException ex) {
  • if (reason == null) {
  • reason = ex;
  • }
  • }
  • } else {
  • println(" skipping: " + aDriver.getClass().getName());
  • }
  • }
  • // if we got here nobody could connect.
  • if (reason != null) {
  • println("getConnection failed: " + reason);
  • throw reason;
  • }
  • println("getConnection: no suitable driver found for " + url);
  • throw new SQLException("No suitable driver found for " + url, "08001");
  • }

底层利用了一个CopyOnWriteArrayList作为容器,装载注册进来的Driver(使用DriverInfo包装),最终getConnection()相关的方法会依次从CopyOnWriteArrayList里的DriverInfo进行连接测试,如果连接成功则返回对应的链接。之所以不使用new com.mysql.jdbc.Driver()的方法是因为Driver对象对于我们来说是不透明的,我们并不会直接用到该类相关的方法和属性,仅需要加载它即可,使用者剩余的工作都是面向DriverManager的。

注:在JDK1.5之后,其实已经不需要去显式调用Class.forName("com.mysql.jdbc.Driver")了,DriverManager会自动去加载合适的驱动,但是前提是CLASSPATH下必须有相应的驱动Jar包。

  1. getConnection():试图建立到给定数据库URL的连接

getConnection()有多种重载方法:

Java
  • // public static Connection getConnection(String url, String user, String password)
  • // 使用url、用户名和密码连接
  • DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test", "root", "12345678");
  • // public static Connection getConnection(String url, java.util.Properties info)
  • // 使用url,用户名和密码信息使用Properties对象传递进行连接
  • Properties info = new Properties对象传递进行连接(); // 要参考数据库文档
  • info.setProperty("user", "root");
  • info.setProperty("password", "12345678");
  • DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test", info);
  • // public static Connection getConnection(String url)
  • // 将用户名和密码写在url中进行连接
  • DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test?user=root&password=12345678");
  1. java.sql.Connection接口:一个连接

getConnection()方法返回的与数据库进行交互的连接,所有与数据库交互都是基于连接对象的。接口的实现在数据库驱动中,它可以对连接的一些基本属性进行设置。

  1. java.sql.Statement接口: 操作sql语句,并返回相应结果的对象

Statement用于执行静态SQL语句并返回它所生成结果的对象。

  • // 根据查询语句返回结果集,只能执行select语句。
  • ResultSet executeQuery(String sql);
  • // 根据执行的DML(insert update delete)语句,返回受影响的行数。
  • int executeUpdate(String sql);
  • // 此方法可以执行任意sql语句。仅当执行select语句,且有返回结果时返回true, 其它语句都返回false。
  • boolean execute(String sql);
  1. java.sql.ResultSet接口: 结果集
  • 封装结果集

ResultSet对象提供一个游标,默认游标指向结果集第一行之前,每调用一次next(),游标向下移动一行。同时它提供一些get方法,如:

  • // 根据序号取值,索引从1开始
  • Object getObject(int columnIndex);
  • // 根据列名取值
  • Object getObject(String ColomnName);

Java的数据类型与数据库中的类型的关系:

类型 Java类型 数据库类型
字节 byte tityint
短整型 short smallint
整型 int int
长整型 long bigint
浮点型 float float
双精度浮点型 double double
字符串 String char, varchar
日期 Date date

通过ResultSet的下列方法可以获取特定类型的结果数据:

  • // 将光标从当前位置向下移动一行
  • boolean next();
  • // 以int形式获取ResultSet结果集当前行指定列号值
  • int getInt(int colIndex);
  • // 以int形式获取ResultSet结果集当前行指定列名值
  • int getInt(String colLabel);
  • // 以float形式获取ResultSet结果集当前行指定列号值
  • float getFloat(int colIndex);
  • // 以float形式获取ResultSet结果集当前行指定列名值
  • float getFloat(String colLabel);
  • String getString(int colIndex);
  • // 以String形式获取ResultSet结果集当前行指定列名值
  • String getString(String colLabel);
  • // 以Date形式获取ResultSet结果集当前行指定列号值
  • Date getDate(int columnIndex);
  • // 以Date形式获取ResultSet结果集当前行指定列名值
  • Date getDate(String columnName);
  • // 关闭ResultSet对象
  • void close();
  • 可移动游标的方法
  • // 将光标从当前位置向前移一行
  • boolean next();
  • // 将光标移动到此 ResultSet 对象的上一行
  • boolean previous();
  • // 参数是当前行的索引,从1开始
  • boolean absolute(int row);
  • // 根据行的索引定位移动的指定索引行
  • void afterLast();
  • // 将光标移动到末尾,正好位于最后一行之后
  • void beforeFirst();
  • 将光标移动到开头正好位于第一行之前

4. JDBC事务

JDBC事务默认是开启了自动提交的,所以我们在修改数据时并不需要手动提交,如下代码:

Java
  • public static void testInsert() {
  • Connection connection = null;
  • PreparedStatement pstmt = null;
  • try {
  • // 2. 创建连接
  • connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test", "root", "12345678");
  • // 3. 得到执行sql语句的Statement对象
  • String sql = "insert into users(name, gender, email, birthday) values (?, ?, ?, ?);";
  • pstmt = connection.prepareStatement(sql);
  • pstmt.setString(1, "John");
  • pstmt.setString(2, "Male");
  • pstmt.setString(3, "John@coderap.com");
  • pstmt.setDate(4, new Date(new java.util.Date().getTime()));
  • // 4. 执行sql语句,并返回结果
  • int result = pstmt.executeUpdate();
  • System.out.println("Result: " + result);
  • } catch (Exception e) {
  • e.printStackTrace();
  • } finally {
  • // 6. 关闭资源
  • if (connection != null) {
  • try {
  • connection.close();
  • } catch (SQLException e) {
  • e.printStackTrace();
  • }
  • }
  • if (pstmt != null) {
  • try {
  • pstmt.close();
  • } catch (SQLException e) {
  • e.printStackTrace();
  • }
  • }
  • }
  • }

如果想要手动提交,只需要将自动提交关闭即可:

  • connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test", "root", "12345678");
  • connection.setAutoCommit(false);

手动提交事务的时候,可以根据实际情况控制事务的回滚(如发生错误的时候),使用connection.rollback();

Java
  • public static void testInsert() {
  • Connection connection = null;
  • PreparedStatement pstmt = null;
  • try {
  • // 2. 创建连接
  • connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test", "root", "12345678");
  • connection.setAutoCommit(false);
  • // 3. 得到执行sql语句的Statement对象
  • String sql = "insert into users(name, gender, email, birthday) values (?, ?, ?, ?);";
  • pstmt = connection.prepareStatement(sql);
  • pstmt.setString(1, "John");
  • pstmt.setString(2, "Male");
  • pstmt.setString(3, "John@coderap.com");
  • pstmt.setDate(4, new Date(new java.util.Date().getTime()));
  • // 4. 执行sql语句,并返回结果
  • int result = pstmt.executeUpdate();
  • // 提交事务
  • connection.commit();
  • System.out.println("Result: " + result);
  • } catch (Exception e) {
  • e.printStackTrace();
  • // 回滚事务
  • try {
  • connection.rollback();
  • } catch (SQLException e1) {
  • e1.printStackTrace();
  • }
  • } finally {
  • // 6. 关闭资源
  • if (connection != null) {
  • try {
  • connection.close();
  • } catch (SQLException e) {
  • e.printStackTrace();
  • }
  • }
  • if (pstmt != null) {
  • try {
  • pstmt.close();
  • } catch (SQLException e) {
  • e.printStackTrace();
  • }
  • }
  • }
  • }