JDBC&数据库连接池

1、JDBC简介

JDBC 就是使用Java语言操作关系型数据库的一套API
全称:( Java DataBase Connectivity ) Java 数据库连接

  • JDBC是使用同一套Java代码操作不同数据库的 一套标准接口
  • 接口的实现类由数据库品牌自己实现 *(驱动 jar 包)

2、JDBC快速入门

  • 基本操作
// 导入对应版本的jar包,Add as library

// 注册驱动,MySQL 5 之后的驱动包可以省略
Class.forName("com.mysql.jdbc.Driver");

// 获取连接
String url = "jdbc:mysql://localhost:3306/db1";
String username = "root";
String passwd = "123456";
Connection connection = DriverManager.getConnection(url, username, passwd);

// 定义sql
String sql = "update account set money = 5000 where id = 1";

// 创建执行sql的对象Statement
Statement statement = connection.createStatement();

// 执行sql语句,返回受影响的行数
int count = statement.executeUpdate(sql);

// 处理返回结果
System.out.println(count);

// 释放资源
statement.close();
connection.close();

3、JDBC API

  • 作用:驱动管理类
  • 注册驱动
  • 获取数据库连接
  • 注册驱动
  • 源码
package com.mysql.jdbc;

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

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}
  • 注册驱动方式

    由上述源码可知,该类中的静态代码块已经实现了 DriverManager 对象的 registerDriver() 方法进行驱动的注册,故在使用中,仅需要加载Driver类即可,故采用反射来注册驱动。

Class.forName("com.mysql.jdbc.Driver");

MySQL 5 之后的驱动包可以省略注册驱动的步骤,其自动加载jar包中META-INF/services/java.sql.Driver文件中的驱动类
* 获取数据库连接
– 函数定义

Connection getConnection(String url, String user, String password);
  • 参数说明
    • url:连接路径
    • 语法: jdbc:mysql://ip地址(域名):端口/数据库名称?参数键值对1&参数键值对2...

      示例: jdbc:mysql://localhost:3306/db_name

      • 如果是本机mysql服务器,则可简写为 jdbc:mysql:///db_name?参数键值对1&参数键值对2...
      • 配置 useSSL=false参数,禁用安全连接方式,解决警告提示,例: jdbc:mysql:///db_name?useSSL=false
    • user:数据库用户名
    • password:数据库密码
// 普通执行SQL对象
Statement createStatement();
// 预编译SQL的执行SQL对象:防止SQL注入   (详见3.6.1)
PreparedStatement prepareStatement(String sql);
// 执行存储过程的对象
CallableStatement prepareCall(String sql);
  • MySQL事务管理
#开启事务 两种方式
begin;
start transaction;
#提交事务
commit;
#回滚事务
rollback;

#MySQL默认自动提交事务
  • JDBC事务管理
// 开启事务
setAutoCommit(bollean autoCommit);  // true:动提交事务,false:手动提交事务(开启事务)
// 提交事务
commit();
// 回滚事务
rollback();
  • 事务示例
try {
    // 开启事务
    connection.setAutoCommit(false);

    // 执行sql语句,返回受影响的行数
    int count1 = statement.executeUpdate(sql1);

    // 制造异常
    int i = 3 / 0;

    int count2 = statement.executeUpdate(sql2);

    // 数据处理
    System.out.println(count1+"  "+count2);

    // 提交事务
    connection.commit();

} catch (Exception throwable) {
    // 回滚事务
    connection.rollback();
    throwable.printStackTrace();
}
  • Statement对象用于执行SQL语句
// 通过数据库连接Connection创建Statement对象
Statement statement = connection.createStatement();
  • 执行DDL语句、DML语句
// 执行DDL,返回DDL语句影响的行数(无影响时返回0)
int executeUpdate(sql);

DDL语句执行成功且有影响时可能返回0,如删除数据库 实际开发中几乎不使用Java操作DDL语句
* 执行DQL语句

