jdbc学习笔记

JDBC笔记

一、为什么要学习jdbc

  • jdbc是Java和数据库之间的必要纽带
    java跟数据库之间的通信要通过jdbc来实现
  • 数据库层框架底层原理
    jdbc是mybatis、hibernate、jpa等应用层框架的底层驱动

二、jdbc的相关概念

2.1 定义

JDBC : Java Database Connectivity || Java连接数据库技术
即,在java代码中,使用JDBC提供的方法,可以发生字符串类型的sql语句到数据库管理软件(Mysql、Oracle等),并且获取语句的执行结果,进而通过java代码实现数据库的crud操作的技术。

jdbc由两部分组成:
1. Java提供的jdbc规范(接口)
2. 各个数据库厂商的实现驱动jar包

2.2 核心api

  • DriverManager
    1.将第三方数据库厂商的实现驱动jar包注册到程序中
    2.根据数据库连接信息获取connection

  • Connection
    1.表示数据库建立的连接,可以在连接的对象上,多次执行数据库crud的操作
    2.可以获取到statement | preparedstatement | callablestatement对象

  • statement | preparedstatement | callablestatement
    1.具体发送sql语句到数据库软件的对象
    2.用不同方式发送过程有所不同,最主要掌握preparedstatement!
    a. 静态sql语句使用statement
    b. 有动态传参的sql语句使用preparedstatement
    c. 执行存储过程的sql语句使用callablestatement

  • result
    1.面对对象思维的产物(抽象成数据库查询的结果集)
    2.存储的是查询数据库结果的对象
    3.需要我们进行解析才能获取到其中的数据

三、核心api的使用

3.1 使用前配置

  1. 首先创建一个lib文件夹用于存放要使用的jar包,将jar包复制到该文件夹下
    在这里插入图片描述
  2. 将jar包加入到Library中
    在这里插入图片描述
  3. 数据库的准备
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 UNIQUE COMMENT '密码',
	nickname VARCHAR(20) NOT NULL UNIQUE COMMENT '昵称'
);

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

3.2 jdbc执行过程

  1. 注册驱动 依赖的jar包,进行安装
  2. 建立与数据库的连接connection
  3. 创建发送sql语句的对象statement
  4. statement对象发送sql语句并且获取到结果集
  5. 解析结果集,获取到数据
  6. 销毁资源,释放connection、statement、resultSet对象
    在这里插入图片描述

3.3 通过statement查询数据库实例demo

package jdbc.com.demo.statement;

import com.mysql.cj.jdbc.Driver;

import java.sql.*;

/**
 * ClassName: StatementQueryPart
 * Package: jdbc.com.demo.statement
 * Description:
 * 使用statement查询t_user表下的全部数据
 * @Author 乌冬面
 * @Create 2023/10/29 0029 20:13
 * @Version 1.0
 */
public class StatementQueryPart {
    /**
     * todo:
     *      DriverManager
     *      Connection
     *      Statement
     *      ResultSet
     * @param args
     */
    public static void main(String[] args) throws SQLException {
        //1.注册驱动
        /**
         * 依赖:
         *      驱动版本 8+ com.mysql.cj.jdbc.Driver
         *      8以下的版本 com.mysql.jdbc.Driver
         */
        DriverManager.registerDriver(new Driver());

        //2.获取connection
        /**
         * java程序要想和数据库建立连接,肯定需要提供连接的数据库的相关信息
         *      数据库ip地址
         *      数据库端口号
         *      账号
         *      密码
         *      连接数据库的名称
         * DriverManager.getConnection(String url,String username,String password)
         *      url : jdbc:数据库厂商名://ip地址:port/数据库名
         *      username : 数据库软件账号
         *      password : 数据库软件密码
         */
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","123456");

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

        //4.通过statement发送sql语句返回结果集
        String sql = "select * from t_user;";
        ResultSet resultSet = statement.executeQuery(sql);

        //5.解析结果集
        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();
    }
}

程序运行结果如下:
在这里插入图片描述

3.4 详解statement实例

3.4.1 注册驱动

在上面的案例中,我们注册驱动时使用的方法为:

