目录
一、JDBC回顾
1.1 六个基本步骤
package test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class TestMain {
public static void main(String[] args) throws Exception {
//JDBC全称----JAVA DataBase Connectivity
//1.导包
//2.加载驱动类
Class.forName("com.mysql.cj.jdbc.Driver");
//3.获取连接,只是一个桥梁,负责让驱动与数据库连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest?useSSL=false","root","12345678");
//4.获取状态参数(流)
//PreparedStatement预处理流
PreparedStatement pstat = connection.prepareStatement("select * from users");
//5.执行操作
//增删改---executeUpdate()
//查询---executeQuery()
//ResultSet底层---Set<Map<String,Object>>
ResultSet rs = pstat.executeQuery();
//6.访问下一个rs中的数据,遍历
while(rs.next()) {
System.out.print(rs.getInt("id"));
System.out.print(rs.getString("uname"));
System.out.print(rs.getString("psw"));
System.out.print(rs.getString("email"));
System.out.print(rs.getDate("birthday"));
System.out.print(rs.getDouble("sal"));
System.out.println();
}
//7.关闭资源
rs.close();
pstat.close();
connection.close();
}
}
1.2 问题的引出
1、JDBC代码流程写在哪个层次中?----DAO数据读写持久化层
2、DAO层中通常的方法什么样子?----对于数据库的增删改查操作
3、DAO方法中该写什么样的代码?----纯粹的JDBC流程 + SQL执行语句
4、DAO层次中的每一个方法里都存在JDBC流程(6步),流程大体上是一样的。所以DAO层次中的代码冗余问题就很多。------封装,也就是(ORM框架)
5、我们所说的JDBC连接池封装,其实是对我们现在的流程进行优化;我们现在所写的六个基本步中是存在很多问题的;而我们最后学的将整个JDBC封装是一个ORM框架;
二、ORM框架
1、ORM框架(Object Relationship Mapping对象关系映射)
(1)对象---domain
(2)关系---对应(映射)
2、ORM框架封装以后的最终效果,我们不用写JDBC流程了,只写SQL + 那些问号值;我们DAO层次中的方法没有执行体,只有方法的壳 + 注解(写SQL)
3、在ORM框架中讲解。
三、JDBC连接池性能优化
3.1 JDBC连接池在创建连接时优化
1、我们发现每次运行TestMain中的JDBC六步时都会有几秒卡顿,要进行优化
package test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class TestMain {
public static void main(String[] args) throws Exception {
//JDBC全称----JAVA DataBase Connectivity
//1.导包
//2.加载驱动类
long t1 = System.currentTimeMillis();
Class.forName("com.mysql.cj.jdbc.Driver");
//3.获取连接,只是一个桥梁,负责让驱动与数据库连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest?useSSL=false","root","12345678");
long t2 = System.currentTimeMillis();
//4.获取状态参数(流)
//PreparedStatement预处理流
PreparedStatement pstat = connection.prepareStatement("select * from users");
//5.执行操作
//增删改---executeUpdate()
//查询---executeQuery()
//ResultSet底层---Set<Map<String,Object>>
ResultSet rs = pstat.executeQuery();
//6.访问下一个rs中的数据,遍历
while(rs.next()) {
System.out.print(rs.getInt("id"));
System.out.print(rs.getString("uname"));
System.out.print(rs.getString("psw"));
System.out.print(rs.getString("email"));
System.out.print(rs.getDate("birthday"));
System.out.print(rs.getDouble("sal"));
System.out.println();
}
//7.关闭资源
rs.close();
pstat.close();
connection.close();
long t3 = System.currentTimeMillis();
System.out.println(t2-t1);//794
System.out.println(t3-t2);//127
}
}
2、我们认为t1 和 t2 之间的时间是创建连接的时间,t2 和 t3 之间的时间是整个执行的时间
3、发现毫秒差很大,原因:
(1)DAO层的代码冗余问题
(2)JDBC流程中有一个性能卡顿,是在创建连接这个地方(由于每次进行数据操作都要去创建连接,每操作一次数据库,就会创建一次连接)
(3)解析:这个Connection就好比是一个桥梁,假如有5个人要过桥,但是此时并没有桥,每一个人都会先创建一个桥,然后过去之后再把桥炸了,然后第二个人想过桥会在创建一个桥过去之后在把桥炸了;所以每个人都会有创建桥的这个过程,里面增加了很多冗余。
4、如何提高创建连接的性能?
(1)假设我们创建5个连接,5个连接不销毁,留着复用。5个连接存在哪里?---集合(也就是连接池),还需要对集合进行管理
(2)需要将每一个连接身上绑定一个状态,连接类不是我们自己写的(Connection不是我们自己写的类),所以我们要创建一个新对象来将连接绑定状态。
3.2 将连接(桥梁)和状态进行绑定
1、MyConnection
1、MyConnection:描述一个真实连接和一个状态的关系,将一个真实可用连接和一个状态封装(绑定)在一起形成一个新的对象
(1)我们首先要在MyConnection类中创建连接和状态,最好是属性,创建属性的目的是为了描述对象的特征和状态;
//创建连接
private Connection conn;
//创建状态(false表示连接空闲--可用,true表示连接已被其他人占用--不可用)默认false;
private boolean used = false;
(2)上面创建了两个属性,发现conn连接这个属性它是没有完成连接操作的。所以我们创建一个普通块,目的是为了给我们创建的conn赋予一定的能力,就是为了我们每次new MyConnection的时候,能得到这个连接,这里的连接指的是让Java代码与数据库连接(桥梁);
{
//我们为什么不直接private Connection conn = DriverManager.getConnection();因为 DriverManager.getConnection();是有异常的
//这两行代码不能给属性,因为属性是不能抛异常的,而且属性这一行无法完成
Class.forName("");
conn = DriverManager.getConnection();
}
(3)那么问题来了,属性不是一个,因为每次new 都会创建新的属性,然后完成一次类加载,然后在创建桥梁,可是我们的类加载只需创建一次就够了啊,所以还需要创建一个静态块,目的为了让类加载执行一次
static {
//由于每new一次就执行一次普通块,所以把类加载放在静态块中
Class.forName("");
}
(4)连接需要获取get,让外界在创建对象时可以获取MyConnection对象中的连接(桥梁)getConn,让用户能获取到这个桥梁;used状态需要来回切换,所以还有set/get方法
//让外界能获取到连接(桥梁)
public Connection getConn() {
return conn;
}
//used状态需要来回切换
public boolean isUsed() {//boolean的get方法,名字是is开头的---规约
return used;
}
public void setUsed(boolean used) {
this.used = used;
}
(6)完整版
package pool;
import java.sql.Connection;
import java.sql.DriverManager;
/**
* @Description: TODO 描述一个真实连接和一个状态的关系
* 将一个真实可用连接(桥梁)和一个状态封装在一起形成一个新的对象MyConnection
* @Author: 曹宇希
* @Date: 2023/11/30 12:20
* @Version: 1.0
* @Company: 版权所有
*/
public class MyConnection {
private static String className = "com.mysql.cj.jdbc.Driver";
private static String url = "jdbc:mysql://localhost:3306/jdbctest?serverTimezone=GMT%2B8";
private static String user = "root";
private static String password = "12345678";
//连接
private Connection conn;
//状态(false表示连接空闲--可用,true表示连接已被其他人占用--不可用)默认false
private boolean used = false;
//一个静态块,目的为了让类加载执行一次
static {
try{
Class.forName(className);
} catch (Exception e) {
e.printStackTrace();
}
}
//一个普通块,目的是为了给属性赋值
{
try{
//桥梁
conn = DriverManager.getConnection(url,user,password);
} catch (Exception e) {
e.printStackTrace();
}
}
//让外界能获取到这个桥梁
public Connection getConn() {
return conn;
}
//used状态需要来回切换
public boolean isUsed() {
return used;
}
public void setUsed(boolean used) {
this.used = used;
}
}
3.3 设计连接池用于装连接
1、ConnectionPool
1、我们设计的连接池类,这个类需要将好多连接存储起来---属性(集合),存储以后当然需要将连接获取到并给用户拿去使用---池子进行管理
2、创建一个属性,这个属性用于当作我们的连接池,那么一个大池子我们用什么存储好?---集合,并且这个集合还可以进行遍历查找,所以采用List集合;那么这个集合里面应该就存储着我们自己创建的连接
private List<MyConnection> pool = new ArrayList();
3、那么连接存储完了,我们还需要给pool连接池赋值,那么应该在哪里赋值?怎么赋值?创建一个普通块;每次类加载的时候就会给连接池装入连接
{
//在ArrayList连接池中创建5个连接
for (int i = 1; i <= 5; i++) {
pool.add(new MyConnection());
}
}
4、设计方法来获取连接池中的连接,返回值是Connection 还是用 MyConnection?问题来了,我们创建的连接给谁用?--用户,用户需要获取连接池的连接,用完连接之后还需要关闭资源,而这个资源我们不能真正的关闭,需要操作资源状态,用完之后还需要还回来(需要切换状态---释放连接)能切换状态的也就是我们自己创建的MyConnection
(1)MyConnection中的getConn方法是为了让其他类获取连接
public MyConnection getMC(){
MyConnection result = null;
for (MyConnection mc:pool) {
if (!mc.isUsed()) {//这个连接可用
mc.setUsed(true);//先把这个连接占为己有
result = mc;//然后存起来
break;
}
}
return result;
}
5、完整版
package pool;
import java.util.ArrayList;
import java.util.List;
/**
* @Description: TODO 这是我们设计的连接池类,这个类需要将好多连接存储起来--属性(集合)
* 存储以后当然需要将连接获取到并给用户拿去使用--池子进行管理
* @Author: 曹宇希
* @Date: 2023/12/1 8:17
* @Version: 1.0
* @Company: 版权所有
*/
public class ConnectionPool {
//属性--大池子 List集合存储着我们自己创建的连接
private List<MyConnection> pool = new ArrayList();
//给pool连接池当中的连接赋值,那么在哪里赋值,怎么赋值,普通块
{
for (int i = 1; i <= 5; i++) {
pool.add(new MyConnection());
}
}
//方法--获取连接 返回值Conn,我们获取连接给谁用?
//用户需要操作状态,用户用完了以后,不能关闭,用完之后还需要还回来(需要切换状态---释放)
//能切换状态的也就是我们自己创建的MyConnection
public MyConnection getMC(){
MyConnection result = null;
for (MyConnection mc:pool) {
if (!mc.isUsed()) {//这个连接可用!mc.isUsed() 其实就是mc.isUsed()==false
mc.setUsed(true);//先把这个连接占为己有
result = mc;//然后存起来
break;
}
}
return result;
}
}
四、JDBC连接池性能的测试
1、最基本的封装完毕了,那该怎么用?该怎么测试出第二次没有创建连接呢?
public class TestMain {
public static void main(String[] args) throws Exception{
// TODO 第一次:创建连接和查询
//1.导包
//2.加载连接池对象
long t1 = System.currentTimeMillis();
ConnectionPool connectionPool = new ConnectionPool();
//3.获取连接
MyConnection mc = connectionPool.getMC();//我们自己包装的类,这个类里面才有连接
Connection conn = mc.getConn();//通过类获取连接
long t2 = System.currentTimeMillis();
//4.状态参数
PreparedStatement pstat = conn.prepareStatement("select * from users");
ResultSet rs = pstat.executeQuery();
//5.rs做业务
while (rs.next()) {
System.out.println(rs.getString("uname"));
}
//6.关闭资源
pstat.close();
rs.close();
mc.setUsed(false);//不能conn.close(),因为我们是自己创建的连接,需要修改连接的状态,不是真的关闭,而是释放
long t3 = System.currentTimeMillis();
System.out.println("创建连接的时间:" + (t2-t1));
System.out.println("执行的时间" + (t3-t2));
// TODO 第二次:不需要创建连接了,直接查询
long t4 = System.currentTimeMillis();
//3.获取连接
MyConnection mc2 = connectionPool.getMC();
Connection conn2 = mc2.getConn();
long t5 = System.currentTimeMillis();
//4.状态参数
PreparedStatement pstat2 = conn2.prepareStatement("select * from users");
ResultSet rs2 = pstat2.executeQuery();
//5.rs做业务
while (rs2.next()) {
System.out.println(rs2.getString("uname"));
}
//6.关闭资源
pstat.close();
rs.close();
mc.setUsed(false);
long t6 = System.currentTimeMillis();
System.out.println("创建连接的时间:" + (t5-t4));
System.out.println("执行的时间" + (t6-t5));
}
}
2、解决的问题
(1)节省了时间
(2)连接池对象,发现单例的就够了
(3)发现有很多信息是写死的,采用配置文件会更好
(4)现在我们只有5个连接,如果6个人过来会怎么样?两个人抢到了同一个连接,所以是个线程安全的问题--线程安全--我们加个锁
(5)如果锁上了,保证连接是唯