java零基础Ⅲ-- 5.JDBC和数据库连接池

连接视频



JDBC概述

基本介绍

1、JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题。

2、Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。

3、JDBC的基本原理

在这里插入图片描述

4、模拟JDBC

/**
 * 我们规定的jdbc接口(方法)
 */
public interface JdbcInterface {

    //连接数据库
    public Object getConnection();
    //crud
    public void crud();
    //关闭连接
    public void close();

}
/**
 * Mysql数据库实现了jdbc接口【模拟】【mysql厂商开发】
 */
public class MysqlJdbcImpl implements JdbcInterface{
    @Override
    public Object getConnection() {
        System.out.println("得到 mysql 的连接");
        return null;
    }

    @Override
    public void crud() {
        System.out.println("完成 mysql 增删改查");
    }

    @Override
    public void close() {
        System.out.println("关闭 mysql 的连接");
    }
}
/**
 * 模拟oracle数据库实现 jdbc
 */
public class OracleJdbcImpl implements JdbcInterface{
    @Override
    public Object getConnection() {
        System.out.println("得到 oracle 的连接");
        return null;
    }

    @Override
    public void crud() {
        System.out.println("完成 对oracle 增删改查");
    }

    @Override
    public void close() {
        System.out.println("关闭 oracle 的连接");
    }
}


public class TestJDBC {
    public static void main(String[] args) {
        //完成对mysql的操作
        JdbcInterface jdbcInterface = new MysqlJdbcImpl();
        //通过接口来调用实现类[动态绑定]
        jdbcInterface.getConnection();
        jdbcInterface.crud();
        jdbcInterface.close();
        System.out.println("======================");
        //完成对oracle的操作
        jdbcInterface = new OracleJdbcImpl();
        jdbcInterface.getConnection();
        jdbcInterface.crud();
        jdbcInterface.close();
    }
}

运行效果:

在这里插入图片描述


JDBC带来的好处

1、如果Java直接访问数据库【不可取】
在这里插入图片描述

2、JDBC带来的好处

在这里插入图片描述

3、说明:JDBC是Java提供一套用于数据库操作的接口API,Java程序员只需要面向这套编程即可。不同的数据库厂商,需要针对这套接口,提供不同实现。


JDBC API

JDBC API是一系列的接口,它统一和规范了应用程序与数据库的连接、执行SQL语句,并得到返回结果等各类操作,相关类和接口在 java.sqljavax.sql包中

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述



JDBC快速入门

JDBC程序编写步骤

  • 1、注册驱动 – 加载Driver 类
  • 2、获取连接 – 得到Connection 连接
  • 3、执行增删改查 – 发送SQL 给mysql执行
  • 4、释放资源 – 关闭相关连接

JDBC第一个程序

通过jdbc 对表 actor 进行添加,删除和修改操作

user zzp_db02;
create table actor(
	id int primary key auto_increment,
	`name` varchar(32) not null default '',
	sex char(1) not null default '男',
	borndate datetime,
	phone varchar(12)
);

mysql-connector-java-5.1.49.jar包,放在项目libs目录下

在这里插入图片描述

然后把jar添加项目里
在这里插入图片描述

在这里插入图片描述

package com.zzpedu.jdbc;

import com.mysql.jdbc.Driver;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
 * 这是第一个JDBC 程序,完成简单的操作
 */
public class Jdbc01 {
    public static void main(String[] args) throws SQLException {
        //前置工作:在项目下创建一个文件夹 比如:libs
        //将 mysql.jar 拷贝到目录下,点击右键 add to project ..加入项目中

        //1. 注册驱动
        Driver driver = new com.mysql.jdbc.Driver();//创建driver对象

        //2. 得到连接
        //解读:
        //(1) jdbc:mysql:// 规定好表示协议,通过jdbc的方式连接mysql
        //(2)localhost 主机 也可以是ip地址
        //(3)3306 表示监听的端口
        //(4) zzp_db02 连接到mysql dbms 的哪个数据库
        //(5)mysql的连接本质就是前面学过的socket连接
        String url = "jdbc:mysql://localhost:3306/zzp_db02";
        //将 用户名和密码放入到properties对象
        // 说明 user 和 password 是规定好的,后面的值根据实际情况写
        Properties properties = new Properties();
        properties.setProperty("user","root");//用户
        properties.setProperty("password","123456");//密码

        //获取mysql数据库连接 http://ip:port/web-manage/inspection/query-enterprise-keywords-export
        Connection connect = driver.connect(url, properties);
        //3. 执行sql语句
        //String sql = "insert into actor values(null,'tom','1','1970-11-11','110')";
        //String sql = "update actor set name = 'jack' where id = 2";
        String sql = "delete from actor where id =2";
        // statement 用于执行静态SQL语句并返回其生成的结果的对象
        Statement statement = connect.createStatement();
        // 如果是 dml语句,返回就是影响的行数
        int rows = statement.executeUpdate(sql);
        System.out.println(rows > 0 ? "成功" : "失败");

        //4.关闭资源连接
        statement.close();
        connect.close();
    }
}

获取数据库连接5种方式

方式1 获取Driver实现类对象

//获取Driver实现类对象
Driver driver = new com.mysql.jdbc.Driver();

String url = "jdbc:mysql://localhost:3306/jdbc_db";

Properties properties = new Properties();
properties.setProperty("user","root");//用户
properties.setProperty("password","123456");//密码
Connection connect = driver.connect(url, properties);
System.out.println(connect);

方式2 反射加载Driver类

方式1 会直接使用 new com.mysql.jdbc.Driver(),属于静态加载,灵活性差,依赖性强
// --- 推出 --> 方式2
Class clazz = Class.forName("com.mysql.jdbc.Driver()");
Driver driver = (Driver) clazz.newInstance();

