JDBC数据库连接技术学习笔记

目录

1. 概述

1.1 JDBC概念和理解

1.2 jdbc核心api和使用路线

1 jdbc技术组成

2 涉及具体核心类和接口

3 jdbc的api使用路线

1.3 为什么选择8+版本mysql-jdbc驱动

2. 核心API

2.1 引入mysql-jdbc驱动jar

2.2 jdbc使用步骤

2.3 基于statement演示查询

2.4 基于statement方式的问题

2.5 基于preparedstatement方式的优化(重点)

2.6 基于preparedstatement方式的CRUB(重点)

2.6.1 数据库数据插入

2.6.2 数据库数据修改

2.6.3 数据库数据删除

2.6.4 数据库数据查询(高级版 用List存储)

2.7 preparedstatement使用方式的总结(重点)

1. 步骤总结

2. 使用API总结

3 全新JDBC扩展提升

3.1 自增长主键回显体现

3.2 批量数据插入性能提升

3.3 JDBC数据库中事务的体现

3.1 章节目标

3.2 事务概念回顾

3.3 数据库表数据

3.4 代码结构设计

 3.5 jdbc事务体现

4 国货之光Druid连接池技术使用

4.1 连接性能消耗问题分析

4.2 数据库连接池作用

4.3 市面常见连接池产品和对比

4.4 国货之光druid连接池使用

1. 硬编码方式(了解不推荐)

2. 软编码方式

3. druid配置(了解)

5 全新JDBC使用优化以及工具类的封装

5.1 jdbc工具类封装v1.0

5.2 jdbc工具类封装v2.0

1. JbdcUtilsV2

2. BankService类和测试方法新版

3. BankDao新版

5.3 高级应用层封装BaseDao

5.4 DQL查询方式封装

6 基于CMS项目JDBC的实战练习

6.1 cms项目介绍和导入

6.2 基于cms项目添加数据库相关配置

1.准备数据库脚本

2.添加配置文件

位置 src下,druid.properties

3. 导入jdbcv2.0工具类

4. 导入baseDao工具类

6.3 基于cms项目实战

1. customerService

2. customerDao


1. 概述

本笔记是学习尚硅谷教育的23版jdbc的课后笔记

1.1 JDBC概念和理解

1. jdbc是(Java Database Connectivity)单词的缩写,翻译为java连接数据库
2. jdbc是java程序连接数据库的技术统称
3. jdbc由java语言的规范(接口)各个数据库厂商的实现驱动(jar)组成
4. jdbc是一种典型的面向接口编程
5. jdbc优势
    1. 只需要学习jdbc规范接口的方法,即可操作所有的数据库软件
    2. 项目中期切换数据库软件,只需要更换对应的数据库驱动jar包,不需要更改代码

1.2 jdbc核心api和使用路线

1 jdbc技术组成

1. jdk下jdbc规范接口, 存储在java.sql和javax.sql包中的api

为了项目代码的可移植性,可维护性,SUN公司从最初就制定了Java程序连接各种数据库的统一接口规范。这样的话,不管是连接哪一种DBMS软件,Java代码可以保持一致性。

2. 各个数据库厂商提供的驱动jar包

因为各个数据库厂商的DBMS软件各有不同,那么内部如何通过sql实现增、删、改、查等管理数据,只有这个数据库厂商自己更清楚,因此把接口规范的实现交给各个数据库厂商自己实现。

jar包是什么?

java程序打成的一种压缩包格式,你可以将这些jar包引入你的项目中,然后你可以使用这个java程序中类和方法以及属性了!

2 涉及具体核心类和接口

3 jdbc的api使用路线

1.3 为什么选择8+版本mysql-jdbc驱动

2. 核心API

2.1 引入mysql-jdbc驱动jar

驱动选择

 导入jar包

2.2 jdbc使用步骤

1. 注册驱动 (导入jar包)
2. 获取连接 (建立connection)
3. 创建发送sql语句对象 (statement)
4. 发送sql语句,并获取返回结果 (statement对象发送sql语句到数据库 并且获取返回结果resultset)
5. 结果集解析  (resultset解析)
6. 资源关闭 (释放connection 释放statement 释放resultset)

2.3 基于statement演示查询

在数据库创建如下数据

CREATE DATABASE testdatabase;

USE testdatabase;

CREATE TABLE t_user(
   id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户主键',
   account VARCHAR(20) NOT NULL UNIQUE COMMENT '账号',
   PASSWORD VARCHAR(64) NOT NULL COMMENT '密码',
   nickname VARCHAR(20) NOT NULL COMMENT '昵称');

INSERT INTO t_user(account,PASSWORD,nickname) VALUES
  ('root','123456','经理'),('admin','666666','管理员');

查询全部用户信息进行控制台输出

基于statement实现查询

public class StatementQueryPart {
    /**
     * TODO:
     *      注册驱动
     *      依赖:驱动版本8+ com.mysql.cj.jdbc.Driver
     *           驱动版本5+ com.mysql.cj.Driver
     */
    public static void main(String[] args) throws SQLException {
        //1.注册驱动
        /**
         * TODO: 注意
         *   Driver -> com.mysql.cj.jdbc.Driver
         */
        DriverManager.registerDriver(new Driver());

        //2.获取连接
        /**
         * TODO: 注意
         *   面向接口编程
         *   java.sql 接口 = 实现类
         *   connection 使用java.sql.Connection接口接收
         *   url格式
         *   jdbc:数据库厂商://ip地址:port/数据库名称
         *   jdbc:mysql://localhost:3306/testdatabase
         */
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/testdatabase",
                "root",
                "你的密码");

        //3.创建小车
        Statement statement = connection.createStatement();

        //4.发送SQL语句
        String sql = "select id,account,password,nickname from t_user ;";
        ResultSet resultSet = statement.executeQuery(sql);

        //5.结果集解析
        //按行读取,没有则为false
        while (resultSet.next()){
            int id = resultSet.getInt("id");
            String account = resultSet.getString("account");
            String password = resultSet.getString("password");
            String nickname = resultSet.getString("nickname");
            System.out.println(id+"::"+account+"::"+password+"::"+nickname);
        }

        //6.关闭资源  【先开后关】
        resultSet.close();
        statement.close();
        connection.close();

    }
}

2.4 基于statement方式的问题


/**
 * ClassName: StatementUserLoginPart
 * Description:
 *      模拟用户登录
 *      1.明确jdbc使用流程和详细讲解内部设计api
 *      2.发现问题 引出prepared Statement
 * TODO:
 *    输入账号密码
 *    进行数据库信息查询(t_user)
 *    反馈登录成功与否
 */

public class StatementUserLoginPart {

