JDBC 和数据库连接池

JDBC 和数据库连接池

1. JDBC 概述

1.1 基本介绍

  1. JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题
  2. Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。
  3. JDBC的基本原理图[重要!]

JDBC 和数据库连接池_第1张图片

1.2 模拟JDBC

  • 先创建一个我们规定的 jdbc 接口(方法)
package com.song.jdbc.myjdbc;

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

    //crud
    public void crud();

    //关闭连接
    public void close();

}
  • 然后mysql数据库实现jdbc接口(这里我们简单的模拟一下)
package com.song.jdbc.myjdbc;
/**
 * 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 的连接");
    }
}
  • 完成对mysql的操作
package com.song.jdbc.myjdbc;

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

    }
}
  • 同样换成Oracle实现jdbc接口(这里我们简单的模拟一下)
package com.song.jdbc.myjdbc;

/**
 * 模拟 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 的连接");
    }
}
  • 这里完成对Oracle的操作跟mysql的一样,只是修改了一下对象
package com.song.jdbc.myjdbc;

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();
    }
}

1.3 JDBC 带来的好处

  1. 如果Java直接访问数据库(示意图)

JDBC 和数据库连接池_第2张图片

  1. JDBC带来的好处(示意图)

JDBC 和数据库连接池_第3张图片

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

1.4 JDBC API

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

JDBC 和数据库连接池_第4张图片

2.JDBC 快速入门

2.1 JDBC 程序编写步骤

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

2.2 JDBC 第一个程序

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

先在SQLyog中创建一个演员表

use sys_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));

JDBC 和数据库连接池_第5张图片

2.3 JDBC 第一个程序代码演示

  1. 打开idea,先进行前置工作
//前置工作: 1.在项目下创建一个文件夹(Directory)比如 libs
// 2.将 mysql.jar 拷贝到该目录下,
// 3.右击mysql.jar文件,点击 add to project ..加入到项目中

JDBC 和数据库连接池_第6张图片

  1. 注册驱动,创建一个driver对象
//1.注册驱动
Driver driver = new Driver();//创建一个Driver对象
  1. 连接mysql
//2.得到连接
//解读
//1.jdbc:mysql:// 这段是规定好的,通过jdbc的连接方式是mysql
//2.localhost的位置可以是ip地址,我这边正好在主机上操作
//3.3306表示我们mysql监听的端口
//4.sys_db02 表示连接到mysql的哪个数据库
//mysql的连接本质就是前面学过的socket连接
String url = "jdbc:mysql://localhost:3306/sys_db02";

//将用户名和密码放入到 Properties 对象
Properties properties = new Properties();
//说明
//这里的 user 和 password 是规定好的,
//后面的值根据实际情况填写,比如 root 和 sys
properties.setProperty("user","root");//用户
properties.setProperty("password","sys");//密码
//尝试给数据库连接到指定的 url
Connection connect = driver.connect(url, properties);
  1. 执行sql
//3.执行 sql
//String sql = "insert into actor values(null,'周杰伦','男','1979-01-18','110')";
//String sql = "insert into actor values(null,'林俊杰','男','1981-03-27','120')";
String sql = "update actor set phone = '119' where name = '林俊杰'";
//statement 用于执行静态SQL语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
int rows = statement.executeUpdate(sql);//如果是 dml语句,返回的就是影响行数
//提示
System.out.println(rows>0?"成功":"失败");
  1. 关闭连接
//4.关闭连接
statement.close();
connect.close();
  1. 运行idea,然后打开SQLyog,发现数据已经插入表格中

JDBC 和数据库连接池_第7张图片

3. 获取数据库连接 5 种方式

3.1 方式一

  1. 基本语法
// 获取Driver实现类对象
Driver driver = new com.mysql.jdbc.Driver();

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

Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","sys");
Connection conn = driver.connect(url, info);
System.out.println(conn);
  1. 案例演示:
package com.song.jdbc;

import com.mysql.jdbc.Driver;
import org.junit.jupiter.api.Test;

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

/**
 * 分析java连接mysql的5种方式
 */
public class JdbcConn {
    //方式一
    @Test
    public void connect01() throws SQLException {
        Driver driver = new Driver();
        String url = "jdbc:mysql://localhost:3306/sys_db02";

        Properties properties = new Properties();
        properties.setProperty("user", "root");
        properties.setProperty("password", "sys");

        Connection connect = driver.connect(url, properties);
        System.out.println(connect);
    }
}

3.2 方式二

  1. 基本语法
//方式1 会直接使用 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 info = new Properties();
info.setProperty("user","root");
info.setProperty("password", "abc123");

Connection conn = driver.connect(url, info);
System.out.println(conn);
  1. 案例演示:
package com.song.jdbc;

import com.mysql.jdbc.Driver;
import org.junit.jupiter.api.Test;

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

/**
 * 分析java连接mysql的5种方式
 */
public class JdbcConn {
    //方式二
    @Test
    public void connect02() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
        //使用反射加载类
        Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
        Driver driver = (Driver) aClass.newInstance();

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

        Properties properties = new Properties();
        properties.setProperty("user", "root");
        properties.setProperty("password", "sys");

        Connection connect = driver.connect(url, properties);
        System.out.println(connect);
    }
}

3.3 方式三

  1. 基本语法
// 使用DriverManager替换Driver
Class clazz = Class.forName("com.mysqljdbc.Driver")
Driver driver = (Driver) clazz.newlnstance();

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

DriverManager.registerDriver(driver);

Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
  1. 案例演示:
package com.song.jdbc;

import com.mysql.jdbc.Driver;
import org.junit.jupiter.api.Test;

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

/**
 * 分析java连接mysql的5种方式
 */
public class JdbcConn {
    //方式三
    @Test
    public void connect03() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
        Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
        Driver driver = (Driver) aClass.newInstance();

        String url = "jdbc:mysql://localhost:3306/sys_db02";
        String user = "root";
        String password = "sys";

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

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

3.4 方式四

