• 模拟数据库事务实现转账


    模拟数据库事务实现转账

    实现思路

    采用和真实数据库同样的策略,来实现数据库的事务。采用redo 和undo 来确保事务的持久性和原子性。
    redo log是为了确保事务的持久性,数据库在进行数据的更新操作时,不一定会把更新的结果,立即写入磁盘。如果在更新过的数据(脏页)还没写入磁盘的阶段出现故障.数据库可以利用redo log来将之前未持久化到磁盘的数据持久化到数据库。.
    undo log是为了确保事务的原子性,数据库在修改数据之前,均利用undo log进行了数据的备份如果在多次操作之间 数据库故障,可以利用undo恢复原先的数据.

    真实的数据库实现A向B转账50的过程(假设两人账户都有100)

    1. 事务开始
    2. 记录A=100到undo log
    3. 修改A=50
    4. 记录A=50到 redo log
    5. 记录B=100到 undo log
    6. 修改B=50
    7. 记录B=50到redo log
    8. 将redo log写入磁盘
    9. 事务提交
    

    代码

    import com.sun.org.apache.regexp.internal.REUtil;
    
    import java.io.*;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Scanner;
    
    public class TransactionDemo {
        private static final String USER_A ="A.txt";
        private static final String USER_B ="B.txt";
    
        private static final String UNDO_LOG_FILE_PATH ="undolog.txt";
        private static final String REDO_LOG_FILE_PATH ="redolog.txt";
    
        //用户A和B的账户中的数据
        private static int userAaccount;
        private static int userBaccount;
    
        public static void main(String[] args) throws IOException, InterruptedException {
            //事务的回滚
            if(!roleback()){
                System.out.println("输入y继续转账,输入n结束转账");
                String input = new Scanner(System.in).next();
                if("n".equals(input.toLowerCase())){
                    return;
                }
                System.out.println("继续转账咯");
            }
    
            //执行转账逻辑
            execute();
            System.out.println("转账成功");
        }
    
        /**
         * 回滚操作
         * @return 执行了回滚返回false,否则则反之
         * @throws IOException
         */
        private static boolean roleback() throws IOException {
            String undo=FileUtil.getFileInfo(UNDO_LOG_FILE_PATH);
            if(!"".equals(undo)){
                System.out.println("上次事务中断,开始回滚");
                Map<String, String> info = getInfoFromLog(UNDO_LOG_FILE_PATH);
                persist(info);
                clearLogs();
                return false;
            }
            String redo=FileUtil.getFileInfo(REDO_LOG_FILE_PATH);
            if(!"".equals(redo)){
                System.out.println("上次事务未能持久化到磁盘,开始持久化");
                Map<String, String> info = getInfoFromLog(REDO_LOG_FILE_PATH);
                persist(info);
                clearLogs();
                return false;
            }
            clearLogs();
            return  true;
    
    
        }
    
    
        /**
         * 事务开始时的初始化操作
         */
        private static void init() throws IOException {
            //清空redo 和undo
            FileUtil.writeToFile("", REDO_LOG_FILE_PATH);
            FileUtil.writeToFile("", UNDO_LOG_FILE_PATH);
            loader();
        }
    
        /**
         * 执行转账事务的方法
         *
         *
         * 基本原理:
         * redo log是为了确保事务的持久性,数据库在进行数据的更新操作时,不一定会把
         * 更新的结果,立即写入磁盘。如果在更新过的数据(脏页)还没写入磁盘的阶段出现故障
         * 数据库可以利用redo log来将之前未持久化到磁盘的数据持久化到数据库。
         *
         * undo log是为了确保事务的原子性,数据库在修改数据之前,均利用undo log进行了数据的备份
         * 如果在操作之间 数据库故障,可以利用undo恢复原先的数据
         * @throws IOException
         * @throws InterruptedException
         */
        private static void execute() throws IOException, InterruptedException {
            //做准备工作
            init();
            //操作用户A的账户数据之前将原先的数据持久化到undolog
            FileUtil.addInfoToFile(" A:"+userAaccount,UNDO_LOG_FILE_PATH);
            //修改用户A的账户
            userAaccount-=50;
    
            //将用户A修改后的账户信息持久化到redo log
            FileUtil.addInfoToFile(" A:"+userAaccount,REDO_LOG_FILE_PATH);
            //将用户B的账号数据备份到undo log
            FileUtil.addInfoToFile(" B:"+userBaccount,UNDO_LOG_FILE_PATH);
            //修改用户B的账户
            userBaccount+=50;
    
            //将用户B修改后的账户信息持久化到redo log
            FileUtil.addInfoToFile(" B:"+userBaccount,REDO_LOG_FILE_PATH);
    
            //能执行到这,所有的更新后的数据以及持久化到redo log了,事务的原子性保障到了清除undo log
            FileUtil.writeToFile("", UNDO_LOG_FILE_PATH);
           
            //将redo log中的数据写入磁盘,完成数据持久化
            Map<String,String> infos=getInfoFromLog(REDO_LOG_FILE_PATH);
            persist(infos);
    
            //能进行到这一步,持久性已经保证了,没必要保留redo,清除
            FileUtil.writeToFile("",REDO_LOG_FILE_PATH);
    
        }
    
    
    
        /**
         * 将用户A和B的账户数据读入内存
         * 模拟数据库将数据页加入缓冲池
         */
        private static void loader() throws IOException{
            userAaccount=Integer.parseInt(FileUtil.getFileInfo(USER_A));
            userBaccount=Integer.parseInt(FileUtil.getFileInfo(USER_B));
        }
    
        /**
         * 从log中解析数据,并以map的方式返回,方便查询
         * @param path
         * @return
         */
        private static Map<String,String> getInfoFromLog(String path) throws IOException {
            Map<String,String> result=new HashMap<String,String>();
            String info=FileUtil.getFileInfo(path);
            String[] infos = info.trim().split(" ");
            for(String str:infos){
                String[] items=str.split(":");
    
                result.put(items[0],items[1]);
            }
            return result;
        }
    
        /**
         * 将账户余额信息持久化到各自的账户文件
         * @param infos
         */
        private static void persist(Map<String,String> infos) throws IOException {
            if(infos.containsKey("A")){
    
                FileUtil.writeToFile(infos.get("A"),USER_A);
            }
            if(infos.containsKey("B")){
                FileUtil.writeToFile(infos.get("B"),USER_B);
            }
    
    
    
        }
    
        /**
         * 清除所有的日志
         * @throws IOException
         */
        private static void clearLogs() throws IOException {
            FileUtil.writeToFile("",UNDO_LOG_FILE_PATH);
            FileUtil.writeToFile("",UNDO_LOG_FILE_PATH);
        }
    
    
    
    
    
    }
    
    
    
    class FileUtil{
        /**
         * 读取文件中的内容
         * @param path 路径
         * @return
         * @throws IOException
         */
        public static String getFileInfo(String path) throws IOException {
            byte[] bytes=new byte[1024];
            ByteBuffer buffer=ByteBuffer.wrap(bytes);
            FileInputStream stream = new FileInputStream(path);
            FileChannel channel = stream.getChannel();
            channel.read(buffer);
            return new String(bytes,"utf-8").trim();
    
        }
    
        /**
         * 向文件中写入内容
         * @param info 待写入的内容
         * @param path 路径
         * @throws IOException
         */
        public static void writeToFile(String info,String path) throws IOException {
            ByteBuffer buffer=ByteBuffer.allocate(1024);
            FileOutputStream stream = new FileOutputStream(path);
            buffer.put(info.getBytes("utf-8"));
            buffer.flip();
            FileChannel channel = stream.getChannel();
            channel.write(buffer);
    
        }
    
        /**
         * 向文件中追加内容
         * @param Info 待追加的内容
         * @param path 路径
         * @throws IOException
         */
        public static void addInfoToFile(String Info,String path) throws IOException {
            String oldInfo=getFileInfo(path);
            writeToFile(oldInfo+Info,path);
        }
    }
    
    
    
  • 相关阅读:
    一次数据库的整理的sql语句
    网页交互及xml的一些属性对程序的影响
    google编程挑战赛Round1的前两道题
    修改Windows帐户密码,导致Sql Server 2000无法启动。
    在虚拟主机中无法实现缩放等交互
    动态控件创建的一些经验
    文章
    如何解决 SQL Server 2000 中的连接问题
    待看
    I帧,P帧,B帧简介
  • 原文地址:https://www.cnblogs.com/zofun/p/11466541.html
Copyright © 2020-2023  润新知