JAVA入门基础_JDBC和数据库连接池_基于MYSQL

JDBC 概述

JDBC 是什么?

  • JDBC 实际上就是为了能够访问不同的数据库,而提供的一套接口规范。

  • 各个数据库厂商实现这套接口规范再提供相对应的 jar 包让 JAVA 程序能够操作对应的数据库。

  • 学习 JDBC 就是在学习这一套接口规范,变成面向接口编程。

  • 既然是外部为 JAVA 提供这些 jar 包,因此这些接口规范一般都在 java.sql 和 javax.sql 包中

JDBC 快速入门

  • 既然 MYSQL 实现了 JDBC 这套接口的规范,那么当然我们需要用到 MYSQL 实现这套接口的 jar 包
    mysql-connector-java-5.1.40.jar

使用 JDBC 连接数据库并进行操作的 5 个步骤

  • (1)注册相对应的数据库驱动

  • (2)获取连接对象

  • (3)获取到命令对象(操作数据库的对象)

  • (4)使用命令对象对数据库进行操作(若是查询操作会返回结果集)

  • (5)关闭连接(包括结果集、命令对象、连接对象)

获取数据库连接的最原始的方式

import com.mysql.jdbc.Driver;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

/**

  • @author codeStars

  • @date 2022/8/17 9:35
    */
    public class Test {

    public static void main(String[] args) throws Exception{
    // 准备连接数据库需要的数据
    String url = "jdbc:mysql://localhost:13306/mydb";
    String user = "root";
    String pwd = "abc123";

     <span class="hljs-comment">// 1. 获取到驱动对象并进行驱动注册</span>
     <span class="hljs-type">Driver</span> <span class="hljs-variable">driver</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">com</span>.mysql.jdbc.Driver();
     DriverManager.registerDriver(driver);
    
     <span class="hljs-comment">// 2. 获取连接对象</span>
     <span class="hljs-type">Connection</span> <span class="hljs-variable">connection</span> <span class="hljs-operator">=</span> DriverManager.getConnection(url, user, pwd);
    
     <span class="hljs-comment">// 3. 获取到命令对象</span>
     <span class="hljs-type">Statement</span> <span class="hljs-variable">statement</span> <span class="hljs-operator">=</span> connection.createStatement();
    
     <span class="hljs-comment">// 4. 对数据库进行操作</span>
     <span class="hljs-type">int</span> <span class="hljs-variable">affectedCount</span> <span class="hljs-operator">=</span> statement.executeUpdate(<span class="hljs-string">"INSERT INTO  animal VALUES(2,'王五')"</span>);
     System.out.println(<span class="hljs-string">"影响的语句数量:"</span> + affectedCount);
    
     <span class="hljs-comment">// 5. 关闭连接</span>
     statement.close();
     connection.close();
    

    }
    }

该方式的弊端

  • (1) 在创建 Driver 对象时,其中的静态代码块就会完成驱动的注册(我们相当于多此一举)

    • 解决方法:只创建一个 Driver 对象,但是不再进行驱动的注册,但是还有如下的问题。

  • (2)虽然只创建了一个 Driver 对象,但是 Driver 类中的静态代码块有这么一句
    DriverManager.registerDriver(new Driver());
    • 由此可以看出,在 Driver 类加载时,就会创建一个新的 Driver 对象来进行驱动的注册。

    • 而我们又 new 了一个就会导致创建了 2 次 Driver 对象,造成了资源的浪费。

    • 因此我们可以采用:Class.forName("com.mysql.jdbc.Driver");的方式完成驱动的注册。


  • (3)可以看到我们连接数据库的参数都是直接编写在 JAVA 代码中的,这很不利于程序的扩展与维护。因此我们需要把配置信息提取出来。

  • (4)扩展:在 MYSQL 驱动 5.1.6 之后,在程序运行时会在 MYSQL 提供的 jar 包的 META-INF\services\java.sql.Driver 文本中找到驱动的全限定类名,调用反射进行自动注册。但还是建议加上Class.forName("com.mysql.jdbc.Driver");