DriverManager.registerDriver(new Driver());

这样编写会出现问题,因为在registerDriver()方法调用和Driver类创建的时候都会注册驱动,驱动注册了两次,无端产生了性能的消耗,所以我们希望只注册一次驱动。因为Driver类的静态代码块始终都是要触发的,所以我们选择只触发其静态代码块。

因此,我们必须要了解什么时候静态代码块会被触发:
类加载机制的流程:

  • 加载[class文件 -> jvm虚拟机的class对象]
  • 连接[验证(检查文件类型) -> 准备(静态变量默认值) -> 解析(触发静态代码块)]
  • 初始化[静态属性赋真实值]

触发类加载的情况:

  1. new 关键字
  2. 调用静态方法
  3. 调用静态属性
  4. 接口 1.8 default默认实现
  5. 反射
  6. 子类触发父类
  7. 程序的入口main


    所以我们可以简便的使用如下代码实现注册驱动:
Class.forName("com.mysql.cj.jdbc.Driver");
3.4.2 getConnection()方法

getConnection()是一个重载方法,允许开发者使用不同方式传入数据库连接核心参数。
其核心属性有:

  1. 数据库软件所在主机的ip地址:localhost | 127.0.0.1
  2. 数据库软件所在主机的端口号:3306
  3. 连接的具体数据库:atguigu
  4. 连接的账号:root
  5. 连接的密码:123456
  6. 可选信息

getConnection()有三种实现形式:

/**
* 三个参数
* getConnection(string url,string user,string password)
*      string url          数据库软件所在信息,连接具体库,以及其他可选信息
*                          语法: jdbc:数据库管理软件名称[mysql\oracle...]://ip地址|主机名:port端口号/数据库名?key=value&key=value...
*                          在本机的省略写法 jdbc:mysql://localhost:3306/atguigu == jdbc:mysql:///atguigu 省略本机地址和3306端口号
*                          强调:必须是本机,并且端口号为3306才可以省略
*      string user         root
*      string password     123456
*
* 两个参数
* getConnection(string url,properties info)
*      string url          同上
*      properties info     存储账号跟密码
*                          properties 类似于Map 只不过key跟value都是用字符串
形式存储的
*
* 一个参数
* getConnection(string url)
*      string url          除传入以上的信息以外,在可选信息中加入账号跟密码的信息
*
*      可选信息:   8.0.25以后会自动识别时区,默认使用utf-8版本
*      serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=true
*/

以下分别对三种方法都进行实现:

Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu","root","123456");
Properties info = new Properties();
info.put("user","root");
info.put("password","123456");
Connection connection1 = DriverManager.getConnection("jdbc:mysql:///atguigu", info);
Connection connection2 = DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=123456");
3.4.3 resultSet解析

resultSet类是一个抽象的结果集,包含了从数据库中查询得到的所有结果。
要想进行数据解析,要做两件事情:

  1. 移动游标指定获取数据行
    1. resultSet内部包含一个游标,指定当前数据行
    2. 默认游标指定的是第一行数据之前
    3. 调用next()方法向后移动一行游标
    4. next()返回值为 true表示往后还有数据,false表示后面没有数据了
  2. 获取指定数据行的列数据
    resultSet.get类型(列名,可以是别名 | 列下标从1开始)

以下是具体resultSet解析数据的代码:

while (resultSet.next()){
    int anInt = resultSet.getInt(1);
    String account1 = resultSet.getString("account");
    String password1 = resultSet.getString(3);
    String nickname = resultSet.getString("nickname");
    System.out.println("nickname: " + nickname);
}

3.5 preparedstatement使用

使用statement会发送sql查询会产生的问题

  • sql语句需要字符串拼接,比较麻烦
  • 只能拼接字符串类型,其他类型的数据类型无法处理
  • 可能发生注入攻击(动态值充当了sql语句结构,影响了原有的查询结果)

