第三章.DBUtils工具包
1.准备工作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3YLP4IdL-1659014506282)(D:\Typora\bin\document\img\1656814736530.png)]
2.DBUtils的介绍
1.为什么要学DBUtils:之前我们使用原生jdbc开发,开发步骤很繁琐,降低了我们的开发速度,所以我们需要学DBUtils加快我们的开发效率
2.DBUtils:是一个加速数据库开发的一个工具包,大大的提高我们的开发效率
3.学习DBUtils的三大类:
QueryRunner:执行sql
ResultSetHandler:处理结果集
DButils:DBUtils工具包中的一个工具类,在此类中定义了很多事务相关,连接相关的方法
3.QueryRunner
3.1.空参的QueryRunner的介绍以及使用
1.构造:QueryRunner()
2.特点:连接对象需要我们自己维护(自己获取,自己关闭)
3.方法:
a.int update(Connection conn, String sql, Object... params)->执行sql,针对于增删改操作
conn:传递连接对象
sql:传递sql语句->支持占位符 ?
params:为?赋值,写一个参数,就是为第一个?赋值,写第二个参数,就是为第二个?赋值->自动匹配
b.query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params)
conn:传递连接对象
sql:传递sql语句->支持占位符
rsh:处理结果集的方式->不用的实现类,处理结果集的方式不同
params:为?赋值->自动匹配
public class Test01 {
@Test
public void add()throws Exception{
//1.创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//2.准备sql
String sql = "insert into category (cname) values (?)";
//3.获取连接对象
Connection connection = DruidUtils.getConnection();
//4.执行sql
qr.update(connection,sql,"手机");
//5.关闭资源
DruidUtils.close(connection,null,null);
}
@Test
public void delete()throws Exception{
//1.创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//3.获取连接对象
Connection connection = DruidUtils.getConnection();
//4.执行sql
qr.update(connection,"delete from category where cid = ?","104");
//5.关闭资源
DruidUtils.close(connection,null,null);
}
}
3.2.有参QueryRunner的介绍以及使用
1.构造:
QueryRunner(DataSource ds)
2.特点:
自动维护连接对象,不用我们自己单独获取,单独关闭
3.方法:
a.int update( String sql, Object... params)->执行sql,针对于增删改操作
sql:传递sql语句->支持占位符 ?
params:为?赋值,写一个参数,就是为第一个?赋值,写第二个参数,就是为第二个?赋值->自动匹配
b.query(String sql, ResultSetHandler<T> rsh, Object... params)
sql:传递sql语句->支持占位符
rsh:处理结果集的方式->不用的实现类,处理结果集的方式不同
params:为?赋值->自动匹配
public class DruidUtils {
private static DataSource dataSource = null;
static {
try {
//创建properties集合
Properties properties = new Properties();
//读取properties配置文件
InputStream in = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(in);
//创建Druid的连接池实现类
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//定义方法,返回创建好的连接池对象->新添加的方法
public static DataSource getDataSource(){
return dataSource;
}
//定义方法,用来获取连接
public static Connection getConnection() {
Connection connection = null;
try {
//从连接池中获取连接对象
connection = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
//关闭资源
public static void close(Connection connection, Statement statement, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
public class Test02 {
@Test
public void add()throws Exception{
//创建queryrunner对象
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
String sql = "insert into category (cname) values (?)";
//执行
qr.update(sql,"化妆品");
}
@Test
public void delete()throws Exception{
//创建queryrunner对象
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
//执行
qr.update("delete from category where cid = ?","105");
}
}
4.ResultSetHandler结果集
4.1.标准的JavaBean
1.私有属性,空参构造,有参构造,get/set,toString
2.细节:
以后我们在定义javabean的时候,所有的属性类型都要写成引用数据类型
因为包装类或者其他引用数据类型默认值为NULL
如果我们的主键是自增的
insert into 表名 values (NULL,值)
因为包装类中有方法,我们将来如果想要处理数据,我们可以调用包装类中的方法取处理,比较方便
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hfGGKl1C-1659014506283)(D:\Typora\bin\document\img\1656818321166.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o8fzMO5k-1659014506283)(D:\Typora\bin\document\img\1656818841557.png)]
4.2.BeanHandler
1.作用:将查询出来的结果集中的第一行数据封装成javabean对象
2.方法:
query(String sql, ResultSetHandler<T> rsh, Object... params)>有参QueryRunner时使用
query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params)->空参QueryRunner时使用
3.构造:
BeanHandler(Class type)
传递的class对象其实就是我们想要封装的javabean类的class对象
想将查询出来的数据封装成哪个javabean对象,就写哪个javabean的class对象
4.怎么理解:
将查询出来的数据为javabean中的成员变量赋值
public class Test01 {
@Test
public void beanHandler()throws Exception{
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
String sql = "select * from category";
Category category = qr.query(sql, new BeanHandler<>(Category.class));
System.out.println(category);
}
}
4.3.BeanListHandler
1.作用:将查询出来的结果每一条数据都封装成一个一个的javabean对象,将这些javabean对象放到List集合中
2.构造:
BeanListHandler(Class type)
传递的class对象其实就是我们想要封装的javabbean类的class对象
想将查询出来的数据封装成哪个javabean对象,就写哪个javabean的class对象
3.怎么理解:
将查询出来的多条数据封装成多个javabean对象,将多个javabean对象放到List集合中
@Test
public void beanListHandler()throws Exception{
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
String sql = "select * from category";
List<Category> list = qr.query(sql, new BeanListHandler<>(Category.class));
for (Category category : list) {
System.out.println(category);
}
}
4.4.ScalarHandler
1.作用:主要是处理单值的查询结果的,执行的select语句,结果集只有1个
2.构造:
ScalarHandler(int columnIndex)->不常用->指定第几列
ScalarHandler(String columnName)->不常用->指定列名
ScalarHandler()->常用
3.注意:
ScalarHandler和聚合函数使用更有意义
@Test
public void scalarHandler()throws Exception{
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
//String sql = "select * from category";
//Object query = qr.query(sql, new ScalarHandler("cname"));
String sql = "select count(*) from category";
Object o = qr.query(sql, new ScalarHandler<>());
System.out.println(o);
}
4.5.ColumnListHandler
1.作用:将查询出来的结果中的某一列数据,存储到List集合中
2.构造:
ColumnListHandler(int columnIndex)->指定第几列
ColumnListHandler(String columnName)->指定列名
ColumnListHandler()-> 默认显示查询结果中的第一列数据
3.注意:
ColumnListHandler可以指定泛型类型,如果不指定,返回的List泛型就是Object类型
@Test
public void columnListHandler()throws Exception{
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
String sql = "select * from category";
//List<Category> list = qr.query(sql, new ColumnListHandler<Category>(2));
//List<Category> list = qr.query(sql, new ColumnListHandler<Category>("cname"));
List<Category> list = qr.query(sql, new ColumnListHandler<Category>());
System.out.println(list);
}
第四章.事务
1.事务
1.1.事务_转账分析图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZKVNRK2w-1659014506285)(D:\Typora\bin\document\img\1656828914759.png)]
CREATE TABLE account(
`name` VARCHAR(10),
money INT
);
1.2.实现转账(不加事务)
public class Test01 {
public static void main(String[] args)throws Exception {
//1.创建QueryRunner对象
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
//2.准备sql
String outMoney = "update account set money = money-1000 where name = ?";
String inMoney = "update account set money = money+1000 where name = ?";
//3.执行sql
qr.update(outMoney,"涛哥");
//System.out.println(1/0);
qr.update(inMoney,"彤彤");
}
}
1.3.事务的介绍
1.作用:事务是用于管理一组操作的(多条sql),使这组操作要么全成功,要么全失败
2.注意1:
我们mysql是自带事务管理的,一次只管理一条sql,所以如果我们想要让事务管理一组操作,需要将mysql自带事务关闭,开启手动事务
3.如何手动操作事务:Connection
a.关闭mysql自带事务,开启手动事务
setAutoCommit(false)
b.提交事务:->一旦事务提交,数据将永久保存,还原不回去了
commit()
c.回滚事务: -> 将数据还原
rollback()
4.注意2: 要想让手动事务成功管理操作,那么三个方法需要用同一条Connection连接对象调用
1.4.DBUtils实现转账(添加事务)
public class Test02 {
public static void main(String[] args)throws Exception {
//1.创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//获取连接
Connection connection = DruidUtils.getConnection();
try{
//开启手动事务,关闭mysql自动事务
connection.setAutoCommit(false);
//2.准备sql
String outMoney = "update account set money = money-1000 where name = ?";
String inMoney = "update account set money = money+1000 where name = ?";
//3.执行sql
qr.update(connection,outMoney,"涛哥");
System.out.println(1/0);
qr.update(connection,inMoney,"彤彤");
//提交事务
connection.commit();
System.out.println("转账成功");
}catch (Exception e){
//回滚事务
connection.rollback();
System.out.println("转账失败");
e.printStackTrace();
}finally {
//关闭资源
DruidUtils.close(connection,null,null);
}
}
}
1.5.mysql中操作事务_了解
开启事务: begin
提交事务: commit
回滚事务: rollback
-- 开启事务
BEGIN;
UPDATE account SET money = money-1000 WHERE `name` = '涛哥';
UPDATE account SET money = money+1000 WHERE `name` = '彤彤';
-- 提交事务
COMMIT;
-- 回滚事务
ROLLBACK;
1.6.分层思想介绍以及架构搭建
三层架构思想:
表现层(controller) : 接收请求,根据不同的结果,给页面做不同的响应
业务层(service) : 处理业务逻辑判断,计算
持久层(dao): 专门和数据库打交道的,数据库操作在dao层写
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z4Gazgkd-1659014506286)(D:\Typora\bin\document\img\1656831806624.png)]
创建package
1.包名都要是小写
2.公司域名倒着写开头
com.atguigu.controller -> 表现层类
com.atguigu.service->业务层类
com.atguigu.dao-> 数据库相关类
com.atguigu.pojo -> 装javabean
com.atguigu.utils -> 装工具类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pl7Yy8g3-1659014506286)(D:\Typora\bin\document\img\1656833146717.png)]
1.6.1.转账_表现层实现
public class AccountController {
public static void main(String[] args) {
//1.创建Scanner对象
Scanner sc = new Scanner(System.in);
//2.输入出钱的姓名
System.out.println("请输入出钱人姓名:");
String outName = sc.next();
//3.输入收钱的姓名
System.out.println("请输入收钱人姓名:");
String inName = sc.next();
//4.输入转账金额
System.out.println("请输入转账金额:");
int money = sc.nextInt();
//5.调用service层转账方法,传递outName,inName,money
AccountService accountService = new AccountService();
accountService.transfer(outName,inName,money);
}
}
1.6.2.转账_service层实现
public class AccountService {
/**
*
* @param outName 出钱人姓名
* @param inName 收钱人姓名
* @param money 转账金额
*
*/
public void transfer(String outName, String inName, int money) {
//调用dao层transfer方法,做出钱,收钱
AccountDao accountDao = new AccountDao();
try{
//出钱
accountDao.outMoney(outName,money);
System.out.println(1/0);
//收钱
accountDao.inMoney(inName,money);
System.out.println("转账成功");
}catch (Exception e){
System.out.println("转账失败");
e.printStackTrace();
}
}
}
1.6.3.转账_dao层实现
public class AccountDao {
/**
*
* @param outName 出钱人姓名
* @param money 转账金额
*/
public void outMoney(String outName, int money) throws SQLException {
QueryRunner qr = new QueryRunner();
Connection connection = DruidUtils.getConnection();
String sql = "update account set money = money-? where name = ?";
qr.update(connection,sql,money,outName);
DruidUtils.close(connection,null,null);
}
/**
*
* @param inName 收钱人姓名
* @param money 转账金额
*/
public void inMoney(String inName, int money) throws SQLException {
QueryRunner qr = new QueryRunner();
Connection connection = DruidUtils.getConnection();
String sql = "update account set money = money+? where name = ?";
qr.update(connection,sql,money,inName);
DruidUtils.close(connection,null,null);
}
}
1.6.4.在service层添加事务(传递连接)
public class AccountService {
/**
*
* @param outName 出钱人姓名
* @param inName 收钱人姓名
* @param money 转账金额
*
*/
public void transfer(String outName, String inName, int money) {
//调用dao层transfer方法,做出钱,收钱
AccountDao accountDao = new AccountDao();
//获取连接
Connection connection = DruidUtils.getConnection();
try{
//开启事务
connection.setAutoCommit(false);
//出钱
accountDao.outMoney(connection,outName,money);
System.out.println(1/0);
//收钱
accountDao.inMoney(connection,inName,money);
//提交事务
connection.commit();
System.out.println("转账成功");
}catch (Exception e){
//回滚事务
try {
connection.rollback();
System.out.println("转账失败");
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}finally {
DruidUtils.close(connection,null,null);
}
}
}
1.6.5.修改dao层代码
public class AccountDao {
/**
*
* @param outName 出钱人姓名
* @param money 转账金额
*/
public void outMoney(Connection connection,String outName, int money) throws SQLException {
QueryRunner qr = new QueryRunner();
String sql = "update account set money = money-? where name = ?";
qr.update(connection,sql,money,outName);
}
/**
*
* @param inName 收钱人姓名
* @param money 转账金额
*/
public void inMoney(Connection connection,String inName, int money) throws SQLException {
QueryRunner qr = new QueryRunner();
String sql = "update account set money = money+? where name = ?";
qr.update(connection,sql,money,inName);
}
}
为啥要分层:
1.好维护
2.做到在自己的类中,干自己的事儿,不要干别人的事儿
问题:
在service层我们获取连接对象,是service层该干的事儿吗?不是昂
那么获取连接对象,不在service层干,那么事务也没法操作了,连接对象也不能传递到dao层了,也无法保证service层和dao层的连接对象是同一个对象了
怎么办?
1.将获取连接这种事抽离出来,不在service写了
2.还要保证service层中的连接对象和dao层的连接对象是同一个对象
2.ThreadLocal
2.1.ThreadLocal基本使用和原理
1.概述:是一个容器
2.特点:
a.只能存储一个数据,如果存储两个,后面的会把前面的覆盖
b.一个线程在ThreadLocal中存储数据,其他线程获取不到
c.一条线程在ThreadLocal中存储的元素,后面只要是属于整个线程中的一部分,都可以共享这个ThreadLocal中的数据
3.使用:
a.创建对象: ThreadLocal<泛型> 名字 = new ThreadLocal<>()
b.存数据: set(数据)
c.获取数据: get()
4.实现原理:
ThreadLocal底层其实是一个map集合
key:当前线程
value:存储的数据
当在ThreadLocal中存储一个数据,此数据直接跟当前线程绑死,所以在当前线程中的任何一个位置都能共享ThreadLocal中的数据,而且是同一个数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a8yAa3KK-1659014506288)(D:\Typora\bin\document\img\1656836506624.png)]
public class Test01 {
public static void main(String[] args) {
ThreadLocal<String> tl = new ThreadLocal<>();
tl.set("张三");
tl.set("李四");
System.out.println(tl.get());
new Thread(()-> System.out.println(tl.get())).start();//null
}
}
2.2.连接对象管理类(小秘书)
**
* 连接对象管理类
* Service层的秘书
*/
public class ConnectionManager {
private static ThreadLocal<Connection> tl = new ThreadLocal<>();
//定义方法,从连接池中获取连接,存到ThreadLocal中
public static Connection getConn(){
//从TheadLocal中获取连接对象
Connection connection = tl.get();
if (connection==null){
connection = DruidUtils.getConnection();
tl.set(connection);
}
return connection;
}
//开启事务
public static void begin() throws SQLException {
getConn().setAutoCommit(false);
}
//提交事务
public static void commit() throws SQLException {
getConn().commit();
}
//回滚事务
public static void back() throws SQLException {
getConn().rollback();
}
//关闭连接
public static void closeConn(){
Connection conn = getConn();
DruidUtils.close(conn,null,null);
}
}
public class AccountService {
/**
*
* @param outName 出钱人姓名
* @param inName 收钱人姓名
* @param money 转账金额
*
*/
public void transfer(String outName, String inName, int money) {
//调用dao层transfer方法,做出钱,收钱
AccountDao accountDao = new AccountDao();
try{
//开启事务
ConnectionManager.begin();
//出钱
accountDao.outMoney(outName,money);
System.out.println(1/0);
//收钱
accountDao.inMoney(inName,money);
//提交事务
ConnectionManager.commit();
System.out.println("转账成功");
}catch (Exception e){
//回滚事务
try {
ConnectionManager.back();
System.out.println("转账失败");
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}finally {
ConnectionManager.closeConn();
}
}
}
public class AccountDao {
/**
*
* @param outName 出钱人姓名
* @param money 转账金额
*/
public void outMoney(String outName, int money) throws SQLException {
QueryRunner qr = new QueryRunner();
String sql = "update account set money = money-? where name = ?";
qr.update(ConnectionManager.getConn(),sql,money,outName);
}
/**
*
* @param inName 收钱人姓名
* @param money 转账金额
*/
public void inMoney(String inName, int money) throws SQLException {
QueryRunner qr = new QueryRunner();
String sql = "update account set money = money+? where name = ?";
qr.update(ConnectionManager.getConn(),sql,money,inName);
}
}
3.事务的特性以及隔离级别
3.1.事务特性:ACID
-
原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
-
一致性(Consistency)事务前后数据的完整性必须保持一致。
-
隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离,正常情况下数据库是做不到这一点的,可以设置隔离级别,但是效率会非常低。
-
持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
3.2 并发访问问题
如果不考虑隔离性,事务存在3中并发访问问题。
-
脏读:一个事务读到了另一个事务未提交的数据.
-
不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。
-
虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。
3.3 隔离级别:解决问题
- 数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。
-
read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。
a)存在:3个问题(脏读、不可重复读、虚读)。
b)解决:0个问题
-
read committed 读已提交,一个事务读到另一个事务已经提交的数据。
a)存在:2个问题(不可重复读、虚读)。
b)解决:1个问题(脏读)
-
repeatable read:可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
a)存在:1个问题(虚读)。
b)解决:2个问题(脏读、不可重复读)
4.serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。
a)存在:0个问题。
b)解决:3个问题(脏读、不可重复读、虚读)
- 安全和性能对比
- 安全性:
serializable > repeatable read > read committed > read uncommitted
- 性能 :
serializable < repeatable read < read committed < read uncommitted
- 安全性:
- 常见数据库的默认隔离级别:
- MySql:
repeatable read
- Oracle:
read committed
- MySql:
3.4 演示
-
隔离级别演示参考:资料/隔离级别操作过程.doc【增强内容,了解】
-
查询数据库的隔离级别
show variables like '%isolation%';
或
select @@tx_isolation;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F9SUqvOt-1659014506289)(D:\Typora\bin\document\img\h.png)]
-
设置数据库的隔离级别
set session transactionisolation level
级别字符串- 级别字符串:
readuncommitted
、read committed
、repeatable read
、serializable
- 例如:
set session transaction isolation level read uncommitted;
-
读未提交:readuncommitted
- A窗口设置隔离级别
- AB同时开始事务
- A 查询
- B 更新,但不提交
- A 再查询?-- 查询到了未提交的数据
- B 回滚
- A 再查询?-- 查询到事务开始前数据
- A窗口设置隔离级别
-
读已提交:read committed
- A窗口设置隔离级别
- AB同时开启事务
- A查询
- B更新、但不提交
- A再查询?–数据不变,解决问题【脏读】
- B提交
- A再查询?–数据改变,存在问题【不可重复读】
- A窗口设置隔离级别
-
可重复读:repeatable read
- A窗口设置隔离级别
- AB 同时开启事务
- A查询
- B更新, 但不提交
- A再查询?–数据不变,解决问题【脏读】
- B提交
- A再查询?–数据不变,解决问题【不可重复读】
- A提交或回滚
- A再查询?–数据改变,另一个事务
- A窗口设置隔离级别
-
串行化:serializable
- A窗口设置隔离级别
- AB同时开启事务
- A查询
- B更新?–等待(如果A没有进一步操作,B将等待超时)
- A回滚
- B 窗口?–等待结束,可以进行操作
- 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency)事务前后数据的完整性必须保持一致。
- 隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离,正常情况下数据库是做不到这一点的,可以设置隔离级别,但是隔离级别越高,效率会非常低。
- 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
如果不考虑隔离性,事务存在3中并发访问问题。(如果隔离级别低,事务跟事务之间有可能互相影响)
1. 脏读:一个事务读到了另一个事务未提交的数据.
2. 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。
3. 虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。
总结:
我们最理想的状态是:一个事务和其他事务互不影响
但是如果不考虑隔离级别的话,就会出现多个事务之间互相影响
而事务互相影响的表现方式为:
脏读
不可重复读
虚读/幻读