  1. 基本语法
// 使用 Class.forName 自动完成注册驱动,简化代码 => 分析源码
Class.forName("com.mysal.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/jdbc_db";
String user = "root";
String password = "hsp"
Connection conn = DriverManager.getConnection(url, user, password);.
System.out.println(conn);
  1. 案例演示:
package com.song.jdbc;

import com.mysql.jdbc.Driver;
import org.junit.jupiter.api.Test;

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

/**
 * 分析java连接mysql的5种方式
 */
public class JdbcConn {
    //方式四:使用Class.forName自动完成注册驱动,简化代码
    @Test
    public void connect04() throws ClassNotFoundException, SQLException {
        //使用反射加载Driver类
        //在加载Driver类时,完成注册
        /*
        源码:com.mysql.jdbc.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 aClass = Class.forName("com.mysql.jdbc.Driver");不写也可以
        //原因:
        // 1.mysqL驱动5.1.6可以无需CLass,forName(“com.mysql.jdbc.Driver”);
        // 2.从idk1.5以后使用了jdbc4,不再需要显示调用class.forName()注册驱动而是
        // 自动调用驱动jar包下META-INF\services\java.sql.Driver文本中的类名称去注册
        // 3.不过建议还是写上 CLassforName(“com.mysql.jdbc.Driver”),更加明确
        Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");

        String url = "jdbc:mysql://localhost:3306/sys_db02";
        String user = "root";
        String password = "sys";

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

3.5 方式五

//使用配置文件,连接数据库更灵活
1.Connection connection =
    DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb","root""root”);
//中的字符串 各个值,比如端口,数据库,用户名,密码为了方便,我们可以将信息写入到.properties 文件中,方便操作
2.jdbc.properties                      
user=root
password=sys
url=jdbc:mysql://localhost:3306/girls
driver=com.mysql.jdbc.Driver
  1. 案例演示:

    • 先创建一个properties文件,把相应的数据放入
    user=root
    password=sys
    url=jdbc:mysql://localhost:3306/sys_db02
    driver=com.mysql.jdbc.Driver
    
    • 然后进行操作:
    package com.song.jdbc;
    
    import com.mysql.jdbc.Driver;
    import org.junit.jupiter.api.Test;
    
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.Properties;
    
    /**
     * 分析java连接mysql的5种方式
     */
    public class JdbcConn {
        //第五种方式
        @Test
        public void connect05() throws IOException, ClassNotFoundException, SQLException {
            //通过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<?> aClass = Class.forName("com.mysql.jdbc.Driver");
    
            Connection connection = DriverManager.getConnection(url, user, password);
            System.out.println("第五种方式:"+connection);
        }
    }
    

3.6 数据库连接小练习

使用方式5完成下面数据库的连接

  1. 创建 news 表
  2. 使用 jdbc 添加 5条数据
  3. 修改 id = 1的记录,将content 改成 一个新的消息
  4. 删除id = 3 的记录

思路分析:

  • 先在MySQL中创建一个news表
-- 1. 创建 news 表
CREATE TABLE news(
	id INT PRIMARY KEY AUTO_INCREMENT,-- 主键自增长
	content VARCHAR(64)NOT NULL DEFAULT'')

-- 查询
SELECT * FROM news;
  • 然后在src下创建一个mysql.properties 存入mysql的相关信息
user=root
password=sys
url=jdbc:mysql://localhost:3306/sys_db02
driver=com.mysql.jdbc.Driver
  • 接下来idea连接mysql并对news表格进行相应的操作
package com.song.jdbc;

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

public class JdbcExercise {
    public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException {
        //使用方式5完成下面数据库的连接
        //1. 创建 news 表
        //在MySQL中创建一张news表
        /**
         * CREATE TABLE news(
         * 	id INT,
         * 	content VARCHAR(64)NOT NULL DEFAULT'')
         */
        //通过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);

        //尝试给数据库连接到指定的 url
        Connection connection = DriverManager.getConnection(url, user, password);

        //2. 使用 jdbc 添加 5条数据
        //添加 sql 数据
        String sql1 = "insert into news values(null,'上海新闻')";
        String sql2 = "insert into news values(null,'北京新闻')";
        String sql3 = "insert into news values(null,'深圳新闻')";
        String sql4 = "insert into news values(null,'广州新闻')";
        String sql5 = "insert into news values(null,'江苏新闻')";
        //statement 用于执行静态SQL语句并返回其生成的结果的对象
        Statement statement = connection.createStatement();
        int i1 = statement.executeUpdate(sql1);
        int i2 = statement.executeUpdate(sql2);
        int i3 = statement.executeUpdate(sql3);
        int i4 = statement.executeUpdate(sql4);
        int i5 = statement.executeUpdate(sql5);
        //添加提示信息
        System.out.println(i1>0?"i1执行成功":"i1执行失败");
        System.out.println(i2>0?"i2执行成功":"i2执行失败");
        System.out.println(i3>0?"i3执行成功":"i3执行失败");
        System.out.println(i4>0?"i4执行成功":"i4执行失败");
        System.out.println(i5>0?"i5执行成功":"i5执行失败");

        //3. 修改 id = 1的记录,将content 改成 一个新的消息
        String sql6 = "update news set content = 'sys学java' where id = 1";
        int i6 = statement.executeUpdate(sql6);
        System.out.println(i6>0?"i6执行成功":"i6执行失败");
        //4. 删除id = 3 的记录
        String sql7 = "delete from news where id = 3";
        int i7 = statement.executeUpdate(sql7);
        System.out.println(i7>0?"i7执行成功":"i7执行失败");
    }
}

执行idea显示

JDBC 和数据库连接池_第8张图片

接下来打开mysql中的news表,看到已经有相应的数据了

JDBC 和数据库连接池_第9张图片

4. ResultSet[结果集]

4.1 基本介绍

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

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

  3. next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,

    因此可以在while循环中使用循环来遍历结果集

JDBC 和数据库连接池_第10张图片

4.2 应用实例

取出前面创建的演员表里面的数据

package com.song.jdbc.resultset_;

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

/**
 * 演示select语句返回Resultset,并取出结果
 */
public class Resultset_ {
    public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {

        //通过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. 尝试给数据库连接到指定的 url
        Connection connection = DriverManager.getConnection(url, user, password);

        //3. 得到一个statement
        Statement statement = connection.createStatement();

        //4. 组织一个sql
        String sql = "select id,name,sex,borndate from actor";

        //执行给定的SQL语句,该语句返回单个 Resultset 对象
        /*前面创建的actor表
        mysql> select id,name,sex,borndate from actor;
        +----+-----------+-----+---------------------+
        | id | name      | sex | borndate            |
        +----+-----------+-----+---------------------+
        |  1 | 周杰伦    | 男  | 1979-01-18 00:00:00 |
        |  2 | 林俊杰    | 男  | 1981-03-27 00:00:00 |
        +----+-----------+-----+---------------------+
         */

        ResultSet resultSet = statement.executeQuery(sql);
        /*进行源码阅读
        具体看示意图
         */

        //5. 使用while循环取出数据
        // resultSet.next() 让光标向后移动,如果没有更多行,则返回 false
        while (resultSet.next()){
            //获取该行的第 1 列
            //通过列名来获取值, 推荐
            int id = resultSet.getInt(1);
            //获取该行的第 2 列
            String name = resultSet.getString(2);
            //获取该行的第 3 列
            String sex = resultSet.getString(3);
            //获取该行的第 4 列
            Date borndate = resultSet.getDate(4);

            //输出相应数据
            System.out.println(id+"\t"+name+"\t"+sex+"\t"+borndate);
        }

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

代码运行后结果显示:

JDBC 和数据库连接池_第11张图片

resultset源码阅读的示意图

JDBC 和数据库连接池_第12张图片

5. Statement

5.1 基本介绍

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

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

    • Statement[存在SQL注入问题]
    • PreparedStatement[预处理]
    • CallableStatement [存储过程]
  3. Statement对象执行SQL 语句,存在SQL注入风险

  4. SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输数据中注入非法的 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'
-- 这样where后面的语句最后一条'1' = '1'恒成立
  1. 要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了,下面进行代码演示.
package com.song.jdbc.statement_;

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

/**
 * 演示statement的注入问题
 */
public class Stetement {
    public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
        Scanner scanner = new Scanner(System.in);
        //让用户输入管理员名和密码
        //如果希望看到SQL注入,这里需要用nextline
        //因为next输入空格或 ' 就表示结束了
        System.out.println("请输入管理员的名字:");
        String admin_name = scanner.nextLine();
        System.out.println("请输入管理员的密码");
        String admin_pwd = scanner.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. 尝试给数据库连接到指定的 url
        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();
    }
}

代码执行过后,我们先输入正确的用户名tom和密码123,成功登陆

JDBC 和数据库连接池_第13张图片

但是当我们输入用户名 1’or 和万能密码 or’1’='1,发现也成功登陆了

JDBC 和数据库连接池_第14张图片

6. PreparedStatement

6.1 基本介绍

  1. PreparedStatement 执行的 SQL 语句中的参数用问号(?)来表示,调用PreparedStatement 对象的 setXxx() 方法来设置这些参数, setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值
  2. 调用 executeQuery0,返回 ResultSet 对象
  3. 调用 executeUpdate0: 执行更新,包括增、删、修改

6.2 预处理好处

  1. 不再使用+ 拼接sql语句,减少语法错误
  2. 有效的解决了sql注入问题!
  3. 大大减少了编译次数,效率较高

6.3 情景案例

1. 查询语句

package com.song.jdbc.preparedstatemenet_;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;

/**
 * 演示preparedstatement的select使用
 */
public class PreparedStatement_ {
    public static void main(String[] args) throws Exception {
        //看一下PreparedStatement类图

        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入管理员名字");
        String admin_name = scanner.nextLine();
        System.out.println("请输入管理员密码");
        String admin_pwd = scanner.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. 尝试给数据库连接到指定的 url
        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) 使用 execyteUpdate()
        //  这里执行 executeQuery() 语句不用再填写 sql
        ResultSet resultSet = preparedStatement.executeQuery();

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

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

执行代码后,先输入正确的用户名和密码,发现可以正常登录

JDBC 和数据库连接池_第15张图片

然后进行SQL注入,发现万能密码不管用了,大大提高了安全性

JDBC 和数据库连接池_第16张图片

2. DML操作(update,insert,delete)

  1. inset操作
package com.song.jdbc.preparedstatemenet_;

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

/**
 * 演示preparedstatement的dml使用
 */
public class PreparedStatementDML {
    public static void main(String[] args) throws Exception {

        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入管理员名字");
        String admin_name = scanner.nextLine();
        System.out.println("请输入管理员密码");
        String admin_pwd = scanner.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. 尝试给数据库连接到指定的 url
        Connection connection = DriverManager.getConnection(url, user, password);

        //3. 得到一个PreparedStatement
        // 3.1 组织sql语句 , sql语句中的 ? 相当于占位符
        String sql = "insert into admin values(?,?) ";
        // 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) 使用 execyteUpdate()
        //  这里执行 executeQuery() 语句不用再填写 sql
        int rows = preparedStatement.executeUpdate();
        //显示提示
        System.out.println(rows>0?"执行成功":"执行失败");

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

执行代码

JDBC 和数据库连接池_第17张图片

查看mysql中的admin表格

JDBC 和数据库连接池_第18张图片

  1. update操作
package com.song.jdbc.preparedstatemenet_;

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

/**
 * 演示preparedstatement的dml使用
 */
public class PreparedStatementDML {
    public static void main(String[] args) throws Exception {

        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入管理员名字");
        String admin_name = scanner.nextLine();
        System.out.println("请输入管理员新密码");
        String admin_pwd = scanner.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. 尝试给数据库连接到指定的 url
        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 = ? ";
        // 3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        // 3.3 给 ? 赋值
        preparedStatement.setString(1,admin_pwd);
        preparedStatement.setString(2,admin_name);

        //4. 执行select语句 使用 executeQuery()
        //  如果执行的是 dml(update,insert,delete) 使用 execyteUpdate()
        //  这里执行 executeQuery() 语句不用再填写 sql
        int rows = preparedStatement.executeUpdate();
        //显示提示
        System.out.println(rows>0?"执行成功":"执行失败");

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

执行代码

JDBC 和数据库连接池_第19张图片

查看mysql中的admin表格

JDBC 和数据库连接池_第20张图片

  1. delete操作
package com.song.jdbc.preparedstatemenet_;

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

/**
 * 演示preparedstatement的dml使用
 */
public class PreparedStatementDML {
    public static void main(String[] args) throws Exception {

        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要删除的管理员名字");
        String admin_name = scanner.nextLine();
        //System.out.println("请输入管理员新密码");
        //String admin_pwd = scanner.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. 尝试给数据库连接到指定的 url
        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_name);

        //4. 执行select语句 使用 executeQuery()
        //  如果执行的是 dml(update,insert,delete) 使用 execyteUpdate()
        //  这里执行 executeQuery() 语句不用再填写 sql
        int rows = preparedStatement.executeUpdate();
        //显示提示
        System.out.println(rows>0?"执行成功":"执行失败");

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

执行代码

JDBC 和数据库连接池_第21张图片

查看mysql中的admin表格

JDBC 和数据库连接池_第22张图片

6.4 课堂小练习

参考上面的代码

  1. 创建 admin表
  2. 使用PreparedStatement 添加 5条数据
  3. 修改 tom 的记录,将name 改成 king
  4. 删除 一条 的记录
  5. 查询全部记录,并显示在控制台

代码如下:

  1. 表格就用上面创建好的admin表格了

  2. 然后进行dml操作

package com.song.jdbc.preparedstatemenet_;

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

/**
 * 1. 创建 admin表
 * 2. 使用PreparedStatement 添加 5条数据
 * 3. 修改 tom 的记录,将name 改成 king
 * 4. 删除 一条 的记录
 * 5. 查询全部记录,并显示在控制台
 */
public class PreparedStatementExercise {
    public static void main(String[] args) throws Exception {

//        1. 创建 admin表
        //就用已经创建好的admin表
        /*
        mysql> select * from admin;
        +------+-----+
        | name | pwd |
        +------+-----+
        | tom  | 123 |
        +------+-----+
        1 row in set (0.00 sec)
         */

        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要删除的名字");
        String admin_name = scanner.nextLine();
        //System.out.println("请输入新的名字");
        //String admin_name1 = scanner.nextLine();
        //System.out.println("请输入要修改的名字");
        //String admin_name2 = scanner.nextLine();
        //System.out.println("请输入管理员密码");
        //String admin_pwd = scanner.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. 尝试给数据库连接到指定的 url
        Connection connection = DriverManager.getConnection(url, user, password);

        //3. 得到一个PreparedStatement
        // 3.1 组织sql语句 , sql语句中的 ? 相当于占位符

        //2. 使用PreparedStatement 添加 5条数据
        //String sql = "insert into admin values(?,?) ";
        //3. 修改 tom 的记录,将name 改成 king
        //String sql = "update admin set name = ? where name = ? ";
        //4. 删除 一条 的记录
        String sql = "delete from admin where name = ? ";
        //5. 查询全部记录,并显示在控制台
        /*
        mysql> select * from admin;
        +------+-----+
        | name | pwd |
        +------+-----+
        | jack | 222 |
        | king | 123 |
        | lily | 666 |
        | lucy | 333 |
        | sys  | abc |
        +------+-----+
        5 rows in set (0.00 sec)
         */

        // 3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        // 3.3 给 ? 赋值
        preparedStatement.setString(1,admin_name);
        //preparedStatement.setString(1,admin_name1);
        //preparedStatement.setString(2,admin_name2);

        //4. 执行select语句 使用 executeQuery()
        //  如果执行的是 dml(update,insert,delete) 使用 execyteUpdate()
        //  这里执行 executeQuery() 语句不用再填写 sql
        int rows = preparedStatement.executeUpdate();
        //显示提示
        System.out.println(rows>0?"执行成功":"执行失败");

        //关闭连接
        preparedStatement.close();
        connection.close();
    }
}
  1. 查看最终的表格

JDBC 和数据库连接池_第23张图片

7. JDBC 的相关API 小结

JDBC 和数据库连接池_第24张图片

8. 封装 JDBCUtils 【关闭连接, 得到连接】

8.1 说明

  1. 在jdbc 操作中,获取连接和 释放资源 是经常使用到,可以将其封装

  2. JDBC连接的工具类JDBCUtils

JDBC 和数据库连接池_第25张图片

8.2 JDBC工具类代码实现

  1. 创建一个JDBC工具类JDBCUtils
package com.song.jdbc.utils;

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;
    private static String driver;

    //在static代码块去初始化
    static {
        try {
            Properties properties = new Properties();
            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);
        }
    }
    public static Connection getConnection(){
        try {
            return DriverManager.getConnection(url,user,password);
        } catch (SQLException e) {
            //1. 编译异常转成运行异常
            //2.调用者可以选择捕获该异常,也可以选择默认处理,比较方便
            throw new RuntimeException(e);
        }
    }

    //关闭资源
    /*可能会需要关闭的
        1.Resultset 结果集
        2.Statement 或者 PreparedStatement
        3.Connection
        4.如果需要关闭资源,就传入对象,否则传入null
     */

    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);
        }
    }
}

8.3 实际使用使用工具类 JDBCUtils

  1. 使用上面创建的工具类进行增删改查

    这里就只弄一个select和insert演示

package com.song.jdbc.utils;

import org.junit.jupiter.api.Test;

import java.sql.*;

/**
 * 该类演示如何使用JDBCUtils工具类,完成dml和select
 */
public class JDBCUtils_Use {
    //查询 select
    @Test
    public void testSelect(){
        //提取出来初始化,扩大作用范围
        //1.得到连接
        Connection connection = null;
        //2.组织一个sql
        String sql = "select * from actor";
        //3.创建PreparedStatement 对象
        PreparedStatement preparedStatement = null;

        ResultSet resultSet = null;

        try {
            //1.得到连接
            connection = JDBCUtils.getConnection();

            //3.创建PreparedStatement 对象
            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");
                Date borndate = resultSet.getDate("borndate");
                String phone = resultSet.getString("phone");

                System.out.println(id+"\t"+name+"\t"+sex+"\t"+borndate+"\t"+phone);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCUtils.close(resultSet,preparedStatement,connection);
        }
    }
    //插入 insert
    @Test
    public void testDML(){//insert,update,delete
        //提取出来初始化,扩大作用范围
        //1.得到连接
        Connection connection = null;
        //2.组织一个sql
        String sql = "insert into actor values(?,?,?,?,?)";
        //3.创建PreparedStatement 对象
        PreparedStatement preparedStatement = null;

        try {
            //1.得到连接
            connection = JDBCUtils.getConnection();

            //3.创建PreparedStatement 对象
            preparedStatement = connection.prepareStatement(sql);
            //给占位符赋值
            preparedStatement.setInt(1,3);
            preparedStatement.setString(2,"王心凌");
            preparedStatement.setString(3,"女");
            preparedStatement.setDate(4, Date.valueOf("1982-09-05"));
            preparedStatement.setString(5,"12566");
            //执行
            preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCUtils.close(null,preparedStatement,connection);
        }
    }
}

9. 事务

9.1 基本介绍

  1. JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚
  2. JDBC程序中为了让多个 SQL 语句作为一个整体执行,需要使用事务
  3. 调用 Connection 的 setAutoCommit(false) 可以取消自动提交事务
  4. 在所有的 SQL 语句都成功执行后,调用 Connection 的 commit();方法提交事务
  5. 在其中某个操作失败或出现异常时,调用 Connection 的 rollback()); 方法回滚事务

9.2 应用实例

模拟经典的转账业务

  1. 先创建表格
-- 创建表格,用于测试java中的事物
CREATE TABLE `account2`(
	id INT PRIMARY KEY AUTO_INCREMENT,
	`name` VARCHAR(32) NOT NULL DEFAULT '',
	balance DOUBLE NOT NULL DEFAULT 0) CHARACTER SET utf8;

INSERT INTO `account2` VALUES(NULL,'马云', 3000);
INSERT INTO `account2` VALUES(NULL,'马化腾',10000);

SELECT * FROM `account2`
  1. 我们先不使用事物处理,我们发现如果中途发生异常抛出,那么可能会发生前面的扣了钱,但是后面的没转到钱
//没有使用事务.
    @Test
    public void noTransaction() {
        //操作转账业务

        //1.得到连接
        Connection connection = null;
        //2.组织一个sql
        String sql1 = "update account2 set balance = balance - 100 where id = 1";
        String sql2 = "update account2 set balance = balance + 100 where id = 2";
        //3.创建PreparedStatement 对象
        PreparedStatement preparedStatement = null;

        try {
            //在默认情况下,connection是默认自动提交的
            connection = JDBCUtils.getConnection();
            preparedStatement = connection.prepareStatement(sql1);
            //执行
            preparedStatement.executeUpdate();
            int i = 1 / 0;//抛出异常
            preparedStatement = connection.prepareStatement(sql2);
            //执行
            preparedStatement.executeUpdate();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtils.close(null, preparedStatement, connection);
        }
    }
  1. 接着进行事务处理,我们发现如果中途发生异常抛出,那么会直接回滚到最开始的时候,避免出现上面的情况
//使用事物来解决
    @Test
    public void transaction() {
        //操作转账业务

        //1.得到连接
        Connection connection = null;
        //2.组织一个sql
        String sql1 = "update account2 set balance = balance - 100 where id = 1";
        String sql2 = "update account2 set balance = balance + 100 where id = 2";
        //3.创建PreparedStatement 对象
        PreparedStatement preparedStatement = null;

        try {
            //在默认情况下,connection是默认自动提交的
            connection = JDBCUtils.getConnection();
            //这里取消自动提交事务
            connection.setAutoCommit(false);

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

            //这里提交事务
            connection.commit();
        } catch (Exception e) {
            //这里我们可以进行回滚,即撤销执行的 SQL
            //默认回滚到事务开始的状态.
            System.out.println("执行发生了异常,撤销执行的 sql");
            try {
                connection.rollback();
            } catch (SQLException ex) {
                throw new RuntimeException(ex);
            }

            throw new RuntimeException(e);
        } finally {
            JDBCUtils.close(null, preparedStatement, connection);
        }
    }

10. 批处理

10.1 基本介绍

  1. 当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率
  2. JDBC的批量处理语句包括下面方法:
    • addBatch():添加需要批量处理的SQL语句或参数
    • executeBatch(): 执行批量处理语句;
    • clearBatch():清空批处理包的语句
  3. JDBC连接MySQL时,如果要使用批处理功能,请再url中加参数
    • ?rewriteBatchedStatements=true
  4. 批处理往往和PreparedStatement一起搭配使用,可以既减少编译次数,又减少运行次数,效率大大提高

10.2 应用实例

  1. 演示向admin2表中添加5000条数据,看看使用批处理耗时多久

  2. 注意:需要修改 配置文件jdbc.properties

    • url=jdbc:mysql://localhost:3306/数据库?rewriteBatchedStatements=true
  3. 代码演示:

    1. 先在数据库里创建表格admin2
    -- 创建表格
    CREATE TABLE admin2(
    	id INT PRIMARY KEY AUTO_INCREMENT,
    	username VARCHAR(32) NOT NULL ,
    	PASSWORD VARCHAR(32) NOT NULL)
    	
    -- 检查表格数据	
    SELECT * FROM admin2;
    
    -- 统计表格行数
    SELECT COUNT(*) FROM admin2;
    
    1. java连接数据库,先用普通的方法检测添加5000条数据需要多长时间?最终结果显示用了很久
    //传统方法,添加5000条数据到admin2
        @Test
        public void noBatch() throws Exception {
            //1.连接数据
            Connection connection = JDBCUtils.getConnection();
    
            //2.sql语句
            String sql = "insert into admin2 values(null,?,?)";
    
            //3.preparedStatement
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            System.out.println("开始执行");
            //开始时间
            long start = System.currentTimeMillis();
            for (int i = 0; i < 5000; i++) {
                preparedStatement.setString(1,"jack"+i);
                preparedStatement.setString(2,"666");
                preparedStatement.executeUpdate();
            }
            //结束时间
            long end = System.currentTimeMillis();
    
            System.out.println("传统方法消耗时间:"+(end-start));
            //输出 : 传统方法消耗时间:265847
    
            //关闭连接
            JDBCUtils.close(null,preparedStatement,connection);
        }
    
    1. JDBC的批量处理检测添加5000条数据需要多长时间?最终结果显示比普通方法快了成千上万倍
    //批处理,添加5000条数据到admin2
        @Test
        public void batch() throws Exception {
            //1.连接数据
            Connection connection = JDBCUtils.getConnection();
    
            //2.sql语句
            String sql = "insert into admin2 values(null,?,?)";
    
            //3.preparedStatement
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            System.out.println("开始执行");
            //开始时间
            long start = System.currentTimeMillis();
            for (int i = 0; i < 5000; i++) {
                preparedStatement.setString(1,"jack"+i);
                preparedStatement.setString(2,"666");
                //添加需要批量处理的SQL语句或参数
                preparedStatement.addBatch();
    
                if (i+1%1000==0){//每1000条批处理
                    //执行批量处理语句
                    preparedStatement.executeBatch();
                    //清空批处理包的语句
                    preparedStatement.clearBatch();
                }
            }
            //结束时间
            long end = System.currentTimeMillis();
    
            System.out.println("批处理方法消耗时间:"+(end-start));
            //输出 : 批处理方法消耗时间:13
    
    
            //关闭连接
            JDBCUtils.close(null,preparedStatement,connection);
        }
    
    • 特别提醒:url=jdbc:mysql://localhost:3306/数据库 后面加上 ?rewriteBatchedStatements=true
    url=jdbc:mysql://localhost:3306/sys_db02?rewriteBatchedStatements=true
    
    1. 对于preparedStatement.addBatch();代码进行源码阅读
    preparedStatement.addBatch();
    
    //将SQL语句加入到批处理包中 --> 源码
    /*
    //1. 第一就创建 ArrayList - elementData => Object[]
    //2. elementData => Object[] 就会存放我们预处理的 sql 语句
    //3. 当 elementData 满后,就按照 1.5 扩容
    //4. 当添加到指定的值后,就 executeBatch
    //5. 批量处理会减少我们发送 sql 语句的网络开销,而且减少编译次数,因此效率提高
    public void addBatch() throws SQLException {
        if (this.batchedArgs == null) {
        this.batchedArgs = new ArrayList();
        }
    
        this.batchedArgs.add(new BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull));
    }
    */
    

    发现我们在代码运行的过程中就是在一个名字为batchedArgs的ArrayList的集合中不停地放数据,放在elementData的一个对象数组里面,一开始有十个位置,当放满以后按照1.5倍扩容

    JDBC 和数据库连接池_第26张图片

11. 数据库连接池

11.1 5k 次连接数据库问题

  1. 编写程序完成连接MySQL5000次的操作

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

  3. 当我们不关闭连接的时候,会发现系统会报异常 Too many connections

    JDBC 和数据库连接池_第27张图片

  4. 我们现在关闭连接,查看连接mysql 5000次耗费多少时间

package com.song.jdbc.datasource;

import com.song.jdbc.utils.JDBCUtils;
import org.junit.jupiter.api.Test;

import java.sql.Connection;

public class ConQuestion {

    //代码 连接mysql 5000次
    @Test
    public void testConnect(){
        //看看连接-关闭 connection 会耗用多久
        long start = System.currentTimeMillis();
        System.out.println("正在连接中...");
        //连接数据库5000次
        for (int i = 0; i < 5000; i++) {
            //使用传统的 jdbc 方式,得到连接
            Connection connection = JDBCUtils.getConnection();
            //sql
            //处理一些事,比如 preparedStatement
            //关闭连接
            JDBCUtils.close(null,null,connection);
        }
        long end = System.currentTimeMillis();
        System.out.println("连接5000次mysql 耗时= "+(end-start));
        //连接5000次mysql 耗时= 14938
    }
}

11.2 传统获取Connection 问题分析

  1. 传统的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中再验证IP地址,用户名和密码(0.05s~1s时间)。需要数据库连接的时候就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源容易造成服务器崩溃。
  2. 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。
  3. 传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MySQL崩溃
  4. 解决传统开发中的数据库连接问题,可以采用数据库连接池技术(connection pool)

11.3 数据库连接池基本介绍

  1. 预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从"缓冲池"中取出一个,使用完毕后再放回去。
  2. 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
  3. 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将加入等待队列中。
  4. 示意图如下

JDBC 和数据库连接池_第28张图片

11.4 数据库连接池种类

  1. JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource只是一个接口,该接口通常由第三方提供实现[提供 .jar],
  2. C3P0 数据库连接池,速度相对较慢,稳定性不错 (hibernate,spring)
  3. DBCP数据库连接池,速度相对c3p0较快,但不稳定
  4. Proxool数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
  5. BoneCP 数据库连接池,速度快
  6. Druid(德鲁伊)是阿里提供的数据库连接池,集DBCP 、C3PO 、Proxool优点于一身的数据库连接池
  7. 我们一般应用的比较多的是C3P0和Druid(德鲁伊)连接池

11.5 C3P0连接池应用实例

  1. 使用代码实现c3p0数据库连接池,配置文件放src目录下 C3P0 .java

  2. 然后我们同样使用c3p0连接mysql 5000次查看效率

11.5.1 C3P0方式一

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

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

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

11.5.2 C3P0方式二

使用配置文件模板来完成

//方式二:使用配置文件模板来完成
//1.将c3p0 提供的 c3p0.config.xml 拷贝到 src目录下
//2.该文件指定了连接数据库和连接池的相关参数
@Test
public void testC3P0_02() throws SQLException {
    ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("sys_java");
    //测试5000次连接mysql
    System.out.println("开始执行...");
    long start = System.currentTimeMillis();
    for (int i = 0; i < 5000; i++) {
        Connection connection = comboPooledDataSource.getConnection();
        //System.out.println("连接ok");
        //关闭连接
        connection.close();
    }
    long end = System.currentTimeMillis();
    System.out.println("c3p0第二种方式 耗时 = "+(end-start));
    //c3p0第二种方式 耗时 = 540
}

由此可见,使用数据库连接池效率提升特别大。

11.6 Druid(德鲁伊)连接池应用实例

package com.song.jdbc.datasource;

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

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
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);
        //测试使用 Druid 连接五千次
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5000; i++) {
            Connection connection = dataSource.getConnection();
            //System.out.println("连接成功!");
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("使用Druid 耗时 = "+(end-start));
        //使用Druid 耗时 = 590
    }
}

以上数据看来好像c3p0和Druid没多大区别

但是当我们操作的数据量变大的时候,就会看出差距

我们把连接5000次改为5000000次可以看到

c3p0耗时14497ms;

Druid(德鲁伊)耗时1630ms;

由此可见Druid(德鲁伊)连接池的效率特别高

//方式二:使用配置文件模板来完成
//1.将c3p0 提供的 c3p0.config.xml 拷贝到 src目录下
//2.该文件指定了连接数据库和连接池的相关参数
@Test
public void testC3P0_02() throws SQLException {
    ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("sys_java");
    //测试5000次连接mysql
    System.out.println("开始执行...");
    long start = System.currentTimeMillis();
    for (int i = 0; i < 5000000; i++) {
        Connection connection = comboPooledDataSource.getConnection();
        //System.out.println("连接ok");
        //关闭连接
        connection.close();
    }
    long end = System.currentTimeMillis();
    //System.out.println("c3p0第二种方式 耗时 = "+(end-start));
    //c3p0第二种方式 耗时 = 540

    System.out.println("c3p0第二种方式(5000000) 耗时 = "+(end-start));
    //c3p0第二种方式(5000000) 耗时 = 14497
}
/**
 * 测试 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);
        //测试使用 Druid 连接五千次
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5000000; i++) {
            Connection connection = dataSource.getConnection();
            //System.out.println("连接成功!");
            connection.close();
        }
        long end = System.currentTimeMillis();
        //System.out.println("使用Druid 耗时 = "+(end-start));
        //使用Druid 耗时 = 590
        System.out.println("使用Druid(5000000) 耗时 = "+(end-start));
        //使用Druid(5000000) 耗时 = 1630
    }
}

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

我们基于原先的JDBC数据库,把他改为Druid(德鲁伊)实现

package com.song.jdbc.datasource;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.IOException;
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 ds;
    //用静态代码块完成 ds 初始化
    static{
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("src\\druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

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

    //关闭连接
    //注意:这里的关闭连接,在数据库连接池技术中,不是真的断掉连接
    //而是把使用的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);
        }
    }
}

11.8 Druid(德鲁伊)实现的工具类JDBCUtilsByDruid使用

这里我们使用一下Druid(德鲁伊)实现的工具类JDBCUtilsByDruid

package com.song.jdbc.datasource;

import com.song.jdbc.utils.JDBCUtils;
import org.junit.jupiter.api.Test;

import java.sql.*;

/**
 * 该类演示如何使用JDBCUtilsByDruid工具类
 */
public class JDBCUtilsByDruid_Use {
    //查询 select
    @Test
    public void testSelect(){
        //提取出来初始化,扩大作用范围
        //1.得到连接
        Connection connection = null;
        //2.组织一个sql
        String sql = "select * from actor";
        //3.创建PreparedStatement 对象
        PreparedStatement preparedStatement = null;

        ResultSet resultSet = null;

        try {
            //1.得到连接
            connection = JDBCUtilsByDruid.getConncteion();
            //查看一下运行类型,可以看到运行类型是DruidPooledConnection
            //com.alibaba.druid.pool.DruidPooledConnection
            System.out.println(connection.getClass());

            //3.创建PreparedStatement 对象
            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");
                Date borndate = resultSet.getDate("borndate");
                String phone = resultSet.getString("phone");

                System.out.println(id+"\t"+name+"\t"+sex+"\t"+borndate+"\t"+phone);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCUtilsByDruid.close(resultSet,preparedStatement,connection);
        }
    }
}

11.9 JDBCUtils工具类和JDBCUtilsByDruid工具类的区别

  1. JDBCUtils工具类是MySQL厂商实现了Connection这个接口;
  2. JDBCUtilsByDruid工具类阿里巴巴厂商实现了Connection这个接口;
  3. 但是他们实现的方法不同:(connection.close)
    1. mysql是直接把连接关闭;
    2. Druid(德鲁伊)是把引用的连接放回连接池,等待下一次引用

12. Apache—DBUtils

12.1 前提引出

我们先分析一个问题:

  1. 关闭connection 后,resultSet 结果集无法使用
  2. resultSet 不利于数据的管理
  3. 示意图

JDBC 和数据库连接池_第29张图片

  1. 我们先用以前集合的土方法来把resultSet存在集合中
//使用土方法来解决 ResultSet =封装=> Arraylist
//这里把返回值写成 ArrayList
// 可以让其他对象直接调用这个方法
@Test
//public ArrayList testSelectToArraylist(){
public void testSelectToArraylist(){
    //提取出来初始化,扩大作用范围
    //1.得到连接
    Connection connection = null;
    //2.组织一个sql
    String sql = "select * from actor where id >= 1";
    //3.创建PreparedStatement 对象
    PreparedStatement preparedStatement = null;

    ResultSet resultSet = null;

    //创建一个ArrayList对象,存放actor对象
    ArrayList<Actor> list = new ArrayList<Actor>();

    try {
        //1.得到连接
        connection = JDBCUtilsByDruid.getConncteion();
        //查看一下运行类型,可以看到运行类型是DruidPooledConnection
        //com.alibaba.druid.pool.DruidPooledConnection
        System.out.println(connection.getClass());

        //3.创建PreparedStatement 对象
        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");
            Date borndate = resultSet.getDate("borndate");
            String phone = resultSet.getString("phone");
            //把得到的 Resultset 的记录,封装到 Actor 对象,放入到 List集合
            list.add(new Actor(id,name,sex,borndate,phone));
        }
        //System.out.println("List集合数据 = "+list);

        //现在我们可以直接从ArrayList集合中取出我们想要的数据
        //增强for取出数据
        for (Actor actor:list){
            System.out.println("id ="+actor.getId()+"\t"+actor.getName());
            //id =1	周杰伦
            //id =2	林俊杰
            //id =3	王心凌
        }

    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        //关闭资源
        JDBCUtilsByDruid.close(resultSet,preparedStatement,connection);
    }
    //因为 ArrayList 和 connection 没有任何关联,所以该集合可以复用
    //return list;
}

12.2 Apache基本介绍

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

12.3 DbUtils类