因此,我们使用preparedStatement来进行动态传入参数的sql语句的执行

        /**
         * preparedStatement
         *    1.编写sql语句结果 不包含动态值部分的语句,动态值部分使用占位符 ? 替代
         *      注意: ?只能替代动态值
         *    2.创建preparedStatement,并且传入动态值
         *    3.动态值 占位符 赋值
         *    4.发送sql语句并且返回结果
         *
         * */
        String sql = "SELECT * FROM t_user WHERE account = ? AND password = ?;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        /*
        * 对单独的占位符进行赋值
        *   1. 参数1 : index 占位符的位置 从左向右从1开始
        *   2. 参数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("登录失败");
        }

注意:

​ 如果想在查询之后将结果集存储到一个List集合之中,应该将结果按行的顺序取出,一行之中每一列的数据都存入到一个Map之中,然后将Map一次存入List集合即可

在这里插入图片描述

获取列信息的相关方法:

  • ResultMetaData resultSet.getMetaData()获取的是当前列的信息对象,可以通过它获取列的名称,根据下角标获取列的数量
  • resultSet.getMetaData().getColumnCount() 获取当前结果集列的数量
  • resultSet.getMetaData().getColumnLabel(i)根据下角标i获取到列的别名,如果没有写别名,则获取列的名称,推荐这种方式
  • resultSet.getMetaData().getColumnName(i)根据下角标i获取到列的名称,不会获取别名

四、jdbc拓展

4.1 主键回显

主键回显是指:在对表数据进行插入等操作时,想要获取到表自增长的主键的值,通过主键回显的方式,来获取到该值。

具体步骤

  1. 首先要在创建preparedStatement对象时,传入一个PreparedStatement.RETURN_GENERATED_KEYS参数,告知其带回主键
PreparedStatement preparedStatement = connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
  1. 取回主键是在preparedStatement对象execute之后,通过PreparedStatement类的getGeneratedKeys()方法,该方法返回一个ResultSet对象,获取ResultSet结果集的方法与之前相同,先用next()方法使其指针移动一位,然后用get()发方法获取主键值
       ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
       generatedKeys.next();
       int id = generatedKeys.getInt(1);
       System.out.println("返回的主键为:" + id );
    

总体demo

    @Test
    public void test_01() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=123456");
        String sql = "insert into t_user(account,password,nickname) values(?,?,?)";
        //要想使用主键回显,要在创建preparedStatement对象时,传入一个PreparedStatement.RETURN_GENERATED_KEYS参数,告知其带回主键
        PreparedStatement preparedStatement = connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
        preparedStatement.setString(1, "test7");
        preparedStatement.setString(2, "1020");
        preparedStatement.setString(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 );
        } else {
            System.out.println("数据插入失败");
        }
        preparedStatement.close();
        connection.close();
    }

4.2 批量插入数据优化

按照传统的插入方式进行批量操作时,每次操作都要重新执行一次execute操作,运行的效率很慢。
因此我们在进行大量数据的插入操作时使用批量操作
使用步骤

  1. 在url中要在地址?之后加上rewriteBatchedStatements=true语句,其作用是允许批量插入操作

      Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?rewriteBatchedStatements=true&user=root&password=123456");
    
  2. 在编写sql语句时,一定要使用values而不能用value,而且sql语句最后不能添加;

    String sql = "insert into t_user(account,password,nickname) values(?,?,?)";
    
  3. 在循环体内执行了set操作之后使用addBatch()方法,将当前插入的数据追加到values之后

    for (int i = 0; i < 10000; i++) {
                preparedStatement.setString(1, "dd" + i);
                preparedStatement.setString(2, "ww" + i);
                preparedStatement.setString(3, "xiaohong" + i);
    
                preparedStatement.addBatch(); //不执行,追加到values的后边
            }
    
  4. 当数据全部插入完成使用executeBatch()方法执行批量插入

    preparedStatement.executeBatch();//执行批量操作
    

总体demo

  @Test
    public void test_02() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        //允许批量插入操作
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?rewriteBatchedStatements=true&user=root&password=123456");
        String sql = "insert into t_user(account,password,nickname) values(?,?,?)";
        long start = System.currentTimeMillis();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        for (int i = 0; i < 10000; i++) {
            preparedStatement.setString(1, "dd" + i);
            preparedStatement.setString(2, "ww" + i);
            preparedStatement.setString(3, "xiaohong" + i);

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

        long end = System.currentTimeMillis();
        System.out.println(end - start);
        preparedStatement.close();
        connection.close();
    }

