Bootstrap

JDBC 2023年最新学习笔记!

课程笔记说明

我的笔记是来源于尚硅谷的赵伟风老师的2023年最新版jdbc的课程

视频链接:

尚硅谷JDBC实战教程(2023最新版jdbc,JDK17+MySQL8)_哔哩哔哩_bilibili

课程资料:

关注“尚硅谷教育”,后台回复JDBC,可以获得对应的资源

我将我的笔记和练习时所使用的代码全部上传到了我的Gitee仓库中,仓库链接:

Jennifer/learn_JDBC - 码云 - 开源中国 (gitee.com)

如有侵权,请及时联系 !

为什么要学习JDBC?

  • java和数据库连接的纽带
  • 数据库 层框架 底层原理

课程目录

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cl3OYxHz-1686999630611)(学习jdbc.assets/image-20230613170846513.png)]

JDBC技术概述

JDBC:Java Database Connectivity,Java 连接数据库技术!

通俗点说,在Java代码中,使用JDBC提供的方法,可以发送字符串类型的SQL语句到数据库管理软件(MySQL,Oracle等),并且获取语句执行结果!

进而实现数据库数据CURD操作的技术!

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

总结:

  • jdbc是java连接数据库技术的统称
  • jdbc是由两部分组成:一是Java提供的jdbc规范(接口),存储在java.sql和javax.sql包中的api,二是各个数据库厂商的实现驱动jar包
  • jdbc技术是一种典型的面向接口编程

优势

  • 我们只需要学习jdbc接口规定方法,即可操作所有数据库软件
  • 项目中期需要切换数据库,我们只需要更新第三方驱动jar包,不需要更改代码

JDBC的核心技术

一是Java提供的jdbc规范(接口),存储在java.sql和javax.sql包中的api

二是各个数据库厂商的实现驱动jar包

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q9YCD7Ck-1686999630615)(学习jdbc.assets/image-20230614134045974.png)]

涉及到的核心类和接口

DriverManager、Connection、PrepareStatement、Result

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gBAXpEBr-1686999630617)(学习jdbc.assets/image-20230614134202491.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qDKn2Beb-1686999630617)(学习jdbc.assets/image-20230614134333842.png)]

JDBC核心API

引入MySQL-JDBC驱动jar

我的MySQL版本是8.0.33

我的mysql-connector-j-8.0.33.jar是自己下载的,从maven仓库中下载的

Maven Repository: mysql » mysql-connector-java » 8.0.33 (mvnrepository.com)

一般来说手动下载jar包从这里找

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gMGaVu9Y-1686999630617)(学习jdbc.assets/image-20230614143243283.png)]

但是

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0bu9vfrW-1686999630618)(学习jdbc.assets/image-20230614143256899.png)]

注意到以下内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XUIRhGMr-1686999630618)(学习jdbc.assets/image-20230614143315021.png)]

跳转

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GoGoOg9C-1686999630618)(学习jdbc.assets/image-20230614143336641.png)]

是可以下载的,就是没找到对应的src文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fHnBnvEM-1686999630623)(学习jdbc.assets/image-20230614143352349.png)]

将上面下载的jar包放入idea,并导入为库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ImOeoTzK-1686999630623)(学习jdbc.assets/image-20230614143523119.png)]

效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OCVN2Xds-1686999630624)(学习jdbc.assets/image-20230614143555776.png)]

JDBC基本使用步骤分析(6步)

1.注册驱动依赖的jar包进行安装

2.建立连接 connection

3.创建发送SQL语句对象的statement

4.statement对象,发送SQL语句到数据库并获取返回结果

5.解析结果集

6.销毁资源

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MeqCnLl8-1686999630624)(学习jdbc.assets/image-20230614144740610.png)]

基于statement演示查询

准备数据

create  database atguigu;
use atguigu;
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','管理员');

select * from t_user;

关键代码

package com.tencent.api.statement;
import com.mysql.cj.jdbc.Driver;
import java.sql.*;

public class StatementQuery {
    public static void main(String[] args) throws SQLException {
        //注册驱动
        /**
         * 注册驱动
         * 依赖:驱动版本8+ 选择com.mysql.cj.jdbc.Driver
         *      驱动版本5+ 选择com.mysql.jdbc.Driver
         */
        DriverManager.registerDriver(new Driver());
        /**
         * java程序要和数据库建立连接
         * 肯定也需要调用某个方法,方法中需要填入连接数据库的基本信息
         * 数据库ip地址:127.0.0.1
         * 数据库端口号:3306
         * 账号:root
         * 密码:1234
         * 连接数据库的名称:atguigu
         */
        /**
         * 参数1:url
         *      jdbc:数据库厂商名://ip地址:port/数据库名
         * 参数2:username 数据库软件的账号 root
         * 参数3:password 数据库软件的密码 1234
         */
        //建立连接
        //java.sql 接口=实现类
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "1234");
        //创建statement声明
        Statement statement = connection.createStatement();

        //发送sql语句,并获取返回结果
        String sql = "select id,account,PASSWORD,nickname from t_user";
        ResultSet resultSet = statement.executeQuery(sql);
        //解析结果集
        //看看有没有下一行数据,有就可以读取
        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);
        }
        //释放资源
        resultSet.close();
        statement.close();
        connection.close();
    }
}

基于statement方式的问题

数据库和上一节的一致

目标是模拟登录,控制台输入账号密码,判断是否登录成功

关键代码和注释

package com.tencent.api.statement;

import com.mysql.cj.jdbc.Driver;

import java.sql.*;
import java.util.Properties;
import java.util.Scanner;

/**
 * 目标:
 *      1.明确jdbc的使用流程 和 详细讲解内部设计API的步骤
 *      2.发现问题,引出prepareStatement
 * 需求:
 *      1.输入账号密码
 *      2.进行数据库信息查询(atguigu u_ser)
 *      3.反馈登录成功或者失败
 *基本流程:
 *      1.键盘输入事件,收集账号和密码信息
 *      2.注册驱动
 *      3.获取连接
 *      4.创建Statement
 *      5.发送sql语句,获取结果
 *      6.结果判断,显示登录成功还是失败
 *      7.关闭资源
 */
