事务&数据库连接池&DBUtils

事务&数据库连接池&DBUtils

  • 事务
    • 事务的应用场景
    • 执行事务
      • 使用Mysql命令行执行事务
      • 使用java执行事务
    • 事务的特性
    • 事务的安全隐患和隔离级别
      • 事务的安全隐患
      • 事务的隔离级别
      • 数据库锁
        • 悲观锁(Pessimistic Lock)
        • 乐观锁(Optimistic Lock)
  • 数据库连接池
    • DBCP
      • 不使用配置文件
      • 使用配置文件方式
    • C3P0
      • 不使用配置文件方式
      • 使用配置文件方式
  • DBUtils
    • 使用DBUtils实现增删改
    • 使用DBUtils实现查询
      • 自己定义匿名内部类
      • 直接使用框架已经写好的实现类

事务

事务(transaction)是指一组操作,里面包含许多个单一的逻辑. 只要其中一个逻辑没执行成功,那么都算整个事务执行失败,所有的数据都回归到最初的状态(回滚).

事务的应用场景

事务是确保连续逻辑的成功.例如:银行转账

执行事务

使用Mysql命令行执行事务

# 开启事务
start transaction;
# 下面写一系列sql语句
# ...(sql)语句 
# 若中途没有发生错误,则使用commit提交事务,将更改提交到磁盘上
commit;
# 若中途发生错误,则使用rollback回滚事务,数据不会写到磁盘上,会维持最初的状态
rollback;

使用java执行事务

JDBC连接对象默认是自动提交的,因此需要使用conn.setAutoCommit(false)语句开启事务.

@Test
public void showTransaction() {

	Connection conn = null;
	PreparedStatement ps = null;
	ResultSet rs = null;

	try {
		conn = JDBCUtil.getConn();
		// JDBC连接对象默认是自动提交的,只有关闭自动提交才能执行事务
		conn.setAutoCommit(false);

		// 创建一个statement用于执行转账操作,转账过程中两个SQL语句之间不应该发生错误.
		String sql = "update account set money = money - ? where id = ?";
		ps = conn.prepareStatement(sql);

		// 执行SQL语句1
		ps.setInt(1, 100);
		ps.setInt(2, 1);
		ps.executeUpdate();

		// SQL语句之间发生错误
		int a = 10 / 0;

		// 执行SQL语句2
		ps.setInt(1, -100);
		ps.setInt(2, 2);
		ps.executeUpdate();

		// 若上述SQL语句执行过程中没有发生错误才提交事务,否则上边对数据库的更改不会被保存
		conn.commit();

	} catch (SQLException e1) {
		try {
			// 若上述SQL语句执行过程中发生了错误,则回滚
			conn.rollback();
		} catch (SQLException e2) {
			e2.printStackTrace();
		}
		e1.printStackTrace();
	} finally {
		JDBCUtil.release(conn, ps, rs);
	}
}

事务的特性

  1. 原子性: 事务中包含的逻辑不可分割
  2. 一致性: 事务执行前后数据保持完整性
  3. 隔离性: 事务在执行期间不应该受到其他事务的影响
  4. 持久性: 事务执行成功,那么数据应该持久保存到磁盘上。

事务的安全隐患和隔离级别

事务之间要求隔离性,即事务在执行期间不应该受到其他事务的影响.因此要对事务设置隔离级别.

事务的安全隐患

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

  1. 脏读: 一个事务读到另外一个事务还未提交(可能被回滚)的脏数据
  2. 不可重复读: 一个事务读到了另外一个事务提交的数据,造成了前后两次查询结果不一致
  3. 幻读: 在第一个事务期间第二个事务添加了数据,导致第一个事务两次查询到的数据条数不同.