    public static void main(String[] args) throws ClassNotFoundException, SQLException {

        //1.输入账号和密码
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入账号: ");
        String account = scanner.nextLine();
        System.out.println("请输入密码: ");
        String password = scanner.nextLine();
        scanner.close();

        //2.jdbc的查询使用
        /**
         * 类加载: java文件 -> 编译 -> 【 class字节码文件 -->  类加载 --> jvm虚拟中  --> Class对象】
         * 类加载具体步骤:  加载 【class文件转成对象加载到虚拟机中】->
         *                连接 【验证(检查类文件) -> 准备 (静态变量赋默认值) -> 解析 (调用静态代码块) 】 ->
         *                初始化 -> (赋真实值)
         * 以下7种方式会触发类加载:
         *    1. new关键字
         *    2. 调用静态属性
         *    3. 调用静态方法
         *    4. 接口 包含1.8 新特性 default关键字
         *    5. 反射 【Class.forName() 类名.class】
         *    6. 子类调用会触发父类的静态代码块
         *    7. 触发类的入口方法main
         */
        //注册一次驱动
        // 字符串 -> 提取到外部配置文件 -> xx.properties -> oracle -> 配置文件的修改
        Class.forName("com.mysql.cj.jdbc.Driver");

        /**
         * 重写: 为了子类扩展父类的方法!父类也间接的规范了子类方法的参数和返回!
         * 重载: 重载一般应用在第三方的工具类上,为了方便用户多种方式传递参数形式!简化形式!
         */
        /**
         * 三个参数:
         *    String URL: 连接数据库地址
         *    String user: 连接数据库用户名
         *    String password: 连接数据库用户对应的密码
         * 数据库URL语法:
         *    JDBC:
         *        jdbc:[mysql / oracle]://ip:port/database
         *        如:
         *        jdbc:mysql | jdbc:oracle :// 127.0.0.1 | localhost : 3306 / 数据库名
         *        jdbc:mysql://localhost:3306/day01
         *        当前电脑的省略写法! 注意:本机和端口3306
         *        jdbc:mysql://localhost:3306/day01 = jdbc:mysql:///day01
         *
         * 两个参数:
         *     String URL : 写法还是jdbc的路径写法!
         *     Properties : 就是一个参数封装容器!至少要包含 user / password key!存储连接账号信息!
         *
         * 一个参数:
         *    String URL: URl可以携带目标地址,可以通过?分割,在后面key=value&key=value形式传递参数
         *                jdbc:mysql:///day01?user=root&password=123456
         * 扩展路径参数(了解):
         *    8.0.27版本驱动 下面是一些可选属性
         *      8.0.25以后自动识别时区 serverTimezone=Asia/Shanghai 不用添加
         *      8版本以后默认使用utf-8格式,剩下的都可以省略
         *    serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true
         *
         */
        //获取连接 方式1
        Connection connection = DriverManager.getConnection("jdbc:mysql:///testdatabase", "root", "123456");
        //获取连接 方式2
//        Properties info = new Properties();
//        info.put("user", "root");
//        info.put("password", "3663573");
//        DriverManager.getConnection("jdbc:mysql:///testdatabase", info);
        //获取连接 方式3
//        DriverManager.getConnection("jdbc:mysql:///testdatabase?user=root&password=3663573wxf");

        //固定方法固定剂
        //创建statement
        //statement 可以承载sql语句运送到sql服务器
        Statement statement = connection.createStatement();

        //执行SQL语句 [动态SQL语句,需要字符串拼接]
        String sql = "select * from t_user where account = '"+account+"' and password = '"+password+"' ;";


        /**
         * SQL分类 :
         *  DDL(容器创建,修改,删除) DML(数据插入,修改,删除) DQL(查询) DCL(劝降控制) TPL(事务控制语言)
         *  ResultSet 结果集对象 = executeQuery(DQL语句)
         *  int       响应行数  = executeUpdate(非DQL语句)
         */
        ResultSet resultSet = statement.executeQuery(sql);

        //ResultSet == 小海豚  你必须有面向对象的思维:Java是面向对象编程的语言 OOP!
        /**
         *
         * TODO:1.需要理解ResultSet的数据结构和小海豚查询出来的是一样,需要在脑子里构建结果表!
         * TODO:2.有一个光标指向的操作数据行,默认指向第一行的上边!我们需要移动光标,指向行,在获取列即可!
         *        boolean = next()
         *              false: 没有数据,也不移动了!
         *              true:  有更多行,并且移动到下一行!
         *       推荐:推荐使用if 或者 while循环,嵌套next方法,循环和判断体内获取数据!
         *       if(next()){获取列的数据!} ||  while(next()){获取列的数据!}
         *
         *TODO:3.获取当前行列的数据!
         *         get类型(int columnIndex | String columnLabel)
         *        列名获取  //label 如果没有别名,等于列名, 有别名label就是别名,他就是查询结果的标识!
         *        列的角标  //从左到右 从1开始! 数据库全是从1开始!
         */

        //进行结果集对象解析
        if (resultSet.next()){
            //只要向下移动,就是有数据 就是登录成功!
            System.out.println("登录成功!");
        }else{
            System.out.println("登录失败!");
        }

        //关闭资源
        resultSet.close();
        statement.close();
        connection.close();
    }
}

存在问题

密码写为 ' or '1' = '1  在这种情况下 无论是否存在用户 都可以登录

2.5 基于preparedstatement方式的优化(重点)

基于preparedstatement解决上述案例的注入攻击和SQL拼接问题(需要重点掌握)

/**
 * ClassName: PSUserLoginPart
 * Description:
 *      使用预编译的statement完成用户登录
 *      TODO:
 *          防止注入攻击   |   演示PS的使用流程
 */
public class PSUserLoginPart {

    public static void main(String[] args) throws Exception {
        //1.输入账号和密码
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入账号: ");
        String account = scanner.nextLine();
        System.out.println("请输入密码: ");
        String password = scanner.nextLine();
        scanner.close();

        //2.ps的数据库流程
        //2.1注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");

        //2.2获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///testdatabase?user=root&password=123456");

        //2.3创建preparedStatement
        //TODO 需要传入SQL语句结构
        //TODO 要的是SQL语句结构,动态值的部分使用 ? ,  占位符!
        //TODO ?  不能加 '?'  ? 只能替代值,不能替代关键字和容器名
        //编写SQL语句结果
        String sql = "select * from t_user where account = ? and password = ? ;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //占位符赋值
        //给占位符赋值! 从左到右,从1开始!
        /**
         *  int 占位符的下角标
         *  object 占位符的值
         */
        preparedStatement.setObject(1, account);
        preparedStatement.setObject(2, password);

        //这哥们内部完成SQL语句拼接!
        //执行SQL语句即可
        //ResultSet 结果集对象 = executeQuery(DQL语句)
        //int       响应行数  = executeUpdate(非DQL语句)
        ResultSet resultSet = preparedStatement.executeQuery();
//        preparedStatement.executeUpdate();

        //进行结果集对象解析
        if (resultSet.next()){
            //只要向下移动,就是有数据 就是登录成功!
            System.out.println("登录成功!");
        }else{
            System.out.println("登录失败!");
        }

        //关闭资源
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }
}

2.6 基于preparedstatement方式的CRUB(重点)

2.6.1 数据库数据插入

