1、数据库事务
1.1 问题引入
典型案例,转账问题。AA给BB转账100元,以下两件事情必须要么都发生,要么都不发生
① update user_table set balance= balance-100 where user='AA';
② update user_table set balance=balance +100 where user='BB';
如果不同时发生,可能导致如下问题:
如果出现异常,后面代码就没法执行,最终导致总额就不对
public class TransactionTest{
@Test
public void testUpdate(){
String sql1="update user_table set balance= balance-100 where user=?";
update(sql1,"AA");
//模拟网络异常
System.out.println(10/0);//这样就会抛异常,后面代码就执行不下去了
String sql2="update user_table set balance=balance +100 where user=?"
update(sql2,"BB");
System.out.println("转账成功");
}
//没考虑事务的,通用增删改操作
public int update(String sql,Object...args){
Connection conn=null;
PreparedStatement ps = null;
try{
conn=JDBCUtils.getConnection();//获取数据库连接
ps=conn.prepareStatement(sql);//预编译sql语句
//填充占位符
for(int i=0;i<args.length;i++){
ps.setObject(i+1,args[i]);//小心参数声明错误
}
return ps.executeUpdate();//返回执行了多少条
} catch(Exception e){
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,ps);
}
return 0;
}
}
1.2 数据库事务介绍
- 事务:
一组逻辑操作单元(即一个或多个DML操作(增删改操作)),使数据从一种状态变换到另一种状态
- 事务的处理原则:
保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。·当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态(只能回滚到上一个commit)。
特别注意:数据一旦提交,就不可回滚
说明:数据只能提交到上次commit的位置,因此需要避免以下3种情况(只有第二种可以解决)
复习:会导致数据自动提交的操作
① DDL(数据库定义语言)操作一旦执行,就会自动提交。(DDL就是一个独立事务,没有阻止办法)
② DML默认情况下,一旦执行,就会自动提交,但是,我们可以通过set autocommit = false的方式取消DML操作的自动提交
③ 默认在关闭数据库连接时,数据会自动提交数据。因此如果多个操作,每个操作使用的是自己单独的连接,没法保证事务。也就是说,同一个事务的多个操作必须在同一个连接下。
2、JDBC事务处理
① 调用Connection对象的setAutoCommit(false),以取消自动提交事务"。
② 在所有的SQL语句都成功执行后,调用commit();方法提交事务,
③ 在出现异常时,调用rollback()方法回滚事务
注意:若此时Connection没有被关闭,还可能被重复使用,则需要恢复其自动提交状态.
setAutoCommit(true),尤其是在使用数据库连接池技术时,执行close()方法前,建议恢复自动提交状态。
要点:
① 取消数据库自动提交
② 手动提交数据
③ 回滚数据
exp:处理这张表格:依然是AA给BB转账100元
考虑了事务的增删改操作
public class TransactionTest{
@Test
public void testUpdateWithTx(){//加入了事务的转账操作
Connection conn = null;
try{
conn=JDBCUtils.getConnection();
//重点1:取消数据库自动提交
conn.setAutoCommit(false);
String sql1="update user_table set balance= balance-100 where user=?";
update(conn,sql1,"AA");
//模拟网络异常
System.out.println(10/0);//这样就会抛异常,后面代码就执行不下去了
String sql2="update user_table set balance=balance +100 where user=?";
update(conn,sql2,"BB");
System.out.println("转账成功");
//重点2:手动提交数据
conn.commit();
//执行以上语句时,一旦发生异常,就会跳到catch这里,因此需要在这里回滚数据
} catch(Exception e){
e.printStackTrace();
//重点3:回滚数据
try{
conn.rollback();
} catch(SQLException e1){
e1.printStackTrace();
}
} finally{
conn.setAutoCommit(true); //最后在关闭连接前,还是把mysql还原成默认的自动提交状态
JDBCUtils.closeResource(conn,null); //只关闭,本方法里面的接口。其他的资源调用方法内部已经关闭好了
}
}
//考虑了事务的增删改操作
public int update(Connection conn,String sql,Object...args){ //从外部传入Connection对象
PreparedStatement ps = null;
try{
ps=conn.prepareStatement(sql);//预编译sql语句
//填充占位符
for(int i=0;i<args.length;i++){
ps.setObject(i+1,args[i]);//小心参数声明错误
}
return ps.executeUpdate();//返回执行了多少条
} catch(Exception e){
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null,ps); //由于是外面传来的Connection,这里不能关闭资源
}
return 0;
}
}
3、事务的ACID属性
原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
3.1数据库并发问题
脏读、不可重复读、幻读
3.2 四种隔离级别
以上参考:https://blog.csdn.net/chengqingshihuishui/article/details/111636445
3.3.1在MySql中设置隔离级别
每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation,表示当前的事务隔离级别。
(1)查看当前的隔离级别:
SELECT @@tx_isolation;
(2)设置当前 mySQL 连接的隔离级别:
set transaction isolation level read committed;
(3)设置数据库系统的全局的隔离级别:
set global transaction isolation level read committed;
补充操作:
(1)创建mysql数据库用户:
create user tom identified by 'abc123';
(2)授予权限
#授予通过网络方式登录的tom用户,对所有库所有表的全部权限,密码设为abc123.
grant all privileges on *.* to tom@'%' identified by 'abc123';
#给tom用户使用本地命令行方式,授予atguigudb这个库下的所有表的插删改查的权限。
grant select,insert,delete,update on atguigudb.* to tom@localhost identified by 'abc123';
3.3.2 Java代码中设置隔离级别
Connection类的setTransactionIsolation();方法,即可设置隔离级别
3.3 代码演示
就是将testTransactionSelect()方法当作一个事务T1,testTransactionUpdate()方法当成另一个事务T2。来演示不同隔离级别解决的问题。
要查询的数据表:
public class TransactionTest{
@Test //这个事务查询
public void testTransactionSelect(){
Connection conn=JDBCUtils.getConnection();
//重点:设置隔离级别,完成这个设置,即可解决不同级别的隔离问题
conn.setTransactionIsolation(Connection.TANSACTION_READ_COMMITTED);
conn.setAutoCommit(false);//取消自动提交
String sql="select user,password,balance from user_table where user=?";
User user = getInstance(conn,User.class,sql,"CC");//获取User数据表
System.out.println(user);
}
@Test //这个事务修改
public void testTransactionUpdate(){
Connection conn = JDBCUtils.getConnection();
conn.setAutoCommit(false);//取消自动提交
String sql ="update user_table set balance=? where user=?";
update(conn,sql,5000,"CC");//update()是上一段代码的通用查询方法
Thread.sleep(15000);//让当前线程沉睡
System.out.println("修改结束");
}
//通用的查询操作,用于返回数据表中的一条记录
public <T> T getInstance(Connection conn,Class<T> clazz,String sql,Object...args){//从外部传入Conn
PreparedStatement ps = conn.prepareStatement(sql);
for(int i=0; i<args.length;i++){
ps.setObject(i+1,args[i]);
}
ResultSet rs = ps.executeQuery();//获取结果集
ResultSetMetaData rsmd = rs.getMetaData();//获取元数据
if(rs.next()){
T t = clazz.newInstance(); //通过反射造T类型的对象(造出某个数据表)
//处理结果集 一行数据中的 每一个列
for(int i=0;i<columnCount;i++){
//获取列值
Object columValue = rs.getObject(i+1);
//获取列名
String columnLabel = rsmd.getColumnLabel(i+1);
//给cust对象指定的columnName属性,赋值为columValue
Field field = clazz.class.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t,columValue);
}
}
return t;
JDBCtils.closeResource(null,ps,rs); //不要关闭外面传进来的Conn
}
}
public class User{ //根据ORM思想,创建User类代表该数据表
private String user;
private String password;
private int balance;
//提供构造器,空参的,带参的,toString,get,set方法
public User(){
super();
}
//等等
}
4、数据库连接池
4.1 JDBC数据库连接池的必要性
- 传统开发模式连接数据库的弊端
在使用开发基于数据库的web程序时,传统的模式基本按以下步骤:
① 在主程序(如servlet、beans)中建立数据库连接
② 进行sql操作
③ 断开数据库连接
- 这种模式开发,存在的问题:
① 普通的JDBC数据库连接(Connection对象)使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
② 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(Java内存泄漏指内存中存在不能被回收的对象)
③ 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃
4.2 数据库连接池技术
为解决以上传统开发中的数据库连接问题,可以采用数据库连接池技术。
① 数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
② 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
③ 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
- 工作原理:
- 数据库连接池技术的优点
① 资源重用
由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
② 更快的系统反应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,
从而减少了系统的响应时间
③ 新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源
④ 统一的连接管理,避免数据库连接泄漏
在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据
4.3 多种开源数据库连接池
JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:比如DBCP,C3P0,Proxool,BoneCP,Druid。
DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池,DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。
特别注意:
① 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
② 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池
exp: Druid(德鲁伊)数据库连接池
Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。
public class TestDruid {
public static void main(String[] args) throws Exception {
Properties pro = new Properties();
//读取类目录下的properties文件 getResourceAsStream()方法返回一个输入流
pro.load(TestDruid.class.getClassLoader().getResourceAsStream("druid.properties"));
DataSource ds = DruidDataSourceFactory.createDataSource(pro);//创建一个DataSource接口实例 Druid的实例
Connection conn = ds.getConnection();//获取数据库连接
System.out.println(conn);
}
}
关于getResourceAsStream(String path)
如果编译这个类生成的.class文件和properties文件在一个目录,path就不需要\。path用了\代表从项目根目录开始找文件
配置文件的写法:druid.properties
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
username=root
password=123456
driverClassName=com.mysql.jdbc.Driver
initialSize=10
maxActive=20
maxWait=1000
filters=wall
5、DAO及相关实现类
DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create增、Retrival查询、Update修改、Delete删除),而 不包含任何业务相关的信息。有时也称作:BaseDAO
作用:为了实现功能的模块化,更有利于代码的维护和升级。
层次结构:是尚硅谷JavaWeb阶段书城项目中DAO使用的体现
5.1 Apache-DBUtils简介
commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。
API介绍:
① 工具类:org.apache.commons.dbutils.DbUtils
提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的。主要方法查字典
http://commons.apache.org/proper/commons-dbutils/download_dbutils.cgi 官网下载的xxbin.jar包下,有doc文档
② org.apache.commons.dbutils.QueryRunner
提供数据库操作的一系列重载的update()和query()操作。该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。主要方法包含更新,插入,批处理,查询
③ org.apache.commons.dbutils.ResultSetHandler
此接口用于处理数据库查询操作得到的结果集。不同的结果集的情形,由不同的子类来实现