事务与连接池

事务与连接池

事务

事务介绍

事务的概念

事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功

数据库开启事务命令

start transaction  开启事务

Rollback  回滚事务

Commit   提交事务

 

Mysql中使用事务

1.创建表

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);

 

2、MySQL中事务默认自动提交的,每当执行一条SQL,就会提交一个事务 (一条SQL 就是一个事务)

Oracle 中事务默认 不自动提交,需要在执行SQL 语句后 通过commint 手动提交事务 

 

3、mysql管理事务

方式一 :同时事务管理SQL 语句 

start transaction  开启事务

rollback  回滚事务 (将数据恢复到事务开始时状态)

commit   提交事务 (对事务中进行操作,进行确认操作,事务在提交后,数据就不可恢复)

 

方式二:数据库中存在一个自动提交变量 ,通过 show variables like '%commit%'; ---- autocommint 值是 on,说明开启自动提交 

关闭自动提交 set autocommit = off / set autocommit = 0

如果设置autocommit 为 off,意味着以后每条SQL 都会处于一个事务中,相当于每条SQL执行前 都执行 start transaction

 

补充:Oracle中 autocommit 默认就是 off

Jdbc使用事务

当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL在一个事务中执行,可使用下列语句:

JDBC控制事务语句

Connection.setAutoCommit(false); //  相当于start transaction

Connection.rollback();  rollback

Connection.commit();  commit

示例 演示银行转帐案例

在JDBC代码中使如下转帐操作在同一事务中执行。

  update from account set money=money-100 where name=‘a’;

  update from account set money=money+100 where name=‘b’;

设置事务回滚点

Savepoint sp = conn.setSavepoint();

Conn.rollback(sp);

Conn.commit();   //回滚后必须要提交

事务特性

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

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

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

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

事务隔离级别

多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。

如果不考虑隔离性,可能会引发如下问题

1、脏读 :指一个事务读取另一个事务未提交的数据 

A 转账 给B 100,未提交

B 查询账户多了100

A 回滚

B 查询账户那100不见了 

 

2、不可重复读:在一个事务先后两次读取发生数据不一致情况,第二次读取到另一个事务已经提交数据 (强调数据更新 update

A 查询账户 5000

B 向 A 账户转入 5000

A 查询账户 10000

 

3、虚读(幻读)  在一个事务中,第二次读取发生数据记录数的不同 ,读取到另一个事务已经提交数据 (强调数据记录变化 insert

A 第一次读取 存在5条记录

B 向 A 插入一条新的记录

A 第二次读取 存在6条记录 

 

数据库内部定义了四种隔离级别,用于解决三种隔离问题

1 Serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)

2 Repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)不可以避免虚读

3 Read committed:可避免脏读情况发生(读已提交)

4 Read uncommitted:最低级别,以上情况均无法保证。(读未提交)

 

操作数据库内部隔离级别

set session transaction isolation level 设置事务隔离级别

select @@tx_isolation 查询当前事务隔离级别

 

实验一:演示脏读发生 

在A窗口 将隔离级别设置 read uncommitted

A、B窗口同时开启事务 

B窗口执行转账操作 update account set money = money - 500 where name='bbb';

update account set money = money + 500 where name ='aaa'; 未提交事务

A窗口查询 select * from account; 查询到转账结果(脏读)

B 回滚 rollback

A窗口查询 金钱丢失

 

实验二:演示不可重复读

在A窗口设置 隔离级别 read committed; 重复实验一,发现无法脏读

B窗口转账后,提交事务 

A窗口查询到 B提交的数据 (不可重复读)

 

实验三:演示阻止不可重复读发生

在A窗口 设置隔离级别 repeatable read 重复试验二,发现不会发生不可重复读

 

实验四:演示serializable 串行事务效果

A窗口设置隔离级别 serializable

A、B同时开启事务

B窗口插入一条数据 insert into account values(null,'ddd',1000);

在A窗口查询数据 select * from account;

发现A 窗口阻塞了,等待B事务执行结束

 

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

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

 

结论: 实际开发中,通常不会选择 serializable 和 read uncommitted , mysql默认隔离级别 repeatable read ,oracle默认隔离级别 read committed

 

JDBC程序中能否指定事务的隔离级别 ?

