JDBC详解

JDBC的概述

  • JDBC(Java Database Connectivity)是 Java 语言中访问关系型数据库的标准接口,它定义了一组 API,使得 Java 程序可以通过统一的方式连接、访问、操作不同的关系型数据库。JDBC API 提供了一套标准的接口,使得开发者可以使用 Java 语言来访问关系型数据库,而不必关心不同数据库之间的差异。
  • JDBC 有两个核心组件:JDBC 驱动程序和 JDBC API。JDBC 驱动程序通过提供特定数据库的实现,将 JDBC API 转换成数据库可以理解的命令。JDBC API 包括了一系列 Java 类和接口,使得开发者可以使用 Java 语言来执行 SQL 查询、更新和管理数据库连接等操作。
  • 使用 JDBC 连接数据库的步骤一般包括以下几个步骤:
    1. 加载 JDBC 驱动程序:使用 Class.forName()方法加载特定数据库的 JDBC 驱动程序。
    2. 创建数据库连接:使用 DriverManager.getConnection()方法创建一个数据库连接对象,该方法需要指定数据库的连接字符串、用户名和密码等信息。
    3. 创建 Statement 对象:使用数据库连接对象创建一个 Statement 对象,用于执行 SQL 查询和更新操作。
    4. 执行 SQL 语句:使用 Statement 对象执行 SQL 语句,包括查询、更新和删除等操作。
    5. 处理结果集:如果执行查询操作,则需要使用 ResultSet 对象处理查询结果。
    6. 关闭数据库连接:使用数据库连接对象的 close()方法关闭数据库连接。
  • DBC 是 Java 平台上与数据库交互的标准方式,它的应用广泛,可以用于开发各种类型的 Java 应用程序,包括 Web 应用程序、桌面应用程序和移动应用程序等。

JDBC 核心 API

JDBC 技术组成

  1. JDK 下 JDBC 规范接口, 存储在 java.sql 和 javax.sql 包中的 API

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

  1. 各个数据库厂商提供的驱动 jar 包

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

  1. jar 包是什么?

Java 程序打成的一种压缩包格式,你可以将这些 jar 包引入你的项目中,然后你可以使用这个 Java 程序中类和方法以及属性。

核心类和接口

  • DriverManager
  • 将第三方数据库厂商的实现驱动 jar 注册到程序中。
  • 可以根据数据库连接信息获取 connection。
  • Connection
  • 和数据库建立的连接,在连接对象上,可以多次执行数据库 CURD 动作。
  • 可以获取 statement 和 preparedstatement,callablestatement 对象。
  • Statement 、 PreparedStatement 、 CallableStatement
  • 具体发送 SQL 语句到数据库管理软件的对象。
  • 不同发送方式稍有不同, preparedstatement 使用为重点
  • Result
  • 面向对象思维的产物(抽象成数据库的查询结果表)。
  • 存储 DQL 查询数据库结果的对象。
  • 需要我们进行解析,获取具体的数据库数据。

JDBC API 使用路线

  • 静态 SQL 路线(没有动态值语句)
    • DriverManager
    • Connection
    • Statement
    • Result
  • 预编译 SQL 路线(有动态值语句)
    • DriverManager
    • Connection
    • PreparedStatement
    • Result
  • 执行标准存储过 SQL 路线
    • DriverManager
    • Collection
    • CallableStatement
    • Result

JDBC的使用

引入 MySQL-JDBC 驱动 jar

  1. 驱动版本的选择

    MySQL 版本推荐驱动版本备注
    MySQL 5.5.x5.0.xcom.mysql.jdbc.Driver
    MySQL 5.7.x5.1.xcom.mysql.jdbc.Driver
    MySQL 8.x8.0.x建议:8.0.25+ 省略时区设置 com.mysql.cj.jdbc.Driver
  2. 在maven工程中导入依赖

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
    

使用步骤

  1. 注册驱动【依赖的jar包 进行安装】
  2. 获取连接【connection建立连接】
  3. 创建发送sql语句对象【statement 创建发送sql语句的statement】
  4. 发送sql语句,并获取返回结果【statement发送sql语句到数据库 并且取得返回结构】
  5. 结果集解析【将result结果解析出来】
  6. 资源关闭【释放resultset、statement、connection】

