数据库,事务和连接池

什么是事务:
事务是指包含多个微小逻辑单元的一组操作, 只要其中有一个逻辑失败了
那么这一组操作就全部以失败告终,不存在一半成功,一半不成功的状况。

事务的作用
事务在平常的CRUD当中也许不太常用, 但是如果我们有一种需求,要求,一组操作中
必须全部成功执行,才算完成任务,只要有一个出错了,那么所有的任务都将回到最初的状况
恢复原样。那么这就可以使用事务了。如: 银行的转账例子

事务怎么用
MySql 的事务默认是自动提交的。 也就是,执行完一条 sql 语句,事务就提交了
但是 Oracle 并不是, 需要我们手动 commit,才会提交事务。

可在命令行中使用  show variables like 'commit';  来查看事务的提交设置。 
1. 新建一个数据库 bank , 并且创建一张表 account , 用于存储用户的姓名 和 存款。
2. 打开命令行,进入 mysql 终端
3. 先使用 show variables  like '%commit%'; 来查看当前数据库的事务自动提交设置。
4. 修改事务的自动提交 为 OFF , 也就是关闭自动提交,我们想手动提交,看看数据的变化。
set autocommit = OFF ;   此处 off 小写也OK ,不区分大小写, 当然也可以写 0  , OFF 对应的是 
0 , ON 对应的是 1;

5. 对某个用户的存款进行修改。 接着在 命令行下查看数据,发现数据已经变化, 但是转到 GUI 下看
   并没有任何变化。 这是因为我们没有提交数据。
   在命令行看到的都是假象而已。 
6. 在命令行下 输入 commit ;   来提交事务。 这时候,GUI 上面就显示出来最终的结果了。
1. 在 java 代码中演示 转账逻辑 , 转账其实就是从A 账户里面 扣钱,  往B账户里面加钱而已。 
关键代码如下: 

			String sql="update account set money = money + ? where id = ?";		
			ps = conn.prepareStatement(sql);
			
			//从id为1 的账户扣除存款 100
			ps.setInt(1, -100);
			ps.setInt(2, 1);
			ps.executeUpdate();
			
			
			//往id为2的账户加钱100 
			ps.setInt(1, 100);
			ps.setInt(2, 2);
			ps.executeUpdate();

2. 以上代码是没有什么问题的, 但是假设我们在扣钱和加钱的中间加入了出现异常的代码。 
那么将会发生只扣钱,不加钱的情况。 这也就证明了,jdbc里面默认事务也是自动提交的。
只要执行完一个操作,也就直接提交事务了。

			//从id为1 的账户扣除存款 100
			ps.setInt(1, -100);
			ps.setInt(2, 1);
			ps.executeUpdate();
			
			int a = 10 / 0 ;
			
			//往id为2的账户加钱100 
			ps.setInt(1, 100);
			ps.setInt(2, 2);
			ps.executeUpdate();	

