事务与数据库连接池与DBUtils

事务

Transaction 指的是一组操作,有很多单个逻辑,只要有一个逻辑执行失败,那么整个事务失败,所有数据回归到一开始的数据。

  • 使用命令行演示事务
  1. 开启事务

     start transaction
    
  2. 提交或者回滚事务

     commit提交事务
     rollback回滚事务
    
  3. 关闭自动提交功能

     set autocommit = off
    
  4. 演示事务
    在这里插入图片描述
    事务只是针对连接对象

  • 代码演示
public static void main(String[] args){
		Connection conn = null;
		PreparedStatement pstmt = null;
		try {
			conn = JDBCUtils.getConnection();
			conn.setAutoCommit(false);//关闭事务的自动提交
			String sql = "update tb_bank set money = money - ? where id = ?";
			pstmt = conn.prepareStatement(sql);
			pstmt.setDouble(1, 200);
			pstmt.setString(2, "001");
			pstmt.executeUpdate();
			
			pstmt.setDouble(1, -200);
			pstmt.setString(2, "002");
			pstmt.executeUpdate();
			conn.commit();//提交事务
		} catch (Exception e) {
			try {
				conn.rollback();//回滚事务
			} catch (Exception e2) {
				e2.printStackTrace();
			}
			e.printStackTrace();
		}finally {
			JDBCUtils.close(conn, pstmt);
		}
	}
事务的特性(保证安全)

一个有效的事务处理系统必须满足(ACID特性):

  1. 原子性
    一个事务必须被视为一个单独的内部不可分的工作单元,事务要么全部执行,要么回滚。这时事务绝对不会执行部分,不会出现A给B转账,B收款部分不执行,导致A失去了钱,B却没有得到。
  2. 一致性
    数据库总是从一种一致性状态转换到另一种一致性状态。即使中间执行的语句崩溃了,但因为表单不会提交而无伤大雅。
  3. 隔离性
    某个事务只有在完成后,结果才会被其他事务可见。
  4. 持久性
    事务所做的数据改变是永久的。

一个支持ACID特性的数据库会比不支持的数据库要求对于CPU处理能力大、更大内存、更多磁盘空间 。

事务安全问题与隔离级别
  • 安全问题

  1. a. 脏读

    一个事务读到另一个还未提交的事务

    b.不可重复读

    一个事务读到了另外一个事务提交的数据 ,造成了前后两次查询结果不一致。对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。

    c.幻读

    一个事务读到了另一个事务insert的数据,造成了查询结果不一致。
    一个事务第一次查询时,查到的数据结果,与第二次查询的结果不一致,像是看错了一样。


  2. 丢失更新

    两个事务查询数据库数据,存入对应的缓存中(均未提及),当a事务修改数据后(由于隔离性,b事务不知道a对它修改了),a事务提交,数据库数据发生第一次改变,之后b事务在原数据(缓存中数据)上修改,提交b事务,则会覆盖a事务的结果,造成a事务更新数据失败,即丢失更新

  • 可串行化

    如果有一个连接的隔离级别设置为了串行化 ,那么谁先打开了事务, 谁就有了先执行的权利, 谁后打开事务,谁就只能得着,等前面的那个事务,提交或者回滚后,才能执行。 但是这种隔离级别一般比较少用。 容易造成性能上的问题。 效率比较低。

    MySQL数据库的四种隔离级别(拦截级别由高到低):

    ① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。(级别最高)
    ② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
    ③ Read committed (读已提交):可避免脏读的发生。
    ④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。(级别最低)

    四种隔离级别效率由高到低:

    Read uncommitted (读未提交) > Read committed (读已提交) > Repeatable read (可重复读) > Serializable (串行化)

    mySql 默认的隔离级别是 可重复读
    Oracle 默认的隔离级别是 读已提交

解决丢失更新
  • 悲观锁
    在查询语句后面加上 for update(数据库锁机制)

    select * from tb_stu for update

    排他锁(写锁)是悲观锁的一种实现。
    排他锁:在一定时间内,只有一个用户可以写入资源,会阻塞其他读锁和写锁。

  • 乐观锁
    A事务在数据库取出数据,其中有一个version,B事务也同样有一个version,两者一开始是相同的。A事务先操作完成提交给数据库,version改变;B事务开始操作,在提交到数据库前先比对自己的version和数据库的version是否一致,如果不一致则取消提交,更新数据,若一致则提交。