静态 SQL 路线的使用(Statement)

不推荐使用,会被SQL注入攻击

例如:在拼接SQL语句的时候,可以凭借password = ‘’ or 1 = 1,这样查询即使password不正确也是返回true

 //执行SQL语句 [动态SQL语句,需要字符串拼接]
String sql = "select * from t_user where account = '"+account+"' and password = '"+password+"' ;";

1、准备数据

use `database-work`;

create table if not exists user (
    username varchar(100),
    password varchar(100)
);

insert into user values ("root","123456");

select * from user;

2、Java实现代码

public class StatementQueryPart {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        // 1、注册驱动
        //导入依赖:8+版本的驱动要带cj,低版本的需要导入 com.mysql.jdbc.Driver
        /*
         * 下面方法会new两次对象,增加了资源消耗不推荐使用
         * DriverManager.registerDriver(new Driver());调用两次。不用
         * new Driver()  频繁修改不优雅
         */
        // 通过反射的方式去注册驱动,推荐使用
        Class.forName("com.mysql.cj.jdbc.Driver");
        // 2、连接数据库
        /*
         *  java连接数据库肯定是调用某个方法,方法也需要填入数据库的基本信息:
         *  数据库ip地址: 192.168.45.128
         *  数据库端口号:3306
         *  账号:root
         *  密码:123456
         *  连接数据库的名称:databases-work
         *  扩展路径参数(了解):
         *      8.0.25以后,自动识别时区 serverTimezone=Asia/Shanghai 不用添加
         *      8版本以后, 默认使用utf-8格式, useUnicode=true&characterEncoding=utf8&useSSL=true 不用添加
         *      serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true
         */
        String url = "jdbc:mysql://192.168.45.128:3306/database-work";
        String username = "root";
        String password = "123456";
        Connection connection = DriverManager.getConnection(url, username, password);
        // 3、创建发送sql语句对象
        Statement statement = connection.createStatement();
        // 4、执行查询
        String sql = "select * from user";
        /* 
        查询语句使用 executeQuery
        增、删、改 使用 executeUpdate
        
        DML: delete、insert、update
        DQL: select、from、where、group by、having、order by、limit
        
        参数: sql 非 DQL
        返回: int
            情况1: DML 返回影响的行数, 例如: 删除了三条数据 return 3; 插入了两条 return 2; 修改了 0 条 return 0;
            情况2: 非 DML return 0;
        int row = executeUpdate(sql)

        参数: sql DQL
        返回: resultSet 结果封装对象
        ResultSet resultSet = executeQuery(sql);
         */
        ResultSet resultSet = statement.executeQuery(sql);
        // 5、结果集解析
        while (resultSet.next()) {
            System.out.println("username -> " + resultSet.getString("username") + " , password -> " + resultSet.getString("password"));
        }
        // 4、关闭资源
        resultSet.close();
        statement.close();
        connection.close();
    }
}

预编译 SQL 路线(PreparedStatement)

推荐使用,通过掉方法传入参数不会有SQL注入的风险

public class PreparedStatementQueryPart {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        // 1、注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        // 2、获取连接
        String url = "jdbc:mysql://192.168.45.128:3306/database-work";
        String username = "root";
        String password = "123456";
        Connection connection = DriverManager.getConnection(url, username, password);
        // 3、创建语句
        // SQL语句动态部分使用 ?占位符
        String sql = "select * from user where username = ? and password = ?";
        // 创建一个预编译的SQL语句对象,没有执行这个SQL
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //占位符赋值
        //给占位符赋值! 从左到右,从1开始!
        /*
         *  int 占位符的下角标
         *  object 占位符的值
         */
        preparedStatement.setObject(1,"root");
        preparedStatement.setObject(2,"123456");
        // 4、执行查询
        ResultSet resultSet = preparedStatement.executeQuery();
        while (resultSet.next()) {
            System.out.println("username -> " + resultSet.getString("username") + " , password -> " + resultSet.getString("password"));
        }
        // 5、关闭资源
        preparedStatement.close();
        connection.close();
    }
}