常用的 API 介绍

DriverManager 驱动管理类

  • getConnection(String url,String user, String password):获取一个连接对象

Connection 接口

  • createStatement():获取一个 Statement 对象

  • prepareStatement(String sql):获取一个 PrepareStatement 对象,传入一个 sql 进行预编译

Statement 接口

  • exexuteUpdate(sql):执行增删改语句,返回影响的行数

  • executeQuery(sql):执行查询,返回 ResultSet 对象

  • execute(sql):执行任意的 sql 命令,返回 boolean 值

PrepareStatement 接口

  • exexuteUpdate():执行增删改语句,返回影响的行数

  • executeQuery():执行查询,返回 ResultSet 对象

  • execute(sql):执行任意的 sql 命令,返回 boolean 值

  • setXxx(占位符索引,占位符的值):为占位符赋对应的值,索引从 1 开始

  • setObject(占位符索引,占位符的值):为占位符赋对应的值,索引从 1 开始

为什么相较于 Statement 接口,我们要使用 PreparedStatement 接口

  • (1)Statement 接口有个致命的缺陷,就是 SQL 注入问题

  • (2)PreparedStatement 解决了 SQL 注入的问题。

  • (3)PreparedStatement 会提前对 sql 命令进行预编译,使得 sql 命令的执行效率提升

封装 JDBCUtils

为什么要封装一个 JDBCUtils 呢

  • (1)若是每个方法中都把获取连接等重复代码全都写一遍,会造成大量代码的冗余。

  • (2)其实就是为了减少代码的冗余

一个非常简单的 JDBCUtils(附代码)

  • (1)提供一个配置文件,放在 src 目录下
url=jdbc:mysql://localhost:13306/mydb
user=root
pwd=abc123
driver=com.mysql.jdbc.Driver
  • (2)编写 JDBCUtils 工具类
import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;

public class JDBCUtils {
private static String user; // 用户名
private static String pwd; // 密码
private static String url; //url
private static String driver; // 驱动名

<span class="hljs-keyword">static</span> {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// 1. 获取配置文件信息</span>
        <span class="hljs-type">Properties</span> <span class="hljs-variable">props</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Properties</span>();
        props.load(<span class="hljs-keyword">new</span> <span class="hljs-title class_">FileInputStream</span>(<span class="hljs-string">"src\\jdbc.Properties"</span>));

        <span class="hljs-comment">// 2. 为静态变量赋值</span>
        user = props.getProperty(<span class="hljs-string">"user"</span>);
        pwd = props.getProperty(<span class="hljs-string">"pwd"</span>);
        url = props.getProperty(<span class="hljs-string">"url"</span>);
        driver = props.getProperty(<span class="hljs-string">"driver"</span>);

        <span class="hljs-comment">// 3. 注册驱动(其实不写也行)</span>
        Class.forName(driver);
    } <span class="hljs-keyword">catch</span> (Exception e) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);
    }

}

<span class="hljs-comment">/**
 * 获取连接
 * <span class="hljs-doctag">@return</span>
 * <span class="hljs-doctag">@throws</span> SQLException
 */</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Connection <span class="hljs-title function_">getConnection</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> SQLException {
    <span class="hljs-keyword">return</span> DriverManager.getConnection(url, user, pwd);
}

<span class="hljs-comment">/**
 * 关闭连接
 */</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">close</span><span class="hljs-params">(ResultSet resultSet, Statement statement, Connection connection)</span> {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">if</span> (resultSet != <span class="hljs-literal">null</span>) {
            resultSet.close();
        }
        <span class="hljs-keyword">if</span> (statement != <span class="hljs-literal">null</span>) {
            statement.close();
        }
        <span class="hljs-keyword">if</span> (connection != <span class="hljs-literal">null</span>) {
            connection.close();
        }
    } <span class="hljs-keyword">catch</span> (SQLException throwables) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(throwables);
    }
}

}

