JavaWeb学习笔记6:事务&数据库连接池&DBUtiles

1-事务介绍与简单使用

事务 - Transaction
其实指的一组操作,里面包含许多个单一的逻辑。只要有一个逻辑没有执行成功,那么都算失败,则所有的数据都回归到最初的状态(回滚)
为什么要有事务?
为了确保逻辑的成功 例子:银行的转账
开启事务命令:
start transaction;
提交或者回滚事务命令:
commit; 提交事务,数据将会写到磁盘上的数据库
rollback; 数据回滚,回到最初的状态
自动提交命令:
set autocommit = off;
autocommit会降低运行效率并且容易发生数据错误,若异常则后续代码无法执行。关闭autocommit后,若要使事务执行则commit,否则就回滚rollback
设置隔离级别命令:
set session transaction isolation level (级别);

事务在Java中的简单使用:

public void testTransaction(){
	Connection conn = null;
	PreparedStatement ps = null;
	ResultSet rs = null;
	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) {
		try {
			//事变: 回滚事务
			conn.rollback();
		} catch (SQLException e1) {
			e1.printStackTrace();
		}
		e.printStackTrace();
	}finally {
		JDBCUtil.release(conn, ps, rs);
	}
}

注意:若不使用事务(autocommit开着),则执行事务逻辑时某处出现的异常可能导致整个数据出错

2-事务 特性(ACID)和安全隐患
  • 原子性
    > 指的是 事务中包含的逻辑,不可分割
  • 一致性
    > 指的是 事务执行前后.数据完整性
  • 隔离性
    > 指的是 事务在执行期间不应该受到其他事务的影响
  • 持久性
    > 指的是 事务执行成功,那么数据应该持久保存到磁盘上

不考虑隔离级别设置,那么会出现以下问题

  • 脏读
    > 一个事务读到另外一个事务还未提交的数据
    脏读/读未提交

  • 不可重复读
    > 一个事务读到了另外一个事务提交的数据,造成了前后两次查询结果不一致

  • 幻读
    > 一个事务读到了另一个事务已提交的插入的数据,导致多次查询结果不一致

事务的可串行化:

如果有一个连接的隔离级别设置为了串行化,那么谁先打开了事务,谁就有了先执行的权利,谁后打开事务,谁就只能得着,等前面的那个事务,提交或者回滚后,才能执行

  • 按效率划分,从高到低
    > 读未提交 > 读已提交 > 可重复读 > 可串行化
  • 按拦截程度,从高到底
    > 可串行化 > 可重复读 > 读已提交 > 读未提交

事务的丢失更新现象:
丢失更新
悲观锁和乐观锁:
1、可以在查询的时候加入for update,表示悲观锁
悲观锁
2、乐观锁要求程序员自己规定标识
乐观锁

事务小结:
1、需要掌握在Java中使用事务

conn.setAutoCommit(false);
conn.commit();
conn.rollback();

2、需要熟悉了解事务的安全隐患和隔离级别
3、mySql默认的隔离级别是可重复读。Oracle是读已提交
4、需要会在数据出现异常时学会排错

3-数据库连接池介绍与简单使用

前提引入:如果每个程序连接数据库都要创建一个数据库对象,那么会比较消耗性能。所以我们一开始直接在内存中使用一块集合空间存放多个连接对象,如果有程序需要连接对象则给一个,使用完毕之后回收,这样的话不仅能够循环利用而且还能够提升效率。

数据库连接池的搭建分析:
1、先创建X个连接
2、要用的程序通过getConnection来获取连接
3、用完之后,使用addBack来归还连接
4、如果连接池中不够了,则扩容
5、list.remove(0)移出list中的第一个,移出后第二个默认到第一个,还是remove(0)

数据库连接池的简单搭建:
这里暂时创建一个(JDBCUtil)对象类,用于声明连接对象。接着自己手动创建一个连接池对象,具体的主要代码如下↓

