一、JDBC 概述
1、概念
- jdbc 是 (Java Database Connectivity) 单词的缩写,翻译为 java 连接数据库
- jdbc 是 java 程序连接数据库的技术统称
- jdbc 由 java 语言的规范 (接口) 和各个数据库厂商的实现驱动 (jar) 组成
- jdbc 是一种典型的面向接口编程
- jdbc 优势
- 只需要学习 jdbc 规范接口的方法,即可操作所有的数据库软件
- 项目中期切换数据库软件,只需要更换对应的数据库驱动 jar 包,不需要更改代码
Java JDBC 核心技术,涵盖概念、核心 API(DriverManager、Connection、Statement、PreparedStatement)、基本使用步骤、SQL 注入问题及解决方案、事务管理、连接池(Druid)配置与使用,以及工具类封装方法。通过代码示例演示增删改查、批量插入、主键回显等实战场景。
各个数据库厂商提供的驱动 jar 包
因为各个数据库厂商的 DBMS 软件各有不同,那么内部如何通过 sql 实现增、删、改、查等管理数据,只有这个数据库厂商自己更清楚,因此把接口规范的实现交给各个数据库厂商自己实现。
jar 包是什么?
java 程序打成的一种压缩包格式,你可以将这些 jar 包引入你的项目中,然后你可以使用这个 java 程序中类和方法以及属性了!
jdk 下 jdbc 规范接口,存储在 java.sql 和 javax.sql 包中的 api
为了项目代码的可移植性,可维护性,SUN 公司从最初就制定了 Java 程序连接各种数据库的统一接口规范。这样的话,不管是连接哪一种 DBMS 软件,Java 代码可以保持一致性。
驱动 jar 版本选择
| mysql 版本 | 推荐驱动版本 | 备注 |
|---|---|---|
| mysql 5.5.x | 5.0.x | com.mysql.jdbc.Driver |
| mysql 5.7.x | 5.1.x | com.mysql.jdbc.Driver |
| mysql 8.x | 8.0.x | 建议:8.0.25+ 省略时区设置 com.mysql.cj.jdbc.Driver |
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
准备数据库数据
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.*;
public class JdbcBasePart {
public static void main(String[] args) throws SQLException {
//1.注册驱动
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xx_jdbc", "root", "mac_root");
//3.创建小车 Statement
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();
}
}
模拟登录,控制台输入账号和密码,判断是否登陆成功成功!
package com.xx;
import java.sql.*;
import java.util.Scanner;
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 的查询使用
//注册一次驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接
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 = '" + account + "' and password = '" + password + "';";
/**
* ResultSet 结果集对象 = executeQuery(DQL 语句)
* int 响应行数 = executeUpdate(非 DQL 语句)
*/
ResultSet resultSet = statement.executeQuery(sql);
//进行结果集对象解析
if (resultSet.next()) {
//只要向下移动,就是有数据 就是登录成功!
System.out.println("登录成功!");
} else {
System.out.println("登录失败!");
}
//关闭资源
resultSet.close();
statement.close();
connection.close();
}
}
admin 666666 登录成功!
admin 123456 'or 1='1 登录成功!
可能发生注入攻击
动态值充当了 SQL 语句结构,影响了原有的查询结果!
利用 preparedStatement 解决上述案例注入攻击和 SQL 语句拼接问题!
package com.xx;
import java.sql.*;
import java.util.Scanner;
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
//TODO 需要传入 SQL 语句结构
//TODO 要的是 SQL 语句结构,动态值的部分使用 ? , 占位符!
//TODO ? 不能加 '?' ? 只能替代值,不能替代关键字和容器名
String sql = "select * from t_user where account = ? and password = ? ;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//占位符赋值
//给占位符赋值!从左到右,从 1 开始!
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();
}
}
/**
* 插入一条用户数据!
* 账号: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();
}
/**
* 修改一条用户数据!
* 修改账号: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();
}
/**
* 删除一条用户数据!
* 根据账号: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();
}
/**
* 查询全部数据!
* 将数据存到 List<Map>中
* map -> 对应一行数据
* map key -> 数据库列名或者别名
* map value -> 数据库列的值
* 1.先创建一个 List<Map>集合
* 2.遍历 resultSet 对象的行数据
* 3.将每一行数据存储到一个 map 对象中!
* 4.将对象存到 List<Map>中
* 5.最终返回
*/
@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();
}
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();
}
/**
* 批量细节:
* 1.url?rewriteBatchedStatements=true
* 2.insert 语句必须使用 values
* 3.语句后面不能添加分号;
* 4.语句不能直接执行,每次需要装货 addBatch() 最后 executeBatch();
*
* @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 语句结构
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();
}
// 事务概念 数据库事务就是一种 SQL 语句执行的缓存机制,不会单条执行完毕就更新数据库数据,最终根据缓 存内的多条语句执行结果统一判定!一个事务内所有语句都成功及事务成功,我们可以触发 commit 提交事务来结束事务,更新数据!一个事务内任意一条语句失败,及事务失败,我们可以触发 rollback 回滚结束事务,数据回到事务之前状态!
// 优势 允许我们在失败情况下,数据回归到业务之前的状态!
// 场景 一个业务涉及多条修改数据库语句!例如:经典的转账案例,转账业务 (加钱和减钱) 批量删除 (涉及多个删除) 批量添加 (涉及多个插入)
// 事务特性
// 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(Exception e){
connection.rollback();
}
-- 继续在 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);
package com.xx;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
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("转账失败!");
}
}
}
package com.xx;
import org.junit.Test;
public class BankTest {
@Test
public void testBank() throws Exception {
BankService bankService = new BankService();
bankService.transfer("ergouzi", "lvdandan", 500);
}
}
/**
* 创建 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();
}
# druid 连接池需要的配置参数,key 固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///xx_jdbc
/**
* 不直接在 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);
}
| 配置 | 缺省 | 说明 |
|---|---|---|
| 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(建议配置下) | |
| initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用 init 方法,或者第一次 getConnection 时 |
| maxActive | 8 | 最大连接池数量 |
| maxIdle | 8 | 已经不再使用,配置了也没效果 |
| minIdle | 最小连接池数量 | |
| maxWait | 获取连接时最大等待时间,单位毫秒。配置了 maxWait 之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置 useUnfairLock 属性为 true 使用非公平锁。 | |
| poolPreparedStatements | false | 是否缓存 preparedStatement,也就是 PSCache。PSCache 对支持游标的数据库性能提升巨大,比如说 oracle。在 mysql 下建议关闭。 |
| maxOpenPreparedStatements | -1 | 要启用 PSCache,必须配置大于 0,当大于 0 时,poolPreparedStatements 自动触发修改为 true。在 Druid 中,不会存在 Oracle 下 PSCache 占用内存过多的问题,可以把这个数值配置大一些,比如说 100 |
| validationQuery | 用来检测连接是否有效的 sql,要求是一个查询语句。如果 validationQuery 为 null,testOnBorrow、testOnReturn、testWhileIdle 都不会其作用。 | |
| testOnBorrow | true | 申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。 |
| testOnReturn | false | 归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能 |
| testWhileIdle | false | 建议配置为 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,是组合关系,并非替换关系 |
位置: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;
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();//还给连接池
}
}
如何一个线程的不同方法获取同一个连接!
ThreadLocal 的介绍: 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;
/* 这个工具类的作用就是用来给所有的 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();
}
}
}
基本上每一个数据表都应该有一个对应的 DAO 接口及其实现类,发现对所有表的操作(增、删、改、查)代码重复度很高,所以可以抽取公共代码,给这些 DAO 的实现类可以抽取一个公共的父类,我们称为
BaseDao
package com.xx;
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 {
// 创建 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);
}
}

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online