JDBC 详解(一):JDBC 基本介绍与使用

416 阅读8分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的统一方法。

JDBC API 介绍

java.sql.DriverManager(类)

用于管理一组JDBC驱动程序的基本服务。注意:javax.sql.DataSource接口是JDBC 2.0 API中的新接口,它提供了连接到数据源的另一种方式。使用DataSource对象是连接到数据源的首选方法。 作为初始化的一部分,DriverManager类将尝试加载在“jdbc.drivers”中引用的驱动程序类。驱动器的系统属性。这允许用户自定义应用程序使用的JDBC驱动程序。

DriverManager方法getConnection和getDrivers已经被增强,以支持Java标准版服务提供程序机制(SPI)。JDBC 4.0驱动程序必须包含文件META-INF/services/java.sql.Driver

应用程序不再需要使用Class.forName()显式加载JDBC驱动程序。当调用getConnection方法时,DriverManager将尝试从初始化时加载的驱动程序和使用与当前applet或应用程序相同的类加载器显式加载的驱动程序中定位合适的驱动程序。

重要方法:

// 尝试建立到给定数据库URL的连接。DriverManager试图从注册的JDBC驱动程序集中选择适当的驱动程序。
public static Connection getConnection(String url,
    java.util.Properties info) throws SQLException {

    return (getConnection(url, info, Reflection.getCallerClass()));
}


// 尝试定位理解给定URL的驱动程序。DriverManager试图从注册的JDBC驱动程序集中选择适当的驱动程序。
public static Driver getDriver(String url) throws SQLException {
    Class<?> callerClass = Reflection.getCallerClass();

    // Walk through the loaded registeredDrivers attempting to locate someone
    // who understands the given URL.
    for (DriverInfo aDriver : registeredDrivers) {
        // If the caller does not have permission to load the driver then
        // skip it.
        if(isDriverAllowed(aDriver.driver, callerClass)) {
            try {
                if(aDriver.driver.acceptsURL(url)) {
                    // Success!
                    println("getDriver returning " + aDriver.driver.getClass().getName());
                return (aDriver.driver);
                }
            } catch(SQLException sqe) {
                // Drop through and try the next driver.
            }
        } else {
            println("    skipping: " + aDriver.driver.getClass().getName());
        }
    }
    throw new SQLException("No suitable driver", "08001");
}


// 向DriverManager注册给定的驱动程序。如果驱动程序当前已注册,则不采取任何操作
public static synchronized void registerDriver(java.sql.Driver driver,
        DriverAction da) throws SQLException {
    
    /* Register the driver if it has not already been added to our list */
    if(driver != null) {
        registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
    } else {
        // This is for compatibility with the original DriverManager
        throw new NullPointerException();
    }
}

java.sql.Connection(接口)

与特定数据库的连接(会话)。在连接的上下文中执行SQL语句并返回结果。 Connection对象的数据库能够提供描述其表、其支持的SQL语法、其存储过程、此连接的功能等的信息(#getMetaData方法获取)。

注意:在配置连接时,JDBC应用程序应该使用适当的连接方法,如setAutoCommit或setTransactionIsolation,默认情况下为AutoCommit

使用JDBC 2.1核心API创建的新Connection对象具有与之关联的初始空类型映射。用户可以在此类型映射中为UDT输入自定义映射。当使用ResultSet方法从数据源检索UDT时。getObject方法将检查连接的类型映射。如下示例就将字段转换为 Map:

java.util.Map map = con.getTypeMap();
map.put("mySchemaName.ATHLETES", Class.forName("Athletes"));
con.setTypeMap(map);

重要方法:

// 创建执行对象,适用于执行一次或不带参数的 SQL,存在 SQL 注入风险
Statement createStatement() throws SQLException;
// 创建预编译执行对象,适用于复杂 SQL 创建,没有SQL 注入风险
PreparedStatement prepareStatement(String sql) throws SQLException;
// 自动提交 SQL 
void setAutoCommit(boolean autoCommit) throws SQLException;

void commit() throws SQLException;
void rollback() throws SQLException;
// 设置事务隔离级别
void setTransactionIsolation(int level) throws SQLException;
// 获取当前连接 Schema
String getSchema() throws SQLException;

java.sql.Statement(接口)

用于执行静态SQL语句并返回其产生的结果的对象。 默认情况下,每个Statement对象只能同时打开一个ResultSet对象。因此,如果一个ResultSet对象的读取与另一个ResultSet对象的读取交织在一起,那么每个ResultSet对象都必须是由不同的Statement对象生成的。如果存在打开的ResultSet对象,Statement接口中的所有执行方法都隐式关闭该语句的当前ResultSet对象。

重要方法:

ResultSet executeQuery(String sql) throws SQLException;
int executeUpdate(String sql) throws SQLException;
ResultSet getResultSet() throws SQLException;

java.sql.ResultSet(接口)

表示数据库结果集的数据表,通常是通过执行查询数据库的语句生成的。 ResultSet对象维护指向其当前数据行的游标。最初,游标定位在第一行之前。next方法将光标移动到下一行,由于它在ResultSet对象中没有更多行时返回false,因此可以在while循环中使用它遍历结果集。 默认的ResultSet对象是不可更新的,并且只有向前移动的游标。因此,您只能迭代它一次,而且只能从第一行到最后一行。可以生成可滚动和/或可更新的ResultSet对象。

Statement stmt = con.createStatement(
                                ResultSet.TYPE_SCROLL_INSENSITIVE,
                                ResultSet.CONCUR_UPDATABLE);
 ResultSet rs = stmt.executeQuery("SELECT a, b FROM TABLE2");
 // rs will be scrollable, will not show changes made by others,
 // and will be updatable

ResultSet接口提供了从当前行检索列值的getter方法(getBoolean、getLong等)。可以使用列的索引号或列的名称检索值。JDBC规范有一个表,显示允许的从SQL类型到ResultSet获取方法所使用的Java类型的映射关系。

当生成ResultSet对象的Statement对象被关闭、重新执行或用于从多个结果序列中检索下一个结果时,ResultSet对象将自动关闭。


重要方法:

boolean next() throws SQLException;
int getRow() throws SQLException;
<T> getT

java.sql.Driver(接口)

每个驱动程序类必须实现的接口。 Java SQL框架允许多个数据库驱动程序。 每个驱动程序都应该提供实现driver接口的类。 DriverManager将尝试加载尽可能多的驱动程序,然后对于任何给定的连接请求,它将依次要求每个驱动程序尝试连接到目标URL。 强烈建议每个Driver类都应该是小的、独立的,这样就可以加载和查询Driver类,而不需要引入大量的支持代码。 当一个Driver类被加载时,它应该创建一个自己的实例,并将其注册到DriverManager。这意味着用户可以通过调用以下命令来加载和注册一个驱动程序:Class.forName(“foo.bah.Driver”)。

重要方法:

boolean acceptsURL(String url) throws SQLException;
Connection connect(String url, java.util.Properties info) throws SQLException;
DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) throws SQLException;