事物(数据一致性问题)

  • 只需要在一个事务的最开头,将自动提交设置为 false

  • 在事务结束位置,自动提交数据

  • 在 catch 中进行数据的回滚

举个小例子

public class Test {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception{
    <span class="hljs-comment">// 1.获取连接</span>
    <span class="hljs-type">Connection</span> <span class="hljs-variable">connection</span> <span class="hljs-operator">=</span> JDBCUtils.getConnection();

    <span class="hljs-comment">// 2. 获取到命令对象</span>
    <span class="hljs-type">String</span> <span class="hljs-variable">sql1</span> <span class="hljs-operator">=</span> <span class="hljs-string">"UPDATE account SET money = money - 100 WHERE id = 1"</span>;
    <span class="hljs-type">String</span> <span class="hljs-variable">sql2</span> <span class="hljs-operator">=</span> <span class="hljs-string">"UPDATE account SET money = money + 100 WHERE id = 2"</span>;

    <span class="hljs-type">PreparedStatement</span> <span class="hljs-variable">preparedStatement1</span> <span class="hljs-operator">=</span> connection.prepareStatement(sql1);
    <span class="hljs-type">PreparedStatement</span> <span class="hljs-variable">preparedStatement2</span> <span class="hljs-operator">=</span> connection.prepareStatement(sql2);

    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// 设置自动提交为false</span>
        connection.setAutoCommit(<span class="hljs-literal">false</span>);

        <span class="hljs-comment">// 事务开始</span>
        preparedStatement1.execute();
        <span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>/<span class="hljs-number">0</span>;
        preparedStatement2.execute();

        <span class="hljs-comment">// 事务结束,提交数据</span>
        connection.commit();
    }<span class="hljs-keyword">catch</span> (Exception e) {
        <span class="hljs-comment">// 出现异常,回滚</span>
        connection.rollback();
        e.printStackTrace();
    }
}

}

批处理

准备步骤

  • 连接数据库的 URL 需要添加上rewriteBatchedStatements参数,示例如下:

  • url=jdbc:mysql://localhost:13306/mydb?rewriteBatchedStatements=true

批处理的原理

  • addBatch():第一次时会创建一个 ArrayList<PreparedStatement>,赋值给 PreparedStatement 对象中的 batchedArgs。往后执行的每次 addBatch() 都会不断的往开始创建的 ArrayList 集合中存储一个个的 PreparedStatement。

  • executeBatch():发送一次请求给 MYSQL 进行批量的处理。

  • clearBatch():清空 batchedArgs 集合,这样之前添加的批处理对象就释放了。

使用实例

// 3. 批处理添加 1000 条数据
	for (int i = 0; i < 1000; i++) {
		// 3.1 添加参数
		preparedStatement.setInt(1,i);
	<span class="hljs-comment">// 3.2 添加到批处理中</span>
	preparedStatement<span class="hljs-selector-class">.addBatch</span>();
}
<span class="hljs-comment">// 3.3 一次性批量处理</span>
preparedStatement<span class="hljs-selector-class">.executeBatch</span>();

<span class="hljs-comment">// 3.4 清空批量处理</span>
preparedStatement<span class="hljs-selector-class">.clearBatch</span>();

数据库连接池(解决了反复连接占用大量的网络资源)

为什么要使用数据库连接池

  • (1)就目前而言,每次连接数据库都需要获取连接,并且使用后就直接关闭了与数据库的连接

  • (2)会占用大量的网络资源,如果有大量的 JAVA 程序同时获取数据库的连接,那么数据库很可能不堪重负直接崩溃。

  • (3)针对如上问题,提出了线程池的思想。

    • 在程序刚运行时,就获取一定量的数据库连接对象,将其放在一个容器当中。

    • 在使用完了该数据库连接对象时,不直接关闭该对象与数据库的连接,而是将其放回容器当中。

    • 综上就可以完成数据库连接对象的复用,减少了反复获取连接而造成的资源占用

C3P0 线程池

