模拟数据库事务实现转账

模拟数据库事务实现转账

实现思路#

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

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

Copy
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. 事务提交

代码#

Copy
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 @   zofun  阅读(727)  评论(0编辑  收藏  举报
编辑推荐:
· 现代计算机视觉入门之:什么是图片特征编码
· .NET 9 new features-C#13新的锁类型和语义
· Linux系统下SQL Server数据库镜像配置全流程详解
· 现代计算机视觉入门之:什么是视频
· 你所不知道的 C/C++ 宏知识
阅读排行:
· 不到万不得已,千万不要去外包
· C# WebAPI 插件热插拔(持续更新中)
· 会议真的有必要吗?我们产品开发9年了,但从来没开过会
· 【译】我们最喜欢的2024年的 Visual Studio 新功能
· 如何打造一个高并发系统?
点击右上角即可分享
微信分享提示
CONTENTS