事务的隔离级别

  1. 未提交读(Read Uncommitted): 事务中的修改,即使没有提交,其他事务也可以看得到. 会导致"脏读”、“幻读”和“不可重复读取”.

  2. 提交读(Read Commited): 大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据. 避免了“脏读取”,但不能避免“幻读”和“不可重复读取”,该级别适用于大多数系统.

  3. 重复读(Repeatable Read): 保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据. 避免了“脏读取”和“不可重复读取”的情况,但不能避免“幻读”,但是带来了更多的性能损失.

  4. 串行化(Serializable): 事务串行执行,一个时刻只能有一个事务被执行. 可以避免"脏读”、“幻读”和“不可重复读取”,但资源消耗最大.

数据库锁

悲观锁(Pessimistic Lock)

悲观锁假定数据一定会发生丢失更新的情况. 每次在查询数据时都上锁.
在Mysql中查询数据时,在SQL语句后加for update可以给被查询字段上悲观锁.

select * from 表名 for update;

乐观锁(Optimistic Lock)

乐观锁假定数据一定不会发生丢失更新的情况. 查询数据时不会上锁,但是会在表中添加一个版本字段,通过比对版本字段判定是否发生冲突.
乐观锁要有程序员自己实现.

数据库连接池

JDBC链接对象的创建工作消耗性能,因此我们一开始先在内存中开辟一块空间存放多个连接对象. 后面需要连接时直接从池中取.
数据库连接池中通过装饰设计模式重写了Connection对象的close()方法.调用close()方法时,不销毁Coonection对象,而是将其放回到连接池中.

开源连接池常用的有 DBCP 和 C3P0

DBCP

不使用配置文件

不使用配置文件,使用其set属性()方法进行初始化,并通过getConnection()方法得到数据库连接对象.

public void showDBCP() {
	Connection conn = null;
	PreparedStatement ps = null;
	
	try {
		// 1. 构建连接池对象
		BasicDataSource dataSource = new BasicDataSource();
		// 使用BasicDataSource的方法配置初始化参数
		dataSource.setDriverClassName("com.mysql.jdbc.Driver");
		dataSource.setUrl("jdbc:mysql://localhost/数据库名");
		dataSource.setUsername("username");
		dataSource.setPassword("password");

		// 2. 使用getConnection方法得到连接对象
		conn = dataSource.getConnection();
		// 下面创建PreStatement对象执行查询
		//	...
	} catch (SQLException e) {
		e.printStackTrace();
	} finally {
		JDBCUtil.release(conn, ps);
	}
}

使用配置文件方式

使用连接池工厂的createDataSource(Properties)方法创建数据库连接池,传入的参数为一个Properties对象.
连接池工厂会自动从配置文件中读取JDBC的初始化参数.

public void showDBCP() {
	Connection conn = null;
	PreparedStatement ps = null;
	
	try {
		// 1. 建立通过连接池工厂创建连接池
		BasicDataSourceFactory factory = new BasicDataSourceFactory();
		Properties properties = new Properties();
		properties.load(new FileInputStream("src//dbconfig.properties"));
		DataSource dataSource = factory.createDataSource(properties);
		
		// 2. 得到连接对象的方法与上面相同
		conn = dataSource.getConnection();
		// 下面创建PreStatement对象执行查询
		//	...
	} catch (Exception e) {
		e.printStackTrace();
	}finally {
		JDBCUtil.release(conn, ps);
	}
}

C3P0

不使用配置文件方式

不使用配置文件时,C3P0的使用方法与DBCP类似,通过set属性()方法传入JDBC初始化参数.

@Test
public void testC3P0() {
	Connection conn = null;
	PreparedStatement ps = null;
	
	try {
		// 1. 创建数据库连接池
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		// 2. 设置数据库初始化参数
		dataSource.setDriverClass("com.mysql.jdbc.Driver");			
		dataSource.setJdbcUrl("jdbc:mysql://localhost/bank");
		dataSource.setUser("root");
		dataSource.setPassword("root");
		
		//3. 得到数据库连接对象
		conn = dataSource.getConnection();
		// ...
	} catch (Exception e) {
		e.printStackTrace();
	}finally {
		JDBCUtil.release(conn, ps);
	}
}

