JDBC (1) 概念入门

193 阅读6分钟

1. 概念

概念: JDBC,全名Java DataBase Connectivity,java和数据库连接中间件,是sun公司面对各个数据库提供的一组接口,从本质上来说就是调用者(程序员)和实现者(数据库厂商)之间的协议。

  • 如果想让java连接数据库必须向java项目添加该数据库对jdbc的支持(对应数据库的驱动jar包),该支持由具体数据库厂商提供。
  • JDBC的API使得开发人员可以使用纯java的方式来连接数据库并进行操作,使得向各种关系数据发送SQL语句就是一件很容易的事。
  • JDBC对Java程序员而言是API,对实现与数据库连接的服务提供商而言是接口模型,作为API,JDBC为程序开发提供标准的接口,并为数据库厂商及第三方中间件厂商实现与数据库的连接提供了标准方法。

不使用JDBC时的Java连库图 使用JDBC时的Java连库图

2. 搭建流程

流程:

  1. 引入对应数据库的驱动包:
    • mysql-connector-java-8.0.15.jar
  2. 编写mysql连接测试方法。
  3. 定义连库账号和密码。
  4. 定义连库的URL:jdbc:mysql://IP地址:端口号/数据库名?参数列表
    • user:数据库用户名。
    • password:数据库密码。
    • useUnicode=true:是否使用Unicode字符集,如果参数characterEncoding设置为gb2312或gbk,本参数值必须设置为true。
    • characterEncoding=utf-8:当useUnicode设置为true时,指定字符编码,比如可设置为gb2312或gbk。
    • autoReconnect=true:当数据库连接异常中断时,是否自动重新连接,默认false。
    • maxReconnects=5:当autoReconnect设置为true时,重试连接的次数,默认3次。
    • initialTimeout=3:当autoReconnect设置为true时,两次重连之间的时间间隔,单位是秒,默认2秒。
    • autoReconnectForPools=true:是否使用针对数据库连接池的重连策略,默认false。
    • failOverReadOnly=true:自动重连成功后,连接是否设置为只读,默认true。
    • useSSL=false:消除控制台的一个红色警告,使用SSL漏洞修复。
    • serverTimezone=UTC:mysql8版本的驱动必须填写的,mysql5版本可以不写。
    • connectTimeout:和数据库服务器建立socket连接时的超时,单位是毫秒,0表示永不超时,默认0。
    • socketTimeout:socket操作(读写)超时,单位是毫秒,0表示永不超时,默认0。
    • 在使用数据库连接池的情况下,最好设置 autoReconnect=true&failOverReadOnly=false 这两个参数。
  5. 通过反射的方式驱动数据库,即反射Driver类:
    • mysql5版本的驱动,Driver类所在的位置:com.mysql.jdbc.Driver
    • mysql8版本的驱动,Driver类所在的位置:com.mysql.cj.jdbc.Driver
  6. 通过 java.sql.DriverManager 类的来获取一个连接:
    • static Connection getConnection(String url, String user, String password)
    • static Connection getConnection(String url):需要将账号密码附在URL参数中。
  7. 测试连接是否关闭:
    • boolean isClosed():关闭返回true。
  8. 无论测试是否成功,都需要将连接关闭以节省内存资源:
    • void close():关闭资源需要抛出SQLException异常。

源码: start/ConnectTest.java

package com.yap.start;

import org.junit.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * @author yap
 */
public class ConnectTest {

    @Test
    public void connectMySql() throws SQLException {
        String user = "yap";
        String password = "yap";
        String driver = "com.mysql.cj.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/dbyap";
        String urlParam = "?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC";
        url += urlParam;
        Connection connection = null;
        try {
            Class.forName(driver);
            connection = DriverManager.getConnection(url, user, password);
            System.out.println(connection.isClosed() ? "fail" : "success");
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            connection.close();
        }
    }

    @Test
    public void connectOracle() throws SQLException {
        String user = "yap";
        String password = "yap";
        String driver = "oracle.jdbc.driver.OracleDriver";
        String url = "jdbc:oracle:thin:@localhost:1521:dbyap";
        Connection connection = null;
        try {
            Class.forName(driver);
            connection = DriverManager.getConnection(url, user, password);
            System.out.println(connection.isClosed() ? "fail" : "success");
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            connection.close();
        }
    }
}

3. DataSource

概念: 为了有效地重复利用驱动和连接的代码,建议将这部分代码封装到一个 DataSource 类中,这个类专门负责驱动数据库、制造连接和关闭连接。