  1. QueryRunner类: 该类封装了SQL的执行,是线程安全的。可以实现增、删、改、查、批处理
  2. 使用QueryRunner类实现查询
  3. ResultSetHandler接口: 该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式
ArrayHandler: 把结果集中的第一行数据转成对象数组。
ArrayListHandler: 把结果集中的每一行数据都转成一个数组,再存放到ListBeanHandler: 将结果集中的第一行数据封装到一个对应的JavaBean实例中。
BeanListHandler: 将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List.
ColumnListHandler: 将结果集中某一列的数据存放到List中。
KevedHandler(name): 将结果集中的每行数据都封装到Map里,再把这些map再存到一个map里,其key为指定的key
MapHandler: 将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值
MapListHandler: 将结果集中的每一行数据都封装到一个Map里,然后再存放到List

12.4 应用实例

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3zNTd5nk-1687625487228)(E:\Java study\Markdown\本地图片\actor表.png)]

12.4.1 返回的结果是多行记录及底层源码阅读

//使用 apache-DBUtils 工具类 + druid 完成对表的 crud 操作
    @Test
    public void testQueryMany() throws SQLException {//返回的是多行的情况
        //1.得到连接
        Connection conncteion = JDBCUtilsByDruid.getConncteion();
        //2.使用DBUTils类和接口,
        // 2.1 先引入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(conncteion, sql, new BeanListHandler<>(Actor.class), 1);
        /**
         * 分析 queryRunner.query 方法:
         * public  T query(Connection conn, String sql, ResultSetHandler rsh, Object... params) throws SQLException {
         *         PreparedStatement stmt = null;//定义 PreparedStatement
         *         ResultSet rs = null;//接收返回一个 ResultSet 结果集
         *         T 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
         *         } catch (SQLException var33) {
         *             this.rethrow(var33, sql, params);
         *         } finally {
         *             try {
         *                 this.close(rs);//关闭 resultset对象
         *             } finally {
         *                 this.close((Statement)stmt);//关闭 statement对象
         *             }
         *         }
         *
         *         return result;
         *     }
         */
        System.out.println("输出集合的信息");
        for (Actor actor:list){
            System.out.println(actor);
        }
        //释放资源
        //上面的queryrunner中已经自动关闭了Resultset和statement
        //所以这里只需要关闭connection就可以了
        JDBCUtilsByDruid.close(null,null,conncteion);
    }

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

