一、事务

事务就是一个事情,组成这个事情可能有多个单元,要求这些单元,要么全都成功,要么全都不成功。

在开发中,有事务的存在,可以保证数据完整性。

事务的操作

create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
insert into account values(null,'aaa',1000);
insert into account values(null,'bbb',1000);
insert into account values(null,'ccc',1000);

mysql下怎样操作

方式1:

start transaction 开启事务
rollback 事务回滚(回滚到最开始位置)
commit 事务提交(没有commit就不会修改数据)

方式2:

show variables like '%commit%'; 可以查看当前autocommit值.在mysql数据库中它的默认值是"on",代表自动事务(执行任何一条mysql语句都会自动提交事务).

测试:将autocommit的值设置为off

1.set autocommit=off 关闭自动事务。

2.必须手动commit才可以将事务提交。

注意:mysql默认autocommit=on oracle默认的autocommit=off;

jdbc下怎样操作

java.sql.Connection接口中有几个方法是用于可以操作事务

1.setAutocommit(boolean flag) 如果flag=false;它就相当于start transaction;

2.rollBack() 事务回滚

3.commit() 事务提交

// 随便抛异常版,仅限演示,开发中不这么写
public class TransactionTest1 {
public static void main(String[] args) throws SQLException {
// 修改id=2这个人的money=500;
String sql = "update account set money=500 where id=2";
Connection con = JdbcUtils.getConnection();
con.setAutoCommit(false); //开启事务,相当于 start transaction;
Statement st = con.createStatement();
st.executeUpdate(sql);
//事务回滚
//con.rollback();
con.commit(); //事务提交
st.close();
con.close();
}
}
// 开发中应该这么写
public class TransactionTest2 {
public static void main(String[] args) {
// 修改id=2这个人的money=500;
String sql = "update account set money=500 where id=2";
Connection con = null;
Statement st = null;
try {
con = JdbcUtils.getConnection();
con.setAutoCommit(false); // 开启事务,相当于 start transaction;
st = con.createStatement();
st.executeUpdate(sql);
} catch (SQLException e) {
e.printStackTrace();
// 事务回滚
try {
con.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
try {
con.commit(); // 事务提交
st.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

二、事务特性(重点)

原子性:事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

一致性:事务前后数据的完整性必须保持一致。

隔离性:多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。

持久性:一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

如果不考虑事务的隔离性,会出现什么问题?

脏读:一个事务读取到另一个事务的未提交数据

不可重复读:两次读取的数据不一致(强调update)

虚读(幻读):两次读取的数据不一致(强调insert)

丢失更新:两个事务对同一条记录进行操作,后提交的事务,将先提交的事务的修改覆盖了。

解决方案

事务的隔离级别有哪些?

Serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)
Repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)不可以避免虚读
Read committed:可避免脏读情况发生(读已提交)
Read uncommitted:最低级别,以上情况均无法保证。(读未提交)

怎样设置事务的隔离级别?

1.mysql中设置

1.查看事务隔离级别

select @@tx_isolation 查询当前事务隔离级别(默认为Repeatable read).

扩展:oracle中默认是Read committed

2.mysql中怎样设置事务隔离级别

set session transaction isolation level 事务隔离级别

2.jdbc中设置

使用java.sql.Connection接口中提供的方法

void setTransactionIsolation(int level) throws SQLException

参数level可以取以下值:

level - 以下 Connection 常量之一:

Connection.TRANSACTION_READ_UNCOMMITTED、
Connection.TRANSACTION_READ_COMMITTED、
Connection.TRANSACTION_REPEATABLE_READ
Connection.TRANSACTION_SERIALIZABLE。

(注意,不能使用 Connection.TRANSACTION_NONE,因为它指定了不受支持的事务。)

演示

1.脏读

一个事务读取到另一个事务的为提交数据

设置A,B事务隔离级别为 Read uncommitted

set session transaction isolation level read uncommitted;

1.在A事务中

start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb';

2.在B事务中

start transaction;

select * from account;

这时,B事务读取时,会发现,钱已经汇完。那么就出现了脏读。

当A事务提交前,执行rollback,在commit, B事务在查询,就会发现,钱恢复成原样

也出现了两次查询结果不一致问题,出现了不可重复读.

2.解决脏读问题

将事务的隔离级别设置为 read committed来解决脏读

设置A,B事务隔离级别为 Read committed

set session transaction isolation level read committed;

1.在A事务中

start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb';

2.在B事务中

start transaction;
select * from account;

这时B事务中,读取信息时,是不能读到A事务未提交的数据的,也就解决了脏读。

让A事务,提交数据 commit;

这时,在查询,这次结果与上一次查询结果又不一样了,还存在不可重复读。

3.解决不可重复读

将事务的隔离级别设置为Repeatable read来解决不可重复读。

设置A,B事务隔离级别为 Repeatable read;

set session transaction isolation level Repeatable read;

1.在A事务中

start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb';

2.在B事务中

start transaction;
select * from account;

当A事务提交后commit;B事务在查询,与上次查询结果一致,解决了不可重复读。

4.设置事务隔离级别Serializable ,它可以解决所有问题

set session transaction isolation level Serializable;

如果设置成这种隔离级别,那么会出现锁表。也就是说,一个事务在对表进行操作时,

其它事务操作不了。

总结

脏读:一个事务读取到另一个事务为提交数据

不可重复读:两次读取数据不一致(读提交数据)---update

虚读:两次读取数据不一致(读提交数据)----insert

事务隔离级别:

read uncommitted 什么问题也解决不了.
read committed 可以解决脏读,其它解决不了.
Repeatable read 可以解决脏读,可以解决不可重复读,不能解决虚读.

Serializable 它会锁表,可以解决所有问题.

安全性:serializable > repeatable read > read committed > read uncommitted

性能 :serializable < repeatable read < read committed < read uncommitted

结论: 实际开发中,通常不会选择 serializable 和 read uncommitted ,

mysql默认隔离级别 repeatable read ,oracle默认隔离级别 read committed

三、丢失更新

多个事务对同一条记录进行了操作,后提交的事务将先提交的事务操作覆盖了。

解决办法:

悲观锁:(假设丢失更新一定会发生 ) ----- 利用数据库内部锁机制,管理事务

提供的锁机制

1.共享锁

select * from table lock in share mode(读锁、共享锁)

2.排它锁

select * from table for update (写锁、排它锁)

update语句默认添加排它锁

乐观锁:(假设丢失更新不会发生) ----- 采用程序中添加版本字段解决丢失更新问题

解决丢失更新:在数据表添加版本字段,每次修改过记录后,版本字段都会更新,如果读取是版本字段,与修改时版本字段不一致,说明别人进行修改过数据 (重改)

四、连接池

就是创建一个容器,用于装入多个Connection对象,在使用连接对象时,从容器中获取一个Connection,使用完成后,在将这个Connection重新装入到容器中。这个容器就是连接池。(DataSource)也叫做数据源.

我们可以通过连接池获取连接对象.

优点:节省创建连接与释放连接 性能消耗 ---- 连接池中连接起到复用的作用 ,提高程序性能

自定义连接池

创建一个MyDataSource类,在这个类中创建一个LinkedList

private LinkedList ll;
ll = new LinkedList();
在其构造方法中初始化List集合,并向其中装入5个Connection对象
for (int i = 0; i < 5; i++) {
Connection con = JdbcUtils.getConnection();
ll.add(con);
}

创建一个public Connection getConnection() 从List集合中获取一个连接对象返回.

创建一个public void readd(Connection) 这个方法是将使用完成后的Connection对象重新装入到List集合中.

代码问题

1.连接池的创建是有标准的.

在javax.sql包下定义了一个接口 DataSource

简单说,所有的连接池必须实现javax.sql.DataSource接口,

我们的自定义连接池必须实现DataSource接口。

2.我们操作时,要使用标准,怎样可以让 con.close()它不是销毁,而是将其重新装入到连接池.

要解决这个问题,其本质就是将Connection中的close()方法的行为改变。

怎样可以改变一个方法的行为(对方法功能进行增强)

1.继承

2.装饰模式

1.装饰类与被装饰类要实现同一个接口或继承同一个父类

2.在装饰类中持有一个被装饰类引用

3.对方法进行功能增强。

3.动态代理

可以对行为增强

Proxy.newProxyInstance(ClassLoacer ,Class[],InvocationHandler);

结论:Connection对象如果是从连接池中获取到的,那么它的close方法的行为已经改变了,不在是销毁,而是重新装入到连接池。

方法增强

继承增强(不好)

public class Demo1 {
public static void main(String[] args) {
Person1 p=new Student1();
p.eat();
}
}
class Person1 {
public void eat(){
System.out.println("吃两个馒头");
}
}
class Student1 extends Person1 {
public void eat(){
super.eat();
System.out.println("加两个鸡腿");
}
}

装饰模式(不好)

动态代理

import javax.sql.DataSource;
public class MyDataSource implements DataSource {
private LinkedList ll; // 用于装Connection对象的容器。
public MyDataSource() throws SQLException {
ll = new LinkedList();
// 当创建MyDateSource对象时,会向ll中装入5个Connection对象。
for (int i = 0; i < 5; i++) {
Connection con = JdbcUtils.getConnection();
ll.add(con);
}
}
public Connection getConnection() throws SQLException {
if (ll.isEmpty()) {
for (int i = 0; i < 3; i++) {
Connection con = JdbcUtils.getConnection();
ll.add(con);
}
}
final Connection con = ll.removeFirst();
Connection proxyCon = (Connection) Proxy.newProxyInstance(con.getClass().getClassLoader(), con.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
if ("close".equals(method.getName())) {
// 这代表是close方法,它要做的事情是将con对象重新装入到集合中.
ll.add(con);
System.out.println("重新将连接对象装入到集合中");
return null;
} else {
return method.invoke(con, args);// 其它方法执行原来操作
}
}
});
return proxyCon;
}
}

五、dbcp连接池(了解)

导入两个jar包:commons-dbcp-1.4.jar和commons-pool-1.5.6.jar

手动配置(手动编码)

BasicDataSource bds = new BasicDataSource();
// 需要设置连接数据库最基本四个条件
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql:///day18");
bds.setUsername("root");
bds.setPassword("abc");
// 得到一个Connection
Connection con = bds.getConnection();

示例:

```java
public class JdbcDemo{
public void test() throws Exception {
BasicDataSource bds = new BasicDataSource();
// 需要设置连接数据库最基本四个条件
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql:///day18");
bds.setUsername("root");
bds.setPassword("123");
Connection con = bds.getConnection();
ResultSet rs = con.createStatement().executeQuery("select * from account");
while(rs.next()){
System.out.println(rs.getInt("id")+" "+rs.getString("name"));
}
rs.close();
con.close();
}
public static void main(String[] args) throws Exception {
JdbcDemo jd = new JdbcDemo();
jd.test();
}
}
```
自动配置(使用配置文件)
Properties props = new Properties();
FileInputStream fis = new FileInputStream("D:\\java1110\\workspace\\day18_2\\src\\dbcp.properties");
props.load(fis);
DataSource ds = BasicDataSourceFactory.createDataSource(props);

示例:

dbcp.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///day18
username=root
password=123
JdbcDemo.java
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import java.sql.*;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.util.Properties;
public class JdbcDemo {
public void test2() throws Exception {
Properties props = new Properties();
// props.setProperty("driverClassName","com.mysql.jdbc.Driver");
// props.setProperty("url","jdbc:mysql:///day18");
// props.setProperty("username","root");
// props.setProperty("password","123");
FileInputStream fis = new FileInputStream("D:\\code\\java\\JDBC\\src\\dbcp.properties");
props.load(fis);
DataSource ds = BasicDataSourceFactory.createDataSource(props);
Connection con = ds.getConnection();
ResultSet rs = con.createStatement().executeQuery("select * from account");
while(rs.next()){
System.out.println(rs.getInt("id")+" "+rs.getString("name"));
}
rs.close();
con.close();
}
public static void main(String[] args) throws SQLException {
JdbcDemo jd = new JdbcDemo();
jd.test2();
}
}

六、c3p0连接池(必须掌握)

C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。

dbcp没有自动回收空闲连接的功能,c3p0有自动回收空闲连接功能,它的性能更强大。

导入包:c3p0-0.9.5.2.jar

手动

ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql:///day18");
cpds.setUser("root");
cpds.setPassword("abc");

事例:

public void test() throws Exception {
BasicDataSource bds = new BasicDataSource();
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql:///day18");
cpds.setUser("root");
cpds.setPassword("123");
Connection con = cpds.getConnection();
ResultSet rs = con.createStatement().executeQuery("select * from account");
while(rs.next()){
System.out.println(rs.getInt("id")+" "+rs.getString("name"));
}
rs.close();
con.close();
}
自动(使用配置文件)
c3p0的配置文件可以是properties也可以是xml.
c3p0的配置文件如果名称叫做 c3p0.properties or c3p0-config.xml 并且放置在classpath路径下(对于web应用就是classes目录),那么c3p0会自动查找。
注意:我们其时只需要将配置文件放置在src下就可以。
使用:ComboPooledDataSource cpds = new ComboPooledDataSource(); 它会在指定的目录下查找指定名称的配置文件,并将其中内容加载。
c3p0-config.xml
com.mysql.jdbc.Driver
jdbc:mysql:///day18
root
123