String url = "jdbc:mysql://localhost:3306/jdbc_db";
Properties properties = new Properties();
properties.setProperty("user","root");//用户
properties.setProperty("password","123456");//密码

Connection connect = driver.connect(url, properties);
System.out.println(connect);
public class JdbcConn {

    //方式1
    @Test
    public void connect01() throws SQLException {
        Driver driver = new com.mysql.jdbc.Driver();//创建driver对象

        String url = "jdbc:mysql://localhost:3306/zzp_db02";
        Properties properties = new Properties();
        properties.setProperty("user","root");//用户
        properties.setProperty("password","123456");//密码
        Connection connect = driver.connect(url, properties);
        System.out.println(connect);
        connect.close();
    }

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

        Driver driver = (Driver)clazz.newInstance();

        String url = "jdbc:mysql://localhost:3306/zzp_db02";
        Properties properties = new Properties();
        properties.setProperty("user","root");//用户
        properties.setProperty("password","123456");//密码
        Connection connect = driver.connect(url, properties);
        System.out.println("方式2=" + connect);
        connect.close();
    }
}
---------------------------------------
控制台输出:
方式2=com.mysql.jdbc.JDBC4Connection@769c9116

方式3 DriverManager

//使用 DriverManager 替换 Driver
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)clazz.newInstance();

String url = "jdbc:mysql://localhost:3306/jdbc_db";
String user = "root";
String password = "zzp";

DriverManager.registerDriver(driver);

Connection connect = DriverManager.getConnection(url,user,password);
System.out.println(connect);
//方式3 使用DriverManager 代替 Driver 进行统一管理
@Test
public void connect03() throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
    //使用反射加载Driver类,动态加载,更加灵活,依赖性减少
    Class<?> clazz = Class.forName("com.mysql.jdbc.Driver");

    Driver driver = (Driver)clazz.newInstance();
    //创建 url 和 user 和 password
    String url = "jdbc:mysql://localhost:3306/zzp_db02";
    String user = "root";
    String password = "123456";

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

    Connection connect = DriverManager.getConnection(url,user,password);
    System.out.println("方式3=" + connect);
}

方式4 DriverManager 反射

// 使用 Class.forName 自动完成注册驱动,简化代码 
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/jdbc_db";
String user = "root";
String password = "123456";

Connection connect = DriverManager.getConnection(url,user,password);
System.out.println(connect);
//方式4 使用Class.forName 自动完成注册启动,简化代码
//这种方式获取连接是使用最多的,推荐使用
@Test
public void connect04() throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
    //使用反射加载Driver类
    // 在加载 Driver类时,完成注册
    /*
        源码:1. 静态代码块,在类加载时,会执行一次
             2. DriverManager.registerDriver(new Driver());
             3. 在加载 Driver的工作时已经完成
        public class Driver extends NonRegisteringDriver implements java.sql.Driver {
            public Driver() throws SQLException {
            }

            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/zzp_db02";
    String user = "root";
    String password = "123456";

    Connection connect = DriverManager.getConnection(url,user,password);
    System.out.println("方式4=" + connect);
}

提示:
1、mysql驱动 5.1.6 可以无需要 Class.forName("com.mysql.jdbc.Driver");
2、从 jdk1.5 以后使用了 jdbc4,不再需要显示的调用Class.forName()注册驱动而是自动调用驱动 jar包下 META-INF\service\java.sql.Driver文本中的类名称去注册
3、建议还是写上 Class.forName("com.mysql.jdbc.Driver"), 更加明确
在这里插入图片描述


方式5 配置文件

//使用配置文件,连接数据库更加灵活
1、Connection connection = 
	DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb", "root", "12346");
// 中的字符串 各个值,比如 端口,数据库,用户名,密码为了方便,我们可以将信息写入到.properties 文件中,方便操作

2、jdbc.properties  配置文件内容如下
	user=root
	password=123456
	url=jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8&serverTimezone=PRC&useSSL=false
	driver=com.mysql.jdbc.Driver
//方式5,在方式4的基础上改进,增加配置文件,让连接mysql更加灵活
@Test
public void connect05() throws SQLException, ClassNotFoundException,  IOException {
    //通过Properties对象获取配置文件的信息
    Properties properties = new Properties();
    properties.load(new FileInputStream("src\\mysql.properties"));
    //获取相关的值
    String user = properties.getProperty("user");
    String password = properties.getProperty("password");
    String url = properties.getProperty("url");
    String driver = properties.getProperty("driver");

    Class.forName(driver);//建议写上
    Connection connect = DriverManager.getConnection(url,user,password);
    System.out.println("方式5=" + connect);
}

在这里插入图片描述



JDBC API

ResultSet[结果集]

基本介绍

在这里插入图片描述

1、表示数据库结果集的数据集,通常通过执行查询数据库的语句生成

2、ResultSet对象保持一个关标指向其当前的数据行。最初,关标位于第一行之前

3、next方法将关标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可以在 while循环中使用循环来遍历结果集

在这里插入图片描述


应用实例

/**
 * 演示select 语句返回 ResultSet,并取出结果
 */