@Test
public void PSInsert() throws Exception {
    /**
     * t_user插入一条数据
     * account  test
     * password test
     * nickname 二狗子
     */

    //1.注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    //2.获取连接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///testdatabase?user=root&password=123456");
    //3.编写SQL语句结果,动态值部分用?代替
    String sql = "insert into t_user (account, password, nickname) values(?,?,?)";
    //4.创建prepareStatement 并且传入SQL语句结果
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    //5.占位符赋值
    preparedStatement.setObject(1, "test");
    preparedStatement.setObject(2, "test");
    preparedStatement.setObject(3, "二狗子");
    //6.发送SQL语句
    int rows = preparedStatement.executeUpdate();
    //7.输出结果
    if (rows > 0) {
        System.out.println("数据插入成功!");
    } else {
        System.out.println("数据插入失败!");
    }
    //8.释放资源
    preparedStatement.close();
    connection.close();
}

2.6.2 数据库数据修改

@Test
public void PSUpdate() throws Exception {
    /**
     * t_user更新指定id的数据为
     * account  test
     * password test
     * nickname 三狗子
     */
    //1.注册驱动
    Class.forName("com.mysql.cj.Driver");
    //2.获取连接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///testdatabase", "root", "123456");
    //3.编写SQL语句结果,动态值部分用?代替
    String sql = "update t_user set nickname = ? where id = ?";
    //4.创建prepareStatement 并且传入SQL语句结果
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    //5.占位符赋值
    preparedStatement.setObject(1, "三狗子");
    preparedStatement.setObject(2, 3);
    //6.发送SQL语句
    int rows = preparedStatement.executeUpdate();
    //7.输出结果
    if (rows > 0) {
        System.out.println("数据更新成功!");
    } else {
        System.out.println("数据更新失败!");
    }
    //8.释放资源
    preparedStatement.close();
    connection.close();
}

2.6.3 数据库数据删除

@Test
public void PSDelete() throws SQLException, ClassNotFoundException {
    /**
     * t_user删除一条指定id的数据
     * account  test
     * password test
     * nickname xxx
     */
    //1.注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    //2.获取连接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///testdatabase", "root", "xxxxxxxx");
    //3.编写SQL语句结果,动态值部分用?代
    String sql = "Delete from t_user where id = ?";
    //4.创建prepareStatement 并且传入SQL语句结果
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    //5.占位符赋值
    preparedStatement.setObject(1, 3);
    //6.发送SQL语句
    int rows = preparedStatement.executeUpdate();
    //7.输出结果
    if (rows > 0) {
        System.out.println("数据删除成功!");
    } else {
        System.out.println("数据删除失败!");
    }
    //8.释放资源
    preparedStatement.close();
    connection.close();
}

2.6.4 数据库数据查询(高级版 用List<Map> 存储)

@Test
public void PSSelect() throws ClassNotFoundException, SQLException {
    /**
     * t_user查询所有数据,并封装到一个List<Map> list集合当中
     *   map -> 对应一行数据
     *      map key -> 数据库列名或者别名
     *      map value -> 数据库列的值
     * TODO: 思路分析
     *    1.先创建一个List<Map>集合
     *    2.遍历resultSet对象的行数据
     *    3.将每一行数据存储到一个map对象中!
     *    4.将对象存到List<Map>中
     *    5.最终返回
     *
     * TODO:
     *    初体验,结果存储!
     *    学习获取结果表头信息(列名和数量等信息)
     */
    //1.注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");

    //2.获取连接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///testdatabase", "root", "xxxxxxx");

    //3.编写SQL语句结果,动态值部分用?代替
    String sql = "select id, account, password, nickname from t_user";

    //4.创建prepareStatement 并且传入SQL语句结果
    PreparedStatement preparedStatement = connection.prepareStatement(sql);

    //5.占位符赋值
    //省略占位符赋值

    //6.遍历resultSet对象的行数据
    ResultSet resultSet = preparedStatement.executeQuery();

    //7.输出结果
    List<Map> list = new ArrayList<>();
    //利用ResultSet内置的函数获取它列名称和列数据角标
    ResultSetMetaData metaData = resultSet.getMetaData();
    int columnCount = metaData.getColumnCount();

    while(resultSet.next()){
        Map map = new HashMap<>();
        //纯手动取值 不智能
//            map.put("id", resultSet.getInt("id"));
//            map.put("account", resultSet.getString("account"));
//            map.put("password", resultSet.getString("password"));
//            map.put("nickname", resultSet.getString("nickname"));
        //自动取值 即便是换一个数据表,也是相同的操作
        for (int i = 1; i <= columnCount; i++) {
            //获取对应列下角标的值
            Object value = resultSet.getObject(i);
            //获取指定下角标列的名称
            //getColumnLabel :会获取别名,无别名获取列名 getColumName 只会获取列的名称
            String columnName = metaData.getColumnLabel(i);
            map.put(columnName, value);
        }
        list.add(map);
    }

    System.out.println(list.toString());
    //8.释放资源
    resultSet.close();
    preparedStatement.close();
    connection.close();
}

2.7 preparedstatement使用方式的总结(重点)

1. 步骤总结

1.注册驱动        Class.forName("com.mysql.cj.jdbc.Driver");

2.获取连接          

Connection connection = DriverManager.getConnection("jdbc:mysql:///testdatabase?user=x&password=x");

3.编写SQL语句

4.创建preparedstatement并且传入SQL语句结构

PreparedStatement preparedStatement = connection.prepareStatement(sql);

5.占位符赋值

preparedStatement.setObject(<?num>, <?String or Int Object>);

6.发送SQL语句,并且获取结果

ResultSet resultSet = preparedStatement.executeQuery();

7.结果集解析

int rows = preparedStatement.executeUpdate(); // 非查询型语句返回int

ResultSet resultSet = preparedStatement.executeQuery(); // 查询型语句 next() 函数返回boolean变量

8.关闭资源

resultSet.close();
preparedStatement.close();
connection.close();

2. 使用API总结

//1.注册驱动
方案1: 调用静态方法,但是会注册两次
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
方案2: 反射触发
Class.forName("com.mysql.cj.jdbc.Driver");

//2.获取连接

Connection connection = DriverManager.getConnection();

3 (String url,String user,String password)
2 (String url,Properties info(user password))
1 (String url?user=账号&password=密码)

//3.创建statement

//静态
Statement statement = connection.createStatement();
//预编译
PreparedStatement preparedstatement = connection.preparedStatement(sql语句结构);

//4.占位符赋值

preparedstatement.setObject(?的位置 从左到右 从1开始,值)

//5.发送sql语句获取结果

int rows = executeUpdate(); //非DQL
Resultset = executeQuery(); //DQL

//6.查询结果集解析

//移动光标指向行数据 next();  if(next())  while(next())
//获取列的数据即可   get类型(int 列的下角标 从1开始 | int 列的label (别名或者列名))
//获取列的信息   getMetadata(); ResultsetMetaData对象 包含的就是列的信息
                getColumnCount(); | getCloumnLebal(index)
//7.关闭资源
close(); 

3 全新JDBC扩展提升

3.1 自增长主键回显体现

 功能实现

