使用JDBC连接数据库:Java实现指南

文章介绍了JDBC的基础使用步骤,包括注册驱动、获取连接、创建Statement、发送SQL和结果解析,然后讨论了基于Statement方式存在的问题,如SQL注入。接着,文章转向PreparedStatement,展示了如何通过预编译SQL语句来优化和防止SQL注入,并演示了CRUD操作。最后,文章提到了Druid连接池技术,解释了不使用连接池的缺点,并对比了硬编码和软编码配置连接池的方式。
摘要由CSDN通过智能技术生成

JDBC基本使用步骤

基于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;

image-20230315105744014

注册驱动

//1.注册驱动
/*
* 依赖:驱动版本8+ com.mysql.cj.jdbc.Drrver
* */
DriverManager.registerDriver(new Driver());

获取连接

java程序要和数据库创建链接

java程序连接数据库,调用某个方法,方法也需要填入连接数据库的基本信息:

数据库ip地址 127.0.0.1
数据库端口号 3306
账号
密码
连接数据库的名称

//java程序要和数据库创建链接
/*
* 参数一:url
*        jdbc:数据库厂商名://ip地址:part/数据库名
* */
//java.sql 接口 = 实现类Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "lsl12345");

创建statement

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

发送sql语句

//4.发送sql语句
String sql="select * 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);
}

关闭资源

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

基于statement方式问题

明确jdbc流程和详细讲解使用(注册驱动,获取连接,发送语句,结果解析)

发现问题,引出preparedstatement

package com.Liang.api.statement;

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

/**
 * @author Mr Liang
 * @create 2023-03-15-11:46
 * 模拟用户登录
 * <p>
 * 明确jdbc流程和详细讲解使用(注册驱动,获取连接,发送语句,结果解析)
 * 发现问题,引出preparedstatement
 * <p>
 * 输入账号和密码
 * 进行数据库信息查询(t_user)
 * 反馈登录成功还是登录失败
 * <p>
 * 键盘输入事件,收集账号和密码信息
 */
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();
        scanner.close();
//      2.注册驱动
        /*
         *  DriverManager.registerDriver(new Driver());
         *  问题:会注册两次驱动
         *       1. DriverManager.static(DriverManager.registerDriver()) 静态代码块,也会注册一次
         *       2. DriverManager.registerDriver(new Driver());方法本身会注册一次
         *  解决:只想注册一次驱动
         *        只触发一个静态代码块即可
         *     如何触发静态代码块:
         *                  在类加载的时刻,会触发静态代码块
         *                   加载,class文件->jvm虚拟机的class对象
         *                   连接,验证(检查文件类型)->准备(静态变量默认值)->解析(触发静态代码块)
         *                   初始化,静态属性赋初值
         *     触发器加载:
         *            1.new 关键字
         *            2.调用类的静态属性
         *            3.调用静态属性
         *            4.接口1.8 default 默认实现
         *            5.反射
         *            6子类触发器
         *            7.程序的入口
         * */

//     方案一   DriverManager.registerDriver(new Driver());被淘汰
//     方案二   new Driver();不灵活,只能依赖于当前驱动
//     方案三   反射 字符串->提取到外部的配置文件->更加灵活,完成数据库驱动的切换
        Class.forName("com.mysql.cj.jdbc.Driver");

//     获取数据库连接,三种方式的传递
        /*     getConnection(1,2,3) 是一个重载方法,允许开发者用不同的形式传入数据库连接的核心参数
         *      模拟用户登录核心属性:
         *              1.数据库软件所在的主机的ip地址/主机名:localhost/127.0.0.1
         *              2.数据库软件所在的端口号
         *              3.连接的具体库
         *              4.账号,密码
         *              5.可选的信息
         *      三个参数
         *      String url :数据库软件所在信息,连接的具体库,以及其他可选信息
         *                  语法:具体:jdbc:mysql://127.0.0.1:3306/atgugui 或
         *                            jdbc:mysql://localhost:3306/atgugui
         *                            本机的省略写法:
         *                            jdbc:mysql://127.0.0.1:3306/atgugui = jdbc:mysql:///atguigu
         *                            省略了本机地址和端口号
         *                            必须是本机,且端口号为3306才可以省略
         *      二个参数:
         *      String url:同上
         *      Properties info:存储账号和密码
         *                      类似于Map ,只不过key=value 都是字符串的形式
         *      一个参数:
         *      String url: 数据库ip,端口号,具体的数据库 可选信息(账号密码)
         *                  jdbc:数据库软件名://ip:part/数据库?key=value&key=value&key=value
         *                  jdbc:mysql://localhost:3306/atguigu?user=root&password=lsl12345
         *                  携带固定的参数名: user password 传递账号和密码信息
         *
         *      url的路径属性可选信息:
         *      url?user=账号&password=密码
         *      路径扩展:serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true
         *
         * */

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