两种方式的执行流程

在这里插入图片描述

使用PreparedStatement实现数据库的增删改查

1、数据库的插入

// 插入数据
@Test
public void testInsert() throws Exception{

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

    //获取连接
    String url = "jdbc:mysql://192.168.45.128:3306/database-work";
    String username = "root";
    String password = "123456";
    Connection connection = DriverManager.getConnection(url, username, password);

    String sql = "insert into user(username,password) values (?,?);";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);

    //占位符赋值
    preparedStatement.setString(1, "admin");
    preparedStatement.setString(2, "123456");

    //发送SQL语句
    int rows = preparedStatement.executeUpdate();

    //输出结果
    System.out.println(rows);

    //关闭资源close
    preparedStatement.close();
    connection.close();
}

2、数据库的修改

// 修改数据
@Test
public void testUpdate() throws Exception{
    //注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");

    //获取连接
    String url = "jdbc:mysql://192.168.45.128:3306/database-work";
    String username = "root";
    String password = "123456";
    Connection connection = DriverManager.getConnection(url, username, password);

    String sql = "update user set password =? where username =?";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);

    //占位符赋值
    preparedStatement.setString(1, "123456789");
    preparedStatement.setString(2, "admin");

    //发送SQL语句
    int rows = preparedStatement.executeUpdate();

    //输出结果
    System.out.println(rows);

    //关闭资源close
    preparedStatement.close();
    connection.close();
}

3、数据库的删除

//  删除数据
@Test
public void testDelete() throws Exception{
    //注册驱动

    //获取连接
    String url = "jdbc:mysql://192.168.45.128:3306/database-work";
    String username = "root";
    String password = "123456";
    Connection connection = DriverManager.getConnection(url, username, password);

    String sql = "delete from user where username =?";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);

    //占位符赋值
    preparedStatement.setString(1, "admin");

    //发送SQL语句
    int rows = preparedStatement.executeUpdate();

    //输出结果
    System.out.println(rows);

    //关闭资源close
    preparedStatement.close();
    connection.close();
}

4、数据库的查询

// 数据查询
@Test
public void testQuery() throws Exception {
    //注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");

    //获取连接
    String url = "jdbc:mysql://192.168.45.128:3306/database-work";
    String username = "root";
    String password = "123456";
    Connection connection = DriverManager.getConnection(url, username, password);

    String sql = "select * from user";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);

    //发送SQL语句
    ResultSet resultSet = preparedStatement.executeQuery();

    //输出结果
    while (resultSet.next()) {
        System.out.println("username -> " + resultSet.getString("username") + " , password -> " + resultSet.getString("password"));
    }

    //关闭资源close
    preparedStatement.close();
    connection.close();
}

全新JDBC扩展提升

自增主键回显的实现

1、使用场景

java程序获取插入数据时mysql维护自增长维护的主键id值,这就是主键回显,主表默认增长,但是从表不知道值,所以要主键回显给从表知道

作用: 在多表关联插入数据时,一般主表的主键都是自动生成的,所以在插入数据之前无法知道这条数据的主键,但是从表需要在插入数据之前就绑定主表的主键,这时可以使用主键回显技术

2、实现代码

    /**
     * 返回插入的主键!
     * 主键:数据库帮助维护的自增长的整数主键!
     */
    @Test
    public void  returnPrimaryKey() throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.cj.jdbc.Driver");

        String url = "jdbc:mysql://192.168.45.128:3306/database-work";
        String username = "root";
        String password = "123456";
        Connection connection = DriverManager.getConnection(url, username, password);

        String sql = "insert into user(username,password) values(?,?)";

        /*
         * TODO: 第二个参数填入 1 | Statement.RETURN_GENERATED_KEYS
         *       告诉statement携带回数据库生成的主键!
         */
        PreparedStatement preparedStatement = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
        preparedStatement.setString(1,"admin");
        preparedStatement.setString(2,"123456");

        // 执行SQL语句 【注意:不需要传入SQL语句】 DML
        preparedStatement.executeUpdate();

        // 获取生成的主键结果集
        ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
        // 移动到结果集的第一行(即生成的主键值所在的行)
        generatedKeys.next();
        // 从结果集中获取自增主键值,参数 1 表示获取第一列的值,即自增主键值
        int anInt = generatedKeys.getInt(1);
        // 输出自增主键值
        System.out.println("anInt = " + anInt);
        
        // 释放资源
        statement.close();
        connection.close();
    }

