Android数据库存储模块封装,让操作记录更好用可复用

简介: Android数据库存储模块封装,让操作记录更好用可复用

按照模块化搭积木的思想。


要实现一款POS机,需要存储模块、配置文件操作模块,通信模块,卡操作模块,界面显示模块。


如果这些都具备了,实现一完整的pos机岂不是很简单?就像搭积木一样。


再在android的框架下按照一种MVVM架构去实现,轻而易举且能做到结构清晰。


先来看结果,操作记录有多简单,


Record01 rec = new Record01();
rec.recType = 1;//赋值
RecordApi api = RecordApi.getInstance();
api.saveRec01(rec);//存储
long unrec = api.getRecUnNum();//获取未上传记录数
Log.d("un send rec num:",String.valueOf(unrec));
//依此上传未上传记录
Record01 rdrec;
for(int i =0 ;i < unrec;i++){
rdrec = api.readRecNotServer(1+i);
Log.d("recDebug:",rdrec.toString());
}
//删除已上传记录:
RecordApi api = RecordApi.getInstance();
api.decRec01(unrec);


目前有这个打算从零开始构建一个Android上运行的POS机应用。


先从构建各个模块开始。银联8583解析的模块有了,操作配置文件的已经有了,前文介绍过了。通信的简单,Android上有各种开源的库,其中的retrofit配合Rxjava就很好用。卡操作模块打算用JNI做封装,因此卡操作模块也简单。重要的就是数据存储模块了,


以及后续对MVVM架构模式做个探究,再加上界面就完整了。


这里主要针对数据存储做个封装,让接口更好用。


Android上对本地数据库的操作很简单,且有很多开源的第三方ORM库可以用,如litepal ,greendao,realm,OrmLite等。


其中的greendao据说是性能很好,它能够支持数千条记录的CRUD每秒,和OrmLite相比,GreenDAO要快几乎4.5倍。


Realm 则是一个移动数据库,可运行于手机、平板和可穿戴设备之上。可以让你的应用更快速,带来难以想象的体验。其目标是为了代替 CoreData 和 SQLite 数据库。 Realm非常易用,不是在SQLite基础上的ORM,它有自己的数据查询引擎。是完全重新开发的数据库,速度非常快,并且支持跨平台,数据加密,数据迁移,支持json,流式api等 。


Android官方也有操作sqllite的ROOM框架。


这里我采用了国人做的一个ORM框架litepal,我觉得它操作简单好用,且升级维护表结构也简单。


只所以要封装,是因为本身的数据的 增,删,改,查,无论用哪种框架或者直接用原生的sqlhelper都不难。


但是这还不能满足我们的要求。


我们的需求是,数据存储不能一直顺序存,存满指定数目要从头覆盖存储。一条一条的覆盖已经上传过的记录。


且从不能删除记录,记录只能打过上传标记被覆盖,不能删除记录表里的记录。


还要能查询未上传记录数目,按顺序依次上传未上传的记录数量。


因此,本次封装实现原理,新建一个数据目录表。一个记录表对应一个记录目录表。


记录目录表里就一条记录,用来跟踪记录表里当前记录写到的ID位置,记录上传ID的位置。


且要考虑同一条记录可能需要上传至多个后台(平台)。没上传之前不能被覆盖。


可能有人说直接在一个记录表里就可以搞定了,或者写几个sql语句可以实现。但是数据量很大时效率是个问题。


以下为实现:


package com.example.yang.testmvvm.database.table;
import org.litepal.annotation.Column;
import org.litepal.crud.LitePalSupport;
/**
 * Created by yangyongzhen on 2018/08/07
 *
 * RecordDir,记录表对应的目录表,用来对记录表进行管理。
 * RecordDir表,记录了当前记录的写的位置及记录读的位置。
 * 可据此实现,查询未上传记录数量,依次上传未上传记录,
 * 记录顺序存储,存满指定容量后从头覆盖存储的方式。
 * 删除记录操作只更新记录目录表的读的位置,从不真正的从记录表删除数据,保证数据的安全性。
 */