// 执行DQL,返回ResultSet结果集对象
ResultSet executeQuery(sql);
@Test
public void testDML() throws SQLException, ClassNotFoundException {
    // 注册驱动, 获取连接, 返回数据库连接对象(细节不表)
    Connection connection = myCreateStatement(url, username, passwd);

    // 定义sql
    String sql = "update account set money = 3000";

    // 执行sql对象Statement
    Statement statement = connection.createStatement();

    // 执行DML语句
    try {
        // 执行sql语句,返回受影响的行数
        int count = statement.executeUpdate(sql);
        // 数据处理
        System.out.println(count > 0 ? "修改成功" : "修改失败");
    } catch (SQLException throwable) {
        System.out.println("修改失败");
        throwable.printStackTrace();
    }

    // 执行DDL语句
    try {
        // DDL 执行删除操作,返回结果为0,不可使用返回值判断执行情况
        System.out.println(statement.executeUpdate("drop database db2"));
    } catch (SQLException throwable) {
        System.out.println("修改失败");
        throwable.printStackTrace();
    }

    // 释放资源
    statement.close();
    connection.close();
}

/**
 *DQL Test 详见3.4.2
 */
  • 作用:封装了SQL查询语句的结果,执行DQL语句后会返回 ResultSet 对象
  • 操作
// 将指针从当前数据移动到下一条数据,并判断当前行是否为有效行
boolean next();     // true:有效行,当前行有数据; false:无效行,当前行无数据

// 获取数据,xxx为数据类型
xxx getxxx(int index);  // index为编号,从1开始
xxx getxxx(String 字段名);
// 举例
int getint("gender");
String getString(1);
@Test
public void testDQL() throws SQLException, ClassNotFoundException {
    // 注册驱动, 获取连接, 返回数据库连接对象(细节不表)
    Connection connection = myCreateStatement(url, username, passwd);

    // 定义sql
    String sql = "select * from account";

    // 执行sql对象Statement
    Statement statement = connection.createStatement();

    // 执行DQL语句,处理返回数据
    ResultSet resultSet = statement.executeQuery(sql);
    ArrayList list = new ArrayList<>();
    while (resultSet.next()) {
        int id = resultSet.getInt("id");
        String name = resultSet.getString("name");
        int money = resultSet.getInt("money");
        list.add(new Account(id, name, money));
    }
    System.out.println(list);

    // 释放资源
    statement.close();
    connection.close();
}

作用:

  • 预编译SQL语句并执行:预防SQL注入问题

SQL注入是通过操作输入来修改实现定义好的SQL语句,用以执行SQL对服务器进行攻击的方式

  • 若后台代码未解决SQL注入问题,用户在前端填写表单时提交内容为包含模糊查询的SQL语句,则数据库查询结果为真,可跳过后台的逻辑判断,直接通过身份认证。如填写 1' OR '1'='1
  • 参考资料:
@Test
public void Login() throws SQLException, ClassNotFoundException {
    // 注册驱动, 获取连接, 返回数据库连接对象(细节不表)
    Connection connection = myCreateStatement(url, username, passwd);

    // 模拟接收到表单数据
    String name = "' OR '1'='1";
    String password = "' OR '1'='1";

    // SQL注入
    String sql = "select * from account where name = '" +
        name + "' and password = '" + password + "'";

    // 执行sql对象Statement
    Statement statement = connection.createStatement();

    // 执行DQL语句
    ResultSet resultSet = statement.executeQuery(sql);

    ArrayList list = new ArrayList<>();

    if(resultSet.next()) System.out.println("登录成功");
    else    System.out.println("登录失败");

    // 释放资源
    statement.close();
    connection.close();
}

注入后SQL语句为:

#where 后条件语句恒为真,即查询到全部数据
select * from account where name = '' or '1' = '1' and password = '' or '1' = '1';
  • 获取PreparedStatement对象