public class StatementUserLoginPart {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        //1.键盘输入事件,收集账号和密码信息
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入账号:");
        String account = scanner.nextLine();
        System.out.println("请输入密码:");
        String password = scanner.nextLine();

        /**
         * 方案1:
         * DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
         *      问题:注册两次驱动
         *          DriverManager.registerDriver()方法本身会注册一次
         *          new Driver()注册一次,因为Driver的无参构造函数中有静态代码块,Driver.static{DriverManagger.registerDriver()}
         *      解决:只想注册一次驱动
         *          只触发静态代码块即可 Driver
         *      触发静态代码块:
         *          类加载机制:类加载的时刻,会触发静态代码块
         *                  加载 【class文件->jvm虚拟机的class对象】
         *                  连接 【验证(检查文件类型)->准备(静态变量初始值)->解析(触发静态代码块】
         *                  初始化(静态属性赋真实值)
         *          以下7种方式会触发类加载:
         *              1. new关键字
         *              2. 调用静态属性
         *              3. 调用静态方法
         *              4. 接口 包含1.8 新特性 default关键字
         *              5. 反射 【Class.forName() 类名.class】
         *              6. 子类调用会触发父类的静态代码块
         *              7. 触发类的入口方法main
         *
         */
        //2.注册驱动
        //方法1:两次注册
//        DriverManager.registerDriver(new Driver());

        //方法2:不够优雅  这只能适合于MySQL,oracle不适用,以后换数据库得改代码
//        new Driver();

        //方式3:反射,触发类加载,触发静态静态代码块的调用。字符串可以提取到外部的配置文件,在不改变代码的情况下,完成数据库驱动的切换
        Class.forName("com.mysql.cj.jdbc.Driver");

        /**
         * 重写: 为了子类扩展父类的方法!父类也间接的规范了子类方法的参数和返回!
         * 重载: 重载一般应用在第三方的工具类上,为了方便用户多种方式传递参数形式!简化形式!
         */
        /**
         * 三个参数:
         *    String URL: 连接数据库地址
         *    String user: 连接数据库用户名
         *    String password: 连接数据库用户对应的密码
         * 数据库URL语法:
         *    JDBC:
         *        jdbc:mysql | jdbc:oracle :// 127.0.0.1 | localhost : 3306 / 数据库名
         *        举例:
         *          jdbc:mysql://localhost:3306/day01
         *          jdbc:mysql://192.168.33.45/3306/day01
         *          当前电脑的省略写法! 注意:本机和端口3306
         *              jdbc:mysql://localhost:3306/day01 = jdbc:mysql:///day01
         *
         * 两个参数:
         *     String URL : 和三个参数的url作用一样。  jdbc:mysql://127.0.0.1:3306/gtguigu
         *     Properties : 就是一个参数封装容器!至少要包含 user / password key!存储连接账号信息!
         *                  properties类似于Map,只不过key=value 都是字符串形式的
         *                  key user : 账号信息
         *                  key password : 密码信息
         * 一个参数:
         *    String URL: URl可以携带目标地址,可以通过?分割,在后面key=value&key=value形式传递参数
         *                jdbc:mysql:///day01?user=root&password=123456
         *                携带固定的参数名 user password 传递账号和密码信息
         * 扩展路径参数(了解):
         *    serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true
         *
         */
        //3.获取连接
        //三个参数
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "1234");

        //两个参数
//        Properties info = new Properties();
//        info.put("uer","root");
//        info.put("password","1234");
//        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", info);
        //一个参数
//        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu?user=root&password=1234");

        //4.创建Statement  "小汽车"
        Statement statement = connection.createStatement();

        //5.发送sql语句,获取结果
        String sql = "select account,PASSWORD,nickname from t_user where account='"+account+"' and PASSWORD='"+password+"';";
        //上面有易错点:sql语句为 :select account,PASSWORD,nickname from t_user where account='root' and PASSWORD='123456';
        //在用变量平替的时候,单引号不能去掉!!!!!!


        /**
         * SQL分类:DDL(容器创建,修改,删除),DML(插入,修改,删除);DQL(查询);DCL(权限控制); TPL(事务控制)
         *
         *  ResultSet 结果集对象 = executeQuery(DQL语句)
         *
         *  int       响应行数  = executeUpdate(非DQL语句)
         *          情况1:DML 返回影响的行数
         *          情况2:非DML,返回0
         */
//        int i = statement.executeUpdate(sql);
        ResultSet resultSet = statement.executeQuery(sql);

        /**
         *
         * 你必须有面向对象的思维:Java是面向对象编程的语言 OOP!
         *
         * 1.需要理解ResultSet的数据结构和小海豚查询出来的是一样,需要在脑子里构建结果表!
         * 2.有一个光标指向的操作数据行,默认指向第一行的上边!
         *      我们需要 1.移动光标,指向行,    2.再获取列
         *        1.游标移动问题:
         *        resultSet内部包含一个游标,指定当前数据
         *        默认指在第一行数据之前
         *        boolean = next()
         *              false: 没有数据,也不移动了!
         *              true:  有更多行,并且移动到下一行!
         *       推荐:推荐使用if 或者 while循环,嵌套next方法,循环和判断体内获取数据!
         *       if(next()){获取列的数据!} ||  while(next()){获取列的数据!}
         *
         * 3.获取当前行列的数据!
         *         resultSet.get类型(int columnIndex | String columnLabel)
         *        列名获取  //label 如果没有别名,等于列名, 有别名label就是别名,他就是查询结果的标识!
         *        列的角标  //从左到右 从1开始! 数据库全是从1开始!
         */
        //6.结果判断,显示登录成功还是失败