@SuppressWarnings({"all"})
public class ResultSet_ {
    public static void main(String[] args) throws Exception {
        //通过Properties对象获取配置文件的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driver = properties.getProperty("driver");
        //1. 注册驱动
        Class.forName(driver);
        //2. 得到连接
        Connection connect = DriverManager.getConnection(url,user,password);

        //3. 得到 statement
        Statement statement = connect.createStatement();
        //4. 组织sql语句
        String sql = "select id,name,sex,borndate from actor";
        //执行给定的SQL语句,该语句返回单个 ResultSet对象。
        /*
        +----+------+-----+---------------------+-------+
        | id | name | sex | borndate            |
        +----+------+-----+---------------------+-------+
        |  1 | zzp  | 女  | 1970-11-11 00:00:00 |
        |  2 | haha | 男  | 1990-11-11 00:00:00 |
        +----+------+-----+---------------------+-------+
         */
        /**
         *  阅读 resultSet 对象的结构
         *
         */
        ResultSet resultSet = statement.executeQuery(sql);

        //5. 使用while取出数据
        while (resultSet.next()){//让关标向后移动,如果没有更多的行,则返回false
            int id = resultSet.getInt(1);//获取该行第1列
            String name = resultSet.getString(2);//获取该行第2列
            String sex = resultSet.getString(3);//获取该行第3列
            Date date = resultSet.getDate(4);//获取该行第4列
            System.out.println("id=" + id + " name=" + name +
                    " sex=" + sex + " borndate=" + date);
        }
        //6. 关闭连接
        resultSet.close();
        statement.close();
        connect.close();
    }
}
-------------------------------------
控制台输出:
id=1 name=zzp sex=女 borndate=1970-11-11
id=2 name=haha sex=男 borndate=1990-11-11

在这里插入图片描述
resultSet对象信息:
在这里插入图片描述


Statement

基本介绍

1、Statement对象 用于执行静态SQL语句并返回其生成的结果的对象

2、在连接建立后,需要对象数据库进行访问,执行 命令或是SQL 语句,可以通过

  • Statement[存在SQL注入问题]
  • PreparedStatement[预处理]
  • CallableStatement[存储过程]

3、Statement对象执行SQL 语句,存在SQL注入风险

4、SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令,恶意攻击数据库

5、要犯范 SQL 注入,只需要用 PreparedStatement(从Statment扩展而来)取代 Statment 就可以了

-- 演示 sql注入
-- 创建一张表
create table admin( -- 管理员表
	`name` varchar(32) not null unique,
	pwd varchar(32) not null default ''
) character set utf8;

-- 添加数据
insert into admin values ('tom','123');

-- 查询某个管理员是否存在
select * from admin where `name` = 'tom' and pwd = '123'; -- 正常查询

-- sql注入
-- 输入用户名 为 1' or
-- 输入万能密码 为  or '1' = '1
select * from admin where `name` = '1' or' and pwd = 'or '1' = '1';

查询sql注入效果:
在这里插入图片描述


应用实例

/**
 * 演示statement 的注入问题
 */
@SuppressWarnings({"all"})
public class Statement_ {
    public static void main(String[] args) throws Exception {
        Scanner scenner = new Scanner(System.in);

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

        //通过Properties对象获取配置文件的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driver = properties.getProperty("driver");
        //1. 注册驱动
        Class.forName(driver);
        //2. 得到连接
        Connection connect = DriverManager.getConnection(url,user,password);

        //3. 得到 statement
        Statement statement = connect.createStatement();

        //4. 组织sql语句
        String sql = "select name,pwd from admin where name = '"
                + admin_user + "' and pwd = '" + admin_pwd + "'";
        ResultSet resultSet = statement.executeQuery(sql);
        if(resultSet.next()){//如果查询一条记录,则说明该管理用户存在
            System.out.println(" 登录成功!");
        }else {
            System.out.println(" 登录失败~");
        }

        //6. 关闭连接
        resultSet.close();
        statement.close();
        connect.close();
    }
}

执行效果:
在这里插入图片描述



PreparedStatement

//编写sql
String sql = "select count(*) from amdin where username = ? and password = ?";

1、PreparedStatement执行的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement对象的 setXxx()方法来设置这些参数,setXxx()方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1开始),第二个是设置的 SQL 语句中的参数的值

2、调用 executeQuery(),返回 ResultSet对象

3、调用 executeUpdate():执行更新,包括增、删、修改操作

在这里插入图片描述


预处理好处

1、不再使用 + 拼接sql语句,减少语法错误

2、有效的解决了sql注入问题

3、大大减少了编译次数,效率高


应用案例1

编写程序完成,登录验证,看看是否可以解决SQL注入问题

/**
 * 演示PreparedStatement使用
 */
@SuppressWarnings({"all"})
public class PreparedStatement_ {
    public static void main(String[] args) throws Exception {
        Scanner scenner = new Scanner(System.in);

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

        //通过Properties对象获取配置文件的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driver = properties.getProperty("driver");
        //1. 注册驱动
        Class.forName(driver);
        //2. 得到连接
        Connection connect = 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 = connect.prepareStatement(sql);
        // 3.3 给 ? 赋值 占位符 从第1开始
        preparedStatement.setString(1,admin_user);
        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(" 登录失败~");
        }

        //6. 关闭连接
        resultSet.close();
        preparedStatement.close();
        connect.close();
    }
}

执行效果:
在这里插入图片描述


应用案例2

dml 增删改 操作

/**
 * 演示PreparedStatement使用DML语句
 */
@SuppressWarnings({"all"})
public class PreparedStatementDML_ {
    public static void main(String[] args) throws Exception {
        Scanner scenner = new Scanner(System.in);

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

        //通过Properties对象获取配置文件的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driver = properties.getProperty("driver");
        //1. 注册驱动
        Class.forName(driver);
        //2. 得到连接
        Connection connect = 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 = connect.prepareStatement(sql);
        // 3.3 给 ? 赋值 占位符 从第1开始
//        preparedStatement.setString(1,admin_user);//添加
//        preparedStatement.setString(2,admin_pwd);

//        preparedStatement.setString(2,admin_user); //修改
//        preparedStatement.setString(1,admin_pwd);

        preparedStatement.setString(1,admin_user); //删除

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

        //6. 关闭连接
        preparedStatement.close();
        connect.close();
    }
}