// SQL语句中的参数,使用?占位符进行替代
String sql = "select * from account where username = ? and password = ?";
// 通过Connection对象获取,并传入SQL语句
PreparedStatement preparedStatement = connection.prepareStatement(sql);
  • 设置参数值
  • 参数值为 ? 占位符位置的值
  • 使用 setXxx(&#x53C2;&#x6570;1,&#x53C2;&#x6570;2)进行赋值
  • 参数:
    • 参数1:? 的位置编号,从1开始
    • 参数2:? 的值
  • 执行SQL
  • 调用这两个方法时不需要传递SQL语句,因为获取SQL语句执行对象时SQL语句已经进行了预编译
// 执行DDL和DML语句
preparedStatement.executeUpdate();
// 执行DQL语句
preparedStatement.executeQuery();
@Test
public void testPreparedStatement() throws SQLException, ClassNotFoundException {
    // 注册驱动, 获取连接, 返回数据库连接对象(细节不表)
    Connection connection = myCreateStatement(url, username, passwd);

    // 模拟接收到表单数据
    String name = "1' OR '1'='1";
    String password = "1' OR '1'='1";

    // 定义sql
    // String sql = "select * from account where name = '" + name + "' and password = '" + password + "'";
    String sql = "select * from account where name = ? and password = ?";

    // 执行sql对象Statement
    // Statement statement = connection.createStatement();

    // 获取PreparedStatement对象
    PreparedStatement preparedStatement = connection.prepareStatement(sql);

    // 设置参数值
    preparedStatement.setString(1, name);
    preparedStatement.setString(2, password);

    // 执行DQL语句
    // ResultSet resultSet = statement.executeQuery(sql);
    ResultSet resultSet = preparedStatement.executeQuery();

    ArrayList list = new ArrayList<>();

    if(resultSet.next()) System.out.println("登录成功");
    else    System.out.println("登录失败");

    // 释放资源
    // statement.close();
    preparedStatement.close();
    connection.close();
}

使用PreparedStatement不会出现SQL注入的问题,其对特殊字符进行了转义

#转义后的SQL语句
select * from account where name = '\'or \'1\' = \'1' and password = '\'or \'1\' = \'1';
  • PrepareStatement的优势
  • 预编译SQL,性能更高
    • 在将含有占位符的SQL传入并创建PreparedStatement对象时就已经进行了预编译
    • 该对象仅执行一次预编译。之后将参数传递给通配符时不会重复编译,效率更高
  • 防止SQL注入
  • Java代码操作数据库的流程
  • 将SQL语句发送到MySQL服务器端
  • MySQL服务端会对SQL语句执行操作
    1. 检查SQL语句:检查语法
    2. 编译SQL语句:将SQL编译成可执行函数

      检查和编译比执行消耗的时间更长。 如果知识重新设置参数,则检查SQL语句和编译SQL语句将不需要重复执行,进而提高性能

    3. 执行SQL语句

4、数据库连接池

  • 数据库连接池
  • 是一个容器,负责分配、管理数据库连接(Connection对象)
  • 允许应用程序重复使用一个现有的数据库连接,而非重新创建
  • 释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引发的数据库连接遗漏
  • 优势
  • 资源复用
  • 提升系统影响速度
  • 避免数据库连接遗漏(回收闲置的数据库连接,避免无意义的占用)

  • 标准接口: DataSource

  • 官方提供的数据库连接池标准接口,由第三方实现接口
Connection getConnection();

不再使用 DriverManager对象回去数据库连接 Connection对象 使用连接池 DataSource获取 Connection对象
* 常见的数据库连接池
– DBCP
– C3P0
– Druid

  • 导入jar包
  • 配置文件
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true
username=root
password=123456
初始化连接数量
initialSize=5
最大连接数量
maxActive=10
最大等待时间
maxWait=3000
  • Java代码
public static void main(String[] args) throws Exception {
    // 加载配置文件
    Properties properties = new Properties();
    properties.load(new FileInputStream("jdbc-demo/src/druid.properties"));
    // 获取连接池对象
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    // 获取对应的数据库连接
    Connection connection = dataSource.getConnection();
    System.out.println(connection);
}

5、练习

  • 实现对tb_brand的CRUD基本操作

    数据库表结构

  • 名称 类型 长度 小数点 允许空 主键 id int 11 0 0 -1 brand_name varchar 20 0 -1 0 company_name varchar 20 0 -1 0 ordered int 11 0 -1 0 description varchar 100 0 -1 0 status int 11 0 -1 0

  • SQL

-- 删除tb_brand表
drop table if exists tb_brand;
-- 创建tb_brand表
create table tb_brand
(
    -- id 主键
    id           int primary key auto_increment,
    -- 品牌名称
    brand_name   varchar(20),
    -- 企业名称
    company_name varchar(20),
    -- 排序字段
    ordered      int,
    -- 描述信息
    description  varchar(100),
    -- 状态:0:禁用  1:启用
    status       int
);
-- 添加数据
insert into tb_brand (brand_name, company_name, ordered, description, status)
values ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),
       ('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界', 1),
       ('小米', '小米科技有限公司', 50, 'are you ok', 1);