//        while (resultSet.next()){
//            //已经指定当前行了
//            int id = resultSet.getInt("id");
//            String account1 = resultSet.getString("account");
//            String password1 = resultSet.getString("PASSWORD");
//            String nickname = resultSet.getString("nickname");
//            System.out.println(id+"  "+account1+"  "+password1+"  "+nickname);
//        }
        //移动一次光标,只要有数据,就代表登录成功
        if(resultSet.next()){
            System.out.println("登录成功!");
        }else {
            System.out.println("登录失败!");
        }

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

存在问题

  • SQL语句需要字符串拼接,比较麻烦
  • 只能拼接字符串类型,其他的数据库类型无法处理
  • SQL注入风险

基于preparedStatement方式优化

易错点:

		//编写SQL语句
        String sql = "select * from t_user where account = ? and password = ? ;";

        //创建预编译statement并且设置SQL语句结果
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //单独的占位符进行赋值
        /**
         * 参数1:index 占位符的位置 从左向右数 从1开始
         * 参数2:object 占位符的值  可以设置任何类型的数据,避免了拼接,使类型更加丰富
         */
        preparedStatement.setObject(1,account);
        preparedStatement.setObject(2,password);

        //发送SQL语句,获取返回的结果
        ResultSet resultSet = preparedStatement.executeQuery();//易错点:注意,这里不加参数!!!!!

关键代码和注释

package com.tencent.api.preparedstatement;

import java.sql.*;
import java.util.Scanner;

/**
 * TODO:防止注入攻击 | 演示ps的使用流程
 *
 */
public class PSUserLoginPart {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("输入账号:");
        String account = scanner.nextLine();
        System.out.println("输入密码:");
        String password = scanner.nextLine();

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


        //获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "1234");

        /**
         *
         * statement
         *  1.创建statement
         *  2.拼接SQL语句
         *  3.发送SQL语句,获取返回的结果
         *
         *  preparedStatement
         *  1.编写SQL语句结果  不包含动态值部分的语句,动态值部分用占位符 ? 代替, 注意:?只能替代动态值
         *  2.创建preparedStatement,并传入动态值
         *  3.动态值 占位符 赋值 ? 单独 赋值即可
         *  4.发送SQL语句,获取返回的结果
         */
//        //创建statement
//        Statement statement = connection.createStatement();
//
//        //发送sql语句并得到返回结果
//        String sql = "select account,PASSWORD,nickname from t_user where account='"+account+"' and PASSWORD='"+password+"';";
//        ResultSet resultSet = statement.executeQuery(sql);

        //编写SQL语句
        String sql = "select * from t_user where account = ? and password = ? ;";

        //创建预编译statement并且设置SQL语句结果
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //单独的占位符进行赋值
        /**
         * 参数1:index 占位符的位置 从左向右数 从1开始
         * 参数2:object 占位符的值  可以设置任何类型的数据,避免了拼接,使类型更加丰富
         */
        preparedStatement.setObject(1,account);
        preparedStatement.setObject(2,password);

        //发送SQL语句,获取返回的结果
        ResultSet resultSet = preparedStatement.executeQuery();//易错点:注意,这里不加参数!!!!!

        //解析结果
        if(resultSet.next()){
            System.out.println("登录成功!");
        }else {
            System.out.println("登录失败!");
        }
        //释放资源
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }
}

基于preparedStatement进行CURD 增删改查(范例)

关键代码

package com.tencent.api.preparedstatement;

import org.junit.Test;

import java.sql.*;
import java.util.*;

public class PSCURDPart {
    @Test
    public void testInsert() throws ClassNotFoundException, SQLException {
        //注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "1234");
        //编写SQL语句,动态值的部分使用?代替
        String sql ="INSERT INTO t_user(account, PASSWORD, nickname) values (?,?,?);";
        //创建preparedStatement,并且传入SQL语句结果
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //占位符赋值
        preparedStatement.setObject(1,"test");
        preparedStatement.setObject(2,"test");
        preparedStatement.setObject(3,"二狗砸");

        //发送SQL语句,并接收
        int rows = preparedStatement.executeUpdate();
        //解析结果
        if(rows>0){
            System.out.println("数据插入成功!");
        }else {
            System.out.println("数据插入失败!");
        }
        //释放资源
        preparedStatement.close();
        connection.close();
    }
    
    @Test
    public void testUpdate() throws ClassNotFoundException, SQLException {
        //update t_user set nickname='三狗子' where id = 3;

        //注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
        //编写SQL语句,动态值的部分使用?代替
        String sql ="update t_user set nickname=? where account = ?";
        //创建preparedStatement,并且传入SQL语句结果
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //占位符赋值
        preparedStatement.setObject(1,"wangjiajia");
        preparedStatement.setObject(2,"test");
        //发送SQL语句,并接收
        int rows = preparedStatement.executeUpdate();
        //解析结果
        if (rows>0){
            System.out.println("更新成功!");
        }else {
            System.out.println("更新失败!");
        }
        //释放资源
        preparedStatement.close();
        connection.close();

    }
    
    @Test
    public void testDelete() throws ClassNotFoundException, SQLException {
        //delete from t_user where account='test';
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
        String sql = "delete from t_user where account=?;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1,"test");
        int rows = preparedStatement.executeUpdate();
        if (rows>0)
            System.out.println("删除成功!");
        else
            System.out.println("删除失败!");
        preparedStatement.close();
        connection.close();
    }


