JDBC编程

3 篇文章 0 订阅
1 篇文章 0 订阅

目录

什么是 JDBC?

JDBC 架构

常见的 JDBC 组件

分析Java连接MySQL的五种方式

方式一:将用户名和密码封装在Properties类中

方式二:在方式一的基础上,利用反射实现驱动

方式三:使用DriverManger替代driver进行统一管理

方式四:使用Class.forname自动完成注册驱动,简化代码

方式五:在方式四的基础上改进,增加配置文件,让连接MySQL更加灵活 

ResultSet

1.最基本的ResultSet 

2.可滚动的ResultSet类型

3.可更新的ResultSet 

4.可保持的ResultSet 

statement(存在SQL注入)

SQL注入实例

PreparedStatement

PreparedStatement vs Statement

预处理查询(preparedstatement防SQL注入实例)

​编辑​编辑

预处理DML

JDBCUtils开发

JDBCUtils查询

JDBCUtils DML

事务

JDBC中事务的相关方法 

JDBC中事务的相关实例

Batch批处理

​编辑

Batch批处理源码分析

JDBC传统模式开发存在的主要问题

时间和内存资源消耗巨大

有内存泄漏的风险

数据库连接池

数据库连接池原理

Java中开源的数据库连接池

C3P0连接池

C3P0操作案例

Druid连接池

Druid操作实例

C3P0和Druid连接池性能对比测试

Apache-DBUtils

DbUtils类

QueryRunner类

查询操作

更新操作

ResultSetHandler接口

演示apache-DBUtils 工具类 + druid 完成对表的crud操作

演示 apache-dbutils + druid 完成 返回的结果是单行记录(单个对象)

演示apache-dbutils + druid 完成查询结果是单行单列-返回的就是object

演示apache-dbutils + druid 完成 dml

分析 queryRunner.query方法

BasicDAO设计模式

为什么要有 BasicDAO

BasicDAO 设计模式的文件目录

BasicDao实例

JDBCUtilsByDruid代码


什么是 JDBC?

JDBC 指 Java 数据库连接,是一种标准Java应用编程接口( JAVA API),用来连接 Java 编程语言和广泛的数据库。

JDBC API 库包含下面提到的每个任务,都是与数据库相关的常用用法。

  • 制作到数据库的连接。
  • 创建 SQL 或 MySQL 语句。
  • 执行 SQL 或 MySQL 查询数据库。
  • 查看和修改所产生的记录。

从根本上来说,JDBC 是一种规范,它提供了一套完整的接口,允许便携式访问到底层数据库,因此可以用 Java 编写不同类型的可执行文件,例如:

  • Java 应用程序
  • Java Applets
  • Java Servlets
  • Java ServerPages (JSPs)
  • Enterprise JavaBeans (EJBs)

所有这些不同的可执行文件就可以使用 JDBC 驱动程序来访问数据库,这样可以方便的访问数据。

JDBC 具有 ODBC 一样的性能,允许 Java 程序包含与数据库无关的代码。

JDBC 架构

JDBC 的 API 支持两层和三层处理模式进行数据库访问,但一般的 JDBC 架构由两层处理模式组成:

  • JDBC API: 提供了应用程序对 JDBC 管理器的连接。

  • JDBC Driver API: 提供了 JDBC 管理器对驱动程序连接。

JDBC API 使用驱动程序管理器和数据库特定的驱动程序来提供异构(heterogeneous)数据库的透明连接。

JDBC 驱动程序管理器可确保正确的驱动程序来访问每个数据源。该驱动程序管理器能够支持连接到多个异构数据库的多个并发的驱动程序。

以下是结构图,其中显示了驱动程序管理器相对于在 JDBC 驱动程序和 Java 应用程序所处的位置。

常见的 JDBC 组件

JDBC 的 API 提供了以下接口和类:

DriverManager :这个类管理一系列数据库驱动程序。匹配连接使用通信子协议从 JAVA 应用程序中请求合适的数据库驱动程序。识别 JDBC 下某个子协议的第一驱动程序将被用于建立数据库连接。

Driver : 这个接口处理与数据库服务器的通信。你将很少直接与驱动程序互动。相反,你使用 DriverManager 中的对象,它管理此类型的对象。它也抽象与驱动程序对象工作相关的详细信息。

Connection : 此接口具有接触数据库的所有方法。该连接对象表示通信上下文,即,所有与数据库的通信仅通过这个连接对象进行。

Statement : 使用创建于这个接口的对象将 SQL 语句提交到数据库。除了执行存储过程以外,一些派生的接口也接受参数。

ResultSet : 在你使用语句对象执行 SQL 查询后,这些对象保存从数据获得的数据。它作为一个迭代器,让您可以通过它的数据来移动。

SQLException : 这个类处理发生在数据库应用程序的任何错误。

分析Java连接MySQL的五种方式

方式一:将用户名和密码封装在Properties类中

    @Test
    public void connect01() throws SQLException {
        Driver driver = new Driver(); //创建driver对象
        String url = "jdbc:mysql://localhost:3306/db03";
        //将 用户名和密码放入到Properties 对象
        Properties properties = new Properties();
        //说明 user 和 password 是规定好,后面的值根据实际情况写
        properties.setProperty("user", "root");// 用户
        properties.setProperty("password", "ztc"); //密码
        Connection connect = driver.connect(url, properties);
        System.out.println(connect);
    }

方式二:在方式一的基础上,利用反射实现驱动

 @Test
    public void connect02() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException {
        //使用反射加载Driver类 , 动态加载,更加的灵活,减少依赖性
        Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
        Driver driver = (Driver)aClass.newInstance();

        String url = "jdbc:mysql://localhost:3306/db03";
        //将 用户名和密码放入到Properties 对象
        Properties properties = new Properties();
        //说明 user 和 password 是规定好,后面的值根据实际情况写
        properties.setProperty("user", "root");// 用户
        properties.setProperty("password", "ztc"); //密码

        Connection connect = driver.connect(url, properties);
        System.out.println("方式2=" + connect);

    }