public class RecordDir extends LitePalSupport {
    @Column(nullable = false)
    //Litepal数据库自动生成的自增的ID
    private long recNO;
    private long writeID;
    private long readID1;
    private long readID2;
    private long readID3;
    private int  mode;
    private int  upDateFlag;
    private long curWriteID;
    private long curReadID;
    @Column(ignore = true)
    public static final long MaxRecNO = 10;
    public long getRecNO() {
        return recNO;
    }
    public long getWriteID() {
        return writeID;
    }
    public long getReadID1() {
        return readID1;
    }
    public long getReadID2() {
        return readID2;
    }
    public long getReadID3() {
        return readID3;
    }
    public long getCurReadID() {
        return curReadID;
    }
    public int getMode() {
        return mode;
    }
    public long getCurWriteID() {
        return curWriteID;
    }
    public void setRecNO(long recNO) {
        this.recNO = recNO;
    }
    public void setWriteID(long writeID) {
        this.writeID = writeID;
    }
    public void setReadID1(long readID1) {
        this.readID1 = readID1;
    }
    public void setReadID2(long readID2) {
        this.readID2 = readID2;
    }
    public void setReadID3(long readID3) {
        this.readID3 = readID3;
    }
    public void setMode(int mode) {
        this.mode = mode;
    }
    public void setCurWriteID(long curWriteID) {
        this.curWriteID = curWriteID;
    }
    public void setCurReadID(long curReadID) {
        this.curReadID = curReadID;
    }
    public int getUpDateFlag() {
        return upDateFlag;
    }
    public void setUpDateFlag(int upDateFlag) {
        this.upDateFlag = upDateFlag;
    }
    @Override
    public String toString() {
        return "RecordDir{" +
                "recNO=" + recNO +
                ", writeID=" + writeID +
                ", readID1=" + readID1 +
                ", readID2=" + readID2 +
                ", readID3=" + readID3 +
                ", mode=" + mode +
                ", upDateFlag=" + upDateFlag +
                ", curWriteID=" + curWriteID +
                ", curReadID=" + curReadID +
                '}';
    }
}


记录操作的接口:


package com.example.yang.testmvvm.database;
import android.content.Context;
import android.util.Log;
import com.example.yang.testmvvm.app.App;
import com.example.yang.testmvvm.database.table.Record01;
import com.example.yang.testmvvm.database.table.RecordDir;
import org.litepal.LitePal;
/**
 * Created by yangyongzhen on 2018/08/07
 * 实现记录的常用操作接口:记录存储,查询未上传记录数,读取未上传记录,删除记录
 */
public class RecordApi {
    public RecordDir recDir01;
    private static Context context;
    private static RecordApi instance = null;
    private RecordApi(Context contxt){
        context = contxt;
        recDir01 = LitePal.find(RecordDir.class, 1);
        if(recDir01 == null){
            recDir01 = new RecordDir();
            recDir01.setWriteID(0);
            recDir01.setReadID1(0);
            recDir01.setRecNO(0);
            recDir01.setUpDateFlag(0);
            recDir01.save();
        }
    }
    /**
     * 保存记录
     * @param rec
     * @return
     */
    public int saveRec01( Record01 rec){
        if(rec == null){
            return 1;
        }
        if(recDir01.getWriteID()+1 > RecordDir.MaxRecNO){
            if((recDir01.getWriteID() + 1 - RecordDir.MaxRecNO) == recDir01.getReadID1()){
                return 2;//记录满
            }
            recDir01.setWriteID(1);
            recDir01.setUpDateFlag(1);
            rec.update(1);
        }
        else {
            if(recDir01.getUpDateFlag() == 1){
                if((recDir01.getWriteID() + 1) == recDir01.getReadID1()){
                    return 3;//记录满
                }
                rec.update(recDir01.getWriteID()+1);
                recDir01.setCurWriteID(recDir01.getWriteID()+1);
                recDir01.setRecNO(recDir01.getRecNO() + 1);
                recDir01.setWriteID(recDir01.getWriteID() + 1);
                recDir01.update(1);
            }
            else {
                rec.save();
                recDir01.setRecNO(recDir01.getRecNO() + 1);
                recDir01.setWriteID(recDir01.getWriteID() + 1);
                recDir01.setCurWriteID(recDir01.getWriteID()+1);
                recDir01.update(1);
            }
        }
        Log.d("WriteRec:",recDir01.toString());
        return 0;
    }
    /**
     * 删除记录,实际上只更改记录目录表的读指针,并不删除记录表的数据
     * 记录表的数据采取循环存储,循环覆盖的模式,保证安全性
     * @param recnum
     * @return
     */
    public int decRec01(long recnum){
        long id = recDir01.getReadID1();
        if(recDir01.getWriteID() == recDir01.getReadID1()){
            return 0;
        }
        if((id + recnum) > RecordDir.MaxRecNO){
            if((id + recnum - RecordDir.MaxRecNO) > recDir01.getWriteID() ){
                recDir01.setReadID1(recDir01.getWriteID());
                recDir01.setCurReadID(recDir01.getWriteID());
                recDir01.update(1);
                return 0;
            }
            recDir01.setReadID1(id + recnum - RecordDir.MaxRecNO);
            recDir01.setCurReadID(id + recnum - RecordDir.MaxRecNO);
            recDir01.update(1);
        }else {
            if(recDir01.getWriteID() > recDir01.getReadID1()){
                if(id + recnum > recDir01.getWriteID()){
                    recDir01.setReadID1(recDir01.getWriteID());
                    recDir01.setCurReadID(recDir01.getWriteID());
                    recDir01.update(1);
                    return 0;
                }
            }
            recDir01.setReadID1(id + recnum);
            recDir01.setCurReadID(id + recnum);
            recDir01.update(1);
        }
        return 0;
    }
    /**
     * 获取未上传的记录条数
     * @return
     */
    public long getRecUnNum()
    {
        long num = 0;
        if(recDir01.getWriteID() == recDir01.getReadID1()){
            num = 0;
            return num;
        }
        if(recDir01.getUpDateFlag() == 0){
            num = (recDir01.getWriteID() - recDir01.getReadID1());
        }else{
            if(recDir01.getWriteID() > recDir01.getReadID1()){
                num = (recDir01.getWriteID() - recDir01.getReadID1());
            }else{
                num = RecordDir.MaxRecNO - recDir01.getReadID1() + recDir01.getWriteID();
            }
        }
        return num;
    }
    /**
     * 读取未上传的记录数据,顺序读取
     * sn取值 1-到-->未上传记录数目
     * @param sn
     * @return
     */
    public Record01 readRecNotServer(long sn){
        Record01 rec;
        long id = recDir01.getReadID1();
        if((id + sn) > RecordDir.MaxRecNO){
            if(id + sn - RecordDir.MaxRecNO > recDir01.getWriteID()){
                return null;
            }
            rec = LitePal.find(Record01.class, id + sn - RecordDir.MaxRecNO );
        }else {
            if(recDir01.getReadID1() < recDir01.getWriteID()){
                if((id + sn) > recDir01.getWriteID()){
                    return null;
                }
            }
            rec = LitePal.find(Record01.class, recDir01.getReadID1() + sn);
        }
        return rec;
    }
    public static RecordApi getInstance(){
        if(instance == null){
            instance = new RecordApi(App.getContext());
        }
        return instance;
    }
}