//演示apache-dbutils + druid 完成 返回的结果是单行记录(单个对象)
    @Test
    public void testQuerySingle() throws SQLException {
        //1.得到连接
        Connection conncteion = JDBCUtilsByDruid.getConncteion();
        //2.使用DBUTils类和接口,
        // 2.1 先引入DBUtils相关的jar,加入到本Project
        //3.创建QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        //4.然后执行相关的方法,返回单个结果
        String sql = "select * from actor where id = ?";
        //因为我们返回的是单行记录<--->单个对象,使用的 Handler 是 BeanHandler
        Actor actor =
                queryRunner.query(conncteion, sql, new BeanHandler<>(Actor.class), 1);
        System.out.println(actor);
        //释放资源
        JDBCUtilsByDruid.close(null,null,conncteion);
    }

12.4.3 演示 apache + druid 完成查询结果是单行单列

//演示 apache + druid 完成查询结果是单行单列 -- 返回的就是 object
    @Test
    public void testScalsar() throws SQLException {
        //1.得到连接
        Connection conncteion = JDBCUtilsByDruid.getConncteion();
        //2.使用DBUTils类和接口,
        // 2.1 先引入DBUtils相关的jar,加入到本Project
        //3.创建QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        //4.然后执行相关的方法,返回单个结果
        String sql = "select name from actor where id = ?";
        //因为返回的是一个对象,使用的 handler 就是 ScalarHandler
        Object obj =
                queryRunner.query(conncteion, sql, new ScalarHandler(), 1);
        System.out.println(obj);
        //释放资源
        JDBCUtilsByDruid.close(null,null,conncteion);
    }