继续沿用之前的表数据

/**
 * TODO:
 *     t_user插入一条数据,并且获得数据库自增长的主键
 * 
 * TODO: 使用总结
 *     1. 创建preparedStatement的时候,给它第二个参数写入 1 | Statement.RETURN_GENERATED_KEYS
 *        告知它回来的时候携带回数据库自增长的主键
 *     2.获取preparedStatement携带主键值的结果集对象,一行一列,获取对应列的数据即可
 */
@Test
public void returnPrimaryKey() throws ClassNotFoundException, SQLException {
    //1. 注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");

    //2. 获取连接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///testdatabase?user=root&password=3663573wxf");

    //3. 编写SQL语句
    String sql = "insert into t_user(account, password, nickname) values(?,?,?)";

    //4. 创建preparedStatement
    /**
     * TODO:第二个参数传入 1 | Statement.RETURN_GENERATED_KEYS
     *       告诉statement携带回数据库生成的主键值
     */
    PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

    //5. 占位符赋值
    preparedStatement.setObject(1, "two egg");
    preparedStatement.setObject(2, "123456");
    preparedStatement.setObject(3, "驴蛋蛋");

    //6. 发送sql语句,获取结果集
    int i = preparedStatement.executeUpdate();

    //7. 结果解析
    if (i > 0) {
        System.out.println("插入成功!");
        //主键获取
        ResultSet resultSet = preparedStatement.getGeneratedKeys();
        boolean next = resultSet.next();
        int id = resultSet.getInt(1);

        System.out.println("主键id是: " + id);
    } else {
        System.out.println("插入失败!");
    }
    //8. 关闭资源

    preparedStatement.close();
    connection.close();
}

3.2 批量数据插入性能提升

功能需求

1. 批量插入数据优化

2. 提升大量数据插入效率

/**
 * TODO:
 *     使用批量的方式插入10000条数据
 *     1.路径后面加上 ?reWriteBatchStatements=true
 *     2.insert into values 【必须写】 语句不能添加 ; 结束
 *     3.不是执行每条,而是preparedStatement.addBatch()
 *     4.最终使用preparedStatement.executeBatch()统一添加
 */
@Test
public void BatchInsert() throws ClassNotFoundException, SQLException {
    //1. 注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");

    //2. 获取连接
    Connection connection = DriverManager.getConnection(
            "jdbc:mysql:///testdatabase?reWriteBatchStatements=true",
            "root", "3663573wxf");

    //3. 编写SQL语句
    String sql = "insert into t_user(account, password, nickname) values(?,?,?)";

    //4. 创建preparedStatement
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    //创建起始时间
    long start = System.currentTimeMillis();

    //5. 占位符赋值
    for (int i = 0; i < 10000; i++) {
        preparedStatement.setObject(1, "account_" + i);
        preparedStatement.setObject(2, "pwd_" + i);
        preparedStatement.setObject(3, "驴蛋蛋" + i);
        //6. 批量进入缓存
        preparedStatement.addBatch();
    }
    //批量统一发送
    preparedStatement.executeBatch();

    //7. 时间计算
    long end = System.currentTimeMillis();
    System.out.println("执行10000次insert插入耗时:" + (end - start) + "ms");

    //8. 关闭资源
    preparedStatement.close();
    connection.close();
}

3.3 JDBC数据库中事务的体现

3.1 章节目标

3.2 事务概念回顾

事务概念

        数据库事务就是一种SQL语句执行的缓存机制,不会单条执行完毕就更新数据库数据,最终根据缓存内的多条语句执行结果统一判定!
         一个事务内所有语句都成功及事务成功,我们可以触发commit提交事务来结束事务,更新数据!
         一个事务内任意一条语句失败,及事务失败,我们可以触发rollback回滚结束事务,
   数据回到事务之前状态!   

举个例子: 
           临近高考,你好吃懒做,偶尔还瞎花钱,父母也只会说'你等着!',待到高考完毕!
           成绩600+,翻篇,庆祝!
           成绩200+,翻旧账,男女混合双打!

优势   允许我们在失败情况下,数据回归到业务之前的状态! 
场景
   一个业务涉及多条修改数据库语句!
   例如: 经典的转账案例,转账业务(加钱和减钱)   
         批量删除(涉及多个删除)
         批量添加(涉及多个插入)     
事务特性

1. 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 

2. 一致性(Consistency)事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

3. 隔离性(Isolation)事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

4. 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响

事务类型  
自动提交 : 每条语句自动存储一个事务中,执行成功自动提交,执行失败自动回滚! (MySQL)
手动提交:  手动开启事务,添加语句,手动提交或者手动回滚即可!

sql开启事务方式
   针对自动提交: 关闭自动提交即可,多条语句添加以后,最终手动提交或者回滚! (推荐)

SET autocommit = off; //关闭当前连接自动事务提交方式
# 只有当前连接有效
# 编写SQL语句即可
SQL
SQL
SQL
#手动提交或者回滚 【结束当前的事务】
COMMIT / ROLLBACK ;  

手动开启事务: 开启事务代码,添加SQL语句,事务提交或者事务回滚! (不推荐)

呼应jdbc技术

  try{
    connection.setAutoCommit(false); //关闭自动提交了
    
    //注意,只要当前connection对象,进行数据库操作,都不会自动提交事务
    //数据库动作!
    //statement - 单一的数据库动作 c u r d 
    
    connection.commit();
  }catch(Execption e){
    connection.rollback();
  }

3.3 数据库表数据

CREATE TABLE t_bank(
   id INT PRIMARY KEY AUTO_INCREMENT COMMENT '账号主键',
   account VARCHAR(20) NOT NULL UNIQUE COMMENT '账号',
   money  INT UNSIGNED COMMENT '金额,不能为负值') ;
   
INSERT INTO t_bank(account,money) VALUES
  ('ergouzi',1000),('lvdandan',1000);

3.4 代码结构设计

 3.5 jdbc事务体现

1. BankService类和测试方法

/**
 * ClassName: BankService
 * Package: com.fanxy.api.transaction
 * Description:
 *  银行卡业务方法,调用dao方法
 * @Author FanXY
 * @Create 2023/4/29 11:12
 * @Version 1.0
 */
public class BankService {
    /**
     * TODO:
     *    事务添加是在业务方法中!
     *    利用try catch 代码块,开启事务,提交事务,事务回滚
     *    将connection传入Dao层即可!Dao只负责使用,不负责close
     *
     * 银行业务操作类 实现转账功能
     * @param addAccount 加钱账户
     * @param subAccount 减钱账户
     * @param money 金额数量
     */
    public void transfer(String addAccount, String subAccount, int money) throws Exception {

        // 一个事务的基本要求是在同一个连接 connection
        //1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");

        //2.获取数据库连接
        Connection connection = DriverManager.getConnection(
                "jdbc:mysql:///testdatabase", "root", "xxxxxxx");


        BankDao bankDao = new BankDao();
        try {
            //开启事务 == 关闭事务自动提交
            connection.setAutoCommit(false);


            //执行数据库动作
            bankDao.add(addAccount, money, connection);
            System.out.println("--------------------------------");
            bankDao.sub(subAccount, money, connection);

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

        } catch (Exception e) {
            //事务回滚
            connection.rollback();

            //抛出异常
            throw e;
        } finally {
            //资源关闭
            connection.close();
        }
    }