Connection接口中定义事务隔离级别四个常量:

static int TRANSACTION_READ_COMMITTED

          指示不可以发生脏读的常量;不可重复读和虚读可以发生。 

static int TRANSACTION_READ_UNCOMMITTED

          指示可以发生脏读 (dirty read)、不可重复读和虚读 (phantom read) 的常量。 

static int TRANSACTION_REPEATABLE_READ

          指示不可以发生脏读和不可重复读的常量;虚读可以发生。 

static int TRANSACTION_SERIALIZABLE

          指示不可以发生脏读、不可重复读和虚读的常量。 

 

通过 void setTransactionIsolation(int level) 设置数据库隔离级别

事务的丢失更新问题(lost update )

两个或多个事务更新同一行,但这些事务彼此之间都不知道其它事务进行的修改,因此第二个更改覆盖了第一个修改 

 

丢失更新问题的解决

悲观锁( Pessimistic Locking )

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

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

乐观锁( Optimistic Locking )

通过时间戳字段 

 

解决方案详解:

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

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

 

一、悲观锁

mysql数据库内部提供两种常用 锁机制:共享锁(读锁)和排它锁(写锁)

允许一张数据表中数据记录,添加多个共享锁,添加共享锁记录,对于其他事务可读不可写的 

一张数据表中数据记录,只能添加一个排它锁,在添加排它锁的数据 不能再添加其他共享锁和排它锁的 ,

对于其他事物可读不可写的 

 

*** 所有数据记录修改操作,自动为数据添加排它锁 

添加共享锁方式:select * from account lock in share mode ;

添加排它锁方式:select * from account for update;

 

* 锁必须在事务中添加 ,如果事务结束了 锁就释放了 

 

解决丢失更新:事务在修改记录过程中,锁定记录,别的事务无法并发修改 

 

二、乐观锁

采用记录的版本字段,来判断记录是否修改过 -------------- timestamp

timestamp 可以自动更新

create table product (

   id int,

   name varchar(20),

   updatetime timestamp

);

 

insert into product values(1,'冰箱',null);

update product set name='洗衣机' where id = 1;

 

* timestamp 在插入和修改时 都会自动更新为当前时间 

 

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

 

 

事务案例:银行转账(在 service 层处理事务  ThreadLocal)

 

数据库连接池

连接池介绍

一次性创建多个连接,将多个连接缓存在内存中 ,形成数据库连接池(内存数据库连接集合),如果应用程序需要操作数据库,只需要从连接池中获取一个连接,使用后,并不需要关闭连接,只需要将连接放回到连接池中。

* 好处:节省创建连接与释放连接 性能消耗 ---- 连接池中连接起到复用的作用 ,提供程序性能

自定义连接池

编写连接池需实现javax.sql.DataSource接口。DataSource接口中定义了两个重载的getConnection方法:

Connection getConnection()

Connection getConnection(String username, String password)

 

实现DataSource接口,并实现连接池功能的步骤:

在DataSource构造函数中批量创建与数据库的连接,并把创建的连接保存到一个集合对象中

实现getConnection方法,让getConnection方法每次调用时,从集合对象中取一个Connection返回给用户。

当用户使用完Connection,调用Connection.close()方法时,Connection对象应保证将自己返回到连接池的集合对象中,而不要把conn还给数据库。

 

自定义连接池程序问题:

1、尽量不要使用具体对象类型的引用 

MyDataSource dataSource = new MyDataSource(); 应该写为 DataSource dataSource = new MyDataSource();

2、使用自定义方法 close 将连接放回连接池 ,需要用户在使用时需要记忆额外API

 

解决:让用户定义连接池时 DataSource dataSource = new MyDataSource(); 在用户使用连接后,应该调用conn.close(); 完成将连接放回到连接池 

对close方法进行方法增强 

 

练习:增强close方法,不真正关闭连接,而是将连接放回到连接池 

增强java中一个方法存在三种方式:1、继承 方法覆盖 2、包装 3、动态代理

1、继承方法覆盖,对原有类方法进行加强 

注意:必须控制对象构造,才能使用继承的方式对方法进行加强

 

2、包装 (装饰者)

