Room记录搜索记录逻辑思路

news/2025/2/26 5:53:23

记录数据使用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


    http://www.niftyadmin.cn/n/5868109.html

    相关文章

    监听其他音频播放时暂停正在播放的音频

    要实现当有其他音频播放时暂停当前音频&#xff0c;你可以使用全局事件总线或 Vuex 来管理音频播放状态。这里我将展示如何使用一个简单的事件总线来实现这个功能。 首先&#xff0c;你需要创建一个事件总线。你可以在项目的一个公共文件中创建它&#xff0c;例如 eventBus.js…

    flutter: table calendar笔记

    pub dev&#xff1a;table_calendar 3.2.0 我来详细解释 TableCalendar 是如何根据不同的 CalendarFormat 来显示界面的。主要逻辑在 CalendarCore 中实现。 核心逻辑分为以下几个部分&#xff1a; 页面数量计算 - _getPageCount 方法根据不同格式计算总页数&#xff1a; in…

    双周报Vol.66: String模式匹配增强、while条件支持使用 is 表达式、新增IDE安装器...多项核心技术更新!

    2025-02-24 MoonBit 更新 String 模式匹配增强 支持在 Array pattern 中使用字符串字面量 在类型为 String 的 Array pattern 中可以通过..操作符来匹配一段字符串&#xff1a; let str "https://try.moonbitlang.com" match str {[.."https://", ..p…

    无人系统:未来技术的自动化与智能化应用

    随着技术的不断发展&#xff0c;无人系统&#xff08;Unmanned Systems&#xff09;作为一类智能化、高效能的自动化设备&#xff0c;已经在多个领域得到了广泛应用。无人系统是指能够自主或通过远程控制执行任务的设备&#xff0c;它们能够代替人类完成高风险或重复性工作&…

    MySQL 入门“鸡”础

    一、Win10 与Ubuntu安装 以下是一篇针对 Ubuntu 安装 MySQL 的过程中写的示例&#xff1a; --- # Ubuntu 安装 MySQL 详细指南 在本教程中&#xff0c;我们将向您展示如何在 Ubuntu 上安装 MySQL&#xff0c;并完成基本的安全配置。以下是具体步骤&#xff1a; # 1. 安装 …

    ubuntu22.04 如何扩根目录空间,当空间不够时

    根目录没扩容之前的空间&#xff0c;只有24G 8、使用 lsblk 查看新硬盘是否被系统识别&#xff0c;如果没有识别请重启虚拟机 9、对新添加的硬盘进行分区 fdisk /dev/sdb 在 command 输入 m 进行帮助 n   增加一个新分区e 扩展分区p 主…

    Flink 中的滚动策略(Rolling Policy)

    在 Apache Flink 中&#xff0c;滚动策略&#xff08;Rolling Policy&#xff09;是针对日志&#xff08;或数据流&#xff09;文件输出的一种管理策略&#xff0c;它决定了在日志文件的大小、时间或其他条件满足特定标准时&#xff0c;如何“滚动”生成新的日志文件。滚动策略…

    计算机视觉算法实战——产品分拣(主页有源码)

    ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​ 1. 领域简介✨✨ 产品分拣是工业自动化和物流领域的核心技术&#xff0c;旨在通过机器视觉系统对传送带上的物品进行快速识别、定位和分类&a…