//      3.创建发送sql语句的statement对象
        Statement statement = connection.createStatement();
//      4.发送sql语句
        String sql = "SELECT * FROM t_user WHERE account = '" + account + "' AND PASSWORD = '" + password + "';";
        /*
         * sql分类:DDL(容器创建,修改,删除),DML(插入,修改,删除),DDL(查询),DCL(权限控制),TPL(事务控制语言)
         */
//        int i = statement.executeUpdate(sql);//非DQL语句
        ResultSet resultSet = statement.executeQuery(sql);//结果封装对象
//      查询结果解析
        /*
         * 移动游标问题
         *  resultSet内部包含一个游标,指向当前行数据
         *  默认游标指的是第一行数据之前
         *  调用next方法向后移动一行游标7
         *  多行数据可以使用while(next){获取每一行的数据}
         *  boolean = next()
         *
         * 获取列的数据问题(获取光标指定的行的数据)
         *    resultSet.get类型(String columnLabel | int columnIndex)
         *    columnLabel:列名 如果有别名 写别名
         *    columnIndex:列的下角标开始:从左向有 从1开始
         * */
      /*  while (resultSet.next()) {
            int id = resultSet.getInt(1);
            String account1 = resultSet.getString("account");
            int password1 = resultSet.getInt(3);
            String nickname = resultSet.getString("nickname");
            System.out.println(id+"--"+account+"--"+password1+"--"+nickname);
        }*/
//        移动一次光标,只要有数据,就代表登录成功
        if (resultSet.next()) {
            System.out.println("登录成功!");
        } else {
            System.out.println("登录失败!");
        }
//      关闭资源
        resultSet.close();
        statement.close();
        connection.close();
    }
}

image-20230315164803567

存在问题:

  1. SQL语句需要字符串拼接,比较麻烦

  2. 只能拼接字符串类型,其他的数据库类型无法处理

  3. 可能发生注入攻击

    动态值充当了SQL语句结构,影响了原有的查询结果!

输入一个不存在的账号

输入上述密码,发现登录成功,动态值充当了SQL语句结构,影响了原有的查询结果

基于preparedStatement方式优化

statement语句只适合进行静态sql语句的执行,如果输入动态sql语句,会发生注入攻击

利用preparedStatement解决上述案例注入攻击和SQL语句拼接问题,前置知道sql语句结构,再给对应结构的动态值部分赋值

与statement不同的地方在编写sql语句的地方

//       编写sql语句结果
        /*
         * 1.编写SQL语句结果 不包含动态值部分的语句,动态值部分使用占位符 ? 替代 注意:?只能提点动态值
         * 2.创建preparedstatement,并传入动态值
         * 3.动态值 占位符 赋值? 单独赋值即可
         * 4.发送sql语句即可,并获取返回结果
         * */
        String sql = "select * from t_user where account= ? and password= ?;";
//       创建预编译statement并且设置sql语句结果
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
//       单独的占位符进行赋值
        /*
        * 参数一:index 占位符的位置
        * 参数二:object 占位符的值,可以设置任何类型的数据,避免了我们拼接和类型更加丰富,
        * */
        preparedStatement.setObject(1,account);
        preparedStatement.setObject(2,password);
//        发送sql语句,并获取返回结果
//        preparedStatement.executeUpdate | executeQuery(String sql);
        ResultSet resultSet = preparedStatement.executeQuery();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IwfpdVLQ-1679019192753)(C:/Users/Administrator/AppData/Roaming/Typora/typora-user-images/image-20230315181350710.png)]

图解

image-20230315181652354

基于preparedStatement演示curd

增添数据

    public void testInsert() throws ClassNotFoundException, SQLException {
        /*
         *  * 插入一条用户数据!
         * 账号: test
         * 密码: test
         * 昵称: 测试
         * */
//      1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
//      2.获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=lsl12345");
//      3.编写sql语句结果,动态值的部分使用?代替
        String sql = "insert into t_user(account,password,nickname) values (?,?,?);";
//      4.创建预编译statement并且设置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.输出结果
        System.out.println(rows);
//      8.关闭资源
        preparedStatement.close();
        connection.close();
    }

