一、事务

1. 事务介绍

事务可以包含多个操作步骤 , 如果有一个步骤失败,那么这一组都以失败告终。

事务是指包含多个微小逻辑单元的一组操作, 只要其中有一个逻辑失败了,那么这一组操作就全部以失败告终,不存在一半成功,一半不成功的状况。

事务在平常的CRUD当中也许不太常用, 但是如果我们有一种需求,要求,一组操作中,必须全部成功执行,才算完成任务,只要有一个出错了,那么所有的任务都将回到最初的状况,恢复原样。那么这就可以使用事务了。如: 银行的转账例子 , 又如一次性往两张表添加记录,需要确保这两张表都能全部成功添加,不允许一张表成功,一张表失败这种情况出现。

2. 事务入门

1. 命令行演示

  • 方式一

-- 开启事务
start transaction; -- 执行操作
update student set age = 28 where id = 6; -- 提交事务,只有提交事务,数据才会真的保存到底层设备上。
commit; -- 如果不想提交,想回到最初的状态,那么可以回滚事务.
rollback;
  • 方式二

mysql 的事务设置是自动提交,我们可以关闭掉自动提交开关,然后手动提交事务。

-- 显示有关提交的变量信息
show variables like '%commit%'; -- 关闭自动提交
set autocommit = off ; 或者写成 set autocommit = 0 ; -- 执行操作,可以不用开启事务
update student set age = 28 where id = 6; -- 必须提交,才能看到结果
commit;

2. 代码演示

 @Test
public void testTransaction(){
Connection conn = null;
try {
ComboPooledDataSource dataSource = new ComboPooledDataSource(); conn = dataSource.getConnection(); //开启事务 关闭自动提交,
conn.setAutoCommit(false); //3. 执行语句
String sql = "insert into student values(null ,?,?)";
PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1,"lisi2");
ps.setInt(2,19); ps.executeUpdate(); //提交事务
conn.commit(); //释放资源..回收连接对象
ps.close();
conn.close(); } catch (Exception e) {
e.printStackTrace(); //如果出现了异常,那么回滚事务
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} }

3. 事务特性 ACID

  • 原子性

原子性(Atomicity) : 事务中的逻辑要全部执行,不可分割。(原子是物理中最小单位)

  • 一致性

一致性(Consistency):事务执行的结果必须是使数据库数据从一个一致性状态变到另外一种一致性状态

  • 隔离性(isolation)

一个事务的执行过程中不能影响到其他事务的执行,即一个事务内部的操作及使用的数据对其他事务是隔离的,并发执行各个事务之间无不干扰。

  • 持久性()

即一个事务一旦提交,它对数据库数据的改变是永久性的。之后的其它操作不应该对其执行结果有任何影响。

4. 事务隔离级别

主要是用来解决事务并发执行,引发的问题。

如果两个事务同时,或者交错执行,那么他们的执行结果可能会受对方影响,这会导致数据的前后显示不一致。所以为了保证并发操作数据的正确性及一致性,SQL规范于1992年提出了数据库事务隔离级别

事务的并发主要有两个方面的问题 : 读的问题 | 写的问题 , 相对于写的问题,读的问题出现的几率更高些。

1. 安全隐患

如果事务没有任何隔离设置,那么在并发情况会出现以下问题。

a. 读的问题
  • 脏读

脏读: 指 一个事务 读到了另一个事务还未提交的数据 :读已提交

  • 不可重复读

一个事务读到了另一个事务提交的更新的数据, 导致多次查询结果不一致。(针对update)

  • 虚读|幻读

一个事务读到了另一个事务已提交的插入的数据,导致多次查询结果不一致。(针对 insert)

b. 写的问题

写的问题其实就只有一个,就是丢失更新

