Java JDBC核心技术全解析

一、JDBC概述

1、概念

  • jdbc是(Java Database Connectivity)单词的缩写,翻译为java连接数据库
  • jdbc是java程序连接数据库的技术统称
  • jdbc由java语言的规范(接口)和各个数据库厂商的实现驱动(jar)组成
  • jdbc是一种典型的面向接口编程
  • jdbc优势
    1. 只需要学习jdbc规范接口的方法,即可操作所有的数据库软件
    2. 项目中期切换数据库软件,只需要更换对应的数据库驱动jar包,不需要更改代码

2、jdbc核心api和使用路线

1、jdbc技术组成

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

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

jar包是什么?

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

jdk下jdbc规范接口, 存储在java.sql和javax.sql包中的api

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

2、涉及具体核心类和接口

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

3、jdbc api使用路线

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

二、全新JDBC核心API

1、引入mysql-jdbc驱动jar

驱动jar版本选择

mysql版本推荐驱动版本备注
mysql 5.5.x5.0.xcom.mysql.jdbc.Driver
mysql 5.7.x5.1.xcom.mysql.jdbc.Driver
msyql 8.x8.0.x建议: 8.0.25+省略时区设置 com.mysql.cj.jdbc.Driver

2、POM依赖

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

3、jdbc基本使用步骤分析

  1. 注册驱动
  2. 获取连接
  3. 创建发送sql语句对象
  4. 发送sql语句,并获取返回结果
  5. 结果集解析
  6. 资源关闭

4、基于statement实现查询

准备数据库数据