方式三:使用DriverManger替代driver进行统一管理

//方式3 使用DriverManager 替代 driver 进行统一管理
    @Test
    public void connect03() throws IllegalAccessException, InstantiationException, ClassNotFoundException, SQLException {

        //使用反射加载Driver
        Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
        Driver driver = (Driver) aClass.newInstance();

        //创建url 和 user 和 password
        String url = "jdbc:mysql://localhost:3306/db03";
        String user = "root";
        String password = "ztc";

        DriverManager.registerDriver(driver);//注册Driver驱动

        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println("第三种方式=" + connection);
    }

方式四:使用Class.forname自动完成注册驱动,简化代码

//方式4: 使用Class.forName 自动完成注册驱动,简化代码
    //这种方式获取连接是使用的最多,推荐使用
    @Test
    public void connect04() throws ClassNotFoundException, SQLException {
        //使用反射加载了 Driver类
        //在加载 Driver类时,完成注册
        /*
            源码: 1. 静态代码块,在类加载时,会执行一次.
            2. DriverManager.registerDriver(new Driver());
            3. 因此注册driver的工作已经完成
            static {
                try {
                    DriverManager.registerDriver(new Driver());
                } catch (SQLException var1) {
                    throw new RuntimeException("Can't register driver!");
                }
            }
         */
        Class.forName("com.mysql.jdbc.Driver");

        //创建url 和 user 和 password
        String url = "jdbc:mysql://localhost:3306/db03";
        String user = "root";
        String password = "ztc";
        Connection connection = DriverManager.getConnection(url, user, password);

        System.out.println("第4种方式~ " + connection);

    }

方式五:在方式四的基础上改进,增加配置文件,让连接MySQL更加灵活 

//方式5: 在方式4的基础上改进,增加配置文件,让连接mysql更加灵活
    @Test
    public void connect05() throws IOException, ClassNotFoundException, SQLException {

        //通过Properties对象获取配置文件的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\myjdbc\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        Class.forName(driver);//建议写上

        Connection connection = DriverManager.getConnection(url, user, password);

        System.out.println("方式5 " + connection);


    }

ResultSet

结果集(ResultSet)是数据中查询结果返回的一种对象,可以说结果集是一个存储查询结果的对象,但是结果集并不仅仅具有存储的功能,他同时还具有操纵数据的功能,可能完成对数据的更新等。

结果集从其使用的特点上可以分为四类,这四类的结果集的所具备的特点都是和Statement语句的创建有关,因为结果集是通过Statement语句执行后产生的,所以可以说,结果集具备何种特点,完全决定于Statement,当然我是说下面要将的四个特点,在Statement创建时包括三种类型。首先是无参数类型的,他对应的就是下面要介绍的基本的ResultSet对应的Statement。下面的代码中用到的Connection并没有对其初始化,变量conn代表的就是Connection对应的对象。SqlStr代表的是响应的SQL语句。

1.最基本的ResultSet 


之所以说是最基本的ResultSet是因为,这个ResultSet他起到的作用就是完成了查询结果的存储功能,而且只能读去一次,不能够来回的滚动读取。这种结果集的创建方式如下: 

Statement st = conn.CreateStatement 
ResultSet rs = Statement.excuteQuery(sqlStr); 


由于这种结果集不支持滚动的读取功能所以,如果获得这样一个结果集,只能使用它里面的next()方法,逐个的读去数据。 

2.可滚动的ResultSet类型

这个类型支持前后滚动取得记录next()、previous(),回到第一行first(),同时还支持要取ResultSet中的第几行 absolute(int n),以及移动到相对当前行的第几行relative(int n),要实现这样的ResultSet在创建Statement时用如下的方法。 

Statement st = conn. createStatement (int resultSetType, int resultSetConcurrency) 
ResultSet rs = st.executeQuery(sqlStr) 


其中两个参数的意义是: 
  resultSetType 是设置 ResultSet 对象的类型可滚动,或者是不可滚动。取值如下: 
    ResultSet.TYPE_FORWARD_ONLY 只能向前滚动 
    ResultSet.TYPE_SCROLL_INSENSITIVE 和 Result.TYPE_SCROLL_SENSITIVE 这两个方法都能够实现任意的前后滚动,使用各种移动的 ResultSet 指针的方法。二者的区别在于前者对于修改不敏感,而后者对于修改敏感。 


  resultSetConcurency 是设置 ResultSet 对象能够修改的,取值如下: 
    ResultSet.CONCUR_READ_ONLY 设置为只读类型的参数。 
    ResultSet.CONCUR_UPDATABLE 设置为可修改类型的参数。 
  所以如果只是想要可以滚动的类型的 Result 只要把 Statement 如下赋值就行了。 

  Statement st = conn.createStatement(Result.TYPE_SCROLL_INSENITIVE, 
                          ResultSet.CONCUR_READ_ONLY); 
  ResultSet rs = st.excuteQuery(sqlStr) ; 

  用这个 Statement 执行的查询语句得到的就是可滚动的 ResultSet 。 

3.可更新的ResultSet 

这样的ResultSet对象可以完成对数据库中表的修改,但是ResultSet只是相当于数据库中表的视图,所以并不是所有的ResultSet只要设置了可更新就能够完成更新的,能够完成更新的ResultSet的SQL语句必须要具备如下的属性: 

  •     只引用了单个表。 
  •     不含有join或者group by子句。 
  •     那些列中要包含主关键字。 

    具有上述条件的,可更新的ResultSet可以完成对数据的修改,可更新的结果集的创建方法是: 

  Statement st = createstatement(Result.TYPE_SCROLL_INSENSITIVE,Result.CONCUR_UPDATABLE) 

4.可保持的ResultSet 

