JavaWeb之JDBC(2.1):数据库事务、DAO及其子类、数据库连接池、DBUtils工具包的使用介绍

写在前面:JDBCUtils类代码主要提供对MySQL数据库的连接、关闭功能,Customer和Order类主要封装对应数据库相关属性并提供get、set、toString方法及构造器,具体代码请看前面的博文。

数据库的事务

1.事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
2.事务处理的原则:
保证所事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所修改,整个事务回滚(rollback)到最初状态。

3.代码的体现:

@Test
public void testUpdateWithTx() {
Connection conn = null;
	try {
		conn = JDBCUtils.getConnection();//①手动实现 ②数据库连接池(C3P0 / DBCP)
		
		//1.设置不能自动提交数据
		conn.setAutoCommit(false);

		String sql1 = "update user_table set balance = balance - 100 where user = ?";
		String sql2 = "update user_table set balance = balance + 100 where user = ?";
		//过程一:
		updateWithTx(conn,sql1,"AA"); //① 手动实现:version 2.0  ② 使用dbUtils包里QueryRunner实现
		
		//出现异常
		System.out.println(10 / 0);
		
		//过程二:
		updateWithTx(conn,sql2,"BB");//① 手动实现:version 2.0
		
		//2.提交数据
		conn.commit();
		System.out.println("转账成功!");
	} catch (Exception e) {
		e.printStackTrace();
		try {
			//3.回滚数据
			conn.rollback();
		} catch (SQLException e1) {
			e1.printStackTrace();
		}
	}finally {
		if(conn != null) {
			//关闭连接
			JDBCUtils.closeResource(conn, null);  //① 手动关闭 ② DbUtils类的关闭方法
		}
	}
}

4.考虑到事务以后,实现的通用的增删改操作: version 2.0

// 通用的增删改方法 (version 2.0
	public void update(Connection conn, String sql, Object... args) {
	PreparedStatement ps = null;
	try {

		// 1.获取PreparedStatement的实例 (或:预编译sql语句)
		ps = conn.prepareStatement(sql);
		// 2.填充占位符
		for (int i = 0; i < args.length; i++) {
			ps.setObject(i + 1, args[i]);
		}

		// 3.执行sql语句
		// ps.execute();

		int count = ps.executeUpdate();
		System.out.println("影响了" + count + "条数据");

	} catch (Exception e) {

		e.printStackTrace();
	} finally {
		// 4.关闭资源
		JDBCUtils.closeResource(null, ps);
	}
}

考虑到事务以后,实现的通用的查询: version 2.0

// 通用的查询方法,返回一个对象 (version 2.0)
public T getInstance(Connection conn, Class<T> clazz, String sql, Object... args) {
	PreparedStatement ps = null;
	ResultSet rs = null;
	try {

		// 1.预编译sql语句,得到PreparedStatement对象
		ps = conn.prepareStatement(sql);

		// 2.填充占位符
		for (int i = 0; i < args.length; i++) {
			ps.setObject(i + 1, args[i]);
		}

		// 3.执行executeQuery(),得到结果集:ResultSet
		rs = ps.executeQuery();

		// 4.得到结果集的元数据:ResultSetMetaData
		ResultSetMetaData rsmd = rs.getMetaData();

		// 5.1通过ResultSetMetaData得到columnCount,columnLabel;通过ResultSet得到列值
		int columnCount = rsmd.getColumnCount();
		if (rs.next()) {
			T t = clazz.newInstance();
			for (int i = 0; i < columnCount; i++) {// 遍历每一个列

				// 获取列值
				Object columnVal = rs.getObject(i + 1);
				// 获取列的别名:列的别名,使用类的属性名充当
				String columnLabel = rsmd.getColumnLabel(i + 1);
				// 5.2使用反射,给对象的相应属性赋值
				Field field = clazz.getDeclaredField(columnLabel);
				field.setAccessible(true);
				field.set(t, columnVal);

			}

			return t;

		}
	} catch (Exception e) {

		e.printStackTrace();
	} finally {
		// 6.关闭资源
		JDBCUtils.closeResource(null, ps, rs);
	}

	return null;
}