12.4.4 演示 apache + druid 完成 dml 操作(update,insert,delete)

//演示 apache + druid 完成 dml 操作(update,insert,delete)
    @Test
    public void testDml() throws SQLException {
        //1.得到连接
        Connection conncteion = JDBCUtilsByDruid.getConncteion();
        //2.使用DBUTils类和接口,
        // 2.1 先引入DBUtils相关的jar,加入到本Project
        //3.创建QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        //4.然后执行相关的方法,返回单个结果
        //添加数据 insert
        //String sql = "insert into actor values(null,'蔡依林','女','1980-9-15','123')";
        //修改数据 update
        //String sql = "update actor set name = '昆凌' where id = ?";
        //删除数据 delete
        String sql = "delete from actor where id = ? ";

        //(1) 执行 dml 操作是 queryRunner.update()
        //(2) 返回的值是受影响的行数(affected:受影响)
        int affectedRow = queryRunner.update(conncteion, sql, 4);
        System.out.println(affectedRow>0?"执行成功":"执行没有影响到表");
    }

12.5 表和JavaBean 的类型映射关系

JDBC 和数据库连接池_第30张图片

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

13.1 前提引出

我们先分析一个问题:

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

  1. SQL 语句是固定,不能通过参数传入,通用性不好,需要进行改进,更方便执行 增删改查
  2. 对于select 操作,如果有返回值 , 返回类型不能固定,需要使用泛型
  3. 将来的表很多,业务需求复杂,不可能只靠一个Java类完成
  4. 从而引出 => BasicDAO
  5. BasicDAO 示意图如下,看看在实际开发中,应该如何处理