SELECT * FROM tb_brand;
@Test
public void selectAll() throws Exception {
    // 导入配置文件
    Properties properties = new Properties();
    properties.load(new FileInputStream("src/druid.properties"));
    // 获取连接池对象
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    Connection connection = dataSource.getConnection();
    Statement statement = connection.createStatement();
    ResultSet resultSet = statement.executeQuery("select * from tb_brand");

    ArrayList brand = new ArrayList<>();
    while (resultSet.next()) {
        brand.add(new Brand(
            resultSet.getInt("id"),
            resultSet.getString("brand_name"),
            resultSet.getString("company_name"),
            resultSet.getInt("ordered"),
            resultSet.getString("description"),
            resultSet.getInt("status")));
    }
    System.out.println(brand);
    // 回收资源
    resultSet.close();
    statement.close();
    connection.close();
}
@Test
public void insert() throws Exception {
    // 导入配置文件
    Properties properties = new Properties();
    properties.load(new FileInputStream("src/druid.properties"));
    // 获取数据库连接池对象
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    Connection connection = dataSource.getConnection();
    // 获取PreparedStatement对象
    PreparedStatement preparedStatement = connection.prepareStatement(
        "insert into tb_brand" +
        "(brand_name, company_name, ordered, description, status) " +
        "values(?, ?, ?, ?, ?);"
    );
    // 设置参数
    preparedStatement.setString(1, "test01");
    preparedStatement.setString(2, "test01-xxx-c");
    preparedStatement.setInt(3, 1);
    preparedStatement.setString(4, "XxxXxx");
    preparedStatement.setInt(5, 1);
    // 执行SQL
    int count = preparedStatement.executeUpdate();
    // 添加是否成功
    System.out.println(count > 0);
    // 释放资源
    preparedStatement.close();
    connection.close();
}

@Test
public void updateByID() throws Exception {
    // 导入配置文件
    Properties properties = new Properties();
    properties.load(new FileInputStream("src/druid.properties"));
    // 创建数据库连接
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    Connection connection = dataSource.getConnection();
    // 获取PreparedStatement对象
    PreparedStatement preparedStatement = connection.prepareStatement(
        "update tb_brand set " +
        "brand_name = ?," +
        "company_name = ?," +
        "ordered = ?," +
        "description = ?," +
        "status = ? " +
        "where id = ?"
    );
    // 设置参数
    int id = 4;
    preparedStatement.setString(1,"aaa");
    preparedStatement.setString(2,"aaa");
    preparedStatement.setInt(3,1);
    preparedStatement.setString(4,"aaa");
    preparedStatement.setInt(5,0);
    preparedStatement.setInt(6, id);
    // 执行SQL
    int count = preparedStatement.executeUpdate();
    // 处理结果
    System.out.println( count > 0 );
    // 回收资源
    preparedStatement.close();
    connection.close();
}
@Test
public void dropByID() throws Exception {
    // 导入配置文件
    Properties properties = new Properties();
    properties.load(new FileInputStream("src/druid.properties"));
    // 创建数据库连接
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    Connection connection = dataSource.getConnection();
    // 获取PreparedStatement对象
    PreparedStatement preparedStatement = connection.prepareStatement(
        "delete from tb_brand where id = ?"
    );
    // 设置参数
    int id = 4;
    preparedStatement.setInt(1, id);
    // 执行SQL
    int count = preparedStatement.executeUpdate();
    // 返回结果
    System.out.println(count > 0);
    // 回收资源
    preparedStatement.close();
    connection.close();
}