正常情况下如果使用Statement执行完一个查询,又去执行另一个查询时这时候第一个查询的结果集就会被关闭,也就是说,所有的Statement的查询对应的结果集是一个,如果调用Connection的commit()方法也会关闭结果集。可保持性就是指当ResultSet的结果被提交时,是被关闭还是不被关闭。JDBC2.0和1.0提供的都是提交后ResultSet就会被关闭。不过在JDBC3.0中,我们可以设置ResultSet是否关闭。要完成这样的ResultSet的对象的创建,要使用的Statement的创建要具有三个参数,这个Statement的创建方式也就是,我所说的 Statement的第三种创建方式。 

  当使用ResultSet的时候,当查询出来的数据集记录很多,有一千万条的时候,那rs所指的对象是否会占用很多内存,如果记录过多,那程序会不会把系统的内存用光呢 ?

  不会的,ResultSet表面看起来是一个记录集,其实这个对象中只是记录了结果集的相关信息,具体的记录并没有存放在对象中,具体的记录内容知道你通过next方法提取的时候,再通过相关的getXXXXX方法提取字段内容的时候才能从数据库中得到,这些并不会占用内存,具体消耗内存是由于你将记录集中的数据提取出来加入到你自己的集合中的时候才会发生,如果你没有使用集合记录所有的记录就不会发生消耗内存厉害的情况。

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
import java.util.Properties;

/**
 * @Author: ztc
 * @Date: 2022/9/23 16:12
 */
public class ResultSet_ {
    public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\myjdbc\\mysql01.properties"));
        String url = properties.getProperty("url");
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");

        Class.forName(driver);

        Connection connection = DriverManager.getConnection(url, user, password);

        //得到statement
        Statement statement = connection.createStatement();
        //组织SQL
        String sql = "select name, times from news";
        java.sql.ResultSet resultSet = statement.executeQuery(sql);
        while (resultSet.next()){
            String name = resultSet.getString(1);
            Date date = resultSet.getDate(2);
            System.out.println(name + "\t" + date + "\t");
        }
        //关闭连接
        statement.close();
        connection.close();
    }
}

statement(存在SQL注入)

Sql 注入攻击是通过将恶意的 Sql 查询或添加语句插入到应用的输入参数中,再在后台 Sql 服务器上解析执行进行的攻击,它目前黑客对数据库进行攻击的最常用手段之一。

一般没有进行SQL语句参数化的登录语句是这样的

Select * From 用户表 Where UserName=xxx and Password=xxx


然后判断返回的行数,如果有返回行,证明账号和密码是正确的,即登录成功,而这样的语句的话,就很容易被注入代码,也就是在登陆的SQL语句中添加一段代码,例如直接在密码框输入【’or 1=1–】,那么它的登录语句就会变成

Select * From 用户表 Where UserName=xxx and Password=xxx or 1=1--


or是或者的意思,也就是Password=xxx的时候可以登录,也可以是1=1的时候可以登录,要知道,1永远等于1,所以登录条件永远成立,所以永远可以登录。


SQL注入实例

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
import java.util.Scanner;

/**
 * @Author: ztc
 * @Date: 2022/9/23 17:39
 */
public class statement_ {
    public static void main(String[] args) throws Exception {

        Scanner scanner = new Scanner(System.in);

        //让用户输入管理员名和密码
        System.out.print("请输入管理员的名字: ");  //next(): 当接收到 空格或者 '就是表示结束
        String admin_name = scanner.nextLine(); //说明,如果希望看到SQL注入,这里需要用nextLine
        System.out.print("请输入管理员的密码: ");
        String admin_pwd = scanner.nextLine();

        //通过Properties对象获取配置文件的信息


        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\myjdbc\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        //1. 注册驱动
        Class.forName(driver);//建议写上

        //2. 得到连接
        Connection connection = DriverManager.getConnection(url, user, password);

        //3. 得到Statement
        Statement statement = connection.createStatement();
        //4. 组织SqL
        String sql = "select name , pwd  from admin where name ='"
                + admin_name + "' and pwd = '" + admin_pwd + "'";
        ResultSet resultSet =  statement.executeQuery(sql);
        if (resultSet.next()) { //如果查询到一条记录,则说明该管理存在
            System.out.println("恭喜, 登录成功");
        } else {
            System.out.println("对不起,登录失败");
        }

        //关闭连接
        resultSet.close();
        statement.close();
        connection.close();

    }
}

 

 

PreparedStatement

  • 可以通过调用 Connection 对象的 prepareStatement(String sql) 方法获取 PreparedStatement 对象
  • PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句
  • PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示(?在SQL中表示占位符),调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值

PreparedStatement vs Statement

  • 代码的可读性和可维护性。
  • PreparedStatement 能最大可能提高性能:
  1. DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
  2. 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。
  3. (语法检查,语义检查,翻译成二进制命令,缓存)
  • PreparedStatement 可以防止 SQL 注入

预处理查询(preparedstatement防SQL注入实例)

其中执行 select 语句使用 executeQuery

import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
/**
 * @Author: ztc
 * @Date: 2022/9/24 8:24
 */
public class PreparedStatement_ {
    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(System.in);

        //让用户输入管理员名和密码
        System.out.print("请输入管理员的名字: ");  //next(): 当接收到 空格或者 '就是表示结束
        String admin_name = scanner.nextLine(); // 老师说明,如果希望看到SQL注入,这里需要用nextLine
        System.out.print("请输入管理员的密码: ");
        String admin_pwd = scanner.nextLine();

        //通过Properties对象获取配置文件的信息

        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\myjdbc\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        //1. 注册驱动
        Class.forName(driver);//建议写上

        //2. 得到连接
        Connection connection = DriverManager.getConnection(url, user, password);

        //3. 得到PreparedStatement
        //3.1 组织SqL , Sql 语句的 ? 就相当于占位符
        String sql = "select name , pwd  from admin where name =? and pwd = ?";
        //3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //3.3 给 ? 赋值
        preparedStatement.setString(1, admin_name);
        preparedStatement.setString(2, admin_pwd);

        //4. 执行 select 语句使用  executeQuery
        //   如果执行的是 dml(update, insert ,delete) executeUpdate()
        //   这里执行 executeQuery ,不要在写 sql

