1.log4j概述
log4j log for java专门为Java提供的日志框架
是目前公司中Java语言收集日志的主流日志框架
a.特点:
收集的优先级
收集数据的目的地 文件 console
收集数据的展示形式 HTML pattern
2.如何使用log4j
a.在程序中导入log4j 的jar包
b.书写配置文件
log4j的配置文件无需学习书写,只需掌握阅读方式,及使用方式即可,可以找到一份较完整的配置文件,在其中按照自己的要求做出相应的修改,即可变成自己所需的配置文件。
i.在配置文件中,顶部的管道可以删除,但是如果管道存在,则必须添加对应的配置信息,选择输出的目的地。
c.日志数据的优先级
所谓日志的优先级就是日志的收集级别。指定收集级别之后,log4j会将当前级别或以上级别的内容同时收集。
off | 最高级别,用于关闭所有的日志文件 |
---|---|
fatal | 指出每个严重的错误事件将会导致应用程序的退出 |
error | 指出虽然发生错误事件,但仍然不影响系统的继续运行。 |
warn | 表明会出现潜在的错误情形 |
info | 一般和在粗粒度级别上,强调应用程序的运行全程 |
debug | 一般用于细粒度级别上,对调试应用程序非常有帮助。 |
all | 最低等级,用于打开所有日志文件 |
d.数据输出的目的地
所谓日志输出的目的地,指的是日志内容向何处输出,输出方向可以是console,也可以是一个文件
org.apache.log4j.ConsoleAppender | 控制台 |
---|---|
org.apache.log4j.FileAppender | 文件 |
org.apache.log4j.DailyRollingFileAppender | 每天产生一个日志文件 |
org.apache.log4j.RollingFileAppender | 文件大小到达指定尺寸的时候产生一个新的文件 |
org.apache.log4j.WriterAppender | 将日志信息以流格式发送到任意指定的地方 |
e.数据输出格式
所谓日志输出格式,就是日志以何种形式向目的地输出,这种形式可以实HTML结构,也可以是自定义结构
org.apache.log4j.HTMLLayout | 以HTML表格形式布局 |
---|---|
org.apache.log4j.PatternLayout | 可以灵活的指定布局格式 |
org.apache.log4j.SimpleLayout | 包含日志信息的级别和信息字符串 |
org.apache.log4j.TTCCLayout | 包含日志产生的时间,线程,类别等等信息 |
f.pattern配置
%p | 输出优先级,即debug,info,warn,error,fatal |
---|---|
%r | 输出自应用启动到输出该log信息耗费的毫秒数 |
%c | 输出所属的类目,通常就是所在类的全名 |
%t | 输出产生该日志事件的线程名 |
%n | 输出一个回车换行符,window平台为“rn” unix平台为“n” |
%d | 输出日志时间点的日期和时间,默认格式为ISO8859-1,也可以在其后指定格式 |
%l | 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数 |
%m | 输出的日志信息内容 |
3.创建日志对象
引入jar包
配置文件log4j.properties
Logger logger=Logger.getLogger(Demo1.class);
日志信息需要通过Logger对象输出
4.代码实现
package cn.tedu.log4j;
import org.apache.log4j.Logger;
//log4j日志文件测试
public class Demo1 {
public static Logger logger=Logger.getLogger(Demo1.class);
public static void main(String[] args) {
logger.fatal("这是一个致命的日志");
logger.error("这是一个错误的日志");
logger.warn("这是一个警告日志");
logger.info("这是一个普通日志");
logger.debug("这是一个测试日志");
System.out.println("完成.....");
}
}
5.修改EasyMall
导入jar包
配置文件log4j.properties
修改代码(所在的监听器里边修改代码,由控制台输出转为文件)
事务
1.事务概述:
在一个事件中,有多个组成单元,这些单元要么全部成功,要么全部失败,这个事件就可以称之为一个事务
2.案例:
转账:
update user set money = money-100 where name=‘a’;
update user set money = money+100 where name=‘b’;
为了保证两句sql同时成功或失败,需要将他们写入数据库的一个事务中
3.在sql端书写事务
数据库中事务里的sql语句,可以选择提交或回滚,如果提交,数据库中的数据才会发生修改,如果回滚,数据库中的数据,会恢复到修改之前的状态。
start transaction | 开启事务 |
---|---|
commit | 提交事务 |
rollback | 回滚事务 |
4.使用jdbc控制事务
在jdbc中,一个sql语句就是一个事务,现在需要多条sql位于一个事务中,所以需要开启事务
conn.setAutoCommit(false) | 开启事务,默认值为true,代表开启自动提交,一句sql就是一个事务,修改为false,代表关闭自动提交,意味着开启使用,多个sql可以在同一个事务中执行 |
---|---|
conn.commit() | 提交事务 |
conn.rollback() | 回滚事务 |
conn.rollback(sp) | 回滚到保存点 |
代码实现:
package cn.tedu.trans;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Savepoint;
//事务测试
public class TransDemo1 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
Savepoint sp=null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/day16","root","root");
//开启事务
conn.setAutoCommit(false);//默认值为true,代表会自动提交
//自动提交代表一句sql就是一个事务,执行sql就会立刻提交这个事务,修改数据库中的值
//设置为false,代表不会自动提交,表示开启事务,需要手动提交或回滚
ps=conn.prepareStatement("update user set money = money-100 where name=?");
ps.setString(1, "a");
ps.executeUpdate();
ps=conn.prepareStatement("update user set money = money+100 where name=?");
ps.setString(1, "b");
ps.executeUpdate();
//添加保存点(这样上边的sql可以执行,并且保留,下边的抛弃)
sp=conn.setSavepoint();
ps=conn.prepareStatement("update user set money = money-100 where name=?");
ps.setString(1, "a");
ps.executeUpdate();
int i=1/0;//会报错然后不执行下一个sql,需要回滚
ps=conn.prepareStatement("update user set money = money+100 where name=?");
ps.setString(1, "b");
ps.executeUpdate();
conn.commit();//在sql书写完成之后,选择使用commit方法提交事务
} catch (Exception e) {
if(conn!=null){
try {
if(sp!=null){
conn.rollback(sp);//报错之后,保存保存点之前的内容,回滚事务,回滚到保存点
conn.commit();//提交剩下的代码
}
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
}finally{
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
rs=null;
}
}
if(ps!=null){
try {
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
ps=null;
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
conn=null;
}
}
}
}
}
事务的四大特性:
a.原子性(Atomicity):原子性是指事务是一个不可分隔的工作单位,,事务中的操作要么都发生要么都不发生。
b.一致性(Consistency):事务前后的完整性必须保持一致
c.隔离性(Isolation):事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干预,并发事务之间的数据要相互隔离。
d.持久性(Durability):持久性是指一个事务一旦被提交,他对数据库中数据的改变就真实的发生了,接下来无论做什么,哪怕是数据库故障也无法撤销这个事务。
2.隔离性
数据库的其它三大特性数据库可以帮我们保证,我隔离性我们需要讨论(根据需求确定)
如果我们数据库的设计者,该如何考虑设计数据库,保证数据库的隔离性。
我们知道数据库的隔离性问题本质上是多线程并发安全问题,所以可以从数据设计读和写来分析。
i.两个事务同时读取数据,这种情况不会产生线程安全问题,因为两个事务对应的线程都未对数据进行修改,任何人读取的数据内容都相同。
ii.两个事务同时写入数据
这种情况一定会出现线程安全问题,因为两个事务对应的线程都会对数据进行修改,第二次修改可能会覆盖第一次修改,所以应该将两个事务严格分开。可以通过修改数据库的隔离级别,来保证两个事务分别执行,避免出现线程安全问题。
iii.一个事务写,一个事务读
在面对一个事务写,一个事务读这种情况时,可以选择修改数据库的隔离级别达到自己想要的效果。如果要求数据库安全性与正确性,则可以选择最高的隔离级别。如果要求数据的写入速度,则可以选择数据库的最低隔离级别。多数情况需要两者谦顾,可以选择中间两个隔离级别。面对数据库中存储的脏读,不可重复读,虚读/幻读这些情况,也可以根据自己的需求,修改数据库的隔离级别。
防止这些问题的隔离级别,推荐使用repeatable read。原因是这个级别,不会出现脏读,和不可重复读。而虚读出现的情况极少。可以认为当前隔离级别是安全且能够保证执行效率的。l
//创建一个数据库
create database day16;
use day16;
//创建表格
cerate table user(
id int,
name varchar(20),
money int
);
insert into user values(1,'a',1000);
insert into user values(2,'b',1000);
cerate table account(
name varchar(20),
hq int,
dq int,
gq int
);
insert into account values ('a',1000,1000,1000);
//开启事务
start transaction;
update user set money=money-100 where name='a';
update user set money=money+100 where name='b';
//提交
commit;
//设置隔离级别(不是全局(global))临时修改
set transaction isolation level read uncommitted;
2.情景分析:
脏读:一个事务读取到另一个未提交事务的数据,导致数据前后读取不一致。
a:1000买家
b:1000商家
a:start transaction
update user set money=money-100 where name='a';
update user set money=money+100 where name='b';
-------------------------------------------------
b:start transaction
select * from user where name='b';-----1100
----------------------------------------------
a:rollback
b:select * from user where name='b';-----1000
3.情景分析:
内容不一致,这种现象被称为不可重复读(读到的会与之前的不一样,所以不能重复的读)
a:1000 1000 1000
b:start transaction
select hq from account where name='a';-----1000
select dq from account where name='a';-----1000
select gq from account where name='a';-----1000
-------------------------------------------------
a:start transaction;
update account set hq=hq-100 where name='a';
commit;
-------------------------------------------
select hq+dq+gq from account where name='a';---2900
hq 1000
dq 1000
gq 1000
sum 2900
虚读/幻读:在整表操作中,一个事务读取到另一个已经提交事务的数据,导致事务提交前后数据不一致的现象,这种现象称之为虚读/幻读
情景分析:
---------------------------------------------------
a:1000
b:2000
-----------------------------------------------
d:
start transaction;
select sum(money) from user;---3000
select account(money) from user;----2
c:3000
start transaction;
insert into user values('c',3000);
commit;
---------------------------------
d:select avg(money) from user;----1500
4.数据隔离级别:
read uncommitted 会出现脏读现象,以及不可重复读和虚读幻读,数据库安全级别最低,但是效率最高;
read committed 可以防止脏读,不能防止不可重复读及虚读/幻读,数据库安全级别较低,效率较高
repeatable read 可以防止脏读和不可重复读,不能防止虚读/幻读(mysql的默认隔离级别)安全性较高,效率较低
serializable 可以防止脏读和不可重复读和虚读幻读,他是mysql最高级别的隔离,是一个串行化的处理方式,但是执行效率较低
//查看隔离级别
select @@tx_isolation
级别的选择
安全:serializable>repeatable read>read committed>read uncommitted
性能:read uncommitted>read committed>repeatable read>serializable
i.由于read uncommitted隔离级别过低,安全性保证较差,所以实际开发中使用的较少,serializable的隔离级别过高,安全性虽然较高但是效率极低,对于开发影响较大,所以实际开发中很少用。
ii.read committed和repeatable read两个隔离级别较为实用,对于数据安全性和语句执行效率都有较好的保证,开发时多数会使用俩者之一
5.修改数据库的隔离级别
set global/session transaction isolation level read uncommitted
set transaction isolation level read uncommitted;临时修改,只对下一次的事务生效
6.查询当前数据库的隔离级别
select @@tx_isolation;
7.数据库中的锁
共享锁和排它锁
a.在非serializable隔离级别之下,查询不加锁,增删改添加排它锁
b.在serializable隔离级别之下,查询添加共享锁,增删改添加排它锁
c.特点:
i.共享锁和共享锁可以共存
ii.共享锁和排它锁不能共存
iii.排它锁和排它锁不能共存
8.~表级锁和行级锁
表级锁:添加锁之后,当前表格,不允许另外的事务操作
行级锁:添加锁之后,当前表格中的某一行或几行,不允许另外的事务操作
9.死锁
两个线程相互等待对方释放资源,这种现象称之为死锁
解决死锁的方案:
i.销毁其中任意一个线程
ii.修改代码