准备工作

  • 既然想要使用的是其他人写好的线程池,那么当然就需要获取到对应的 jar 包了
    • c3p0-0.9.2.1.jar
    • mchange-commons-java-0.2.20.jar
    • mysql-connector-java-5.1.40.jar

  • 在 scr 目录下添加一个配置文件 c3p0-config.xml,创建 ComboPooledDataSource 对象时自动读取该配置文件
<c3p0-config>
    <!-- 使用默认的配置读取数据库连接池对象 -->
    <default-config>
        <!--  连接参数 -->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:13306/mydb?useSSL=false</property>
        <property name="user">root</property>
        <property name="password">abc123</property>
    <span class="hljs-comment">&lt;!-- 连接池参数 --&gt;</span>
    <span class="hljs-comment">&lt;!--初始化申请的连接数量--&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"initialPoolSize"</span>&gt;</span>5<span class="hljs-tag">&lt;/<span class="hljs-name">property</span>&gt;</span>
    <span class="hljs-comment">&lt;!--最大的连接数量--&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"maxPoolSize"</span>&gt;</span>10<span class="hljs-tag">&lt;/<span class="hljs-name">property</span>&gt;</span>
    <span class="hljs-comment">&lt;!--最小的连接数量,当线程池空闲时,会将线程池中的连接释放到只剩下5个 --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"minPoolSize"</span>&gt;</span>5<span class="hljs-tag">&lt;/<span class="hljs-name">property</span>&gt;</span>
    <span class="hljs-comment">&lt;!--超时时间,如果超过该时间没有获取到数据库连接,就抛出异常,单位为毫秒 --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"checkoutTimeout"</span>&gt;</span>3000<span class="hljs-tag">&lt;/<span class="hljs-name">property</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">default-config</span>&gt;</span>

<!-- 注意:在创建 ComboPooledDataSource 时,可以指定配置的名称,这里做一个示例 -->
<!-- <named-config name="other_c3p0">-->
<!-- <property name="driverClass">com.mysql.jdbc.Driver</property>-->
<!-- <property name="jdbcUrl">jdbc:mysql://localhost:13306/mydb?useSSL=false</property>-->
<!-- <property name="user">root</property>-->
<!-- <property name="password">root</property>-->

<span class="hljs-comment">&lt;!--        &lt;property name="initialPoolSize"&gt;5&lt;/property&gt;--&gt;</span>
<span class="hljs-comment">&lt;!--        &lt;property name="maxPoolSize"&gt;10&lt;/property&gt;--&gt;</span>
<span class="hljs-comment">&lt;!--        &lt;property name="checkoutTimeout"&gt;3000&lt;/property&gt;--&gt;</span>
<span class="hljs-comment">&lt;!--    &lt;/named-config&gt;--&gt;</span>

</c3p0-config>

获取连接的示例

// ComboPooledDataSource dataSource = new ComboPooledDataSource("other_c3p0");
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 获取连接
    Connection connection = dataSource.getConnection();

// 该行其实就是将连接放回线程池当中,并不是切断与 MYSQL 的连接。注意!!!
connection.close();

Druid 线程池(推荐)

准备工作

  • 既然想要使用的是其他人写好的线程池,那么当然就需要获取到对应的 jar 包了
    • c3p0-0.9.2.1.jar
    • mchange-commons-java-0.2.20.jar
    • mysql-connector-java-5.1.40.jar

  • 在 scr 目录下添加一个配置文件 c3p0-config.xml,创建 ComboPooledDataSource 对象时自动读取该配置文件
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:13306/mydb?useSSL=false
username=root
password=abc123

# 初始线程数
initialSize=5
# 最大线程数
maxActive=10
# 超时时间
maxWait=3000

获取连接示例

