赞
踩
Java Database Connectivity | Java 数据库连接 技术
通过 JDBC
提供的方法 可以发送字符串
类型的 SQL语句
到DBMS
(数据库管理系统 比如 MySQL Oracle), 并且获取到语句的执行结果。
从而实现 对 数据库 数据的 CRUD
( 增删改查 )
JDBC
由 Java语言的规范(接口) 和 各个数据库厂商的实现驱动(jar) 组成
JDBC
是一种典型的面向接口
编程
mysql版本 | 推荐驱动版本 | 备注 |
mysql 5.5.x | 5.0.x | com.mysql.jdbc.Driver |
mysql 5.7.x | 5.1.x | com.mysql.jdbc.Driver |
msyql 8.x | 8.0.x | 建议: 8.0.25+省略时区设置com.mysql.cj.jdbc.Driver |
Java工程导入依赖
1. 项目创建lib文件夹
2. 导入驱动依赖jar包
3. jar包右键-添加为项目依赖
JDBC 技术组成:
为了项目代码的可移植性,可维护性,SUN公司从最初就制定了Java程序连接各种数据库的统一接口规范。这样的话,不管是连接哪一种DBMS软件,Java代码可以保持一致性。
因为各个数据库厂商的DBMS软件各有不同,那么内部如何通过sql实现增、删、改、查等管理数据,只有这个数据库厂商自己更清楚,因此把接口规范的实现交给各个数据库厂商自己实现。
java程序打包的一种压缩包格式,可以将这些jar包引入项目中,就可以使用这个java程序中类和方法以及属性了!
核心类和接口
DriverManager
获取连接;
接着建立连接;
PreparedStatement
(最常用)发送sql
语句;
若是查询操作,则对应的查询结果放在Result
中。
DriverManager
jar
注册到程序中connection
statement
和 preparedstatement
, callablestatement
对象SQL
语句到数据库管理软件的对象preparedstatement
使用为重点!API 使用路线
JDBC基本使用步骤分析(6步)
CREATE DATABASE jdbc_test;
USE jdbc_test;
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','管理员');
// @Description: 使用statement查询t_user表下,全部数据 // 1. 注册驱动 DriverManager.registerDriver(new Driver()); // 2. 建立连接 /* * java连接数据库肯定是调用某个方法,方法也需要填入数据库的基本信息: * 数据库ip地址:127.0.0.1 * 数据库端口号:3306 * 账号:root * 密码:root * 连接数据库的名称:jdbc_test * * DriverManager.getConnection的参数 * 参数1 url: * jdbc:数据库厂商名://ip地址:port/数据库名 * jdbc:mysql://127.0.0.1:3306/jdbc_test * */ //java.sql 接口 = 实现类 Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test", "root", "root"); // 3. 创建statement对象 Statement statement = connection.createStatement(); // 4. 发送sql String sql = "SELECT * 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 + "--" + account + "--" + password + "--" + nickname); } // 6. 关闭资源【先打开的后关】 resultSet.close(); statement.close(); connection.close();
需求:模拟登录 输入用户名/密码
//1.输入账号和密码 Scanner scanner = new Scanner(System.in); String account = scanner.nextLine(); String password = scanner.nextLine(); scanner.close(); //方法1:DriverManager.registerDriver(new Driver());调用两次。不用 //方法2:new Driver() 频繁修改不优雅 //方法3.使用反射 注册一次驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //获取连接 //省略写法: jdbc:mysql://localhost:3306/test = jdbc:mysql:///test Connection connection = DriverManager.getConnection("jdbc:mysql:///jdbc_test", "root", "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); //ResultSet == navicat /** * 1.需要理解ResultSet的数据结构和navicat查询出来的是一样! * 2.有一个光标指向的操作数据行,默认指向第一行的上边!我们需要移动光标,指向行,在获取列即可! * boolean = next() * false: 没有数据,也不移动了! * true: 有更多行,并且移动到下一行! * 推荐:推荐使用if 或者 while循环,嵌套next方法,循环和判断体内获取数据! * if(next()){获取列的数据!} || while(next()){获取列的数据!} * *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();
存在的问题:
利用preparedStatement解决上述案例注入攻击和SQL语句拼接问题!
public class PreparedStatementLoginPart { 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. 注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 3. 获取连接 Connection connection = DriverManager.getConnection("jdbc:mysql:///jdbc_test", "root", "root"); // 4.创建sql对象 与 sql语句 String sql = "SELECT * FROM t_user WHERE account = ? AND password = ?;" ; PreparedStatement preparedStatement = connection.prepareStatement(sql); // 占位符? 赋值 从左到右,从1开始 // 参数:int 占位符下角标 ; objects 占位符的值 preparedStatement.setObject(1,account); preparedStatement.setObject(2,password); // 5. 结果集解析 ResultSet resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { //只要向下移动,就是有数据 就是登录成功! System.out.println("登录成功"); }else{ System.out.println("登录失败"); } resultSet.close(); preparedStatement.close(); connection.close(); } }
/** * 插入一条用户数据! * 账号: test * 密码: test * 昵称: 测试 */ @Test public void testInsert() throws SQLException, ClassNotFoundException { Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/jdbc_test", "root", "root"); 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,"测试"); int rows = preparedStatement.executeUpdate(); System.out.println(rows); if(rows > 0){ System.out.println("插入成功"); }else{ System.out.println("插入失败"); } preparedStatement.close(); connection.close(); }
行 id account password nickname
行 id account password nickname
行 id account password nickname
resultSet :
有行有列 获取数据时候,一行一数据
内部有一个游标,默认指向数据的第一行之前!
利用next()
方法移动游标 ,
指向数据行,再获取行中列的数据
MeteData
getColumnLabel
先获取列的别名,没有别名用列名,用这个!
而getColumnName
只是列名
/** * 查询全部数据! * 将数据存到List<Map>中 * 一个map -> 对应一行数据 * map key -> 数据库列名或者别名 * map value -> 数据库列的值 * * 思路分析 * 1.先创建一个List<Map>集合 * 2.遍历resultSet对象的行数据 * 3.将每一行数据存储到一个map对象中! * 4.将对象存到List<Map>中 * 5.最终返回 * * 获取结果表头信息(列名和数量等信息) */ @Test public void testQueryMap() throws SQLException, ClassNotFoundException { Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/jdbc_test", "root", "root"); String sql = "SELECT id,account,password,nickname FROM t_user;"; PreparedStatement preparedStatement = connection.prepareStatement(sql); ResultSet resultSet = preparedStatement.executeQuery(); List<Map> mapList = new ArrayList<>(); //获取列信息对象 //metaData装的是当前列的信息对象(通过他可以获取列对应的下角标,或者是列的数量) ResultSetMetaData metaData = resultSet.getMetaData(); //获取列的数量 int columnCount = metaData.getColumnCount(); while (resultSet.next()){ //一行数据对应一个map HashMap map = new HashMap(); for (int i = 1; i <= columnCount; i++) { //参数:列名 与 值 //next从第一行开始:第一列 的, 第一个数值 map -> 第二列 的, 第二个数字.... 列数结束退出for 下个next开始 map.put(metaData.getColumnLabel(i),resultSet.getObject(i)); } mapList.add(map); } //[{password=123456, nickname=经理, id=1, account=root}, {password=666666, nickname=管理员, id=2, account=admin}] System.out.println(mapList); preparedStatement.close(); connection.close(); resultSet.close(); }
非常麻烦的写法:
/** * 修改一条用户数据! * 修改账号: test的用户,将nickname改为临时 */ @Test public void testUpdate() throws SQLException, ClassNotFoundException { Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test", "root", "root"); String sql = "UPDATE t_user SET nickname = ? WHERE account = ?;"; PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1,"临时"); preparedStatement.setString(2,"test"); int rows = preparedStatement.executeUpdate(); System.out.println(rows); if(rows > 0){ System.out.println("修改成功"); }else{ System.out.println("修改失败"); } preparedStatement.close(); connection.close(); }
/** * 删除一条用户数据! * 根据账号: test */ @Test public void testDelete() throws SQLException, ClassNotFoundException { Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql:///jdbc_test", "root", "root"); String sql = "DELETE FROM t_user WHERE account = ?;"; PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1,"test"); int rows = preparedStatement.executeUpdate(); System.out.println(rows); if(rows > 0){ System.out.println("删除成功"); }else{ System.out.println("删除失败"); } preparedStatement.close(); connection.close(); }
//1.注册驱动
//2.获取连接
//3.编写SQL语句
//4.创建preparedstatement并且传入SQL语句结构
//5.占位符赋值
//6.发送SQL语句,并且获取结果
//7.结果集解析
//8.关闭资源
//1.注册驱动 方案1: 调用静态方法,但是会注册两次 DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver()); 方案2: 反射触发 Class.forName("com.mysql.cj.jdbc.Driver"); //2.获取连接 Connection connection = DriverManager.getConnection(); 3 (String url,String user,String password) 2 (String url,Properties info(user password)) 1 (String url?user=账号&password=密码 ) //3.创建statement //静态 Statement statement = connection.createStatement(); //预编译 PreparedStatement preparedstatement = connection.preparedStatement(sql语句结构); //4.占位符赋值 preparedstatement.setObject(?的位置 从左到右 从1开始,值) //5.发送sql语句获取结果 int rows = executeUpdate(); //非DQL Resultset = executeQuery(); //DQL //6.查询结果集解析 //移动光标指向行数据 next(); if(next()) while(next()) //获取列的数据即可 get类型(int 列的下角标 从1开始 | int 列的label (别名或者列名)) //获取列的信息 getMetadata(); ResultsetMetaData对象 包含的就是列的信息 getColumnCount(); | getCloumnLebal(index) //7.关闭资源 close();
功能需求:
/** * @Description: 返回插入的主键! */ public class PSAboutPrimaryKey { public static void main(String[] args) throws SQLException, ClassNotFoundException { Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql:///jdbc_test?user=root&password=root"); String sql = "insert into t_user(account,password,nickname) values (?,?,?);"; // 第二个参数:告诉statement 要带回 数据库生成的主键值 PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); preparedStatement.setObject(1,"doug"); preparedStatement.setObject(2,"doug"); preparedStatement.setObject(3,"道格"); int rows = preparedStatement.executeUpdate(); System.out.println(rows); // 结果集返回一个 只有主键的表 ; 固定使用getGeneratedKeys 来获取 ResultSet resultSet = preparedStatement.getGeneratedKeys(); //从第一行之前往下移一行 resultSet.next(); // 指向第一列,获取值 int anInt = resultSet.getInt(1); System.out.println("anInt "+anInt);//anInt 4 preparedStatement.close(); connection.close(); } }
/** *改动了三处:(1)路径(2)必写values,且后面不加;(3)装货addBatch()最后executeBatch(); * 批量细节: * 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:///jdbc_test?rewriteBatchedStatements=true","root","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,"yoo"+i); statement.setObject(2,"yoo"); statement.setObject(3,"批量"+i); //6.装车 statement.addBatch(); } //发车! 批量操作! statement.executeBatch(); long end = System.currentTimeMillis(); System.out.println("消耗时间:"+(end - start)); //7.结果集解析 //8.释放资源 connection.close(); }
事务详解 见总结部分
常见的数据库连接池:
现在使用更多的是:Hikari、Druid (性能更优越)
没有使用数据库连接池:
- 客户端执行SQL语句:要先创建一个新的连接对象,然后执行SQL语句,SQL语句执行后又需要关闭连接对象从而释放资源,每次执行SQL时都需要创建连接、销毁链接,这种频繁的重复创建销毁的过程是比较耗费计算机的性能。
数据库连接池是个容器,负责分配、管理数据库连接(Connection)
允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
数据库连接池的好处:
从创建连接变成了获取连接 销毁链接变成了回收连接
建立一个连接池,这个池中可以容纳一定数量的连接对象,一开始, 我们可以先替用户先创建好一些连接对象,等用户要拿连接对象时,就直接从池中拿,不用新建了,这样也可以节省时间。然后用户用完后,放回去,别人可以接着用。
像线程池、对象池…
javax.sql.DataSource接口 :
规范了连接池获取连接的方法 规范了连接池回收连接的方法
DataSource 接口 = 第三方连接池 实现类
配置文件 : druid.properties
# druid连接池需要的配置参数,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///jdbc_test
代码实现:
import com.alibaba.druid.pool.DruidDataSourceFactory; // 记得导入druid.jar /** * @Description: * 通过外部配置文件 , 实例化druid连接池对象 * 不直接在java代码编写配置文件! * 利用工厂模式,传入配置文件对象,创建连接池! */ public class DruidUserPool { public static void main(String[] args) throws Exception { //1. 读取外部配置文件文件 Properties Properties properties = new Properties(); //2.src下的方法,可以使用类加载器提供的方法 InputStream ips = DruidUserPool.class.getClassLoader().getResourceAsStream("druid.properties"); properties.load(ips); //3. 使用连接池的工具类的工程模式,创建连接池 DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); Connection connection = dataSource.getConnection(); //这里写 数据库 CRUD connection.close(); } }
封装一个工具类,内部包含连接池对象,同时对外提供连接的方法和回收连接的方法!
var
是什么:
var
不是关键字,它相当于是一种动态类型;
var
动态类型是编译器根据变量所赋的值来推断类型;
var
没有改变Java的本质,var只是一种简便的写法,
就是说在定义局部变量时,任意什么类型都可以用var
定义变量的类型会根据所赋的值来判断。
如何使用var
:
resultset.getString()后面加.var,再按两下回车
类加载
: java文件 -> 编译 -> 【 class字节码文件 --> 类加载 --> jvm虚拟中 --> Class对象】
类加载具体步骤:
加载 【class文件转成对象加载到虚拟机中】->
连接 【验证(检查类文件) -> 准备 (静态变量赋默认值) -> 解析 (调用静态代码块) 】 ->
初始化 -> (赋真实值)
以下7种方式会触发
类加载
:
- new关键字
- 调用静态属性
- 调用静态方法
- 接口 包含1.8 新特性 default关键字
- 反射 【Class.forName() 类名.class】
- 子类调用会触发父类的静态代码块
- 触发类的入口方法main
事务:
- 数据库事务就是一种SQL语句执行的
缓存
机制,不会单条执行完毕就更新数据库数据,最终根据缓存内的多条语句执行结果统一判定!- 一个事务内所有语句都成功及事务成功,我们可以触发
commit
提交事务来结束事务,更新数据!- 一个事务内任意一条语句失败,及事务失败,我们可以触发
rollback
回滚结束事务, 数据回到事务之前状态!- 也就是:允许我们在失败情况下,数据回归到业务之前的状态!
- 使用场景: 一个业务涉及多条修改数据库语句!
例如: 经典的转账案例,转账业务(加钱和减钱)
批量删除(涉及多个删除)
批量添加(涉及多个插入)
事务特性:
原子性 (Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency)事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
隔离性(Isolation)事务的隔离性是指一个事务的执行不能被其他事务干扰,
即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响
事务类型:
SQL开启事务方式【事务都在一个连接中】
针对自动提交: 关闭自动提交即可,多条语句添加以后,最终手动提交或者回滚! (推荐)
SET autocommit = off; //关闭当前连接自动事务提交方式
# 只有当前连接有效
# 编写SQL语句即可
SQL
SQL
SQL
#手动提交或者回滚 【结束当前的事务】
COMMIT / ROLLBACK ;
手动开启事务: 开启事务代码,添加SQL语句,事务提交或者事务回滚! (不推荐)
线程本地变量:为同一个线程存储共享变量
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中当前线程共享变量的值。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。