JDBC 和数据库连接池_第31张图片

13.2 基本说明

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

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

  3. 在BaiscDao 的基础上,实现一张表 对应一个Dao,更好的完成功能,

    比如 Customer表Customeriava类(javabean)-CustomerDao.java

13.3 应用实例

完成一个简单设计

先创建一个包com.song.dao_

然后在这个包下创建一些子包

  1. 先创建actor表
-- 创建表格
CREATE TABLE new_goods(
	id INT PRIMARY KEY AUTO_INCREMENT,
	goods_name VARCHAR(10),
	price DOUBLE);
  1. com.song.dao_ .utils // 工具类

    • 创建一个durid.properties ,将相关信息填入
    #key=value
    driverClassName=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/sys_db02?rewriteBatchedStatements=true
    #url=jdbc:mysql://localhost:3306/sys_db02
    username=root
    password=sys
    #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
    
    • 然后创建JDBC工具类
    package com.song.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 ds;
        //用静态代码块完成 ds 初始化
        static{
            Properties properties = new Properties();
            try {
                properties.load(new FileInputStream("src\\druid.properties"));
                ds = DruidDataSourceFactory.createDataSource(properties);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        //编写getConnection方法
        public static Connection getConncteion() throws SQLException {
            return ds.getConnection();
        }
    
        //关闭连接
        //注意:这里的关闭连接,在数据库连接池技术中,不是真的断掉连接
        //而是把使用的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);
            }
        }
    }
    
  2. com.song.dao_ .domain // javabean

    • 创建Goods类
    package com.song.dao_.domain;
    
    public class Goods {
        private Integer id;
        private String name;
        private Double price;
    
        public Goods() {
        }
    
        public Goods(Integer id, String name, Double price) {
            this.id = id;
            this.name = name;
            this.price = price;
        }
    
        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 Double getPrice() {
            return price;
        }
    
        public void setPrice(Double price) {
            this.price = price;
        }
    
        @Override
        public String toString() {
            return "\nGoods{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", price=" + price +
                    '}';
        }
    }
    
  3. com.song.dao_ .dao // 存放XxXDAO 和BasicDAO

    • 开发 BasicDAO,是其他 DAO 的父类
    package com.song.dao_.dao;
    
    import com.song.jdbc.datasource.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.getConncteion();
                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.getConncteion();
                return qr.query(connection,sql,new BeanListHandler<>(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.getConncteion();
                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.getConncteion();
                return qr.query(connection,sql,new ScalarHandler(),parameters);
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
                JDBCUtilsByDruid.close(null,null,connection);
            }
        }
    }
    
    • 创建子类ActorDAO
    package com.song.dao_.dao;
    
    public class GoodsDAO extends BasicDAO{
        //1.有BasicDAO的方法
        //2.根据业务需求,可以添加自己特有的方法
    }
    
  4. com.song.dao_ .test // 写测试类

    package com.song.dao_.test;
    
    import com.song.dao_.dao.ActorDAO;
    import com.song.dao_.domain.Actor;
    import org.junit.jupiter.api.Test;
    
    import java.util.List;
    
    public class TestDAO {
        //测试 ActorDAO 对 actor 表 crud 操作
        @Test
        public void testDAO(){
            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.查询单行
            Object o =
                    actorDAO.querySingle("select * from actor where id = ?", Actor.class, 1);
            System.out.println("====查询单行显示====");
            System.out.println(o);
            
            //3.查询单行单列
            Object o2 =
                    actorDAO.queryScalar("select name from actor where id = ?", 1);
            System.out.println("====查询单行单列====");
            System.out.println(o2);
    
            //4.进行dml操作     update,insert,delete
            //insert
            /*
            int update = actorDAO.update("insert into actor values(null,?,?,?,?)",
                    "蔡依林", "女", "1980-9-15", "123");
            System.out.println(update>0?"执行成功":"执行没有影响表");
             */
    
            //update
            /*
            int update =
                    actorDAO.update("update actor set name = '昆凌' where id = ?", 4);
            System.out.println(update>0?"执行成功":"执行没有影响表");
             */
    
            //delete
            int update =
                    actorDAO.update("delete from actor where id = ?", 4);
            System.out.println(update>0?"执行成功":"执行没有影响表");
        }
    }
    