    /**
     * 目标:查询所用用户数据,并且封装到一个Lis<Map> list集合中
     *
     * 数据库 -> resultSet -> java -> 一行 - map(key=列名,value=列的内容) ->  Lis<Map> list
     *
     * 实现思路:
     *      遍历行数据,一行对应一个map,获取一行的列名和对应的列的属性,装配即可
     *      将map装到一个集合就可以了
     *
     * 难点:
     *      如何获取列的名称?
     *
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    @Test
    public void testSelect() throws ClassNotFoundException, SQLException {
        //select id,account,PASSWORD,nickname from t_user;
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
        String sql = "select id,account,PASSWORD,nickname from t_user";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
//        preparedStatement.setObject(1,"test");
        ResultSet resultSet = preparedStatement.executeQuery();
//        while (resultSet.next()){
//            String account = resultSet.getString("account");
//            String password = resultSet.getString("password");
//            String nickname = resultSet.getString("nickname");
//            System.out.println(account+password+nickname);
//        }

        List<Map> list = new ArrayList<>();

        //获取列的信息对象
        //metaData装的就是当前结果集列的信息对象(可以获取列的名称根据下标,可以获取列的数量
        ResultSetMetaData metaData = resultSet.getMetaData();

//        获取列的数量,有了它以后可以水平遍历列
        int columnCount = metaData.getColumnCount();

        while (resultSet.next()){
            Map map = new HashMap();
            //一行数据对应一个map

//            //纯手动取值
//            map.put("id",resultSet.getString("id"));
//            map.put("account",resultSet.getString("account"));
//            map.put("password",resultSet.getString("password"));
//            map.put("nickname",resultSet.getString("nickname"));

            //自动水平遍历列,从1开始,因为数据库中下标从1开始;并且小于等于总列数
            for (int i = 1; i <= columnCount; i++) {
                //获取指定列下标的值
                Object value = resultSet.getObject(i);
                //获取指定列下标的列的名称
                //getColumnLabel:会优先获取列的别名,没有别名选名称; 不要使用getColumnName:只会获取列名
                String columnLabel = metaData.getColumnLabel(i);
                map.put(columnLabel,value);
            }
            //一行数据中的所有列全部存到了map中
            //将map存储到集合中即可
            list.add(map);
        }
        System.out.println(list);
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }
    
    //自己复现一遍
    @Test
    public void testSelectOK() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
        String sql = "select * from t_user;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        ResultSet resultSet = preparedStatement.executeQuery();
        List<Map> list = new ArrayList<>();
        ResultSetMetaData metaData = resultSet.getMetaData();
        int columnCount = metaData.getColumnCount();
        while (resultSet.next()){
            Map map = new HashMap();
            for (int i = 1; i <= columnCount; i++) {
                Object object = resultSet.getObject(i);
                String columnLabel = metaData.getColumnLabel(i);
                map.put(columnLabel,object);

            }
            list.add(map);
        }
        System.out.println(list);
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }
}

JDBC扩展提升

自增长主键回显实现

功能需求

  1. java程序获取插入数据时,MySQL维护自增长的主键id值,就是主键回显
  2. 作用:在多表关联插入数据时,一般主表的主键都是自动生成的,所以在插入数据之前无法知道这条数据的主键,但是从表需要在插入数据前就绑定主表的主键,这可以使用主键回显技术
  3. 简单理解:auto increment的主键的值需要返回给程序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t5lqT2Mk-1686999630625)(学习jdbc.assets/image-20230616105009162.png)]

功能实现

核心实现代码:

        //创建preparedStatement的时候,告知,携带回数据库自增长的主键
        PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
        
 
  			//获取司机装主键值的结果集对象,一行一列,获取对应的数据即可
            ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
            generatedKeys.next();//移动一下光标
            int id = generatedKeys.getInt(1);
            System.out.println("id:"+id);

继续使用前面的数据库

/**
 * 练习preparedStatement的特殊使用情况
 */
public class PSOtherPart {

    /**
     * Todo:
     *      t_user插入一条数据,并且获取数据库自增长的主键
     *
     * Todo:使用总结
     *      1.创建preparedStatement的时候,告知,携带回数据库自增长的主键(sql,Statement.RETURN_GENERATED_KEYS)
     *      2.获取司机装主键值的结果集对象,一行一列,获取对应的数据即可(ResultSet generatedKeys = preparedStatement.getGeneratedKeys();)
     */
    @Test
    public void returnPrimaryKey() throws ClassNotFoundException, SQLException {
        //注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //创建连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
        //写SQL语句
        String sql = "INSERT INTO t_user(account, PASSWORD, nickname) values (?,?,?);";
        //创建preparedStatement
        //创建preparedStatement的时候,告知,携带回数据库自增长的主键
        PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
        //动态绑定数据内容,占位符赋值
        preparedStatement.setObject(1,"test1");
        preparedStatement.setObject(2,"123456");
        preparedStatement.setObject(3,"驴蛋蛋");
        //发送 ,查询,得到结果
        int rows = preparedStatement.executeUpdate();
        //解析结果
        if(rows>0){
            System.out.println("插入成功!");

            //可以获取回显的主键
            //获取司机装主键值的结果集对象,一行一列,获取对应的数据即可
            ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
            generatedKeys.next();//移动一下光标
            int id = generatedKeys.getInt(1);
            System.out.println("id:"+id);
        }else {
            System.out.println("插入失败!");
        }
        //释放资源
        preparedStatement.close();
        connection.close();
    }

批量数据数据插入性能提升

批量插入核心代码

    /**
     * TODO:总结批量插入
     *      1.路径后面添加?rewriteBatchedStatements=true 允许批量插入
     *      2.insert into values,不能是value,最后不能写;
     *      3.不是执行语句每条,而是批量添加 addBatch()
     *      4.遍历 添加完毕后,统一 批量执行executeBatch()
     */
     
      //创建连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?rewriteBatchedStatements=true", "root", "1234");
        
        String sql = "INSERT INTO t_user(account, PASSWORD, nickname) values (?,?,?)";//一定要写values,不能是value
        