一个测试 demo:


btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //String anotherName = "John Doe";
                //myViewModel.getName().setValue(anotherName);
                //myViewModel.loginmodel.login("qq8864","123456", LoginType.TYPE_LOGIN);
                Record01 rec = new Record01();
                rec.recType = 1;
                RecordApi api = RecordApi.getInstance();
                api.saveRec01(rec);
                long unrec = api.getRecUnNum();
                Record01 rdrec;
                Log.d("un send rec num:",String.valueOf(unrec));
                for(int i =0 ;i < unrec;i++){
                    rdrec = api.readRecNotServer(1+i);
                    Log.d("recDebug:",rdrec.toString());
                }
            }
        });
        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //mThread = new LongTimeWork();
                //mThread.start();
                /*
                sysCfg.ver.value = "1234";
                sysCfg.ip.value = "218.28.133.181";
                sysCfg.saveConfig();
                sysCfg.printConfig();
                sysCfg.ver.value = "5678";
                sysCfg.saveConfig();
                sysCfg.printConfig();
                */
                RecordApi api = RecordApi.getInstance();
                api.decRec01(1);
                Log.d("del rec:",String.format("curwriteid:%d,curreadid:%d",api.recDir01.getCurWriteID(),api.recDir01.getCurReadID()));
            }
        });


测试日志:


D/WriteRec:: RecordDir{recNO=81, writeID=9, readID1=2, readID2=0, readID3=0, mode=0, upDateFlag=1, curWriteID=9, curReadID=2}
D/un send rec num:: 7
D/recDebug:: Record01{id=3, physn=0, recType=1, psamTid='null', lineID=0, dealTime='null', cardAsn='null', dealMoney=0, oldBalance=0}
D/recDebug:: Record01{id=4, physn=0, recType=1, psamTid='null', lineID=0, dealTime='null', cardAsn='null', dealMoney=0, oldBalance=0}
D/recDebug:: Record01{id=5, physn=0, recType=1, psamTid='null', lineID=0, dealTime='null', cardAsn='null', dealMoney=0, oldBalance=0}
D/recDebug:: Record01{id=6, physn=0, recType=1, psamTid='null', lineID=0, dealTime='null', cardAsn='null', dealMoney=0, oldBalance=0}
D/recDebug:: Record01{id=7, physn=0, recType=1, psamTid='null', lineID=0, dealTime='null', cardAsn='null', dealMoney=0, oldBalance=0}
D/recDebug:: Record01{id=8, physn=0, recType=1, psamTid='null', lineID=0, dealTime='null', cardAsn='null', dealMoney=0, oldBalance=0}
D/recDebug:: Record01{id=9, physn=0, recType=1, psamTid='null', lineID=0, dealTime='null', cardAsn='null', dealMoney=0, oldBalance=0}


至此,操作记录存储以及上传未上传记录变得很简单了。


首先一开始先定义好Recod01表结构。