执行效果:
在这里插入图片描述


小结

在这里插入图片描述

在这里插入图片描述



JDBCUtils

封装JDBCUtils类

在这里插入图片描述

说明

在jdbc 操作中,获取连接 和 释放资源 是经常使用到,
可以将其封装JDBC连接的 工具类 JDBCUtils


代码实现

package com.zzpedu.jdbc.uitls;

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

/**
 * 这是一个根据类,完成 mysql的连接和关闭资源
 */
public class JDBCUtils {
    //定义相关的属性(4个),因为只需要一份,因此我们做成 static
    private static String user;//用户名
    private static String password;//密码
    private static String url;//url
    private static String driver;//驱动

    //在static代码块去初始化
    static{
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("src\\mysql.properties"));
            //获取相关的值
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            url = properties.getProperty("url");
            driver = properties.getProperty("driver");
        } catch (IOException e) {
            //在实际开发中,我们可以这样处理
            //1. 将编译异常转成 运行异常
            //2. 这时调用者,可以选择捕获异常,也可以选择默认处理该异常,比较方便
            throw new RuntimeException(e.getMessage());
        }
    }

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

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

}

实际使用

@Test
public void testDml(){//insert update delete
     //1. 得到连接
     Connection connection = null;
     //2. 组织sql
     //String sql = "update actor set name = ? where id = ?";
     //String sql = "insert into actor(name,phone) value (?,?)";
     String sql = "delete from actor where id = ?";
     PreparedStatement preparedStatement = null;
     try {
         connection = JDBCUtils.getConnection();
         //3. 创建 PreparedStatement对象
         preparedStatement = connection.prepareStatement(sql);
         //给占位符 赋值
//            preparedStatement.setString(1,"king"); //修改
//            preparedStatement.setInt(2,2);

//            preparedStatement.setString(1,"milan"); // 新增
//            preparedStatement.setString(2,"114");

         preparedStatement.setInt(1,3); // 删除

         //执行
         preparedStatement.executeUpdate();
     } catch (SQLException e) {
         e.printStackTrace();
     }finally {
         //关闭资源
         JDBCUtils.close(null,preparedStatement,connection);
     }

}

@Test
public void testSelect(){
    //1. 得到连接
    Connection connection = null;
    //2. 组织sql
    String sql = "select * from actor where id = ?";
    PreparedStatement preparedStatement = null;
    ResultSet set = null;
    try {
        connection = JDBCUtils.getConnection();
        //3. 创建 PreparedStatement对象
        preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1,1);//给 ? 赋值
        //执行 得到结果集
        set = preparedStatement.executeQuery();
        //遍历该结果集
        while (set.next()){
            int id = set.getInt("id");
            String name = set.getString("name");
            String sex = set.getString("sex");
            Date borndate = set.getDate("borndate");
            String phone = set.getString("phone");
            System.out.println(id + " " + name + " " + sex + " " + borndate + " " + phone);
        }

    } catch (SQLException e) {
        e.printStackTrace();
    }finally {
        //关闭资源
        JDBCUtils.close(set,preparedStatement,connection);
    }
}



事务

基本介绍

1、JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务;每次执行一个 sql语句时,如果执行成功,就会向数据库自动提交,而不能回滚。

2、JDBC程序中为了让更多的 sql 语句作为一个整体执行,需要使用事务

3、调用 Connection 的 setAutoCommit(false)可以取消自动提交事务

4、在所有的 sql 语句都成功执行后,调用 Connection 的commit();方法提交事务

5、在其中某个操作失败或出现异常时,调用 Connection 的 rollbock();方法回滚事务


应用实例

模拟经典的转账业务

CREATE TABLE `account` (
  `id` int(11) primary key auto_increment,
  `name` varchar(32) not null default '',
  `money` double not null default 0
) CHARSET=utf8;

insert into account values(null,'tom',3000);
insert into account values(null,'king',10000);
  • 不使用事务可能出现的问题模拟
//没有使用事务
@Test
public void noTransaction(){
    //操作转账的业务
    //1. 得到连接
    Connection connection = null;
    //2. 组织sql
    String sql1 = "update account set money = money - 100 where id = 1";
    String sql2 = "update account set money = money + 100 where id = 2";
    PreparedStatement preparedStatement = null;
    try {
        connection = JDBCUtils.getConnection();// 在默认情况下,connection是默认自动提交
        //3. 创建 PreparedStatement对象
        preparedStatement = connection.prepareStatement(sql1);
        //执行 第1条sql
        preparedStatement.executeUpdate();
        int i = 1 / 0; //抛出异常
        preparedStatement = connection.prepareStatement(sql2);
        //执行 第2条sql
        preparedStatement.executeUpdate();
    } catch (SQLException e) {
        e.printStackTrace();
    }finally {
        //关闭资源
        JDBCUtils.close(null,preparedStatement,connection);
    }
}

执行效果:

在这里插入图片描述
在这里插入图片描述

  • 使用事务解决上述问题
//使用事务来解决
@Test
public void userTransaction(){
    //操作转账的业务
    //1. 得到连接
    Connection connection = null;
    //2. 组织sql
    String sql1 = "update account set money = money - 100 where id = 1";
    String sql2 = "update account set money = money + 100 where id = 2";
    PreparedStatement preparedStatement = null;
    try {
        connection = JDBCUtils.getConnection();// 在默认情况下,connection是默认自动提交
        // 将 connection 设置为 不自动提交 setAutoCommit(false)
        connection.setAutoCommit(false);//开启了事务
        //3. 创建 PreparedStatement对象
        preparedStatement = connection.prepareStatement(sql1);
        //执行 第1条sql
        preparedStatement.executeUpdate();
        int i = 1 / 0; //抛出异常
        preparedStatement = connection.prepareStatement(sql2);
        //执行 第2条sql
        preparedStatement.executeUpdate();

        //这里提交事务
        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);
    }
}

