一 事务
(一)事务的概念
事务指逻辑上一组操作,组成这组操作的各个单元,要么全部成功要么全部不成功。 例如:A--B转账,对应如下的两条sql语句:
Update account set money = money-100 where name=’a’;;
Update account set money = money+100 where name=’b’;
数据库默认事务时自动提交的,也就是发一条sql它就执行一条,如果想多条sql放在一个事务中,需要如下。
(二)数据库开启事务cmd命令
开启事务:start transaction
回退事务:rollback
提交事务:commit
(三)JDBC开启事务
1.一般事务
conn.setAutoCommit(false);//设置事务不自动提交。
Conn.commit();//后提价事务。
Conn.rollback();//出现异常,回退事务。
Connection conn=null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = DriverManager.getConnection
("jdbc:mysql://localhost:3306/mytransation", "root", "root");
//设置不自动提交
conn.setAutoCommit(false);
ps = conn.prepareStatement("update account set money=money-100 where name=?");
ps.setString(1, "a");
ps.executeUpdate();
//假设异常点
/*int a=1/0;*/
ps = conn.prepareStatement("update account set money=money+100 where name=?");
ps.setString(1,"b");
ps.executeUpdate();
//在这里提交事务
conn.commit();
}
catch(Exception e){
try {
//出现异常回退事务
if(conn!=null)
conn.rollback();
System.out.println("出现异常,事务回退");
} catch (SQLExcep tion e2) {
e2.printStackTrace();
}
System.out.println(e.getMessage());
if(conn!=null){
try {
conn.close();
} catch (SQLException e1) {
e1.printStackTrace();
System.out.println("Connection异常");
} finally{
conn = null;
}
2.设置回滚点提交事务,提交异常之前的sql语句
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
Savepoint sp = null;
try{
conn = DriverManager.getConnection("jdbc:mysql:///mytransation", "root", "root");
conn.setAutoCommit(false);//设置不自动提交事务
ps = conn.prepareStatement("update account set money=money-100 where name=?");
ps.setString(1, "a");
ps.executeUpdate();
ps = conn.prepareStatement("update account set money=money+100 where name=?");
ps.setString(1,"b");
ps.executeUpdate();
sp = conn.setSavepoint();//设置回滚点
ps = conn.prepareStatement("update account set money=money-100 where name=?");
ps.setString(1, "a");
ps.executeUpdate();
int a=1/0;//模拟异常
ps = conn.prepareStatement("update account set money=money+100 where name=?");
ps.setString(1,"b");
ps.executeUpdate();
conn.commit();//提交事务
} catch(Exception e){
try{
if(conn !=null){//如果sp为空,则在回滚点之前回退模式
if(sp == null){
conn.rollback();
}else{//否则回滚到回滚点的数据,让其提交。
conn.rollback(sp);
conn.commit();
}
}
}
catch(SQLException e1){
}
}
3.事务的四大特性(ACID,很重要)。
原子性(Atomicity):事务时一个不可分割的工作单位,事务中操作 要么发生,要么都不发生。
一致性(Consistency):事务前后数据的完整性必须保持一致。
隔离性(Isolation):多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰。多个并发事务之间数据要相互隔离。
持久性(Durability):事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
如果不考虑事务的隔离性,会出现的问题:
(1)脏读:一个事务读取到另外一个事务的未提交的数据
(2)不可重复读: 同一个数据(表)不可重复读(针对于update更新数据)
(3)虚读:同一数据(表)不可重复读(强调insert)
(4)丢失更新:
4.隔离级别以及隔离级别的设置
(1)隔离级别
Read uncommitted 数据库不能防止脏读、不可重复读、虚读。
Read committed(oracle) 能防止脏读,不能防止不可重复读、虚读。
Repeatable read(mysql) 能防止脏读、不可重复读,不能防止虚读。
Serializable 单线程数据库,能防止所有的问题。
性能分析:read uncommitted>read committed>repeatable read>serializable
安全性能:serializable>repeatable read>read committed>read uncommitted
(2)隔离级别的设置
1)cmd操作命令
Select @@tx_isolation 查询当前事务隔离级别
Set session transaction isolation level 设置的事务级别
2)JDBC中操作隔离级别
Connection的实例方法setTransactionIsolation(int level)
可以的值是(Connection的静态常量):
TRANSACTION_READ_UNCOMMITTED(不能防止脏读,不可重复读,虚读)
TRANSACTION_READ_COMMITTED(能防止脏读,不能解决不可重复读,虚读)
TRANSACTION_REPEATABLE_READ(能防止脏读,不可重复读,不能解决虚读)
TRANSACTION_SERIALIZABLE(可以解决所有的问题)
5.脏读、不可重复读、虚读案例演示。
(1)脏读演示
一开始状态:A账户:1000元
B 账户:1000元
A账户向B账户汇款的情况:
start transaction
Update account set money=money-100 where name=’A’;
Update account set money=money+100 where name=’B’;
-------此时B查询,设置隔离级别为read uncommitted
Select * from account where name=’B’;结果为1100;
Rollback;回滚事务,数据并未修改成功。
(2)不可重复读的
a 账户三个月工资为:1000,1000,1000
Start transaction
Select sum(money) from account where name=’a’;
-------此时 财务b,将第一个月工资修改为1300,并且立即提交,即
Update account set money=1300 where name=’a’;
Select sum(money)/count(*) from account where name=’a’;提交,发现报表不对。
(3)虚读
6.转账小案例,事务控制。
1)逻辑层(service)调用dao层的两个方法(一个转入方法,一个转出方法),解决其中一个方法失败,因此需要事务控制,事务回滚等。
2)用到事务,那么就应该使用同一个Connection对象,因此,在service层的方法里,应该共用一个Connection对象。
3)mysql,修改不存在的用户名的账户值返回值代表影响的行数,由此可以找到解决该方法的问题。
4)学会使用同一个ThreadLocal,可以理解为同一个线程的容器,只要在同一个线程,就可以取出来使用。示例:
Private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
Public static Connection getCon(){
If(tl.get==null){
Tl.set(DriverManager.getConnection(“”,””,””));
}
Return tl.get();
}
7.数据丢失更新。
1)数据丢失更新示意图(多个事务对同一行数据进行操作,后提交的事务将覆盖先提交的事务)。
2)数据更新丢失的解决方法
(1)悲观锁(假设丢失更新一定发生,利用数据库内部锁机制)
共享锁:
Select * from account lock in share mode(读锁,共享锁)
排它锁(被锁中的可以是一行数据,也可以是整张表):
Select * from account for update(写锁,排它锁)。
Update 默认是排他锁。
(2)乐观锁(加锁丢失更新不会发生,采用程序中添加版本字段解决丢失更新问题)
create table product (
id int,
name varchar(20),
updatetime timestamp
);
insert into product values(1,'冰箱',null);
update product set name='洗衣机' where id = 1;
解决丢失更新:在数据表添加版本字段,每次修改过记录后,版本字段都会更新,如果读取是版本字段,与修改时版本字段不一致,说明别人进行修改过数据 (重改)
8.数据库连接池
(1)定义:创建一个容器,这个容器来装多个Connection对象,在使用该对象的时候,从容器获取一个Connection对象,使用完毕后,再把这个Connection对象重新装到容器当中。那么这个容器就叫做数据库连接池。
(2)自定义连接池
步骤:1.创建一个MyDataSource类,在这个类中创建一个LinkedList<Connection>
2.在构造器方法中初始化List集合,并向其中装入5个Connection对象
3.创建一个public Connection getConnection();从List集合中获取一个连接对象返回
4.创建一个Public void readdd(Connetion)这个方法是将使用完成后的Connection对象重新装到List集合中。
代码问题:
1.连接池的创建时有标准的,在javax.sql包下定义一个接口DataSource,我们必须实现
2.改变Connection的close()方法,不是销毁它,而是将它重新装入到连接池中。解决这
个问题本质就是将Connection的close()行为改变。
三种方式改变方法行为:继承;装饰者模式;动态代理
(3)使用开源连接池
1.dbcp(DataBase Connection Pool)
Dbcp是Apache下的一个开源连接池,两个关键.jar包(commons-dbcp-1.4.jar和commons-pool-1.5.6.jar)
1)手动配置
BasicDataSource bds = new BasicDataSource();
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql:///mytransation");
bds.setUsername("root");
bds.setPassword("root");
Connection con = bds.getConnection();
ResultSet rs = con.prepareStatement("select * from account").executeQuery();
while(rs.next()){
System.out.println("账户:"+rs.getString("name")+",余额:"+rs.getDouble("money"));
}
JDBCUtils.closeRS(rs);
con.close();//不是关闭连接,而是放回连接池
2)自动配置
Properties p = new Properties();
p.load(new FileInputStream("D:\\Myeclipse java程序\\AccountTransaction\\src\\dbcp.properties"));2.c3p0 (重点,是一个开源数JDBC连接池,实现数据源和JNDI绑定,支持JDBC3和JDBC2的标准扩展。使用它的开源项目有Hibernate,Spring等。性能更强,拥有自动回收空闲连接功能)
1)手动配置
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql:///mytransation");
cpds.setUser("root");
cpds.setPassword("root");
Connection con = cpds.getConnection();
ResultSet rs = con.prepareStatement("select * from account").executeQuery();
while(rs.next()){
System.out.println("账户:"+rs.getString("name")+",余额:"+rs.getDouble("money")+"c3p0hand");
}
JDBCUtils.closeRS(rs);
con.close();//不是关闭连接,而是放回连接池
2)自动配置
C3p0的配置文件可以是properties也可以是xml,对应的文件名是c3p0.properties或者c3p0-config.xml,要求放在classpath路径下(也就是web应用的classes目录),我们放在src目录下即可。
使用:ComboPooledDataSource cpds = new ComboPooledDataSource();
Xml文件形式:
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>Properties形式:
(4)tomcat内置连接池(测试出来!!)
Tomcat连接池用的是dbcp。
1)tomcat怎样管理连接池
要想将一个dbcp连接池让tomcat管理,只需要创建一个context.xml配置文件,在配置文件中配置相关信息。
<Context>
<Resource name=”jdbc/EmployeeDB” auth=”Container”
Type=”javax.sql.DataSource” username=”root” password=”root”
driverClassName=”com.mysql.jdbc.Driver” url=”jdbc:mysql:///mytransat”
maxActive=”8” maxIdle=”4”
/>
</Context>
问题:context.xml文件的配置位置:
(1)在tomcat/conf/context.xml 这个连接池是给整个服务器用的(全局)
(2)在tomcat/conf/Catalina/localhost 连接池只给localhost虚拟主机使用(全局)。
(3)将其配置在web应用的META-INF下。
注意:如果是全局设置,那么需要将数据库驱动放置在tomcat/lib目录下。
问题:怎么从tomcat获取链接池对象?
Context context = new InitialContext();
Context envCtx = (Context) context.lookup(“java:comp/env”);//固定路径
DataSource ds = (DataSource)envCtx.lookup(“jdbc/EmployeeDB”);
.......操作Connection对象