javax.sql.DataSource(接口)

该工厂用于提供到此DataSource对象所表示的物理数据源的连接(A factory for connections to the physical data source that this DataSource object represents.)。作为 DriverManager工具的替代项,DataSource对象是获取连接的首选方法。实现DataSource接口的对象通常在基于JavaTM Naming and Directory Interface (JNDI) API的命名服务中注册

DataSource接口由驱动程序供应商实现。共有三种类型的实现:

  • 基本实现:生成标准的Connection对象
  • 连接池实现:生成自动参与连接池的Connection对象。此实现与中间层连接池管理器一起使用。
  • 分布式事务实现: 生成一个Connection对象,该对象可用于分布式事务,大多数情况下总是参与连接池。此实现与中间层事务管理器一起使用,大多数情况下总是与连接池管理器一起使用。

DataSource对象的属性在必要时可以修改。例如,如果将数据源移动到另一个服务器,则可更改与服务器相关的属性。其优点在于,由于可以更改数据源的属性,所以任何访问该数据源的代码都无需更改。

通过DataSource对象访问的驱动程序本身不会向DriverManager注册。通过查找操作获取DataSource对象,然后使用该对象创建Connection对象。使用基本的实现,通过DataSource对象获取的连接与通过DriverManager设施获取的连接相同。

Apache Commons的DBCP(Database Connection Pool) 就是基于 DataSource 实现的数据库连接池组件

JDBC 使用

基于 Driver 使用 JDBC

下面一段代码展示了 JDBC 的简单实用

public class JdbcUserRepository {
    private final String url;
    private final String user;
    private final String password;
    private final String driverName;

    public JdbcUserRepository(String url, String user, String password, String driverName) {
        this.url = url;
        this.user = user;
        this.password = password;
        this.driverName = driverName;
    }

    public JdbcUser selectUserById(int userId) {
        JdbcUser user = null;
        Statement statement = null;
        Connection connection = null;
        try {
            connection = getConnectionByDriver();
            // connection = getConnectionByDataSource();
            statement = connection.createStatement();
            user = getUserFromRs(statement.executeQuery(preparedQuerySql(userId)));
        } catch (Exception e) {
            //
        } finally {
            try {
                if (statement != null) {
                    statement.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                //
            }
        }
        return user;
    }

    private Connection getConnectionByDriver() throws ClassNotFoundException, SQLException {
        Class.forName(driverName);
        return DriverManager.getConnection(url, user, password);
    }



    private String preparedQuerySql(int id) {
        return "select * from user where id = " + id + ";";
    }

    private JdbcUser getUserFromRs(ResultSet resultSet) throws SQLException {
        JdbcUser user = null;
        while (resultSet.next()) {
            user = new JdbcUser();
            user.setId(resultSet.getInt(1));
            user.setName(resultSet.getString(2));
            user.setAge(resultSet.getInt(3));
        }
        return user;
    }

    public static void main(String[] args) {
        JdbcUserRepository userRepository = new JdbcUserRepository(
            "jdbc:mysql://localhost:3306/calcite",
            "root",
            "123456",
            "com.mysql.cj.jdbc.Driver");

        JdbcUser user = userRepository.selectUserById(1);
        System.out.println(user);
    }
}

可以注意到,Class.forName(driverName); 这段代码只是检查类是否存在,驱动类的发现与加载在 DriverManager 中完成

// 在类加载时会自动向 DriverManager 注册自己
    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }

// DriverManager 中维护了 registeredDrivers 列表
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());
    }
}

基于 DataSource 使用 JDBC

	// 基于 DBCP 连接池能力
	private Connection getConnectionByDataSource() throws SQLException {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUrl(url);
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        return dataSource.getConnection();
    }