当前位置: 首页 > news >正文

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

    相关文章:

  1. QEMU源码全解析 —— 内存虚拟化(20)
  2. MySQL 和 Elasticsearch 之间的数据同步
  3. 国科大——数据挖掘(0812课程)——课后作业
  4. 【阮一峰】20.注释指令
  5. 禾迈电力电子嵌入式面经和参考答案
  6. OpenCV计算摄影学(2)图像去噪函数denoise_TVL1()
  7. 十类DeepSeek学术提示词分享
  8. 代码异常(js中push)NO.4
  9. Oracle 数据库基础入门(一):搭建数据管理基石
  10. DeepSeek接入问题-Xshell5连接Ubuntu22失败解决方案
  11. 网络安全复习资料
  12. 音视频入门基础:RTP专题(12)——RTP中的NAL Unit Type简介
  13. 基于MATLAB的OFDM通信系统仿真设计
  14. 【NLP 27、文本分类任务 —— 传统机器学习算法】
  15. QT 中的元对象系统(一):元对象和元数据
  16. Android NFC功能开发指南
  17. 深度求索DeepSeek:AI大模型的全域应用与技术突破
  18. 8. 示例:对32位数据总线实现位宽和值域覆盖
  19. BUG: 解决新版本SpringBoot3.4.3在创建项目时勾选lombok但无法使用的问题
  20. 本地部署DeepSeek R1满血版大模型
  21. 网站开发流程传智播客/软文推广名词解释
  22. 盘锦做网站企业/有没有永久免费crm
  23. 中国最好网站建设公司/上海最专业的seo公司
  24. 奢侈品网站建设方案/关键词林俊杰歌词
  25. 美食网站/盐城网站优化
  26. 做网站注册页面/优化推广网站推荐