批量数据插入性能的提升

可以提升大量数据插入时的效率

    /**
     *改动了三处:(1)路径(2)必写values,且后面不加;(3)装货addBatch()最后executeBatch();
     * 批量细节:
     *    1.url?rewriteBatchedStatements=true
     *    2.insert 语句必须使用 values
     *    3.语句后面不能添加分号;
     *    4.语句不能直接执行,每次需要装货  addBatch() 最后 executeBatch();
     * 批量插入优化!
     */
    @Test
    public  void batchInsertYH() throws Exception{
        Class.forName("com.mysql.cj.jdbc.Driver");

        String url = "jdbc:mysql://192.168.45.128:3306/database-work?rewriteBatchedStatements=true";
        String username = "root";
        String password = "123456";

        Connection connection = DriverManager.getConnection(url, username, password);

        String sql = "insert into user(username,password) values(?,?)";
        PreparedStatement statement = connection.prepareStatement(sql);

        for (int i = 0; i < 10000; i++) {
            statement.setString(1,"admin");
            statement.setString(2,"123456");
            statement.addBatch();
        }

        // 执行批量插入数据的sql
        statement.executeBatch();

        connection.close();
    }

JDBC中数据事务的实现

1、事务的概念

数据库事务就是一种SQL语句执行的缓存机制,不会单条执行完毕就更新数据库数据,最终根据缓存内的多条语句执行结果统一判定!

一个事务内所有语句都成功及事务成功,我们可以触发commit提交事务来结束事务,更新数据!

一个事务内任意一条语句失败,及事务失败,我们可以触发rollback回滚结束事务, 数据回到事务之前状态!

2、数据表

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

3、代码执行流程

在这里插入图片描述

4、实现代码

BankDao.java

package com.liuyi.bank;


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

public class BankDao {
    /**
     * 加钱方法
     * @param account
     * @param money
     * @param connection 业务传递的connection和减钱是同一个! 才可以在一个事务中!
     * @return 影响行数
     */
    public int addMoney(String account, int money, Connection connection) throws ClassNotFoundException, SQLException {


        String sql = "update t_bank set money = money + ? where account = ? ;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //占位符赋值
        preparedStatement.setObject(1, money);
        preparedStatement.setString(2, account);

        //发送SQL语句
        int rows = preparedStatement.executeUpdate();

        //输出结果
        System.out.println("加钱执行完毕!");

        //关闭资源close
        preparedStatement.close();

        return rows;
    }

    /**
     * 减钱方法
     * @param connection 业务传递的connection和加钱是同一个! 才可以在一个事务中!
     * @return 影响行数
     */
    public int subMoney(String account, int money,Connection connection) throws ClassNotFoundException, SQLException {

        String sql = "update t_bank set money = money - ? where account = ? ;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //占位符赋值
        preparedStatement.setObject(1, money);
        preparedStatement.setString(2, account);

        //发送SQL语句
        int rows = preparedStatement.executeUpdate();

        //输出结果
        System.out.println("减钱执行完毕!");

        //关闭资源close
        preparedStatement.close();

        return rows;
    }
}

BankService.java

package com.liuyi.bank;

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

public class BankService {
    private final BankDao bankDao;

