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";

        // 1. 获取到驱动对象并进行驱动注册
        Driver driver = new com.mysql.jdbc.Driver();
        DriverManager.registerDriver(driver);

        // 2. 获取连接对象
        Connection connection = DriverManager.getConnection(url, user, pwd);

        // 3. 获取到命令对象
        Statement statement = connection.createStatement();

        // 4. 对数据库进行操作
        int affectedCount = statement.executeUpdate("INSERT INTO  animal VALUES(2,'王五')");
        System.out.println("影响的语句数量:" + affectedCount);

        // 5. 关闭连接
        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; //驱动名

    static {
        try {
            // 1. 获取配置文件信息
            Properties props = new Properties();
            props.load(new FileInputStream("src\\jdbc.Properties"));

            // 2. 为静态变量赋值
            user = props.getProperty("user");
            pwd = props.getProperty("pwd");
            url = props.getProperty("url");
            driver = props.getProperty("driver");

            // 3. 注册驱动(其实不写也行)
            Class.forName(driver);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 获取连接
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, user, pwd);
    }

    /**
     * 关闭连接
     */
    public static void close(ResultSet resultSet, Statement statement, Connection connection) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException throwables) {
            throw new RuntimeException(throwables);
        }
    }
}

事物(数据一致性问题)

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

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

  • 在catch中进行数据的回滚

举个小例子

public class Test {

    public static void main(String[] args) throws Exception{
        // 1.获取连接
        Connection connection = JDBCUtils.getConnection();

        // 2. 获取到命令对象
        String sql1 = "UPDATE account SET money = money - 100 WHERE id = 1";
        String sql2 = "UPDATE account SET money = money + 100 WHERE id = 2";

        PreparedStatement preparedStatement1 = connection.prepareStatement(sql1);
        PreparedStatement preparedStatement2 = connection.prepareStatement(sql2);

        try {
            // 设置自动提交为false
            connection.setAutoCommit(false);

            // 事务开始
            preparedStatement1.execute();
            int i = 1/0;
            preparedStatement2.execute();

            // 事务结束,提交数据
            connection.commit();
        }catch (Exception e) {
            // 出现异常,回滚
            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);

		// 3.2 添加到批处理中
		preparedStatement.addBatch();
	}
    // 3.3 一次性批量处理
    preparedStatement.executeBatch();

    // 3.4 清空批量处理
    preparedStatement.clearBatch();

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

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

  • (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>

        <!-- 连接池参数 -->
        <!--初始化申请的连接数量-->
        <property name="initialPoolSize">5</property>
        <!--最大的连接数量-->
        <property name="maxPoolSize">10</property>
        <!--最小的连接数量,当线程池空闲时,会将线程池中的连接释放到只剩下5个 -->
        <property name="minPoolSize">5</property>
        <!--超时时间,如果超过该时间没有获取到数据库连接,就抛出异常,单位为毫秒 -->
        <property name="checkoutTimeout">3000</property>
    </default-config>

<!--  注意:在创建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>-->
    
    <!--        <property name="initialPoolSize">5</property>-->
    <!--        <property name="maxPoolSize">10</property>-->
    <!--        <property name="checkoutTimeout">3000</property>-->
    <!--    </named-config>-->
</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"));

    // 1. 获取数据源
    DataSource dataSource = DruidDataSourceFactory.createDataSource(props);

    // 2. 获取连接
    Connection connection = dataSource.getConnection();
    System.out.println(connection);

    // 3. 关闭连接(其实是将连接对象放回线程池)
    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,其中只需要编写专属于自己的特有方法。

小提示

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

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

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

posted @ 2022-08-17 16:44  CodeStars  阅读(83)  评论(0编辑  收藏  举报