更新数据

    public void testInsert() throws ClassNotFoundException, SQLException {
        /*
         *  * 插入一条用户数据!
         * 账号: test
         * 密码: test
         * 昵称: 测试
         * */
//      1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
//      2.获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=lsl12345");
//      3.编写sql语句结果,动态值的部分使用?代替
        String sql = "update t_user set password='123' where id=?;";
//      4.创建预编译statement并且设置sql语句结果
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
//      5.占位符赋值
        preparedStatement.setObject(1, "1");
        
//      6.发送sql语句
        int rows = preparedStatement.executeUpdate();
//      7.输出结果
        System.out.println(rows);
//      8.关闭资源
        preparedStatement.close();
        connection.close();
    }

删除数据

public void testDelete() throws ClassNotFoundException, SQLException {
    /**
     * 删除一条用户数据!
     * 根据账号: test
     */
    //      1.注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    //      2.获取连接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=lsl12345");
    //      3.编写sql语句结果,动态值的部分使用?代替
    String sql="delete  from  t_user where account=?";
    //      4.创建预编译statement并且设置sql语句结果
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    //      5.占位符赋值
    preparedStatement.setObject(1,"test");
    //      6.发送sql语句
    int rows = preparedStatement.executeUpdate();
    //      7.输出结果
    System.out.println(rows);
    //      8.关闭资源
    preparedStatement.close();
    connection.close();
}

数据查询

  public void testSelect() throws Exception {
        /**
         * 查询全部数据!
         *   将数据存到List<Map>中
         *   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:///atguigu?user=root&password=lsl12345");
        //      3.编写sql语句结果,动态值的部分使用?代替
        String sql="select * from t_user;";
        //      4.创建preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //      5.没有动态sql语句,省略占位符的赋值
        //      6.发送sql语句
        ResultSet resultSet = preparedStatement.executeQuery();
        //      7.结果集解析
        List<Map> list= new ArrayList<>();
//        TODO:metaData 存储当前结果集列的信息对象(可以获取列的名称根据下角标,可以获取列的数量)
        ResultSetMetaData metaData = resultSet.getMetaData();
//        TODO:水平遍历列
        int columnCount = metaData.getColumnCount();
        while (resultSet.next()){
            Map map=new HashMap();
//          一行数据对应一个Map,纯手动写法,在后续更改中很繁琐
        /*  map.put("id",resultSet.getInt("id"));
            map.put("account",resultSet.getString("account"));
            map.put("password",resultSet.getString("password"));
            map.put("nickname",resultSet.getString("nickname"));
            list.add(map);*/
            for (int i = 0; i < columnCount; i++) {
//                获取指定列下角标的值
                Object value = resultSet.getObject(i);
//                获取指定列下角标的列的名称ResultSetMetaData
                String columnLabel = metaData.getColumnLabel(i);//可以获取列的别名
                map.put(columnLabel,value);
            }
            list.add(map);
        }
        System.out.println("list="+list);
        //8.关闭资源
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }

image-20230315211355394

preparedStatement使用方法总结

使用步骤

//1.注册驱动

//2.获取连接

//3.编写SQL语句

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

//5.占位符赋值

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

//7.结果集解析

//8.关闭资源

使用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(); 

Druid连接池技术使用

总结缺点:
(1)不使用数据库连接池,每次都通过DriverManager获取新连接,用完直接抛弃断开,连接的利用率太低,太浪费。
(2)对于数据库服务器来说,压力太大了。我们数据库服务器和Java程序对连接数也无法控制,很容易导致数据库服务器崩溃。

我们就希望能管理连接。

  • 我们可以建立一个连接池,这个池中可以容纳一定数量的连接对象,一开始,
    我们可以先替用户先创建好一些连接对象,等用户要拿连接对象时,就直接从池中拿,
    不用新建了,这样也可以节省时间。然后用户用完后,放回去,别人可以接着用。
  • 可以提高连接的使用率。当池中的现有的连接都用完了,那么连接池可以向服务器申
    请新的连接放到池中。
  • 直到池中的连接达到“最大连接数”,就不能在申请新的连接了,如果没有拿到连接的用户只能等待。

JDBC 的数据库连接池使用 javax.sql.DataSource接口进行规范,所有的第三方连接池 都实现此接口,自行添加具体实现!也就是说,所有连接池获取连接的和回收连接方法都一样,不同的只有性能和扩展功能
- DBCP 是Apache提供的数据库连接池,速度相对c3p0较快,但因自身存在BUG
- C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以
- Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,
稳定性较c3p0差一点

硬编码