// 通用的查询方法,返回多个对象构成的集合 (version 2.0)
public List<T> getForList(Connection conn, Class<T> clazz, String sql, Object... args) {
	PreparedStatement ps = null;
	ResultSet rs = null;
	List<T> list = new ArrayList<>();
	try {

		// 1.预编译sql语句,得到PreparedStatement对象
		ps = conn.prepareStatement(sql);

		// 2.填充占位符
		for (int i = 0; i < args.length; i++) {
			ps.setObject(i + 1, args[i]);
		}

		// 3.执行executeQuery(),得到结果集:ResultSet
		rs = ps.executeQuery();

		// 4.得到结果集的元数据:ResultSetMetaData
		ResultSetMetaData rsmd = rs.getMetaData();

		// 5.1通过ResultSetMetaData得到columnCount,columnLabel;通过ResultSet得到列值
		int columnCount = rsmd.getColumnCount();
		while (rs.next()) {// ①判断下一行是否有数据 ②如果数据,指针下移一位
			T t = clazz.newInstance();

			// 装配一个行数对应的一个对象
			for (int i = 0; i < columnCount; i++) {// 遍历每一个列

				// 获取列值
				Object columnVal = rs.getObject(i + 1);
				// 获取列的别名:列的别名,使用类的属性名充当
				String columnLabel = rsmd.getColumnLabel(i + 1);
				// 5.2使用反射,给对象的相应属性赋值
				Field field = clazz.getDeclaredField(columnLabel);
				field.setAccessible(true);
				field.set(t, columnVal);

			}

			list.add(t);

		}

		return list;

	} catch (Exception e) {

		e.printStackTrace();
	} finally {
		// 6.关闭资源
		JDBCUtils.closeResource(null, ps, rs);
	}
	return null;
}

事务的属性

在这里插入图片描述
数据操作过程中可能出现的问题:
在这里插入图片描述
在这里插入图片描述

DAO及其子类介绍

//DAO:data(base) access Object 数据(库)访问对象
【DAO.java】
//DAO:data(base) access object

public class DAO<T> {
private Class<T> clazz = null; //clazz:T
public DAO(){
	//获取泛型参数
	clazz = ReflectionUtils.getGenericSuperClassParam(this.getClass());
//		System.out.println(this.getClass());
	}
	//获取特殊意义的数据
	public <E> E getValue(Connection conn, String sql, Object... args) {

	PreparedStatement ps = null;
	ResultSet rs = null;
	try {
		// 1.预编译sql语句,得到PreparedStatement对象
		ps = conn.prepareStatement(sql);

		// 2.填充占位符
		for (int i = 0; i < args.length; i++) {
			ps.setObject(i + 1, args[i]);
		}
		
		// 3.执行executeQuery(),得到结果集:ResultSet
		rs = ps.executeQuery();
		//4.处理结果集数据
		if(rs.next()){
			return (E) rs.getObject(1);
		}
	} catch (SQLException e) {
		
		e.printStackTrace();
	}finally{
		//5.关闭资源
		JDBCUtils.closeResource(null, ps, rs);
		
	}
	

	return null;
}

// 通用的查询方法,返回多个对象构成的集合 (version 2.0
public List<T> getForList(Connection conn, String sql, Object... args) {
	PreparedStatement ps = null;
	ResultSet rs = null;
	List<T> list = new ArrayList<>();
	try {

		// 1.预编译sql语句,得到PreparedStatement对象
		ps = conn.prepareStatement(sql);

		// 2.填充占位符
		for (int i = 0; i < args.length; i++) {
			ps.setObject(i + 1, args[i]);
		}

		// 3.执行executeQuery(),得到结果集:ResultSet
		rs = ps.executeQuery();

		// 4.得到结果集的元数据:ResultSetMetaData
		ResultSetMetaData rsmd = rs.getMetaData();

		// 5.1通过ResultSetMetaData得到columnCount,columnLabel;通过ResultSet得到列值
		int columnCount = rsmd.getColumnCount();
		while (rs.next()) {// ①判断下一行是否有数据 ②如果数据,指针下移一位
			T t = clazz.newInstance();

			// 装配一个行数对应的一个对象
			for (int i = 0; i < columnCount; i++) {// 遍历每一个列

				// 获取列值
				Object columnVal = rs.getObject(i + 1);
				// 获取列的别名:列的别名,使用类的属性名充当
				String columnLabel = rsmd.getColumnLabel(i + 1);
				// 5.2使用反射,给对象的相应属性赋值
				Field field = clazz.getDeclaredField(columnLabel);
				field.setAccessible(true);
				field.set(t, columnVal);
			}
			list.add(t);
		}
		return list;
	} catch (Exception e) {

		e.printStackTrace();
	} finally {
		// 6.关闭资源
		JDBCUtils.closeResource(null, ps, rs);
	}

	return null;
}
// 通用的查询方法,返回一个对象 (version 2.0
public T getInstance(Connection conn, String sql, Object... args) {

	PreparedStatement ps = null;
	ResultSet rs = null;
	try {
		// 1.预编译sql语句,得到PreparedStatement对象
		ps = conn.prepareStatement(sql);
		// 2.填充占位符
		for (int i = 0; i < args.length; i++) {
			ps.setObject(i + 1, args[i]);
		}
		// 3.执行executeQuery(),得到结果集:ResultSet
		rs = ps.executeQuery();

		// 4.得到结果集的元数据:ResultSetMetaData
		ResultSetMetaData rsmd = rs.getMetaData();

		// 5.1通过ResultSetMetaData得到columnCount,columnLabel;通过ResultSet得到列值
		int columnCount = rsmd.getColumnCount();
		if (rs.next()) {
			T t = clazz.newInstance();
			for (int i = 0; i < columnCount; i++) {// 遍历每一个列
				// 获取列值
				Object columnVal = rs.getObject(i + 1);
				// 获取列的别名:列的别名,使用类的属性名充当
				String columnLabel = rsmd.getColumnLabel(i + 1);
				// 5.2使用反射,给对象的相应属性赋值
				Field field = clazz.getDeclaredField(columnLabel);
				field.setAccessible(true);
				field.set(t, columnVal);
			}
			return t;
		}
	} catch (Exception e) {

		e.printStackTrace();
	} finally {
		// 6.关闭资源
		JDBCUtils.closeResource(null, ps, rs);
	}
	return null;
}
// 通用的增删改方法 (version 2.0
public void update(Connection conn, String sql, Object... args) {
	PreparedStatement ps = null;
	try {

		// 1.获取PreparedStatement的实例 (或:预编译sql语句)
		ps = conn.prepareStatement(sql);
		// 2.填充占位符
		for (int i = 0; i < args.length; i++) {
			ps.setObject(i + 1, args[i]);
		}

		// 3.执行sql语句
		// ps.execute();

		int count = ps.executeUpdate();
		System.out.println("影响了" + count + "条数据");

	} catch (Exception e) {

		e.printStackTrace();
	} finally {
		// 4.关闭资源
		JDBCUtils.closeResource(null, ps);

	}
}
}