        ResultSet resultSet = preparedStatement.executeQuery();
        if (resultSet.next()) { //如果查询到一条记录,则说明该管理存在
            System.out.println("恭喜, 登录成功");
        } else {
            System.out.println("对不起,登录失败");
        }

        //关闭连接
        resultSet.close();
        preparedStatement.close();
        connection.close();

    }
}


预处理DML

执行 dml 语句使用  executeUpdate

package myjdbc;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.Properties;
import java.util.Scanner;
/**
 * @Author: ztc
 * @Date: 2022/9/24 8:46
 */
public class PreparedStatementDML_ {
    public static void main(String[] args) throws Exception{
        Scanner scanner = new Scanner(System.in);

        //让用户输入管理员名和密码
        System.out.print("请输入添加管理员的名字: ");  //next(): 当接收到 空格或者 '就是表示结束
        String admin_name = scanner.nextLine(); // 老师说明,如果希望看到SQL注入,这里需要用nextLine
        System.out.print("请输入管理员的新密码: ");
        String admin_pwd = scanner.nextLine();

        //通过Properties对象获取配置文件的信息

        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\myjdbc\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        //1. 注册驱动
        Class.forName(driver);//建议写上

        //2. 得到连接
        Connection connection = DriverManager.getConnection(url, user, password);

        //3. 得到PreparedStatement
        //3.1 组织SqL , Sql 语句的 ? 就相当于占位符
        //添加记录
        String sql = "insert into admin values(?, ?)";
        //String sql = "update admin set pwd = ? where name = ?";
//        String sql = "delete from  admin where name = ?";
        //3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //3.3 给 ? 赋值
        preparedStatement.setString(1, admin_name);

        preparedStatement.setString(2, admin_pwd);

        //4. 执行 dml 语句使用  executeUpdate
        int rows = preparedStatement.executeUpdate();
        System.out.println(rows > 0 ? "执行成功" : "执行失败");
        //关闭连接
        preparedStatement.close();
        connection.close();

    }
}

JDBCUtils开发

JDBCUtils是为简化代码而出现的一个工具类

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;

/**
 * @Author: ztc
 * @Date: 2022/9/24 9:39
 */