6、附录

属性 说明 建议值 url 数据库的jdbc连接地址。一般为连接oracle/mysql。 示例如下:mysql : jdbc:mysql://ip:port/dbname?option1&option2&…

或oracle : jdbc:oracle:thin:@ip:port:oracle_sid等 username 登录数据库的用户名 password 登录数据库的用户密码 initialSize 启动程序时,在连接池中初始化多少个连接 10-50已足够 maxActive 连接池中最多支持多少个活动会话 maxWait 程序向连接池中请求连接时,超过maxWait的值后,认为本次请求失败,即连接池没有可用连接,单位毫秒,设置-1时表示无限等待 100 minEvictableIdleTimeMillis 池中某个连接的空闲时长达到 N 毫秒后, 连接池在下次检查空闲连接时,将回收该连接 要小于防火墙超时设置net.netfilter.nf_conntrack_tcp_timeout_established的设置 timeBetweenEvictionRunsMillis 检查空闲连接的频率 单位毫秒, 非正整数时表示不进行检查 keepAlive 程序没有close连接且空闲时长超过 minEvictableIdleTimeMillis,则会执行validationQuery指定的SQL,以保证该程序连接不会池kill掉,其范围不超过minIdle指定的连接个数。 true minIdle 回收空闲连接时,将保证至少有minIdle个连接. 与initialSize相同 removeAbandoned 要求程序从池中get到连接后, N 秒后必须close,否则druid 会强制回收该连接,不管该连接中是活动还是空闲, 以防止进程不会进行close而霸占连接。 false,当发现程序有未正常close连接时设置为true removeAbandonedTimeout 设置druid 强制回收连接的时限,当程序从池中get到连接开始算起,超过此值后,druid将强制回收该连接,单位秒。 应大于业务运行最长时间 logAbandoned 当druid强制回收连接后,是否将stack trace 记录到日志中 true testWhileIdle 当程序请求连接,池在分配连接时,是否先检查该连接是否有效。(高效) true validationQuery 检查池中的连接是否仍可用的 SQL 语句,drui会连接到数据库执行该SQL, 如果正常返回,则表示连接可用,否则表示连接不可用 testOnBorrow 程序

连接时,进行连接有效性检查(低效,影响性能) false testOnReturn 程序

连接时,进行连接有效性检查(低效,影响性能) false poolPreparedStatements 缓存通过以下两个方法发起的SQL:

public PreparedStatement prepareStatement(String sql)

public PreparedStatement prepareStatement(String sql,int resultSetType, int resultSetConcurrency) true maxPoolPrepareStatementPerConnectionSize 每个连接最多缓存多少个SQL 20 filters 这里配置的是插件,常用的插件有:监控统计: filter:stat;日志监控: filter:log4j 或者 slf4j;防御SQL注入: filter:wall stat,wall,slf4j connectProperties 连接属性。比如设置一些连接池统计方面的配置。 比如设置一些数据库连接属性:druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

