Room记录搜索记录逻辑思路

记录数据使用ROOM,传递使用ViewModel LiveDataBus,这篇文章主要记录 搜索记录 本地 线上,上传失败,记录本地,网络回复统一上传等逻辑的操作。

目录

首先是设计数据表:

定义DAO操作接口

定义数据库类

Mvvm 模式中封装使用Room


首先阐述下搜索记录的要求,后面会逐一针对这些要求使用Room本身能力解决。

  1. 最多记录10条;
  2. 搜索分为点位搜索,和普通搜索。
  3. 列表展示输入搜索的文字,要求文字不可有重复。
  4. 记录分为本地记录和在线记录。

首先是设计数据表:

@Entity(indices = {@Index(value = {"searchKeyword", "searchType"}, unique = true)})
public class SearchHistoryEntity {

    @PrimaryKey(autoGenerate = true)
    public int id;
    public String userId; // 可以为null表示未登录用户
    public String searchKeyword;
    public long searchTime;
    @TypeConverters(Converters.class)
    public SearchType searchType; // 使用枚举类型
    public double latitude; // 纬度
    public double longitude; // 经度
    public boolean loginStatus; // false: 未登录, true: 已登录

}
  • @Entity 表示这是一个 Room 数据库中的表。
  • indices 参数定义了一个组合索引 searchKeyword 和 searchType,并且设置为唯一索引。这意味着在表中,searchKeyword 和 searchType 的组合必须是唯一的。

serchType 是枚举类型,为了区分 普通搜索和点位搜索。

设置searchKeyword 和 searchType 组合必须是唯一的。

以上可以满足第二、三条要求。

定义DAO操作接口

@Dao
public interface SearchHistoryDao {
    // 插入新的搜索记录,如果发生冲突则替换
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    long insertPerson(SearchHistoryEntity entity);
    @Query("DELETE FROM SearchHistoryEntity WHERE id = (SELECT id FROM SearchHistoryEntity ORDER BY searchTime ASC LIMIT 1)")
    void deleteOldestSearchHistory();
    // 删除所有的搜索历史记录
    @Query("DELETE FROM SearchHistoryEntity")
    void deleteAllSearchHistory();

    // 查询所有历史记录
    @Query("SELECT * FROM SearchHistoryEntity ORDER BY searchTime DESC")
    public List<SearchHistoryEntity> selectHis();

    // 查询所有已登录的数据
    @Query("SELECT * FROM SearchHistoryEntity WHERE userId = :userId")
    List<SearchHistoryEntity> getSearchHistoryByUserId(String userId);

    // 查询所有未登录的数据
    @Query("SELECT * FROM SearchHistoryEntity WHERE loginStatus = 0 ORDER BY searchTime DESC")
    List<SearchHistoryEntity> getAllLoggedOutData();

    // 删除所有已登录的数据
    @Query("DELETE FROM SearchHistoryEntity WHERE loginStatus = 1")
    void deleteAllLoggedInData();

}

这个类其实就是操作数据的工具类,里面最主要需要解释的只有

  • @Insert(onConflict = OnConflictStrategy.REPLACE): 该注解表明该方法用于向数据库插入数据。onConflict 参数设置为 OnConflictStrategy.REPLACE,这意味着如果发生冲突(例如已经存在具有相同主键的行),则用新数据替换现有行。

这样是第二天 第三条的补充,在插入相同搜索的记录的时候覆盖原有记录。

定义数据库类

@Database(entities = {SearchHistoryEntity.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
    public abstract SearchHistoryDao searchHistoryDao();

    private static volatile AppDatabase INSTANCE;
    private static final int NUMBER_OF_THREADS = 4;
    static final ExecutorService databaseWriteExecutor = Executors.newFixedThreadPool(NUMBER_OF_THREADS);

    static AppDatabase getDatabase(final Context context) {
        if (INSTANCE == null) {
            synchronized (AppDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                            AppDatabase.class, "app_database")
                            .addCallback(sRoomDatabaseCallback)
                            .build();
                }
            }
        }
        return INSTANCE;
    }

    private static RoomDatabase.Callback sRoomDatabaseCallback =
            new RoomDatabase.Callback() {
                @Override
                public void onCreate(@NonNull SupportSQLiteDatabase db) {
                    super.onCreate(db);

                    // 创建触发器:确保最多保留10条记录
                    db.execSQL("CREATE TRIGGER limit_search_history_count " +
                            "AFTER INSERT ON SearchHistoryEntity " +
                            "WHEN (SELECT COUNT(*) FROM SearchHistoryEntity) > 10 " +
                            "BEGIN " +
                            "DELETE FROM SearchHistoryEntity WHERE id = (SELECT id FROM SearchHistoryEntity ORDER BY searchTime ASC LIMIT 1); " +
                            "END");
                }
            };
}

