JDBC
准备动作
-
问题1:你知道的存储数据的方式有哪些?
- 变量,数组,集合:只能对数据进行临时性存储,程序执行结束后,数据就丢失了.
- IO流:可以把数据进行永久存储,但是不方便用户进行精细化管理.
- 数据库: 指的就是存储数据的仓库, 本质就是一个文件系统, 可以有规律的对数据存储, 方便用户进行CURD.
- C:(Create: 增), U:(Update: 改), R: (Read: 查), D: (Delete:删)
- 数据库才是实际开发中, 我们真正存储数据的地方.
-
问题2:我们常说的数据库指的是数据库管理系统(DBMS, DataBase Management System), 那么数据库和数据库管理系统之间有什么关系?
- 数据库管理系统(DBMS):
- 可以用来管理多个 数据库, 实现数据库的CURD等操作.
- 类似于: IDEA, 它就是一个工具, 软件, 可以创建多个 项目.
- 数据库:这个才是真正存储数据表的仓库, 类似于: Java中的项目.
- 数据表:这个才是真正存储数据的地方, 类似于: Java中的类.
- 表数据:一条表数据其实就对应一个JavaBean对象.
- 数据库管理系统(DBMS):
-
问题3:我们常说的DBMS大多数都是关系型数据库,那什么是关系型数据库?
- 所谓的关系型数据库:指的是数据表与数据表之间的关系,例如:有一对一,一对多,多对多等…
- 补充:非关系型数据库:指的是数据表与数据表之间没有关系,甚至大多数的非关系型数据库连数据表的概念都没有,采用行列,键值对的方式存储数据.
-
问题4:常见的关系型数据库有哪些?
- 关系型:MySQL,Oracle,SQLServer,DB2,SyBase,SQLite…
- 非关系型:MongoDB,Redis,HBase
-
在windows系统中安装MySQL软件
- 安装
- 安装之前最好关闭防火墙.
- 傻瓜式安装, 下一步下一步即可, 详见安装文档.
- 安装路径最好不要出现中文, 空格, 特殊符号等.
- 最好不要直接安装到盘符目录下.
- 卸载:
- 卸载之前, 先去备份你的数据.
- 从控制面板卸载.
- 用CCleaner软件清理注册表.
- 登陆:
- 方式1: 明文.
windows徽标键 + 字母R -> cmd -> mysql -uroot -p密码 -> 回车 - 方式2: 暗文.
windows徽标键 + 字母R -> cmd -> mysql -u root -p -> 回车 -> 输入密码 -> 回车
- 方式1: 明文.
- 两个小问题:
- 问题1: 10061, MySQL服务没开, 开启此服务即可.
- 方式1: windows徽标键 + 字母R -> services.msc -> 打开服务列表, 找到mysql服务, 手动开启.
- 方式2: dos指令实现
- net start mysql服务名
- net stop mysql服务名
- 问题2: using password yes, 说明用户名或者密码错误.
- 问题1: 10061, MySQL服务没开, 开启此服务即可.
- 安装
-
MySQL的可视化工具: SQLYog
- 它类似于你们前边的 DataGrip, 但是DataGrip是重量级的软件, 依赖比较多, 效率相对较慢, 所以我们用: SQLYog.
JDBC相关知识
-
概述:
- 全称叫Java DataBase Connectivity, 即: Java数据库连接, 就是通过Java代码操作数据库的技术. 本质就是一坨类和接口.
-
回顾:
-
集合: 就是Java用来存储多个数据的容器, 本质就是一坨类和接口.
- 接口: Collection, Map, List, Set
- 类: ArrayList, HashSet, HashMap
-
IO流: 就是Java用来进行数据传输的技术, 本质就是一坨类和接口.
- 接口: Serializable
- 类: Reader, Writer, InputStream, OutputStream
-
-
驱动:就是设备与设备之间通信的桥梁.
-
原理:
- 大白话:
- 由Sun公司提供统一规范(就是一些类和接口, 就是JDBC规范), 具体的体现和实现交给各个数据库厂商来实现.
- 详细版:
- 我们知道, 不同的数据库, 生厂商也不同, 导致驱动规范也不尽相同. 例如: 我们要使用MySQL数据库, 就要安装MySQL的驱动,我们要装Oracle数据库, 就要装Oracle驱动, 因为各个数据库厂商驱动规范不同, 就导致我们要: 用什么数据库,就要学习对应的驱动,
- 本身Java已经提供了大量的API供我们学习, 我们再额外记忆大量的驱动类, 就会导致学习Java难度增加, 针对于这个情况,
- Sun公司就和各个数据库厂商协商规定, 由Sun公司提供统一的规则(规范), 具体的实现交给各个数据库厂商来做.
- 我们不需要关心具体的类是什么, 只要关心Sun公司提供的规范怎么用即可, 这就是: 面向接口编程思想
- 大白话:
JDBC的作用:
- 连接数据库.
- 向数据库发送指令(SQL语句), 获取结果集.
- 操作结果集.
JDBC的核心步骤
0. 导入驱动, 其实就是导入jar包, 只要做一次就行.
1. 注册驱动.
2. 获取连接对象.
3. 根据连接对象, 获取可以执行SQL语句的对象.
4. 执行SQL语句, 获取结果集.
5. 操作结果集.
6. 释放资源.
JDBC的案例:
1. 快速入门.
2. JDBC相关API详解.
3. JDBC的CURD操作.
4. JDBCUtils工具类的定义和使用.
5. SQL注入攻击问题演示.
6. PreparedStatement预编译功能解决SQL注入攻击问题.
7. PreparedStatement的CURD操作.
8. 数据库连接池入门.
9. C3P0Utils工具类的定义和使用.
10. JDBC操作事务.
JDBC入门案例
package com.itheima.jdbc;
import com.mysql.jdbc.Driver;
import java.sql.*;
public class Demo01 {
public static void main(String[] args) throws Exception {
//1. 注册驱动.
DriverManager.registerDriver(new Driver());
//2. 获取连接对象.
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day16", "root", "root");
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "select * from users;";
ResultSet rs = stat.executeQuery(sql);
//5. 操作结果集.
while (rs.next()) {
int uid = rs.getInt("uid");
String username = rs.getString("username");
String password = rs.getString("password");
System.out.println(uid + "," + username + "," + password);
}
//6. 释放资源.
conn.close();
stat.close();
rs.close();
}
}
DriverManager类
-
概述:它表示 驱动管理者类, 主要作用是: 注册驱动 以及 获取连接对象.
-
作用(成员方法):
- public static void registerDriver(Driver d);
- 注册驱动, 连接什么数据库, 就注册谁的驱动, 但是这个方法实际开发不用, 因为该方式会导致驱动注册两次, 具体源码分析如下:
-
com.mysql.jdbc.Driver:
- 数据库厂商提供的具体驱动类
-
java.sql.Driver:
-
Sun公司提供的JDBC的规范, Driver接口: 表示驱动, 所有的驱动类都得实现它, 这个叫: 面向接口编程.
-
public class com.mysql.jdbc.Driver implements java.sql.Driver { static { //静态代码块, 一般用来注册驱动的, 这里已经把驱动注册了. DriverManager.registerDriver(new Driver()); } }
-
public static Connection getConnection(String url, String username, String password);
-
获取连接对象, 三个参数分别是: 数据库连接字符串, 要连接的数据库的账号, 要连接的数据库的密码.
-
数据库连接字符串解释:
-
格式:协议:子协议://要连接的主机的ip:端口号/具体的要连接的数据库
-
例如:jdbc:mysql://127.0.0.1:3306/day16
-
如果是操作本机, 则可以改为:
- jdbc:mysql://127.0.0.1:3306/day16
- jdbc:mysql://localhost:3306/day16
- jdbc:mysql:**///**day16
-
-
DriverManager类详解
package com.itheima.jdbc;
import com.mysql.jdbc.Driver;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class Demo02 {
public static void main(String[] args) throws Exception {
//1. 注册驱动.
//DriverManager.registerDriver(new Driver());
//反射的方式,加载字节码文件进内存
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接对象.
//Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day16", "root", "root");
//Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/day16", "root", "root");
Connection conn = DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "select * from users;";
ResultSet rs = stat.executeQuery(sql);
//5. 操作结果集.
while (rs.next()) {
int uid = rs.getInt("uid");
String username = rs.getString("username");
String password = rs.getString("password");
System.out.println(uid + "," + username + "," + password);
}
//6. 释放资源.
conn.close();
stat.close();
rs.close();
}
}
Connection接口
- 概述:表示连接对象, 负责Java和数据库的连接的, 也可以获取: 可以执行SQL语句的对象, 以及进行 事务管理
- 作用(成员方法):
- 获取可以执行SQL语句的对象.
- public Statement createStatement();
- 获取可以执行SQL语句的对象, 不具有预编译功能, 不能解决SQL注入攻击问题.
- public PreparedStatement prepareStatement(String sql);
- 获取可以执行SQL语句的对象, 具有预编译功能, 能解决SQL注入攻击问题.
- PreparedStatement接口是 Statement接口的 子接口.
- public Statement createStatement();
- 事务管理
- public void setTransactionIsolation(int level); 设置事物的隔离级别
- public void commit(); 提交事务
- public void rollback(); 事务回滚
- public void setAutoCommit(boolean flag); 开启事务
- 获取可以执行SQL语句的对象.
Statement接口
- 概述:表示可以执行SQL语句的对象, 主要功能有: 执行SQL语句, 获取结果集; 进行批处理管理.
- 作用(成员方法):
- 获取可以执行SQL语句的对象.
- public int executeUpdate(String sql);
- 执行SQL语句, 获取结果集, 执行的是: 更新语句, 结果集是 受影响的行数
- public ResultSet executeQuery(String sql);
- 执行SQL语句, 获取结果集, 执行的是: 查询语句, 结果集是 虚拟的一张表
- public int executeUpdate(String sql);
- 批处理 注意: 批处理只针对于 更新语句(增删改) 有效
- public void addBatch(String sql); 添加批处理
- public int[] executeBatch(); 执行批处理, 获取结果集
- public void clearBatch(); 清空批处理
- 获取可以执行SQL语句的对象.
ResultSet接口
- 概述:表示 执行查询语句后的结果集对象, 可以把它看做一张虚拟的表.
- 作用(成员方法):
- 获取可以执行SQL语句的对象.
- public boolean next();
- 判断结果集中是否有下一条数据, 类似于: Iterator#hasNext()方法.
- public Xxx getXxx(String columnName);
- 根据列名, 获取该列的信息, 掌握, 因为它更通用.
- public Xxx getXxx(int columnIndex);
- 根据列的编号, 获取该列的信息, 编号从 1 开始.
- public boolean next();
- 获取可以执行SQL语句的对象.
ResultSet接口详解
package com.itheima.jdbc;
import com.mysql.jdbc.Driver;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class Demo02 {
public static void main(String[] args) throws Exception {
//1. 注册驱动.
//反射的方式,加载字节码文件进内存
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接对象.
Connection conn = DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "select uid, username, password from users;";
//String sql = "select username, uid, password from users;";
ResultSet rs = stat.executeQuery(sql);
//5. 操作结果集.
while (rs.next()) {
//根据列名获取元素
/*int uid = rs.getInt("uid");
String username = rs.getString("username");
String password = rs.getString("password");*/
//根据列的编号获取
int uid = rs.getInt(1);
String username = rs.getString(2);
String password = rs.getString(3);
System.out.println(uid + "," + username + "," + password);
}
//6. 释放资源.
conn.close();
stat.close();
rs.close();
}
}
JDBC的CURD操作
package com.itheima.jdbc;
import org.junit.Test;
import java.sql.*;
public class Demo03 {
//JDBC的 查 的动作
@Test
public void show1() throws Exception {
//1. 注册驱动.
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接对象.
Connection coon = DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = coon.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "select * from users;";
ResultSet rs = stat.executeQuery(sql);
//5. 操作结果集.
while (rs.next()) {
System.out.println(rs.getInt("uid") + "," + rs.getString("username") + "," + rs.getString("password"));
}
//6. 释放资源.
coon.close();
stat.close();
rs.close();
}
//JDBC的 增 的动作
@Test
public void show2() throws Exception {
//1. 注册驱动.
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接对象.
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/day16", "root", "root");
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "insert into users values(null,'admin_04','pw4444');";
int i = stat.executeUpdate(sql);
//5. 操作结果集.
System.out.println(i > 0 ? "添加成功" : "添加失败");
//6. 释放资源.
conn.close();
stat.close();
}
//JDBC的 改 的动作
@Test
public void show3() throws Exception {
//1. 注册驱动.
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接对象.
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day16", "root", "root");
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "update users set username = '刘亦菲' , password = 'pw_lyf' where uid = 4 ;";
int i = stat.executeUpdate(sql);
//5. 操作结果集.
System.out.println(i>0?"修改成功":"修改失败");
//6. 释放资源
conn.close();
stat.close();
}
//JDBC的 删 的动作
@Test
public void show4() throws Exception {
//1. 注册驱动.
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接对象.
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day16", "root", "root");
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "delete from users where username = '刘亦菲';";
int i = stat.executeUpdate(sql);
//5. 操作结果集.
System.out.println(i>0?"删除成功":"删除失败");
//6. 释放资源
conn.close();
stat.close();
}
}
JDBCUtils工具类的CURD操作
JDBCUtils类
package com.itheima.jdbc_utils;
import java.sql.*;
//自定义的JDBCUtils工具类,用来操作:JDBC代码的
public class JDBCUtils {
//1.构造方法私有化
private JDBCUtils() {
}
//成员都是静态的
//2.通过静态代码块的方式注册驱动
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//3.定义方法,用来获取连接对象
public static Connection getConnection() {
try {
return DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//如果有问题,返回null
return null;
}
//4.释放资源
public static void release(Connection conn, Statement stat, ResultSet rs) {
try {
if (rs != null) {
rs.close();
rs = null; //GC会优先回收null对象
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
if (stat != null) {
stat.close();
stat = null;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
conn = null;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}
测试类
package com.itheima.jdbc;
import com.itheima.jdbc_utils.JDBCUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class Demo04 {
//JDBC的 查 的动作
@Test
public void show1() throws Exception {
//1. 注册驱动.
//2. 获取连接对象.
Connection coon = JDBCUtils.getConnection();
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = coon.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "select * from users;";
ResultSet rs = stat.executeQuery(sql);
//5. 操作结果集.
while (rs.next()) {
System.out.println(rs.getInt("uid") + "," + rs.getString("username") + "," + rs.getString("password"));
}
//6. 释放资源.
JDBCUtils.release(coon, stat, rs);
}
//JDBC的 增 的动作
@Test
public void show2() throws Exception {
//1. 注册驱动.
//2. 获取连接对象.
Connection conn = JDBCUtils.getConnection();
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "insert into users values(null,'admin_06','pw4666');";
int i = stat.executeUpdate(sql);
//5. 操作结果集.
System.out.println(i > 0 ? "添加成功" : "添加失败");
//6. 释放资源.
JDBCUtils.release(conn, stat, null);
}
//JDBC的 改 的动作
@Test
public void show3() throws Exception {
//1. 注册驱动.
//2. 获取连接对象.
Connection conn = JDBCUtils.getConnection();
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "update users set username = '刘亦菲' , password = 'pw_lyf' where uid = 5 ;";
int i = stat.executeUpdate(sql);
//5. 操作结果集.
System.out.println(i > 0 ? "修改成功" : "修改失败");
//6. 释放资源
JDBCUtils.release(conn, stat, null);
}
//JDBC的 删 的动作
@Test
public void show4() throws Exception {
//1. 注册驱动.
//2. 获取连接对象.
Connection conn = JDBCUtils.getConnection();
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "delete from users where username = '刘亦菲';";
int i = stat.executeUpdate(sql);
//5. 操作结果集.
System.out.println(i > 0 ? "删除成功" : "删除失败");
//6. 释放资源
JDBCUtils.release(conn, stat, null);
}
}
SQL注入攻击
- 概述:
- 所谓的SQL注入攻击,指的是SQL语句中的部分内容是要求用户键盘录入的,此时如果用户录入一些非法值,从而改变了我们SQL语句的结构,就有可能引发一系列的安全问题,这个问题就叫:SQL注入攻击,个别人会叫"猪肉攻击".
案例:
模拟用户登陆.
"标准"的SQL语句:
select * from users where username = '用户录入的账号' and password = '用户录入的密码';
听话的用户:
账号: 刘亦菲
密码: pw11112
"小屌丝"用户:
账号: sdkfsd
密码: skdfs ' or '1=1
"老屌丝"用户:
账号: sldfs
密码: sdkfs '; drop database day17; '
-
产生原因:
- 之所以会产生SQl注入攻击问题,原因是因为用户录入的非法值或者特殊符号被我们的SQL语句给识别成关键字或者特殊符号了, 从而改变了我们SQL语句的结构, 引发的问题.
-
解决思路:
- 预先对SQL语句进行 预编译, 即: 在此时已经决定了SQL语句中各部分的内容(即: SQL语句的结构), 把一会儿需要用户录入的地方先用 占位符 处理.
- 预编译之后, 不管用户录入什么数据, 我们都只是当做普通的字符串处理.
- SQL语句的预编译功能, 我们可以通过 PreparedStatement接口实现
SQL注入攻击问题_代码演示
package com.itheima.jdbc;
import com.itheima.jdbc_utils.JDBCUtils;
import java.sql.*;
import java.util.Scanner;
public class Demo05 {
public static void main(String[] args) throws Exception {
//1. 提示用户录入账号和密码,并接收
Scanner sc = new Scanner(System.in);
System.out.println("请输入您的账号:");
String uname = sc.nextLine();
System.out.println("请输入您的密码:");
String pwd = sc.nextLine();
//2. 获取连接对象.
Connection conn = JDBCUtils.getConnection();
//3. 根据连接对象, 获取可以执行SQL语句的对象.
String sql = "select * from users where username = ? and password = ? ;";
PreparedStatement ps = conn.prepareStatement(sql);
//4. 执行SQL语句, 获取结果集.
//给占位符设置值
ps.setString(1,uname); //qweretw
ps.setString(2,pwd); //dasd'or'1=1 登录成功
//执行sql语句
ResultSet rs = ps.executeQuery();
//5. 操作结果集.
System.out.println(rs.next() ? "登录成功" : "登录失败");
//6. 释放资源.
JDBCUtils.release(conn, ps, rs);
}
}
prepareStatement的CURD操作
package com.itheima.jdbc;
import com.itheima.jdbc_utils.JDBCUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class Deno06 {
//查
@Test
public void show1() throws Exception {
//1. 获取连接对象.
Connection conn = JDBCUtils.getConnection();
//2. 根据连接对象, 获取可以执行SQL语句的对象.
String sql = "select * from users where uid = ?;";
PreparedStatement ps = conn.prepareStatement(sql);
//3. 执行SQL语句, 获取结果集.
ps.setInt(1, 4);
ResultSet rs = ps.executeQuery();
//4. 操作结果集.
while (rs.next()) {
System.out.println("uid" + "," + rs.getString("username") + "," + rs.getString("password"));
}
//5. 释放资源.
JDBCUtils.release(conn, ps, rs);
}
//增
@Test
public void show2() throws Exception {
//1. 获取连接对象.
Connection coon = JDBCUtils.getConnection();
//2. 根据连接对象, 获取可以执行SQL语句的对象.
String sql = "insert into users values(null,?,?)";
PreparedStatement ps = coon.prepareStatement(sql);
//3. 执行SQL语句, 获取结果集.
ps.setString(1, "李四");
ps.setString(2, "pw_465465");
int i = ps.executeUpdate();
//4. 操作结果集.
System.out.println(i > 0 ? "添加成功" : "添加失败");
//5. 释放资源.
JDBCUtils.release(coon, ps, null);
}
//改
@Test
public void show3() throws Exception {
//1. 获取连接对象.
Connection conn = JDBCUtils.getConnection();
//2. 根据连接对象, 获取可以执行SQL语句的对象.
String sql = "update users set username = ? where uid = ?;";
PreparedStatement ps = conn.prepareStatement(sql);
//3. 执行SQL语句, 获取结果集.
ps.setString(1, "佟丽娅");
ps.setInt(2, 4);
int i = ps.executeUpdate();
//4. 操作结果集.
System.out.println(i > 0 ? "修改成功" : "修改失败");
//5. 释放资源.
JDBCUtils.release(conn, ps, null);
}
//删
@Test
public void show4() throws Exception {
//1. 获取连接对象.
Connection conn = JDBCUtils.getConnection();
//2. 根据连接对象, 获取可以执行SQL语句的对象.
String sql = "delete from users where uid = ?;";
PreparedStatement ps = conn.prepareStatement(sql);
//3. 执行SQL语句, 获取结果集.
ps.setInt(1, 3);
int i = ps.executeUpdate();
//4. 操作结果集.
System.out.println(1 > 0 ? "删除成功" : "删除失败");
//5. 释放资源.
JDBCUtils.release(conn, ps, null);
}
}
数据库连接池
简介:
-
概述:
- 实际开发中,当我们需要使用大量生命周期短的连接对象时,每次频繁的创建和销毁是非常消耗系统资源的,针对于这种情况。
- 我们可以创建一个池子出来,里边放写一些连接对象,用的时候从里边拿,用完之后再放回去,这样的好处是:节约资源,提高效率,而这个池就叫:数据库连接池,DataBase,Connection pool(简称:DBCP)
-
分类:
- DBCP:属于Apache软件基金组织,不能自动回收空闲连接.
- C3P0:属于Apache软件基金组织,能自动回收空闲连接.
细节:JAVA框架的底层部分只要涉及到数据库连接池了,用的都是 C3P0 - Druid:属于阿里巴巴,中文名叫:德鲁伊,采用分布式事务的方式实现的,能够更好的兼容分布式框架.
数据库连接池的 操作步骤:C3P0为例
- 导入c3po-config.xml文件,该文件必须放到:src目录下,因为C3P0数据库连接池对象会自动读取改配置文件的信息.
- 导入C3P0的jar包,最短的那个(c3p0-0.9.1.2.jar)
- 创建数据库连接池对象.
方式1:读取配置文件中,默认标签的信息.
方式2:读取配置文件中,指定标签的信息. - 从数据库连接池中获取连接对象
- 关闭连接对象,即:Connection#close()方法,comm.colse(),解释如下:
- 虽然都是调用conn.close(),来 “关闭” 连接对象,但是:
- 如果连接对象是我们自己创建的,调用conn.close(),
- 就是:销毁连接对象.
- 如果连接对象是我们从数据库连接池中获取的,调用conn.close(),
就是:自动归还连接对象到数据库连接池中,因为连接池已经重写了Connection#close()方法.
- 如果连接对象是我们自己创建的,调用conn.close(),
- 虽然都是调用conn.close(),来 “关闭” 连接对象,但是:
数据库连接池入门
package com.itheima.jdbc;
import com.itheima.jdbc_utils.JDBCUtils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class Demo07 {
public static void main(String[] args) throws Exception {
//查询uid为1的账号信息
//1. 获取连接对象.
//方式1:自己创建,这个连接对象是我们自己创建的,调用conn.close()方法的时候,就是:销毁连接对象
//Connection conn = JDBCUtils.getConnection();
//方式2:从数据库连接池中获取
//创建数据库连接池,读取:默认参数信息,即:空参构造
//ComboPooledDataSource cpds = new ComboPooledDataSource();
//创建数据库连接池,读取:指定参数信息,即:带参构造
ComboPooledDataSource cpds = new ComboPooledDataSource("otherc3p0");
//从数据库连接池中获取连接对象
Connection conn = cpds.getConnection();
//2. 根据连接对象, 获取可以执行SQL语句的对象.
String sql = "select * from users;";
PreparedStatement ps = conn.prepareStatement(sql);
//3. 执行SQL语句, 获取结果集.
ResultSet rs = ps.executeQuery();
//4. 操作结果集.
while (rs.next()) {
System.out.println(rs.getInt("uid") + "," + rs.getString("username") + "," + rs.getString("password"));
}
//5. 释放资源.
JDBCUtils.release(conn, ps, rs);
/* 虽然都是自己conn.close(),但是如果是自己创建的连接对象,就是:销毁,
如果是从池子中获取的,就是:自动归还 */
}
}
-
问题:
- 虽然我们目前已经实现了数据库连接池的使用, 但是这种方式的效率并不高, 因为目前的数据库连接池对象没有实现共享,
- 即: 在这里我们创建了一个数据库连接池, 内置5个连接对象, 但是我们就用1个, 其它4个就浪费掉了, 这样做, 还没有之前的方式效率高, 如何解决?
-
答案:
- 把C3P0数据库连接池定义成共享的, 即: 自定义C3P0Utils工具类.
C9P0Utils工具类的定义和使用
package com.itheima.jdbc;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
//自定义的C3P0Utils工具类
public class C3P0Utils {
//1.构造方法私有化
private C3P0Utils() {
}
//成员都是静态的
//2.定义一个私有的,静态的成员常量表示数据库连接池
//DataSource:是JDBC指定的规范,即所有的数据库连接池对象都是它的子类
public static final DataSource ds = new ComboPooledDataSource();
//3.对外提供方法,用来获取连接对象
public static DataSource getDataSource(){
return ds;
}
//4.对外提供方法,用来获取连接对象
public static Connection getConnection(){
try {
return ds.getConnection();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
//5.释放资源
public static void release(Connection conn, Statement stat, ResultSet rs) {
try {
if (rs != null) {
rs.close();
rs = null; //GC会优先回收null对象
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
if (stat != null) {
stat.close();
stat = null;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
conn = null;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}
package com.itheima.jdbc;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class Demo06 {
//查
@Test
public void show1() throws Exception {
//1. 获取连接对象.
//方式1:自己创建
//Connection conn = JDBCUtils.getConnection();
//方式2:先获取数据库连接池,再从池子中获取连接对象
/*DataSource ds = C3P0Utils.getDataSource();
Connection conn = ds.getConnection();*/
//方式3:直接从数据库连接池中获取连接对象
Connection conn = C3P0Utils.getConnection();
//2. 根据连接对象, 获取可以执行SQL语句的对象.
String sql = "select * from users where uid = ?;";
PreparedStatement ps = conn.prepareStatement(sql);
//3. 执行SQL语句, 获取结果集.
ps.setInt(1, 1);
ResultSet rs = ps.executeQuery();
//4. 操作结果集.
while (rs.next()) {
System.out.println("uid" + "," + rs.getString("username") + "," + rs.getString("password"));
}
//5. 释放资源.
C3P0Utils.release(conn, ps, rs);
}
//增
@Test
public void show2() throws Exception {
//1. 获取连接对象.
Connection coon = C3P0Utils.getConnection();
//2. 根据连接对象, 获取可以执行SQL语句的对象.
String sql = "insert into users values(null,?,?)";
PreparedStatement ps = coon.prepareStatement(sql);
//3. 执行SQL语句, 获取结果集.
ps.setString(1, "李四");
ps.setString(2, "pw_465465");
int i = ps.executeUpdate();
//4. 操作结果集.
System.out.println(i > 0 ? "添加成功" : "添加失败");
//5. 释放资源.
C3P0Utils.release(coon, ps, null);
}
//改
@Test
public void show3() throws Exception {
//1. 获取连接对象.
Connection conn = C3P0Utils.getConnection();
//2. 根据连接对象, 获取可以执行SQL语句的对象.
String sql = "update users set username = ? where uid = ?;";
PreparedStatement ps = conn.prepareStatement(sql);
//3. 执行SQL语句, 获取结果集.
ps.setString(1, "佟丽娅");
ps.setInt(2, 4);
int i = ps.executeUpdate();
//4. 操作结果集.
System.out.println(i > 0 ? "修改成功" : "修改失败");
//5. 释放资源.
C3P0Utils.release(conn, ps, null);
}
//删
@Test
public void show4() throws Exception {
//1. 获取连接对象.
Connection conn = C3P0Utils.getConnection();
//2. 根据连接对象, 获取可以执行SQL语句的对象.
String sql = "delete from users where uid = ?;";
PreparedStatement ps = conn.prepareStatement(sql);
//3. 执行SQL语句, 获取结果集.
ps.setInt(1, 3);
int i = ps.executeUpdate();
//4. 操作结果集.
System.out.println(1 > 0 ? "删除成功" : "删除失败");
//5. 释放资源.
C3P0Utils.release(conn, ps, null);
}
}
事务
简介:
-
概述:事务指的是逻辑上的一组操作,组成该操作的各个逻辑单元,要么全部执行成功,要么全部执行失败.
- 大白话理解:同生共死.
-
特点:
- 原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位.事务中的操作要么都发生,要么都不发生.
- 一致性(Consistency) 事务前后 数据的完整性必须保持一致.
- 隔离性(Isolation) 事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其他用的的事务所干扰,多个并发事务之间数据要相互隔离.
- 持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响.
-
如果不考虑隔离性,可能发生如下的问题:
- 关于读:
- 脏读:也叫:读-未提交
指的是一个事务读取到了另一个事务还没有来得及提交的数据,导致多次查询结果不一致. - 不可重复读:也叫:读-已提交(修改)
指的是一个事务读取到了另一个事务已经提交的(修改)数据,导致多次查询结果一致. - 虚读:也叫:读-已提交(插入)
指的是一个事务读取到了另一个事务已经提交的(插入)数据,导致多次查询结果不一致.
- 脏读:也叫:读-未提交
- 关于写:丢失更新.
- 关于读:
-
所谓的考虑隔离性, 就是所谓的 隔离级别, 具体如下:
隔离级别从小到大分别是:
read uncommitted < read committed < repeatable read < serializable
安全级别从小到大分别是:
read uncommitted < read committed < repeatable read < serializable
效率从大到小分别是:
read uncommitted > read committed > repeatable read > serializable
- 隔离级别详解:
read uncommitted:
会发生问题: 3个, 脏读, 不可重复读, 虚读.
已解决问题: 0个.
read committed:
会发生问题: 2个, 不可重复读, 虚读.
已解决问题: 1个, 脏读.
repeatable read:
会发生问题: 1个, 虚读.
已解决问题: 2个, 脏读, 不可重复读.
serializable:
会发生问题: 0个.
已解决问题: 3个, 脏读, 不可重复读, 虚读.
- 细节:
- MySQL 和 Oracle的区别如下:
- MySQL:默认隔离级别是 repeatable read, 且默认开启了事务的提交功能, 每一个SQL语句都是一个单独的事务, 端口号是: 3306
- Oracle:默认隔离级别是: read committed, 没有开启事务的自动提交功能, 需要手动设置. 端口号是1521
- SQL语句中, 关于事务的SQL语句如下:
show variables like ‘%auto%’ 查看是否开启事务的自动提交功能.
select @@tx_isolation; 查看事务的隔离级别
begin 开启事务
start transaction 开启事务
commit 提交事务, 即: 把执行后的结果持久性的写到数据表中.
rollback 事务回滚, 即: 把数据还原到事务还没有执行之前的状态.
set session transaction isolation level 隔离级别; 临时的设置事务的隔离级别为指定的级别.
JDBC操作事务,模拟转账
package com.itheima.jdbc;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class Demo08 {
public static void main(String[] args) {
//需求:张三 -> 李四 转 1000
//方式1: 不采用事务
//method01();
//方式2: 采用事务
Connection conn = null;
Statement stat = null;
try {
//1. 获取连接对象.
conn = C3P0Utils.getConnection();
//2. 根据连接对象, 获取可以执行SQL语句的对象.
stat = conn.createStatement();
//3. 执行SQL语句, 获取结果集.
String sql1 = "update account set money = money - 1000 where aid = 1;";
String sql2 = "update account set money = money + 1000 where aid = 2;";
//开启事务,关闭事务的自动提交功能,相当于开启事务,什么时候我提交或者回滚事务了,说明事务结束
conn.setAutoCommit(false);
//扣钱的动作
int a = stat.executeUpdate(sql1);
//System.out.println(1 / 0);
//价钱的动作
int b = stat.executeUpdate(sql2);
//4. 操作结果集.
if (a == b && a == 1) {
conn.commit(); //提交事务
System.out.println("转账成功");
}
} catch (Exception e) {
try {
conn.rollback(); //事务回滚
} catch (SQLException throwables) {
throwables.printStackTrace();
}
System.out.println("转账失败");
} finally {
//5. 释放资源.
C3P0Utils.release(conn, stat, null);
}
}
private static void method01() {
Connection conn = null;
Statement stat = null;
try {
//1. 获取连接对象.
conn = C3P0Utils.getConnection();
//2. 根据连接对象, 获取可以执行SQL语句的对象.
stat = conn.createStatement();
//3. 执行SQL语句, 获取结果集.
String sql1 = "update account set money = money - 1000 where aid = 1;";
String sql2 = "update account set money = money + 1000 where aid = 2;";
//扣钱的动作
int a = stat.executeUpdate(sql1);
System.out.println(1 / 0);
//价钱的动作
int b = stat.executeUpdate(sql2);
//4. 操作结果集.
if (a == b && a == 1) {
System.out.println("转账成功");
}
} catch (Exception e) {
System.out.println("转账失败");
} finally {
//5. 释放资源.
C3P0Utils.release(conn, stat, null);
}
}
}