执行效果:

在这里插入图片描述

在这里插入图片描述



批处理

基本介绍

1、当需要成批插入或更新记录时,可以采用用Java的批量更新机制,这一机制允许许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。

2、JDBC的批量处理语句包括下面方法
addBatch():添加需要批量处理的sql语句或参数
executeBatch():执行批量执行处理语句
clearBatch():清空批处理包的语句

3、JDBC连接MySQL时,如果要使用批处理功能,请再url中加参数?rewriteBatchedStatements=true

4、批处理往往和PreparedStatement一起搭配使用,可以即减少编译次数,有减少运行次数,效率大大提高


应用实例

1、演示向admin2表中添加5000条数据,看看批处理耗时多久
2、注意: 需要修改 配置文件 jdbc.properties url=url=jdbc:mysql://localhost:3306/数据库?rewriteBatchedStatements=true

-- 创建测试表 admin2
create table admin2(
	id int primary key auto_increment,
	username varchar(32) not null,
	pwd varchar(32) not null
);

传统方法:

//传统方法,添加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,"tom" + i);
        preparedStatement.setString(2,"666");
        preparedStatement.executeUpdate();
    }
    long end = System.currentTimeMillis();
    System.out.println("传统的方式 耗时=" + (end - start));//传统的方式 耗时=13607
    JDBCUtils.close(null,preparedStatement,connection);
}

在这里插入图片描述

使用批量方式添加数据(先清空表数据),注意 配置文件 url后添加 rewriteBatchedStatements=true

//使用批量方式添加数据
@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,"tom" + i);
        preparedStatement.setString(2,"666");
        //将sql语句加入批处理包中
        preparedStatement.addBatch();
        //当有1000条记录时,再批量执行
        if((i + 1) % 1000 == 0){//满1000条
            preparedStatement.executeBatch();
            //清空
            preparedStatement.clearBatch();
        }
    }
    long end = System.currentTimeMillis();
    System.out.println("批量的方式 耗时=" + (end - start));//批量的方式 耗时=141
    JDBCUtils.close(null,preparedStatement,connection);
}

在这里插入图片描述


批处理源码分析

preparedStatement.addBatch();

package com.mysql.jdbc;

public class PreparedStatement extends StatementImpl implements java.sql.PreparedStatement {
	.....
	//源码:
	//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));
	    }
	}
}

添加第一条记录
在这里插入图片描述

添加第二条数据
在这里插入图片描述



数据库连接池

5k次连接数据库问题

1、编写程序完成连接MySQL 5000次的操作

2、看看有什么问题,耗时又是多久 ==> 数据库连接池

演示不关闭连接:

//代码 连接mysql 5000次
@Test
public void testCon(){
    for (int i = 0; i < 5000; i++) {
        //使用传统的jdbc方式,得到连接
        Connection connection = JDBCUtils.getConnection();
        //做一些工作,比如得到PreparedStatement,发送sql
        //...
        //故意 不关闭连接
    }
}

执行效果:

在这里插入图片描述

在这里插入图片描述

演示关闭连接:

//代码 连接mysql 5000次
@Test
public void testCon(){
    //看看连接 - 关闭 connection 会耗用多久
    long start = System.currentTimeMillis();
    System.out.println("开始连接...");
    for (int i = 0; i < 5000; i++) {
        //使用传统的jdbc方式,得到连接
        Connection connection = JDBCUtils.getConnection();
        //做一些工作,比如得到PreparedStatement,发送sql
        //...
        //关闭连接
        JDBCUtils.close(null,null,connection);
    }
    long end = System.currentTimeMillis();
    System.out.println("传统方式5000次 耗时=" + (end - start));//传统方式5000次 耗时=25437
}

在这里插入图片描述


传统获取 Connection问题分析

1、传统的JDBC数据库连接使用 DrierManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,在验证IP地址,用户名和密码(0.05~1s时间)。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃

2、每一次数据库连接,使用完都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终导致重启数据库。

3、传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MySQL崩溃

4、解决传统开发中的数据库连接问题,可以采用数据库连接池技术(connection pool)。


数据库连接池基本介绍

1、预先在缓冲中放入一定数量的连接,当需要建立数据库连接时,只需从 “缓冲池” 中取一个,使用完毕之后再放回去。

2、数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。

3、当应用程序向连接池请求的连接数超过最大连接数量时,这些请求被加入等待队列中

在这里插入图片描述

4、示意图

在这里插入图片描述


数据库连接池种类

1、JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由第三方提供实现 【提供 .jar】

2、C3P0 数据库连接池,速度相对较慢,稳定性不错(hibernate, spring)

3、DBCP 数据库连接池,速度相对 c3p0 较快,但不稳定

4、Proxool 数据库连接池,有监控连接池状态的功能,稳定性较 c3p0 差一点

5、BoneCP 数据库连接池,速度快

6、Druid(德鲁伊) 是阿里提供的数据库连接池,集DBCP、C3P0、Proxool优点于一身的数据库连接池


C3P0应用案例

在这里插入图片描述

先添加c3p0-0.9.2.1.jarmchange-commons-java-0.2.11.jar到libs目录下

在这里插入图片描述

再分别把jar添加进项目中
在这里插入图片描述

在这里插入图片描述

使用代码实现 c3p0 数据库连接池

方式1:手动配置信息

