1.事务
1.1事务的四大特性(ACID)
- 1.原子性:事务中的所有操作要么全部执行成功,要么执行全部失败。
- 2.一致性:事务执行后,数据库状态与其它业务规则保持一致。
- 3.隔离性:隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。
- 4.持久性:一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中。即使提交事务后数据库马上崩溃,在数据库重启后,也必须能保证通过某种机制恢复数据。
1.2mysql中操作事务
在控制台中输入语句:start transaction;
即开始事务。
在控制台中输入语句:commit transaction;
即提交事务。
在控制台中输入语句:rollback;
回滚事务,即在此事务中执行的操作全部无效,数据库回到start transaction;
之前(前提是使用该语法前没有执行commit transaction;
操作)。
1.3Jdbc中操作事务
在Jdbc中处理事务都是通过Connection对象完成的,同一事务中的所有操作,都在使用同一个Connection对象。
setAutoCommit(boolean);
设置是否自动提交事务,如果为true表示自动提交(默认值就是true),也就是每条执行的sql语句都是一个单独的事务,如果设置false,那么就相当于开启了事务了。con.setAutoCommit(false);
语句表示开启事务。
con.commit();
提交并结束事务。
con.rollback();
回滚事务。
2.事务的隔离级别
2.1事务的并发读问题
- 脏读:读取到另一份事务未提交数据,即读到了脏数据。
- 不可重复读:两次读取不一致。对统一记录的两次读取不一致,因为另一事务对该记录做了修改。
- 幻读:又叫虚读。对同一张表的两次查询不一致,因为另一事务进行了插入了一条记录的操作。
2.2四大隔离级别(防止上述问题)
- a.SERIALIZABLE(串行化):不会出现任何并发问题,因为它是对同一数据的访问是串行的,非并发访问的。性能最差,可能导致死锁。
- b.REPEATABLE READ(可重复读)(mysql默认级别):防止脏读和不可重复读,不能处理幻读问题。性能比a的好。
- c.READ COMMITTED(读已提交数据)(Oracle默认级别):防止脏读,没有处理不可重复读,也没有处理幻读。性能比上述b好。
- d.READ UNCOMMITTED(读未提交数据):可能出现任何事务并发问题。性能最好。但基本没人用。
2.3查看mysql的隔离级别
在控制台中输入语句:select @@tx_isolation;
也可以通过下面命令来设置隔离级别:set transaction isolationlevel[4选1];
2.4在Jdbc中设置隔离级别
con.setTransactionisolation[int lever];
3.数据库连接池
用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、拓机。如下图所示:
数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现的尤为突出.。对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。数据库连接池正式针对这个问题提出来的。数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。如下图所示:
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中, 这些数据库连接的数量是由最小数据库连接数来设定的.无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量.连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
数据库连接池的最小连接数和最大连接数的设置要考虑到以下几个因素:
1.最小连接数(MinActive):是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费。
2.最大连接数(MaxActive):是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作。
3.如果最小连接数与最大连接数相差很大:那么最先连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接.不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,他将被放到连接池中等待重复使用或是空间超时后被释放。
3.1池参数(所有池参数都有默认值)
设置初始化大小:connection.setInitialSize();
设置最小空闲连接数:connection.setMinIdle();
设置最大空闲连接数:connection.setMaxIdle();
设置最小连接数:connection.setMinActive();
设置最大连接数:connection.setMaxActive();
设置增量:一次创建的最小单位。
设置最大的等待时间:connection.setMaxWait();
3.2四大连接参数
连接池也是使用Jdbc中的四大连接参数和驱动jar包来完成创建连接对象。
3.3实现的接口
连接池必须实现javax.sql.DataSource接口。
从连接池返回的Connection对象,它的close()方法与众不同。调用它的close()方法不是关闭,而是把连接归还给池。
3.4DBCP数据库连接池
DBCP是Apache软件基金组织下的开源连接池实现,要使用DBCP数据源,需要应用程序应在系统中增加如下两个jar文件:
- Commons-dbcp.jar:连接池的实现
- Commons-pool.jar:连接池实现的依赖库
Demo:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31public class Demo{
public static void main(String[] args)
{
/*
*1.创建连接池对象
*2.配置四大参数
*3.配置池参数
*4.得到连接对象
*/
BasicDataSource dataSource=new BasicDataSource();
dataSource.setDriverClassName(“com.mysql.jdbc.Driver”);
dataSource.setUrl(“jdbc:mysql://localhost:3306/mydb”);
dataSource.setUsername(“root”);
dataSource.setPassword(123);
dataSource.setMaxActive(20);
dataSource.setMinIdle(3);
dataSource.setMaxWait(1000);
Connection con=dataSource.getConnection();
System.out.println(con);
/*
*连接池内部使用四大参数创建了连接对象,即mysql驱动提供的Connection
*连接池使用mysql的连接对象进行了装饰,只对close()方法进行了增强!
*装饰之后的Connection的close()方法,用来把当前连接归还给池
*/
con.close();//把连接归还给池。
}
}
既然谈到装饰,那下面我们就在下文3.7中来谈谈装饰者模式。
3.5c3p0数据库连接池
c3p0,全名叫ComboPooledDataSource;
需要导入的jar包:
- 连接池的实现:c3p0-0.9.5.2.jar
- 依赖库:mchange-commons.jar
Demo:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class Demo{
public static void main(String[] args)
{
//创建连接池对象
ComboPooledDataSource dataSource=new ComboPooledDataSource();
//进行四大参数的配置
dataSource.setDriverClass(“com.mysql.jdbc.Driver”);
dataSource.setJdbcUrl(“jdbc:mysql://localhost:3306/mydb”);
dataSource.setUser("root");
dataSource.setPassword("123");
//池配置
dataSource.setAcquireIncrement(5);
dataSource.setInitialPoolSize(20);
dataSource.setMinPoolSize(2);
dataSource.setMaxPoolSize(50);
Connection con=dataSource.getConnection();
System.out.println(con);
con.close();
}
}
3.5.1c3p0配置文件的使用
配置文件要求:
文件名称:必须叫c3p0-config.xml。
文件的位置:必须在src下。
c3p0配置文件:
写入配置文件后的Demo:1
2
3
4
5
6
7
8
9public class Demo{
public static void main(String[] args)
{
//在创建连接池对象时,这个对象就会自动加载配置文件,不用我们来指定。
ComboPooledDataSource data=new comboPooledDataSource();
Connection con=data.getConnection();
System.out.println(con);
}
}
3.6Tomcat配置数据库连接池
3.6.1Tomcat配置JNDI资源
JNDI:java命名和目录接口。作用:在服务器上配置资源,然后通过统一的方式来获取配置的资源。
首先需要在Tomcat/conf/Catelina/localhost目录下新建文件名: 项目名.xml
在该.xml文件中写入以下内容
3.6.1获取资源的代码
1 | Context initCtx=new InitialContext();//创建一个上下文。 |
Demo:测试类
3.7装饰者模式
将对象增强的手段有:
- 继承
缺点:1.增强的内容是死的,不能动。2.被增强的对象也是死的。
- 装饰者模式
特点:1.增强的内容是不能修改的。2.被增强的对象可以是任意的。
- 动态代理(AOP):以后再详讲,博客出来后会给出链接。
下面通过一个简单的例子来对装饰者模式进行讲解
class 咖啡类 {};
class 加奶咖啡 extends 咖啡类 {};
class 加糖咖啡 extends 咖啡类 {};
class 加盐咖啡 extends 咖啡类 {};
咖啡 a=new 加糖咖啡();
咖啡 b=new 加盐咖啡(a);//对a进行装饰,就是给a加盐
咖啡 c=new 加奶咖啡(b);//对b进行装饰,就是给b加奶
装饰者模式在Java API中的IO流中用到的很多。如BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter、ObjectInputStream、ObjectOutputStream这几个都是运用了装饰模式的装饰流。关于的IO流的详情见下篇博客Java之IO流详解。
4.ThreadLocal
早在JDK 1.2的版本中就提供Java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
4.1Thread API
- void set(Object value);设置当前线程的线程局部变量的值。
- Object get();该方法返回当前线程所对应的线程局部变量。
- void remove();将当前线程局部变量的值删除,目的是为了减少内存的占用。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
4.2ThreadLocal内部结构
ThreadLocal内部用Map来保存数据。虽然在使用上述API时没有给出键,但其实它内部使用了当前线程作为键。内部结构见下面demo:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class ThreadLocal
{
private Map<Thread,T> map=new HashMap<Thread,T>();
public void set(T value){
map.put(Thread.currentThread(),value);
}
public void remove(){
map.remove(Thread.currentThread());
}
public T get(){
return map.get(Thread.currentThread());
}
}
5.dbtils结果集处理器介绍
需要导入的jar包:
- common-dbutil.jar
- c3p0.jar
- mchange-commons.jar
关键要得到QueryRunner对象,然后调用其各种方法。
update()方法:
1.int update(String sql,Object… params) 可执行增删改语句。
2.重载方法int update(Connection con,String sql, Object… params)需要调用者提供Connection,这说明本方法不再管理Connection了。本重载方法支持事务。query()方法:
1.T query (String sql,ResultSetHandler rsh,Object… params)可执行查询操作。
2.重载方法:T query(Connection con,String sql,ResultSetHandler rsh,Object… params); 本重载方法支持事务。它会先得到ResultSet,然后调用rsh的handle()把rs转换成需要的类型。ResultSetHandler接口
1.BeanHandler(单行)-->构造器需要一个class类型的参数,用来把一行结果转换成指定类型的javaBean对象。
2.BeanListHandler(多行)—>构造器也是需要一个Class类型的参数,用来把一行结果集转换成一个javabean,哪么多行就是转换成List对象,一堆javabean。
3.MapHandler(单行)—>把一行结果集转换成Map对象。
4.MapListHandler(多行)—>把一行记录转换成一个Map,多行就是多个Map,即List
dbutil结果处理集原理代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87package demo;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Created by codingBoy on 16/10/19.
*/
public class QR<T>
{
private DataSource dataSource;
public QR(DataSource dataSource)
{
this.dataSource=dataSource;
}
public QR(){
super();
}
public int update(String sql,Object... params)
{
Connection con=null;
PreparedStatement pstmt=null;
try
{
con=dataSource.getConnection();//通过连接池得到连接对象
pstmt=con.prepareStatement(sql);
initParams(pstmt,params);//给出参数
return pstmt.executeUpdate();//调用update执行增、删、该
}catch (Exception e)
{
throw new RuntimeException(e);
}finally {
try{
if (pstmt!=null) pstmt.close();
if (con!=null) con.close();
}catch (SQLException e){}
}
}
//给参数赋值
public void initParams(PreparedStatement pstmt,Object... params) throws SQLException {
for (int i = 0; i < params.length; i++)
{
pstmt.setObject(i+1,params[i]);
}
}
public T query(String sql,RsHandler<T> rh,Object... params) throws SQLException {
Connection con=null;
PreparedStatement pstmt=null;
ResultSet rs=null;
try
{
con=dataSource.getConnection();//通过连接池得到连接对象
pstmt=con.prepareStatement(sql);
initParams(pstmt,params);//给出参数
rs=pstmt.executeQuery();//调用update执行增、删、该
return rh.handle(rs);
}catch (Exception e)
{
throw new RuntimeException(e);
}finally {
if (rs!=null) rs.close();
if (pstmt!=null) pstmt.close();
if (con!=null) con.close();
}
}
interface RsHandler<T>
{
public T handle(ResultSet rs);
}
}
这样我们以后对数据库进行增、删、改操作时,只需写以下代码即可:
1 | 1.QueryRunner qr=new QueryRunner(JdbcUtils.getDataSource);//创建QueryRunner对象,并传入连接池对象 |
通过这简单的四步就可以对数据库进行增删改了。
对数据库进行查询操作时,只需写以下代码:1
2
3
4
5
6
7
8
9
101.QueryRunner qr=new QueryRunner(JdbcUtils.getDataSource);//创建QueryRunner对象,并传入连接池对象
2.String sql="select * from user where id=?";//给出sql语句模板
3. Object[] params={参数};//传入参数
//4. ResultSetHandler<Object> rsh=new ResultSetHandler(){
// @Override
// public Object handle(Result rs) throws SQLException{
// return null;
// }
// };
5.Object object=qr.query(sql,new BeanHandler<Object>(Object.class),params);
通过这几步即可实现对数据的查询操作了。
下面的解释写给自己看的:关于connection是否关闭的问题
在jar包中,QueryRunner类的update(没有connection参数的)方法,在finally中将connection进行了关闭;在update(有connection参数的)方法中,在finally中没有对connection进行关闭(暂时这么记吧,不然要是进行关闭了的话,在传智播客写的小工具封装类TxQueryRunner中将connection传入JdbcUtils的releaseConnecion()方法中对connection进行关闭时会出现报错)。
在讲到事务时,我们会对QueryRunner进行再次封装。上述写出的QueryRunner的代码只是包中的QueryRunner源码方法的一部分(因为源码中还有很多的重载方法),我们会通过另一个类TxQueryRunner(较QueryRunner多出的一个功能就是它支持事务)继承该类,在TxQueryrunner类中,对connection进行了判断:若connection为事务中的connection则在TxqueryRunner的update()方法中不对connection进行关闭,而是在commitTransaction()即提交事务时进行关闭;若connection为普通连接,则将connection进行关闭。那么以后我们在DAO中要获取的就不是QueryRunner对象,而是通过QueryRunner qr=new TxQueryRunner();
获取TxQueryRunner对象了。
2018.3.19更
欢迎加入我的Java交流1群:659957958。
2018.4.21更:如果群1已满或者无法加入,请加Java学习交流2群:305335626 。
6.联系
If you have some questions after you see this article,you can tell your doubts in the comments area or you can find some info by clicking these links.