模拟数据库事务实现转账

模拟数据库事务实现转账

实现思路

采用和真实数据库同样的策略,来实现数据库的事务。采用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);
    }
}


posted @ 2019-09-11 10:03  zofun  阅读(723)  评论(0编辑  收藏  举报