3. 关闭自动提交,由我们自己来控制事务的提交。  要想操作事务,只能在Connection身上做文章。
也只有它才能控制事务的设置。


		try {
			conn = JDBCUtil.getConn();
			
			//关闭自动提交
			conn.setAutoCommit(false);
			
			String sql="update account set money = money + ? where id = ?";		
			ps = conn.prepareStatement(sql);
			
			//从id为1 的账户扣除存款 100
			ps.setInt(1, -100);
			ps.setInt(2, 1);
			ps.executeUpdate();
			
			int a = 10 / 0 ;
			
			//往id为2的账户加钱100 
			ps.setInt(1, 100);
			ps.setInt(2, 2);
			ps.executeUpdate();
			
			
			//如果没有出错,到这里就直接提交事务了。
			conn.commit();
		} catch (SQLException e) {
			System.out.println("出现异常了, 转账中途出现错误");
			e.printStackTrace();
			try {
				//如果出现的了异常,那么让事务回滚,其实就是回到最初的状态。
				conn.rollback();
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
		}finally {
			JDBCUtil.release(conn, ps);
		}


	


 

事务特性ACID【面试】
原子性(Atomicity) : 事务中的逻辑要全部执行,不可分割。(原子是物理中最小单位)
一致性(Consistency): 指事务执行前和执行后, 数据的完整性保持一致
隔离性(Isolation): 指一个事务在执行的过程中不应该受其他事务的影响
持久性(Durability): 事务执行结束(提交或回滚), 数据都应持久化到数据中 

安全问题&隔离级别【面试】
安全问题
读 问题
脏读: 指 一个事务 读到了另一个事务还未提交的数据
不可重复读: 一个事务读到了另一个事务提交的数据, 导致多次查询结果不一致。
幻读: 一个事务读到了另一个事务已提交的插入的数据,导致多次查询结果不一致。

写 问题
丢失更新
悲观锁
    指事务在一开始就认为丢失更新一定会发生, 这是一件很悲观的事情。 具体操作步骤如下:
    1. 所有事务在执行操作前,先查询一次数据, 查询语句如下:
       select * from student  for update  ; 后面的for update 其实是数据库锁机制 
       一种排他锁。
    2. 哪个事务先执行这个语句, 哪个事务就持有了这把锁, 可以查询出来数据, 
       后面的事务再执行这条语句,不会有任何数据显示,就只能等着。 

    3. 一直等到前面的那个事务提交数据后, 后面的事务数据才会出来,那么才可以往下接着操作。 
       这有点像男生去上卫生间似的, 如果谁先来,谁就可以进去蹲着, 后面来的人,得等着。
       只有里面的人出来了,才能进去。 这其实就是 java 中的同步的概念



乐观锁
    乐观锁是指,从来不会觉得丢失更新会发生。那么它的具体做法是什么呢? 
    要求程序员在数据库中添加字段,然后在后续更新的时候,对该字段进行判定比对, 
    如果一致才允许更新。例子如下:

    1. 数据库表中,额外添加了一个version字段, 用于记录版本, 默认从0 开始, 
       只要有针对表中数据 进行修改的,那么version就+1.
    2. 开启A事务, 然后开启B事务 。 
    3. A 先执行数据库表操作。 因为以前都没有人修改过。 所以是允许A事务修改数据库的,
      但是修改完毕,就把version的值变成  1 了 。
    4. B事务, 这时候如果想执行修改,那么是不允许修改的。 因为B事务以前是没有查询过数据库内容的 
    ,所以它认为数据库版本还是0 。 但是数据库的版本经过A修改,已经是1了。
      所以这时候不允许修改, 要求其重新查询 。

    5. B重新查询后, 将会得到version 为 1的数据,这份数据就是之前A 事务修改的数据了, 
       B 在进行修改,也是在A的基础上修改的。 所以就不会有丢失更新的情况出现了。
       乐观锁的机制 ,其实是通过比对版本或者比对字段的方式来实现的, 这与大家在未来的学习中,
       或者在工作中,使用到的版本控制软件【SVN , GIT】机制是一样的。

隔离级别
Read Uncommited【读未提交】
指的是 : 一个事务可以读取到另一个事务还未提交的数据。 这就会引发 “脏读” 
读取到的是数据库内存中的数据,而并非真正磁盘上的数据。
例子: 
1. 开启一个命令行窗口A, 开始事务,然后查询表中记录。  设置当前窗口的事务隔离级别为 读未提交  
命令如下:
set session transaction isolation level read uncommitted;
2. 另外在打开一个窗口B, 也开启事务, 然后执行 sql 语句, 但是不提交
3. 在A窗口重新执行查询, 会看到B窗口没有提交的数据。 

Read Commited 【读已提交】
与前面的读未提交刚好相反,这个隔离级别是 ,只能读取到其他事务已经提交的数据
那些没有提交的数据是读不出来的。但是这会造成一个问题是: 前后读取到的结果不一样。 
发生了不可重复!!!, 所谓的不可重复读,就是不能执行多次读取,否则出现结果不一 。 
例子如下:
1. 开启一个命令行窗口A, 开始事务,然后查询表中记录。  设置当前窗口的事务隔离级别为 读未提交  
命令如下:
set session transaction isolation level read committed;
2. 另外在打开一个窗口B, 也开启事务, 然后执行 sql 语句, 但是不提交 
3. 在A窗口重新执行查询, 是不会看到B窗口刚才执行sql 语句的结果,因为它还没有提交。
4. 在B窗口执行提交。
5. 在A窗口中执行查看, 这时候才会看到B窗口已经修改的结果。
6. 但是这会造成一个问题是: 在A窗口中, 第一次查看数据和第二次查看数据,结果不一样。

Repeatable Read 【重复读】 -  MySql 默认的隔离级别就是这个。
该隔离级别, 可以让事务在自己的会话中重复读取数据,并且不会出现结果不一样的状况
即使其他事务已经提交了,也依然还是显示以前的数据。
1. 开启一个命令行窗口A, 开始事务,然后查询表中记录。  
设置当前窗口的事务隔离级别为 读未提交  
命令如下:
set session transaction isolation level read committed;
2. 另外在打开一个窗口B, 也开启事务, 然后执行 sql 语句, 但是不提交 
3. 在A窗口重新执行查询, 是不会看到B窗口刚才执行sql 语句的结果,因为它还没有提交。
4. 在B窗口执行提交。
5. 在A窗口中执行查看, 这时候查询结果,和以前的查询结果一致。不会发生改变。

Serializable 【可串行化】 
该事务级别是最高级的事务级别了。比前面几种都要强大一点,也就是前面几种的问题
【脏读、不可重复读、幻读】都能够解决。但是有一些缺点。
1. 开启一个命令行窗口A, 开始事务,然后查询表中记录。  
设置当前窗口的事务隔离级别为 读未提交  命令如下:
set session transaction isolation level read serializable;
2. 另外在打开一个窗口B, 也开启事务, 然后执行 sql 语句, 但是不提交 
3. 在A窗口重新执行查询, 会卡主,没有任何信息显示。 
4. 在B窗口执行提交。
5. 在A窗口中执行查看, 这时候才会显示结果。
Serializable  可以防止上面的所有问题,但是都使用该隔离级别也会有些问题。 
比如造成并发的性能问题。 其他的事务必须得等当前正在操作表的事务先提交,才能接着往下
否则只能一直在等着。
 
数据库连接池
连接池指:创建一个池子(容器) , 专门用来管理连接对象

连接池的作用:
1. 更快响应速度,连接池里的连接在一开始就已经创建好了,后面如果需要直接拿就可以了,无需创建。
2. 资源的重复利用、避免重复创建对象连接对象使用完毕后,再归还到池子中进行统一管理即可。

自定义连接池【了解】
问题
1. 对象没有做成单例
在哪里使用,都需要new MyDataSource().  这就会造成有多个对象的情况出现, 那就不只一个池子了。
2. 需要额外记住 addBack方法。
sun公司定义的数据库连接池里面并没有这个addBack方法,所以谁需要用我们这个连接池
需要记住这个方法是用来回收连接对象的。
3. 无法面向接口编程。 
由于我们的连接池直接定义成了一个类,并且里面还额外添加了一个addBack方法
这就造成了我们无法面向接口编程

装饰者模式
此小节主要是为了解决上面出现的问题。
不打算添加 addBack 方法, 而是扩展原来的 close 方法。 让 close 方法,不关闭连接
而是把连接对象归还给池子。要想实现这个需求,有以下几种方式:
1. 直接修改JDBC 里面的那个 Connection 接口的具体实现。让它的 close 方法别关闭连接
而是变成回收连接对象。
答案: 无法办到。 咱们没把发修改人家的源代码
2. 既然不能直接修改,那么我们改成自己定义一个类,然后继承那个 Connection 接口的实现类,
然后在它的 close 方法基础上,加入我们的回收逻辑。
答案: 无法办到, 因为我们根本不知道 Connection 接口的实现类是哪一个. 
3. 使用装饰者模式来实现。
答案: 可以办到。 
	
开源连接池【掌握】

DBCP(DataBase Connection Pool)数据库连接池,是java数据库连接池的一种
由Apache开发通过数据库连接池,可以让程序自动管理数据库连接的释放和断开



使用方法
1. 导入jar包  commons-dbcp.jar,commons-pool.jar

2.  不使用配置文件方式


		BasicDataSource dataSource = new BasicDataSource();
		dataSource.setDriverClassName("com.mysql.jdbc.Driver");
		dataSource.setUrl("jdbc:mysql://localhost/users");
		dataSource.setUsername("root");
		dataSource.setPassword("root");

		
		conn = dataSource.getConnection();
		String sql = "select * from user";
		pstmt = conn.prepareStatement(sql);

		...


3. 使用配置文件方式。

		1. 在 src 下定义配置文件 dbcp.properties. 内容如下: 


			#连接设置
			driverClassName=com.mysql.jdbc.Driver
			url=jdbc:mysql://localhost:3306/jdbc
			username=root
			password=root

			...

		

		2. 使用代码读取配置文件,即可获取连接池

			Properties properties = new Properties();
			properties.load(new FileInputStream("src/dbcp.properties"));
			DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);

			conn = dataSource.getConnection();
			String sql = "select * from user";
			pstmt = conn.prepareStatement(sql);

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

用法
1. 拷贝jar 文件   c3p0-0.9.1.2.jar
2. 不使用配置文件方式
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost/users");
dataSource.setUser("root");
dataSource.setPassword("root");
conn = dataSource.getConnection();
String sql = "select * from user";
pstmt = conn.prepareStatement(sql);
3. 使用配置文件方式。
> c3p0的配置文件 支持 properties , 也支持 xml 方式。 不过开发中,一般使用xml方式来配置
1.  src下, 创建xml文件,名为:c3p0-config.xml  注意此处,名字需要固定了。


			<c3p0-config>
  				<default-config>
    						<property name="driverClass">com.mysql.jdbc.Driver</property>
    						<property name="jdbcUrl">jdbc:mysql://localhost/user</property>
    						<property name="user">root</property>
    						<property name="password">root</property>
    
						<!-- 可选配置 -->
    						<property name="initialPoolSize">5</property>
  			</default-config>

		</c3p0-config>
	2. 代码中获取连接child对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值