JDBC下

JDBC(下)

一、数据库连接池

 

无连接池:

 

有连接池:

 

(一)连接池简介

连接池的原理图:

 

连接池原理:

数据库连接池负责分配、管理和释放数据库连接,它的核心思想是连接复用,通过建立一个数据库连接池,这个池中有若干个连接对象,当用户想要连接数据库,就要先从连接池中获取连接对象,然后操作数据库。一旦连接池中的连接对象被用完了判断连接对象的个数是否已达上限,如果没有可以再创建新的连接对象,如果已达上限,用户必须处于等待状态,等待其他用户释放连接对象,直到连接池中有被释放的连接对象了,这时候等待的用户才能获取连接对象,从而操作数据库。这样就可以使连接池中的连接得到高效、安全的复用,避免了数据库连接频繁建、关闭的开销。这项技术明显提高对数据库操作的性能。

连接池的优势:

  1. 程序员启动时提前创建好了连接, 不需要手动创建, 给服务器减轻压力
  2. 连接关闭时不会直接销毁, 而是归还连接, 达到重复利用的效果
  3. 如果超过了设定连接最大值, 处于等待状态, 没有达到上限, 还可以自动创建;
  4. 如果连接空闲了, 会自动释放(默认销毁),这样让系统达到更优;  

(二)手动实现连接池(了解)

实现步骤:

  1. 创建一个连接类, 需要借助昨天封装的JDBCUtils工具类
  2. 初始化连接的默认值
  3. 创建一个集合(连接池) 存储连接
  4. 创建一个从连接池中获取连接的方法
  5. 创建一个向连接池中归还的方法
  6. 测试连接池的使用

package com.ujiuye.pools;

 

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.util.ArrayList;

import java.util.List;

 

import com.ujiuye.utils.JDBCUtils;

 

//手动实现连接池