    public BankService() {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            this.bankDao = new BankDao();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
    //一个事物最基本的是在同一个连接中connection,一个转账方法是一个事物,将connection传入dao
    //实现层即可,dao层不用关闭connection,由事物统一关闭
    /**
     * 转账业务方法
     * @param addAccount  加钱账号
     * @param subAccount  减钱账号
     * @param money  金额
     */
    public void transfer(String addAccount, String subAccount, int money) {
        Connection connection = null;
        try {
            String username = "root";
            String password = "123456";
            String url = "jdbc:mysql://192.168.45.128:3306/database-work?rewriteBatchedStatements=true";
            connection = DriverManager.getConnection(url, username, password);
            connection.setAutoCommit(false);
            bankDao.addMoney(addAccount, money, connection);
            bankDao.subMoney(subAccount, money, connection);
            connection.commit();
            System.out.printf("--- 转账成功:[%s] 收到 %d, [%s] 发送 %d ---\n", addAccount, money, subAccount, money);
        } catch (Exception e) {
            if (connection != null) {
                try {
                    connection.rollback();
                } catch (SQLException se) {
                    System.err.println("回滚错误!");
                    throw new RuntimeException(se);
                }
            }
            System.err.println("转账失败!详情:");
            e.printStackTrace();
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException se) {
                    System.err.println("关闭连接失败!");
                }
            }
        }
    }
}

BankTest.java

package com.liuyi.bank;


import org.junit.Test;

public class BankTest {
    @Test
    public void testBank() throws Exception {
        BankService bankService = new BankService();
        bankService.transfer("root", "admin",500);
    }
}

Druid连接池技术使用

1、不使用数据库连接池的缺点

(1)不使用数据库连接池,每次都通过DriverManager获取新连接,用完直接抛弃断开, 连接的利用率太低,太浪费。

(2)对于数据库服务器来说,压力太大了。我们数据库服务器和Java程序对连接数也无法控制 ,很容易导致数据库服务器崩溃。

2、数据库连接池的优点

  • 我们可以建立一个连接池,这个池中可以容纳一定数量的连接对象,一开始, 我们可以先替用户先创建好一些连接对象,等用户要拿连接对象时,就直接从池中拿, 不用新建了,这样也可以节省时间。然后用户用完后,放回去,别人可以接着用。

  • 可以提高连接的使用率。当池中的现有的连接都用完了,那么连接池可以向服务器申 请新的连接放到池中。

  • 直到池中的连接达到“最大连接数”,就不能在申请新的连接了,如果没有拿到连接的用户只能等待。

1、硬编码方式

    /**
     * 创建druid连接池对象,使用硬编码进行核心参数设置!
     *   必须参数: 账号
     *             密码
     *             url
     *             driverClass
     *   非必须参数:
     *           初始化个数
     *           最大数量等等  不推荐设置
     注意他要做的,注册驱动,获取连接,规定最大数量
     直接使用代码设置连接池连接参数方式
     */
    @Test
    public void druidHard() throws SQLException {
        //1.连接池对象
        DruidDataSource dataSource = new DruidDataSource();

        //2.设置四个必须参数
        //2.设置四个必须参数
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setUrl("jdbc:mysql://192.168.45.128:3306/database-work?rewriteBatchedStatements=true");
        //非必须
        dataSource.setInitialSize(5);  // 初始化数量
        dataSource.setMaxActive(10);   // 最大数量

        //3.获取连接
        Connection connection = dataSource.getConnection();
        // JDBC的步骤 正常curd
        //4.回收连接
        connection.close();
    }

2、软编码方式

使用 druid.properties 配置文件的方式

druid.properties

driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=123456
url=jdbc:mysql://192.168.45.128:3306/database-work?rewriteBatchedStatements=true

druid配置

配置缺省说明
name配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
jdbcUrl连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username连接数据库的用户名
password连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
driverClassName根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize0初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive8最大连接池数量
maxIdle8已经不再使用,配置了也没效果
minIdle最小连接池数量
maxWait获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatementsfalse是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements-1要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrowtrue申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturnfalse归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdlefalse建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls物理连接初始化的时候执行的sql
exceptionSorter根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

druid声明代码

/**
 * 不直接在java代码编写配置文件!
 * 利用工厂模式,传入配置文件对象,创建连接池!
 */
