前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >第29次文章:事务机制

第29次文章:事务机制

作者头像
鹏-程-万-里
发布2019-09-27 14:55:10
3110
发布2019-09-27 14:55:10
举报

开始进入事务咯,一点点学习数据库内容啦~


一、事务

1、事务的基本概念

一组要么同时执行成功,要么同时执行失败的SQL语句。是数据库操作的一个执行单元!

事务开始于:

-连接到数据库上,并执行一条DML语句(insert、update或delete)。

-前一个事务结束后,又输入了另外一条DML语句。

事务结束于:

-执行commit或rollback语句

-执行一条DDL语句,例如create table、grant语句;在这种情况下,会自动执行commit语句。

-断开与数据库的连接。

-执行了一条DML语句,该语句却失败了;在这种情况中,会为这个无效的DML语句执行rollback语句。

2、事务的四大特点(ACID)

-atomicity(原子性):表示一个事务内部的所有操作是一个整体,要么全部成功,要么全部失败;

-consistency(一致性):表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前的状态;

-isolation(隔离性):事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事物修改它之后的状态,事务不会查看中间状态的数据。

-durability(持久性):持久性事务完成之后,它对于系统的影响是永久性的。

隔离性级别,从低到高:

读取未提交(Read uncommitted)——>读取已提交(read committed)——>可重复读(repeatable read)——>序列化(serializable)

在现实的使用中,一般使用的是读取已提交,在整个顺序中,越往后效率越低。

3、实际实现

事务的这种特点在现实生活中是十分容易被理解的,比如我们去银行取钱的过程中,卡上的余额和银行给出的金额,属于整个事务,只有每一步都执行成功之后,整个事务才会一起改变原有的状态。这也防止了,客户没有拿到钱,卡上余额却减少的情况发生。

下面我们结合简单实例来分析一下事务的运行机制:

代码语言:javascript
复制
package com.peng.jdbc;

import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import com.mysql.jdbc.Connection;
/**
 * 测试事务
 */