//方式1:相关参数,在程序中指定user,password,url
@Test
public void testC33p0_01() throws Exception {
    //1. 创建一个数据源对象
    ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
    //2. 通过配置文件 mysql.properties 获取相关的信息
    //通过Properties对象获取配置文件的信息
    Properties properties = new Properties();
    properties.load(new FileInputStream("src\\mysql.properties"));
    //获取相关的值
    String user = properties.getProperty("user");
    String password = properties.getProperty("password");
    String url = properties.getProperty("url");
    String driver = properties.getProperty("driver");

    //给数据源 comboPooledDataSource 设置相关的参数
    //注意:连接管理是由 comboPooledDataSource 来管理
    comboPooledDataSource.setDriverClass(driver);
    comboPooledDataSource.setJdbcUrl(url);
    comboPooledDataSource.setUser(user);
    comboPooledDataSource.setPassword(password);

    //设置初始化连接数
    comboPooledDataSource.setInitialPoolSize(10);//初始化连接数据库连接数 为 10
    //最大连接数
    comboPooledDataSource.setMaxPoolSize(50);
    //测试连接池的效率,测试对mysql 5000操作
    long start = System.currentTimeMillis();
    for (int i = 0; i < 5000; i++) {
        Connection connection = comboPooledDataSource.getConnection(); //这个方法就是从 DataSource 接口实现
        connection.close();
    }
    long end = System.currentTimeMillis();
    //c3p0 5000次连接mysql 耗时=706
    System.out.println("c3p0 5000次连接mysql 耗时=" + (end - start));
}

在这里插入图片描述

方式2:使用配置文件模板来完成

将c3p0 提供的 c3p0.config.xml 拷贝到 src目录下
在这里插入图片描述

<c3p0-config>
    <!-- 数据源名称代表连接池 -->
    <named-config name="zzp_edu">
        <!-- 启动类 -->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <!-- url -->
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/zzp_db02</property>
        <!-- 用户名 -->
        <property name="user">root</property>
        <!-- 密码 -->
        <property name="password">123456</property>
        <!-- 每次增长的连接数 -->
        <property name="acquireIncrement">5</property>
        <!-- 初始化连接数 -->
        <property name="initialPoolSize">10</property>
        <!-- 最小连接数 -->
        <property name="minPoolSize">5</property>
        <!-- 最大连接数 -->
        <property name="maxPoolSize">50</property>

        <!-- 可连接的最多的命令对象数 -->
        <property name="maxStatements">5</property>

        <!-- 每个连接对象可连接的最多的命令对象数 -->
        <property name="maxStatementsPerConnection">2</property>
    </named-config>
</c3p0-config>
//方式2:使用配置文件模板来完成
//1. 将c3p0 提供的 c3p0.config.xml 拷贝到 src目录下
//2. 该文件指定了数据库和连接池的相关参数
@Test
public void testC33p0_02() throws Exception {
    //创建一个数据源对象(读取配置文件c3p0.config.xml 的 <named-config name="zzp_edu">)
    ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("zzp_edu");

    //测试连接池的效率,测试对mysql 5000操作
    long start = System.currentTimeMillis();
    for (int i = 0; i < 5000; i++) {
        Connection connection = comboPooledDataSource.getConnection(); //这个方法就是从 DataSource 接口实现
        connection.close();
    }
    long end = System.currentTimeMillis();
    //c3p0的第二种方式 5000次连接mysql 耗时=564
    System.out.println("c3p0的第二种方式 5000次连接mysql 耗时=" + (end - start));
}

在这里插入图片描述



Druid(德鲁伊)应用实例

添加druid-1.1.12.jar到项目中
在这里插入图片描述

添加druid.properties配置文件到src目录下
在这里插入图片描述

#key=value
# 驱动名称
driverClassName=com.mysql.jdbc.Driver
# url数据库地址
url=jdbc:mysql://localhost:3306/zzp_db02?useUnicode=true&characterEncoding=utf8&serverTimezone=PRC&useSSL=false&rewriteBatchedStatements=true
# 用户名
username=root
# 密码
password=123456
#initial connection Size
# 初始化连接数
initialSize=10
#min idle connection size
# 最小连接数(空闲数)
minIdle=5
#max active connection size
# 最大连接数
maxActive=50
#max wait time (5000 mil seconds)
# 最大等待时间5s(超时时间)
maxWait=5000

使用代码实现Druid(德鲁伊)数据库连接池

package com.zzpedu.jdbc.datasource;

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

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

/**
 * 测试druid的使用
 */
public class Druid_ {