List<Connection> connList = new ArrayList<Connection>();
public List<Connection> getConnList() {
	return connList;
}
public void setConnList(List<Connection> connList) {
	this.connList = connList;
}
public MyDataSource() {
	for (int i = 0; i < 10; i++) {
		Connection conn = JDBCUtil_old.getConn();
		connList.add(conn);
	}
}
//目前使用到的方法只有getConnection
@Override
public Connection getConnection() throws SQLException {
	if(connList.size()==0){
		for (int i = 0; i < 5; i++) {//一次加5个
			Connection conn = JDBCUtil_old.getConn();
			connList.add(conn);
		}
	}
	Connection conn = new ConnectionWrap(connList.remove(0),this.connList);//删除并返回Connection
	System.out.println("分配一个Connection之后的list大小:"+connList.size());
	return conn;
}
//使用完毕之后,利用addBack方法,回收连接
public void addBack(Connection conn){
	connList.add(conn);
	System.out.println("还原一个Connection之后的list大小:"+connList.size());
}

自定义数据库连接池的问题:
1、需要额外记住addBack方法
2、单例模式的使用,创建多个数据库连接池,占用内存高
3、由于addBack方法接口中没有,无法面向接口编程
解决自定义数据库连接池问题的重点在于把addBack换成接口已有的方法:
1、使用装饰者模式
2、使用动态代理

4-数据库连接池 装饰者模式

装饰者模式改进自定义数据库连接池主要代码:

Connection conn = null;
List<Connection> connList = null;
public ConnectionWrap(Connection conn,List<Connection> connList) {
	this.conn = conn;
	this.connList = connList;
}
@Override
public void close() throws SQLException {
	connList.add(conn);
	System.out.println("还原一个Connection之后的list大小:"+connList.size());
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
	return conn.prepareStatement(sql);
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
	conn.setAutoCommit(autoCommit);
}
@Override
public void commit() throws SQLException {
	conn.commit();
}
@Override
public void rollback() throws SQLException {
	conn.rollback();
}
5-数据库连接池DBCP

DBCP连接池的apache上的一个Java连接池项目。
1、使用直连的方式获取连接:

//1. 构建数据源对象
BasicDataSource dataSource = new BasicDataSource();
//连的是什么类型的数据库, 访问的是哪个数据库 , 用户名, 密码。。
//jdbc:mysql://localhost/bank 主协议:子协议 ://本地/数据库
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost/bank");
dataSource.setUsername("root");
dataSource.setPassword("root");
//2. 得到连接对象
conn = dataSource.getConnection();

2、通过配置文件、工厂获取连接:
首先导入两个jar包(一个pool,一个dbcp),一个properties文件
其次建立工厂,通过工厂来获取连接,代码如下:

BasicDataSourceFactory bdsf = new BasicDataSourceFactory();
//bdsf不能生成自定义的DataSource
//MyDataSource ds = null;
DataSource ds = null;
Connection conn = null;
InputStream in = null;
Properties pro = null;
try {
	pro = new Properties();
	in = new FileInputStream("src/dbcpconfig.properties");
	pro.load(in);
	ds = bdsf.createDataSource(pro);
	conn = ds.getConnection();
}
6-数据库连接池C3P0

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

//1. 创建datasource
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//2. 设置连接数据的信息
dataSource.setDriverClass("com.mysql.jdbc.Driver");

//忘记了---> 去以前的代码 ---> jdbc的文档
dataSource.setJdbcUrl("jdbc:mysql://localhost/bank");
dataSource.setUser("root");
dataSource.setPassword("root");
//3. 得到连接对象
conn = dataSource.getConnection();

2、通过配置文件获取连接:

ComboPooledDataSource ds = new ComboPooledDataSource();	//后有参数可以选择数据库类型
Connection conn = null;
conn = ds.getConnection();