        //动态绑定数据内容,占位符赋值
        for (int i = 0; i < 10000; i++) {
            preparedStatement.setObject(1,"yba"+i);
            preparedStatement.setObject(2,"wxja"+i);
            preparedStatement.setObject(3,"wjja"+i);

            //不执行,追加到values的后面
            preparedStatement.addBatch();
        }
        //执行批量操作
        preparedStatement.executeBatch();

普通插入耗时5285ms

/**
     * 使用普通的方式创建10000条数据
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    @Test
    public void testInsert() throws ClassNotFoundException, SQLException {
        //注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //创建连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
        //写SQL语句
        String sql = "INSERT INTO t_user(account, PASSWORD, nickname) values (?,?,?);";
        //创建preparedStatement
        //创建preparedStatement的时候,告知,携带回数据库自增长的主键
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        long start = System.currentTimeMillis();

        //动态绑定数据内容,占位符赋值
        for (int i = 0; i < 10000; i++) {
            preparedStatement.setObject(1,"yb"+i);
            preparedStatement.setObject(2,"wxj"+i);
            preparedStatement.setObject(3,"wjj"+i);

            //发送 ,查询,得到结果
            preparedStatement.executeUpdate();
        }

        long end = System.currentTimeMillis();

        //解析结果
        System.out.println("执行10000次数据消耗的时间"+(end-start));//执行10000次数据消耗的时间5285
        //释放资源
        preparedStatement.close();
        connection.close();
    }

批量插入耗时222ms

/**
     * 使用批量的方式创建10000条数据
     *
     * TODO:总结批量插入
     *      1.路径后面添加?rewriteBatchedStatements=true 允许批量插入
     *      2.insert into values,不能是value,最后不能写;
     *      3.不是执行语句每条,而是批量添加 addBatch()
     *      4.遍历 添加完毕后,统一 批量执行executeBatch()
     */
    @Test
    public void testBatchInsert() throws ClassNotFoundException, SQLException {
        //注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //创建连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?rewriteBatchedStatements=true", "root", "1234");
        //写SQL语句
        String sql = "INSERT INTO t_user(account, PASSWORD, nickname) values (?,?,?)";//一定要写values,不能是value
        //创建preparedStatement
        //创建preparedStatement的时候,告知,携带回数据库自增长的主键
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        long start = System.currentTimeMillis();

        //动态绑定数据内容,占位符赋值
        for (int i = 0; i < 10000; i++) {
            preparedStatement.setObject(1,"yba"+i);
            preparedStatement.setObject(2,"wxja"+i);
            preparedStatement.setObject(3,"wjja"+i);

            //不执行,追加到values的后面
            preparedStatement.addBatch();
        }
        //执行批量操作
        preparedStatement.executeBatch();

        long end = System.currentTimeMillis();

        //解析结果
        System.out.println("执行10000次数据消耗的时间"+(end-start));//执行10000次数据消耗的时间5285
        //释放资源
        preparedStatement.close();
        connection.close();
    }

JDBC中数据库事务的实现

目标

使用jdbc代码,添加数据库事务动作

  • 开启事务
  • 事务提交,事务回滚

事务概念回顾

对应在jdbc中的实现

try{
	connection.setAutoCommit(false);//关闭自动提交
	
	//只要是当前connection对象进行数据库操作,都不会自动 提交事务
	//statement是单一的数据库动作,增删改查等
	
	connection.commit();
}catch(Exception e){
	connection.rollback;
}

数据准备

创建银行表并插入两条数据

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

select * from t_bank;

代码结构设计

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gZKIrk11-1686999630625)(学习jdbc.assets/image-20230616155544940.png)]

Database Access Object(DAO)是一种设计模式,将应用程序的业务逻辑与访问逻辑分离。DAO提供了一个抽象的接口,使应用程序可以访问数据存储。

没有设置事务出现的问题

在没有设置设置事务前,只是逻辑上控制加钱减钱,当银行账户前为负数时,会报错:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6xi4PLZY-1686999630626)(学习jdbc.assets/image-20230616161933094.png)]

查询数据库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BBBBKcuI-1686999630626)(学习jdbc.assets/image-20230616161959780.png)]

发现二者和不是2000了,不能保证数据库事务的一致性要求

这部分的代码实现

package com.tencent.api.transaction;

import org.junit.Test;

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

/**
 * 银行卡业务方法,调用dao方法
 */
public class BankService {

    public void transfer(String addAccount,String subAccount,int money) throws SQLException, ClassNotFoundException {

        BankDao dao = new BankDao();
        dao.add(addAccount,money);
        System.out.println("-------------");
        dao.sub(subAccount,money);
    }

    @Test
    public void start() throws SQLException, ClassNotFoundException {
        transfer("ergouzi","lvdandan",500);
    }
}

package com.tencent.api.transaction;

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

/**
 * bank表的数据库操作方法存储类
 */
public class BankDao {
    /**
     * 加钱的数据库操作方法(jdbc)
     * @param account 加钱的账号
     * @param money 加钱的金额
     */
    public void add(String account,int money) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
        String sql = "update t_bank set money=money+? where account=?;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1,money);
        preparedStatement.setObject(2,account);
        preparedStatement.executeUpdate();
        preparedStatement.close();
        connection.close();
        System.out.println("加钱成功!");
    }

    /**
     * 减钱的数据库操作方法(jdbc)
     * @param account 减钱的账号
     * @param money 减钱的金额
     */
    public void sub(String account,int money) throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
        String sql = "update t_bank set money=money-? where account=?;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1,money);
        preparedStatement.setObject(2,account);
        preparedStatement.executeUpdate();
        preparedStatement.close();
        connection.close();
        System.out.println("减钱成功!");
    }
}

jdbc 事务实现

核心代码

/**
     * 
     * TODO:
     *      事务添加是在业务方法中(service)
     *      利用try catch代码块,开启事务和提交事务,和事务回滚
     *      将connection传入dao层即可,dao只负责使用,对connection不要用close() 
     */
