Java笔记(14)

来源:互联网 发布:淘宝网是如何盈利的 编辑:程序博客网 时间:2024/06/06 00:50
掌握JDBC完成数据表CURD
掌握DAO模式程序


今天学习重点:JDBC事务管理、数据库连接池技术


事务:一组全部成功、全部失败操作。(这组操作不可分割)
案例:转账案例 


MySQL 数据库 默认情况下 一条SQL就是一个单独事务,事务是自动提交的
Oracle 数据库 默认情况下 事务不是自动提交 ,所有SQL都将处于一个事务中,你需要手动进行commit提交/rollback回滚


设计账户table
create table account(
    id int primary key not null,
    name varchar(40),
    money double
);


insert into account values(1,'aaa',1000);
insert into account values(2,'bbb',1000);
insert into account values(3,'ccc',1000);


在mysql管理事务
start transaction  开启事务 (所有对数据表增加、修改、删除操作 临时表进行)
rollback  回滚事务 (取消刚刚操作)
commit   提交事务 (确认刚才操作)
* 在事务管理中执行sql,使用数据库内临时表保存,在没有进行事务提交或者回滚,其它用户无法看到事务操作结果的
* SQL语言中只有 DML才能被事务管理 insert update delete 


Oracle实验
create table account(
    id int primary key not null,
    name varchar(40),
    money number
);


使用JDBC程序如何控制事务
Connection.setAutoCommit(false); //  相当于start transaction
Connection.rollback();  rollback --- 回滚到事务开启时状态
Connection.commit();  commit


* 将mysql的jar 复制 WEB-INF/lib
* 复制之前编写的JDBCUtils工具类
* 将数据库配置文件 dbconfig.properties 复制 src目录下 ----- 修改数据库配置


事务回滚点 SavePoint 
* 当时事务特别复杂,有些情况不会回滚到事务最开始状态,需要将事务回滚到指定位置 
Savepoint sp = conn.setSavepoint(); 设置回滚点
Conn.rollback(sp); 事务回滚到指定位置


create table person(
   id int primary key,
   name varchar(40)
);


----------------------------------------------------------------------------------------------
事务的四大特性: ACID  原子性、一致性、隔离性、持久性
原子性:事务的一组操作不可分割,要么都成功,要么都失败 
一致性:事务前后数据完整性 转账前 A 和 B 账户总和2000元,转账后 总和还是2000 元
隔离性:并发访问存在时,事务之间是隔离的,一个事务不应该影响其它事务运行效果 
持久性:当事务一旦提交,事务数据永久存在,无法改变 


企业开发中一定要保证事务原子性,事务最复杂问题都是由事务隔离性引起的


不考虑事务隔离将引发哪些问题:脏读、不可重复读、虚读
脏读:一个事务读取另一个事务 未提交数据 ---- 是数据库隔离中最重要问题
不可重复读:一个事务读取另一个事务 已提交数据,在一个事务中两次读取结果不同 ----- 在某些情况下出现问题
虚读:一个事务读取另一个事务 插入数据,造成在一个事务中两次读取记录条数不同 
* 虚读 不可重复读 区别? 不可重复读读取 update数据 ,虚读读取insert 数据


数据库为了解决三类隔离引发问题:提供四个数据库隔离级别(所有数据库通用)
Serializable : 串行处理  ---- 解决三类问题
Repeatable read :可以解决 不可重复读、脏读,会发生虚读   ------- MySQL 默认级别
read committed : 可以 解决脏读 ,会发生 不可重复读、虚读  -------- Oracle默认级别
read uncommitted : 会导致三类问题发生 
Serializable  > Repeatable read > read committed > read uncommitted
数据库隔离问题危害 脏读> 不可重复读 > 虚读


安全级别越高,处理效率越低;安全级别越低,效率高


在数据库中通过
set transaction isolation level 设置事务隔离级别
select @@tx_isolation 查询当前事务隔离级别


小实验:将演示各个级别导致隔离问题 --- 默认并发访问
打开两个mysql 窗口


1、脏读问题 read uncommitted ---- 
将B窗口隔离级别设置 read uncommitted
set transaction isolation level read uncommitted;
在A、B窗口分别开启一个事务 start transaction
在A窗口完成转账操作 
update account set money= money - 200 where name='aaa';
update account set money= money +200 where name='bbb';


在B窗口进行查询 ---- 读取到未提交转账结果
A创建回滚 rollback B窗口结果 恢复之前