另外提供【ReflectionUtils.java】

class ReflectionUtils{
	//获取形参clazz的父类的泛型
	public static Class getGenericSuperClassParam(Class clazz){//比如:clazz:CustomerDAO,OrderDAO		
		Type genericSuperClass = clazz.getGenericSuperclass();
		ParameterizedType paramsType = (ParameterizedType) genericSuperClass;
		Type[] arguments = paramsType.getActualTypeArguments();
		return (Class)arguments[0];		
	}	
}

CustomerDAO类

public class CustomerDAO extends DAO<Customer>{	
public CustomerDAO(){		
}	
//获取表中的条目数
public long getCount(Connection conn){
	String sql = "select count(*) from customers";		
	long count = getValue(conn, sql);
	return count;
}	
//获取最大的生日
public Date getMaxBirth(Connection conn){
	String sql = "select max(birth) from customers";
	Date birth = getValue(conn, sql);
	return birth;
}

/**
 * 返回数据表中的所客户记录
 * @author shkstart 邮箱:shkstart@126.com
 * @param conn
 * @return
 */
public List<Customer> getAll(Connection conn){
	String sql = "select id,name,email,birth from customers";
	List<Customer> list = getForList(conn, sql);
	return list;
	
}

/**
 * 返回指定id位置上的客户
 * 
 * @author shkstart 邮箱:shkstart@126.com
 * @param conn
 * @param id
 * @return
 */
public Customer getCustomerById(Connection conn,int id){
	String sql = "select id,name,email,birth from customers where id = ?";
	Customer customer = getInstance(conn, sql, id);
	return customer;
}

/**
 * 修改cust.getId()位置的客户信息。
 * 
 * @author shkstart 邮箱:shkstart@126.com
 * @param conn
 * @param cust
 */
public void update(Connection conn ,Customer cust){
	String sql = "update customers set name = ?,email = ?,birth = ? where id = ?";
	update(conn, sql, cust.getName(),cust.getEmail(),cust.getBirth(),cust.getId());
}

/**
 * 删除指定id位置的customer
 * @author shkstart 邮箱:shkstart@126.com
 * @param conn
 * @param id
 */
public void deleteById(Connection conn,int id){
	
	String sql = "delete from customers where id = ?";
	update(conn, sql, id);
}

/**
 * 将 cust添加到数据库中
 * @author shkstart 邮箱:shkstart@126.com
 * @param conn
 * @param cust
 */
public void addCustomer(Connection conn,Customer cust){
	
	String sql = "insert into customers(name,email,birth)values(?,?,?)";
	
	update(conn, sql,cust.getName(),cust.getEmail(),cust.getBirth());
}
}