使用配置文件方式

C3P0使用的配置文件为xml格式,可以在一个xml文件中写多套配置,其格式如下所示:


<c3p0-config>

	
	<default-config>
		<property name="driverClass">com.mysql.jdbc.Driverproperty>
		<property name="jdbcUrl">jdbc:mysql://localhost/bankproperty>
		<property name="user">rootproperty>
		<property name="password">rootproperty>
		<property name="initialPoolSize">10property>
		<property name="maxIdleTime">30property>
		<property name="maxPoolSize">100property>
		<property name="minPoolSize">10property>
		<property name="maxStatements">200property>
	default-config>

	
	<named-config name="oracle">
		<property name="acquireIncrement">50property>
		<property name="initialPoolSize">100property>
		<property name="minPoolSize">50property>
		<property name="maxPoolSize">1000property>

		
		<property name="maxStatements">0property>
		<property name="maxStatementsPerConnection">5property>

		
		<user-overrides user="master-of-the-universe">
			<property name="acquireIncrement">1property>
			<property name="initialPoolSize">1property>
			<property name="minPoolSize">1property>
			<property name="maxPoolSize">5property>
			<property name="maxStatementsPerConnection">50property>
		user-overrides>
	named-config>
c3p0-config>
public void testC3P0(){
	Connection conn = null;
	PreparedStatement ps = null;
	
	try {		
		// 参数传入配置名,缺省为默认配置
		ComboPooledDataSource dataSource = new ComboPooledDataSource("配置名");	
		// 得到连接对象
		conn = dataSource.getConnection();
		// ...
	} catch (Exception e) {
		e.printStackTrace();
	}finally {
		JDBCUtil.release(conn, ps);
	}
}

DBUtils

数据库连接池帮我们获得连接对象,而DBUtils帮助我们完成CRUD.
DBUtils提供了一种通用的方法来执行CRUD,而无关具体查询参数.

使用DBUtils实现增删改

使用QueryRunner对象的update("SQL语句", 参数...)方法完成数据的增删改.

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

使用DBUtils实现查询

底层PreparedStatement对象在执行SQL查询语句后,会返回一个ResultSet对象.而使用DBUtils,我们希望执行查询后直接返回的是Java对象.
因此需要给QueryRunner对象的query()函数传入一个实现ResultSetHandler接口的类的对象,这个类中的T handle(ResultSet rs)方法指明了如何将ResultSet对象转换为T对象.

这个接口的实现类可以自己定义,也可以使用现成的类.

自己定义匿名内部类

QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());

List<Account> accounts = queryRunner.query("select * from account where id = ?",
	new ResultSetHandler<List<Account>>() {
		@Override
		public List<Account> handle(ResultSet rs) throws SQLException {
			List<Account> accounts;
			while (rs.next()) {
				Account account = new Account(rs.getString("name"), rs.getInt("money"));
				accounts.add(account);
			}
			return accounts;
		}
	}, 6);

直接使用框架已经写好的实现类

QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
// 查询单个对象
Account account = queryRunner.query( "select * from account where id = ?", 
	new BeanHandler<Account>(Account.class), 8);
// 查询多个对象
List<Account> list = queryRunner.query("select * from account ",
	new BeanListHandler<Account>(Account.class));

ResultSetHandler的常用实现类如下:

  • BeanHandler: 查询到的单个数据封装成一个对象
  • BeanListHandler: 查询到的多个数据封装成一个List<对象>
  • ArrayHandler: 查询到的单个数据封装成一个数组
  • ArrayListHandler: 查询到的多个数据封装成一个集合,集合里面的元素是数组
  • MapHandler: 查询到的单个数据封装成一个map
  • MapListHandler: 查询到的多个数据封装成一个集合,集合里面的元素是map

你可能感兴趣的:(#,JAVA,WEB,JDBC)