2、不可重复读 read committed
将B窗口隔离级别设置 read committed
set transaction isolation level read committed;
重复刚才操作 


在A创建没有提交前,B窗口查询数据不会改变  (避免脏读)
A窗口提交 commit 
B窗口读取A窗口提交结果 (在同一个事务中 发生不可重复读)


3、虚读 Repeatable read
将B窗户 隔离级别设置 Repeatable read
set transaction isolation level Repeatable read;
在A、B创建分别开启事务
在A窗口转账 提交
B 连续查询,会发现不会读取到 A提交 数据结果 (避免不可重复读)


* 虚读发生概率很低 
A窗口插入一条数据 B窗口能够在同一个事务查询到 --- 虚读


4、演示 Serializable  串行处理效果
将B窗口级别设置 Serializable  
set transaction isolation level Serializable;
在A、B窗口同时开启事务


在B窗口查询,在A窗口插入 --- 发现A窗口阻塞
* 当B窗口操作表数据时,别窗口无法操作 


************************ 
在JDBC程序中如何控制数据库隔离级别  Connection setTransactionIsolation(int level) 
* 如果不设置隔离级别---- 采用数据库默认 


* 64位mysql5.5 无法修改隔离级别 set session transaction isolation level read committed;


----------------------------------------------------------------------------------------------
事务丢失更新问题 
两个事务同时读取同一条记录,A先修改记录,B也修改记录(B是不知道A修改过),B提交数据后B的修改结果覆盖了A的修改结果。


解决丢失更新:通过悲观锁 和 乐观锁


1、悲观锁原理,使用数据库内部锁机制,进行table的锁定,在A修改数据时,A就将数据锁定,B此时无法进行修改 ----- 无法发生两个事务同时修改
* 假设丢失更新会发生 


在mysql中默认情况下,当你修改数据,自动为数据加锁(在事务中) ---- 防止两个事务同时修改数据   ---- 读锁
* 事务和锁和不可分开的,锁一定是在事务中使用 ,当事务关闭锁自动释放


在mysql内部有两种常用锁 读锁和写锁
读锁(共享锁) 一张表可以添加多个读锁,如果表添加读锁(不是当前事务添加的),该表不可以修改
* select * from account lock in share mode;
* 共享锁非常容易发生死锁
写锁(排它锁) 一张表只能加一个排它锁,排他锁和其它共享锁、排它锁都具有互斥效果 。
* 如果一张表想添加排它锁,前提之前表一定没有加过共享锁和排他锁
* select * from account for update ;


悲观锁可以使用 排它锁实现 ----- 解决丢失更新问题


2、乐观锁原理: 使用不是数据库锁机制,而是一个特殊标记字段,如果控制字段状态和内容,得知数据是否发生并发访问!
* 假设丢失更新不会发生
* 数据库timestamp 时间戳字段
create table blog (
  id int primary key,
  title varchar(40),
  updatetime timestamp
);


insert into blog values(1,'java学习',null); ---- timestamp在数据插入时,字段生成当前时间
update blog set title = '传智播客考试' where id =1 ; ---- timestamp 在数据修改时,自动更新为当前时间


------------------------------------------------------------------------------------------------------
数据库开发中存在问题,每次客户请求,在服务器端都单独创建一个连接操作数据库,当并发访问量非常大,很容易造成内存溢出,而且创建连接、释放连接资源非常消耗服务器性能。 


连接池原理: 在服务器端一次性创建多个连接,将多个连接保存在一个连接池对象中,当请求需要操作数据库时,不会为请求创建新的连接,而是直接从连接池中获得一个连接,操作数据库结束,并不需要真正关闭连接,而是将连接放回到连接池中。
* 节省创建连接、释放连接 资源


自定义一个连接池
1、编写class 实现DataSource 接口
2、在class构造器 一次性创建10个连接,将连接保存LinkedList中
3、实现getConnection  从 LinkedList中 返回一个连接
4、提供将连接放回连接池中方法
* 当用户使用连接后,不能调用Connection的close,而要使用连接池提供关闭方法,将连接放回连接池


用户调用Connection的 close能否将连接放回连接池呢?
------------------ 修改close方法原来逻辑


Java中常用三种方法可以增强 原有方法
1、类继承 、方法覆盖 
* 必须控制对象创建,才能使用该方式 
2、装饰者模式方法加强 
* 必须和目标对象实现相同接口或继续相同父类,特殊构造器(传入被包装对象)
3、动态代理 