事务操作

@Test
public void test(){
	Connection conn = null;
	try {
		CustomerDAO dao = new CustomerDAO();
		conn = JDBCUtils.getConnection(); //①手动创建连接 ②数据库连接池
		
		conn.setAutoCommit(false);
		//针对于一个事务,体现对表的增、删、改、查
		dao.addCustomer();
		dao.updateCustomer();  //①手动使用PreparedStatement实现CRUD操作
						    //②调用DBUtils包里的QueryRunner类实现CRUD操作

		conn.commit();
	} catch (Exception e) {
		e.printStackTrace();
		try {
			if(conn != null)
				conn.rollback();
		} catch (SQLException e1) {
			
			e1.printStackTrace();
		}
	}finally{
		JDBCUtils.closeResource(conn, null);//①手动的关闭资源 ②调用DBUtils类的方法关闭资源
	}
}

数据库连接池

1.传统连接的问题:
普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用.若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。
这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。

2.如何解决传统开发中的数据库连接问题:使用数据库连接池技术
3.使用数据库连接池的好处:
①资源重用
②效率更高
③便于连接池的管理
4.实现的方式:

使用C3P0数据库连接池 :hibernate官方推荐使用
使用DBCP数据库连接池 :tomcat 服务器自带dbcp数据库连接池

C3P0连接池

导入jar包:
在这里插入图片描述
菜单栏选择File>ProjectStructure>
在这里插入图片描述
选择jar包并展开
在这里插入图片描述
使用配置文件

private static ComboPooledDataSource cpds = new ComboPooledDataSource("helloc3p0");
// 使用C3P0实现数据库的连接2:使用配置文件
public static Connection getConnection2() throws Exception {
		
		Connection conn = cpds.getConnection();
		return conn;
}

在这里插入图片描述

其中,配置文件定义在src下。名为:c3p0-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
	<named-config name="helloc3p0">
		<!-- 使用c3p0实现数据库连接的4个基本信息 -->
		<property name="driverClass">com.mysql.jdbc.Driver</property>
		<property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
		<property name="user">root</property>
		<property name="password">abc123</property>


		<!-- 对c3p0数据库连接池的管理 -->
		<!-- 如果c3p0数据库连接池连接数不够的情况,一次性向数据库申请的连接数 -->
		<property name="acquireIncrement">5</property>
		<!-- 初始化时,连接池中的连接数 -->
		<property name="initialPoolSize">10</property>
		<!-- 连接池中的最少连接数 -->
		<property name="minPoolSize">10</property>
		<!-- 连接池中最大的连接数 -->
		<property name="maxPoolSize">100</property>
		<!-- 连接池中最多可以维护的statement的个数 -->
		<property name="maxStatements">100</property>
		<!-- 每个连接最多可以使用statement的个数 -->
		<property name="maxStatementsPerConnection">2</property>
	</named-config>
</c3p0-config>

DBCP连接池

导入jar包:
在这里插入图片描述
自行展开,不再叙述,eclipse直接buildPath。

使用配置文件:

//使用DBCP数据库连接池获取数据库连接:使用配置文件
	private static DataSource dataSource = null;
	static{
		try {
			Properties pros = new Properties();
			InputStream is = DBCPTest.class.getClassLoader().getResourceAsStream("dbcp.properties");
			pros.load(is);
			dataSource = BasicDataSourceFactory.createDataSource(pros);
		} catch (IOException e) {
			
			e.printStackTrace();
		} catch (Exception e) {
			
			e.printStackTrace();
		}
		
	}

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

其中,配置文件定义在src下:dbcp.properties