    @Test
    public void testDruid() throws Exception {
        //1. 加入 Druid jar包
        //2. 加入 配置文件,将druid.properties配置文件拷贝到项目的src目录下
        //3. 创建Properties对象,读取配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src//druid.properties"));

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

执行效果:
在这里插入图片描述


将JDBCUtils工具类改成Druid(德鲁伊实现)

通过德鲁伊数据库连接池获取连接对象

package com.zzpedu.jdbc.datasource;

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;

/**
 * 基于druid数据库连接池的工具类
 */
public class JDBCUtilsByDruid {

    private static DataSource dataSource;

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

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

    //关闭连接,强调:在数据库连接池技术中,close 不是真的断掉数据库连接
    //而是把使用的Connection放回连接池
    public static void close(ResultSet set, Statement statement,Connection connection){
        //判断是否为null
        try {
            if(set != null){
                set.close();
            }
            if(statement != null){
                statement.close();
            }
            if(connection != null){
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

测试一下工具类:

package com.zzpedu.jdbc.datasource;

import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;

/**
 * @author zzp
 */
public class JDBCUtilsByDruid_Use {
    @Test
    public void testSelect(){
        System.out.println("使用 druid方式完成");
        //1. 得到连接
        Connection connection = null;
        //2. 组织sql
        String sql = "select * from actor where id = ?";
        PreparedStatement preparedStatement = null;
        ResultSet set = null;
        try {
            connection = JDBCUtilsByDruid.getConnection();
            System.out.println(connection.getClass());//运行类型 class com.alibaba.druid.pool.DruidPooledConnection
            //3. 创建 PreparedStatement对象
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1,1);//给 ? 赋值
            //执行 得到结果集
            set = preparedStatement.executeQuery();
            //遍历该结果集
            while (set.next()){
                int id = set.getInt("id");
                String name = set.getString("name");
                String sex = set.getString("sex");
                Date borndate = set.getDate("borndate");
                String phone = set.getString("phone");
                System.out.println(id + " " + name + " " + sex + " " + borndate + " " + phone);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            //关闭资源
            JDBCUtilsByDruid.close(set,preparedStatement,connection);
        }
    }

}

执行效果:

在这里插入图片描述



Apache – DBUtils

先分析一个问题

1、关闭 connection 后,resultSet 结果集无法使用

演示代码:得到ResultSet 结果集 立马关闭连接connection.close()

@Test
public void testSelect(){
    System.out.println("使用 druid方式完成");
    //1. 得到连接
    Connection connection = null;
    //2. 组织sql
    String sql = "select * from actor where id >= ?";
    PreparedStatement preparedStatement = null;
    ResultSet set = null;
    try {
        connection = JDBCUtilsByDruid.getConnection();
        System.out.println(connection.getClass());//运行类型 class com.alibaba.druid.pool.DruidPooledConnection
        //3. 创建 PreparedStatement对象
        preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1,1);//给 ? 赋值

        //执行 得到结果集
        set = preparedStatement.executeQuery();
        connection.close();//关闭连接
        //遍历该结果集
        while (set.next()){
            int id = set.getInt("id");
            String name = set.getString("name");
            String sex = set.getString("sex");
            Date borndate = set.getDate("borndate");
            String phone = set.getString("phone");
            System.out.println(id + " " + name + " " + sex + " " + borndate + " " + phone);
        }

    } catch (SQLException e) {
        e.printStackTrace();
    }finally {
        //关闭资源
        JDBCUtilsByDruid.close(set,preparedStatement,connection);
    }
}

执行效果:

在这里插入图片描述

2、resultSet 不利于数据的管理【只能用一次,关闭即没有了,不能重复使用】

3、示意图

在这里插入图片描述


用自己的土方法来解决

代码演示:

/**
 *  Actor 对象和 actor表的记录对应
 */
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 + '\'' +
                '}';
    }
}
//使用 图土方法来解决 ResultSet 封装 到 ArrayList
@Test
public void/*ArrayList<Actor>*/ testSelectToArrayList(){
    //1. 得到连接
    Connection connection = null;
    //2. 组织sql
    String sql = "select * from actor where id >= ?";
    PreparedStatement preparedStatement = null;
    ResultSet set = null;
    //创建ArrayList对象,存放actor对象
    ArrayList<Actor> list = new ArrayList<>();
    try {
        connection = JDBCUtilsByDruid.getConnection();
        System.out.println(connection.getClass());//运行类型 class com.alibaba.druid.pool.DruidPooledConnection
        //3. 创建 PreparedStatement对象
        preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1,1);//给 ? 赋值

        //执行 得到结果集
        set = preparedStatement.executeQuery();
        //遍历该结果集
        while (set.next()){
            int id = set.getInt("id");
            String name = set.getString("name");
            String sex = set.getString("sex");
            Date borndate = set.getDate("borndate");
            String phone = set.getString("phone");
            //把得到的 resultSet 的记录,封装到 Actor对象,放入到 list集合
            list.add(new Actor(id,name,sex,borndate,phone));
        }
        System.out.println("list集合数据=" + list);
        for (Actor actor : list){
            System.out.println(actor.getId() + " " + actor.getName());
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }finally {
        //关闭资源
        JDBCUtilsByDruid.close(set,preparedStatement,connection);
    }
    //因为ArrayList 和 connection 没有任何关联,所以该集合可以复用
    //return list;
}

执行效果:

在这里插入图片描述


基本介绍

1、commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对 JDBC的封装,使用dbutils能极大简化 jdbc编码额工资量


DbUtils类

·1、QueryRunner类:该类封装了SQL的执行,是线程安全的。可以实现增删改查、批处理

2、使用QueryRunner类实现查询

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

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

应用实例

使用 DBUtils + 数据库连接池(德鲁伊) 方式,完成对表的crud

引入commons-dbutils-1.3.jar到libs目录,添加到项目中
在这里插入图片描述

//使用 apache-DBUtils 工具类 + druid 完成对表的 crud操作
@Test
public void testQueryMany() throws SQLException {//返回结果是多行的情况
    //1. 得到 连接(德鲁伊)
    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) 参数1:connection 连接
    //    参数2:sql 执行sql语句
    //    参数:new BeanListHandler<>(Actor.class)  在将得到resultSet --> Actor 对象 -> 封装到 ArrayList中
    //         底层使用反射机制 去获取Actor 类的属性,然后进行封装
    //(4) 1 就是给 sql语句中的?赋值, 可以有多个值,因为是可变参数  Object... params
    //(5) 底层得到的 resultSet,会在query 关闭,关闭PreparedStatement
    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);
}

执行效果:

在这里插入图片描述


DBUtils源码分析

List<Actor> list = queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);

package org.apache.commons.dbutils;

public class QueryRunner {
	...
	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[resultSet][使用到反射,对传入class对象处理]
        } catch (SQLException var33) {
            this.rethrow(var33, sql, params);
        } finally {
            try {
                this.close(rs);//关闭 resultSet
            } finally {
                this.close((Statement)stmt);//关闭 preparedStatement
            }
        }

        return result;
    }

}

应用实例2