直接使用代码设置连接池参数方式

  • 1.创建druid连接的对象
  • 2.设置连接参数
  • 3.获取连接[通用方法,所有连接池都一样]
  • 4.回收连接
    public void testhard() throws SQLException {
//       连接池对象
        DruidDataSource dataSource = new DruidDataSource();
//        设置参数
//        必须 连接数据库驱动类的全限定符【注册驱动】 | url |user | password
        dataSource.setUrl("jdbc:mysql:///atguigu");
        dataSource.setUsername("root");
        dataSource.setPassword("lsl12345");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
//       非必须 初始化连接数量,最大的连接数量
        dataSource.setInitialSize(5);//初始化连接数量
        dataSource.setMaxActive(10);//最大的数量
//       获取连接
        Connection connection = dataSource.getConnection();
//       数据curd
        
//      回收连接
        connection.close();//连接池提供的连接被回收
    }
}

软编码

软编码需要在外部添加一配置文件,存放在src/路径下
image-20230316084650608

在配置文件中添加如下内容
image-20230316084834380

  public void testSoft() throws Exception {
//        读取外部配置文件 Properties
          Properties properties = new Properties();
//         src 下的文件可以使用类加载器
          InputStream is = DriudUsePart.class.getClassLoader().getResourceAsStream("druid.properties");
          properties.load(is);
//        使用连接池工具类的工程模式,创建连接池
          DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
          Connection connection = dataSource.getConnection();


//        数据库curd


//        回收连接
          connection.close();
    }

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

jdbc 工具类封装 v1.0

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

工具类的方法推荐写成静态,外部调用更加方便

 * 实现:
 * 属性:连接池对象【实例化一次】
 *      单例模式
 *      静态代码块
 *         static{
 *             全局调用一次
 *           }
 * 方法:
 *     对外提供连接的方法
 *     回收外部传入连接方法
public class JdbcUtils {
    private static DataSource dataSource = null;//连接池对象

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

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

    /*
     * 回收连接的方法
     * */
    public static void freeConnection(Connection connection) throws SQLException {
        connection.close();//连接池的连接,调用close就是回收!
    }
}

接下来使用封装的工具类

public class jdbcCurdPart {
    public void testInsert() throws Exception {
//        提供连接
        Connection connection = JdbcUtils.getConnection();
//        数据库的curd

//        数据的回收
        JdbcUtils.freeConnection(connection);
    }
}

jdbc 工具类封装 v2.0

在1.0版本中,同一个线程的不用方法调用getConnection方法获取的不是同一个对象,即获取的是不通的连接

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

ThreadLocal的介绍:

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

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

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

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

3、ThreadLocal对象.remove: 移除ThreadLocal中当前线程共享变量的值。
 *  TODO:
 *    利用线程本地变量,存储连接信息,确保一个线程的多个方法可以获取同一个connection
 *    优势:事务操作的时候 service 和 dao 属于同一个线程,不再传递参数
 *           可以调用getConnection自动获取的是相同的连接池
 */
public class JdbcUtilsV2 {
    private static DataSource dataSource = null;//连接池对象

    private static ThreadLocal<Connection> tl = new ThreadLocal<>();//声明线程本地变量对象,存储Connection

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

    /*
     * 对外提供连接的方法
     * */
    public static Connection getConnection() throws SQLException {
//       线程本地变量中是否存在
        Connection connection = tl.get();
//       如果第一次没有
        if (connection == null) {
//            线程本地变量为空,从连接池获取
            connection = dataSource.getConnection();
            tl.set(connection);//存储一份Connection对象
        }
        return connection;
    }

    /*
     * 回收连接的方法
     * */
    public static void freeConnection() throws SQLException {
        Connection connection1 = tl.get();
        if (connection1 != null) {
            tl.remove();//清空线程本地变量状态
            connection1.setAutoCommit(true);//事务状态回归
            connection1.close();//回收到连接池
        }
    }
}

整个过程为,每次先线程本地变量取一份,没有的话,再从连接池中取,同时也存到线程本地变量一份。回收的时候从线程本地变量中拿,然后清空线程本地变量状态,将事务状态回归。

高级应用层封装BaseDao

前面的工具类,只封装了以下的三个步骤,剩余的步骤仍需要自己手动编写

image-20230316160442851

使用BaseDao继续进行优化

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

这些对应的Dao都继承于BaseDao,重复的方法也就自动的拥有了,不必再重复增删改查的动作。

在BaseDao中去调用工具类获取连接

BaseDao的封装