数据库连接池

  1. 数据库的连接对象创建工作,比较消耗性能。
  2. 一开始现在内存中开辟一块空间(集合) , 一开始先往池子里面放置 多个连接对象。 后面需要连接的话,直接从池子里面去。不要去自己创建连接了。 使用完毕, 要记得归还连接。确保连接对象能循环利用。
自定义连接池
  • 代码实现
/**
 * 这是一个数据库连接池
 * 一开始先往池子放上10个连接
 * 来的程序通过getConnection方法获取连接
 * 用完之后通过addBack方法归还连接
 * 扩容
 */
public class MyDataSource implements DataSource{
	List<Connection> list = new ArrayList<Connection>();
	
	public MyDataSource() {
		for(int i = 0; i < 10; i++) {
			Connection conn = JDBCUtil.getConn();
			list.add(conn);
		}
	}

	//该连接池对外公布的获取连接的方法
	@Override
	public Connection getConnection() throws SQLException {
		if(0 == list.size()) {//连接池没有了
			for(int i = 0; i < 5; i++) {
				Connection conn = JDBCUtil.getConn();
				list.add(conn);
			}
		}
		Connection conn = list.remove(0);
		return conn;
	}
	
	public void addBack(Connection conn) {
		list.add(conn);
	}
}
  • 解决自定义数据库连接池出现的问题

因为多了一个addBack()方法,导致无法面向接口编程,因为接口里没有这个方法,使用这个连接池的地方需要你额外记住这个方法。
我们打算修改原来的JDBCUtils的close方法,让它成为归还连接对象的方法,而不是真的关闭连接。

装饰者模式
ConnectionWrap:

public class ConnectionWrapper implements Connection{
	Connection conn = null;
	List<Connection> list;

	public ConnectionWrapper(Connection conn, List<Connection> list) {
		super();
		this.conn = conn;
		this.list = list;
	}

	@Override
	public void close() throws SQLException {
		System.out.println("有人来归还了,归还前是" + list.size());
		list.add(conn);
		System.out.println("有人来归还了,归还后是" + list.size());
	}
	//......
}

之后在MyDataSource 里返回的是装饰过的ConnectionWrapper 类,测试类中只需要conn.close()就可以,这时候的close不是关闭连接,而是ConnectionWrapper里的close归还连接。

开源连接池
  • DBCP
public void testDBCP01() {
		Connection conn = null;
		PreparedStatement pstmt = null;
		try {
			//1.创建数据源对象
			BasicDataSource dataSource = new BasicDataSource();
			dataSource.setDriverClassName("com.mysql.jdbc.Driver");
			dataSource.setUrl("jdbc:mysql://localhost:3306/bank_db");
			dataSource.setUsername("root");
			dataSource.setPassword("root");
			//2.得到连接对象
			conn = dataSource.getConnection();
			String sql = "insert into tb_bank values ('004', 'admin', 3000)";
			pstmt = conn.prepareStatement(sql);
			pstmt.executeUpdate();
		} catch (SQLException e) {
			e.printStackTrace();
		}finally {
			JDBCUtil.release(conn, pstmt);
		}
	}

配置法:

@Test
	public void testDBCP01() {
		Connection conn = null;
		PreparedStatement pstmt = null;
		try {
			BasicDataSourceFactory dataFactory = new BasicDataSourceFactory();
			Properties properties = new Properties();
			InputStream is = getClass().getClassLoader().getResourceAsStream("dbcpconfig.properties");
			properties.load(is);
			DataSource dataSource = dataFactory.createDataSource(properties);
			
			conn = dataSource.getConnection();
			String sql = "insert into tb_bank values ('005', 'adward', 30000)";
			pstmt = conn.prepareStatement(sql);
			pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			JDBCUtil.release(conn, pstmt);
		}
	}
  • C3P0
    方法一:
public void testC3P0() {
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		Connection conn = null;
		PreparedStatement pstmt = null;
		try {
			dataSource.setDriverClass("com.mysql.jdbc.Driver");
			dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/bank_db");
			dataSource.setUser("root");
			dataSource.setPassword("root");
			
			conn = dataSource.getConnection();
			String sql = "insert into tb_bank values ('006', 'bella', 9000)";
			pstmt = conn.prepareStatement(sql);
			pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			JDBCUtil.release(conn, pstmt);
		}
	}