Original: https://www.cnblogs.com/dandelion-000-blog/p/16576702.html
Author: Dandelion_000
Title: JDBC&数据库连接池

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/609530/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • JVM调优案例分析(4)

    1.概述 前面三篇介绍了处理Java虚拟机内存问题的知识与工具,在处理实际项目的问题 时,除了知识与工具外,经验也是一个很重要的因素。因此本章将与读者分享几个比较 有代表性的实际案…

    Java 2023年6月13日
    080
  • 【Java分享客栈】一文搞定CompletableFuture并行处理,成倍缩短查询时间。

    前言 工作中你可能会遇到很多这样的场景,一个接口,要从其他几个service调用查询方法,分别获取到需要的值之后再封装数据返回。 还可能在微服务中遇到类似的情况,某个服务的接口,要…

    Java 2023年6月9日
    083
  • Rust特征与泛型区别点

    1. Rust的特征与泛型 use std::boxed::Box; // 定义一个特征 pub trait Animal{ fn bark(&self); } pub s…

    Java 2023年6月15日
    076
  • Springboot源码——应用程序上下文分析

    前两篇(Spring MVC源码——Root WebApplicationContext 和 Spring MVC源码——Servlet WebApplicationContext…

    Java 2023年5月30日
    079
  • 数据结构中点链表的插入操作

    Status listinsert(Linklist &L,int i,ElemType e) //bool vs status :在函数需要有返回值的时候,既可以使用bo…

    Java 2023年6月5日
    064
  • 面向对象ooDay9

    同一个对象被造型为不同的类型时,有不同的功能——-所有对象都是多态的(明天总结详细讲) 对象的多态:水、我、你…… 同一类型的引用在…

    Java 2023年6月13日
    060
  • Java循环中的do…while循环控制

    do…while循环格式 初始化语句 ; do { 循环体语句 ; 条件控制语句 ; } while( 条件判断语句 ); int i = 0; boolean loo…

    Java 2023年6月8日
    063
  • Java ThreadLocal 与 OOM

    ThreadLocal 实例通常都是 static 类型,用于关联线程和线程上下文。 ThreadLocal 提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命…

    Java 2023年5月29日
    072
  • 针对MYSQL8.0报错:COM.MYSQL.JDBC.EXCEPTIONS.JDBC4.MYSQLNONTRANSIENTCONNECTIONEXCEPTION: COULD NOT CREATE

    问题是因为版本问题,驱动和数据库不匹配导致。 这个问题让我找了好久,给大家分享一下: 因为我的maven项目中导入的mysql-connector-java的版本是5.1.13,而…

    Java 2023年6月5日
    075
  • spring boot集成mybatis 出现 nvalid bound statement (not found)

    公司新搭建的项目 再idea中进行springboot集成mybatis时项目能正常启动,但在链接数据库时提示nvalid bound statement (not found) …

    Java 2023年5月30日
    072
  • dom4j解析xml时报出文件提前结束

    在写javaweb小项目的时候,用dom4j解析xml报出如下错误: org.dom4j.DocumentException:Error ……. Neste…

    Java 2023年6月13日
    0107
  • axios二次封装

    查看代码 import axiox from ‘axios’ import store from "@/store"; import utils from &q…

    Java 2023年6月8日
    083
  • jdbc连接数据库做简单的增查

    帮朋友写的一个作业,在这里记录一下,用久了springboot框架竟然忘记了以前都是怎么连接数据库的了。 需求 需要用户登录成功后才能进行操作 用户登陆成功后在控制台显示&#822…

    Java 2023年6月9日
    081
  • Spring MVC

    关于能用Spring怎样简化Web开发,想必大家已经好奇有段时间了。毕竟简化Web开发是Spring重头戏中的重头戏,也是我们学习Spring的主要目的。至于Spring是怎样简化…

    Java 2023年6月5日
    082
  • javaGUI中下拉列表联动问题

    首先 : 具体解释下 标题 下拉列表联动 就是将两个或两个以上的下拉列表连接(称作连接,后面就明白什么意思了) 简单的 例如 当我们点击别的省或市时 简而言之 就是实现下拉列表的联…

    Java 2023年6月5日
    082
  • Vulnhub-DC-4靶机实战

    前言 靶机下载地址:https://www.vulnhub.com/entry/dc-4,313/ KALI地址:192.168.75.108靶机地址:192.168.75.207…

    Java 2023年6月13日
    079
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球