username=root
password=abc123
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///test
maxActive=10

DBUtils提供的jar包实现CRUD操作

导入jar包并展开:
在这里插入图片描述
使用现成的jar中的QueryRunner测试增、删、改的操作

@Test
public void testDelete() throws Exception {
	Connection conn = JDBCUtils.getConnection3();

	QueryRunner runner = new QueryRunner();
	String sql = "delete from customers where id >= ?";

	int count = runner.update(conn, sql, 25);
	System.out.println("删除了" + count + "条记录");

	JDBCUtils.closeResource(conn, null);
}

@Test
public void testInsert() throws Exception {

	Connection conn = JDBCUtils.getConnection3();

	QueryRunner runner = new QueryRunner();
	String sql = "insert into customers(name,email,birth) values(?,?,?)";

	int count = runner.update(conn, sql, "武壮", "wuz@126.com", "2000-7-8");
	System.out.println("插入了" + count + "条记录");

	JDBCUtils.closeResource(conn, null);
}

使用现成的jar中的QueryRunner测试查询的操作. ResultSetHandler接口

// BeanHandler:用于返回一个java类的对象
@Test
public void testQuery() throws Exception {
	Connection conn = JDBCUtils.getConnection3();

	QueryRunner runner = new QueryRunner();
	String sql = "select id,name,email,birth from customers where id = ?";
	BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
	Customer customer = runner.query(conn, sql, handler, 6);
	System.out.println(customer);

	JDBCUtils.closeResource(conn, null);
}

// BeanListHandler:用于返回一个由多个java类的对象构成的集合
@Test
public void testQueryForList() throws Exception {
	Connection conn = JDBCUtils.getConnection3();

	QueryRunner runner = new QueryRunner();
	String sql = "select id,name,email,birth from customers where id <= ?";
	//方式一:
//		BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
	//方式二:
	ResultSetHandler<List<Customer>> handler = new BeanListHandler<>(Customer.class);
	List<Customer> customers = runner.query(conn, sql, handler, 6);
	customers.forEach(System.out::println);

	JDBCUtils.closeResource(conn, null);
}

//ScalarHandler:返回特殊值的handler
@Test
public void testScalarHandler() throws Exception{
	Connection conn = JDBCUtils.getConnection3();

	QueryRunner runner = new QueryRunner();
	ScalarHandler handler = new ScalarHandler();
	
//		String sql = "select count(*) from customers";
//		long count = (long) runner.query(conn, sql,handler);
//		System.out.println(count);
		String sql = "select max(birth) from customers";
	Date maxBirth = (Date) runner.query(conn, sql,handler);
	System.out.println(maxBirth);
	JDBCUtils.closeResource(conn, null);
}

图解JDBC事务及数据库连接池

数据库事务

事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。
为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。

JDBC 事务处理

当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚
为了让多个 SQL 语句作为一个事务执行:

调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
在出现异常时,调用 rollback(); 方法回滚事务
若此时 Connection 没有被关闭, 则需要恢复其自动提交状态

数据库事务使用的过程

public void testJDBCTransaction() {
Connection conn = null;
try {
// 1.获取数据库连接
conn = JDBCUtils.getConnection();
// 2.开启事务
conn.setAutoCommit(false);
// 3.进行数据库事务操作

// 4.若没有异常,则提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
// 5.若有异常,则回滚事务
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}} finally {
JDBCUtils.close(null, null, conn); }  }

事务的ACID(acid)属性

  1. 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  2. 一致性(Consistency)事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
  3. 隔离性(Isolation)事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  4. 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响

以第一个 DML 语句的执行作为开始
以下面的其中之一作为结束:

COMMIT 或 ROLLBACK 语句
DDL 或 DCL 语句(自动提交)
用户会话正常结束
系统异常终了

DDL: Data Definition Language 数据定义语言(用来定义数据库结构): create table; alter table; drop table; create index; drop index
DCL: Data Control Language 数据控制语言(用来控制数据库的访问):grant; revoke; commit; rollback; lock;
DML: Data Manipulation Language 数据操纵语言(用来查询与更新记录): insert; update; delete

COMMIT和ROLLBACK语句的优点

使用COMMIT 和 ROLLBACK语句,我们可以:

确保数据完整性。
数据改变被提交之前预览。
将逻辑上相关的操作分组。