丢失更新:指一个事务去修改数据库, 另一个事务也修改数据库,最后的那个事务,不管是提交还是回滚都会造成前面一个事务的数据更新丢失

  • 解决办法:

    1. 悲观锁

    还没干活就想着会丢失更新。 查询数据的后面跟上关键字 for update

    1. 乐观锁

    程序员自己控制,在表里面增加一个字段version (版本的意思), 默认是0 。 只要有一次操作,这个版本就递增。下次来操作,先对版本号,如果对不上,表示是旧的数据,得先查询。

2. 隔离级别

a. 读未提交
  1. 打开两个dos终端, 设置A窗口的隔离级别为 读未提交

  1. 两个窗口都分别开启事务

读未提交: 一个事务可以读取到另一个事务没有提交的数据

待解决: 3个(脏读、不可重复读、虚读)

解决:0个

b. 读已提交
  1. 设置A窗口的隔离级别为 读已提交

  1. A B 两个窗口都开启事务, 在B窗口执行更新操作。

  1. 在A窗口执行的查询结果不一致。 一次是在B窗口提交事务之前,一次是在B窗口提交事务之后。

读已提交:一个事务能读取到另一个事务已经提交的数据,但是未提交的数据读取不到了。

解决: 脏读

待解决: 不可重复 , 虚读

c. 可重复读

Repeatable Read set session transaction isolation level repeatable read;

解决: 脏读 、不可重复读

待解决: 虚读(但是其实mysql已经在底层把这个问题给避免了。)

d. 序列化 | 串行化

serializable set session transaction isolation level serializable;

  • 按功能大小 排序 : 序列化 > 可重复读 > 读已提交 > 读未提交
  • 按效率来排序: 序列化 < 可重复读 < 读已提交 < 读未提交

MySql: 默认隔离级别: 可重复读

Oracle: 读已提交

5. 事务管理

该小节讲述的是在三层结构里,如何优雅的声明事务和管理事务。

1. 三层结构介绍

三层结构其实是指把项目代码按照不同的逻辑、功能划分出来的层级,包含 控制层、业务逻辑层 、 数据访问层 ,对应着我们平常见到的 controller | service | dao 。 每个层级有各自的功能作用,

controller : 负责接收处理页面请求 和 响应

service : 负责处理业务逻辑(如: 数据封装、逻辑判断等)

dao: 负责和数据库打交道(增删改查)

  • 此处以用户注册来演示三层代码结构

    • controller
    public class UserController {
    private static final String TAG = "UserController"; @PostMapping("/users")
    public String register(User user){
    try {
    UserService userService = new UserServiceImpl();
    userService.register(user);
    } catch (SQLException e) {
    e.printStackTrace();
    }
    return "注册成功";
    }
    }
    • service
    public interface UserService {
    void register(User user) throws SQLException;
    } ------------------下面是具体的实现----------------------
    public class UserServiceImpl implements UserService {
    @Override
    public void register(User user) throws SQLException {
    UserDao userDao = new UserDaoImpl(); //可以在这里对注册的密码进行加密, 逻辑上的校验
    userDao.save(user);
    }
    }
    • dao
    public interface UserDao {
    void save(User user) throws SQLException;
    } --------------------下面是具体的实现----------------------------
    public class UserDaoImpl implements UserDao {
    private static final String TAG = "UserDaoImpl"; @Override
    public void save(User user) throws SQLException { QueryRunner runner = new QueryRunner(C3P0Util.getDataSource());
    String sql = "insert into user values(null , ? ,? ,?,?)";
    runner.update(sql ,
    user.getUsername() ,
    user.getPassword(),
    user.getEmail(),
    user.getPhone());
    }
    }

2. 三层结构中的事务管理

上面已经给大家演示过了未来写代码的一个雏形, 那么在这种三层体系中,我们该如何管理事务呢? 要想解答这个疑问,首先得想想,有关事务的代码应该在哪一层编写?

  • 事务应该位于哪一个层级?

