JDBC基础

发布于:2025-02-10 ⋅ 阅读:(35) ⋅ 点赞:(0)

JDBC数据库

基础篇

一.JDBC

1.JDBC的概念
  • JDBC: Java Database Connectivity,意为Java数据库连接

  • JDBC是java提供的一组独立于任何数据库管理系统的API

2.JDBC的核心组成
  • 接口规范:

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

    • 接口存储在java.sql和javax.sql下

  • 实现规范:

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

    • 厂商将实现内容和过程封装成jar文件,我们程序员只需要将jar文件引入到项目中集成即可,就可以开发调用实现过程操作数据库了

    3.快速入门
    public class JDBCQuick {
        public static void main(String[] args) throws Exception {
            //注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //获取连接
            String url = "jdbc:mysql://localhost:3306/atguigu";
            String username = "root";
            String password = "1211lyshhy";
            Connection connection =  DriverManager.getConnection(url, username, password);
            //3.获取数据库操作对象
            Statement statement = connection.createStatement();
            //4.执行sql,返回结果集
            String sql = "SELECT * FROM t_emp";
            ResultSet resultSet = statement.executeQuery(sql);
            //5.处理结果集
            while (resultSet.next()){
                int id = resultSet.getInt("emp_id");
                String name = resultSet.getString("emp_name");
                double empSalary = resultSet.getDouble("emp_salary");
                int empAge = resultSet.getInt("emp_age");
                System.out.println("id = " + id + ", name = " + name + ", empSalary = " + empSalary + ", empAge = " + empAge);
            }
            //6.释放资源(先开后关)
            resultSet.close();
            statement.close();
            connection.close();
        }
    }

4.核心api的理解
4.1注册驱动
 Class.forName("com.mysql.cj.jdbc.Driver");
  • 在java中,当使用JDBC连接数据库时,需要加载数据库特定的驱动程序,以便于数据库进行通信.加载驱动程序的目的是为了注册驱动程序,使得JDBC API能够识别并与特定的数据库进行交互.

  • 从JDK6开始,不再需要显示的调用Class.forname()来加载JDBC 驱动程序,只要在类路径中集成了对应的jar文件,会自动在初始化时注册驱动程序.

4.2 Connection
  • Connection接口是JDBC API的重要接口,用于建立与数据库的通信通道。换而言之,Connection对象不为空,则代表一次数据库连接。

  • 在建立连接时,需要指定数据库URL、用户名、密码参数。

    • URL:jdbc:mysql://localhost:3306/atguigu

      • jdbc:mysql://IP地址:端口号/数据库名称?参数键值对1&参数键值对2

  • Connection 接口还负责管理事务,Connection 接口提供了 commitrollback 方法,用于提交事务和回滚事务。

  • 可以创建 Statement 对象,用于执行 SQL 语句并与数据库进行交互。

  • 在使用JDBC技术时,必须要先获取Connection对象,在使用完毕后,要释放资源,避免资源占用浪费及泄漏。

4.3 Statement
  • Statement 接口用于执行 SQL 语句并与数据库进行交互。它是 JDBC API 中的一个重要接口。通过 Statement 对象,可以向数据库发送 SQL 语句并获取执行结果。

  • 结果可以是一个或多个结果。

    • 增删改:受影响行数单个结果。

    • 查询:单行单列、多行多列、单行多列等结果。

  • 但是Statement 接口在执行SQL语句时,会产生SQL注入攻击问题:

    • 当使用 Statement 执行动态构建的 SQL 查询时,往往需要将查询条件与 SQL 语句拼接在一起,直接将参数和SQL语句一并生成,让SQL的查询条件始终为true得到结果。

4.4 PreparedStatement
  • PreparedStatementStatement 接口的子接口,用于执行预编译的 SQL 查询,作用如下:

    • 预编译SQL语句:在创建PreparedStatement时,就会预编译SQL语句,也就是SQL语句已经固定。

    • 防止SQL注入:PreparedStatement 支持参数化查询,将数据作为参数传递到SQL语句中,采用?占位符的方式,将传入的参数用一对单引号包裹起来'',无论传递什么都作为值。有效防止传入关键字或值导致SQL注入问题。

    • 性能提升:PreparedStatement是预编译SQL语句,同一SQL语句多次执行的情况下,可以复用,不必每次重新编译和解析。

  • 后续的学习我们都是基于PreparedStatement进行实现,更安全、效率更高!