编写包装类 1) 必须要与被装饰对象,有相同父类或者实现相同接口  2) 必须将被装饰对象传递给装饰者 (构造函数)

包装是一种通用方法加强措施,不管是否控制目标对象创建,都可以完成方法加强

 

3、动态代理 

原理:利用类加载器 ,在内存中根据目标类 加载器和接口 创建一个代理对象 ,通过代理对象完成对原有对象方法进行加强

注意:被代理对象,必须实现接口 

应用场景:也是只有存在目标对象,就可以通过动态代理进行加强

 

DBCP连接池

DBCP 是Apache 的commons 项目的一个子项目 

去官网下载 dbcp 和 pool 的jar包 ,DBCP 依赖 POOL的jar包 

 

复制两个jar 到WEB-INF/lib下 

 

DBCP连接池 核心类 BasicDataSource

 

* 任何数据库连接池,需要数据库连接,必须通过JDBC四个基本参数构造 

 

1、手动设置参数

                // 使用连接池

BasicDataSource basicDataSource = new BasicDataSource();

// 设置JDBC四个基本参数

basicDataSource.setDriverClassName("com.mysql.jdbc.Driver");

basicDataSource.setUrl("jdbc:mysql:///day14");

basicDataSource.setUsername("root");

basicDataSource.setPassword("abc");

2、通过配置文件

// 根据属性参数 获得连接池

InputStream in = DBCPTest.class.getResourceAsStream("/dbcp.properties");

Properties properties = new Properties();

// 装载输入流

properties.load(in);

 

DataSource dataSource = BasicDataSourceFactory.createDataSource(properties)

C3P0连接池

主流开源连接池,在Hibernate和Spring 都提供对C3P0连接池支持

去下载c3p0 开发包 http://sourceforge.net

 

将c3p0的jar 复制WEB-INF/lib下 

 

1、手动 

// 核心连接池类

ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();

// 设置四个JDBC基本连接属性

comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");

comboPooledDataSource.setJdbcUrl("jdbc:mysql:///day14");

comboPooledDataSource.setUser("root");

comboPooledDataSource.setPassword("abc");

 

2、在src下新建c3p0-config.xml

ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); 会自定加载配置文件 

 

常用基本连接池属性

    acquireIncrement   如果连接池中连接都被使用了,一次性增长3个新的连接

    initialPoolSize     连接池中初始化连接数量 默认:3

    maxPoolSize      最大连接池中连接数量 默认:15连接

    maxIdleTime      如果连接长时间没有时间,将被回收 默认:0 连接永不过期

    minPoolSize      连接池中最小连接数量 默认:3

Tomcat内置连接池

tomcat服务器内置连接池 (使用Apache DBCP)

配置tomcat 内置连接池,通过JNDI方式 去访问tomcat的内置连接池 

 

JNDI Java命名和目录接口,是JavaEE一项技术 ,允许将一个Java对象绑定到一个JNDI容器(tomcat)中 ,并且为对象指定一个名称

通过javax.naming 包 Context 对JNDI 容器中绑定的对象进行查找,通过指定名称找到绑定Java对象 

 

操作步骤

1、配置使用tomcat 内置连接池 配置 元素 

context元素有三种常见配置位置

1) tomcat/conf/context.xml 所有虚拟主机,所有工程都可以访问该连接池 

2) tomcat/conf/Catalina/localhost/context.xml 当前虚拟主机(localhost)下所有工程都可以使用该连接池

3) 当前工程/META-INF/context.xml 只有当前工程可以访问该连接池 

 

  

            type="javax.sql.DataSource" username="root" password="abc"

            driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql:///day14"

            maxActive="8" maxIdle="4"/>

 

* 必须先将mysql驱动jar包 复制tomcat/lib下 

* 在tomcat启动服务器时,创建连接池对象,绑定 jdbc/EmployeeDB 指定名称上 

 

2、通过运行在JNDI容器内部的程序(Servlet/JSP)去访问tomcat内置连接池 

Context context = new InitialContext();

Context envCtx = (Context)context.lookup("java:comp/env"); 固定路径

DataSource datasource = (DataSource) envCtx.lookup("jdbc/EmployeeDB"); 通过绑定名称,查找指定java对象

 

你可能感兴趣的:(事务与连接池)