事务操作应该在service层中实现, controller是用于接收和响应请求的,至于这个请求中间是怎么运作的,它其实是不关心的。 那么为什么不能是dao层呢? 因为dao层的每一个动作,仅仅是表示和数据库的一次交互而已,如果我们一个请求需要完成两个或者两个以上的数据库操作,那么在dao层里面事务就无法处理了。反之,service层主要适用于表示业务,那么这个业务可以调用调用一次dao层的方法,也可以调用多次方法,它们都可以看成是一个整体,不可分割。那么在service层囊括起来正好。 这有一个必须要注意的点: 要求service层和dao层使用的连接对象是同一个。

a. 传递Connection

上面已经说明了,需要在service层开启事务,那么如何确保dao层使用的连接对象和开启事务的连接对象是同一个,可以通过参数传递connection 对象

  • service
public class UserServiceImpl implement UserService{

    /**
* 转账
*/
public void transfer() { Connection conn = null;
try {
//1. 获取连接对象
conn = C3P0Util.getConn(); //2. 开启事务,其实就是关闭自动提交
conn.setAutoCommit(false); //3. 调用dao层方法
UserDao userDao = new UserDaoImpl(); userDao.outMoney(conn,"zhangsan",100);
userDao.inMoney(conn,"lisi",100); //4. 提交事务
conn.commit();
} catch (SQLException e) {
e.printStackTrace(); //5. 如果有异常,那么回滚事务
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} }
}
  • dao

public class UserDaoImpl implement UserDao{
//转入
@Override
public void inMoney(Connection conn , String name, int money) throws SQLException {
QueryRunner runner = new QueryRunner();
String sql = "update user set money = money + ? where name = ?";
runner.update(conn , sql , money , name); } //转出
@Override
public void outMoney(Connection conn , String name, int money) throws SQLException {
QueryRunner runner = new QueryRunner();
String sql = "update user set money = money - ? where name = ?";
runner.update(conn , sql , money , name);
}
}
b. 使用ThreadLocal

前面使用参数形式来传递Conenction,也不是不行。但是如果想让代码写得更加优雅,那么还是得使用ThreadLocal 。 ThreadLocal 直译过来的意思是: 线程本地。她可以用来存储数据,供当前的线程调用,底层其实使用Map集合来存数据。map的Key 是当前现成对象, value 值就是我们存储的数据了。ThreadLocal 不能用来做线程间通讯或者共享内容 ,它所存的值只针对当前线程有效。

上面的例子,从service开始,直到两个dao方法的执行完毕,都是在一个线程内工作,那么我们可以在service层就把connection对象存储到ThradLocal里面,在dao层取出来使用即可。

一句话概括这个ThreadLocal的作用: 就是可以往里面存东西,然后可以在整个线程里面取出来。

  • C3P0Util