public void transfer(String addAccount,String subAccount,int money) throws SQLException, ClassNotFoundException {

        BankDao dao = new BankDao();

        //一个事务的最基本的要求,必须是同一个连接对象 connection
        //一个转账方法,应该属于同一个事务
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");

        try {
            //开启事务
            connection.setAutoCommit(false);//事务的自动提交功能关上!!!
            //执行数据库动作
            dao.add(addAccount,money,connection);
            System.out.println("-------------");
            dao.sub(subAccount,money,connection);

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

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

    }

关键代码(看注释)

package com.tencent.api.transaction;

import org.junit.Test;

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

/**
 * 银行卡业务方法,调用dao方法
 */
public class BankService {

    /**
     * 
     * TODO:
     *      事务添加是在业务方法中(service)
     *      利用try catch代码块,开启事务和提交事务,和事务回滚
     *      将connection传入dao层即可,dao只负责使用,对connection不要用close()     * 
     * @param addAccount
     * @param subAccount
     * @param money
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public void transfer(String addAccount,String subAccount,int money) throws SQLException, ClassNotFoundException {

        BankDao dao = new BankDao();

        //一个事务的最基本的要求,必须是同一个连接对象 connection
        //一个转账方法,应该属于同一个事务

        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");

        try {
            //开启事务
            connection.setAutoCommit(false);//事务的自动提交功能关上!!!
            //执行数据库动作
            dao.add(addAccount,money,connection);
            System.out.println("-------------");
            dao.sub(subAccount,money,connection);

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

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

    }

    @Test
    public void start() throws SQLException, ClassNotFoundException {
        transfer("ergouzi","lvdandan",500);
    }
}


package com.tencent.api.transaction;

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

/**
 * bank表的数据库操作方法存储类
 */
public class BankDao {
    /**
     * 加钱的数据库操作方法(jdbc)
     * @param account 加钱的账号
     * @param money 加钱的金额
     */
    public void add(String account,int money,Connection connection) throws  SQLException {
//        Class.forName("com.mysql.cj.jdbc.Driver");
//        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
        String sql = "update t_bank set money=money+? where account=?;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1,money);
        preparedStatement.setObject(2,account);
        preparedStatement.executeUpdate();
        preparedStatement.close();
//        connection.close();
        System.out.println("加钱成功!");
    }

    /**
     * 减钱的数据库操作方法(jdbc)
     * @param account 减钱的账号
     * @param money 减钱的金额
     */
    public void sub(String account,int money,Connection connection) throws SQLException {
//        Class.forName("com.mysql.cj.jdbc.Driver");
//        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
        String sql = "update t_bank set money=money-? where account=?;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1,money);
        preparedStatement.setObject(2,account);
        preparedStatement.executeUpdate();
        preparedStatement.close();
//        connection.close();
        System.out.println("减钱成功!");
    }
}

Druid连接池技术使用

连接池性能消耗问题分析

connection可以复用!

数据库连接池的作用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q4RB8rho-1686999630626)(学习jdbc.assets/image-20230616170206718.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eR650Wb6-1686999630626)(学习jdbc.assets/image-20230616170822733.png)]

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

javax.sql.DataSource接口,规范了连接池获取连接和回收连接的方法

DataSource = 第三方连接池实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c5QH7Oyl-1686999630626)(学习jdbc.assets/image-20230616171938724.png)]

国货之光Druid连接池的使用

记得导入Druid工具类jar

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

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

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

        //设置参数
        //必须 连接数据库驱动类的全限定符   [注册驱动] 和url和user和password
        //帮我们完成驱动注册和连接
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/atguigu");
        dataSource.setUsername("root");
        dataSource.setPassword("1234");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.driver");
        //非必须 初始化连接数量 最大的连接数量。。。
        dataSource.setInitialSize(5);//初始化连接数量
        dataSource.setMaxActive(10);//最大的连接数量

        //获取连接
//        DruidPooledConnection connection = dataSource.getConnection();
        Connection connection = dataSource.getConnection();//用父接口类接收即可

        //数据库CURD

        //回收链接
        connection.close();//连接池提供的连接,close,就是回收连接
    }

软编码方式(推荐)

外部配置

  • 存放在src/druid.properties
#key = value  =>  java properties读取 (key|value)
#druid配置的key固定命名

driverClassname=com.mysql.cj.jdbc.Driver
username=root
password=1234
url=jdbc:mysql://127.0.0.1/atguigu

Druid声明

 /**
     * 通过读取外部配置文件的方法,实例化druid连接池对象
     */
    @Test
    public void testSoft() throws Exception {
        
        //1.读取外部配置文件 properties
        Properties properties = new Properties();
        //src下的文件,可以使用类加载器提供的方法
        InputStream ips = DruidUsedPart.class.getClassLoader().getResourceAsStream("druid.properties");
        properties.load(ips);
        
        //2.使用连接池的工具类的工程模式,创建连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

        Connection connection = dataSource.getConnection();
        
        //数据库的CURD
        
        connection.close();
    }

JDBC使用优化以及工具类封装

jdbc工具类封装v1.0:将druid连接池对象、连接和释放方法封装到工具类

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

外部配置文件

  • 存放在src/druid.properties
#key = value  =>  java properties读取 (key|value)
#druid配置的key固定命名

driverClassname=com.mysql.cj.jdbc.Driver
username=root
password=1234
url=jdbc:mysql://127.0.0.1/atguigu

工具类代码

/**
 * v1.0版本的工具类
 *      内部包含一个连接池对象,并且对外提供获取连接和回收连接的方法
 *
 * 小建议:
 *      工具类的方法,推荐写成静态的,外部可以通过类名.方法()的方式调用,更加方便
 *
 * 实现:
 *      属性:连接池对象 【实例化一次】
 *          单例模式
 *          静态代码块:
 *              static{
 *                  全局调用一次
 *              }
 *      方法:
 *          对外提供连接的方法
 *          回收外部传入连接的方法
 */
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) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        try {
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
            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()就是回收
    }
}

jdbc工具类封装v2.0:在v1的基础上,考虑事务前提 ,引入ThreadLocal,一个线程的不同方法使用同一个connection对象

对版本1进行优化:考虑事务的前提下,一个线程的不同方法如何获得同一个连接?

ThreadLocal的介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3Lq1WjC-1686999630627)(学习jdbc.assets/image-20230616202519067.png)]

使用同一个 线程,调用方法(add()、sub())的时候不需要传入connection对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wv5lkAM1-1686999630627)(学习jdbc.assets/image-20230616204309406.png)]

代码实现

工具类 utils/JDBCUtilsV2.java

