JDBC和数据库连接池


JDBC

JDBC概述

JDBC全称为:Java DataBase Connectivity(java数据库连接),是SUN公司为了简化、统一对数据库的操作,定义了一套Java操作数据库的规范
JDBC是一组专门负责连接并操作数据库的标准和规范,在整个JDBC 中实际上大量的提供的是接口,具体实现由数据库厂商提供,不同数据库其JDBC驱动程序是不同的。
这样就能保证使用者的代码不会根据数据库的不同而不同。
JDBC与数据库驱动之间的关系:接口与实现的关系。

JDBC使用步骤

在操作JDBC时,我们大概可以分成四个步骤来完成:

  1. 加载数据库驱动程序,加载的时候需要将驱动程序配置到classpath之中。
  2. 连接数据库,通过Connection 接口和 DriverManager 类完成。
  3. 操作数据库,通过下面三个接口完成:
  • Statement
  • PreparedStatement
  • ResultSet
  1. 关闭数据库,在实际开发中数据库资源非常有限,操作完之后必须关闭。

一、注册驱动

驱动就是JDBC实现类,由不同数据库厂家制定。
注册驱动的方式总共有四种:

  • 最常用:Class.forName("com.mysql.jdbc.Driver");
  • System.setProperty("jdbc.drivers","com.mysql.jdbc.Driver);
  • 在jvm运行中配置参数 -D jdbc.drivers=com.mysql.jdbc.Driver
  • 不推荐:DriverManager.register(new com,mysql.jdbc.Driver());
    • 导致驱动被注册两次。
    • 强烈依赖数据库的驱动jar包。

二、与数据库建立连接

String url = protocol+ip+":"+port+"/"+schema+"?user="+user+"&password="+password;
Connection conn = DriverManager.getConnection(url);

url:SUN公司与数据库厂商之间的一种协议:jdbc:mysql://localhost:3306/Test

jdbc: mysql:// localhost:3306 /Test
协议 子协议 IP:端口号 数据库名
Properties info = new Properties();//要参考数据库文档
info.setProperty("user", "root");
info.setProperty("password","root");
Connection conn = DriverManager.getConnection(url, info);
DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");

三、获取Statement对象

Statement statement = conn.createStatement();

上面的 createStatement 会出现** SQL注入 **问题。
因此要使用 PreparedStatement 预编译处理,使用占位符 “ ? ”,然后再给占位符赋对应类型的值,避免SQL注入攻击。

PreparedStatement statement = conn.prepareStatement(
    "select distinct loan_type from loan where bank = ?"
);
statement.setString(1, "Citibank");

预处理的好处

  1. 用PreparedStatement可以写动态参数化的查询;
  2. PreparedStatement 比 Statement 性能更佳;
  3. PreparedStatement 可以防止SQL注入式攻击;
  4. 相对普通的凌乱的字符串追加拼接,PreparedStatement 可读性更好、更安全;

四、执行SQL

  • 根据查询语句返回结果集。只能执行select语句:statement.executeQuery(sql);
  • 根据执行的DML语句,返回受影响的行数:statement.executeUpdate(sql);
  • 执行任意sql语句,返回boolean值(是否返回ResultSet结果集):statement.execute(sql);

除此之外还可以将不同的操作放入批处理的缓冲区(内存)里,进行sql批量执行。

  • 添加语句到批处理缓冲区中: statement.addBatch("SQL语句");
  • 如果构造时已经传入SQL语句,直接:statement.addBatch();
  • 执行批处理:int[] batch = statement.executeBatch();

返回的是每条语句影响结果的数组,另外,查询的时候不可用。

  • 清空缓冲区:statement.clearBatch();

五、处理结果集

ResultSet 提供一个游标,默认游标指向结果集第一行之前。

  1. 移动游标的方法:
  • next():将光标从当前位置向下移一行。
  • previous():将光标移动到此 ResultSet 对象的上一行。
  • absolute(int row):参数是当前行的索引,从1开始。根据行的索引定位移动的指定索引行。
  • afterLast():将光标移动到末尾,正好位于最后一行之后。
  • beforeFirst():将光标移动到开头,正好位于第一行之前。
  1. 使用一些get方法(第几列或者字段名),获取值:
  • getObject
  • getInt
  • getFloat
  • getString
  • getDate

JDBC使用例子

    public void testQuery() {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            // 1.初始化驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 2.建立连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?character=UTF-8", "root", "root");
            // 3.通过连接创建 Statement
            statement = connection.createStatement();
            // 4.使用 Statement 执行 SQL 语句
            resultSet = statement.executeQuery("select * from stu");
            // 对结果集处理
            int id = 0;
            String name = "";
            int classId = 0;
            List<Student> list = new ArrayList<>();
            while (resultSet.next()) {
                id = resultSet.getInt("id");
                name = resultSet.getString("name");
                classId = resultSet.getInt("c_id");
                list.add(new Student(id, name, classId));
            }
            System.out.println(list);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //使用工具类关闭
            CloseUtils.closeAll(resultSet, statement, connection);
        }
    }

连接池

我们在进行CRUD时,一直重复性的写一些代码,比如最开始的注册驱动,获取连接代码,一直重复写,通过编写一个获取连接的工具类后,解决了这个问题。
但是又会出现新的问题,每进行一次操作,就会获取一个连接,用完之后,就销毁,就这样一直新建连接、销毁连接,而 **连接的创建与销毁是比较耗时的 **。