public static void main(String[] args) throws Exception{
    // 获取到配置文件
    Properties props = new Properties();
    props.load(new FileInputStream("src\\druid.properties"));
<span class="hljs-comment">// 1. 获取数据源</span>
<span class="hljs-type">DataSource</span> <span class="hljs-variable">dataSource</span> <span class="hljs-operator">=</span> DruidDataSourceFactory.createDataSource(props);

<span class="hljs-comment">// 2. 获取连接</span>
<span class="hljs-type">Connection</span> <span class="hljs-variable">connection</span> <span class="hljs-operator">=</span> dataSource.getConnection();
System.out.println(connection);

<span class="hljs-comment">// 3. 关闭连接(其实是将连接对象放回线程池)</span>
connection.close();

}

Apache-DBUtils 工具类(ORM 对象关系映射)

ResultSet 有什么弊端?

  • (1)ResultSet 结果集与 Connection 连接是关联的,一旦 Connection 连接关闭,将会导致 ResultSet 不可用

  • (2)ResultSet 不利于数据管理(因为只能使用一次,毕竟不可能一直不关闭连接)

  • (3)ResultSet 返回的信息难以处理,只能通过 getXxx 这种方式获取,不利于维护。

那么我们应该如何解决呢?

  先想一想,ResultSet 返回的是数据表中一行行的数据,那么我们是不是可以定义一个 JAVA 类与其字段进行匹配呢,这个思想称之为:ORM 对象关系映射,这样的类称之为 JaveBean 或 pojo 或 domain

  • (1)获取到一个 ResultSet 集合,我们将其中的一行行数据分别存储到一个个对应的 JavaBean 当中,再使用一个集合(例如 ArrayList)来进行存储

  • (2)完成了上述步骤后,Connection 连接对象即便关闭了,也不会影响到 ArrayList 容器中已经存储好的数据

  • (3)而 Apache-DBUtils 就很好的帮我们封装了上述的内容,非常好用。

Apache-DBUtils 使用的准备工作

  • 准备 jar 包: commons-dbutils-1.7.jar

Apache-DBUtils 常用 API

QueryRunner 指令执行类

  • update():增删改
    image

  • query():查询
    image

ResultSetHandler 各处理器的作用

处理器的类名 作用
ArrayHandler 把结果集中的第一行数据转成对象数组
ArrayListHandler 把结果集中的每一行数据都转成一个对象数组,再存放到 List 中
BeanHandler 将结果集中的第一行数据封装到一个对应的 JavaBean 中
BeanListHandler 将结果集中的每一行数据都封装到一个对应的 JavaBean 中,存放到 List
MapHandler 将结果集中的第一行数据封装到一个 Map 里,key 是列名,value 就是对应的值
MapListHandler 将结果集中的每一行数据都封装到一个 Map 里,然后再存放到 List
ColumnListHandler 将结果集中某一列的数据存放到 List 中
KeyedHandler(name) 将结果集中的每一行数据都封装到一个 Map 里 (List<Map>),再把这些 map 再存到一个 map 里,其 key 为指定的列
ScalarHandler 将结果集第一行的某一列放到某个对象中

DAO 和增删改查(提供通用的数据库访问对象,减少代码的冗余)

什么是 DAO,为什么要使用 DAO

  • DAO 的英文全程是 Data Access Object 数据访问对象。理解为直接用于访问数据库的对象。

  • 思考:我们在编写代码时,如果需要获取多个不同数据表当中的数据,我们应该怎么办?


  传统的解决思维:不同的表,就对应不同的 JavaBean。不过每次数据的获取,都要编写不同的代码,比如编写 sql 时,A 表就写 select A 表,B 表就写 select B 表


  如上的思维的问题在于:如果有相同的功能,比如说都是想获取表中的所有数据,都是想要通过 id 取得一条数据,都是想进行一个普通的增删改,那么依然要编写不同的代码,造成代码的大量冗余,也不便于管理


  这个时候我们就可以将通用的方法,抽取为一个BasisDao,实现相对应的通用方法。再编写其余访问不同数据表的 Dao来继承 BasisDao,其中只需要编写专属于自己的特有方法。

小提示

编写工具类处理异常的小建议

  • 可以不抛出编译时异常,直接抛出运行时异常。

  • 这样的好处是:调用者可以选择捕获处理或者默认处理。