//配置文件
<default-config>
	<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
	<property name="jdbcUrl">jdbc:mysql://localhost:3306/bank?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC</property>
	<property name="user">root</property>
	<property name="password">qq</property>
	<property name="automaticTestTable">con_test</property>
	<property name="checkoutTimeout">30000</property>
	<property name="idleConnectionTestPeriod">30</property>
	<property name="initialPoolSize">10</property>
	<property name="maxIdleTime">30</property>
	<property name="maxPoolSize">100</property>
	<property name="minPoolSize">10</property>
	<property name="maxStatements">200</property>
</default-config>

数据库连接池总结:
1、配置文件中的url如果就写到目标数据库名则会报错,原因是没有规定使用的解码器
2、配置文件中写了解码器的代码但没有使用转义字符,会报错"characterEncoding" must end with the ‘;’ delimiter;原因是xml不能解析’&’,所以把‘&’改成"&amp ;" 即可
3、Tried to check-in a foreign resource! - 线程无法正常运行,不要手动关闭ds
4、ComboPooledDataSource源代码中自动会帮助获取连接信息,所以要写好配置文件

7-DBUtils以及Handler

利用DBUtils进行增删改查操作,这里主要利用到QueryRunner对象

//dbutils 只是帮我们简化了CRUD的代码,但是连接的创建以及获取工作不在他的考虑范围
对象的生成:QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
//增加
queryRunner.update("insert into account values (null , ? , ? )", "aa" ,1000);
//删除
queryRunner.update("delete from account where id = ?", 5);
//更新
queryRunner.update("update account set money = ? where id = ?", 10000000 , 6);

这里要单独把查找功能写出来,因为查找的时候有利用到一个Handler集合对象
1、我们直接new接口的匿名实现类(ResultSetHandler)

QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
Account  account =  queryRunner.query("select * from account where id = ?", new ResultSetHandler<Account>(){
	@Override
	public Account handle(ResultSet rs) throws SQLException {
		Account account  =  new Account();
		while(rs.next()){
			String name = rs.getString("name");
			int money = rs.getInt("money");
			
			account.setName(name);
			account.setMoney(money);
		}
		return account;
	}
 }, 6);
System.out.println(account.toString());
//ResultSetHandler为最大的接口,所以要使用必须自行定义接口方法,所以@Override

2、直接使用已经写好的实现类(BeanListHandler)

ComboPooledDataSource ds = new ComboPooledDataSource();	//后有参数可以选择数据库类型
QueryRunner eq = new QueryRunner(ds);
try {
	//查找
	List<Account> accounts = eq.query("select * from account", new BeanListHandler<Account>(Account.class));
	for (Account account : accounts) {
		String name = account.getName();
		int money = account.getMoney();
		System.out.println("Account:"+name+"-"+money);
	}
} catch (SQLException e) {
	e.printStackTrace();
}

DBUtils与Handler总结:

ResultSetHandler 常用的实现类
以下是使用频率从高往低排序结果:
------------------------------------------✈
BeanHandler, 查询到的单个数据封装成一个对象
BeanListHandler, 查询到的多个数据封装 成一个List<对象>
------------------------------------------🚑
ArrayHandler, 查询到的单个数据封装成一个数组
ArrayListHandler, 查询到的多个数据封装成一个集合 ,集合里面的元素是数组。
------------------------------------------⛵
MapHandler, 查询到的单个数据封装成一个map
MapListHandler,查询到的多个数据封装成一个集合 ,集合里面的元素是map。
------------------------------------------🚲
ColumnListHandler
KeyedHandler
ScalarHandler

HPF-自我总结

  这部分的重点就是要学会使用事务,并且会解决关于数据库连接出现的问题。
  其次,我个人用C3P0用得多,学到目前阶段感觉C3P0是真的很方便,要好好掌握。
  最后,熟练使用BeanListHandler,这个框架很好用
  ——— 给自己一个恰如其分的自信。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章