zoukankan      html  css  js  c++  java
  • java核心技术第五篇之事务和MVC模式

    第一部分:事务
    1.事务的简介:
    1.1 在一组操作中(比如增加操作,修改操作),只有增加和修改操作都成功之后,这两个操作才能真正的成功.
    ,如果这两个操作中,有一个失败了,这两个操作都失败了.

    1.2 应用的场景:转账的例子.
    (1) 有两个人:小奥和小温
    (2) 小温转账5000给小奥
    (3) 小温少5000,小奥多5000
    (4) 产生问题:小温给小奥转账5000,小温少5000,发生异常错误,小奥没有得到钱
    (5) 使用事务解决问题

    2.mysql中操作事务:
    2.0 两个概念
    (1) 提交事务:让表中数据真正生效
    (2) 回滚事务:回到操作之前的状态

    2.1 在mysql中事务默认是自动提交的

    2.2 设置mysql的事务提交方式不是自动提交,需要手动提交事务.
    (1)查询当前mysql的事务提交方式:show variables like '%commit%';
    (2)查询提交方式:set autocommit = off/0; 0---OFF 1---ON

    (3)mysql数据库操作事务语句
    = 打开事务:start transaction;
    = 提交事务:commit;
    = 回滚事务:rollback;

    3.jdbc操作事务:
    3.1 在jdbc操作中,事务也是自动提交的

    3.2 设置事务不是自动提交
    (1) 在Connection里面setAutoCommit(boolean autocommit),设置是否自动提交
    =参数值,默认是true,设置不自动提交设置值false;
    (2) 在connection里面commit(),提交事务
    (3) 在Connection里面rollback(),回滚事务

    3.3 演示转账的例子()

    4.事务的特性:
    4.1有四个特性
    (1)原子性 : 在一组操作中,要么都成功,有一个失败所有的都失败.
    (2)一致性 : 在操作之前和之后数据一致的
    (3)隔离性 : 多个事务之间的操作不会互相影响的
    = 开启第一个事务一
    = 开启第二个事务二
    == 如果在事务一,添加数据,不提交事务
    == 在事务二厘米,查询不到添加的数据的
    (4)持久性 : 提交事务之后,数据真正生效



    5.如果不考虑事务的隔离性,产生问题(三个读的问题)
    5.1脏读
    (1)有两个事务,其中一个事务读到另一个事务没有提交的数据
    insert into account values('小胖',30000);
    insert into account values('小苍',30000);

    (2)演示操作步骤:
    第一步:打开两个cmd窗口,分别连接数据库,切换到day14;
    第二步:在左边的窗口中设置事务的隔离级别 read uncommitted
    set session transaction isolation level read uncommitter;
    第三步:在两个窗口中分别开启事务
    第四步:在右边窗口,小胖给小苍转账10000;
    update account sel sal = sal -10000 where username = "小胖";
    update account sal sal = sal + 10000 where username = "小苍";

    (3)产生的问题: 如果小胖把事务回滚了,小苍查询不到数据,称为脏读.

    (4)解决脏读的方法: 设置事务额隔离级别
    (5)脏读是一个问题,不允许发生的

    5.2不可重复读
    (1)有两个事务,其中一个没有提交的事务读到另一事务提交的update的操作
    (2) 操作步骤:
    第一步:打开两个cmd窗口,分别连接数据库,切换到day14;
    第二步:在左边的窗口设置隔离级别 read committed
    set session set sal = sal-10000 where username = '小胖';
    第三步:在两个窗口中分别开启事务
    第四步:在右边窗口,小胖给小苍转账10000;
    update account set sal = sal -10000 where username = '小胖';
    update account set sal = sal + 10000 where username = '小苍';
    第五步: 在左边查询结果,发现数据变化,左边是在一个事务中,没有提交的事务,却读到另一事务中提交的数据.
    称为不可重复读
    (3)是一种现象,在一些情况下允许发生的
    (4)解决脏读的方法:设置事务的隔离级别解决
    (5)演示步骤:
    第一步:打开两个cmd窗口,分别连接数据库,切换到day14;
    第二步:在左边的窗口中设置事务的隔离级别 repeatable read
    set session transaction isolation level repeatable read;
    第三步:在两个cmd窗口中分别开启事务
    第四步:在右边窗口,小胖给小苍转账10000;
    update account set sal = sal-10000 where username = '小胖';
    update account set sal = sal + 10000 where username = "小苍";
    (3)产生问题:如果小胖把事务回滚了,小苍查询不到数据,称为脏读



    5.3虚读(幻读)
    (1)有两个事务,其中一个没有提交的事务读到另一事务提交的insert的操作

    5.4解决读的问题: 设置事务的隔离级别解决
    数据库提供四个隔离级别,防止三类读问题:
    serializable : 串行的.避免脏读,不可重复读和虚读发生
    repeatable read : 重复读.避免脏读,但是不可重复读和虚读有可能发生
    read committed : 已提交读.避免脏读,但是不可重复读和虚读有可能发生
    read uncommitted : 未提交读.脏读,不可重复读,虚读

    隔离级别优先级:read uncommitted << read commited << repeatble read << serializable

    (2)mysql数据库默认隔离级别: repeatble read

    (3)操作语句
    select @@tx isolation; 查询当前事务隔离级别
    set session transaction isolation level 设置事务隔离级别


    6.JDBC中设置事务的隔离级别
    6.1 Connection里面setTransactionIsolation(int lexel)方法设置
    = 方法的参数:使用Connection里面常量表示不同的隔离级别

    关于事务中掌握内容:
    1.mysql里面操作事务语句:
    (1) 开启事务:start transaction
    (2) 提交事务:commit
    (3) 回滚事务:rollback
    (4) 在mysql里面默认自动提交

    2.jdbc操作事务的三个方法

    3.事务的四个特性

    4.如果不考虑隔离性,产生三个读的问题
    (1)设置事务的隔离级别解决
    (2)mysql默认的隔离级别,repeatable read


    */

    /*

    01.事务_概述
    1)."事务"是"数据库"中的概念,它是指针对一个业务,在数据库中要执行多个操作。
    例如:银行转账
    张三给李四转账1000元;
    在数据库中至少要做两个操作:
    1).将张三的账户减少1000元;
    2).将李四的账户增加1000元;
    对于数据库软件,要有能力将多个SQL语句作为一个"整体",要么全部成功,要么全部失败。
    这个整体被执行的业务就叫:事务。
    2).我们今天讲到的事务处理的方式:
    1).在MySQL中怎样直接操作事务;
    2).通过JDBC怎样操作数据库中的事务;
    3).通过DBUtils怎样操作数据库中的事务;

    02.事务_MySQL中的事务处理
    1).自动事务:MySQL的默认事务处理方式
    将每条SQL作为一个独立的事务的进行处理,会被立即修改到数据库中。
    2).手动事务:
    1).关闭自动事务[不常用]:
    A).查看当前的事务处理方式:show variables like 'autocommit';
    结果:autocommit ON (自动提交--打开)
    B).关闭自动提交:
    set autocommit = off;
    C).发送SQL语句
    update users set ....
    ....
    update users set ....
    D).提交:
    commit;//将把之前发送的所有SQL语句全部更新
    或者
    回滚:
    rollback;//将把之前所有的SQL语句全部取消
    注意:此设置只对当前的连接用户有效
    2).在"自动事务"的情况下,临时开启一个事务【常用】:
    A).临时开启一个事务:
    start transaction;
    B).发送SQL语句
    update users set ....
    ....
    update users set ....
    C).提交:
    commit;
    或者
    回滚:
    rollback;
    注意:不论提交或者回滚,当前的事务立即结束。而且立即恢复到之前的提交模式。
    如果不提交或者回滚,就断开连接,后期数据库会将此次事务的所有操作全部回滚。

    03.事务_JDBC中的事务处理
    ......
    conn.setAutoCommit(false);//开启事务--设置Connection对象的"自动提交-false"

    try{
    int row1 = stmt.executeUpdate("update users set loginName = 'aa33' where uid = 1");

    int row2 = stmt.executeUpdate("update users set loginName = 'bb33' where uid = 2");

    conn.commit();//5.提交

    }catch(Exception e){
    conn.rollback();//6.回滚事务
    }finally{
    conn.setAutoCommit(true);//开启自动提交
    conn.close();//关闭连接
    }

    System.out.println("完毕!");

    源码:

    package cn.baidu.demo01_JDBC中的事务处理;

    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.Statement;

    public class Demo {
    public static void main(String[] args) throws Exception {
    //1.注册驱动
    Class.forName("com.mysql.jdbc.Driver");
    //2.获取连接对象
    Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/hei66_day21","root","123");
    //3.开启事务--设置Connection对象的"自动提交-false"
    conn.setAutoCommit(false);
    //4.发送SQL语句
    Statement stmt = conn.createStatement();
    try{
    int row1 = stmt.executeUpdate("update users set loginName = 'aa33' where uid = 1");

    int row2 = stmt.executeUpdate("update users set loginName = 'bb33' where uid = 2");
    //5.提交
    conn.commit();
    System.out.println("提交事务......");
    }catch(Exception e){
    //6.回滚事务
    conn.rollback();
    System.out.println("回滚事务......");
    }finally{
    //开启自动提交
    conn.setAutoCommit(true);
    //关闭连接
    conn.close();
    }

    System.out.println("完毕!");
    }
    }


    04.事务_DBUtils中的事务处理

    QueryRunner qr = new QueryRunner();//1.创建一个QueryRunner对象
    //2.获取一个Connection对象
    ComboPooledDataSource ds = new ComboPooledDataSource();
    Connection conn = ds.getConnection();
    //设置为手动提交
    conn.setAutoCommit(false);
    //3.发送SQL语句
    String sql1 = "update users set loginName = 'aa55' where uid = 1";
    String sql2 = "update users2 set loginName = 'bb55' where uid = 2";
    try{
    int row1 = qr.update(conn, sql1);//【注意--调用的update方法要接收一个Connection对象】
    int row2 = qr.update(conn, sql2);//【注意--调用的update方法要接收一个Connection对象】
    //提交
    conn.commit();
    System.out.println("提交事务......");
    }catch(Exception e){
    //回滚
    conn.rollback();
    System.out.println("回滚事务......");
    }finally{
    //设置为自动提交
    conn.setAutoCommit(true);
    //回收连接
    conn.close();
    }

    源码:

    package cn.baidu.demo02_DBUtils中的事务处理;

    import java.sql.Connection;
    import java.sql.Statement;

    import org.apache.commons.dbutils.QueryRunner;

    import com.mchange.v2.c3p0.ComboPooledDataSource;

    public class Demo {
    public static void main(String[] args) throws Exception {
    //1.创建一个QueryRunner对象
    QueryRunner qr = new QueryRunner();

    //2.获取一个Connection对象
    ComboPooledDataSource ds = new ComboPooledDataSource();
    Connection conn = ds.getConnection();
    //设置为手动提交
    conn.setAutoCommit(false);
    //3.发送SQL语句
    String sql1 = "update users set loginName = 'aa55' where uid = 1";
    String sql2 = "update users2 set loginName = 'bb55' where uid = 2";
    try{
    int row1 = qr.update(conn, sql1);
    int row2 = qr.update(conn, sql2);
    //提交
    conn.commit();
    System.out.println("提交事务......");
    }catch(Exception e){
    //回滚
    conn.rollback();
    System.out.println("回滚事务......");
    }finally{
    //设置为自动提交
    conn.setAutoCommit(true);
    //回收连接
    conn.close();
    }



    }
    }

    -----------------------------------------------------------------------------------
    05.MVC模式_不使用MVC模式实现登录注册案例

    package cn.baidu.demo03_不使用MVC模式实现登录注册案例;

    import java.sql.SQLException;
    import java.util.Scanner;

    import org.apache.commons.dbutils.QueryRunner;
    import org.apache.commons.dbutils.handlers.BeanHandler;

    public class Demo {
    public static void main(String[] args) throws SQLException {
    Scanner sc = new Scanner(System.in);
    while(true){
    System.out.println("1.登录 2.注册 3.退出:");
    int op = sc.nextInt();
    switch(op){
    case 1://登录
    toLogin();
    break;
    case 2://注册
    toRegist();
    break;
    case 3://退出
    System.out.println("谢谢使用!!");
    System.exit(0);
    default:
    System.out.println("错误的输入!");
    break;
    }
    }
    }
    //注册
    private static void toRegist() throws SQLException {
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入用户名:");
    String loginName = sc.next();
    System.out.println("请输入密码:");
    String loginPwd = sc.next();

    //1.验证用户名和密码的字符
    //略

    //2.数据库验证--用户名不能重复
    QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());
    String sql = "select * from users where loginName = ?";
    User userBean = qr.query(sql, new BeanHandler<User>(User.class),loginName);
    if(userBean != null){
    System.out.println("用户名:" + loginName + " 已存在!");
    return;
    }

    //3.将这条信息写入到数据库
    sql = "insert into users values(null,?,?)";
    int row = qr.update(sql,loginName,loginPwd);
    if(row > 0){
    System.out.println("注册成功!");
    }else{
    System.out.println("注册失败!");
    }
    }
    //登录
    private static void toLogin() throws SQLException {
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入登录名:");
    String loginName = sc.next();
    System.out.println("请输入密码:");
    String loginPwd = sc.next();

    //查询数据
    QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());
    String sql = "select * from users where loginName = ? and loginPwd = ?";
    User user = qr.query(sql, new BeanHandler<User>(User.class),loginName,loginPwd);
    if(user != null){
    System.out.println("欢迎:" + loginName + " 登录系统!!");
    }else{
    System.out.println("用户名或密码错误!!");
    }
    }
    }

    package cn.baidu.demo03_不使用MVC模式实现登录注册案例;

    import javax.sql.DataSource;

    import com.mchange.v2.c3p0.ComboPooledDataSource;

    public class JDBCUtils {
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

    public static DataSource getDataSource(){
    return dataSource;
    }
    }


    06.MVC模式_使用MVC模式实现登录

    MVC模式:(MVC模式产生的原因)如果将程序的所有代码写在一个类中,这样代码量太大,会对后期程序维护造成困难.

    什么是MVC模式呢?
    A:任何的程序都可以分为两部分代码:
    1.用于接收用户数据,为用户显示数据的代码:视图层 例如:键盘录入和输出语句
    2.用于业务逻辑处理的代码 :控制层 例如:数据逻辑处理和执行SQL语句等
    3.作为逻辑模型 :模型层 例如:JavaBean

    B:作为企业级开发,要分五层:
    1.视图层(View)(1.接收数据;2.显示数据)
    2.控制层(Controller)(1.业务分发:查找相应的业务层)
    3.业务层(Service)(1.负责处理具体的业务逻辑)
    4.持久层(DAO)(1.所有访问数据库的代码)
    5.数据类型(类:Model)
    (从1-5,进过数据库再返回去5-1)

    好处:1.将不同功能的代码分到不同的类中,使一个类中都具有相同功能的代码(高内聚)
    2.从视图层-->控制层-->业务层-->持久层 有一个"顺序的依赖关系",反向,则没有这种依赖关系,相互独立.(低耦合)

    注意:1.从视图层到持久层,之间是顺序的依赖关系,不能跳级.
    2.返现不存在依赖关系,不能在底层主动调用上一层的类中的方法.

    /*

    package cn.baidu.demo04_MVC实现登录注册;

    public class UserModel {
    private Integer uid;
    private String loginName;
    private String loginPwd;
    public UserModel(Integer uid, String loginName, String loginPwd) {
    super();
    this.uid = uid;
    this.loginName = loginName;
    this.loginPwd = loginPwd;
    }
    public UserModel() {
    super();
    // TODO Auto-generated constructor stub
    }
    public Integer getUid() {
    return uid;
    }
    public void setUid(Integer uid) {
    this.uid = uid;
    }
    public String getLoginName() {
    return loginName;
    }
    public void setLoginName(String loginName) {
    this.loginName = loginName;
    }
    public String getLoginPwd() {
    return loginPwd;
    }
    public void setLoginPwd(String loginPwd) {
    this.loginPwd = loginPwd;
    }
    @Override
    public String toString() {
    return "User [uid=" + uid + ", loginName=" + loginName + ", loginPwd=" + loginPwd + "]";
    }


    }

    package cn.baidu.demo04_MVC实现登录注册;

    import javax.sql.DataSource;

    import com.mchange.v2.c3p0.ComboPooledDataSource;

    public class JDBCUtils {
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

    public static DataSource getDataSource(){
    return dataSource;
    }
    }


    /*
    * 视图层:只接受和输出语句,显示
    *
    */
    public class View {
    private Controller control = new Controller();


    public void start() {
    Scanner sc = new Scanner(System.in);
    while(true) {
    System.out.println("1.登录 2.注册 3.退出");
    int op = sc.nextInt();
    switch(op) {
    case 1://登录
    toLogin();
    break;
    case 2://注册
    toRegist();
    break;
    case 3://退出
    System.out.println("谢谢使用!!");
    System.exit(0);
    default :
    System.out.println("错误的输入!");
    break;
    }


    }
    }


    //注册
    private void toRegist() {
    // TODO Auto-generated method stub
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入用户名:");
    String loginName = sc.nextLine();
    System.out.println("请输入密码:");
    String loginPwd = sc.nextLine();

    //把接收的数据封装到一个User对象
    UserModel user = new UserModel();
    user.setLoginName(loginName);
    user.setLoginPwd(loginPwd);

    //调用控制层
    try{
    if(control.toRegist(user)) {
    System.out.println("注册成功!");
    }else {
    System.out.println("注册失败!");
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    //登录
    private void toLogin() {
    // TODO Auto-generated method stub
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入登录名:");
    String loginName = sc.nextLine();
    System.out.println("请输入密码:");
    String loginPwd = sc.nextLine();

    //封装JavaBean
    UserModel user = new UserModel();
    user.setLoginName(loginName);
    user.setLoginPwd(loginPwd);

    //调用控制层
    try {
    if(this.control.toLogin(user)) {
    System.out.println("欢迎:" + loginName + "登录系统!");
    //启动学员信息管理的视图层代码
    }else {
    System.out.println("登录失败!可能的原因:用户名和密码错误!");
    }
    }catch (Exception e) {
    e.printStackTrace();
    }

    }

    }

    package com.baidu_02;

    /*
    * 控制层:进行业务分发,发给业务层
    *
    */
    public class Controller {
    private Service service = new Service();
    //1.注册
    public boolean toRegist(UserModel user) throws Exception {
    //1.调用Service
    return service.toRegist(user);
    }

    //2.登录
    public boolean toLogin(UserModel user) throws Exception {
    //调用Service
    return service.toLogin(user);
    }

    }

    //业务层:主要进行逻辑处理

    public class Service {
    private DAO dao = new DAO();
    //1.注册:
    public boolean toRegist(UserModel user) throws Exception {
    /*
    * 1.验证用户名和密码的字符
    * 用户名6-12个字符,由数字,大小字母,小写字母组成
    * 密码:必须是6位数字
    *
    */
    String regex = "[0-9,A-Z,a-z]{6,12}";
    if(!user.getLoginName().matches(regex)) {
    return false;
    }
    // \d 表示数字
    regex = "\d{6}";
    if(!user.getLoginPwd().matches(regex)) {
    return false;
    }

    //2.数据库验证--用户名不能重复--调用DAO
    UserModel resultUser = dao.findByLoginName(user.getLoginName());
    //此用户名已经被使用
    if(resultUser != null) {
    return false;
    }

    //3.将这条信息写入到数据库
    return dao.save(user);
    }

    //2.登录
    public boolean toLogin(UserModel user) throws Exception {
    //1.做一些用户名和密码的字符验证
    //...
    //2.查询此用户
    UserModel resultBean = dao.findByLoginAndPassword(user);
    return resultBean != null;
    }

    }


    //持久层:主要对数据库进行操作

    public class DAO {
    private QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());

    //1.使用用户名查询一条记录
    public UserModel findByLoginName(String loginName) throws Exception {
    String sql = "select * from user where loginName = ?";
    UserModel user = qr.query(sql, new BeanHandler<UserModel>(UserModel.class), loginName);
    //查到返回:UserModel对象:否则:返回NULL
    return user;
    }

    //2.写入一条记录
    public boolean save(UserModel user) throws Exception {
    String sql = "insert into user values(null,?,?)";
    int row = qr.update(sql, user.getLoginName(),user.getLoginPwd());
    //如果不影响1行: 返回true;如果影响0行:返回false
    return row > 0;
    }

    //3.用户名和密码查询一个用户
    public UserModel findByLoginAndPassword(UserModel user) throws Exception {
    String sql = "select * from user where loginName = ? and loginPwd = ?";
    //这里的BeanHandler是因为查询满足条件的数据的第一条记录
    UserModel query = qr.query(sql, new BeanHandler<UserModel>(UserModel.class), user.getLoginName(),user.getLoginPwd());
    return query;

    }

    }
    /*
    07.转账案例_MVC模式部署分析
    08.转账案例_MVC模式事务处理实现
    10.线程间共享对象实现_ThreadLocal
    11.转账案例_使用ThreadLocal实现在service层和DAO层实现共享Connection对象

    其实从程序角度看,tlt变量的确是一个,毫无疑问的。但是为什么打印出来的数字就互不影响呢?
    是因为使用了Integer吗?-----不是。
    原因是:protected T initialValue()和get(),因为每个线程在调用get()时候,发现Map中不存在就创建。调用它的时候,就创建了一个新变量
    ,类型为T。每次都新建,当然各用个的互不影响了。

    意义:

    1.提供了保存对象的方法:每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。

    2.避免参数传递的方便的对象访问方式:将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()
    方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。

    理解ThreadLocal中提到的变量副本
    “当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本” —— 并不是通过ThreadLocal.set( )实现的,而是每个线程使用“new对象”(或拷贝) 的操作来创建对象副本, 通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象(ThreadLocal实例是作为map的key来使用的)。

    如果ThreadLocal.set( )进去的对象是多线程共享的同一个对象,那么ThreadLocal.get( )取得的还是这个共享对象本身 —— 那么ThreadLocal还是有并发访问问题的!

    解决线程安全问题,本质上就是解决资源共享问题,一般有以下手段:

    1)可重入(不依赖环境);2)互斥(同一时间段只允许一个线程使用);3)原子操作;4)Thread-Local

    为了保证函数是可重入的,需要做到一下几点:

    1,不在函数内部使用静态或者全局数据

    2,不返回静态或者全局数据,所有的数据都由函数调用者提供

    3,使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

    4, 如果必须访问全局数据,使用互斥锁来保护

    5,不调用不可重入函数

    可重入函数:可重入函数是线程安全函数的一种,其特点在于它们被多个线程调用时,不会引用任何共享数据。
    可重入函数通常要比不可重入的线程安全函数效率高一些,因为它们不需要同步操作。更进一步说,将第2类线程不安全函数转化为线程安全函数的唯一方法就是重写它,使之可重入。
    显式可重入函数:如果所有函数的参数都是传值传递的(没有指针),并且所有的数据引用都是本地的自动栈变量(也就是说没有引用静态或全局变量),那么函数就是显示可重入的,也就是说不管如何调用,我们都可断言它是可重入的。

    隐式可重入函数:可重入函数中的一些参数是引用传递(使用了指针),也就是说,在调用线程小心地传递指向非共享数据的指针时,它才是可重入的。例如rand_r就是隐式可重入的。
    我们使用可重入(reentrant)来包括显式可重入函数和隐式可重入函数。然而,可重入性有时是调用者和被调用者共有的属性,并不只是被调用者单独的属性。

    原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切[1] 换到另一个线程).


    public class View {
    public void start(){
    Scanner sc = new Scanner(System.in);
    System.out.println("转出方:");
    String outUserName = sc.next();
    System.out.println("转入方:");
    String inUserName = sc.next();
    System.out.println("转账金额:");
    int money = sc.nextInt();

    //封装JavaBean
    TransBean t = new TransBean();
    t.setOutUserName(outUserName);
    t.setInUserName(inUserName);
    t.setMoney(money);
    Thread tt;
    //调用控制层
    Controller contrl = new Controller();
    try {
    if(contrl.toTrans(t)){
    System.out.println("转账成功!");
    }else{
    System.out.println("转账失败,所有操作被取消!");
    }
    } catch (SQLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    }


    public class Controller {
    public boolean toTrans(TransBean t) throws SQLException{
    //调用服务层
    Service service = new Service();
    return service.toTrans(t);
    }
    }


    public class Service {
    private DAO dao = new DAO();
    public boolean toTrans(TransBean t) throws SQLException{
    //1.验证账户是否存在;
    AccountBean acc = dao.findByUserName(t.getOutUserName());
    if(acc == null){
    return false;
    }
    acc = dao.findByUserName(t.getInUserName());
    if(acc == null){
    return false;
    }
    //2.验证转出方的余额是否支持转账;
    Integer outBalance = dao.findBalanceByUserName(t.getOutUserName());
    if(t.getMoney() > outBalance){
    return false;
    }
    //3.事务处理实施转账业务;
    //1.获取一个连接对象
    Connection conn = JDBCUtils.getDataSource().getConnection();
    //2.关闭自动提交
    conn.setAutoCommit(false);
    //将conn通过ThreadLocal存储到当前线程的map中
    Const.local.set(conn);
    //3.调用dao,执行转账
    try{
    boolean b1 = dao.updateBalanceByUserName(t.getOutUserName(), -t.getMoney());
    int i = 10 / 0;
    boolean b2 = dao.updateBalanceByUserName(t.getInUserName(), t.getMoney());
    if(b1 && b2){
    //4.提交
    conn.commit();
    return true;
    }else{
    //5.回滚
    conn.rollback();
    return false;
    }
    }catch(Exception e){
    //5.回滚
    conn.rollback();
    return false;
    }finally{
    conn.setAutoCommit(true);//还原为自动事务
    conn.close();//归还到连接池中
    }
    }
    }

    public class DAO {
    private QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());
    //1.使用用户名查找一个用户
    public AccountBean findByUserName(String userName) throws SQLException{
    String sql = "select * from account where username = ?";
    AccountBean acc = qr.query(sql, new BeanHandler<AccountBean>(AccountBean.class),userName);
    return acc;
    }
    //2.获取某个用户的余额
    public Integer findBalanceByUserName(String userName) throws SQLException{
    String sql = "select balance from account where username = ?";
    Object result = qr.query(sql, new ScalarHandler(),userName);
    if(result != null){
    return Integer.valueOf(result.toString());
    }
    return null;
    }
    //3.修改某个账户的余额
    public boolean updateBalanceByUserName(String userName,Integer money) throws SQLException{
    String sql = "update account set balance = balance + ? where username = ?";
    //通过ThreadLocal从当前线程对象的Map中获取conn
    int row = qr.update(Const.local.get(), sql, money,userName);
    return row > 0;
    }
    }


    public class Const {
    public static ThreadLocal<Connection> local = new ThreadLocal<>();
    }


    07.转账案例_MVC模式部署分析

    /*
    * 前端的JavaBean,跟前端表单对应
    */
    public class TransBean {
    private String outUserName;
    private String inUserName;
    private Integer money;
    public TransBean(String outUserName, String inUserName, Integer money) {
    super();
    this.outUserName = outUserName;
    this.inUserName = inUserName;
    this.money = money;
    }
    public TransBean() {
    super();
    // TODO Auto-generated constructor stub
    }
    public String getOutUserName() {
    return outUserName;
    }
    public void setOutUserName(String outUserName) {
    this.outUserName = outUserName;
    }
    public String getInUserName() {
    return inUserName;
    }
    public void setInUserName(String inUserName) {
    this.inUserName = inUserName;
    }
    public Integer getMoney() {
    return money;
    }
    public void setMoney(Integer money) {
    this.money = money;
    }
    @Override
    public String toString() {
    return "TransBean [outUserName=" + outUserName + ", inUserName=" + inUserName + ", money=" + money + "]";
    }
    }


    /*
    * 后端的JavaBean,跟表结构相同
    */
    public class AccountBean {
    private Integer id;
    private String username;
    private Integer money;
    public Integer getId() {
    return id;
    }
    public void setId(Integer id) {
    this.id = id;
    }
    public String getUsername() {
    return username;
    }
    public void setUsername(String username) {
    this.username = username;
    }
    public Integer getMoney() {
    return money;
    }
    public void setMoney(Integer money) {
    this.money = money;
    }
    public AccountBean() {
    super();
    // TODO Auto-generated constructor stub
    }
    public AccountBean(Integer id, String username, Integer money) {
    super();
    this.id = id;
    this.username = username;
    this.money = money;
    }
    @Override
    public String toString() {
    return "Account [id=" + id + ", username=" + username + ", money=" + money + "]";
    }
    }


    package cn.baidu.demo05_MVC_结合事务处理实现转账案例;

    import javax.sql.DataSource;

    import com.mchange.v2.c3p0.ComboPooledDataSource;

    public class JDBCUtils {
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

    public static DataSource getDataSource(){
    return dataSource;
    }
    }

    import java.util.Scanner;
    //视图层
    public class View {
    public void start(){
    Scanner sc = new Scanner(System.in);
    System.out.println("转出方:");
    String outUserName = sc.next();
    System.out.println("转入方:");
    String inUserName = sc.next();
    System.out.println("转账金额:");
    int money = sc.nextInt();

    //封装JavaBean
    TransBean t = new TransBean();
    t.setOutUserName(outUserName);
    t.setInUserName(inUserName);
    t.setMoney(money);
    Thread tt;
    //调用控制层
    Controller contrl = new Controller();
    try {
    if(contrl.toTrans(t)){
    System.out.println("转账成功!");
    }else{
    System.out.println("转账失败,所有操作被取消!");
    }
    } catch (SQLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    }


    //控制层
    public class Controller {
    public boolean toTrans(TransBean t) throws SQLException{
    //调用服务层
    Service service = new Service();
    return service.toTrans(t);
    }
    }

    //业务层或者服务层
    public class Service {
    private DAO dao = new DAO();
    public boolean toTrans(TransBean t) throws SQLException{
    //1.验证账户是否存在;
    AccountBean acc = dao.findByUserName(t.getOutUserName());
    if(acc == null){
    return false;
    }
    acc = dao.findByUserName(t.getInUserName());
    if(acc == null){
    return false;
    }
    //2.验证转出方的余额是否支持转账;
    Integer outBalance = dao.findBalanceByUserName(t.getOutUserName());
    if(t.getMoney() > outBalance){
    return false;
    }
    //3.事务处理实施转账业务;
    //1.获取一个连接对象
    Connection conn = JDBCUtils.getDataSource().getConnection();
    //2.关闭自动提交
    conn.setAutoCommit(false);
    //3.调用dao,执行转账
    try{
    boolean b1 = dao.updateBalanceByUserName(conn,t.getOutUserName(), -t.getMoney());
    // int i = 10 / 0;
    boolean b2 = dao.updateBalanceByUserName(conn,t.getInUserName(), t.getMoney());
    if(b1 && b2){
    //4.提交
    conn.commit();
    return true;
    }else{
    //5.回滚
    conn.rollback();
    return false;
    }
    }catch(Exception e){
    //5.回滚
    conn.rollback();
    return false;
    }finally{
    conn.setAutoCommit(true);//还原为自动事务
    conn.close();//归还到连接池中
    }
    }
    }


    public class DAO {
    private QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());
    //1.使用用户名查找一个用户
    public AccountBean findByUserName(String userName) throws SQLException{
    String sql = "select * from account where username = ?";
    AccountBean acc = qr.query(sql, new BeanHandler<AccountBean>(AccountBean.class),userName);
    return acc;
    }
    //2.获取某个用户的余额
    public Integer findBalanceByUserName(String userName) throws SQLException{
    String sql = "select balance from account where username = ?";
    Object result = qr.query(sql, new ScalarHandler(),userName);
    if(result != null){
    return Integer.valueOf(result.toString());
    }
    return null;
    }
    //3.修改某个账户的余额
    public boolean updateBalanceByUserName(Connection conn,String userName,Integer money) throws SQLException{
    String sql = "update account set balance = balance + ? where username = ?";
    int row = qr.update(conn, sql, money,userName);
    return row > 0;
    }
    }


    08.转账案例_MVC模式事务处理实现
    10.线程间共享对象实现_ThreadLocal
    11.转账案例_使用ThreadLocal实现在service层和DAO层实现共享Connection对象

    ------------------------以下内容【了解】-------------------------------------------
    12.事务的特性_ACID
    原子性:强调事务的不可分割.多条语句要么都成功,要么都失败。
    一致性:强调的是事务的执行的前后,数据要保持一致.
    隔离性:一个事务的执行不应该受到其他事务的干扰.
    持久性:事务一旦结束(提交/回滚)数据就持久保持到了数据库.

    13.事务的特性_不考虑事务的隔离性可能引发的问题
    * 脏读 :一个事务读到另一个事务还没有提交的数据.
    * 不可重复读 :一个事务读到了另一个事务已经提交的update的数据,导致在当前的事务中多次查询结果不一致.
    * 虚读/幻读 :一个事务读到另一个事务已经提交的insert的数据,导致在当前的事务中多次的查询结果不一致.

    14.事务的特性_解决引发的读问题
    设置事务的隔离级别:
    1 read uncommitted :未提交读.脏读,不可重复读,虚读都可能发生.
    2 read committed :已提交读.避免脏读.但是不可重复读和虚读有可能发生.(Oracle默认)
    4 repeatable read :可重复读.避免脏读,不可重复读.但是虚读有可能发生.(MySql默认)
    8 serializable :串行化的.避免脏读,不可重复读,虚读的发生.
    1).查看当前事务的隔离级别:
    select @@tx_isolation;
    2).设置当前事务的隔离级别:
    set session transaction isolation level 上面1,2,4,8四个隔离级别名称之一;
    3).级别超高,越安全,效率越低。
    ==========================================================================================================================
    学习目标总结:
    1、理解事务的概念
    a. 能够说出事务的概念
    事务指的是逻辑上的一组操作(多条sql语句),组成这组操作的各个单元要么全都成功,要么全都失败。
    b. 能够说出事务的特性
    原子性:强调事务的不可分割.多条语句要么都成功,要么都失败。
    一致性:强调的是事务的执行的前后,数据要保持一致.
    隔离性:一个事务的执行不应该受到其他事务的干扰.
    持久性:事务一旦结束(提交/回滚)数据就持久保持到了数据库.
    2、理解脏读,不可重复读,幻读的概念及解决办法
    a. 能够说出不考虑事务的隔离性会出现的问题
    1.脏读:一个事务读到另一个事务还没有提交的数据.
    2.不可重复读:一个事务读到了另一个事务已经提交的update的数据,导致在当前的事务中多次查询结果不一致.
    3.虚读/幻读:一个事务读到另一个事务已经提交的insert的数据,导致在当前的事务中多次的查询结果不一致.

    b. 能够说出如何使用事务的隔离级别分别可以解决哪些问题
    1 read uncommitted:什么都没解决。
    2 read committed:只解决脏读。(Oracle默认)
    4 repeatable read:解决了脏读、不可重复读、(MYSQL中也解决了虚读)(MySql默认)
    8 serializable:将两个事务完全隔离,一个事务没有执行完毕,另一个事务只能等待;

    3、能够在MySQL中使用事务
    a. 说出mysql中对事务支持的特点
    1.自动事务:MySQL默认
    2.手动事务:
    b. 使用命令行手动开启事务
    start transaction;
    c. 使用命令行手动提交事务
    commit;
    d. 使用命令行手动回滚事务
    rollback;
    e. 说出MySQL数据库的默认隔离级别
    repeatable read

    4、能够在转账功能中使用DBUtils开发包完成事务控制
    a. 能够编写一个转账的程序功能
    b. 能够在程序中添加事务保证转账程序的数据正确
    5、能够使用DBUtils并通过ThreadLocal绑定Connection的方式控制事务
    a. 独立编写操作数据库的JDBC程序
    b. 能够在程序中使用ThreadLocal绑定Connection的方式控制事务

  • 相关阅读:
    Java中的责任链设计模式,太牛了!
    醒醒吧,世界上有技术驱动型公司!
    现身说法:37岁老码农找工作
    如何快速安全的插入千万条数据?
    这个函数,1987年在这了,别动它!
    word自动备份,word误删内容恢复
    给Ubuntu 16.04更换更新源
    Django分别使用Memcached和Redis作为缓存的配置(Linux环境)
    Pycharm远程连接服务器,并在本地调试服务器代码
    Navicat远程连接阿里云服务器的mysql
  • 原文地址:https://www.cnblogs.com/haizai/p/11297581.html
Copyright © 2011-2022 走看看