记录数据使用ROOM,传递使用ViewModel LiveDataBus,这篇文章主要记录 搜索记录 本地 线上,上传失败,记录本地,网络回复统一上传等逻辑的操作。
目录
首先是设计数据表:
定义DAO操作接口
定义数据库类
Mvvm 模式中封装使用Room
首先阐述下搜索记录的要求,后面会逐一针对这些要求使用Room本身能力解决。
- 最多记录10条;
- 搜索分为点位搜索,和普通搜索。
- 列表展示输入搜索的文字,要求文字不可有重复。
- 记录分为本地记录和在线记录。
首先是设计数据表:
@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
- 创建 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();
}
}
- 创建 单利 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 就是数据升级的操作,这里不做介绍。
- 创建 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);
}
}
- 创建 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