连接池就是为了解决这个问题而出现的一个方法,为了提高性能,开发连接池,连接池中一直保持有n个连接,供调用者使用,调用者用完返还给连接池,继续给别的调用者使用。
比如:

  1. 连接池中一开始就有10个连接。
  2. 当有5个用户拿走了5个连接后,池中还剩5个。
  3. 当第6个用户在去池中拿连接而前面5个连接还没归还时,连接池就会新建一个连接给第六个用户,让池中一直能够保存最少5个连接。
  4. 当这样新建了很多连接后,用户归还连接回来时,会比原先连接池中的10个连接更多,连接池就会设置一个池中最大空闲的连接数,如果超过了这个数,就会将超过的连接给释放掉,连接池就是这样工作的。

连接池概述

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。
释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。
这项技术能明显提高对数据库操作的性能。

常用连接池

现在很多WEB服务器 (Weblogic, WebSphere, Tomcat) 都提供了DataSoruce的实现,即连接池的实现。
通常我们把DataSource的实现,按其英文含义称之为数据源。
数据源中都包含了数据库连接池的实现,也有一些开源组织提供了数据源的独立实现:

  • DBCP
  • C3P0

实际应用时不需要编写连接数据库代码,直接从数据源获得数据库的连接。
程序员编程时也应尽量使用这些数据源的实现,以提升程序的数据库访问性能。

DBCP连接池

Apache 软件基金组织下的开源连接池实现,且 Tomcat 的连接池正是采用该连接池来实现的。
该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。

需要的jar包

  • Commons-dbcp.jar:连接池的实现
  • Commons-pool.jar:连接池实现的依赖库

配置方式

  1. 代码中手动设置参数
BasicDataSource bds = new BasicDataSource();
//配置4个基本参数
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql:///book");
bds.setUsername("root");
bds.setPassword("654321");

//管理连接配置
bds.setMaxActive(50); //最大活动数
bds.setMaxIdle(20);  //最大空闲数
bds.setMinIdle(5);  //最小空闲数
bds.setInitialSize(10);  //初始化个数

//获取连接
try {
    return bds.getConnection();
} catch (SQLException e) {
    throw new RuntimeException(e);
}
  1. 使用配置文件(常用方式)
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/book
username=root
password=654321

#<!-- 初始化连接 -->
initialSize=10

#最大连接数量
maxActive=50

#<!-- 最大空闲连接 -->
maxIdle=20

#<!-- 最小空闲连接 -->
minIdle=5

#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000

#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] 
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=gbk

#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true

#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=

#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED

使用方式(工具类)

public class DBCPUtils {
    private static DataSource ds = null;
    static{
        Properties prop = new Properties();
        try {
            prop.load(DBCPUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties"));//根据DBCPUtil的classes的路径,加载配置文件
            ds = BasicDataSourceFactory.createDataSource(prop);//得到一个数据源 
        } catch (Exception e) {
            throw new ExceptionInInitializerError("初始化错误,请检查配置文件");
        }
    }
    
    public static Connection getConnection(){
        try {
            return ds.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException("服务器忙。。。");
        }
    }
    
    public static void release(Connection conn,Statement stmt,ResultSet rs){
        //关闭资源
        if(rs!=null){
            try {
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;
        }
        if(stmt!=null){
            try {
                stmt.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            stmt = null;
        }
        if(conn!=null){
            try {
                conn.close();//关闭
            } catch (Exception e) {
                e.printStackTrace();
            }
            conn = null;
        }
    }
    
}

C3P0连接池

Apache 软件基金组织下的开源连接池实现,且 Tomcat 的连接池正是采用该连接池来实现的。
该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。

需要的jar包

  • c3p0.jar:连接池的实现
  • c3p0-jdk.jar:连接池实现的依赖库

oracle数据库还需要别的jar包

配置方式

  • 只能使用xml方式
<c3p0-config>
    <!-- 默认配置,如果没有指定则使用这个配置 -->
    <default-config>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/book</property>
        <property name="user">root</property>
        <property name="password">654321</property>
    
        <property name="checkoutTimeout">30000</property>
        <property name="idleConnectionTestPeriod">30</property>
        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
        <property name="maxStatements">200</property>
        <user-overrides user="test-user">
            <property name="maxPoolSize">10</property>
            <property name="minPoolSize">1</property>
            <property name="maxStatements">0</property>
        </user-overrides>
    </default-config> 
    <!-- 命名的配置 -->
    <named-config name="jxpx">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/myums</property>
        <property name="user">root</property>
        <property name="password">root</property>
    <!-- 如果池中数据连接不够时一次增长多少个 -->
        <property name="acquireIncrement">5</property>
        <property name="initialPoolSize">20</property>
        <property name="minPoolSize">10</property>
        <property name="maxPoolSize">40</property>
        <property name="maxStatements">0</property>
        <property name="maxStatementsPerConnection">5</property>
    </named-config>
</c3p0-config>

从配置文件中看,需要注意一个地方,一个是default-config,一个是name-config
两者都区别在于创建核心类对象时,如果将name-config作为参数传进去,那么将会调用name-config下的配置信息,否则将调用default-config下的配置信息。

使用方式
两种方式使用c3p0,加参数,使用named-config 的配置信息,不加参数,自动加载配置信息,加载的是default-config中的信息。
用的都是new ComboPooledDataSource()


参考博文

posted @ 2022-07-21 19:24  鹿友  阅读(231)  评论(0编辑  收藏  举报