方法二:推荐使用C3P0的配置方法
在src下导入c3p0-config.xml,一定要是这个名字。同方法一同样,只不过删除dataSource.setxxx就行
在这里插入图片描述c3p0-config配置:
在这里插入图片描述这个时候JDBCUtils就可以更新了,除了close类型的方法全部删除,添加如下代码就可以了(配合配置文件):

static ComboPooledDataSource dataSource = null;
static {
	dataSource = new ComboPooledDataSource();//里面自己选择是哪个数据库,有MySQL和oracle
}

public static Connection getConnection() throws Exception {
	return dataSource.getConnection();
}

DBUtils

  • 增删改

queryRunner.update(sql语句,参数);

例:

try {
			ComboPooledDataSource dataSource = new ComboPooledDataSource();
			//QueryRunner简化了CRUD的代码,但是不包括连接的创建以及工作获取
			QueryRunner queryRunner = new QueryRunner(dataSource);
			queryRunner.update("insert into tb_bank values ('008', ?, ?)", "顾溪栎", 20000);//增
		} catch (SQLException e) {
			e.printStackTrace();
		}
  1. 查询一个对象
Result result2 = queryRunner.query("select * from tb_bank where id = ?", 
					new BeanHandler<Result>(Result.class), 3);//查询第三个
  1. 查询全部
List<Result> result3 = queryRunner.query("select * from tb_bank", 
					new BeanListHandler<Result>(Result.class));
  1. 直接new 接口的匿名实现类
Result result = queryRunner.query("select * from tb_bank", new ResultSetHandler<Result>() {
	@Override
	public Result handle(ResultSet rs) throws SQLException {
		Result r = new Result();
		while(rs.next()) {
			String name = rs.getString("name");
			double money = rs.getDouble("money");
			r.setName(name);
			r.setMoney(money);
		}
		return r;
	}
});

注Result:
在这里插入图片描述

  • ResultSetHandler 常用的实现类
    以下两个是使用频率最高的

      BeanHandler,  查询到的单个数据封装成一个对象
      BeanListHandler, 查询到的多个数据封装 成一个List<对象>
    

ArrayHandler,  查询到的单个数据封装成一个数组
ArrayListHandler,  查询到的多个数据封装成一个集合 ,集合里面的元素是数组。 

MapHandler,  查询到的单个数据封装成一个map
MapListHandler,查询到的多个数据封装成一个集合 ,集合里面的元素是map。 
元数据

meta data

描述数据的数据String sql,描述这份sql字符串的数据叫元数据。

数据库元数据 DatabaseMetaData
参数元数据 ParameterMetaData
结果集元数据 ResultSetMetaData

CommonCRUDUtil
增删改万能代码:

//传递的数据类型不确定,所以使用Object
public void update(String sql, Object ...args) {
		......
//	for(int i = 0; i < args.length; i++) {
//		pstmt.setObject(i + 1, args[i]);
//	}
			
	//使用元数据,获取?的个数
	ParameterMetaData metaData = pstmt.getParameterMetaData();
	int count = metaData.getParameterCount();
	for(int i = 0; i < count; i++) {
		pstmt.setObject(i + 1, args[i]);
	}
	......
}

查询万能代码:
由于查询,你不知道传过来的是哪一个表,所以无法封装返回,需要调用者自己封装

公共查询方法:

public <T> T query(String sql, ResultSetHandler<T> handler, Object ...args) {
		......
		try {
			......
			//把结果集发给调用者,让他自己去封装。
			result = handler.hander(rs);
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			JDBCUtil.release(conn, pstmt, rs);
		}
		return result;
	}
public interface ResultSetHandler<T> {
   /**
    * 定义数据封装类型。规范
    * @param rs
    * @return
    */
   public T hander(ResultSet rs); 
}

实现类:

//封装Result类型的数据
class ResultSetHandlerImpl<T> implements ResultSetHandler<Result> {
		@Override
		public Result hander(ResultSet rs) {
			try {
				if(rs.next()) {
					Result r = new Result();
					String name = rs.getString("name");
					double money = rs.getDouble("money");
					r.setName(name);
					r.setMoney(money);
					return r;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			return null;
		}
	}

这样一来测试类只需要传递sql语句,new一个它自己要封装的类型的ResultSetHandlerImpl以及参数数据就可以。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值