数据完整性:
存储在数据库中的所有数据值均处于正确的状态。如果数据库中存储有不正确的数据值,则该数据库称为已丧失数据完整性。
数据库采用多种方法来保证数据完整性,包括外键、束约、规则和触发器。

提交或回滚前的数据状态

改变前的数据状态是可以恢复的
执行 DML 操作的用户可以通过 SELECT 语句查询提交或回滚之前的修正
其他用户不能看到当前用户所做的改变,直到当前用户结束事务。
DML语句所涉及到的行被锁定, 其他用户不能操作。

提交后的数据状态

数据的改变已经被保存到数据库中。
改变前的数据已经丢失。
所有用户可以看到结果。
锁被释放, 其他用户可以操作涉及到的数据。

提交数据

改变数据

DELETE FROM employees
WHERE  employee_id = 99999;

1 row deleted.

INSERT INTO departments 
VALUES (290, 'Corporate Tax', NULL, 1700);

1 row inserted.

提交改变

COMMIT;
Commit complete.

数据回滚后的状态

使用 ROLLBACK 语句可使数据变化失效:
数据改变被取消。
修改前的数据状态可以被恢复。
在这里插入图片描述

附:数据库的隔离级别

对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:

脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.
不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.
幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.

数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题.
一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱.
数据库提供的 4 种事务隔离级别:
在这里插入图片描述
Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED
Mysql 支持 4 种事务隔离级别. Mysql 默认的事务隔离级别为: REPEATABLE READ
每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别.
查看当前的隔离级别: SELECT @@tx_isolation;
设置当前 mySQL 连接的隔离级别:

set  transaction isolation level read committed;

设置数据库系统的全局的隔离级别:

set global transaction isolation level read committed;

SET autocommit = 0; 禁止操作自动提交

数据库连接池

JDBC数据库连接池的必要性

在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:

在主程序(如servlet、beans)中建立数据库连接
进行sql操作
断开数据库连接

这种模式开发,存在的问题:

普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用.若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。
这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。

数据库连接池(connection pool)

为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
在这里插入图片描述

数据库连接池的工作原理

在这里插入图片描述

数据库连接池技术的优点

资源重用

由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。

更快的系统反应速度

数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间

新的资源分配手段

对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源

统一的连接管理,避免数据库连接泄露

在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露

两种开源的数据库连接池

JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:

DBCP 数据库连接池
C3P0 数据库连接池

DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。

C3P0 数据源

在这里插入图片描述

DBCP 数据源

DBCP 是 Apache 软件基金组织下的开源连接池实现,该连接池依赖该组织下的另一个开源系统:Common-pool. 如需使用该连接池实现,应在系统中增加如下两个 jar 文件:

Commons-dbcp.jar:连接池的实现
Commons-pool.jar:连接池实现的依赖库

Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。

DBCP 数据源使用范例

数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但上面的代码并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
范 例
在这里插入图片描述

DBUtils工具类

将常用的操作数据库的JDBC的类和方法集合在一起,就是DBUtils.
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