五、druid连接池

5.1 使用连接池的原因

在传统的获取连接的方式中,每次在使用jdbc操作前都要先建立一个数据库的连接,在操作完成之后再销毁连接。当操作量特别大时,这样的方式不仅繁琐,而且每次都建立销毁连接的开销很大。因此,提出连接池,是为了将连接复用起来,提高效率。

5.2 druid连接池的使用

druid连接池的使用有两种方式:软连接和硬链接的方式,下面将依次介绍
1 硬链接方式

		//1.创建一个连接池对象
        DruidDataSource dataSource = new DruidDataSource();

        //2.设置连接池参数[必须/非必须]
        //必须 连接数据库驱动类的全限定符[注册驱动] | url | user | password
        //非必须 初始化连接数、最大连接数
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); 
        dataSource.setUrl("jdbc:mysql://localhost:3306/atguigu?user=root&password=123456"); 
        dataSource.setUsername("root");     
        dataSource.setPassword("123456");   
        dataSource.setInitialSize(5); 	//设置初始化连接数

        //3.获取连接
        Connection connection = null;
        try {
            connection = dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        System.out.println(connection);

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

​ 2 软连接的使用,首先要创建一个properties文件,在文件中存储创建连接池的具体参数信息

driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=123456
url=jdbd:mysql:///atguigu

​ 注意:在properties文件中的各种key关键字的名称是特定的,具体可以使用参考文档查看,不要写错了

然后要获取到这些参数信息,让然后创建连接池

		//1.读取外部配置文件 Properties
        Properties properties = new Properties();
        //src下的文件可以使用类加载器获取
        InputStream ips = 	DruidUse.class.getClassLoader().getResourceAsStream("druid.properties");
        properties.load(ips);
        //2.使用连接池的工具类的工厂模式,创建连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

        //3.获取连接
        Connection connection = null;
        try {
            connection = dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        System.out.println(connection);

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

六、工具类的封装

6.1 非DQL的封装

public int executeUpdate(String sql, Object... params) throws SQLException {
        //获取连接
        Connection connection = JdbcUtils.getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        for (int i = 0; i < params.length; i++) {
            preparedStatement.setObject(i + 1, params[i]);
        }
        int rows = preparedStatement.executeUpdate();
        preparedStatement.close();
        //没有开启事务,则由dao层关闭connection
        if (connection.getAutoCommit()){
            JdbcUtils.closeConnection();
        }
        return rows;
    }

6.2 DQL的封装

/**
     * 使用泛型来返回数据
     *      <T>声明一个泛型,不确定类型
     *          确定泛型 使用参数Class<T> clazz
     *          使用反射技术为属性赋值
     * @param clazz
     * @param sql
     * @param params
     * @return
     * @param <T>
     * @throws SQLException
     */
    public <T> List<T> executeQuery(Class<T> clazz,String sql, Object... params) throws SQLException, InstantiationException, IllegalAccessException {
        Connection connection = JdbcUtils.getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        for (int i = 0; i < params.length; i++) {
            preparedStatement.setObject(i + 1, params[i]);
        }
        ResultSet resultSet = preparedStatement.executeQuery();
        List<T> list = new ArrayList<>();
        while (resultSet.next()) {
            //调用类的无参构造函数实例化对象
            T t = clazz.newInstance();
            for (int i = 0; i < resultSet.getMetaData().getColumnCount(); i++) {
                //获取对象的属性名
                String columnName = resultSet.getMetaData().getColumnLabel(i + 1);
                //获取对象的属性值
                Object value = resultSet.getObject(i + 1);
                try {
                    //使用反射给对象赋值
                    Field field = clazz.getDeclaredField(columnName);
                    //属性可以设置,打破private的修饰限制
                    field.setAccessible(true);
                    //赋值方法 , t->要赋值的对象(如果属性是静态的,第一个参数可以为null) ,value->具体的属性值
                    field.set(t, value);
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            list.add(t);
        }
        return list;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值