@Test
public void druidHard() throws Exception {
    // 获取外部配置文件 Properties
    Properties properties = new Properties();

    // 获取输入流,利用类加载器
    InputStream druidFile = DruidDbUtils.class.getClassLoader().getResourceAsStream("druid.properties");
    properties.load(druidFile);

    // 使用连接池工具类的工厂模式,创建连接池
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

    // 获取连接
    Connection connection = dataSource.getConnection();

    // 关闭连接
    connection.close();
}

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

为了避免每次都要创建连接池,我们封装一个工具类,内部包含连接池对象,同时对外提供连接的方法和回收连接的方法!

JDBC工具类封装 v1.0

druid.properties

driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=123456
url=jdbc:mysql://192.168.45.128:3306/database-work?rewriteBatchedStatements=true

JDBCUtilsVersion1.java

package com.liuyi.Utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

public class JDBCUtilsVersion1 {
    private static final DataSource ds;

    static {
        Properties properties = new Properties();
        try {
            properties.load(JDBCUtilsVersion1.class.getClassLoader().getResourceAsStream("druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static Connection getConnection() throws SQLException {
        return ds.getConnection();  //这么写,不能保证同一个线程,两次getConnection()得到的是同一个Connection对象
        // 如果不能保证是同一个连接对象,就无法保证事务的管理
    }

    public static void releaseConnection(Connection conn) throws SQLException {
        conn.setAutoCommit(true);
        conn.close(); // 还给连接池
    }
}

JDBC工具类封装 v2.0

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

ThreadLocal的介绍:

线程本地变量:为同一个线程存储共享变量

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

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

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

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

3、ThreadLocal对象.remove: 移除ThreadLocal中当前线程共享变量的值。

JDBCUtilsVersion2.java

package com.liuyi.Utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

//事物时,Service和dao属于同一线程,不用再传参数了
/*
这个工具类的作用就是用来给所有的SQL操作提供“连接”,和释放连接。
这里使用ThreadLocal的目的是为了让同一个线程,在多个地方getConnection得到的是同一个连接。
这里使用DataSource的目的是为了(1)限制服务器的连接的上限(2)连接的重用性等
 */
public class JDBCUtilsVersion2 {
    private static DataSource ds;
    private static final ThreadLocal<Connection> tl = new ThreadLocal<>();
    static{//静态代码块,JDBCToolsVersion1类初始化执行
        try {
            Properties pro = new Properties();
            pro.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(pro);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws SQLException {
        //获取连接
        Connection connection = tl.get();
        if(connection  == null){//当前线程还没有拿过连接,就给它从数据库连接池拿一个
            connection = ds.getConnection();
            //连接存入共享变量中
            tl.set(connection);
        }
        return connection;
    }

    public static void releaseConnection() throws SQLException {
        Connection connection = tl.get();
        if(connection != null){
            tl.remove();
            //避免还给数据库连接池的连接不是自动提交模式(建议)
            //因为其在业务类中会关闭事务的自动提交
            connection.setAutoCommit(true);
            connection.close();
        }
    }
}

应用层封装BaseDao

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

针对DQL查询和非DQL进行,分成两类

package com.liuyi.Utils;

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

public abstract class BaseDao {
    /*
    通用的增、删、改的方法
    String sql:sql
    Object... args:给sql中的?设置的值列表,可以是0~n
     */
    protected int update(String sql, Object... args) throws SQLException {
        try (Connection connection = JDBCUtilsVersion2.getConnection();
             PreparedStatement ps = connection.prepareStatement(sql)) {

            // 设置?的值
            if (args != null && args.length > 0) {
                for (int i = 0; i < args.length; i++) {
                    ps.setObject(i + 1, args[i]);// ?的编号从1开始,不是从0开始,数组的下标是从0开始
                }
            }

            // 执行sql
            return ps.executeUpdate();
        }
    }

    /*
        通用的查询多个Javabean对象的方法,例如:多个员工对象,多个部门对象等
        这里的clazz接收的是T类型的Class对象,
        如果查询员工信息,clazz代表Employee.class,
        如果查询部门信息,clazz代表Department.class,
        返回List<T> list
     */
    protected <T> ArrayList<T> query(Class<T> clazz, String sql, Object... args) throws Exception {
        try (Connection connection = JDBCUtilsVersion2.getConnection();
             PreparedStatement ps = connection.prepareStatement(sql)) {

            // 设置?的值
            if (args != null && args.length > 0) {
                for (int i = 0; i < args.length; i++) {
                    ps.setObject(i + 1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
                }
            }

            ArrayList<T> list = new ArrayList<>();
            try (ResultSet res = ps.executeQuery()) {
                ResultSetMetaData metaData = res.getMetaData();
                int columnCount = metaData.getColumnCount();//获取结果集列数

                while (res.next()) {
                    Constructor<T> constructor = clazz.getDeclaredConstructor();
                    T t = constructor.newInstance();//要求这个类型必须有公共的无参构造

                    for (int i = 1; i <= columnCount; i++) {
                        Object value = res.getObject(i);

                        String columnName = metaData.getColumnLabel(i);//获取第i列的字段名或字段的别名
                        Field field = clazz.getDeclaredField(columnName);
                        field.setAccessible(true);

                        field.set(t, value);
                    }

                    list.add(t);
                }
            }

            return list;
        }
    }

    protected <T> T queryBean(Class<T> clazz, String sql, Object... args) throws Exception {
        ArrayList<T> list = query(clazz, sql, args);
        return list.isEmpty() ? null : list.get(0);
    }
}
  • 23
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
JDBC(Java Database Connectivity)是一种Java语言访问数据库的标准API,它提供了一组用于访问关系型数据库的类和接口。在使用JDBC时,我们需要进行一些配置,下面是jdbc配置的详解: 1. 导入jdbc驱动:在项目中引入对应数据库的jdbc驱动包,这个驱动包通常由数据库厂商提供,也可以在Maven或Gradle中添加对应的依赖。 2. 加载驱动类:使用Class.forName()方法加载JDBC驱动程序类,加载后会自动注册到DriverManager中。 3. 建立数据库连接:使用DriverManager.getConnection()方法建立与数据库的连接,需要传递数据库连接URL、用户名和密码等参数。 4. 创建Statement对象:使用Connection对象的createStatement()方法创建Statement对象,用于发送SQL语句到数据库执行。 5. 执行SQL语句:使用Statement对象的execute()、executeQuery()、executeUpdate()等方法执行SQL语句,获取结果集或影响的行数。 6. 处理结果集:如果执行的SQL语句返回了结果集,可以使用ResultSet对象进行遍历和获取数据。 7. 关闭资源:在使用完JDBC后,需要释放资源,包括ResultSet对象、Statement对象和Connection对象等,可以使用close()方法进行释放。 下面是一个简单的JDBC配置示例: ```java import java.sql.*; public class JdbcDemo { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "123456"; Connection conn = null; Statement stmt = null; ResultSet rs = null; try { // 加载MySQL驱动 Class.forName("com.mysql.jdbc.Driver"); // 建立连接 conn = DriverManager.getConnection(url, user, password); // 创建Statement对象 stmt = conn.createStatement(); // 执行SQL语句 rs = stmt.executeQuery("SELECT * FROM users"); // 处理结果集 while (rs.next()) { int id = rs.getInt("id"); String name = rs.getString("name"); int age = rs.getInt("age"); System.out.println("id=" + id + ", name=" + name + ", age=" + age); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } finally { // 关闭资源 try { if (rs != null) rs.close(); if (stmt != null) stmt.close(); if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } ``` 在这个示例中,我们使用了MySQL数据库,首先通过Class.forName()方法加载MySQL驱动类,然后使用DriverManager.getConnection()方法建立与数据库的连接,使用Connection对象的createStatement()方法创建Statement对象,使用executeQuery()方法执行SQL语句,再使用ResultSet对象处理结果集。最后在finally块中关闭资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

‘༺༃修༒罗༃༻’

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

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

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

打赏作者

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

抵扣说明:

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

余额充值