/**
 * v1.0版本的工具类
 *      内部包含一个连接池对象,并且对外提供获取连接和回收连接的方法
 *
 * 小建议:
 *      工具类的方法,推荐写成静态的,外部可以通过类名.方法()的方式调用,更加方便
 *
 * 实现:
 *      属性:连接池对象 【实例化一次】
 *          单例模式
 *          静态代码块:
 *              static{
 *                  全局调用一次
 *              }
 *      方法:
 *          对外提供连接的方法
 *          回收外部传入连接的方法
 *
 * 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) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        try {
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 对外提供连接的方法
     * @return
     */
    public static Connection getConnection() throws SQLException {

        //线程本地变量中是否存在
        Connection connection = tl.get();

        //第一次没有
        if (connection == null){
            //线程本地变量没有,连接池获取
            connection = dataSource.getConnection();
            //把刚刚获取的connection存到本地变量
            tl.set(connection);

        }

        return connection;
    }

    public static void freeConnection() throws SQLException {//注意这里不需要传入connection参数
        Connection connection = tl.get();
        if (connection != null) {
            tl.remove();//清空线程本地变量数据
            connection.setAutoCommit(true);//事务状态回归
            connection.close();//会收到连接池即可
        }
    }
}

新的事务类方法

BankDao.java

/**
 * bank表的数据库操作方法存储类
 */
public class BankDao {
    /**
     * 加钱的数据库操作方法(jdbc)
     * @param account 加钱的账号
     * @param money 加钱的金额
     */
    public void add(String account,int money) throws  SQLException {

        //使用新版本的JDBCUtilsV2,可以不在方法声明部分添加Connection变量,可以直接调用工具类JDBCUtilsV2的getConnection()方法
        Connection connection = JDBCUtilsV2.getConnection();

        String sql = "update t_bank set money=money+? where account=?;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1,money);
        preparedStatement.setObject(2,account);
        preparedStatement.executeUpdate();
        preparedStatement.close();
//        connection.close();
        System.out.println("加钱成功!");
    }

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

        Connection connection = JDBCUtilsV2.getConnection();

        String sql = "update t_bank set money=money-? where account=?;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1,money);
        preparedStatement.setObject(2,account);
        preparedStatement.executeUpdate();
        preparedStatement.close();
//        connection.close();
        System.out.println("减钱成功!");
    }
}

BankService

public class BankService {

    /**
     *
     * TODO:
     *      事务添加是在业务方法中(service)
     *      利用try catch代码块,开启事务和提交事务,和事务回滚
     *      将connection传入dao层即可,dao只负责使用,对connection不要用close()     *
     *
     * @param addAccount
     * @param subAccount
     * @param money
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public void transfer(String addAccount,String subAccount,int money) throws SQLException, ClassNotFoundException {

        BankDao dao = new BankDao();

        //一个事务的最基本的要求,必须是同一个连接对象 connection
        //一个转账方法,应该属于同一个事务

        //下面内容也省略了
//        Class.forName("com.mysql.cj.jdbc.Driver");
//        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
        //新
        Connection connection = JDBCUtilsV2.getConnection();

        try {
            //开启事务
            connection.setAutoCommit(false);//事务的自动提交功能关上!!!
            //执行数据库动作
//            dao.add(addAccount,money,connection);不需要传递connection
            dao.add(addAccount,money);
            System.out.println("-------------");
//            dao.sub(subAccount,money,connection);
            dao.sub(subAccount,money);

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

            //抛出异常
            throw e;
        }finally {
//            connection.close();
            JDBCUtilsV2.freeConnection();
        }
    }

    @Test
    public void start() throws SQLException, ClassNotFoundException {
        transfer("ergouzi","lvdandan",500);
    }
}

高级应用层封装BaseDAO

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VWtYYG0C-1686999630627)(学习jdbc.assets/image-20230616212327986.png)]

BaseDao源码

package com.atguigu.cms.utils;

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

/**
 * 封装Dao数据库重复代码
 * 封装两个方法:
 *      一个简化非DQL
 *      一个简化DQL
 */
public abstract class BaseDao {

    /**
     * 封装简化非DQL语句
     * @param sql       带占位符的SQL语句
     * @param params    占位符的值,注意,传入SQL语句的值和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]);
            }
        }


        int rows = preparedStatement.executeUpdate();

        preparedStatement.close();

        //是否回收连接,需要考虑是不是事务
//        connection.setAutoCommit(false);//开启事务了,不要关连接了,业务层service处理
        if (connection.getAutoCommit()) {
            //没有开启事务,正常回收连接
            JDBCUtilsV2.freeConnection();//记得调用工具类,不要直接写connection.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 = class
     *      2.要使用反射技术属性赋值
     *
     *
     * 查询结果封装到一个实体类集合
     * @param clazz 要接值的实体类集合的模板对象
     * @param sql 查询语句,要求列名或者别名等于实体类的属性名!!!一定要格外注意
     * @param parameters 占位符的值,要和?位置对应
     * @param <T> 声明的结果的类型
     * @return 声明的结果的类型
     */
    public <T> List<T> executeQuery(Class<T> clazz,String sql,Object ... parameters) throws SQLException, IllegalAccessException, InstantiationException, NoSuchFieldException {


        Connection connection = JDBCUtilsV2.getConnection();

        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        if (parameters != null && parameters.length !=0){
            for (int i = 1; i <= parameters.length; i++) {
                preparedStatement.setObject(i,parameters[i-1]);
            }
        }

        ResultSet resultSet = preparedStatement.executeQuery();

        //这个结果集中不装list<Map>了,而是装T的结果集
        List<T> list = new ArrayList<>();

        //获取列的数据
        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);
                //对象的属性名
                String columnLabel = metaData.getColumnLabel(i);

                //反射,给对象的属性值赋值
                Field field = clazz.getDeclaredField(columnLabel);
                field.setAccessible(true);//属性可以设置,打破private的修饰限制
                /**
                 * 参数1:要赋值的对象,如果属性是静态属性,第一个参数可以为null
                 * 参数2:具体的属性值
                 */
                field.set(t,value);
            }
            //将t存储到集合中
            list.add(t);
        }
        resultSet.close();
        preparedStatement.close();
        if (connection.getAutoCommit()) {
            JDBCUtilsV2.freeConnection();
        }

        return list;
    }
}