4.5 ResultSet
  • ResultSet是 JDBC API 中的一个接口,用于表示从数据库中执行查询语句所返回的结果集。它提供了一种用于遍历和访问查询结果的方式。

  • 遍历结果:ResultSet可以使用 next() 方法将游标移动到结果集的下一行,逐行遍历数据库查询的结果,返回值为boolean类型,true代表有下一行结果,false则代表没有。

  • 获取单列结果:可以通过getXxx的方法获取单列的数据,该方法为重载方法,支持索引和列名进行获取。

5.基于PreparedStatement实现CRUD

5.1 查询单行单列
增删改只有查询语句不一样
 public void testinsert2() throws Exception {
​
        Connection connection = DriverManager.getConnection
                ("jdbc:mysql:///atguigu", "root", "1211lyshhy");
        PreparedStatement preparedStatement = connection.prepareStatement("insert into t_emp(emp_name,emp_salary,emp_age) values(?,?,?)");
​
​
​
//        preparedStatement.setString(1,emp_id);
        preparedStatement.setString(1, "rose");
        preparedStatement.setDouble(2, 345.67);
        preparedStatement.setInt(3, 28);
        int rowsInserted = preparedStatement.executeUpdate();
        if (rowsInserted > 0) {
            System.out.println("Update successfully.");
        }
        else System.out.println("Update failed.");
​
        preparedStatement.close();
        connection.close();
    }

进阶篇

7.1ORM
  • 在使用JDBC操作数据库时,我们会发现数据都是零散的,明明在数据库中是一行完整的数据,到了Java中变成了一个一个的变量,不利于维护和管理。而我们Java是面向对象的,一个表对应的是一个类,一行数据就对应的是Java中的一个对象,一个列对应的是对象的属性,所以我们要把数据存储在一个载体里,这个载体就是实体类!

  • ORM(Object Relational Mapping)思想,对象到关系数据库的映射,作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来,以面向对象的角度操作数据库中的数据,即一张表对应一个类,一行数据对应一个对象,一个列对应一个属性!

7.2 主键回显
  • 在数据中,执行新增操作时,主键列为自动增长,可以在表中直观的看到,但是在Java程序中,我们执行完新增后,只能得到受影响行数,无法得知当前新增数据的主键值。在Java程序中获取数据库中插入新数据后的主键值,并赋值给Java对象,此操作为主键回显。

@Test
    public void testReturnPK() throws Exception {
        //注册驱动
        //获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1211lyshhy");
​
        //获取数据库操作对象,告知preparedstatement,返回新增数据的主键列的值
        PreparedStatement preparedStatement = connection.prepareStatement
                ("insert into t_emp( emp_name, emp_salary, emp_age) values(?,?,?) ", Statement.RETURN_GENERATED_KEYS);
​
        //执行sql语句返回结果集
        Employee employee = new Employee(null,"jack",123.45,29);
        preparedStatement.setString(1, employee.getEmpName());
        preparedStatement.setDouble(2, employee.getEmpSalary());
        preparedStatement.setInt(3,employee.getEmpAge());
        int resultSet = preparedStatement.executeUpdate();
        ResultSet generatedKeys = null;
        //处理结果集
        if (resultSet > 0) {
            System.out.println("success");
            //返回的主键值,是一个单行单列的结果
             generatedKeys = preparedStatement.getGeneratedKeys();
            if (generatedKeys.next()) {
                int emp_id = generatedKeys.getInt(1);
                System.out.println(emp_id);
            }
        }else {
            System.out.println("fail");
        }
        //关闭资源
        if (generatedKeys != null) {
        generatedKeys.close();
        }
        preparedStatement.close();
        connection.close();
​
    }
7.3批量操作

按照上面的代码,如果要连续修改多条数据,增加多条数据,就要写很多代码

由此我们需要一个高效的批量操作

//源代码
@Test
    public void testMoreInsert() throws Exception {
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1211lyshhy");
        PreparedStatement preparedStatement = connection.prepareStatement("insert into t_emp( emp_name, emp_salary, emp_age) values(?,?,?) ");
        Long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++ ) {
            preparedStatement.setString(1, "marry"+i);
            preparedStatement.setDouble(2, 100.0+i);
            preparedStatement.setInt(3, 20+i);
            preparedStatement.executeUpdate();
        }
        Long end = System.currentTimeMillis();
        System.out.println(end - start);
        preparedStatement.close();
        connection.close();
    }花费11秒多