BeanHandler:把结果集转为一个 Bean
BeanListHandler:把结果集转为一个 Bean 的集合
MapHandler:把结果集转为一个 Map
MapListHandler:把结果集转为一个 Map 的 List
ScalarHandler:把结果集转为一个类型的数据返回, 该类型通		常指 String 或其它 8 种基本数据类型.
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
package com.hexiang.utils; import java.sql.*; import java.util.*; /** * * Title: 数据库工具类 * * * Description: 将大部分的数据库操作放入这个类中, 包括数据库连接的建立, 自动释放等. * * * @author beansoft 日期: 2004年04月 * @version 2.0 */ public class DatabaseUtil { /** 数据库连接 */ private java.sql.Connection connection; /** * All database resources created by this class, should be free after all * operations, holds: ResultSet, Statement, PreparedStatement, etc. */ private ArrayList resourcesList = new ArrayList(5); public DatabaseUtil() { } /** 关闭数据库连接并释放所有数据库资源 */ public void close() { closeAllResources(); close(getConnection()); } /** * Close given connection. * * @param connection * Connection */ public static void close(Connection connection) { try { connection.close(); } catch (Exception ex) { System.err.println("Exception when close a connection: " + ex.getMessage()); } } /** * Close all resources created by this class. */ public void closeAllResources() { for (int i = 0; i < this.getResourcesList().size(); i++) { closeJDBCResource(getResourcesList().get(i)); } } /** * Close a jdbc resource, such as ResultSet, Statement, Connection.... All * these objects must have a method signature is void close(). * * @param resource - * jdbc resouce to close */ public void closeJDBCResource(Object resource) { try { Class clazz = resource.getClass(); java.lang.reflect.Method method = clazz.getMethod("close", null); method.invoke(resource, null); } catch (Exception e) { // e.printStackTrace(); } } /** * 执行 SELECT 等 SQL 语句并返回结果集. * * @param sql * 需要发送到数据库 SQL 语句 * @return a ResultSet object that contains the data produced * by the given query; never null */ public ResultSet executeQuery(String sql) { try { Statement statement = getStatement(); ResultSet rs = statement.executeQuery(sql); this.getResourcesList().add(rs); this.getResourcesList().add(statement);// BUG fix at 2006-04-29 by BeanSoft, added this to res list // MySql 数据库要求必需关闭 statement 对象, 否则释放不掉资源 // - 此观点错误, 因为关闭此对象后有时数据无法读出 //statement.close(); return rs; } catch (Exception ex) { System.out.println("Error in executeQuery(\"" + sql + "\"):" + ex); // ex.printStackTrace(); return null; } } /** * Executes the given SQL statement, which may be an INSERT, * UPDATE, or DELETE statement or an SQL * statement that returns nothing, such as an SQL DDL statement. 执行给定的 SQL * 语句, 这些语句可能是 INSERT, UPDATE 或者 DELETE 语句, 或者是一个不返回任何东西的 SQL 语句, 例如一个 SQL * DDL 语句. * * @param sql * an SQL INSERT,UPDATE or * DELETE statement or an SQL statement that * returns nothing * @return either the row count for INSERT, * UPDATE or DELETE statements, or * 0 for SQL statements that return nothing */ public int executeUpdate(String sql) { try { Statement statement = getStatement(); return statement.executeUpdate(sql); // MySql 数据库要求必需关闭 statement 对象, 否则释放不掉资源 // - 此观点错误, 因为关闭此对象后有时数据无法读出 //statement.close(); } catch (Exception ex) { System.out.println("Error in executeUpdate(): " + sql + " " + ex); //System.out.println("executeUpdate:" + sql); ex.printStackTrace(); } return -1; } /** * 返回记录总数, 使用方法: getAllCount("SELECT count(ID) from tableName") 2004-06-09 * 可滚动的 Statement 不能执行 SELECT MAX(ID) 之类的查询语句(SQLServer 2000) * * @param sql * 需要执行的 SQL * @return 记录总数 */ public int getAllCount(String sql) { try { Statement statement = getConnection().createStatement(); this.getResourcesList().add(statement); ResultSet rs = statement.executeQuery(sql); rs.next(); int cnt = rs.getInt(1); rs.close(); try { statement.close(); this.getResourcesList().remove(statement); } catch (Exception ex) { ex.printStackTrace(); } return cnt; } catch (Exception ex) { System.out.println("Exception in DatabaseUtil.getAllCount(" + sql + "):" + ex); ex.printStackTrace(); return 0; } } /** * 返回当前数据库连接. */ public java.sql.Connection getConnection() { return connection; } /** * 连接新的数据库对象到这个工具类, 首先尝试关闭老连接. */ public void setConnection(java.sql.Connection connection) { if (this.connection != null) { try { getConnection().close(); } catch (Exception ex) { } } this.connection = connection; } /** * Create a common statement from the database connection and return it. * * @return Statement */ public Statement getStatement() { // 首先尝试获取可滚动的 Statement, 然后才是普通 Statement Statement updatableStmt = getUpdatableStatement(); if (updatableStmt != null) return updatableStmt; try { Statement statement = getConnection().createStatement(); this.getResourcesList().add(statement); return statement; } catch (Exception ex) { System.out.println("Error in getStatement(): " + ex); } return null; } /** * Create a updatable and scrollable statement from the database connection * and return it. * * @return Statement */ public Statement getUpdatableStatement() { try { Statement statement = getConnection() .createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); this.getResourcesList().add(statement); return statement; } catch (Exception ex) { System.out.println("Error in getUpdatableStatement(): " + ex); } return null; } /** * Create a prepared statement and return it. * * @param sql * String SQL to prepare * @throws SQLException * any database exception * @return PreparedStatement the prepared statement */ public PreparedStatement getPreparedStatement(String sql) throws SQLException { try { PreparedStatement preparedStatement = getConnection() .prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); this.getResourcesList().add(preparedStatement); return preparedStatement; } catch (Exception ex) { ex.printStackTrace(); } return null; } /** * Return the resources list of this class. * * @return ArrayList the resources list */ public ArrayList getResourcesList() { return resourcesList; } /** * Fetch a string from the result set, and avoid return a null string. * * @param rs * the ResultSet * @param columnName * the column name * @return the fetched string */ public static String getString(ResultSet rs, String columnName) { try { String result = rs.getString(columnName); if (result == null) { result = ""; } return result; } catch (Exception ex) { } return ""; } /** * Get all the column labels * * @param resultSet * ResultSet * @return String[] */ public static String[] getColumns(ResultSet resultSet) { if (resultSet == null) { return null; } try { ResultSetMetaData metaData = resultSet.getMetaData(); int numberOfColumns = metaData.getColumnCount(); if (numberOfColumns <= 0) { return null; } String[] columns = new String[numberOfColumns]; //System.err.println("numberOfColumns=" + numberOfColumns); // Get the column names for (int column = 0; column < numberOfColumns; column++) { // System.out.print(metaData.getColumnLabel(column + 1) + "\t"); columns[column] = metaData.getColumnName(column + 1); } return columns; } catch (Exception ex) { ex.printStackTrace(); } return null; } /** * Get the row count of the result set. * * @param resultset * ResultSet * @throws SQLException * if a database access error occurs or the result set type is * TYPE_FORWARD_ONLY * @return int the row count * @since 1.2 */ public static int getRowCount(ResultSet resultset) throws SQLException { int row = 0; try { int currentRow = resultset.getRow(); // Remember old row position resultset.last(); row = resultset.getRow(); if (currentRow > 0) { resultset.absolute(row); } } catch (Exception ex) { ex.printStackTrace(); } return row; } /** * Get the column count of the result set. * * @param resultSet * ResultSet * @return int the column count */ public static int getColumnCount(ResultSet resultSet) { if (resultSet == null) { return 0; } try { ResultSetMetaData metaData = resultSet.getMetaData(); int numberOfColumns = metaData.getColumnCount(); return numberOfColumns; } catch (Exception ex) { ex.printStackTrace(); } return 0; } /** * Read one row's data from result set automatically and put the result it a * hashtable. Stored as "columnName" = "value", where value is converted to * String. * * @param resultSet * ResultSet * @return Hashtable */ public static final Hashtable readResultToHashtable(ResultSet resultSet) { if (resultSet == null) { return null; } Hashtable resultHash = new Hashtable(); try { String[] columns = getColumns(resultSet); if (columns != null) { // Read data column by column for (int i = 0; i < columns.length; i++) { resultHash.put(columns[i], getString(resultSet, columns[i])); } } } catch (Exception ex) { ex.printStackTrace(); } return resultHash; } /** * Read data from result set automatically and put the result it a * hashtable. Stored as "columnName" = "value", where value is converted to * String. * * Note: assume the default database string encoding is ISO8859-1. * * @param resultSet * ResultSet * @return Hashtable */ @SuppressWarnings("unchecked") public static final Hashtable readResultToHashtableISO(ResultSet resultSet) { if (resultSet == null) { return null; } Hashtable resultHash = new Hashtable(); try { String[] columns = getColumns(resultSet); if (columns != null) { // Read data column by column for (int i = 0; i < columns.length; i++) { String isoString = getString(resultSet, columns[i]); try { resultHash.put(columns[i], new String(isoString .getBytes("ISO8859-1"), "GBK")); } catch (Exception ex) { resultHash.put(columns[i], isoString); } } } } catch (Exception ex) { ex.printStackTrace(); } return resultHash; } /** Test this class. */ public static void main(String[] args) throws Exception { DatabaseUtil util = new DatabaseUtil(); // TODO: 从连接池工厂获取连接 // util.setConnection(ConnectionFactory.getConnection()); ResultSet rs = util.executeQuery("SELECT * FROM e_hyx_trans_info"); while (rs.next()) { Hashtable hash = readResultToHashtableISO(rs); Enumeration keys = hash.keys(); while (keys.hasMoreElements()) { Object key = keys.nextElement(); System.out.println(key + "=" + hash.get(key)); } } rs.close(); util.close(); } }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值