public class JDBCUtils_ {
    private static String user;
    private static String password;
    private static String url;
    private static String driver;
    //在static代码块去初始化
    static {
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("src:\\myjdbc\\mysql.propertiees"));
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            url = properties.getProperty("url");
            driver = properties.getProperty("driver");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //连接数据库, 返回Connection
    public static Connection getConnection(){
        try {
            return  DriverManager.getConnection(url,user,password);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    //关闭相关资源
    /*
        1. ResultSet 结果集
        2. Statement 或者 PreparedStatement
        3. Connection
        4. 如果需要关闭资源,就传入对象,否则传入 null
     */
    public static void close(ResultSet set, Statement statement,Connection connection){
        try {
            if (set != null) {
                set.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

JDBCUtils查询

    @Test
    public void testSelect(){
        //得到连接
        Connection connection = null;
        //组织一个SQL
        String sql = "select * from actor";
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        //创建preparedStatement对象
        try {
            connection = JDBCUtils_.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                String sex = resultSet.getString("sex");
                String phone = resultSet.getString("phone");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils_.close(resultSet,preparedStatement,connection);
        }
    }

JDBCUtils DML

    @Test
    public void testDML() {//insert , update, delete

        //1. 得到连接
        Connection connection = null;
        //2. 组织一个sql
        String sql = "update actor set name = ? where id = ?";

        PreparedStatement preparedStatement = null;
        //3. 创建PreparedStatement 对象
        try {
            connection = JDBCUtils_.getConnection();

            preparedStatement = connection.prepareStatement(sql);
            //给占位符赋值
            preparedStatement.setString(1, "周星驰");
            preparedStatement.setInt(2, 1);
            //执行
            preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCUtils_.close(null, preparedStatement, connection);
        }
    }

事务

事务具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。

  • 原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做;
  • 一致性(Consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的;
  • 隔离性(Isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰;
  • 持久性(Durability):持久性也称永久性(Permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。

对于其中的隔离性,数据库服务器有时会为了提供更好的处理并发能力,会牺牲一定的隔离性。这也是正确性和性能之间的对抗。如MySQL默认的隔离级别为READ COMMITTED,即读提交,可以读取其他事务已经提交的内容,可以避免脏读,但是无法避免重复度和幻读。

JDBC中事务的相关方法 

JDBC中事务的相关实例

    @Test
    public void useTransaction() {

        //操作转账的业务
        //1. 得到连接
        Connection connection = null;
        //2. 组织一个sql
        String sql = "update account set balance = balance - 100 where id = 1";
        String sql2 = "update account set balance = balance + 100 where id = 2";
        PreparedStatement preparedStatement = null;
        //3. 创建PreparedStatement 对象
        try {
            connection = JDBCUtils.getConnection(); // 在默认情况下,connection是默认自动提交
            //将 connection 设置为不自动提交
            connection.setAutoCommit(false); //开启了事务
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.executeUpdate(); // 执行第1条sql

            int i = 1 / 0; //抛出异常
            preparedStatement = connection.prepareStatement(sql2);
            preparedStatement.executeUpdate(); // 执行第3条sql

            //这里提交事务
            connection.commit();

        } catch (SQLException e) {
            //这里我们可以进行回滚,即撤销执行的SQL
            //默认回滚到事务开始的状态.
            System.out.println("执行发生了异常,撤销执行的sql");
            try {
                connection.rollback();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCUtils.close(null, preparedStatement, connection);
        }
    }

Batch批处理

使用之前,需要在连接数据库的url加入 ?rewriteBatchedStatements=true 设置支持批量处理

package com.hspedu.jdbc.batch_;

import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;

/**
 * @Author: ztc
 * @Date: 2022/9/24 12:31
 */
public class Batch_ {

    //传统方法,添加5000条数据到admin2

    @Test
    public void noBatch() throws Exception {

        Connection connection = JDBCUtils.getConnection();
        String sql = "insert into admin2 values(null, ?, ?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        System.out.println("开始执行");
        long start = System.currentTimeMillis();//开始时间
        for (int i = 0; i < 5000; i++) {//5000执行
            preparedStatement.setString(1, "jack" + i);
            preparedStatement.setString(2, "666");
            preparedStatement.executeUpdate();
        }
        long end = System.currentTimeMillis();
        System.out.println("传统的方式 耗时=" + (end - start));//传统的方式 耗时=10702
        //关闭连接
        JDBCUtils.close(null, preparedStatement, connection);
    }

    //使用批量方式添加数据
    @Test
    public void batch() throws Exception {

        Connection connection = JDBCUtils.getConnection();
        String sql = "insert into admin2 values(null, ?, ?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        System.out.println("开始执行");
        long start = System.currentTimeMillis();//开始时间
        for (int i = 0; i < 5000; i++) {//5000执行
            preparedStatement.setString(1, "jack" + i);
            preparedStatement.setString(2, "666");
            
            preparedStatement.addBatch();
            //当有1000条记录时,在批量执行
            if((i + 1) % 1000 == 0) {//满1000条sql
                preparedStatement.executeBatch();
                //清空一把
                preparedStatement.clearBatch();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("批量方式 耗时=" + (end - start));//批量方式 耗时=108
        //关闭连接
        JDBCUtils.close(null, preparedStatement, connection);
    }
}

Batch批处理源码分析

/*
//1. //第一就创建 ArrayList - elementData => Object[]
//2. elementData => Object[] 就会存放我们预处理的sql语句
//3. 当elementData满后,就按照1.5扩容
//4. 当添加到指定的值后,就executeBatch
//5. 批量处理会减少我们发送sql语句的网络开销,而且减少编译次数,因此效率提高
public void addBatch() throws SQLException {
    synchronized(this.checkClosed().getConnectionMutex()) {
        if (this.batchedArgs == null) {

            this.batchedArgs = new ArrayList();
        }

        for(int i = 0; i < this.parameterValues.length; ++i) {
            this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
        }

        this.batchedArgs.add(new PreparedStatement.BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull));
    }
}

 */

JDBC传统模式开发存在的主要问题

时间和内存资源消耗巨大

普通的JDBC数据库连接使用DriverManager来获取,每次向数据库建立连接的时候都要将Connection加载到内存中,再根据JDBC代码(或配置文件)中的用户名和密码进行验证其正确性。这一过程一般会花费0.05~1s,一旦需要数据库连接的时候就必须向数据库请求一个,执行完后再断开连接。显然,如果同一个数据库在同一时间有数十人甚至上百人请求连接势必会占用大量的系统资源,严重的会导致服务器崩溃。

有内存泄漏的风险

因为每一次数据库连接使用完后都需要断开连接,但如果程序出现异常致使连接未能及时关闭,这样就可能导致内存泄漏,最终只能以重启数据库的方法来解决;

另外使用传统JDBC模式开发不能控制需要创建的连接数,系统一般会将资源大量分出给连接以防止资源不够用,如果连接数超出一定数量也会有极大的可能导致内存泄漏。

数据库连接池

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

数据库连接池原理

连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

Java中开源的数据库连接池

  1. C3P0:是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate  一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。
  2. Proxool:是一个Java SQL Driver驱动程序,提供了对选择的其它类型的驱动程序的连接池封装。可以非常简单的移植到现存的代码中,完全可配置,快速、成熟、健壮。可以透明地为现存的JDBC驱动程序增加连接池功能。
  3. DDConnectionBroker:是一个简单、轻量级的数据库连接池。
  4. Jakarta DBCP:DBCP是一个依赖Jakartacommons-pool对象池机制的数据库连接池。DBCP可以直接的在应用程序中使用。
  5. DBPool:是一个高效、易配置的数据库连接池。它除了支持连接池应有的功能之外,还包括了一个对象池,使用户能够开发一个满足自己需求的数据库连接池。
  6. XAPool:是一个XA数据库连接池。它实现了javax.sql.XADataSource并提供了连接池工具。
  7. Primrose:是一个Java开发的数据库连接池。当前支持的容器包括Tomcat4&5、Resin3与JBoss3。它同样也有一个独立的版本,可以在应用程序中使用而不必运行在容器中。Primrose通过一个WEB接口来控制SQL处理的追踪、配置,以及动态池管理。在重负荷的情况下可进行连接请求队列处理。
  8. SmartPool:是一个连接池组件,它模仿应用服务器对象池的特性。SmartPool能够解决一些临界问题如连接泄漏(connection leaks)、连接阻塞、打开的JDBC对象(如Statements、PreparedStatements)等。

  9. MiniConnectionPoolManager:是一个轻量级数据库连接池。它只需要Java1.5(或更高)并且没有依赖第三方包。

  10. BoneCP:是一个快速、开源的数据库连接池。帮用户管理数据连接,让应用程序能更快速地访问数据库。比C3P0/DBCP连接池速度快25倍。

  11. Druid:(阿里开发)Druid不仅是一个数据库连接池,还包含一个ProxyDriver、一系列内置的JDBC组件库、一个SQL Parser。

    支持所有JDBC兼容的数据库,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等。   Druid提供了MySql、Oracle、Postgresql、SQL-92的SQL的完整支持,这是一个手写的高性能SQL Parser,支持Visitor模式,使得分析SQL的抽象语法树很方便。   简单SQL语句用时10微秒以内,复杂SQL用时30微秒。    通过Druid提供的SQL Parser可以在JDBC层拦截SQL做相应处理,比如说分库分表、审计等。Druid防御SQL注入攻击的WallFilter,就是通过Druid的SQL Parser分析语义实现的。

C3P0连接池

C3P0是一个开源的JDBC连接池,它实现了数据源与JNDI绑定,支持JDBC3规范和实现了JDBC2的标准扩展说明的Connection和Statement池的DataSources对象。

  即将用于连接数据库的连接整合在一起形成一个随取随用的数据库连接池(Connection pool)。

配置xml文件

<c3p0-config>

  <named-config name="ztc">
<!-- 驱动类 -->
  <property name="driverClass">com.mysql.jdbc.Driver</property>
  <!-- url-->
  	<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/db03</property>
  <!-- 用户名 -->
  		<property name="user">root</property>
  		<!-- 密码 -->
  	<property name="password">******</property>
  	<!-- 每次增长的连接数-->
    <property name="acquireIncrement">5</property>
    <!-- 初始的连接数 -->
    <property name="initialPoolSize">10</property>
    <!-- 最小连接数 -->
    <property name="minPoolSize">5</property>
   <!-- 最大连接数 -->
    <property name="maxPoolSize">10</property>

	<!-- 可连接的最多的命令对象数 -->
    <property name="maxStatements">5</property> 
    
    <!-- 每个连接对象可连接的最多的命令对象数 -->
    <property name="maxStatementsPerConnection">2</property>
  </named-config>
</c3p0-config>

C3P0操作案例

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.jupiter.api.Test;

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

/**
 * @Author: ztc
 * @Date: 2022/9/24 14:27
 */
public class C3P0_ {
    @Test
    public void testC3P0_02() throws SQLException {
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("ztc");

        //测试5000次连接mysql
        long start = System.currentTimeMillis();
        System.out.println("开始执行");
//        System.out.println("连接成功");
        for (int i = 0; i < 5000; i++){
            Connection connection = comboPooledDataSource.getConnection();
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("c3p0耗时为:"+(end - start));
    }
}

Druid连接池

Druid为监控而生的数据库连接池,它是阿里巴巴开源平台上的一个项目。Druid是Java语言中最好的数据库连接池,Druid能够提供强大的监控和扩展功能.它可以替换DBCP和C3P0连接池。Druid提供了一个高效、功能强大、可扩展性好的数据库连接池。

配置properties文件

#key=value
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db03?rewriteBatchedStatements=true
username=root
password=*******
#initial connection Size
initialSize=10
#min idle connecton size
minIdle=5
#max active connection size
maxActive=20
#max wait time (5000 mil seconds)
maxWait=5000

Druid操作实例

package JDBC_;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.jupiter.api.Test;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;

/**
 * @Author: ztc
 * @Date: 2022/9/24 15:32
 */
public class Druid_ {
    @Test
    public void testDruid() throws Exception{
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\druid.properties"));

        //创建一个指定参数的数据库连接池,Druid连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
        long start = System.currentTimeMillis();
        for (int i = 0; i<500000; i++){
            Connection connection = dataSource.getConnection();
//            System.out.println(connection.getClass());
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("druid连接池 操作500000 耗时=" + (end - start));
    }
}

C3P0和Druid连接池性能对比测试

均进行500000次数据库连接

C3P0测试如下:

@Test
    public void testC3P0_02() throws SQLException {
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("ztc");

        //测试5000次连接mysql
        long start = System.currentTimeMillis();
        System.out.println("开始执行");
//        System.out.println("连接成功");
        for (int i = 0; i < 500000; i++){
            Connection connection = comboPooledDataSource.getConnection();
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("c3p0耗时为:"+(end - start));
    }
}

 Druid测试如下:

 @Test
    public void testDruid() throws Exception{
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\druid.properties"));

        //创建一个指定参数的数据库连接池,Druid连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
        long start = System.currentTimeMillis();
        for (int i = 0; i<500000; i++){
            Connection connection = dataSource.getConnection();
//            System.out.println(connection.getClass());
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("druid连接池 操作500000 耗时=" + (end - start));
    }

可明显看出,Druid的耗时性能比C3P0强。 

Apache-DBUtils

commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,创建连接、结果集封装、释放资源,同时也不会影响程序的性能。

API介绍:

  • org.apache.commons.dbutils.QueryRunner --- 核心
  • org.apache.commons.dbutils.ResultSetHandler --- 结果集封装器
  • org.apache.commons.dbutils.DbUtils --- 工具类

DbUtils类

DbUtils :提供如加载驱动、关闭连接、事务提交、回滚等常规工作的工具类,里面的所有方法都是静态的。主要方法如下:

DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和ResultSet。

  public static void close(…) throws java.sql.SQLException

这一类"quietly"方法不仅能在Connection、Statement和ResultSet为NULL情况下避免关闭,还能隐藏一些在程序中抛出的SQLException。

  public static void closeQuietly(…)

用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。

  public static void commitAndCloseQuietly(Connection conn)

装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。

  public static boolean loadDriver(java.lang.String driverClassName)

QueryRunner类

  • 该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。

  • QueryRunner类提供了两个构造方法:

    • 默认的构造方法:QueryRunner()

    • 需要一个 javax.sql.DataSource 来作参数的构造方法:QueryRunner(DataSource ds)

批处理

  batch(Connection conn, String sql, Object[][] params)  // 传递连接批处理
  batch(String sql, Object[][] params)  // 不传递连接批处理

查询操作

  public Object query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params)
  public Object query(String sql, ResultSetHandler<T> rsh, Object... params) 

更新操作

  public int update(Connection conn, String sql, Object... params)
  public int update(String sql, Object... params)

ResultSetHandler接口

该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。

ResultSetHandler 接口的实现类(构造方法不唯一,在这里只用最常见的构造方法):

  • ArrayHandler():把结果集中的第一行数据转成对象数组(存入Object[])。

  • ArrayListHandler():把结果集中的每一行数据都转成一个对象数组,再存放到List中。

  • BeanHandler(Class<T> type):将结果集中的第一行数据封装到一个对应的JavaBean实例中。

  • BeanListHandler(Class<T> type):将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。

    Parameters:

    type - The Class that objects returned from handle() are created from.

  • ColumnListHandler(String columnName/int columnIndex):将结果集中某一列的数据存放到List中。

  • MapHandler():将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。

  • MapListHandler():将结果集中的每一行数据都封装到一个Map里,然后再将所有的Map存放到List中。

  • KeyedHandler(String columnName):将结果集每一行数据保存到一个“小”map中,key为列名,value该列的值,再将所有“小”map对象保存到一个“大”map中 , “大”map中的key为指定列,value为“小”map对象

  • ScalarHandler(int columnIndex):通常用来保存只有一行一列的结果集。

演示apache-DBUtils 工具类 + druid 完成对表的crud操作

@Test
        public void testQueryMany() throws SQLException { //返回结果是多行的情况

            //1. 得到 连接 (druid)
            Connection connection = JDBCUtilsByDruid.getConnection();
            //2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
            //3. 创建 QueryRunner
            QueryRunner queryRunner = new QueryRunner();
            //4. 就可以执行相关的方法,返回ArrayList 结果集
            //String sql = "select * from actor where id >= ?";
            //   注意: sql 语句也可以查询部分列
            String sql = "select id, name from actor where id >= ?";
            //(1) query 方法就是执行sql 语句,得到resultset ---封装到 --> ArrayList 集合中
            //(2) 返回集合
            //(3) connection: 连接
            //(4) sql : 执行的sql语句
            //(5) new BeanListHandler<>(Actor.class): 在将resultset -> Actor 对象 -> 封装到 ArrayList
            //    底层使用反射机制 去获取Actor 类的属性,然后进行封装
            //(6) 1 就是给 sql 语句中的? 赋值,可以有多个值,因为是可变参数Object... params
            //(7) 底层得到的resultset ,会在query 关闭, 关闭PreparedStatment
            List<Actor> list =
                    queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
            System.out.println("输出集合的信息");
            for (Actor actor : list) {
                System.out.print(actor);
            }

            //释放资源
            JDBCUtilsByDruid.close(null, null, connection);

        }

演示 apache-dbutils + druid 完成 返回的结果是单行记录(单个对象)

        @Test
        public void testQuerySingle() throws SQLException {

            //1. 得到 连接 (druid)
            Connection connection = JDBCUtilsByDruid.getConnection();
            //2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
            //3. 创建 QueryRunner
            QueryRunner queryRunner = new QueryRunner();
            //4. 就可以执行相关的方法,返回单个对象
            String sql = "select * from actor where id = ?";
            // 老韩解读
            // 因为我们返回的单行记录<--->单个对象 , 使用的Hander 是 BeanHandler
            Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 1);
            System.out.println(actor);

            // 释放资源
            JDBCUtilsByDruid.close(null, null, connection);

        }

演示apache-dbutils + druid 完成查询结果是单行单列-返回的就是object

        @Test
        public void testScalar() throws SQLException {

            //1. 得到 连接 (druid)
            Connection connection = JDBCUtilsByDruid.getConnection();
            //2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
            //3. 创建 QueryRunner
            QueryRunner queryRunner = new QueryRunner();

            //4. 就可以执行相关的方法,返回单行单列 , 返回的就是Object
            String sql = "select name from actor where id = ?";
            //老师解读: 因为返回的是一个对象, 使用的handler 就是 ScalarHandler
            Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 1);
            System.out.println(obj);

            // 释放资源
            JDBCUtilsByDruid.close(null, null, connection);
        }

演示apache-dbutils + druid 完成 dml

        @Test
        public void testDML() throws SQLException {

            //1. 得到 连接 (druid)
            Connection connection = JDBCUtilsByDruid.getConnection();
            //2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
            //3. 创建 QueryRunner
            QueryRunner queryRunner = new QueryRunner();

            //4. 这里组织sql 完成 update, insert delete
            //String sql = "update actor set name = ? where id = ?";
            //String sql = "insert into actor values(null, ?, ?, ?, ?)";
            String sql = "delete from actor where id = ?";

            //老韩解读
            //(1) 执行dml 操作是 queryRunner.update()
            //(2) 返回的值是受影响的行数 (affected: 受影响)
            //int affectedRow = queryRunner.update(connection, sql, "林青霞", "女", "1966-10-10", "116");
            int affectedRow = queryRunner.update(connection, sql, 1000 );
            System.out.println(affectedRow > 0 ? "执行成功" : "执行没有影响到表");

            // 释放资源
            JDBCUtilsByDruid.close(null, null, connection);

        }

        @Test
        public void testDML() throws SQLException {

            //1. 得到 连接 (druid)
            Connection connection = JDBCUtilsByDruid.getConnection();
            //2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
            //3. 创建 QueryRunner
            QueryRunner queryRunner = new QueryRunner();

            //4. 这里组织sql 完成 update, insert delete
            //String sql = "update actor set name = ? where id = ?";
            //String sql = "insert into actor values(null, ?, ?, ?, ?)";
            String sql = "delete from actor where id = ?";

            //老韩解读
            //(1) 执行dml 操作是 queryRunner.update()
            //(2) 返回的值是受影响的行数 (affected: 受影响)
            //int affectedRow = queryRunner.update(connection, sql, "林青霞", "女", "1966-10-10", "116");
            int affectedRow = queryRunner.update(connection, sql, 66 );
            System.out.println(affectedRow > 0 ? "执行成功" : "执行没有影响到表");

            // 释放资源
            JDBCUtilsByDruid.close(null, null, connection);

        }

分析 queryRunner.query方法

/**
 * 分析 queryRunner.query方法:
 * public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
 *         PreparedStatement stmt = null;//定义PreparedStatement
 *         ResultSet rs = null;//接收返回的 ResultSet
 *         Object result = null;//返回ArrayList
 *
 *         try {
 *             stmt = this.prepareStatement(conn, sql);//创建PreparedStatement
 *             this.fillStatement(stmt, params);//对sql 进行 ? 赋值
 *             rs = this.wrap(stmt.executeQuery());//执行sql,返回resultset
 *             result = rsh.handle(rs);//返回的resultset --> arrayList[result] [使用到反射,对传入class对象处理]
 *         } catch (SQLException var33) {
 *             this.rethrow(var33, sql, params);
 *         } finally {
 *             try {
 *                 this.close(rs);//关闭resultset
 *             } finally {
 *                 this.close((Statement)stmt);//关闭preparedstatement对象
 *             }
 *         }
 *
 *         return result;
 *     }
 */

BasicDAO设计模式

在开发中需要用到多个表,BasicDAO设计模式可以对多个表进行管理,增加开发效率。

为什么要有 BasicDAO


Apache-Dbutils + Druid 简化了 JDBC 开发,但还有不足:

  • SQL语句是固定,不能通过参数传入,通用性不好,需要进行改进,更方便执行增删改查
  • 对于 select 操作,如果有返回值,返回类型不能固定,需要使用泛型
  • 将来的表很多,业务需求复杂,不可能只靠一个 Java 类完成

DAO:Data Access Object 数据访问对象

  • 这样的通用类,称为 BasicDao ,是专门和数据库交互的,即完成对数据库(表)的 crud 操作
  • 在 BaiscDao 的基础上,实现一张表对应一个 Dao,更好的完成功能,比如 Customer表 --- Customer.java类(javabean) --- CustomerDao.java

BasicDAO 设计模式的文件目录

BasicDao实例


JDBCUtilsByDruid代码
 

package dao_.utils;

/**
 * @Author: ztc
 * @Date: 2022/9/24 21:57
 */

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class JDBCUtilsByDruid {

    private static DataSource ds;

    //在静态代码块完成 ds初始化
    static {
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("src\\druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    //编写getConnection方法
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }

    //在数据库连接池技术中,close 不是真的断掉连接
    //而是把使用的Connection对象放回连接池
    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 e) {
            throw new RuntimeException(e);
        }
    }
}

BasicDao代码

package dao_.dao;

import dao_.utils.JDBCUtilsByDruid;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

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

/**
 * @Author: ztc
 * @Date: 2022/9/25 10:40
 */
public class BasicDao<T> {
    private QueryRunner qr =new QueryRunner();
    //开发通用的DML方法,针对任意的表
    public int update(String sql,Object... parameters){
        Connection connection = null;
        try {
            connection = JDBCUtilsByDruid.getConnection();

            int update  = qr.update(connection,sql,parameters);
            return update;
        } catch (SQLException e) {
            throw new RuntimeException(e);//将编译异常转换成运行时异常抛出
        } finally {
            JDBCUtilsByDruid.close(null,null,connection);
        }
    }
     public List<T> queryMulti(String sql, Class<T> clazz, Object... parameter){
        Connection connection = null;

         try {
             connection = JDBCUtilsByDruid.getConnection();
             return qr.query(connection,sql,new BeanListHandler<T>(clazz),parameter);
         } catch (SQLException e) {
             throw new RuntimeException(e);
         } finally {
             JDBCUtilsByDruid.close(null,null,connection);
         }
     }
    //查询单行结果 的通用方法
    public T querySingle(String sql, Class<T> clazz, Object... parameters) {

        Connection connection = null;
        try {
            connection = JDBCUtilsByDruid.getConnection();
            return  qr.query(connection, sql, new BeanHandler<T>(clazz), parameters);

        } catch (SQLException e) {
            throw  new RuntimeException(e); //将编译异常->运行异常 ,抛出
        } finally {
            JDBCUtilsByDruid.close(null, null, connection);
        }
    }

    //查询单行单列的方法,即返回单值的方法

    public Object queryScalar(String sql, Object... parameters) {

        Connection connection = null;
        try {
            connection = JDBCUtilsByDruid.getConnection();
            return  qr.query(connection, sql, new ScalarHandler(), parameters);

        } catch (SQLException e) {
            throw  new RuntimeException(e); //将编译异常->运行异常 ,抛出
        } finally {
            JDBCUtilsByDruid.close(null, null, connection);
        }
    }
}

ActorDao代码

package dao_.dao;

import dao_.domain.Actor;

/**
 * @Author: ztc
 * @Date: 2022/9/25 11:03
 */
public class ActorDao extends BasicDao<Actor>{

}

JavaBean  Actor

package dao_.domain;

/**
 * @Author: ztc
 * @Date: 2022/9/24 21:57
 */

import java.util.Date;

public class Actor { //Javabean, POJO, Domain对象

    private Integer id;
    private String name;
    private String sex;
    private Date borndate;
    private String phone;

    public Actor() { //一定要给一个无参构造器[反射需要]
    }

    public Actor(Integer id, String name, String sex, Date borndate, String phone) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.borndate = borndate;
        this.phone = phone;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getBorndate() {
        return borndate;
    }

    public void setBorndate(Date borndate) {
        this.borndate = borndate;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "\nActor{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", borndate=" + borndate +
                ", phone='" + phone + '\'' +
                '}';
    }
}

测试类TestDao代码

package dao_.test;

import dao_.dao.ActorDao;
import dao_.domain.Actor;
import org.junit.jupiter.api.Test;

import java.util.List;

/**
 * @Author: ztc
 * @Date: 2022/9/25 11:03
 */
public class TestDao {
    @Test
    public void testActorDAO(){
        ActorDao actorDao = new ActorDao();
        //查询
        List<Actor> actors = actorDao.queryMulti("select * from actor where id >= ?",Actor.class,1);
        System.out.println("====查询结果====");
        for (Actor actor : actors) {
            System.out.println(actor);
        }
        //2. 查询单行记录
        Actor actor = actorDao.querySingle("select * from actor where id = ?", Actor.class, 2);
        System.out.println("====查询单行结果====");
        System.out.println(actor);

        //3. 查询单行单列
        Object o = actorDao.queryScalar("select name from actor where id = ?", 1);
        System.out.println("====查询单行单列值===");
        System.out.println(o);

        //4. dml操作  insert ,update, delete
        int update = actorDao.update("insert into actor values(null, ?, ?,  ?)", "张无忌", "男","999");

        System.out.println(update > 0 ? "执行成功" : "执行没有影响表");


    }
}

  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java码蚁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值