流程:

  1. 引入静态块:
    • 通过反射驱动 Driver 类。
    • 通过 DriverManager 类获取一个有效的连接。
  2. 封装一个获取连接的方法:synchronized Connection getConnection()
    • 获取连接的方法需要加锁,否则如果赵四和刘能获取同一个连接,然后赵四关闭这个连接的时候刘能还没有使用完就会出现问题。
  3. 封装一个关闭连接的方法:void closeConnection(Connection connection)

源码: datasource/DataSource.java

package com.yap.datasource;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * @author yap
 */
public class DataSource {

    private static Connection connection;

    static {
		String driver = "com.mysql.cj.jdbc.Driver";
		String user = "yap";
		String password = "yap";
		String url = "jdbc:mysql://localhost:3306/dbyap";
		String urlParam = "?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC";
		url += urlParam;
        try {
			Class.forName(driver);
            connection = DriverManager.getConnection(url, user, password);
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
    }

    public synchronized Connection getConnection() {
        return connection;
    }

    public void closeConnection(Connection connection) {
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

源码: test/DataSourceTest.datasource()

package com.yap.test;
import com.yap.factory.DataSource;
import com.yap.factory.DataSourceFactory;
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author yap
 */
public class DataSourceTest {

    @Test
    public void datasource() {
        com.yap.datasource.DataSource dataSource = new com.yap.datasource.DataSource();
        Connection connection = dataSource.getConnection();
        try {
            System.out.println(connection.isClosed() ? "fail" : "success");
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            dataSource.closeConnection(connection);
        }
    }
}

4. 优化-连接池

概念: 每次访问数据库,都需要获取一个连接,很浪费资源,我们可以在直接在静态块中准备10个或者更多的连接,形成一个连接池,当调用者想要获取连接的时候,直接从池中获取,当调用者想要关闭连接的时候,将连接回收到池中,重新利用,这就是连接池的概念。

流程:

  1. 设置一个连接池属性:List<Connection> connectionPool
  2. 封装一个初始化连接池的方法:void initConnectionPool()
    • 使用 ArrayList 类型来初始化 connectionPool 属性。
    • 利用循环获取指定数量的连接,并且添加到连接池中。
  3. 静态块中调用 initConnectionPool() 方法完成连接池的初始化工作。
  4. 改造 getConnection() 方法:
    • 如果当前连接池中有连接,则从连接池中获取一个并返回。
    • 如果当前连接池中没有连接,则新建一个连接并返回。
  5. 改造 closeConnection() 方法:
    • 如果当前连接池中连接数量达到最大,则直接关闭新来的连接。
    • 如果当前连接池中仍有空余位置,则回收新来的连接。

源码: pool/DataSource.java

package com.yap.pool;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author yap
 */
public class DataSource {
    private static List<Connection> connectionPool;
    private static int connectionPoolSize = 10;

    static {
        String driver = "com.mysql.cj.jdbc.Driver";
        try {
            Class.forName(driver);
            initConnectionPool();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static void initConnectionPool() {
        connectionPool = new ArrayList<>();
        for (int i = 0; i < connectionPoolSize; i++) {
            connectionPool.add(createNewConnection());
        }
    }

    private static Connection createNewConnection() {
        String user = "yap";
        String password = "yap";
        String url = "jdbc:mysql://localhost:3306/dbyap";
        String urlParam = "?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC";
        url += urlParam;
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    public synchronized Connection getConnection() {
        Connection connection = null;
        if (connectionPool.isEmpty()) {
            connection = createNewConnection();
        } else {
            connection = connectionPool.remove(0);
        }
        return connection;
    }

    public void closeConnection(Connection connection) {
        try {
            if (connectionPool.size() < connectionPoolSize) {
                connectionPool.add(connection);
            } else {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

源码: pool/DataSourceTest.pool()

package com.yap.test;
import com.yap.factory.DataSource;
import com.yap.factory.DataSourceFactory;
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author yap
 */
public class DataSourceTest {

    @Test
    public void pool() {
        com.yap.pool.DataSource dataSource = new com.yap.pool.DataSource();
        Connection connection = dataSource.getConnection();
        try {
            System.out.println(connection.isClosed() ? "fail" : "success");
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            dataSource.closeConnection(connection);
        }
    }

}

5. 优化-属性文件

概念: 属性文件可以帮助我们代码解耦,将一些配置信息单独提取出来,放入到属性文件中,然后使用程序去读取属性文件的内容,这样的操作可以使得配置与代码分离。

流程:

  1. 建议新创建一个资源文件夹 resources,并直接在该目录下创建 db.properties 文件。
    • 资源文件夹不能嵌套资源文件夹,但是可以包含 package 或者 folder
  2. 将驱动串,连接串,账号密码等信息以 K=V 的形式提取到属性文件中。
  3. DataSource 类中封装一个 readPropertiesFile(),负责读取属性文件中的信息。
    • java.util.PropertyResourceBundle 类专门负责操作属性文件。
    • static ResourceBundle getBundle(String baseName):通过属性文件名获取属性文件对象。
      • 参数填写属性文件的名字即可,不要添加后缀。
    • String getString(String key):通过key来获取属性文件中的value值。
  4. 在静态块中,先行调用 readPropertiesFile(),将需要的信息提升到成员属性中。

如果url中的utf-8不支持,使用utf8替换。

源码: resources/db.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.user=yap
jdbc.password=yap
jdbc.url=jdbc:mysql://localhost:3306/dbyap?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
jdbc.connectionPoolSize=10

源码: properties/DataSource.java

package com.yap.properties;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;

/**
 * @author yap
 */
public class DataSource {
    private static List<Connection> connectionPool;
    private static int connectionPoolSize;
    private static String driver;
    private static String user;
    private static String password;
    private static String url;

    static {
        readPropertiesFile();
        try {
            Class.forName(driver);
            initConnectionPool();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static void readPropertiesFile() {
        ResourceBundle bundle = PropertyResourceBundle.getBundle("db");
        driver = bundle.getString("jdbc.driver");
        user = bundle.getString("jdbc.user");
        password = bundle.getString("jdbc.password");
        url = bundle.getString("jdbc.url");
        connectionPoolSize = Integer.parseInt(bundle.getString("jdbc.connectionPoolSize"));
    }

    private static void initConnectionPool() {
        connectionPool = new ArrayList<>();
        for (int i = 0; i < connectionPoolSize; i++) {
            connectionPool.add(createNewConnection());
        }
    }

    private static Connection createNewConnection() {
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    public synchronized Connection getConnection() {
        Connection connection = null;
        if (connectionPool.isEmpty()) {
            connection = createNewConnection();
        } else {
            connection = connectionPool.remove(0);
        }
        return connection;
    }

    public void closeConnection(Connection connection) {
        try {
            if (connectionPool.size() < connectionPoolSize) {
                connectionPool.add(connection);
            } else {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

源码: test/DataSourceTest.properties()

package com.yap.test;
import com.yap.factory.DataSource;
import com.yap.factory.DataSourceFactory;
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author yap
 */
public class DataSourceTest {
    @Test
    public void properties() {
        com.joezhou.properties.DataSource dataSource = new com.joezhou.properties.DataSource();
        Connection connection = dataSource.getConnection();
        try {
            System.out.println(connection.isClosed() ? "fail" : "success");
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            dataSource.closeConnection(connection);
        }
    }
}

6. 优化-工厂模式

概念: 在测试类中,我们需要自己创建一个 DataSource 然后再使用它,即是生产者,又是使用者,所以我们可以使用静态工厂模式,将生产者和使用者分离。

源码: factory/DataSource.java

package com.yap.factory;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;

/**
 * @author yap
 */
public class DataSource {
    private static List<Connection> connectionPool;
    private static int connectionPoolSize;
    private static String driver;
    private static String user;
    private static String password;
    private static String url;

    static {
        readPropertiesFile();
        try {
            Class.forName(driver);
            initConnectionPool();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static void readPropertiesFile() {
        ResourceBundle bundle = PropertyResourceBundle.getBundle("db");
        driver = bundle.getString("jdbc.driver");
        user = bundle.getString("jdbc.user");
        password = bundle.getString("jdbc.password");
        url = bundle.getString("jdbc.url");
        connectionPoolSize = Integer.parseInt(bundle.getString("jdbc.connectionPoolSize"));
    }

    private static void initConnectionPool() {
        connectionPool = new ArrayList<>();
        for (int i = 0; i < connectionPoolSize; i++) {
            connectionPool.add(createNewConnection());
        }
    }

    private static Connection createNewConnection() {
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    public synchronized Connection getConnection() {
        Connection connection = null;
        if (connectionPool.isEmpty()) {
            connection = createNewConnection();
        } else {
            connection = connectionPool.remove(0);
        }
        return connection;
    }

    public void closeConnection(Connection connection) {
        try {
            if (connectionPool.size() < connectionPoolSize) {
                connectionPool.add(connection);
            } else {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

源码: factory/DataSourceFactory.java


/**
 * @author yap
 */
public class DataSourceFactory {
    public static DataSource getDataSource() {
        return new DataSource();
    }
}

源码: test/DataSourceTest.factory()

package com.yap.test;
import com.yap.factory.DataSource;
import com.yap.factory.DataSourceFactory;
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author yap
 */
public class DataSourceTest {
    @Test
    public void factory() {
        DataSource dataSource = DataSourceFactory.getDataSource();
        Connection connection = dataSource.getConnection();
        try {
            System.out.println(connection.isClosed() ? "fail" : "success");
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            dataSource.closeConnection(connection);
        }
    }
}