存记录时:


Record01 rec = new Record01();
rec.recType = 1;//赋值
RecordApi api = RecordApi.getInstance();
api.saveRec01(rec);//存储
long unrec = api.getRecUnNum();//获取未上传记录数
Record01 rdrec;
Log.d("un send rec num:",String.valueOf(unrec));
//依此上传未上传记录
for(int i =0 ;i < unrec;i++){
    rdrec = api.readRecNotServer(1+i);
    Log.d("recDebug:",rdrec.toString());
}


//删除已上传记录:


RecordApi api = RecordApi.getInstance();
api.decRec01(unrec);
相关文章
|
24天前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
92 4
|
1月前
|
SQL Java 数据库连接
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率。本文介绍了连接池的工作原理、优势及实现方法,并提供了HikariCP的示例代码。
50 3
|
3月前
|
SQL 关系型数据库 MySQL
学成在线笔记+踩坑(3)——【内容模块】课程分类查询、课程增改删、课程计划增删改查,统一异常处理+JSR303校验
课程分类查询、课程新增、统一异常处理、统一封装结果类、JSR303校验、修改课程、查询课程计划、新增/修改课程计划
学成在线笔记+踩坑(3)——【内容模块】课程分类查询、课程增改删、课程计划增删改查,统一异常处理+JSR303校验
|
3月前
|
前端开发 应用服务中间件 API
|
4月前
|
存储 SQL 安全
【数据库高手的秘密武器:深度解析SQL视图与存储过程的魅力——封装复杂逻辑,实现代码高复用性的终极指南】
【8月更文挑战第31天】本文通过具体代码示例介绍 SQL 视图与存储过程的创建及应用优势。视图作为虚拟表,可简化复杂查询并提升代码可维护性;存储过程则预编译 SQL 语句,支持复杂逻辑与事务处理,增强代码复用性和安全性。通过创建视图 `high_earners` 和存储过程 `get_employee_details` 及 `update_salary` 的实例,展示了二者在实际项目中的强大功能。
44 1
|
4月前
|
JSON 数据格式 Java
化繁为简的魔法:Struts 2 与 JSON 联手打造超流畅数据交换体验,让应用飞起来!
【8月更文挑战第31天】在现代 Web 开发中,JSON 成为数据交换的主流格式,以其轻量、易读和易解析的特点受到青睐。Struts 2 内置对 JSON 的支持,结合 Jackson 库可便捷实现数据传输。本文通过具体示例展示了如何在 Struts 2 中进行 JSON 数据的序列化与反序列化,并结合 AJAX 技术提升 Web 应用的响应速度和用户体验。
129 0
|
4月前
|
监控 Java 开发工具
如何快速对接Android平台GB28181接入模块(SmartGBD)
大牛直播SDK推出的Android平台GB28181接入SDK(SmartGBD),可实现不具备国标音视频能力的 Android终端,通过平台注册接入到现有的GB/T28181—2016服务,可用于如执法记录仪、智能安全帽、智能监控、智慧零售、智慧教育、远程办公、明厨亮灶、智慧交通、智慧工地、雪亮工程、平安乡村、生产运输、车载终端等场景,可能是业内为数不多功能齐全性能优异的商业级水准GB28181接入SDK。
|
4月前
|
Android开发 iOS开发
Android项目架构设计问题之将隐式跳转的逻辑进行抽象和封装如何解决
Android项目架构设计问题之将隐式跳转的逻辑进行抽象和封装如何解决
47 0
|
4月前
|
编解码 开发工具 Android开发
Android平台RTMP直播推送模块技术接入说明
大牛直播SDK跨平台RTMP直播推送模块,始于2015年,支持Windows、Linux(x64_64架构|aarch64)、Android、iOS平台,支持采集推送摄像头、屏幕、麦克风、扬声器、编码前、编码后数据对接,功能强大,性能优异,配合大牛直播SDK的SmartPlayer播放器,轻松实现毫秒级的延迟体验,满足大多数行业的使用场景。RTMP直播推送模块数据源,支持编码前、编码后数据对接
|
4月前
|
监控 开发工具 Android开发
结合GB/T28181规范探讨Android平台设备接入模块心跳实现
本文介绍了GB28181标准中的状态信息报送机制,即心跳机制,用于监控设备与服务器间的连接状态。根据国标GB/T28181-2016,设备在异常时需立即发送状态信息,在正常状态下则按固定间隔(默认60秒)定期发送。若连续三次(默认值)未收到心跳,则视为离线。文章展示了在Android平台的GB28181设备接入模块(SmartGBD)中,如何调整心跳间隔为20秒及超时次数为3次,并给出了心跳消息的示例和异常处理代码片段。对于希望深入了解或遇到问题的开发者,作者提供了进一步交流的机会。