使用 DBUtils + 数据库连接池(德鲁伊) 完成 返回的结果是单行记录(单个对象)

//演示 apache-dbutils + druid 完成 返回的结果是单行记录(单个对象)
@Test
public void testQuerySingle() throws Exception {
    //1. 得到 连接(德鲁伊)
    Connection connection = JDBCUtilsByDruid.getConnection();
    //2. 使用 DBUtils 类和接口,引入 DBUtils 相关的jar包,加入到本Project
    //3. 创建 QueryRunner
    QueryRunner queryRunner = new QueryRunner();
    //4. 就可以执行相关的方法,返回单个对象
    String sql = "select * from actor where id = ?";
    //解读:
    // 因为我们返回的单行记录 <---> 单个对象,使用的 Handler 是 BeanHandler
    Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 1);
    System.out.println(actor);

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

执行效果:
在这里插入图片描述


完成查询结果是单行单列 – 返回就是Object

//演示 apache-dbutils + druid 完成查询结果是单行单列 -- 返回就是Object
@Test
public void testScalar() throws SQLException {
    //1. 得到 连接(德鲁伊)
    Connection connection = JDBCUtilsByDruid.getConnection();
    //2. 使用 DBUtils 类和接口,引入 DBUtils 相关的jar包,加入到本Project
    //3. 创建 QueryRunner
    QueryRunner queryRunner = new QueryRunner();
    //4. 就可以执行相关的方法,返回单行当列
    String sql = "select name from actor where id = ?";
    //解读:因为返回的是一个对象,使用的 Handler 是 ScalarHandler
    Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 1);
    System.out.println("obj=" + obj);

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

执行效果:
在这里插入图片描述


完成 dml (update insert delete)

//演示 apache-dbutils + druid 完成 dml (update insert delete)
@Test
public void testDML() throws SQLException {
    //1. 得到 连接(德鲁伊)
    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, "jim", 2); // 更新
    //int affectedRow = queryRunner.update(connection, sql, "jack","男","1992-01-01","118"); // 新增
    int affectedRow = queryRunner.update(connection, sql,  30); // 删除
    System.out.println(affectedRow > 0 ? "执行成功" : "执行没有影响到表");
    //释放资源
    JDBCUtilsByDruid.close(null,null,connection);
}

执行效果:

在这里插入图片描述


表和 JavaBean 的类型映射关系

在这里插入图片描述



DAO增删改查和通用方法 - BasicDao

先分析一个问题

apache-dbutils + druid 简化了JDBC开发,但还有不足:

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

示意图:

在这里插入图片描述


基本说明

1、DAO:data access object 数据访问对象

2、这样的通用类,称为 BasicDao,是专门和数据库交换的,即完成对数据库(表)的crud操作。

3、在 BasicDao基础上,实现一张表,对应一个Dao,更好的完成功能,比如 Customer表 -- Customer.java类(javabean) -- CustomerDao.java


BasicDAO 应用实例

完成一个简单设计

创建包:com.zzpedu.dao_
1、com.zzpedu.dao_.utils //工具类
2、com.zzpedu.dao_.domain //javabean
3、com.zzpedu.dao_.dao //存放XxxDao 和BasicDAO
4、com.zzpedu.dao_.test //写测试类

在这里插入图片描述

utils 包下

package com.zzpedu.dao_.utils;

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;

/**
 * 基于druid数据库连接池的工具类
 */
public class JDBCUtilsByDruid {

    private static DataSource dataSource;

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

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

    //关闭连接,强调:在数据库连接池技术中,close 不是真的断掉数据库连接
    //而是把使用的Connection放回连接池
    public static void close(ResultSet set, Statement statement,Connection connection){
        //判断是否为null
        try {
            if(set != null){
                set.close();
            }
            if(statement != null){
                statement.close();
            }
            if(connection != null){
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

domain 包下

package com.zzpedu.dao_.domain;

import java.util.Date;

/**
 *  Actor 对象和 actor表的记录对应
 */
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 + '\'' +
                '}';
    }
}

dao 包下

package com.zzpedu.dao_.dao;

import com.zzpedu.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;

/**
 * 开发BasicDAO,是其他DAO的父类
 */
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);
        }
    }

    /**
     *  返回多个对象(即查询结果是多行),针对任意表
     * @param sql sql语句,可以有 ?
     * @param clazz 传入一个类的Class对象 比如 Actor.class
     * @param parameters 传入 ? 的具体值,可以是多个
     * @return 根据Actor.class 返回对应的 ArrayList 集合
     */
    public List<T> queryMulti(String sql, Class<T> clazz, Object... parameters){
        Connection connection = null;
        try {
            connection = JDBCUtilsByDruid.getConnection();
            return qr.query(connection, sql, new BeanListHandler<T>(clazz), parameters);
        } 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);
        }
    }
}
package com.zzpedu.dao_.dao;

import com.zzpedu.dao_.domain.Actor;

/**
 *
 */
public class ActorDAO extends BasicDAO<Actor>{
    //1. 继承BasicDAO 就有 BasicDAO 使用的方法
    //2. 根据业务需求,可以编写特有的方法

}

test 包下,测试

package com.zzpedu.dao_.test;

import com.zzpedu.dao_.dao.ActorDAO;
import com.zzpedu.dao_.domain.Actor;
import org.junit.Test;

import java.util.List;

/**
 *
 */
public class TestDAO {
    //测试ActorDAO 对 actor表crud操作
    @Test
    public void testActorDAO(){
        ActorDAO actorDAO = new ActorDAO();
        //1. 查询
        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, 1);
        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,?,?,?,?)", "张无忌", "男", "2000-11-11", "888");
        System.out.println(update > 0 ? "执行成功" : "执行没有影响表");
    }
}

执行效果:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值