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)

大家都在看

  • 自定义博客园主题

    博客园主题代码GitHub地址 https://github.com/yushixin-1024/Cnblogs-Theme-SimpleMemory 该项目Fork自https:…

    Java 2023年6月8日
    0127
  • Java基础–线程创建方式

    线程的创建主要有两种形式,通过 &#x7EE7;&#x627F;Thread或者 &#x5B9E;&#x73B0;Runnable&#x63…

    Java 2023年6月5日
    066
  • Java8 中增强 Future:CompletableFuture

    增强的 Future:CompletableFuture CompletableFuture(它实现了 Future 接口) 和 Future 一样,可以作为函数调用的契约。当你向…

    Java 2023年5月29日
    063
  • 28.多线程,分组服务端,1万客户端连接,代码优化

    客户端只发送数据,服务端只接收数据。 服务端增加,用户接入,离开函数。 客户端增加,判断是否成功连接服务端标志。 问题:服务端接收客户端数据包,不稳定,波动比较大。 客户端代码: …

    Java 2023年5月29日
    075
  • vmware虚拟机备忘录

    虚拟机拷贝自其他服务器,有时候IP,网络都配置正确就是没法ping 通网络,重启虚拟机也没用 此时将网卡删除,重新添加 systemctl restart network 即可 O…

    Java 2023年5月30日
    075
  • 国内三大地图(腾讯、高德、百度)路线规划功能的整合

    写在前面 基于导航到门店的需求,于是有了这一个随笔,例如一些社区团购,自提点导航的功能,同样适用的。 话不多说,开整 一、先定一个目标点(这个通常是通过接口获取的) 建议通过腾讯地…

    Java 2023年6月16日
    0170
  • 数据批处理速度慢?不妨试试这个

    业务系统产生的明细数据通常要经过加工处理,按照一定逻辑计算成需要的结果,用以支持企业的经营活动。这类数据加工任务一般会有很多个,需要批量完成计算,在银行和保险行业常常被称为跑批,其…

    Java 2023年6月15日
    0105
  • Android JNI中C调用Java方法

    背景需求 我们需要在JNI的C代码调用Java代码。实现原理:使用JNI提供的反射借口来反射得到Java方法,进行调用。 JNI关键方法讲解。 在同一个类中,调用其他方法 JNIE…

    Java 2023年5月29日
    083
  • Z-blog csrf漏洞学习

    Z-blog csrf 环境搭建 1. 首先我在本地搭了一个z-blog。 ​ 思路:csrf并不侧重于哪种功能点,只要检测不规范,就可能利用成功,所以我考虑了一下后台添加管理员的…

    Java 2023年6月6日
    088
  • Linux 运行jar包提示 no main manifest attribute

    解决办法:在pom文件中添加如下依赖 解释: 在sprinboot项目中pom.xml文件加 ****,代表maven打包时会将外部引入的jar包(比如在根目录下或resource…

    Java 2023年6月5日
    098
  • Java三大特性之多态

    多态概述 Java有三大特性:封装、继承和多态。 ​ 那么什么是多态呢?所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序…

    Java 2023年6月5日
    089
  • eslint关闭配置–vue-webpack

    javascript;gutter:true;devServer: { overlay: { warnings: false, errors: false } }, lintOnS…

    Java 2023年5月29日
    073
  • TL,你是如何管理项目风险的?

    沙包和打伞的故事 美国在1961年到1972年组织实施的一系列载人登月飞行任务。目的是实现载人登月飞行和人对月球的实地考察,为载人行星飞行和探测进行技术准备,它是世界航天史上具有划…

    Java 2023年6月8日
    080
  • html学习笔记

    结构化标准语言(HTML、XML) 表现标准语言(CSS) 行为标准(DOM、ECMAScript) 网页基本标签 标题标签:到 段落标签: 换行标签: 水平线标签: 字体样式标签…

    Java 2023年6月5日
    064
  • SpringBoot 整合缓存Cacheable实战详细使用

    前言 我知道在接口api项目中,频繁的调用接口获取数据,查询数据库是非常耗费资源的,于是就有了缓存技术,可以把一些不常更新,或者经常使用的数据,缓存起来,然后下次再请求时候,就直接…

    Java 2023年6月13日
    0120
  • vnpy源码阅读学习(5):关于MainEngine的代码阅读

    在入口文件中,我们看到了除了窗体界面的产生,还有关于 MainEngine和 EventEngin部分。今天来学习下 MainEngine的代码。 首先在run代码中,我们看到以下…

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