public class Demo06 {
  public static void main(String[] args) {
    Connection conn = null;
    PreparedStatement ps1 = null;
    PreparedStatement ps2 = null;
    try {
      //加载驱动类
      Class.forName("com.mysql.jdbc.Driver");
      conn = (Connection) DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc", "root", "123456");
      conn.setAutoCommit(false);//JDBC中默认是true,自动提交事务
      
      //此时ps1和ps2为同一事务,要么同时成功,要么同时失败
      String sql = "insert into t_user (username,pwd) values (?,?)";
      ps1 = conn.prepareStatement(sql);
      ps1.setObject(1, "peng1");
      ps1.setObject(2, "123456");
      ps1.execute();
      System.out.println("插入一条记录,peng1");
      
      try {
        Thread.sleep(6000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }

      ps2 = conn.prepareStatement(sql);
      ps2.setObject(1, "peng2");
      ps2.setObject(2, "123456");
      ps2.execute();
      System.out.println("插入一条记录,peng2");
      
      conn.commit();//对此事务进行手动提交
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
      try {
        conn.rollback();//如果报有异常,则回滚到原来的位置
      } catch (SQLException e1) {
        e1.printStackTrace();
      }
    } catch (SQLException e) {
      e.printStackTrace();
    }finally {
            try {
        if(ps1 != null) {
          ps1.close();
        }
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        if(ps2 != null) {
          ps2.close();
        }
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        if(conn != null) {
          conn.close();
        }
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
  }
}

tips:

(1)在这段代码中,我们将JDBC的事务提交方式由自动提交事务改为手动提交(false)。

(2)我们在整段代码中创建了两个PreparedStatement对象,分别是ps1和ps2,在两个对象的向数据库中插入记录之后,我们进行手动提交事务。所以,此时ps1和ps2合在一起构成了一个完整的事务,只有两个对象同时成功,才能够完成整个事务。为了模拟一个事务机制,我们在两个对象ps1和对象ps2之间线程休眠6秒。

我们先清空数据库的t_user表格,成功的插入两条语句之后,我们依次查看控制台和数据库表格中的信息。

fig1:控制台信息

fig2:数据库信息

从控制台和数据库表格的情况来看,两者都是已经成功的插入了相应的语句。

下面我们来检测一下加入第二条插入命令失败之后的结果(我们还是先清空一下数据表格),在上段代码中注释掉命令“ps2.setObject(2, "123456");”,不向sql语句的第二个占位符处输送参数。继续查看控制台和数据库的结果:

fig3:插入失败控制台信息

fig4:事务失败后数据库中的信息

在这次的结果中,我们可以从控制台的信息中知道只有对象ps2的语句运行失败。当我们查看数据库的表格的时候,发现数据库依旧为空,代表着两条记录都没有被插入进来。

tips:经过上面成功和失败的案列对比,可以很好的体现事务的特点:我们将ps1和ps2当做一个事务来进行处理,只有当两条命令ps1和ps2都成功的执行之后,数据库中才会产生相应的结果,否则会回滚(rollback)到整个事务开始之前的状态,回滚操作放在异常出现的地方。

二、时间类型

在数据库中,我们经常会涉及到一些时间相关的操作,比如登录时间,注册时间等等,这些时间也有不一样的类型,分别代表着不同的精度。

1、java.util.Date

数据库中的时间类型都是继承自java.util.Date中的类型。

-子类:java.sql.Date 表示年月日

-子类:java.sql.Time 表示时分秒

-子类:java.sql.TimeStamp 表示年月日时分秒

2、日期比较处理

通过上面不同时间类型的简单介绍,我们可以根据它们的差别,进行简单的下面两个层面的应用。在实际中,一般使用的都是Date和TimeStamp进行使用。

(1)插入随机日期

我们向表格中插入1000条记录,测试一下上面我们介绍到几种时间类型。

代码语言:javascript
复制
package com.peng.jdbc;

import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Random;

import com.mysql.jdbc.Connection;
/**
 * 测试时间类型(Date,Time,TimeStamp)
 */
public class Demo07 {
  public static void main(String[] args) {
    Connection conn = null;
    PreparedStatement ps = null;
    try {
      //加载驱动类
      Class.forName("com.mysql.jdbc.Driver");
      conn = (Connection) DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc", "root", "123456");
      conn.setAutoCommit(false);
      String sql = "insert into t_user (username,pwd,regTime,lastLoginTime) values (?,?,?,?)";
      for (int i = 0;i<1000;i++) {
        ps = conn.prepareStatement(sql);
        ps.setObject(1, "peng"+i);
        ps.setObject(2, "123456");
        long rand = 1000000000 + new Random().nextInt(1000000000);
        java.sql.Date date1 = new java.sql.Date(System.currentTimeMillis() - rand);
        java.sql.Timestamp date2 = new java.sql.Timestamp(System.currentTimeMillis() - rand);
        ps.setDate(3, date1);
        ps.setTimestamp(4, date2);
        ps.execute();
      }
      System.out.println("插入一条记录,peng");
      conn.commit();//对此事务进行手动提交
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
      try {
        conn.rollback();//如果报有异常,则回滚到原来的位置
      } catch (SQLException e1) {
        e1.printStackTrace();
      }
    } catch (SQLException e) {
      e.printStackTrace();
    }finally {
      try {
        if(ps != null) {
          ps.close();
        }
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        if(conn != null) {
          conn.close();
        }
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
  }
}

随机查看一部分结果:

tips:在我们设计数据库表格的时候,我们将“regTime”的类型设计为Date类型,将“lastLoginTime”设计为Timestamp,在表格中的显示上也可以看出两者之间的不同。

(2)取出指定日期范围的记录

根据上面已经插入的表格,我们对时间一定范围内的记录进行筛选

代码语言:javascript
复制
/**
 * 测试时间类型(Date,Time,TimeStamp),取出指定时间段的数据
 */
public class Demo08 {
  
  /**
   * 将时间字符串转换为一个long型数字
   * @param dateStr
   * @return
   */
  public static long str2date (String dateStr) {
    DateFormat formate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    try {
      return formate.parse(dateStr).getTime();
    } catch (ParseException e) {
      e.printStackTrace();
      return 0;
    }
  }
  
  public static void main(String[] args) {
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
      //加载驱动类
      Class.forName("com.mysql.jdbc.Driver");
      conn = (Connection) DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc", "root", "123456");  
      //根据regTime的Date类型进行筛选
//      String sql = "select * from t_user where regTime>? and regTime<?";//?代表占位符,在此处是占位等待后续的参数传进来
//      ps = conn.prepareStatement(sql);
//      ps.setObject(1, new java.sql.Date(str2date("2019-07-08 12:00:00")));
//      ps.setObject(2, new java.sql.Date(str2date("2019-07-12 00:00:00")));
//      rs = ps.executeQuery();
//      while(rs.next()) {
//        System.out.println(rs.getInt("id")+"----"+rs.getString("username")+"----"+rs.getDate("regTime"));
//      }
            
      //根据lastLogintime的Timestamp进行筛选
      String sql = "select * from t_user where lastLoginTime>? and lastLoginTime<? order by lastLoginTime";
      ps = conn.prepareStatement(sql);
      ps.setObject(1, new Timestamp(str2date("2019-07-12 12:00:00")));
      ps.setObject(2, new Timestamp(str2date("2019-07-12 13:00:00")));
      rs = ps.executeQuery();
      while(rs.next()) {
        System.out.println(rs.getInt("id")+"----"+rs.getString("username")+"----"+rs.getTimestamp("lastLoginTime"));
      }
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    } catch (SQLException e) {
      e.printStackTrace();
    }finally {
      try {
        if(rs != null) {
          rs.close();
        }
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        if(ps != null) {
          ps.close();
        }
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        if(conn != null) {
          conn.close();
        }
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
  }
}

我们简单看一下结果图:

tips:

(1)在对时间进行筛选的时候,我们可以根据regTime的Date类型进行筛选(注释掉的那一段),也可以根据lastLogintime的Timestamp进行筛选。两种方式在上面的代码中都已经被呈现出来。

(2)在new Date对象和Timestamp对象的时候,由于都可以向构造器中传递long类型的毫秒参数,所以我们自己定义一个方法str2date(),将我们传递的时间字符串转化为长整形数据毫秒。

(3)在使用SimpleDateFormat定义时间字符串类型的时候,我们需要注意一下每个单位的大小写,比如:时分秒中的小时“H”代表着24小时制,"h"代表12小时制。所以在使用相应的字符串格式的时候,我们还需要注意每种格式的使用方法,否则在对时间进行筛选的时候,很容易出错。


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java小白成长之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开始进入事务咯,一点点学习数据库内容啦~
  • 一、事务
    • 1、事务的基本概念
      • 2、事务的四大特点(ACID)
        • 3、实际实现
        • 二、时间类型
          • 1、java.util.Date
            • 2、日期比较处理
            相关产品与服务
            数据库
            云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档