JDBC笔记

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

简介

JDBC简单执行过程:

总结Java提供接口;数据库厂商提供实现;程序员调用接口;接口调用实现类,连接操作数据库

 JDBC的概念

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

java操作数据库

步骤:

1 注册驱动(将数据库厂商得驱动类通过类加载得方式加载到程序中)

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

2 通过DriverManager调用getConnection()传入主机地址端口号要使用得数据库,用户名,用户密码---------以此来获得一个连接对象

 Connection接口是JDBC API的重要接口,用于建立与数据库的通信通道

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

3 通过connection对象调用createStatement()方法获得statement对象(是发送SQL语句的对象)

`Statement` 接口用于执行 SQL 语句并与数据库进行交互可以向数据库发送 SQL 语句并获取执行结果。

PreparedStatement(处理SQL注入攻击问题)

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

4 编写SQL语句(语法与在Mysql中写的一样)

5 通过statement调用executeQuery()方法,传入SQL语句-----------依次获得resultSet(结果集)

            MySQL中查询的结果被封装在resultSet中,resultSet中有行和列的数据存储

6 通过resultSet调用getXXX()方法获得数据

            查询结果中该列的数据是什么类型的就调用  get该类型()----------可通过列名也可通过索引获取函数

7 关闭资源    connection连接对象   statement传输对象     resultSet结果集

实体类搭建与ORM封装对象

实体类和ORM

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

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

例如将下面的表封装为一个个对象

   /*ORM封装多个对象:
    *创建一个数组将封装好的对象放入数组中;
    * 在循环中创建一个对象,下一次循环会将改对象覆盖掉,所以在依次循环的最后要赋值完毕的对象保存在集合中
    */
    @Test
    public void testORMList() throws SQLException {
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "abc123LQ");
        PreparedStatement preparedStatement = connection.prepareStatement("SELECT emp_id,emp_name,emp_salary,emp_age FROM t_emp");
        ResultSet resultSet = preparedStatement.executeQuery();
        Employee employee=null;
        //将多个对象保存在集合中;
        List<Employee> employeeList=new ArrayList<>();

        while(resultSet.next()){
            employee=new Employee();//下一次循环会将改对象覆盖掉,所以在依次循环的最后要赋值完毕的对象保存在集合中
            int emp_id = resultSet.getInt("emp_id");
            String emp_name = resultSet.getString("emp_name");
            double emp_salary = resultSet.getDouble("emp_salary");
            int emp_age = resultSet.getInt("emp_age");
            //为对象属性赋值
            employee.setEmpId(emp_id);
            employee.setEmpName(emp_name);
            employee.setEmpSalary(emp_salary);
            employee.setEmpAge(emp_age);
            //将每次循环的一行数据对象存储在集合里
            employeeList.add(employee);
        }
        //遍历集合
        for (Employee employee1 : employeeList) {
            System.out.println(employee1);
        }
        //资源释放
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }

主键回显

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

批量操作

 插入多条数据时,一条一条发送给数据库执行,效率低下!

 通过批量操作,可以提升多次操作效率!

    批量操作

    Java默认连接Mysql是无法进行皮量操作的

    步骤:1 必须在连接数据库的URL后追加?rewriteBatchedStatements=true

          将jdbc:mysql:///atguigu改为jdbc:mysql:///atguigu?rewriteBatchedStatements=true

         2 新增SQL必须用values,且语句最后不要追加;结束

         3 通过prepareStatement调用addBatch()方法-------------将SQL语句进行批量添加操作

         4 通过prepareStatement调用executeBatch()方法---------------------统一执行批量操作

连接池

 现有问题

> - 每次操作数据库都要获取新连接,使用完毕后就close释放,频繁的创建和销毁造成资源浪费。

> - 连接的数量无法把控,对服务器来说压力巨大。

连接池

> 连接池就是数据库连接对象的缓冲区,通过配置,由连接池负责创建连接、管理连接、释放连接等操作。

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

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

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

    Druid软编码实现

    1 创建Properties集合,用于存储外部配置文件的key与value值

           创建Properties对象

    2 读取外部配置文件,获取输入流,加载到properties集合里

           加载类时,创建输入流

           类加载;类名.class.getClassLoader()

           创建输入流:在类加载后调用getResourceAsStream()方法,出入要读取的文件路径

        加载到properties集合里:通过properties调用load()方法

    3 基于properties集合构建DruidDataSource连接池

          通过DruidDataSourceFactory调用createDataSource()方法,出入已读取好文件的properties对象-----------获得连接池(dataSource)

    4 通过连接池获取连接对象

           通过dataSource连接池调用getConnection()-----------------获得连接对象