在表的操作中,将增、删、改归并为一类,查为另一类,即分为非DQL与DQL类

因此在BaseDao只需封装两个方法即可

非DQL查询方式封装

* 封装简化非DQL语句,
*   sql 带占位符的值
*   params 占位符的值
*   return 返回执行影响的参数
    public int executeUpdate(String sql,Object... params){//sql语句中的占位符是暂不可知的,这里使用可变参数列表

    }
package com.Liang.api.utils;

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

/**
 * @author Mr Liang
 * @create 2023-03-16-16:15
 * 封装Dao层数据库重复代码
 * TODO;
 *     封装两个方法  一个简化非DQL
 *                  一个简化DQL
 */
public abstract class BaseDao {
    /*
     * 封装简化非DQL语句
     * sql 带占位符的值
     * params 占位符的值
     * return 返回执行影响的参数
     * */
    public int executeUpdate(String sql, Object... params) throws SQLException {//sql语句中的占位符是暂不可知的,这里使用可变参数列表
//      获取连接
        Connection connection = JdbcUtilsV2.getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
//      占位符赋值
//       可变参数可以当做数组进行使用
        for (int i = 0; i <= params.length; i++) {
            preparedStatement.setObject(i, params[i-1]);
        }
//      发送sql语句
        int rows = preparedStatement.executeUpdate();
        preparedStatement.close();
//      处理连接
        if(connection.getAutoCommit()){
//            没有开启事务,正常回收连接
            JdbcUtilsV2.freeConnection();
        }
//        是否回收连接,需要考虑是不是事务!
//        connection.setAutoCommit(false);//表示开启事务 不要管连接即可 业务层进行处理
        return rows;
    }
}

以下就将非DQL的方法封装完成,接下来通过继承BaseDao类进行中间操作

public class PSCURDPART extends BaseDao {
    @Test
    public void testInsert() throws ClassNotFoundException, SQLException {
        /*
         *  * 插入一条用户数据!
         * 账号: test
         * 密码: test
         * 昵称: 测试
         * */
        String sql = "insert into t_user(account,password,nickname) values (?,?,?);";
        int i = executeUpdate(sql, "测试1", "qwer", "运营员");
        System.out.println("i="+i);
    }
}

image-20230316183750707

数据插入成功

image-20230316183836938

DQL查询方式封装

非DQL语句封装方法的返回值固定为int

DQL语句封装方法的返回值是什么类型呢?

前面在执行查询语句操作时我们使用list<map>存放查询结果的返回值,在封装方法时并不适用,
     map的key和value都是自定义的,不用先设定好
     map 没有数据校验机制
     map不支持反射操作

数据库中的数据对应到java中的实体类,多行数据对象List<Java实体类> list的一个集合,返回的为Listlist 某一个实体类的集合

image-20230316192556906

通过传入Class对象确定泛型,后续通过列名反射对应的属性名进行赋值

    public <T> List<T> executeQuery(Class<T> clazz,String sql,Object...params) throws Exception {
        //        获取连接
        Connection connection = JdbcUtilsV2.getConnection();
        //        创建preparedStatement
        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();
        //       结果集解析
        List<T> list = new ArrayList<>();
//        TODO:metaData 存储当前结果集列的信息对象(可以获取列的名称根据下角标,可以获取列的数量)
        ResultSetMetaData metaData = resultSet.getMetaData();
//        TODO:水平遍历列
        int columnCount = metaData.getColumnCount();
        while (resultSet.next()) {
            T t = clazz.getDeclaredConstructor().newInstance();//调用类的无参构造函数实例化对象
//          一行数据对应一个T类型的对象,
            for (int i = 1; i <= columnCount; i++) {
//                对象的属性值
                Object value = resultSet.getObject(i);
//                获取指定列下角标的列的名称 ResultSetMetaData
                String propertyName = metaData.getColumnLabel(i);//可以获取列的别名
//                通过反射,给对象的属性值进行赋值
                Field field = clazz.getDeclaredField(propertyName);
                field.setAccessible(true);//属性可以设置,打破private私有修饰
                /*
                * 参数一:要赋值的对象 如果属性是静态,第一个参数可以为null
                * 参数二:具体的属性值
                * */
                field.set(t,value);
            }
            list.add(t);
        }
//        关闭资源
        resultSet.close();
        preparedStatement.close();
        if(connection.getAutoCommit()){
//            没有事务可以关闭
            JdbcUtilsV2.freeConnection();
        }
        return list;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

AMBLE RUM

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

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

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

打赏作者

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

抵扣说明:

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

余额充值