这里是一个模版代码,其中唯一注意的sRoomDatabaseCallback ,添加一个触发器,当数据超过10条后,按照添加顺序删除最早添加的数据。

其实和DAO接口中定义的 deleteOldestSearchHistory 方法功能是一样的,但是显然触发器的方式更省心。不需要我们每次添加都要去获取一次存储了多少条数据。再去删除。

满足第一条

以上就是Room完整的模版代码,下面才是最主要的第四个条件。

我项目框架是Mvvm,所以操作数据库的代码就放到了 Repository 中,其实没必要,但是咱们尊许模式搞一遍,不在意的话直接使用  db = AppDatabase.getDatabase(this); 即可

Mvvm 模式中封装使用Room

  1. 创建 AppDataBase 抽象类
@TypeConverters(value = {Converters.class})
@Database(entities = {SearchHistoryEntity.class}, version = 1)
public abstract class AppDataBase extends RoomDatabase {
    public abstract SearchHistoryDao searchHistoryDao();

}

对了,差点忘记 Converters 了,在数据库中使用枚举  JSONObject 需要为其创建一个转换器

public class Converters {
    @TypeConverter
    public static SearchType toSearchType(String value) {
        return value == null ? null : SearchType.valueOf(value);
    }

    @TypeConverter
    public static String fromSearchType(SearchType searchType) {
        return searchType == null ? null : searchType.name();
    }
}
  1. 创建 单利 DbUtil 帮助类
public class DbUtil {
    private AppDataBase appDataBase;

    private static DbUtil instance;
    private Context context;

    private String dbName;

    public static DbUtil getInstance() {
        if (instance == null) {
            instance = new DbUtil();
        }
        return instance;
    }

    public void init(Context context,String dbName) {
        this.context =context.getApplicationContext();
        this.dbName = dbName;
        appDataBase = null;
    }


    public AppDataBase getAppDataBase() {
        if (appDataBase == null) {
            if (TextUtils.isEmpty(dbName)) {
                throw new NullPointerException("dbName is null");
            }
            appDataBase = Room.databaseBuilder(context, AppDataBase.class, dbName)
                    .allowMainThreadQueries()
                    .enableMultiInstanceInvalidation()
//                    .addMigrations(MIGRATION_1_2)
                    .addCallback(sRoomDatabaseCallback)
                    .build();
        }
        return appDataBase;
    }


    /**
     * 数据库版本 1->2 user表格新增了age列
     */
    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {

        }
    };

    /**
     * 数据库版本 2->3 新增book表格
     */
    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {

        }
    };
    private static RoomDatabase.Callback sRoomDatabaseCallback =
            new RoomDatabase.Callback() {
                @Override
                public void onCreate(@NonNull SupportSQLiteDatabase db) {
                    super.onCreate(db);

                    db.execSQL("CREATE TRIGGER limit_search_history_count " +
                            "AFTER INSERT ON SearchHistoryEntity " +
                            "WHEN (SELECT COUNT(*) FROM SearchHistoryEntity) > 30 " +
                            "BEGIN " +
                            "DELETE FROM SearchHistoryEntity WHERE id = (SELECT id FROM SearchHistoryEntity ORDER BY searchTime ASC LIMIT 1); " +
                            "END");
                }
            };

}

MIGRATION_1_2 就是数据升级的操作,这里不做介绍。

  1. 创建 Repository
public class HomeSearchRepository extends BaseModel {

    private SearchHistoryDao searchHistoryDao;

    public HomeSearchRepository() {
        searchHistoryDao = DbUtil.getInstance().getAppDataBase().searchHistoryDao();
    }

   /**
     * Description:插入查询历史(name 相同,忽略策略)
     * author:clp
     * ModificationTime: 2024/12/26 13:06
     */
    public long insertHis(SearchHistoryEntity entity) {
        return searchHistoryDao.insertPerson(entity);
    }
}
  1. 创建 ViewModel