代码

    @Test
    public void testResourcesDruid() throws Exception {
        //1 创建Properties集合,用于存储外部配置文件的key与value值
        Properties properties=new Properties();

        //2 读取外部配置文件,获取输入流,加载到properties集合里、
        InputStream inputStream = DruidTest.class.getClassLoader().getResourceAsStream("db.properties");
        /*加载类时,创建输入流*/
        properties.load(inputStream);

        //3 基于properties集合构建DruidDataSource连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

        //4 通过连接池获取连接对象
        Connection connection = dataSource.getConnection();
        //5 开发CRUD
        System.out.println(connection);

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

HikariCP编码实现  

    1 创建Properties集合,用于存储外部配置文件的keyvalue--------------通过Properties构造器创建properties对象

    2 读取外部配置文件,获取输入流,加载到properties集合里

           加载类时,创建输入流

           类加载;类名.class.getClassLoader()

           创建输入流:在类加载后调用getResourceAsStream()方法,出入要读取的文件路径

        加载到properties集合里:通过properties调用load()方法

    3 创建HikariConfig连接池配置对象,将properties集合传进去

             通过HikariConfig构造器创建连接池配置对象,传入properties

    4 基于HikariConfig连接池配置对象,构建HikariDataSource

            通过调用HikariDataSource的构造器,传入HikariConfig创建连接池

    5 通过连接池获取连接对象

            通过HikariDataSource的对象调用getConnection()方法

    @Test
    public void testResourcesHikari() throws IOException, SQLException {
        //1 创建Properties集合,用于存储外部配置文件的key与value值
        Properties properties=new Properties();

        //2 读取外部配置文件,获取输入流,加载到properties集合里
        InputStream InputStream = HikariTest.class.getClassLoader().getResourceAsStream("hikari.properties");
        properties.load(InputStream);

        //3 创建HikariConfig连接池配置对象,将properties集合传进去
        HikariConfig hikariConfig=new HikariConfig(properties);

        //4 基于HikariConfig连接池配置对象,构建HikariDataSource
        HikariDataSource hikariDataSource = new HikariDataSource(hikariConfig);
        Connection connection = hikariDataSource.getConnection();

        //5 获取连接
        System.out.println(connection);

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

工具类:

JDBC优化及工具类封装

 现有问题

> 我们在使用JDBC的过程中,发现部分代码存在冗余的问题:

> - 创建连接池。

> - 获取连接。

> - 连接的回收。

JDBC工具类

     1 维护连接池对象(该连接池在整个项目中都可以使用);维护了一个线程绑定变量的ThreadLocal对象

              在本地线程里存一个连接对象,此值可以在当前项目中任何位置都可以拿到----------在一个项目中使用的是同一个连接对象

     2 对外提供在ThreadLocal中获取连接的方法

     3 对外提供收回连接的方法,回收过程中,将要回收的连接从ThreadLocal中移除

 注意:工具类仅对外提供共性的功能代码,所以方法均为静态方法

      使用ThreadLocal就是为了一个线程在对此数据库操作过程中。使用的是同一个连接

代码:

public class JDBCUtil2 {
    //创建连接池引用,因为要提供为当前项目的全全局使用,所以创建为静态的
    private static DataSource dataSource;
    private static ThreadLocal<Connection> threadLocal=new ThreadLocal<>();

    //在项目启动时即创建连接池对象,并赋值给dataSource(使用静态代码块:静态类型的属性在静态代码块中赋值)
    static {
        try {
            Properties properties = new Properties();
            InputStream InputStream = JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");
            properties.load(InputStream);//静态代码块是不能向外声明异常的
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }//-------------------------------------------------------------------维护连接池对象(该连接池在整个项目中都可以使用)


    public static Connection getConnection(){
        try {
            //在ThreadLocal中获取Connection
            Connection connection = threadLocal.get();
            if(connection==null){//threadLocal中没有存储Connection,也就是第一次获取
                //在连接池中获取一个连接,存储在threaLocal里
                connection = dataSource.getConnection();
                threadLocal.set(connection);
            }
            return connection;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }//-------------------------------------------------------------------对外提供在连接池中获取连接的方法

    public static void release(){
        try {
            Connection connection = threadLocal.get();
            if(connection!=null){
                //从threaLocal中移除当前已经存储的connection对象
                threadLocal.remove();
                //如果开启了事务的手动提交,提交操作完毕后,还给连接池之前要将事务的手动提交改为true
                connection.setAutoCommit(true);---------------------------------事务
                //将connection对象归还连接池
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }//-------------------------------------------------------------------对外提供收回连接的方法
}

DAO概念及搭建

DAO:Data Access Object,数据访问对象。

> Java是面向对象语言,数据在Java中通常以对象的形式存在。一张表对应一个实体类,一张表的操作对应一个DAO对象!

> Java操作数据库时,我们会将对同一张表的增删改查操作统一维护起来,维护的这个类就是DAO

> DAO层只关注对数据库的操作,供业务层Service调用,将职责划分清楚!

 BaseDAO概念

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

代码及思路:

{
    /**
     * 通用的增删改方法
     * @param sql  调用者要执行的sql语句
     * @param params  SQL语句中占位符要赋值的参数
     * @return  返回受影响的行数
     */
    public int executeUpdate(String sql,Object... params) throws Exception {
        //1 通过JDBCUtil2获取数据库连接
        Connection connection = JDBCUtil2.getConnection();

        //2 预编译SQL语句
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //3 为占位符赋值
        if(params!=null && params.length>0){//防止没有为占位符输入要赋值的参数
            for(int i=0;i<params.length;i++){
                //占位符是从以开始的,参数的数组是从0开始的
                preparedStatement.setObject(i+1,params[i]);
            }
        }
        int row = preparedStatement.executeUpdate();
        //5 释放资源(要在返回值前释放)
        preparedStatement.close();
        if(connection.getAutoCommit()){
            JDBCUtil2.release();
        }
        //4 返回结果
        return row;
    }

//BaseDAO搭建通用查询方法
    /*
    思路:
    通用的查询:多行多列,单行多列,单行单列
        多行多列 List<T>
        单行多列 对象
        单行单列 封装的是一个结果 ,Double Integer....
    封装过程:
        1 返回的类型:泛型:类型不确定,调用者知道;调用时,将此次查询的结果类型告知BaseDAO就可以了
        2 返回的结果:通用,List  可以存储多个结果,也可以存储单个结果 get(0)
        3 结果的封装:反射,要求调用者告知BaseDAO要封装对象的类对象
     */
      public <T> List<T> executeQuery(Class<T> clazz,String sql,Object... params) throws Exception {
    Connection connection = JDBCUtil2.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 = preparedStatement.getMetaData();
    //列数
    int columnCount = metaData.getColumnCount();
    List<T> list=new ArrayList<>();
    //处理结果
    while (resultSet.next()){
        //循环一次,代表有一行数据,就创建一个对象将其存储起来
        //通过反射创建对象
        T t=clazz.newInstance();
        //通过循环遍历当前行的列,获取每一列的数据
        for(int i=1;i<=columnCount;i++){
            //通过下标获取列的值,获取到的列的value值,这个值就是t这个对象中的某一属性的值
            Object value = resultSet.getObject(i);

            //获取当前拿到的列的名字=对象的属性名
            String filedName = metaData.getColumnLabel(i);

            //通过类的对象和filedName获取要粉装的对象的属性
            Field field = clazz.getDeclaredField(filedName);
            //突破封装的private
            field.setAccessible(true);
            field.set(t,value);
        }
        list.add(t);
    }
    resultSet.close();
    preparedStatement.close();
    if(connection.getAutoCommit()){
        JDBCUtil2.release();
    }
    return list;
}

//通用的查询单个结果方法
/**
 * 通用查询:在上面查询的集合结果中获取第一个结果简化了单行单列的获取,也简化了单行多列的获取
 */
      public <T> T executeQueryBean(Class<T> clazz,String sql,Object... params) throws Exception{
          List<T> list = this.executeQuery(clazz,sql,params);
          if(list==null || list.size()==0){
              return null;
          }
          return list.get(0);
      }

}

事务

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

- 事务的特性:

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

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

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

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

- 事务的提交方式:

  - 自动提交:每条语句自动存储一个事务中,执行成功自动提交,执行失败自动回滚!

  - 手动提交:  手动开启事务,添加语句,手动提交或者手动回滚即可!

注意点:当开启事务后,切记一定要根据代码执行结果来决定是否提交或回滚!否则数据库看不到数据的操作结果


网站公告

今日签到

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