JDBC进阶操作
数据库事务
数据库事务介绍
- 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
- 事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务**回滚(rollback)**到最初状态。
- 为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。
JDBC事务处理
-
数据一旦提交,就不可回滚。
-
数据什么时候意味着提交?
- 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
- **关闭数据库连接,数据就会自动的提交。**如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下。
-
JDBC程序中为了让多个 SQL 语句作为一个事务执行:
- 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
- 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
- 在出现异常时,调用 rollback(); 方法回滚事务
若此时 Connection 未被关闭,还可能被重复使用,则需恢复其自动提交状态
setAutoCommit(true)
, 尤其是在使用数据库连接池技术时,执行close()方法前,建议恢复自动提交状态。/** * 通用的增删改 Version 2.0 引入事务 * 修改的地方: 连接由外部引入和关闭 * @param sql String sql语句 * @param args 可变参数列表 */ public static void commonUpdateWithTx(Connection connection, String sql, Object ...args){ PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); for (int i = 0; i < args.length; i++) { ps.setObject(i+1, args[i]); } ps.execute(); } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeConnection(null, ps); } } /** * 通用的增删改 Version 1.0 没有引入事务 * @param sql String sql语句 * @param args 可变参数列表 */ public static void commonUpdate(String sql, Object ...args){ Connection connection = null; PreparedStatement ps = null; try { connection = JDBCUtils.getConnection(); ps = connection.prepareStatement(sql); for (int i = 0; i < args.length; i++) { ps.setObject(i+1, args[i]); } ps.execute(); } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeConnection(connection, ps); } }
对比上述两段代码, 要引入事务, 则连接需要在外部创建, 如此可以保证
autoCommit()
的完整性, 同样的, 由于是外部引入的, 所以关闭工作也由外部来完成
@Test
public void testUpdateWithTx() throws Exception {
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
System.out.println(connection.getAutoCommit());
// 取消数据的自动提交
connection.setAutoCommit(false);
String sql1 = "UPDATE user_table set balance = balance - 100 WHERE user = ?";
commonUpdateWithTx(connection, sql1, "AA");
// 模拟网络异常
System.out.println(10 / 0);
String sql2 = "UPDATE user_table set balance = balance + 100 WHERE user = ?";
commonUpdateWithTx(connection, sql2, "BB");
System.out.println("转账成功");
connection.commit();
} catch (Exception e) {
System.out.println("转账失败");
if (connection != null) {
connection.rollback();
}
} finally {
connection.setAutoCommit(true);
JDBCUtils.closeConnection(connection);
}
}
DAO
DAO理论
在面向对象设计过程中,有一些"套路”用于解决特定问题称为模式。
DAO 模式
提供了访问关系型数据库系统所需操作的接口,将数据访问和业务逻辑分离对上层提供面向对象的数据访问接口。
从以上 DAO 模式使用可以看出,DAO 模式的优势就在于它实现了两次隔离。
- 隔离了数据访问代码和业务逻辑代码。业务逻辑代码直接调用DAO方法即可,完全感觉不到数据库表的存在。分工明确,数据访问层代码变化不影响业务逻辑代码,这符合单一职能原则,降低了藕合性,提高了可复用性。
- 隔离了不同数据库实现。采用面向接口编程,如果底层数据库变化,如由 MySQL 变成 Oracle 只要增加 DAO 接口的新实现类即可,原有 MySQL 实现不用修改。这符合 "开-闭" 原则。该原则降低了代码的藕合性,提高了代码扩展性和系统的可移植性。
DAO实现
一个典型的DAO 模式主要由以下几部分组成。
- DAO接口: 把对数据库的所有操作定义成抽象方法,可以提供多种实现。
- DAO 实现类: 针对不同数据库给出DAO接口定义方法的具体实现。
- 实体类:用于存放与传输对象数据。
- 数据库连接和关闭工具类: 避免了数据库连接和关闭代码的重复使用,方便修改。
DAO测试
DAO优化
[涉及泛型, 有点难, 暂时跳过]
数据库连接池
数据库连接池必要性
-
在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:
- 在主程序(如servlet、beans)中建立数据库连接
- 进行sql操作
- 断开数据库连接
-
这种模式开发,存在的问题:
- 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。**数据库的连接资源并没有得到很好的重复利用。**若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
- **对于每一次数据库连接,使用完后都得断开。**否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(回忆:何为Java的内存泄漏?)
- 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
什么是数据库连接池
-
为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
-
数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
-
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
-
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
-
工作原理
-
数据库连接池技术的优点
1. 资源重用
由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
2. 更快的系统反应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间
3. 新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源
4. 统一的连接管理,避免数据库连接泄漏
在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露
-
主流数据库连接池
- c3p0
- DBCP
- Druid
Druid
德鲁伊连接池(阿里的), 由于连接池功能很多, 涉及的知识也比较多, 这里只提一下如何实现连接
Maven引入:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
<scope>compile</scope>
</dependency>
public class DruidUtils {
private static DataSource source;
// 初始化数据库连接池
static {
Properties props = new Properties();
InputStream is = null;
try {
is = DruidUtils.class
.getClassLoader().getResourceAsStream("druid.properties");
props.load(is);
source = DruidDataSourceFactory.createDataSource(props);
System.out.println("source已经赋值");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static Connection getDruidConnection() {
try {
return source.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
JDBC框架
DBUtils
是Apache提供的JDBC操作的类, 主要借助QueryRunner执行
QueryRunner runner = new QueryRunner();
这个对象有丰富的方法, 便于我们使用, 一般我们需要自己提供一个Connection
连接
如果涉及查询, 可能还需要涉及Handle
类, 如BeanHandle
等
评论区