    @Test
    public void start() throws Exception {
        // 二狗子给驴蛋蛋转账500
        transfer("lvdandan", "ergouzi", 500);
    }
}

2.BankDao

/**
 * ClassName: BankDao
 * Package: com.fanxy.api.transaction
 * Description:
 * bank表的数据库操作存储类
 *
 * @Author FanXY
 * @Create 2023/4/29 11:05
 * @Version 1.0
 */
public class BankDao {

    /**
     * bank表的数据库操作方法
     *
     * @param account 加钱的账户
     * @param money   加钱的金额
     */
    public void add(String account, int money, Connection connection) throws ClassNotFoundException, SQLException {

        //3.SQL语句
        String sql = "update t_bank set money = money + ? where account = ?;";

        //4.创建Statement
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //5.占位符赋值
        preparedStatement.setObject(1, money);
        preparedStatement.setString(2, account);

        //6.执行sql语句
        int i = preparedStatement.executeUpdate();
        if(i > 0){
            System.out.println("加钱成功");
        }else{
            System.out.println("加钱失败");
        }

        //7.关闭资源
        preparedStatement.close();
    }

    /**
     * 减钱的数据库操作方法
     *
     * @param account 减钱的账户
     * @param money   减钱的金额
     */
    public void sub(String account, int money, Connection connection) throws SQLException, ClassNotFoundException {

        //3.SQL语句
        String sql = "update t_bank set money = money - ? where account = ?;";

        //4.创建Statement
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //5.占位符赋值
        preparedStatement.setObject(1, money);
        preparedStatement.setString(2, account);

        //6.执行sql语句
        int i = preparedStatement.executeUpdate();
        if(i > 0){
            System.out.println("减钱成功");
        }else{
            System.out.println("减钱失败");
        }

        //7.关闭资源
        preparedStatement.close();
    }
}

4 国货之光Druid连接池技术使用

4.1 连接性能消耗问题分析

4.2 数据库连接池作用

4.3 市面常见连接池产品和对比

4.4 国货之光druid连接池使用

记得导入druid工具类jar

1. 硬编码方式(了解不推荐)

public class DruidUsePart {

/**
 * 直接使用代码设置连接池
 * 1.创建一个druid连接池对象
 * 2.设置连接池参数[必须 | 非必须]
 * 3.获取连接[通用方法,所有连接池都一样]
 * 4.回收连接[通用方法,所有连接池都一样]
 */
public void testHard() throws SQLException {

    //连接池对象
    DruidDataSource druidDataSource = new DruidDataSource();

    //设置参数
    //必须 连接数据库驱动类的权限定符【注册驱动】 | url | user | password |
    druidDataSource.setUrl("jdbc:mysql:///testdatabase");
    druidDataSource.setUsername("root");
    druidDataSource.setPassword("xxxxxxxx");
    druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); //帮助我们进行驱动注册和获取连接

    //非必须 初始化连接数量, 最大的连接数量
    druidDataSource.setInitialSize(5);//初始化连接数量
    druidDataSource.setMaxActive(10);//最大的连接数量

    //获取连接
    Connection connection = druidDataSource.getConnection();
    //数据库crud

    //回收连接
    connection.close();//连接池的close是回收连接不是关闭连接
}

2. 软编码方式

2.1 外部配置

存放在src/druid.properties

# druid连接池需要的配置参数,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///atguigu

2.2 druid声明代码

通过工程模式的createDataSource方法创建连接池,源码角度还是使用的new的方式,但是会读取properties,按照它规定的格式读入名称,配置druid连接池,这也是为什么我们properties文件的配置名称固定的原因

/**
* 通过读取外部配置文件的方式,实例化druid连接池对象
*/
@Test
public void testSoft() throws Exception {

   //1. 读取外部配置文件 properties
   Properties properties = new Properties();

   //src下的文件可以使用类的加载器提供的方法读取文件
   InputStream ips = DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties");

   properties.load(ips);
   //2. 使用连接池的工具类的工程模式,创建连接池
   DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
        
   Connection connection = dataSource.getConnection();
        
   //数据库crud
        
   connection.close();
}

3. druid配置(了解)

5 全新JDBC使用优化以及工具类的封装

5.1 jdbc工具类封装v1.0

我们封装一个工具类,内部包含连接池对象,同时对外提供连接的方法和回收连接的方法

外部配置文件位置: src/druid.properties

# druid连接池需要的配置参数,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///atguigu

工具类代码

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * ClassName: JdbcUtils
 * Package: com.fanxy.api.util
 * Description:
 *      v1.0版本工具类
 *      内部包含一个连接池对象,并且对外提供获取链接和回收连接的方法
 *      小建议:
 *          工具类的方法推荐使用静态,外部调用更方便
 *      实现:
 *          属性 连接池对象【实例化一次】
            单例模式
 *          static{
 *              全局调用一次
 *          }
 *      方法:
 *          对外提供连接的方法
 *          回收外部传入连接的方法
 * @Author FanXY
 * @Create 2023/5/1 21:06
 * @Version 1.0
 */
public class JdbcUtils {
    private static DataSource dataSource = null;//连接池对象