ublic class HomeSearchViewModel extends BaseViewModel<HomeSearchRepository> {

    public MutableLiveData<List<SearchHistoryEntity>> searchHistories = new MutableLiveData<>();


    public HomeSearchViewModel(@NonNull Application application) {
        super(application);
        initAroundSearchDatas();
        gson = new Gson();
    }

    /**
     * 保存搜索历史
     *
     * @param entity 搜索的内容
     */
    public void insertHistory(SearchHistoryEntity entity) {
        if (isLogin) {
            uploadHistory(entity,true);
        } else {
            if (model.insertHis(entity) != -1) {
                searchHistories.postValue(model.getAllLoggedOutData());
            }

        }
    }
}

    isLogin 判断是否登录,

    登录状态 走接口上传,未登录状态 直接存入数据库,存入成功 postValue 到Activity 接收。

    Activity代码就不贴出来了。也就是

    ViewModel.observe(this, new Observer<List<SearchHistoryEntity>>() {
        @Override
        public void onChanged(List<SearchHistoryEntity> searchHistoryEntities) {
            if (searchHistoryEntities != null) {
                mSearchHistoryAdapter.setNewData(searchHistoryEntities);
            }
        }
    });

    如果对Mvvm 框架使用感兴趣可以参考我上篇文章 Mvvm + viewModel

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/978919.html

    如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

    相关文章

    Idea编译项目很久之后,提示 Error:java:OutOfMemoryError:insufficient memory

    项目挺老的的了&#xff0c;平常项目启动&#xff0c;也要挺久的&#xff0c;但是最起码能启动成功&#xff0c;今天下午的时候&#xff0c;项目启动了十几分&#xff0c;一直在转圈&#xff0c;后面控制台输出了这一行异常 Error:java:OutOfMemoryError:insufficient memory …

    MySQL 数据增删改查

    一、插入数据 1.1 insert插入数据 &#xff08;1&#xff09;insert语法格式 INSERT [INTO] 表名 [字段名] VALUES (值列表);&#xff08;2&#xff09;示例 ① 向学生表中插入一行数据 ② 向学生表中插入多行数据 二、更新数据 2.1 update更新数据 &#xff08;1&…

    基于flask+vue框架的的医院预约挂号系统i1616(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

    系统程序文件列表 项目功能:用户,医生,科室信息,就诊信息,医院概况,挂号信息,诊断信息,取消挂号 开题报告内容 基于FlaskVue框架的医院预约挂号系统开题报告 一、研究背景与意义 随着医疗技术的不断进步和人们健康意识的日益增强&#xff0c;医院就诊量逐年增加。传统的现场…

    专业的UML开发工具StarUML

    专业的UML开发工具StarUML 可靠的软件建模软件StarUML StarUML 是一款支持统一建模语言 (UML)框架的开源建模软件。它提供了几种类型的图表&#xff0c;并允许用户生成多种语言的代码。在它的帮助下&#xff0c;软件开发人员可以创建设计、概念和编码解决方案。但是&#xff0…

    转化率(漏斗分析)——mysql计算过程

    转化率(漏斗分析)——mysql计算过程 问题&#xff1a;有一张表&#xff0c;记录了不同用户的用户id&#xff0c;浏览页面时间&#xff0c;加入购物车时间&#xff0c;下单时间&#xff0c;支付时间&#xff0c;算出每天的各个环节的转化率 创建表info(含用户id&#xff0c;浏…

    【Oracle专栏】sqlplus显示设置+脚本常用显示命令

    Oracle相关文档&#xff0c;希望互相学习&#xff0c;共同进步 风123456789&#xff5e;-CSDN博客 1.内容概述 本文主要针对oracle 运维中常用知识点进行整理&#xff0c;包括&#xff1a; 1&#xff09;sqlplus模式下&#xff0c;为了方便查询设置相应的行宽、列宽、行数。…

    基于SpringBoot的“同城宠物照看系统”的设计与实现(源码+数据库+文档+PPT)

    基于SpringBoot的“同城宠物照看系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统总体结构图 局部E-R图 系统首页界面 系统…

    为什么要将PDF转换为CSV?CSV是Excel吗?

    在企业和数据管理的日常工作中&#xff0c;PDF文件和CSV文件承担着各自的任务。PDF通常用于传输和展示静态的文档&#xff0c;而CSV因其简洁、易操作的特性&#xff0c;广泛应用于数据存储和交换。如果需要从PDF中提取、分析或处理数据&#xff0c;转换为CSV格式可能是一个高效…

    docker简介-学习与参考

    docker Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言并遵从 Apache2.0 协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。 容器是完全使用沙箱…

    Linux网络之传输层协议(UDP,TCP协议)

    目录 重新认识端口号 端口号划分 netstat pidof UDP协议 UDP的特点 面向数据报 UDP的缓冲区 全双工和半双工 TCP协议 TCP的特点 TCP报头分析 源端口&#xff0c;目标端口&#xff0c;数据偏移(报文首部长度) 序号 确认号 窗口 6个标志位 ACK SYN …

    【Python 入门基础】—— 人工智能“超级引擎”,AI界的“瑞士军刀”,

    欢迎来到ZyyOvO的博客✨&#xff0c;一个关于探索技术的角落&#xff0c;记录学习的点滴&#x1f4d6;&#xff0c;分享实用的技巧&#x1f6e0;️&#xff0c;偶尔还有一些奇思妙想&#x1f4a1; 本文由ZyyOvO原创✍️&#xff0c;感谢支持❤️&#xff01;请尊重原创&#x1…

    Minio分布式多节点多驱动器集群部署

    Minio分布式多节点多驱动器集群部署 Minio分布式多节点多驱动器集群部署节点规划先决条件开放防火墙端口设置主机名更新域名映射文件时间同步存储要求内存要求 增加虚拟机磁盘(所有机器都要执行)部署分布式 MinIO测试上传与预览测试高可用MinIO 配置限制模拟单节点磁盘故障模拟…

    开源工具推荐:Checkmate监控

    1. 概述 GitHub&#xff1a;bluewave-labs/Checkmate: Checkmate is an open-source, self-hosted tool designed to track and monitor server hardware, uptime, response times, and incidents in real-time with beautiful visualizations. 官网文档&#xff1a;Welcome …

    Unity报错:InvalidOperationException: Insecure connection not allowed

    Unity报错:InvalidOperationException: Insecure connection not allowed 介绍问题原因解决方案方案一&#xff1a;Unity配置文件修改方案二&#xff1a;将网址做备案和证书认证 总结 介绍 最近在做抖音小程序的升级&#xff08;官方换新API&#xff09;&#xff0c;框架的加载…

    linux中安装部署Jenkins,成功构建springboot项目详细教程

    参考别人配置Jenkins的git地址为https&#xff0c;无法连上github拉取项目&#xff0c;所以本章节介绍通过配置SSH地址来连github拉取项目 目录&#xff1a; 1、springboot项目 1.1 创建名为springcloudproject的springboot项目工程 1.2 已将工程上传到github中&#xff0c;g…

    idea + Docker + 阿里镜像服务打包部署

    一、下载docker desktop软件 官网下载docker desktop&#xff0c;需要结合wsl使用 启动成功的画面(如果不是这个画面例如一直处理start或者是stop需要重新启动&#xff0c;不行就重启电脑) 打包成功的镜像在这里&#xff0c;如果频繁打包会导致磁盘空间被占满&#xff0c;需…

    cpp中的继承

    一、继承概念 在cpp中&#xff0c;封装、继承、多态是面向对象的三大特性。这里的继承就是允许已经存在的类&#xff08;也就是基类&#xff09;的基础上创建新类&#xff08;派生类或者子类&#xff09;&#xff0c;从而实现代码的复用。 如上图所示&#xff0c;Person是基类&…

    MySQL - Navicat自动备份MySQL数据

    对于从事IT开发的工程师&#xff0c;数据备份我想大家并不陌生&#xff0c;这件工程太重要了&#xff01;对于比较重要的数据&#xff0c;我们希望能定期备份&#xff0c;每天备份1次或多次&#xff0c;或者是每周备份1次或多次。 如果大家在平时使用Navicat操作数据库&#x…

    el-input实现金额输入

    需求&#xff1a;想要实现一个输入金额的el-input&#xff0c;限制只能输入数字和一个小数点。失焦数字转千分位&#xff0c;聚焦转为数字&#xff0c;超过最大值&#xff0c;红字提示 效果图 失焦 聚焦 报错效果 // 组件limitDialog <template><el-dialog:visible.s…

    基于 C++ Qt 的 Fluent Design 组件库 QFluentWidgets

    简介 QFluentWidgets 是一个基于 Qt 的 Fluent Designer 组件库&#xff0c;内置超过 150 个开箱即用的 Fluent Designer 组件&#xff0c;支持亮暗主题无缝切换和自定义主题色。 编译示例 以 Qt5 为例&#xff08;Qt6 也支持&#xff09;&#xff0c;将 libQFluentWidgets.d…