//批量操作
@Test
    public void testMoreBatch() throws Exception {
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?rewriteBatchedStatements=true", "root", "1211lyshhy");
        PreparedStatement preparedStatement = connection.prepareStatement("insert into t_emp( emp_name, emp_salary, emp_age) values(?,?,?) ");//不能在sql语句上加分号
        Long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++ ) {
            preparedStatement.setString(1, "marry"+i);
            preparedStatement.setDouble(2, 100.0+i);
            preparedStatement.setInt(3, 20+i);
            preparedStatement.addBatch();
        }
        preparedStatement.executeBatch();
        Long end = System.currentTimeMillis();
        System.out.println(end - start);
        preparedStatement.close();
        connection.close();
    }
​

rewriteBatchedStatements=true 是 MySQL 的一个优化参数,用于启用批量操作的优化。没有这个参数,批量插入可能不会真正被优化。

使用 PreparedStatement 提高性能和安全性,防止 SQL 注入。

使用循环为每条记录设置参数,并通过 addBatch 将 SQL 语句添加到批处理队列。

还可以优化: 可以通过分批次成批

连接池

连接池概述:

连接池就是数据库链接对象的缓冲区,通过配置,有连接池负责连接,管理连接,释放连接等操作

预先创建数据库连接放入链接池,用户请求时,通过池直接获取链接,使用完毕后,将连接放回池中,避免了频繁的创建和销毁,同时解决了创建的效率

当池中无连接可用,且未达到上限时,连接池会创建新连接

池中连接达到上限,用户会请求等待,可以设置超时时间

常见连接池 Druid Hikari

Druid连接池使用
  • 代码实现

    • 硬编码方式

    public class DruidTest {
        public void testHardCodeDruid() throws Exception {
        /*
        * 硬编码: 将连接池的配置信息和Java代码耦合在一起
        * 1.创建DruidDataSource连接池对象
        * 2.设置连接池的配置信息[必须|非必须]
        * 3.通过连接池获取连接对象
        * 4.回收连接(不是释放连接,而是将连接归还给连接池,给其他线程进行复用)
        * */
    ​
            //1.创建DruidDataSource连接池对象
            DruidDataSource druidDataSource = new DruidDataSource();
    //        2.设置连接池的配置信息[必须|非必须]
    //        2.1必须设置的配置
            druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
            druidDataSource.setUrl("jdbc:mysql:///atguigu");
            druidDataSource.setUsername("root");
            druidDataSource.setPassword("1211lyshhy");
    //        2.2非必须设置的配置
            druidDataSource.setInitialSize(10);//创建连接对象,初始化10个
            druidDataSource.setMaxActive(20);//
    //        3.通过连接池获取连接对象
            DruidPooledConnection connection = druidDataSource.getConnection();
            //基于connection进行CRUD
            //4.回收连接
        }
    }

    • 软编码方式

    把配置信息保存在一个配置文件中

      @Test
        public void testResourcesDruid() throws Exception {
    //    1.创建一个properties集合,用于存储外部配置文件的key和value值
            Properties properties = new Properties();
    //   2.读取外部配置文件,获取输入流,加载到Properties集合里
            InputStream resourceAsStream = DruidTest.class.getClassLoader().getResourceAsStream("db.properties");
            properties.load(resourceAsStream);
    //    3.基于Properties集合构建DruidDataSources连接池
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    //    4.通过连接池获取连接对象
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
    //      5.开发CRUD
    ​
    //    6.回收连接
            connection.close();
        }

    Hikari连接池使用

  • 软编码

Test
    public void testResourcesHikari() throws Exception {
        Properties properties = new Properties();
        InputStream resourceAsStream = HikariTest.class.getClassLoader().getResourceAsStream("hakari.properties");
        properties.load(resourceAsStream);
        HikariConfig hikariConfig = new HikariConfig(properties);
        HikariDataSource hikariDataSource = new HikariDataSource(hikariConfig);
        Connection connection = hikariDataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }
  • 硬编码

@Test
    public void testHardCodeHikari() throws Exception {
    /*
    * 硬编码: 将连接池的配置信息和Java代码耦合在一起
    * */
        HikariDataSource ds = new HikariDataSource();
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setJdbcUrl("jdbc:mysql://localhost:3306/atguigu");
        ds.setUsername("root");
        ds.setPassword("1211lyshhy");
        ds.setMinimumIdle(10);
        ds.setMaximumPoolSize(20);
​
        Connection connection = ds.getConnection();
        System.out.println(connection);
        connection.close();
    }