CREATE DATABASE xx_jdbc; USE xx_jdbc; CREATE TABLE t_user( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户主键', account VARCHAR(20) NOT NULL UNIQUE COMMENT '账号', PASSWORD VARCHAR(64) NOT NULL COMMENT '密码', nickname VARCHAR(20) NOT NULL COMMENT '昵称'); INSERT INTO t_user(account,PASSWORD,nickname) VALUES ('root','123456','技术总监'),('admin','666666','CTO');
package com.xx; import java.sql.*; /** * @Author: xueqimiao * @Date: 2023/1/4 09:14 */ public class JdbcBasePart { public static void main(String[] args) throws SQLException { //1.注册驱动 /** * TODO: 注意 * Driver -> com.mysql.cj.jdbc.Driver */ DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver()); //2.获取连接 /** * 面向接口编程 * java.sql 接口 = 实现类 * connection 使用java.sql.Connection接口接收 */ Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xx_jdbc", "root", "mac_root"); //3.创建小车 Statement statement = connection.createStatement(); //4.发送SQL语句 String sql = "select id,account,password,nickname from t_user ;"; ResultSet resultSet = statement.executeQuery(sql); //5.结果集解析 while (resultSet.next()) { int id = resultSet.getInt("id"); String account = resultSet.getString("account"); String password = resultSet.getString("password"); String nickname = resultSet.getString("nickname"); System.out.println(id + "\t" + account + "\t" + password + "\t" + nickname); } //6.关闭资源 【先开后关】 resultSet.close(); statement.close(); connection.close(); } }

5、基于statement方式问题

模拟登录,控制台输入账号和密码,判断是否登陆成功成功!

package com.xx; import java.sql.*; import java.util.Scanner; /** * @Author: xueqimiao * @Date: 2023/1/4 09:23 */ public class JdbcStatementLoginPart { public static void main(String[] args) throws ClassNotFoundException, SQLException { //1.输入账号和密码 Scanner scanner = new Scanner(System.in); String account = scanner.nextLine(); String password = scanner.nextLine(); scanner.close(); //2.jdbc的查询使用 /** * 类加载: java文件 -> 编译 -> 【 class字节码文件 --> 类加载 --> jvm虚拟中 --> Class对象】 * 类加载具体步骤: 加载 【class文件转成对象加载到虚拟机中】-> * 连接 【验证(检查类文件) -> 准备 (静态变量赋默认值) -> 解析 (调用静态代码块) 】 -> * 初始化 -> (赋真实值) * 以下7种方式会触发类加载: * 1. new关键字 * 2. 调用静态属性 * 3. 调用静态方法 * 4. 接口 包含1.8 新特性 default关键字 * 5. 反射 【Class.forName() 类名.class】 * 6. 子类调用会触发父类的静态代码块 * 7. 触发类的入口方法main */ //注册一次驱动 Class.forName("com.mysql.cj.jdbc.Driver"); /** * 重写: 为了子类扩展父类的方法!父类也间接的规范了子类方法的参数和返回! * 重载: 重载一般应用在第三方的工具类上,为了方便用户多种方式传递参数形式!简化形式! */ /** * 三个参数: * String URL: 连接数据库地址 * String user: 连接数据库用户名 * String password: 连接数据库用户对应的密码 * 数据库URL语法: * JDBC: * ip port * jdbc:mysql | jdbc:oracle :// 127.0.0.1 | localhost : 3306 / 数据库名 * jdbc:mysql://localhost:3306/xx_jdbc * 192.168.33.45 * jdbc:mysql://192.168.33.45/3306/xx_jdbc * 当前电脑的省略写法! 注意:本机和端口3306 * jdbc:mysql://localhost:3306/xx_jdbc = jdbc:mysql:///xx_jdbc * * 两个参数: * String URL : 写法还是jdbc的路径写法! * Properties : 就是一个参数封装容器!至少要包含 user / password key!存储连接账号信息! * * 一个参数: * String URL: URl可以携带目标地址,可以通过?分割,在后面key=value&key=value形式传递参数 * jdbc:mysql:///xx_jdbc?user=root&password=123456 * 扩展路径参数(了解): * serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true * */ //获取连接 Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc", "root", "mac_root"); //固定方法固定剂 //创建statement Statement statement = connection.createStatement(); //执行SQL语句 [动态SQL语句,需要字符串拼接] String sql = "select * from t_user where + account + "' and + password + "' ;"; /** * ResultSet 结果集对象 = executeQuery(DQL语句) * int 响应行数 = executeUpdate(非DQL语句) */ ResultSet resultSet = statement.executeQuery(sql); //ResultSet == 你必须有面向对象的思维:Java是面向对象编程的语言 OOP! /** * * TODO:1.需要理解ResultSet的数据结构和在navicat中查询出来的是一样,需要在脑子里构建结果表! * TODO:2.有一个光标指向的操作数据行,默认指向第一行的上边!我们需要移动光标,指向行,在获取列即可! * boolean = next() * false: 没有数据,也不移动了! * true: 有更多行,并且移动到下一行! * 推荐:推荐使用if 或者 while循环,嵌套next方法,循环和判断体内获取数据! * if(next()){获取列的数据!} || while(next()){获取列的数据!} * *TODO:3.获取当前行列的数据! * get类型(int columnIndex | String columnLabel) * 列名获取 //lable 如果没有别名,等于列名, 有别名label就是别名,他就是查询结果的标识! * 列的角标 //从左到右 从1开始! 数据库全是从1开始! */ //进行结果集对象解析 if (resultSet.next()) { //只要向下移动,就是有数据 就是登录成功! System.out.println("登录成功!"); } else { System.out.println("登录失败!"); } //关闭资源 resultSet.close(); statement.close(); connection.close(); } }
admin 666666 登录成功! admin 123456 'or 1='1 登录成功!

1、存在问题

  1. SQL语句需要字符串拼接,比较麻烦
  2. 只能拼接字符串类型,其他的数据库类型无法处理

可能发生注入攻击

动态值充当了SQL语句结构,影响了原有的查询结果!

6、基于preparedStatement方式优化

利用preparedStatement解决上述案例注入攻击和SQL语句拼接问题!
package com.xx; import java.sql.*; import java.util.Scanner; /** * @Author: xueqimiao * @Date: 2023/1/4 09:30 */ public class JdbcPreparedStatementLoginPart { public static void main(String[] args) throws ClassNotFoundException, SQLException { //1.输入账号和密码 Scanner scanner = new Scanner(System.in); String account = scanner.nextLine(); String password = scanner.nextLine(); scanner.close(); //2.jdbc的查询使用 //注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //获取连接 Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc", "root", "mac_root"); //创建preparedStatement //connection.createStatement(); //TODO 需要传入SQL语句结构 //TODO 要的是SQL语句结构,动态值的部分使用 ? , 占位符! //TODO ? 不能加 '?' ? 只能替代值,不能替代关键字和容器名 String sql = "select * from t_user where account = ? and password = ? ;"; PreparedStatement preparedStatement = connection.prepareStatement(sql); //占位符赋值 //给占位符赋值! 从左到右,从1开始! /** * int 占位符的下角标 * object 占位符的值 */ preparedStatement.setObject(2, password); preparedStatement.setObject(1, account); //这哥们内部完成SQL语句拼接! //执行SQL语句即可 ResultSet resultSet = preparedStatement.executeQuery(); //preparedStatement.executeUpdate() //进行结果集对象解析 if (resultSet.next()) { //只要向下移动,就是有数据 就是登录成功! System.out.println("登录成功!"); } else { System.out.println("登录失败!"); } //关闭资源 resultSet.close(); preparedStatement.close(); connection.close(); } }
img

7、基于preparedStatement增删改查

1、新增

/** * 插入一条用户数据! * 账号: test * 密码: test * 昵称: 测试 */ @Test public void testInsert() throws Exception { //注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //获取连接 Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc", "root", "mac_root"); //TODO: 切记, ? 只能代替 值!!!!! 不能代替关键字 特殊符号 容器名 String sql = "insert into t_user(account,password,nickname) values (?,?,?);"; PreparedStatement preparedStatement = connection.prepareStatement(sql); //占位符赋值 preparedStatement.setString(1, "test"); preparedStatement.setString(2, "test"); preparedStatement.setString(3, "测试"); //发送SQL语句 int rows = preparedStatement.executeUpdate(); //输出结果 System.out.println(rows); //关闭资源close preparedStatement.close(); connection.close(); }

2、修改

/** * 修改一条用户数据! * 修改账号: test的用户,将nickname改为tomcat */ @Test public void testUpdate() throws Exception { //注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //获取连接 Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc", "root", "mac_root"); //TODO: 切记, ? 只能代替 值!!!!! 不能代替关键字 特殊符号 容器名 String sql = "update t_user set nickname = ? where account = ? ;"; PreparedStatement preparedStatement = connection.prepareStatement(sql); //占位符赋值 preparedStatement.setString(1, "tomcat"); preparedStatement.setString(2, "test"); //发送SQL语句 int rows = preparedStatement.executeUpdate(); //输出结果 System.out.println(rows); //关闭资源close preparedStatement.close(); connection.close(); }

3、删除

/** * 删除一条用户数据! * 根据账号: test */ @Test public void testDelete() throws Exception { //注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //获取连接 Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc", "root", "mac_root"); //TODO: 切记, ? 只能代替 值!!!!! 不能代替关键字 特殊符号 容器名 String sql = "delete from t_user where account = ? ;"; PreparedStatement preparedStatement = connection.prepareStatement(sql); //占位符赋值 preparedStatement.setString(1, "test"); //发送SQL语句 int rows = preparedStatement.executeUpdate(); //输出结果 System.out.println(rows); //关闭资源close preparedStatement.close(); connection.close(); }

4、查询

/** * 查询全部数据! * 将数据存到List<Map>中 * map -> 对应一行数据 * map key -> 数据库列名或者别名 * map value -> 数据库列的值 * 1.先创建一个List<Map>集合 * 2.遍历resultSet对象的行数据 * 3.将每一行数据存储到一个map对象中! * 4.将对象存到List<Map>中 * 5.最终返回 * <p> * 学习获取结果表头信息(列名和数量等信息) */ @Test public void testQueryMap() throws Exception { //注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //获取连接 Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc", "root", "mac_root"); //TODO: 切记, ? 只能代替 值!!!!! 不能代替关键字 特殊符号 容器名 String sql = "select id,account,password,nickname from t_user ;"; PreparedStatement preparedStatement = connection.prepareStatement(sql); //占位符赋值 本次没有占位符,省略 //发送查询语句 ResultSet resultSet = preparedStatement.executeQuery(); //创建一个集合 List<Map> mapList = new ArrayList<>(); //获取列信息对象 ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); while (resultSet.next()) { Map map = new HashMap(); for (int i = 1; i <= columnCount; i++) { map.put(metaData.getColumnLabel(i), resultSet.getObject(i)); } mapList.add(map); } System.out.println(mapList); //关闭资源close preparedStatement.close(); connection.close(); resultSet.close(); }

三、全新JDBC扩展提升

1、自增长主键回显实现

java程序获取插入数据时mysql维护自增长维护的主键id值,这就是主键回显作用: 在多表关联插入数据时,一般主表的主键都是自动生成的,所以在插入数据之前无法知道这条数据的主键,但是从表需要在插入数据之前就绑定主表的主键,这时可以使用主键回显技术:
/** * 返回插入的主键! * 主键:数据库帮助维护的自增长的整数主键! * * @throws Exception */ @Test public void returnPrimaryKey() throws Exception { //1.注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2.获取连接 Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc?user=root&password=mac_root"); //3.编写SQL语句结构 String sql = "insert into t_user (account,password,nickname) values (?,?,?);"; //4.创建预编译的statement,传入SQL语句结构 /** * TODO: 第二个参数填入 1 | Statement.RETURN_GENERATED_KEYS * 告诉statement携带回数据库生成的主键! */ PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); //5.占位符赋值 statement.setObject(1, "tomcat"); statement.setObject(2, "123456"); statement.setObject(3, "汤姆猫"); //6.执行SQL语句 【注意:不需要传入SQL语句】 DML int i = statement.executeUpdate(); //7.结果集解析 System.out.println("i = " + i); //一行一列的数据!里面就装主键值! ResultSet resultSet = statement.getGeneratedKeys(); resultSet.next(); int anInt = resultSet.getInt(1); System.out.println("pk = " + anInt); //8.释放资源 statement.close(); connection.close(); }

2、批量数据插入性能提升

  • 功能需求
    1. 批量数据插入优化
    2. 提升大量数据插入效率
  • 功能实现
/** * 批量细节: * 1.url?rewriteBatchedStatements=true * 2.insert 语句必须使用 values * 3.语句后面不能添加分号; * 4.语句不能直接执行,每次需要装货 addBatch() 最后 executeBatch(); * <p> * 批量插入优化! * * @throws Exception */ @Test public void batchInsertYH() throws Exception { //1.注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2.获取连接 Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc?rewriteBatchedStatements=true", "root", "mac_root"); //3.编写SQL语句结构 String sql = "insert into t_user (account,password,nickname) values (?,?,?)"; //4.创建预编译的statement,传入SQL语句结构 /** * TODO: 第二个参数填入 1 | Statement.RETURN_GENERATED_KEYS * 告诉statement携带回数据库生成的主键! */ long start = System.currentTimeMillis(); PreparedStatement statement = connection.prepareStatement(sql); for (int i = 0; i < 10000; i++) { //5.占位符赋值 statement.setObject(1, "ergouzi" + i); statement.setObject(2, "lvdandan"); statement.setObject(3, "驴蛋蛋" + i); //6.装车 statement.addBatch(); } //发车! 批量操作! statement.executeBatch(); long end = System.currentTimeMillis(); System.out.println("消耗时间:" + (end - start)); //8.释放资源 connection.close(); }

3、 jdbc中数据库事务实现

1、事务概念

// 事务概念 数据库事务就是一种SQL语句执行的缓存机制,不会单条执行完毕就更新数据库数据,最终根据缓 存内的多条语句执行结果统一判定! 一个事务内所有语句都成功及事务成功,我们可以触发commit提交事务来结束事务,更新数据! 一个事务内任意一条语句失败,及事务失败,我们可以触发rollback回滚结束事务, 数据回到事务之前状态! 举个例子: 临近高考,你好吃懒做,偶尔还瞎花钱,父母也只会说'你等着!',待到高考完毕! 成绩600+,翻篇,庆祝! 成绩200+,翻旧账,男女混合双打! //优势 允许我们在失败情况下,数据回归到业务之前的状态! //场景 一个业务涉及多条修改数据库语句! 例如: 经典的转账案例,转账业务(加钱和减钱) 批量删除(涉及多个删除) 批量添加(涉及多个插入) // 事务特性 1. 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 2. 一致性(Consistency)事务必须使数据库从一个一致性状态变换到另外一个一致性状态。 3. 隔离性(Isolation)事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。 4. 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响 // 事务类型 自动提交 : 每条语句自动存储一个事务中,执行成功自动提交,执行失败自动回滚! (MySQL) 手动提交: 手动开启事务,添加语句,手动提交或者手动回滚即可! // sql开启事务方式 针对自动提交: 关闭自动提交即可,多条语句添加以后,最终手动提交或者回滚! (推荐) SET autocommit = off; //关闭当前连接自动事务提交方式 # 只有当前连接有效 # 编写SQL语句即可 SQL SQL SQL #手动提交或者回滚 【结束当前的事务】 COMMIT / ROLLBACK ; 手动开启事务: 开启事务代码,添加SQL语句,事务提交或者事务回滚! (不推荐) // 呼应jdbc技术 try{ connection.setAutoCommit(false); //关闭自动提交了 //注意,只要当前connection对象,进行数据库操作,都不会自动提交事务 //数据库动作! //statement - 单一的数据库动作 c u r d connection.commit(); }catch(Execption e){ connection.rollback(); }

2、建表

-- 继续在 xx_jdbc 的库中创建银行表 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、BankDao

4、BankService

package com.xx; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; /** * @Author: xueqimiao * @Date: 2023/1/4 09:49 */ public class BankService { /** * 转账业务方法 * * @param addAccount 加钱账号 * @param subAccount 减钱账号 * @param money 金额 */ public void transfer(String addAccount, String subAccount, int money) throws ClassNotFoundException, SQLException { System.out.println("addAccount = " + addAccount + ", subAccount = " + subAccount + ", money = " + money); //注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //获取连接 Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc", "root", "mac_root"); int flag = 0; //利用try代码块,调用dao try { //开启事务(关闭事务自动提交) connection.setAutoCommit(false); BankDao bankDao = new BankDao(); //调用加钱 和 减钱 bankDao.addMoney(addAccount, money, connection); System.out.println("--------------"); bankDao.subMoney(subAccount, money, connection); flag = 1; //不报错,提交事务 connection.commit(); } catch (Exception e) { //报错回滚事务 connection.rollback(); throw e; } finally { connection.close(); } if (flag == 1) { System.out.println("转账成功!"); } else { System.out.println("转账失败!"); } } }

5、BankTest

package com.xx; import org.junit.Test; /** * @Author: xueqimiao * @Date: 2023/1/4 09:48 */ public class BankTest { @Test public void testBank() throws Exception { BankService bankService = new BankService(); bankService.transfer("ergouzi", "lvdandan", 500); } }

四、Druid连接池使用

1、POM依赖

2、硬编码方式(了解,不推荐)

/** * 创建druid连接池对象,使用硬编码进行核心参数设置! * 必须参数: 账号 * 密码 * url * driverClass * 非必须参数: * 初始化个数 * 最大数量等等 不推荐设置 */ @Test public void druidHard() throws SQLException { DruidDataSource dataSource = new DruidDataSource(); //设置四个必须参数 dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUsername("root"); dataSource.setPassword("root"); dataSource.setUrl("jdbc:mysql:///xx_jdbc"); //获取连接 Connection connection = dataSource.getConnection(); // JDBC的步骤 //回收连接 connection.close(); }

3、软编码方式

  • 外部配置存放在src/druid.properties
# druid连接池需要的配置参数,key固定命名 driverClassName=com.mysql.cj.jdbc.Driver username=root password=root url=jdbc:mysql:///xx_jdbc
  • druid声明代码
/** * 不直接在java代码编写配置文件! * 利用工厂模式,传入配置文件对象,创建连接池! * @throws Exception */ @Test public void druidSoft() throws Exception { Properties properties = new Properties(); InputStream ips = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties"); properties.load(ips); DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); }

4、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,是组合关系,并非替换关系

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

1、jdbc工具类封装一

位置: src/druid.properties

# druid连接池需要的配置参数,key固定命名 driverClassName=com.mysql.cj.jdbc.Driver username=root password=root url=jdbc:mysql:///xx_jdbc
package com.xx; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; /** * @Author: xueqimiao * @Date: 2023/1/4 10:02 */ public class JDBCToolsVersion1 { private static DataSource ds; 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 { return ds.getConnection(); //这么写,不能保证同一个线程,两次getConnection()得到的是同一个Connection对象 //如果不能保证是同一个连接对象,就无法保证事务的管理 } public static void free(Connection conn) throws SQLException { conn.setAutoCommit(true); conn.close();//还给连接池 } }

2、jdbc工具类封装二

如何一个线程的不同方法获取同一个连接!

ThreadLocal的介绍:

https://blog.xueqimiao.com/juc/104a1e/

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

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

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

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

3、ThreadLocal对象.remove: 移除ThreadLocal中当前线程共享变量的值。
package com.xx; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; /** * @Author: xueqimiao * @Date: 2023/1/4 10:04 */ /* 这个工具类的作用就是用来给所有的SQL操作提供“连接”,和释放连接。 这里使用ThreadLocal的目的是为了让同一个线程,在多个地方getConnection得到的是同一个连接。 这里使用DataSource的目的是为了(1)限制服务器的连接的上限(2)连接的重用性等 */ public class JDBCTools { private static DataSource ds; private static 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 free() throws SQLException { Connection connection = tl.get(); if (connection != null) { tl.remove(); connection.setAutoCommit(true);//避免还给数据库连接池的连接不是自动提交模式(建议) connection.close(); } } }

3、封装BaseDao

基本上每一个数据表都应该有一个对应的DAO接口及其实现类,发现对所有表的操作(增、删、改、查)代码重复度很高,所以可以抽取公共代码,给这些DAO的实现类可以抽取一个公共的父类,我们称为BaseDao
package com.xx; import java.lang.reflect.Field; import java.sql.*; import java.util.ArrayList; /** * @Author: xueqimiao * @Date: 2023/1/4 10:06 */ public abstract class BaseDao { /* 通用的增、删、改的方法 String sql:sql Object... args:给sql中的?设置的值列表,可以是0~n */ protected int update(String sql, Object... args) throws SQLException { // 创建PreparedStatement对象,对sql预编译 Connection connection = JDBCTools.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 int len = ps.executeUpdate(); ps.close(); //这里检查下是否开启事务,开启不关闭连接,业务方法关闭! //没有开启事务的话,直接回收关闭即可! if (connection.getAutoCommit()) { //回收 JDBCTools.free(); } return len; } /* 通用的查询多个Javabean对象的方法,例如:多个员工对象,多个部门对象等 这里的clazz接收的是T类型的Class对象, 如果查询员工信息,clazz代表Employee.class, 如果查询部门信息,clazz代表Department.class, */ protected <T> ArrayList<T> query(Class<T> clazz, String sql, Object... args) throws Exception { // 创建PreparedStatement对象,对sql预编译 Connection connection = JDBCTools.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<>(); ResultSet res = ps.executeQuery(); /* 获取结果集的元数据对象。 元数据对象中有该结果集一共有几列、列名称是什么等信息 */ ResultSetMetaData metaData = res.getMetaData(); int columnCount = metaData.getColumnCount();//获取结果集列数 //遍历结果集ResultSet,把查询结果中的一条一条记录,变成一个一个T 对象,放到list中。 while (res.next()) { //循环一次代表有一行,代表有一个T对象 T t = clazz.newInstance();//要求这个类型必须有公共的无参构造 //把这条记录的每一个单元格的值取出来,设置到t对象对应的属性中。 for (int i = 1; i <= columnCount; i++) { //for循环一次,代表取某一行的1个单元格的值 Object value = res.getObject(i); //这个值应该是t对象的某个属性值 //获取该属性对应的Field对象 // String columnName = metaData.getColumnName(i);//获取第i列的字段名 String columnName = metaData.getColumnLabel(i);//获取第i列的字段名或字段的别名 Field field = clazz.getDeclaredField(columnName); field.setAccessible(true);//这么做可以操作private的属性 field.set(t, value); } list.add(t); } res.close(); ps.close(); //这里检查下是否开启事务,开启不关闭连接,业务方法关闭! //没有开启事务的话,直接回收关闭即可! if (connection.getAutoCommit()) { //回收 JDBCTools.free(); } return list; } protected <T> T queryBean(Class<T> clazz, String sql, Object... args) throws Exception { ArrayList<T> list = query(clazz, sql, args); if (list == null || list.size() == 0) { return null; } return list.get(0); } }

Read more

IntelliJ IDEA 接入 AI 编程助手(Copilot、DeepSeek、GPT-4o Mini)

IntelliJ IDEA 接入 AI 编程助手(Copilot、DeepSeek、GPT-4o Mini)

IntelliJ IDEA 接入 AI 编程助手(Copilot、DeepSeek、GPT-4o Mini) 📊 引言 近年来,AI 编程助手已成为开发者的高效工具,它们可以加速代码编写、优化代码结构,并提供智能提示。本文介绍如何在 IntelliJ IDEA 中集成 DeepSeek、GPT-4o Mini、GitHub Copilot,并探索 本地 AI 编程助手 方案,帮助开发者在不同场景下提升编程效率。 👨‍💻 1. GitHub Copilot 集成 Copilot 是由 GitHub 和 OpenAI 推出的 AI 代码补全工具,它可以根据上下文智能生成代码片段。 GitHub Copilot 免费版 vs 付费版对比。 功能免费版付费版代码补全每月

By Ne0inhk
OpenCode 踩坑记:GitHub Copilot 按次计费?我的账单为何暴涨 3 倍!

OpenCode 踩坑记:GitHub Copilot 按次计费?我的账单为何暴涨 3 倍!

从发现问题到深度分析,一篇文章搞懂 OpenCode + GitHub Copilot 的正确打开方式 🌟 前言:一个意外的"惊喜" 进入2026年,朋友圈和技术群里都在讨论一个新的AI开发工具 —— OpenCode,号称是 AI 编程助手的"终极形态",支持 GitHub Copilot、Claude、GPT-4 等多种模型,还能自动执行多步任务。 作为一个爱折腾的程序员,我立马下载试用。我有 GitHub Copilot 企业订阅,而且OpenCode还支持,用起来应该不花钱吧? 结果一周后,我收到了公司 IT 部门的"温馨提醒" 📧: “您的 Copilot 使用量是团队平均水平的 3 倍,请注意合理使用…” 什么情况??我明明只是让

By Ne0inhk

TRAE vs Qoder vs Cursor vs GitHub Copilot:谁才是真正的“AI 工程师”?

引言:工具选择 = 成本 + 效率 + 风险 的综合权衡 2026 年,AI 编程工具已从“玩具”走向“生产主力”。但面对 TRAE、Qoder、Cursor、GitHub Copilot 等选项,开发者不仅要问: * 它能写 Rust 吗?支持中文需求吗? * 更要问:一个月多少钱?团队用得起吗?代码安全有保障吗? 本文将从 五大核心维度 深度剖析四大主流 AI IDE: 1. 核心理念与自主性 2. 多语言与跨生态支持能力 3. 工程化与交付闭环能力 4. 中文本地化与业务适配 5. 收费模式、定价策略与企业成本 帮你做出技术可行、经济合理、风险可控的决策。 一、核心理念:

By Ne0inhk
AI编程工具对比:Cursor、GitHub Copilot与Claude Code

AI编程工具对比:Cursor、GitHub Copilot与Claude Code

文章目录 * AI编程工具对比:Cursor、GitHub Copilot与Claude Code * 一、产品定位与核心架构 * 1.1 Cursor:AI原生IDE的代表 * 1.2 GitHub Copilot:代码补全的行业标杆 * 1.3 Claude Code:终端Agent的革新者 * 二、核心功能深度对比 * 2.1 代码生成与理解能力 * 2.2 自动化与工作流集成 * 2.3 隐私与数据安全 * 三、成本效益分析 * 3.1 定价模式对比 * 3.2 投资回报比 * 四、适用场景与用户画像 * 4.1 最佳应用场景 * 4.2 用户反馈摘要 * 五、

By Ne0inhk