    static {
        //初始化连接池对象
        Properties properties = new Properties();
        InputStream ips = JdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties");
        try {
            properties.load(ips);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        try {
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 对外提供连接的方法
     * @return
     */
    public static Connection getConnection() throws SQLException {
       return dataSource.getConnection();
    }
    /**
     * 回收连接的方法
     */
    public static void freeConnection(Connection connection) throws SQLException {
        connection.close(); //连接池的close就是回收连接
    }
}

调用类代码

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

public class JdbcCrudPart {
    public void testInsert() throws SQLException {

        Connection connection = JdbcUtils.getConnection();

        //基于数据库的Crud

        JdbcUtils.freeConnection(connection);
    }
}

5.2 jdbc工具类封装v2.0

优化工具类v1.0版本,考虑事务的情况下!如何一个线程的不同方法获取同一个连接!

ThreadLocal的介绍:

JDK 1.2的版本中就提供java.lang.ThreadLocal,为解决多线程程序的并发问题提供了一种新的思路。 使用这个工具类可以很简洁地编写出优美的多线程程序。通常用来在在多线程中管理共享数据库连接、 Session

ThreadLocal用于保存某个线程共享变量,原因是在Java中,每一个线程对象中都有一个 ThreadLocalMap,其key就是一个ThreadLocal,而Object即为该线程的 共享变量。而这个map是通过ThreadLocal的set和get方法操作的。对于同一个static ThreadLocal, 不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

1. ThreadLocal对象.get: 获取ThreadLocal中当前线程共享变量的值。

2. ThreadLocal对象.set: 设置ThreadLocal中当前线程共享变量的值。

3. ThreadLocal对象.remove: 移除ThreadLocal中当前线程共享变量的值。

1. JbdcUtilsV2

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * ClassName: JdbcUtils
 * Package: com.fanxy.api.util
 * Description:
 *      v2.0版本工具类
 *      内部包含一个连接池对象,并且对外提供获取链接和回收连接的方法
 *
 *      实现:
 *          属性 连接池对象【实例化一次】
 *      单例模式
 *          static{
 *              全局调用一次
 *          }
 *      方法:
 *          对外提供连接的方法
 *          回收外部传入连接的方法
 * @Author FanXY
 * @Create 2023/5/1 21:41
 * @Version 2.0
 *
 * TODO:
 *      利用线程本地变量,存储连接信息!确保一个线程的多个方法
 *      可以获取同一个connection
 *      优势:事务操作的时候 service和dao属于同一个线程 不在传递参数了
 *      大家都可以调用getConnection自动获取相同的连接池
 */
public class JdbcUtilsV2 {
    private static DataSource dataSource = null;//连接池对象
    private static ThreadLocal<Connection> tl = new ThreadLocal<>();
    static {
        //初始化连接池对象
        Properties properties = new Properties();
        InputStream ips = JdbcUtilsV2.class.getClassLoader().getResourceAsStream("druid.properties");
        try {
            properties.load(ips);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        try {
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 对外提供连接的方法
     * @return
     */
    public static Connection getConnection() throws SQLException {
        //先查看线程本地变量是否存在
        Connection connection = tl.get();
        if (connection == null) {
            //线程本地变量没有,连接池获取
            connection = dataSource.getConnection();
            tl.set(connection);
        }
        return connection;
    }
    /**
     * 回收连接的方法
     */
    public static void freeConnection() throws SQLException {
        Connection connection = tl.get();
        if (connection != null) {
            tl.remove();//清空线程本地变量
            connection.setAutoCommit(true);//事务状态回归
            connection.close(); //连接池的close就是回收连接
        }
    }
}

2. BankService类和测试方法新版

public class BankService {
    /**
     * TODO:
     *    事务添加是在业务方法中!
     *    利用thr catch 代码块,开启事务,提交事务,事务回滚
     *    将connection传入Dao层即可!Dao只负责使用,不负责close
     *
     * 银行业务操作类 实现转账功能
     * @param addAccount 加钱账户
     * @param subAccount 减钱账户
     * @param money 金额数量
     */
    public void transfer(String addAccount, String subAccount, int money) throws Exception {

        // 一个事务的基本要求是在同一个连接 connection
        Connection connection = JdbcUtilsV2.getConnection();

        BankDao bankDao = new BankDao();
        try {
            //开启事务 == 关闭事务自动提交
            connection.setAutoCommit(false);

            //执行数据库动作
            bankDao.add(addAccount, money);
            System.out.println("--------------------------------");
            bankDao.sub(subAccount, money);

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

        } catch (Exception e) {
            //事务回滚
            connection.rollback();

            //抛出异常
            throw e;
        } finally {
            //资源关闭
            JdbcUtilsV2.freeConnection();
        }
    }

    @Test
    public void start() throws Exception {
        // 二狗子给驴蛋蛋转账500
        transfer("lvdandan", "ergouzi", 500);
    }
}

3. BankDao新版

public class BankDao {

    /**
     * bank表的数据库操作方法
     *
     * @param account 加钱的账户
     * @param money   加钱的金额
     */
    public void add(String account, int money) throws ClassNotFoundException, SQLException {

        Connection connection = JdbcUtilsV2.getConnection();

        //3.SQL语句
        String sql = "update t_bank set money = money + ? where account = ?;";

        //4.创建Statement
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //5.占位符赋值
        preparedStatement.setObject(1, money);
        preparedStatement.setString(2, account);

        //6.执行sql语句
        int i = preparedStatement.executeUpdate();
        if(i > 0){
            System.out.println("加钱成功");
        }else{
            System.out.println("加钱失败");
        }

        //7.关闭资源
        preparedStatement.close();
    }

    /**
     * 减钱的数据库操作方法
     *
     * @param account 减钱的账户
     * @param money   减钱的金额
     */
    public void sub(String account, int money) throws SQLException, ClassNotFoundException {

        Connection connection = JdbcUtilsV2.getConnection();

        //3.SQL语句
        String sql = "update t_bank set money = money - ? where account = ?;";

        //4.创建Statement
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //5.占位符赋值
        preparedStatement.setObject(1, money);
        preparedStatement.setString(2, account);

        //6.执行sql语句
        int i = preparedStatement.executeUpdate();
        if(i > 0){
            System.out.println("减钱成功");
        }else{
            System.out.println("减钱失败");
        }

        //7.关闭资源
        preparedStatement.close();
    }
}

5.3 高级应用层封装BaseDao

基本上每一个数据表都应该有一个对应的DAO接口及其实现类,发现对所有表的操作(增、删、改、查)代码重复度很高,所以可以抽取公共代码,给这些DAO的实现类可以抽取一个公共的父类,我们称为BaseDao

 非DQL查询封装

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

/**
 * ClassName: BaseDao
 * Package: com.fanxy.api.utils
 * Description:
 *      封装Dao层数据库重复代码
 * TODO:
 *      封装两个方法
 *      1.DQL方法
 *      2.非DQL方法
 *
 * @Author FanXY
 * @Create 2023/5/1 22:10
 * @Version 1.0
 */
public abstract class BaseDao {

    /**
     * 封装简化非DQL语句
     * @param sql   带占位符的sql语句
     * @param params    占位符的值 注意传入占位符的值,必须等于SQL语句?位置
     * @return      返回改变的行数
     */
    public int executeUpdate(String sql, Object... params) throws SQLException {

        //获取连接
        Connection connection = JdbcUtilsV2.getConnection();

        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //占位符赋值
        if (params != null && params.length != 0) {
            //可变参数可以当作数组使用
            for (int i = 1; i <= params.length; i++) {
                preparedStatement.setObject(i, params[i-1]);
            }
        }

        //发送SQL语句
        int rows = preparedStatement.executeUpdate();

        //是否回收连接,需要考虑是不是事务!
        if (connection.getAutoCommit()) {
            //没有开启事务 正常回收连接
            JdbcUtilsV2.freeConnection();
        }
        //preparedStatement.close();//开启事务 不要管连接即可 业务层处理
        return rows;
    }
}

此时业务层代码简化为

public class PSCURDPart extends BaseDao {

    @Test
    public void PSInsert() throws Exception {

        String sql = "insert into t_user (account, password, nickname) values(?,?,?)";

        int i = executeUpdate(sql, "test333", "test333", "Tom");
        System.out.println("i = " + i);
    }

    @Test
    public void PSUpdate() throws Exception {
        String sql = "update t_user set nickname = ? where id = ?";

        int i = executeUpdate(sql, "新的nickname", 3);
        System.out.println("i = " + i);
    }

    @Test
    public void PSDelete() throws SQLException, ClassNotFoundException {

        String sql = "Delete from t_user where id = ?";
        int i = executeUpdate(sql, 3);
        System.out.println("i = " + i);
    }
}

5.4 DQL查询方式封装

和非DQL方式一样,封装在BaseDao类中

    /**
     * 非DQL语句封装方法 ->返回值 固定为int
     * DQL语句封装方法 ->返回值是什么类型?    List<T>
     *                并不是list<Map> map key和value自定义!不用先设定好!
     *                               map 没有数据校验机制
     *                               map 不支持反射操作
     *
     *                数据库数据 -> Java实体类
     *                table
     *                    t_user -> id, account, password, nickname
     *                java
     *                    User(类) -> id, account, password, nickname
     *                表中一行 -> java类中一个对象 -> 多行 -> List<Java>实体类 list;
     *
     *   <T> 声明一个泛型,不确定类型
     *      1.确定泛型 User.class T = User
     *      2.要使用反射技术属性赋值
     *   public <T> List<T> executeQuery(Class<T> clazz, String sql, Object ... params);
     *
     * 将查询结果封装到一个实体类集合!
     * @param clazz 要接值的实体类集合的模板对象
     * @param sql 查询语句 要求列名或者别名等于实体类的属性名!  u_id as uId => uId
     * @param params 占位符的值 要和 ? 对应位置传递
     * @return 查询的实体类集合
     * @param <T> 声明的结果的泛型
     * @throws Exception
     */
    public <T> List<T> executeQuery(Class<T> clazz, String sql, Object... params) throws Exception {
        // 获取连接
        Connection connection = JdbcUtilsV2.getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //占位符赋值
        if (params != null && params.length != 0) {
            //可变参数可以当作数组使用
            for (int i = 1; i <= params.length; i++) {
                preparedStatement.setObject(i, params[i-1]);
            }
        }

        //发送SQL语句
        ResultSet resultSet = preparedStatement.executeQuery();

        //preparedStatement.close();//开启事务 不要管连接即可 业务层处理
        List<T> list = new ArrayList<>();

        //利用ResultSet内置的函数获取它列名称和列数据角标
        ResultSetMetaData metaData = resultSet.getMetaData();
        int columnCount = metaData.getColumnCount();

        //省略占位符赋值

        while (resultSet.next()) {
            //调用T的无参构造函数生成对象
            T t = clazz.newInstance();

            //自动取值 即便是换一个数据表,也是相同的操作
            for (int i = 1; i <= columnCount; i++) {
                //对象的属性值
                Object value = resultSet.getObject(i);

                //获取指定下角标列的名称
                //getColumnLabel :会获取别名,无别名获取列名 getColumName 只会获取列的名称
                String columnName = metaData.getColumnLabel(i);

                //反射给对象的属性值赋值
                Field field = clazz.getDeclaredField(columnName);
                field.setAccessible(true);
                /**
                 * 参数1: 要赋值的对象 如果属性是静态的,第一个参数可以设置为null
                 * 参数2: 具体的属性值
                 */
                field.set(t, value);
            }
            //一行数据存储到一个User对象中
            //将对象存入list集合中即可
            list.add(t);
        }

        //关闭资源
        resultSet.close();
        preparedStatement.close();
        if (connection.getAutoCommit()) {
            //没有开启事务 正常回收连接
            JdbcUtilsV2.freeConnection();
        }
        return list;
    }

6 基于CMS项目JDBC的实战练习

6.1 cms项目介绍和导入

项目导入

1. 打开项目

 2. 配置JDK 

6.2 基于cms项目添加数据库相关配置

1.准备数据库脚本

-- 员工表

CREATE TABLE t_customer(
  id INT PRIMARY KEY AUTO_INCREMENT COMMENT '客户主键',
  name VARCHAR(20)  COMMENT '客户名称',
  gender VARCHAR(4) COMMENT '客户性别',
  age INT  COMMENT '客户年龄',
  salary DOUBLE(8,1) COMMENT '客户工资',
  phone VARCHAR(11) COMMENT '客户电话');

2.添加配置文件

位置 src下,druid.properties

3. 导入jdbcv2.0工具类

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * ClassName: JdbcUtils
 * Package: com.fanxy.api.util
 * Description:
 *      v2.0版本工具类
 *      内部包含一个连接池对象,并且对外提供获取链接和回收连接的方法
 *
 *      实现:
 *          属性 连接池对象【实例化一次】
 *      单例模式
 *          static{
 *              全局调用一次
 *          }
 *      方法:
 *          对外提供连接的方法
 *          回收外部传入连接的方法
 * @Author FanXY
 * @Create 2023/5/1 21:41
 * @Version 2.0
 *
 * TODO:
 *      利用线程本地变量,存储连接信息!确保一个线程的多个方法
 *      可以获取同一个connection
 *      优势:事务操作的时候 service和dao属于同一个线程 不在传递参数了
 *      大家都可以调用getConnection自动获取相同的连接池
 */
public class JdbcUtilsV2 {
    private static DataSource dataSource = null;//连接池对象
    private static ThreadLocal<Connection> tl = new ThreadLocal<>();
    static {
        //初始化连接池对象
        Properties properties = new Properties();
        InputStream ips = JdbcUtilsV2.class.getClassLoader().getResourceAsStream("druid.properties");
        try {
            properties.load(ips);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        try {
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 对外提供连接的方法
     * @return
     */
    public static Connection getConnection() throws SQLException {
        //先查看线程本地变量是否存在
        Connection connection = tl.get();
        if (connection == null) {
            //线程本地变量没有,连接池获取
            connection = dataSource.getConnection();
            tl.set(connection);
        }
        return connection;
    }
    /**
     * 回收连接的方法
     */
    public static void freeConnection() throws SQLException {
        Connection connection = tl.get();
        if (connection != null) {
            tl.remove();//清空线程本地变量
            connection.setAutoCommit(true);//事务状态回归
            connection.close(); //连接池的close就是回收连接
        }
    }
}

4. 导入baseDao工具类

import com.fanxy.api.utils.JdbcUtilsV2;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * ClassName: BaseDao
 * Package: com.fanxy.api.utils
 * Description:
 *      封装Dao层数据库重复代码
 * TODO:
 *      封装两个方法
 *      1.DQL方法
 *      2.非DQL方法
 *
 * @Author FanXY
 * @Create 2023/5/1 22:10
 * @Version 1.0
 */
public abstract class BaseDao {

    /**
     * 封装简化非DQL语句
     * @param sql   带占位符的sql语句
     * @param params    占位符的值 注意传入占位符的值,必须等于SQL语句?位置
     * @return      返回改变的行数
     */
    public int executeUpdate(String sql, Object... params) throws SQLException {

        //获取连接
        Connection connection = JdbcUtilsV2.getConnection();

        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //占位符赋值
        if (params != null && params.length != 0) {
            //可变参数可以当作数组使用
            for (int i = 1; i <= params.length; i++) {
                preparedStatement.setObject(i, params[i-1]);
            }
        }

        //发送SQL语句
        int rows = preparedStatement.executeUpdate();

        //是否回收连接,需要考虑是不是事务!
        if (connection.getAutoCommit()) {
            //没有开启事务 正常回收连接
            JdbcUtilsV2.freeConnection();
        }
        //preparedStatement.close();//开启事务 不要管连接即可 业务层处理
        return rows;
    }

    /**
     * 非DQL语句封装方法 ->返回值 固定为int
     * DQL语句封装方法 ->返回值是什么类型?    List<T>
     *                并不是list<Map> map key和value自定义!不用先设定好!
     *                               map 没有数据校验机制
     *                               map 不支持反射操作
     *
     *                数据库数据 -> Java实体类
     *                table
     *                    t_user -> id, account, password, nickname
     *                java
     *                    User(类) -> id, account, password, nickname
     *                表中一行 -> java类中一个对象 -> 多行 -> List<Java>实体类 list;
     *
     *   <T> 声明一个泛型,不确定类型
     *      1.确定泛型 User.class T = User
     *      2.要使用反射技术属性赋值
     *   public <T> List<T> executeQuery(Class<T> clazz, String sql, Object ... params);
     *
     * 将查询结果封装到一个实体类集合!
     * @param clazz 要接值的实体类集合的模板对象
     * @param sql 查询语句 要求列名或者别名等于实体类的属性名!  u_id as uId => uId
     * @param params 占位符的值 要和 ? 对应位置传递
     * @return 查询的实体类集合
     * @param <T> 声明的结果的泛型
     * @throws Exception
     */
    public <T> List<T> executeQuery(Class<T> clazz, String sql, Object... params) throws Exception {
        // 获取连接
        Connection connection = JdbcUtilsV2.getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //占位符赋值
        if (params != null && params.length != 0) {
            //可变参数可以当作数组使用
            for (int i = 1; i <= params.length; i++) {
                preparedStatement.setObject(i, params[i-1]);
            }
        }

        //发送SQL语句
        ResultSet resultSet = preparedStatement.executeQuery();

        //preparedStatement.close();//开启事务 不要管连接即可 业务层处理
        List<T> list = new ArrayList<>();

        //利用ResultSet内置的函数获取它列名称和列数据角标
        ResultSetMetaData metaData = resultSet.getMetaData();
        int columnCount = metaData.getColumnCount();

        //省略占位符赋值

        while (resultSet.next()) {
            //调用T的无参构造函数生成对象
            T t = clazz.newInstance();

            //自动取值 即便是换一个数据表,也是相同的操作
            for (int i = 1; i <= columnCount; i++) {
                //对象的属性值
                Object value = resultSet.getObject(i);

                //获取指定下角标列的名称
                //getColumnLabel :会获取别名,无别名获取列名 getColumName 只会获取列的名称
                String columnName = metaData.getColumnLabel(i);

                //反射给对象的属性值赋值
                Field field = clazz.getDeclaredField(columnName);
                field.setAccessible(true);
                /**
                 * 参数1: 要赋值的对象 如果属性是静态的,第一个参数可以设置为null
                 * 参数2: 具体的属性值
                 */
                field.set(t, value);
            }
            //一行数据存储到一个User对象中
            //将对象存入list集合中即可
            list.add(t);
        }

        //关闭资源
        resultSet.close();
        preparedStatement.close();
        if (connection.getAutoCommit()) {
            //没有开启事务 正常回收连接
            JdbcUtilsV2.freeConnection();
        }
        return list;
    }
}

6.3 基于cms项目实战

1. customerService

import com.atguigu.cms.dao.CustomerDao;
import com.atguigu.cms.javabean.Customer;

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

/**
 * 这是一个具有管理功能的功能类. 内部数据不允许外部随意修改, 具有更好的封装性.
 */
public class CustomerService {


    private CustomerDao customerDao = new CustomerDao();

    /**
     * 用途:返回所有客户对象
     * 返回:集合
     */
    public List<Customer> getList() {

        try {
            return customerDao.findAll();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

   /**
     * 用途:添加新客户
     * 参数:customer指定要添加的客户对象
     */
    public void addCustomer(Customer customer) {

        try {
            customerDao.addCustomer(customer);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 用途:返回指定id的客户对象记录
     * 参数: id 就是要获取的客户的id号.
     * 返回:封装了客户信息的Customer对象
     */
    public Customer getCustomer(int id) {
        try {
            return customerDao.findById(id);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 修改指定id号的客户对象的信息
     * @param id 客户id
     * @param cust 对象
     * @return 修改成功返回true, false表明指定id的客户未找到
     */
    public boolean modifyCustomer(int id, Customer cust) {

        int rows = 0;
        try {
            rows = customerDao.updateById(id, cust);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        if (rows == 0){
            return false;
        }
        return true;
    }

    /**
     * 用途:删除指定id号的的客户对象记录
     * 参数: id 要删除的客户的id号
     * 返回:删除成功返回true;false表示没有找到
     */
    public boolean removeCustomer(int id) {
        int rows = 0;
        try {
            rows = customerDao.removeById(id);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        if (rows == 0){
            return false;
        }
        return true;
    }
}

2. customerDao

import com.atguigu.cms.javabean.Customer;
import com.atguigu.cms.utils.BaseDao;

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

/**
 * ClassName: CustomerDao
 * Package: com.atguigu.cms.dao
 * Description:
 *
 * @Author FanXY
 * @Create 2023/5/1 23:54
 * @Version 1.0
 */
public class CustomerDao extends BaseDao {
    /**
     * 查询数据库全部成员方法
     *
     * @return
     */
    public List<Customer> findAll() throws Exception {

        List<Customer> customers = executeQuery(Customer.class, "select * from t_customer");
        return customers;
    }

    /**
     * 添加成员方法
     *
     * @param customer
     */
    public void addCustomer(Customer customer) throws SQLException {
        String sql = "INSERT INTO t_customer(name, gender, age, salary, phone) values(?, ?, ?, ?, ?)";

        executeUpdate(sql, customer.getName(), customer.getGender(), customer.getAge(),
                customer.getSalary(), customer.getPhone());
    }

    /**
     * 修改成员信息方法
     *
     * @param id
     * @return 影响行数
     */
    public int updateById(int id, Customer customer) throws SQLException {
        String sql = "UPDATE t_customer SET name=?,gender=?, age=?, salary=?, phone=? where id = ?";
        int i = executeUpdate(sql, customer.getName(), customer.getGender(), customer.getAge(),
                customer.getSalary(), customer.getPhone(), customer.getId());
        return i;
    }

    /**
     * 查询成员方法
     *
     * @param id
     * @return 返回成员信息
     */
    public Customer findById(int id) throws Exception {

        String sql = "select * from t_customer where id = ?";
        List<Customer> customerList = executeQuery(Customer.class, sql, id);
        if (customerList != null && customerList.size() > 0){
            return customerList.get(0);
        }
        return null;
    }

    /**
     * 删除成员方法
     *
     * @param id
     * @return 影响行数
     */
    public int removeById(int id) throws SQLException {
        String sql = "delete from t_customer where id =?";
        int rows = executeUpdate(sql, id);
        return rows;
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值