软编码的好处

把信息写入配置文件中而不是在java代码里

  1. 提高灵活性

    • 通过修改外部配置文件即可调整程序行为,无需重新编译或部署代码。

    • 例如:更改数据库连接信息、切换日志级别等。

  2. 增强可维护性

    • 配置集中管理,便于查找和修改。

    • 代码更简洁,逻辑更清晰,降低维护难度。

  3. 支持动态调整

    • 某些配置可以在程序运行时动态加载,支持热更新,无需重启服务。

    • 例如:通过 Spring 的 @ConfigurationProperties 动态加载配置。

  4. 提高复用性

    • 相同的代码可以在不同的环境下运行,只需切换配置文件即可。

    • 例如:开发环境、测试环境和生产环境使用不同的配置。

  5. 方便测试与调试

    • 测试人员可以通过修改配置文件模拟不同的运行环境和条件。

    • 减少硬编码导致的测试环境不一致问题。

  6. 增强可扩展性

    • 新增功能时,只需扩展配置文件,而无需修改代码。

    • 例如:支持新的支付方式,只需在配置文件中添加对应的支付网关信息。

  7. 支持国际化

    • 通过软编码实现多语言支持(如外置的资源文件),可根据用户语言动态加载。

  8. 降低错误风险

    • 硬编码的内容难以发现和修改,可能导致不可预期的错误。

    • 软编码将配置集中管理,减少因代码改动引入的错误。

高级篇

JDBC工具类封装

/* JDBC工具类(v2.0)
 * 1.维护一个连接池对象,维护了一个线程绑定的ThreadLocal对象
 * 2.对外提供在ThreadLocal中获取连接的方法
 * 3.对外提供回收连接的方法,回收过程中,将要回收的连接在threadlocal中移除
 */
public class JDBCUtilV2 {
    private static DataSource dataSource;
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
​
    static {
        try {
            Properties properties = new Properties();
            InputStream resourceAsStream = JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");
            properties.load(resourceAsStream);
            dataSource= DruidDataSourceFactory.createDataSource(properties);
​
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static Connection getConnection(){
        try {
            //threadlocal中获取连接
            Connection connection = threadLocal.get();
            if(connection==null){
                connection = dataSource.getConnection();//获取一个连接,存储在local里
                threadLocal.set(connection);
            }
            return connection;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
​
    public static void release(){
        try {
            Connection connection = threadLocal.get();
            if(connection!=null){
                connection.close();
                threadLocal.remove();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

DAO封装

public class BaseDao {
    public int executeUpdate(String sql, Object... params) throws Exception {
        Connection connection = JDBCUtilV2.getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        if (params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++) {
                preparedStatement.setObject(i + 1, params[i]);
            }
        }
        int row = preparedStatement.executeUpdate();
        preparedStatement.close();
        JDBCUtilV2.release();
        return row;
​
    }
​
    /*
    通用的查询: 多行多列,单行多列,单行单列
          多行多列: List<Employees>
          单行多列: Employee
          单行多列: 封装的是一个结果
          封装过程:
                1.返回的类型: 泛型: 类型不确定,调用者知道,调用时,将此次查询的结果类型告知baseDao
                2.返回的结果: 通用 List 可以返回多个结果,也可以返回单个结果
                3.结果的封装: 反射,要求调用者告知BaseDao要封装对象的类的对象*/
    public <T> List<T> excuteQuery(Class<T> clazz,String sql, Object... params)throws Exception {
        Connection connection = JDBCUtilV2.getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        if (params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++) {
                preparedStatement.setObject(i + 1, params[i]);
            }
        }
        ResultSet resultSet = preparedStatement.executeQuery();
        ResultSetMetaData metaData = resultSet.getMetaData();
        List<T> list = new ArrayList<>();
        while (resultSet.next()) {
            T t = clazz.newInstance();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
​
                Object value = resultSet.getObject(i);
                String fieldName = metaData.getColumnLabel(i);
                Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                field.set(t, value);
            }
​
            list.add(t);
        }
        resultSet.close();
        preparedStatement.close();
        JDBCUtilV2.release();
        return list;
    }
    public <T> List<T> excuteQueryBean(Class<T> clazz,String sql, Object... params)throws Exception{
        List<T> ts = this.excuteQuery(clazz, sql, params);
        if (ts == null || ts.size() == 0) {
            return null;
        }
        return (List<T>) ts.get(0);
​
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到