-----------------------------------------------------------------------------------------------------------
在实际开发中 不会自己实现连接池 ,使用开源免费数据库连接池
Apache commons-dbcp 连接池
c3p0 数据库连接池
Tomcat内部提供数据库连接池


1、当使用Apache DBCP 需要下载 commons-dbcp.jar commons-pool.jar 
* apache commons 子项目 zip包没有快速入门文档 只有API
手动设置四个参数
编写properties配置文件 ------ Properties对象加载文件


2、下载c3p0 解压 doc目录 存在c3p0 使用入门
ComboPooledDataSource 手动设置参数
配置文件 在src目录新建 c3p0-config.xml
<default-config> 
<named-config name="intergalactoApp"> 自定义配置可以有很多个
* 在实际软件系统中,测试环境、开发环境、线上数据库 是不同数据库 


Basic Pool Configuration 基本属性
    acquireIncrement  当连接池连接用完了,根据该属性决定一次性新建多少连接 
    initialPoolSize  初始化一次性创建多少个连接 
    maxPoolSize 最大连接数 
    maxIdleTime 最大空闲时间,当连接池中连接经过一段时间没有使用,根据该数据进行释放
    minPoolSize 最小连接池尺寸


当创建连接池时,一次性创建initialPoolSize 个连接,当连接使用完一次性创建 acquireIncrement  个连接,连接最大数量 maxPoolSize ,当连接池连接数量大于 minPoolSize ,经过maxIdleTime 连接没有使用, 该连接将被释放 


ComboPooledDataSource dataSource = new ComboPooledDataSource("自定义配置名称");
* 如果不存在配置,将使用默认配置


3、Tomcat内置连接池
因为tomcat和 dbcp 都是Apache公司项目,tomcat内部连接池就是dbcp 
* Tomcat 支持Servlet/JSP 容器 ,并不支持所有JavaEE 规范 ------- JNDI 
开发者通过JNDI方式 访问Tomcat内置 连接池


将web工程部署到tomcat 三种方式: 配置server.xml <Context> 元素、配置独立xml文件 <Context> 元素 、直接将网站目录复制Tomcat/webapps
虚拟目录 ---- <Context> 元素


<Context>
<Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
               maxActive="100" maxIdle="30" maxWait="10000"
               username="root" password="123" driverClassName="com.mysql.jdbc.Driver"
               url="jdbc:mysql://localhost:3306/day14"/>
</Context>


在哪里配置<Context> ???三个位置
1) tomcat安装目录/conf/context.xml --------- 对当前tomcat内部所有虚拟主机中任何工程都有效
2) tomcat安装目录/conf/Catalina/虚拟主机目录/context.xml -------- 对当前虚拟主机任何工程都有效
3) 在web工程根目录/META-INF/context.xml ------- 对当前工程有效


用JNDI访问连接池! 什么是JNDI ?
1)、将一个Java对象,绑定JNDI容器中,为java对象起一个名字
2)、Java其它程序 通过名字 检索到绑定对象


编写JNDI程序
1)、 将jar复制tomcat/lib
2)、 编写访问JNDI程序 运行Tomcat内部 ---- 通常Servlet、JSP
                        // 创建检索对象
Context initCtx = new InitialContext();
// 默认查找顶级java 名称串 固定:java:comp/env
Context envCtx = (Context) initCtx.lookup("java:comp/env");
// 根据设置名称 查找连接池对象
DataSource ds = (DataSource) envCtx.lookup("jdbc/TestDB");




总结:
1、mysql数据库控制事务 start transaction 、commit 、 rollback
* mysql默认事务自动提交,Oracle需要手动提交


2、JDBC程序控制事务 conn.setAutoCommit(false) conn.commit() conn.rollback();
* SavePoint 


3、事务四大特性 ACID 
4、如果不考虑隔离性 引发三类问题:脏读、不可重复读、虚读
5、数据库四个隔离级别 Serializable / Repeatable read / Read Committed / Read UnCommitted
* 隔离级别实验 


6、丢失更新 悲观锁和乐观锁
悲观锁 select * ... for update
乐观锁 时间戳


7、连接池原理 
8、三种方法增强 !!!!!!!!!!!!!
继承覆盖、装饰者、动态代理


9、Apache DBCP 、c3p0 、Tomcat内置连接池 使用 ------- c3p0 


10、Tomcat内置连接池 --- JNDI原理 
JNDI容器启动时,将对象绑定容器中,为对象起名字
JNDI容器中其它程序,可以通过名称 ---- 访问绑定对象




 





































































0 0
原创粉丝点击