测试BaseDao(utils包下面的PSCURDPart.java)

package com.tencent.api.utils;

import com.tencent.api.transaction.BankDao;
import org.junit.Test;

import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class PSCURDPart extends BaseDao{
    @Test
    public void testInsert() throws ClassNotFoundException, SQLException {
        String sql ="INSERT INTO t_user(account, PASSWORD, nickname) values (?,?,?);";
        int rows = executeUpdate(sql, "test", "testpsword", "不知道对不对");

        if(rows>0){
            System.out.println("数据插入成功!");
        }else {
            System.out.println("数据插入失败!");
        }
    }
    @Test
    public void testUpdate() throws ClassNotFoundException, SQLException {
        //update t_user set nickname='三狗子' where id = 3;
        //编写SQL语句,动态值的部分使用?代替
        String sql ="update t_user set nickname=? where account = ?";

        int rows = executeUpdate(sql,"wxj1234","test");
        //解析结果
        if (rows>0){
            System.out.println("更新成功!");
        }else {
            System.out.println("更新失败!");
        }

    }
    @Test
    public void testDelete() throws ClassNotFoundException, SQLException {
        String sql = "delete from t_user where account=?;";
        int rows = executeUpdate(sql,"test");
        if (rows>0)
            System.out.println("删除成功!");
        else
            System.out.println("删除失败!");
    }


    /**
     * 目标:查询所用用户数据,并且封装到一个Lis<Map> list集合中
     *
     * 数据库 -> resultSet -> java -> 一行 - map(key=列名,value=列的内容) ->  Lis<Map> list
     *
     * 实现思路:
     *      遍历行数据,一行对应一个map,获取一行的列名和对应的列的属性,装配即可
     *      将map装到一个集合就可以了
     *
     * 难点:
     *      如何获取列的名称?
     *
     * @throws ClassNotFoundException
     * @throws SQLException
     */

    //自己复现一遍
    @Test
    public void testSelectOK() throws ClassNotFoundException, SQLException {
        //这里暂时用不了查询方法,因为整个体系结构中没有设置实体类


        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
        String sql = "select * from t_user;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        ResultSet resultSet = preparedStatement.executeQuery();
        List<Map> list = new ArrayList<>();
        ResultSetMetaData metaData = resultSet.getMetaData();
        int columnCount = metaData.getColumnCount();
        while (resultSet.next()){
            Map map = new HashMap();
            for (int i = 1; i <= columnCount; i++) {
                Object object = resultSet.getObject(i);
                String columnLabel = metaData.getColumnLabel(i);
                map.put(columnLabel,object);

            }
            list.add(map);
        }
        System.out.println(list);
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }
}

基于CMS项目JDBC实战练习

准备工作

  • 导入项目(我基本不需要改动东西)

  • 创建表格

show databases ;

use atguigu;

create table t_customer(
    id int primary key auto_increment comment '客户主键',
    name varchar(20) comment '客户姓名',
    gender varchar(4) comment '客户性别',
    age int,
    salary double(8,1),
    phone varchar(11)
)
  • idea和数据库建立连接

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6kebMscg-1686999630628)(学习jdbc.assets/image-20230617171919792.png)]

  • 修改配置文件成自己数据库的连接方式

    driverClassName=com.mysql.cj.jdbc.Driver
    username=root
    password=1234
    url=jdbc:mysql:///atguigu
    initialSize=5
    
  • 导入baseDao工具类和JDBCUtils

  • 导入依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vvoKZgjw-1686999630628)(学习jdbc.assets/image-20230617175005609.png)]

(不知道为啥课程资料给的不是视频中所述的原版资料,所以领会思想好了)

实战改造

主要是改变了CustomerService.java文件,生成了CustomerDao.java文件

CustomerService.java

public class CustomerService {


    private CustomerDao customerDao = new CustomerDao();

    /**
     * 用途:查询数据库客户集合
     * 返回:集合
     */
    public List<Customer> getList() throws SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        return customerDao.findAll();
    }


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

    /**
     * 用途:返回指定id的客户对象记录
     * 参数: id 就是要获取的客户的id号.
     * 返回:封装了客户信息的Customer对象
     */
    public Customer getCustomer(int id) throws SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        return customerDao.findById(id);
        
    }

    /**
     * 修改指定id号的客户对象的信息
     * @param id 客户id
     * @param cust 对象
     * @return 修改成功返回true, false表明指定id的客户未找到
     */
    public boolean modifyCustomer(int id, Customer cust) throws SQLException {
        int rows = customerDao.updateById(cust,id);
        if (rows>0){
            return true;
        }else {
            return false;
        }
    }

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

CustomerDao.java

public class CustomerDao extends BaseDao {
    public List<Customer> findAll() throws SQLException, NoSuchFieldException, InstantiationException, IllegalAccessException {
        String sql = "select * from t_customer";
        List<Customer> customers = executeQuery(Customer.class, sql);
        return customers;
    }

    public Customer findById(int id) throws SQLException, NoSuchFieldException, InstantiationException, IllegalAccessException {
        String sql = "select * from t_customer where id=?";
        List<Customer> customers = executeQuery(Customer.class, sql,id);

        //返回的是集合,需要处理成一个
        if(customers != null && customers.size()>0){
            return customers.get(0);
        }

        return null;
    }
    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());
    }
    public int updateById(Customer customer, int id) throws SQLException {
        String sql = "update t_customer set name=?,gender=?,age=?,salary=?,phone=? where id = ?;";
        int rows = executeUpdate(sql, customer.getName(), customer.getGender(), customer.getAge(), customer.getSalary(), customer.getPhone(), customer.getId());
        return rows;
    }

    public int removeCustomerById(int id) throws SQLException {
        String sql = "delete from t_customer where id = ?;";
        int rows = executeUpdate(sql, id);
        return rows;
    }
}
;