private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>(); public static Connection getConnTL(){
try { Connection conn = threadLocal.get();
if(conn == null){
conn = dataSource.getConnection();
threadLocal.set(conn);
}
//2. 获取连接
return conn;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
  • service
/**
* 转账
*/
public void transfer() { Connection conn = null;
try {
//1. 获取连接对象 , 当第一次调用该方法的时候,Connction已经存储到ThreadLocal中去了。
conn = C3P0Util.getConnTL(); //2. 开启事务,其实就是关闭自动提交
conn.setAutoCommit(false); //3. 调用dao层方法
UserDao userDao = new UserDaoImpl(); userDao.outMoney("zhangsan",100);
userDao.inMoney("lisi",100); //4. 提交事务
conn.commit();
} catch (SQLException e) {
e.printStackTrace(); //5. 如果有异常,那么回滚事务
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} }
  • dao
  //转入
@Override
public void inMoney( String name, int money) throws SQLException {
QueryRunner runner = new QueryRunner();
String sql = "update user set money = money + ? where name = ?";
runner.update(C3P0Util.getConnTL() , sql , money , name);
} //转出
@Override
public void outMoney( String name, int money) throws SQLException {
QueryRunner runner = new QueryRunner();
String sql = "update user set money = money - ? where name = ?";
runner.update(C3P0Util.getConnTL() , sql , money , name);
}

二、H2数据库

1. H2介绍

h2是一个开源的纯java编写的轻量级数据库,是一个用Java开发的嵌入式数据库,只有一个jar文件,可以直接嵌入到应用项目中。

h2最大的用途在于可以同应用程序打包在一起发布,这样可以非常方便地存储少量结构化数据,它的另一个用途是用于单元测试。

启动速度快,而且可以关闭持久化功能,每一个用例执行完随即还原到初始状态。h2的第三个用处是作为缓存,作为NoSQL的一个补充。

  • 产品优势

    • 纯Java编写,不受平台的限制;
    • 只有一个jar文件,适合作为嵌入式数据库使用;
    • h2提供了一个十分方便的web控制台用于操作和管理数据库内容;
    • 功能完整,支持标准SQL和JDBC。麻雀虽小五脏俱全;
    • 支持内嵌模式、服务器模式和集群。

2. 下载 安装

  • 地址

http://www.h2database.com/html/download.html

  • 目录介绍
 h2
  |---bin
  | |---h2-1.1.116.jar   //H2数据库的jar包(驱动也在里面)
  | |---h2.bat    //Windows控制台启动脚本
  | |---h2.sh //Linux控制台启动脚本
  | |---h2w.bat //Windows控制台启动脚本(不带黑屏窗口)
  |---docs //H2数据库的帮助文档(内有H2数据库的使用手册)
  |---service //通过wrapper包装成服务。
  |---src //H2数据库的源代码
  |---build.bat //windows构建脚本
  |---build.sh //linux构建脚本
  • 启动连接

3. H2运行模式

  1. 内嵌模式

内嵌模式下,应用和数据库同在一个JVM中,通过JDBC进行连接。 可持久化,但同时只能一个客户端连接。内嵌模式性能会比较好

  1. 服务器模式

使用服务器模式和内嵌模式一样,只不过它可以跑在另一个进程里。

  1. 混合模式

第一个应用以内嵌模式启动它,对于后面的应用来说它是服务器模式跑着的

3. java 操作

  • 嵌入式连接
 Connection conn = DriverManager.getConnection("jdbc:h2:D:/aa/test", "sa", "");
  • 远程连接
 Connection conn = DriverManager.
getConnection("jdbc:h2:tcp://localhost/D:/aa/test", "sa", "");
  • 内存数据库
   Connection conn = DriverManager.
getConnection("jdbc:h2:tcp://localhost/mem:test2", "sa", "");

https://www.jianshu.com/p/2953c64761aa MySql 避免幻读

springboot09 事务 H2数据库的更多相关文章

  1. H2数据库集群

    H2数据库集群 1. H2数据库简单介绍 1.1 H2数据库优势 经常使用的开源数据库:H2,Derby,HSQLDB.MySQL,PostgreSQL. 当中H2,HSQLDB相似,十分适合作为嵌入 ...

  2. H2数据库攻略

    H2是一个开源的嵌入式数据库引擎,采用java语言编写,不受平台的限制,同时H2提供了一个十分方便的web控制台用于操作和管理数据库内容.H2还提供兼容模式,可以兼容一些主流的数据库,因此采用H2作为 ...

  3. ADO.NET中使用事务进行数据库读写的办法

    使用事务一般是进行数据写入,数据读取一般是不需要这货的 第一种办法: 使用存储过程: 顾名思义,在存储过程中定义好变量,定义好事务开始,结束,错误回滚然后在ADO.NET中正常调用存储过程的方法就行 ...

  4. 【树莓派】h2数据库操作相关

    之前在树莓派上面操作时候,遇到一些业务方面的bug,和团队中的同事经过多次尝试,但就是难以重现用户现场的问题. 但是问题却实实在在地发生,虽然并不是必然可重现的bug,但是也比较闹心: 发生了问题,也 ...

  5. JDBC(二)之JDBC处理CLOB和BLOB及事务与数据库元数据获取

    前面大概介绍了JDBC连接数据库的过程,以及怎么操作数据库,今天给大家分享JDBC怎么处理CLOB和BLOB存储图片的事情,以及JDBC怎么去处理事务.怎么在插入数据的时候生成主键返回值 一.JDBC ...

  6. 使用H2数据库进行单元测试

    背景 H2 数据库是一个开源的嵌入型内存数据库,采用纯Java语言实现: 程序非常小巧轻便,整个完整的Jar包也只有1.5M左右,很容易集成到项目中. 官网地址 http://www.h2databa ...

  7. mysql的事务和数据库锁的关系

    数据库加事务并不是数据就安全来了,事务和锁要分析清楚和配合使用 问题背景处于对高并发的秒杀环节的理解整理如下: 秒杀的时候高并发主要注意1.在秒杀的情况下,肯定不能如此高频率的去读写数据库,会严重造成 ...

  8. java~springboot~h2数据库在单元测试中的使用

    单元测试有几点要说的 事实上springboot框架是一个tdd框架,你在进行建立项目时它会同时建立一个单元测试项目,而我们的代码用例可以在这个项目里完成,对于单元测试大叔有以下几点需要说明一下: 单 ...

  9. Confluence 6 嵌入的 H2 数据库

    为了让你的 Confluence 在安装成功后就可以使用而不需要使用任何外部的数据库,Confluence 使用一个嵌入的 H2 数据库. 当你选择对 Confluence 进行评估和测试的时候,H2 ...

随机推荐

  1. No such file or directory 8356:error:02001003:system library:fopen:No such process:crypto\bio\bss_file.c:7 4:fopen

    使用OpenSSL生成证书,构建根证书前,需要构建随机数文件(.rand),命令如下: openssl rand - 报错如下: OpenSSL> rand - Can't open priva ...

  2. AngularJS THML DOM

    AngularJS为HTML Dom元素属性提供了绑定应用数据的指令. data-ng-disabled指令直接提供了绑定应用程序的数据到HTML元素的disabled属性. <!DOCTYPE ...

  3. Sass 语法格式及编译

    一.sass语法格式 这里说的 Sass 语法是 Sass 的最初语法格式,他是通过 tab 键控制缩进的一种语法规则,而且这种缩进要求非常严格.另外其不带有任何的分号和大括号.常常把这种格式称为 S ...

  4. util.Date与sql.Date转换

    一. 时间类型 1.  sql包下, Date:只有年月日. Time:只有时分秒. Timestamp:表示时间戳,有年月日时分秒,以及毫秒. 2.  util包下, Date是sql包下三种时间类 ...

  5. 日常运维管理技巧一(查看负载 W)

    日常运维管理技巧一(查看负载 W) 今天针对Linux系统管理做一个专题的记录,以后会用的几率也是很大的,只要掌握必备的基础知识,做初级系统管理员是不成问题的. 作为一个运维工程师.系统管理员,如果对 ...

  6. 循环语句:LOOP,WHILE和数字式循环

    一 简单循环 1 语法: LOOP      要执行的语句;      EXIT WHEN <条件语句> --条件满足,退出循环语句  END LOOP; 2 例子: DECLARE    ...

  7. java后台poi根据模板导出excel

    public class ExcelUtils { private static final String INSPECTIONRECORD_SURFACE_TEMPLET_PATH = " ...

  8. 交换机基础配置之单交换机划分vlan

    我们以以上拓扑图为例 pc0的IP地址为:192.168.1.1 pc1的ip地址为:192.168.1.2 两台主机在同一网段,相互ping是能ping通的 我们的目的是在单交换机上划分两个vlan ...

  9. Struts2之基于配置的字段校验

    上一篇struts2之输入校验介绍了手动完成输入校验,也即依靠重写validate方法和validateXxx方法,指定请求某个方法时对传入的参数进行校验. 本篇介绍基于配置的字段校验.下面是登录的常 ...

  10. select值改变

    改变select的值,然后执行一个方法.可以用chang: $("#select").change(function(){ //要执行的内容 });