public class MyDataBasePool {

//初始化连接数量

private static int initSize=5;

//定义一个集合(存储连接)

private static List<Connection> connPool = new ArrayList<Connection>();

//初始化连接

static {

for(int i = 0; i < initSize; i++) {

Connection conn = JDBCUtils.getConnection();

//存储到集合

connPool.add(conn);

}

System.out.println("连接池初始化成功, 创建连接的数目为: " + initSize);

}

 

//从连接池获取连接的方法

public static Connection getConnection() {

//获取连接

Connection conn = connPool.get(0);

//移除连接

connPool.remove(0);

System.out.println("用户正在使用" + conn + "连接, 当前池子里剩余连接为: " + connPool.size());

return conn;

}

 

//创建一个向连接池中归还的方法

public static void closeAll(ResultSet rs, PreparedStatement ps, Connection conn) {

//判断

if(rs != null) {

try {

rs.close();

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

if(ps != null) {

try {

ps.close();

} catch (SQLException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

if(conn != null) {

try {

//归还连接

//conn.close();

connPool.add(conn);

System.out.println("用户归还连接" + conn + "连接, 当前池子里剩余连接为: " + connPool.size());

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

 

}

 

虽然使用手动实现连接池也能够达到重复利用的状态, 但是比较麻烦, 功能上还不完善;

借助于第三方提供好的连接池:

常见的连接池(主流):

DBCP连接池,  C3P0连接池,   Druid连接池(阿里)

DBCP连接池与C3P0连接池区别:

  1. DBCP连接池效率比较高, 可能出现丢失连接, 安全性较低
  2. C3P0连接池相对DBCP连接池来说,效率偏低, 安全性较高;

(三)DBCP连接池

DBCP也是一个开源的连接池,是Apache Common成员之一,在企业开发中也比较常见,tomcat内置的连接池。

DBCP连接池目前市面上极为高效的连接池。

DBCP连接池一秒钟可以创建并传递10万个左右的连接对象。

连接池使用使用步骤:

1、下载并导包

 

2、编写数据库连接的属性配置文件

配置文件名称:*.properties

l 配置文件位置:建议放在src

 

需求:测试连接池查询商品的名字。

 

 

 

(四)c3p0连接

C3P0开源免费的连接池!目前使用它的开源项目有:SpringHibernate等。

框架:

SSH: SpringstrutsHibernate 整合后的框架简称SSH

SSm: Spring  SpringMVC  Mybatis整合后的框架简称SSM

市面上最流行的框架有: SpringBoot, SpringCloud, MyBatisPlus, SpringdataJPA

前台用的框架: Augula(很少使用), Vue

使用第三方工具需要导入jar包,c3p0使用时还需要添加配置文件 c3p0-config.xml

连接池的使用步骤:

1、下载并导包

 

2、编写数据库连接的属性配置文件

配置文件名称:c3p0-config.xml

l 配置文件位置:建议放在src

 

需求:测试连接池查询商品的名字

 

抽取工具类:

 

优化:

 

二、DBUtils工具

(一)DBUtils工具简介

Commons DbUtilsApache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能。尤其结合连接池使用效果更为理想。

DBUtils工具: 封装了对JDBC的操作, 简化了JDBC操作, 从而简化了代码的书写;

Commons DbUtils的核心是两个类一个接口:

1DBUtils类:主要为关闭连接,装载JDBC驱动程序之类的常规工作提供方法,都是静态的方法。

 

2QueryRunner类:提供对sql语句操作的API.(核心类, 使用它调用增删改查的方法)

QueryRunner qRunner=new QueryRunner(new ComboPooledDataSource());

创建对象时需要传入一个连接池的数据源,这里结合c3p0连接池来完成。

 

3ResultSetHandler接口:用于处理ResultSet结果集,将结果集的的数据转换成不同形式。该接口的实现类有很多:

ArrayHandler

将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是这条记录中的每一个字段的值。

ArrayListHandler

将结果集中的每一条记录都封装到一个Object[]数组中,将这些数组在封装到List集合中。

BeanHandler

将结果集中第一条记录封装到一个指定的javaBean中。

BeanListHandler

将结果集中每一条记录封装到指定的javaBean中,将这些javaBean在封装到List集合中。

ScalarHandler

它是用于单列数据查询。例如:select count(*) from users 操作。

MapHandler

将结果集中第一条记录封装到Map集合中,Key代表列名, Value代表该列数据。

MapListHandler

将结果集中每一条记录封装到Map集合中,Key代表列名, Value代表该列数据,Map集合再存储到List集合

(二)JavaBean

JavaBean是使用Java语言开发的一个可重用的组件,在JSP的开发中可以使用JavaBean减少重复代码,使整个JSP代码的开发更简洁。简单来讲就是实体类,用来支持业务逻辑的协助类,在JDBC操作中一般一个实体类会对应数据库中的一张表,实体类中的属性会与表中的列一一对应。

JavaBean的设计原则:

  1. 类必须是公共的, 修饰符public
  2. 类中属性必须是私有的, 修饰符private
  3. 必须为每个属性提供getter/setter方法
  4. 类中必须有无参构造(显示无参构造)

 

(三)DBUtils核心类-QueryRunner创建

QueryRunner的创建有两种方式:

自动模式: 可以直接将连接池当做参数直接放入, 称之为自动模式

手动模式: 将连接池对象放在执行更新数据时进行放入

 

(四)DBUtilsDML操作

update(String sql,Object…param)

自动模式

执行数据库的更新语句   只能做增删改操作, 不能做查询操作

参数1sql语句

参数2Object[] 数组,对每个占位符进行依次的赋值。

返回值:int   更新了几行数据

update(Connection con,

String sql,Object…param)

手动模式

执行数据库的更新语句   只能做增删改操作, 不能做查询操作

参数1Connection对象

参数2sql语句

参数3Object[] 数组,对每个占位符进行依次的赋值。

返回值:int   更新了几行数据

DBUtils使用步骤:

下载并导包

 

 

案例需求

需求1: 新增数据(向商品表中添加一条数据)

 

 

需求2: 修改数据(根据pid修改数据)

 

需求3: 删除数据(根据pid删除数据)

 

(五)DBUtilsDQL操作

query(String sql,

handler,

Object[] param)

自动模式

执行查询语句

参数:

参数1:需要执行的sql语句

参数2:如何处理二维表格。

参数3:给SQL语句中的?占位符 赋值

返回值:就是二维表格中的数据

根据参数2动态改变。

Object[],List<Object[]>,map, list<map>,javaBean,List<JavaBean>

query(Connection con,

String sql,handler,

Object[] param)

手动模式

执行查询语句

参数1Connection对象

参数2:需要执行的sql语句

参数3:如何处理二维表格。

参数4:给SQL语句中的?占位符 赋值

返回值:就是二维表格中的数据

根据参数2动态改变。

Object[],List<Object[]>,map, list<map>,javaBean,List<JavaBean>

案例需求:

1、将查询结果封装到JavaBean中,最常用的查询方法

 

 

 

2、将查询结果封装到数组中

 

3、将查询结果封装到Map集合中

 

 

4、查询单列数据

 

三、事务

 

(一)事务的概念

什么是事务?

事务就是应用程序中的一个完整的业务逻辑(包含多个小单元, 每一个小单元分别对应数据库中的数据进行CRUD的操作),我们通过事务保证所有的小单元, 要么同时成功, 要么同时失败;也就是说事务具有原子性;

作用: 要么全部成功, 要么全部失败;

(二)事务的应用场景

在实际的业务开发中,有些业务操作要多次访问数据库。一个业务要发送多条SQL语句给数据库执行。需要将多次访问数据库的操作视为一个整体来执行,要么所有的SQL语句全部执行成功。如果其中有一条SQL语句失败,就进行事务的回滚,所有的SQL语句全部执行失败。

场景描述:模拟银行转账业务。

  转账人   柳岩   

  收账人   宝强   

  模拟金额 第一次模拟:   500    第二次模拟:【  1000  

数据准备:

CREATE TABLE account(

  id VARCHAR(10) UNIQUE NOT NULL,-- 账户编号唯一

  username VARCHAR(40) NOT NULL,-- 账户名

  money DOUBLE UNSIGNED -- 账户金额,不能为负数

);

INSERT INTO account (id, username, money) VALUES ('p001', '柳岩', 1000), ('p002','宝强', 1000);

(三)MySQL的事务操作

MySQL的事务操作分为:

  1. 自动提交事务
  2. 手动提交事务

何为手动提交事务:

开启事务: start transaction;

提交事务: commit

回滚事务: rollback

测试:

 

 

 

(四)JDBC事务的操作

JDBC中由连接对象Connection来管理事务:

Connection 对象的方法名

描述

conn.setAutoCommit(false)

开启事务(true:自动提交事务, false:手动提交事务), 默认值为true

如果需要自己控制事务,需要将其设置为false。

conn.commit()

提交事务

conn.rollback()

回滚事务

以上三个方法必须确保连接对象Connection处于开启状态下才能使用。

同时也要保证是在同一个连接对象中使用

(五)事务的四大特性

事务的四大特性(ACID):(高频面试题)

(1)原子性(Atomicity:原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。

(2)一致性(Consistency:一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

(3)隔离性(Isolation:隔离性是当多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,即多个并发事务之间互不影响。

(4)持久性(Durability:持久性是指一个事务一旦被提交了,那么对数据库中数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

(六)事务的隔离级别(面试题)

事务的隔离级别分为以下四种:

 

以上四种隔离级别按照从低到高的顺序排列为:

Read uncommitted <Read committed <Repeatable read <Serializable

 

1、Read uncommitted未提交,就是一个事务可以读取另一个未提交事务的数据。

 

事例:老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的账户,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。

 

分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读。

 

那怎么解决脏读呢?使用Read committed(已提交读),能解决脏读问题。

 

2、Read committed:已提交,就是一个事务要等另一个事务提交后才能读取数据。

 

事例:程序员拿着信用卡去享受生活(假设卡里只有3.6万),当他买单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的...

 

分析:这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。

 

那怎么解决可能的不可重复读问题?使用Repeatable read (可重复读)。

 

3、Repeatable read:可重复读,就是在开始读取数据(事务开启)时,不再允许修改操作

 

事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。

 

分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。

 

什么时候会出现幻读?

 

事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。

 

那怎么解决幻读问题?使用Serializable(可串行化)。

 

4、Serializable 序列化

 

Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。

原文地址:https://www.cnblogs.com/masterhxh/p/13686472.html