14. 课后练习

开发GoodsDao和Goods, 完成对goods表的crud.

表格示意图如下:

JDBC 和数据库连接池_第32张图片

JDBC 和数据库连接池_第33张图片

  1. 先创建actor表
-- 创建演员表测试

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));
  1. com.song.dao_ .utils // 工具类

    • 创建一个durid.properties ,将相关信息填入
    #key=value
    driverClassName=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/sys_db02?rewriteBatchedStatements=true
    #url=jdbc:mysql://localhost:3306/sys_db02
    username=root
    password=sys
    #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
    
    • 然后创建JDBC工具类
    package com.song.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 ds;
        //用静态代码块完成 ds 初始化
        static{
            Properties properties = new Properties();
            try {
                properties.load(new FileInputStream("src\\druid.properties"));
                ds = DruidDataSourceFactory.createDataSource(properties);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        //编写getConnection方法
        public static Connection getConncteion() throws SQLException {
            return ds.getConnection();
        }
    
        //关闭连接
        //注意:这里的关闭连接,在数据库连接池技术中,不是真的断掉连接
        //而是把使用的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);
            }
        }
    }
    
  2. com.song.dao_ .domain // javabean

    • 创建Goods类
    package com.song.dao_.domain;
    
    public class Goods {
        private Integer id;
        private String goods_name;
        private Double price;
    
        public Goods() {
        }
    
        public Goods(Integer id, String goods_name, Double price) {
            this.id = id;
            this.goods_name = goods_name;
            this.price = price;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getGoods_name() {
            return goods_name;
        }
    
        public void setGoods_name(String goods_name) {
            this.goods_name = goods_name;
        }
    
        public Double getPrice() {
            return price;
        }
    
        public void setPrice(Double price) {
            this.price = price;
        }
    
        @Override
        public String toString() {
            return "\nGoods{" +
                    "id=" + id +
                    ", goods_name='" + goods_name + '\'' +
                    ", price=" + price +
                    '}';
        }
    }
    
  3. com.song.dao_ .dao // 存放XxXDAO 和BasicDAO

    • 开发 BasicDAO,是其他 DAO 的父类
    package com.song.dao_.dao;
    
    import com.song.jdbc.datasource.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.getConncteion();
                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.getConncteion();
                return qr.query(connection,sql,new BeanListHandler<>(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.getConncteion();
                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.getConncteion();
                return qr.query(connection,sql,new ScalarHandler(),parameters);
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
                JDBCUtilsByDruid.close(null,null,connection);
            }
        }
    }
    
    • 创建子类GoodsDAO
    package com.song.dao_.dao;
    
    public class GoodsDAO extends BasicDAO{
        //1.有BasicDAO的方法
        //2.根据业务需求,可以添加自己特有的方法
    }
    
  4. com.song.dao_ .test // 写测试类

    package com.song.dao_.test;
    
    import com.song.dao_.dao.GoodsDAO;
    import com.song.dao_.domain.Goods;
    import org.junit.jupiter.api.Test;
    
    import java.util.List;
    
    public class TestDAO2 {
        //测试 GoodsDAO 对 new_goods 表 crud 操作
        //先进行 dml 操作
        @Test
        public void testDAO(){
            GoodsDAO goodsDAO = new GoodsDAO();
            //insert
            /*
            int update1 = goodsDAO.update
                    ("insert into new_goods values(null,?,?)", "小米手机", 1999);
            int update2 = goodsDAO.update
                    ("insert into new_goods values(null,?,?)", "魅族手机", 1999);
            int update3 = goodsDAO.update
                    ("insert into new_goods values(null,?,?)", "三星手机", 3999);
            int update4 = goodsDAO.update
                    ("insert into new_goods values(null,?,?)", "苹果手机", 8999);
            int update5 = goodsDAO.update
                    ("insert into new_goods values(null,?,?)", "华为手机", 9999);
            int update6 = goodsDAO.update
                    ("insert into new_goods values(null,?,?)", "华为手机", 9999);
    
            System.out.println(update1>0?"第一条执行成功":"执行没有影响表");
            System.out.println(update2>0?"第二条执行成功":"执行没有影响表");
            System.out.println(update3>0?"第三条执行成功":"执行没有影响表");
            System.out.println(update4>0?"第四条执行成功":"执行没有影响表");
            System.out.println(update5>0?"第五条执行成功":"执行没有影响表");
            System.out.println(update6>0?"第五条执行成功":"执行没有影响表");
             */
    
            //update
            /*
            int update = goodsDAO.update
                    ("update new_goods set goods_name = 'OPPO' where id = ?", 6);
            System.out.println(update>0?"修改执行成功":"执行没有影响表");
             */
    
            //delete
            int update = goodsDAO.update
                    ("delete from new_goods where id = ?", 6);
            System.out.println(update>0?"删除执行成功":"执行没有影响表");
    
        }
        //查找
        @Test
        public void testSelect(){
            GoodsDAO goodsDAO = new GoodsDAO();
            //查找多行
            List<Goods> goods =
                    goodsDAO.queryMulti("select * from new_goods where id >= ?", Goods.class, 1);
            System.out.println("====查询多行显示====");
            for (Goods good : goods) {
                System.out.println(good);
            }
    
            //查找单行
            Object single = goodsDAO.querySingle
                    ("select * from new_goods where id = ?", Goods.class, 1);
            System.out.println("====查询单行显示====");
            System.out.println(single);
    
            //查找单行单列显示
            Object scalar= goodsDAO.queryScalar
                    ("select goods_name from new_goods where id = ?", 1);
            System.out.println("====查询单行单列显示====");
            System.out.println(scalar);
        }
    }
    
  5. 最终显示结果如下:

JDBC 和数据库连接池_第34张图片

你可能感兴趣的:(JDBC,和数据库连接池,MySQL数据库,Java学习,数据库,java,mysql)