一、事务
事务就是一个事情,组成这个事情可能有多个单元,要求这些单元,要么全都成功,要么全都不成功。
在开发中,有事务的存在,可以保证数据完整性。
事务的操作
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<Connection>
private LinkedList<Connection> ll;
ll = new LinkedList<Connection>();
- 在其构造方法中初始化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<Connection> ll; // 用于装Connection对象的容器。 public MyDataSource() throws SQLException { ll = new LinkedList<Connection>(); // 当创建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
orc3p0-config.xml
并且放置在classpath路径下(对于web应用就是classes目录),那么c3p0会自动查找。
注意:我们其时只需要将配置文件放置在src下就可以。使用:
ComboPooledDataSource cpds = new ComboPooledDataSource();
它会在指定的目录下查找指定名称的配置文件,并将其中内容加载。c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql:///day18</property> <property name="user">root</property> <property name="password">123</property> </default-config> </c3p0-config>