目录
Room实现数据统一管理-Repo
在多module环境中,将数据类单独写在一个module中,只需引入到其他类中即可实现数据库的访问进行增删改查操作
在使用LitePal框架时,发现数据插入等操作极为方便,只需object.insert()即可实现数据写入,但是经常使用是的Room框架并且也挺依赖它可以获取LiveData对象的,所以自己整理了一个统一的管理类Repo,采用工厂模式找到各个Repo类并进行对应的Dao层操作
原理
Room
这里引用Google对Room的解释
处理大量结构化数据的应用可极大地受益于在本地保留这些数据。最常见的使用场景是缓存相关的数据,这样一来,当设备无法访问网络时,用户仍然可以在离线状态下浏览该内容。
Room 持久性库在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。具体来说,Room 具有以下优势:
- 针对 SQL 查询的编译时验证。
- 可最大限度减少重复和容易出错的样板代码的方便注解。
- 简化了数据库迁移路径。
出于这些方面的考虑,我们强烈建议您使用 Room,而不是直接使用 SQLite API。
反射
因为该模块是采用工厂模式进行设计,所以各个Repo类的获取均通过反射得到
引用百度百科对反射机制的解释
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。
具体实现
Repo(Kotlin)
class Repo {
companion object {
@SuppressLint("StaticFieldLeak")
var context: Context? = null
/**
* 保存各个Repo类
*/
private val instance: MutableMap<String, BaseRepo<*>?> = HashMap(3)
/**
* 在创建application中使用
* @param context 上下文对象
*/
@JvmStatic
fun init(context: Context) {
Repo.context = context
}
/**
* 获取指定的数据仓库
* @param c 类
* @param <T> repo
* @return T
</T> */
@JvmStatic
fun <T : BaseRepo<*>> get(c: Class<T>): T {
if (instance[c.name] == null) {
try {
val repo = Class.forName(c.name).newInstance() as T
// 通过反射获取BaseRepo的init初始化函数
try {
val initMethod = c.superclass.getDeclaredMethod(
"init",
Context::class.java
)
initMethod.isAccessible = true
initMethod.invoke(repo, context)
} catch (e: NoSuchMethodException) {
e.printStackTrace()
} catch (e: InvocationTargetException) {
e.targetException.printStackTrace()
}
instance[c.name] = repo
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: InstantiationException) {
e.printStackTrace()
} catch (e: ClassNotFoundException) {
e.printStackTrace()
}
}
return instance[c.name] as T!!
}
}
}
BaseRepo(Kotlin)
open class BaseRepo<D : BaseDao<*>> {
/**
* 该类对应的dao层对象
*/
lateinit var dao: D
/**
* 从Room配置类中获取该dao层对象
*/
@Synchronized
fun init(context: Context?) {
val instance = AbstractDatabase.getInstance(context!!)
val entityClass =
(javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<D>
val methods = AbstractDatabase::class.java.methods
for (method in methods) {
if (entityClass.name.contains(method.returnType.name)) {
try {
dao = method.invoke(instance) as D
break
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: InvocationTargetException) {
e.printStackTrace()
}
}
}
}
}
BaseDao(Java)
最初使用Kotlin时发现泛型会被转为Any类型,导致方法报错,所以这里使用Java
public interface BaseDao<T extends BaseEntity> {
/**
* 插入一条数据
* @param t 数据源
*/
@Insert
void insert(T t);
/**
* 插入一组数据
* @param ts 数据源
*/
@Insert
void insert(T... ts);
/**
* 插入一组数据
* @param ts 数据源
*/
@Insert
void insert(List<T> ts);
/**
* 删除一条数据
* @param t 数据源
*/
@Delete
void delete(T t);
/**
* 删除一组数据
* @param ts 数据源
*/
@Delete
void delete(T... ts);
/**
* 删除一组数据
* @param ts 数据源
*/
@Delete
void delete(List<T> ts);
/**
* 更新一条数据
* @param t 数据源
*/
@Update
void update(T t);
/**
* 更新一组数据
* @param ts 数据源
*/
@Update
void update(T... ts);
/**
* 更新一组数据
* @param ts 数据源
*/
@Update
void update(List<T> ts);
@RawQuery
List<T> query(SupportSQLiteQuery sql);
@RawQuery
int queryInt(SupportSQLiteQuery sql);
}
BaseEntity(Java)
最初使用Kotlin时发现泛型会被转为Any类型,导致方法报错,所以这里使用Java
public class BaseEntity {
private String tag = "BaseEntity";
@Ignore
public void insert() {
Log.d(tag, "插入数据 " + this.toString());
Repo.get(getRepo()).dao.insert(this);
}
/**
* 清空数据通过查询所有数据再删除实现
*/
@Ignore
public void clear() {
Log.d(tag, "清空数据 " + this.toString());
Repo.get(getRepo()).dao.delete(
Repo.get(getRepo()).dao.query(new SimpleSQLiteQuery("select * from " + getClass().getSimpleName())));
}
@Ignore
public int count() {
Log.d(tag, "获取总数 " + this.toString());
return Repo.get(getRepo()).dao.queryInt(new SimpleSQLiteQuery("select count(1) from " + getClass().getSimpleName()));
}
@Ignore
public void update() {
Log.d(tag, "更新数据 " + this.toString());
Repo.get(getRepo()).dao.update(this);
}
@Ignore
public void delete() {
Log.d(tag, "删除数据 " + this.toString());
Repo.get(getRepo()).dao.delete(this);
}
@Ignore
private Class getRepo() {
Class c = getClass();
Class dao = null;
try {
dao = Class.forName("com.example.entity.repo." + c.getSimpleName() + "Repo");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return dao;
}
}
准备工作(Kotlin)
数据类
/**
* 数据类必须继承BaseEntity
*/
@Entity
data class Teacher(
/**
* 唯一id,自增
*/
@PrimaryKey(autoGenerate = true) val id: Int?,
/**
* 名字
*/
@ColumnInfo var name: String = "",
/**
* 1 男
* 0 女
*/
@ColumnInfo var gender: Int = 0
): BaseEntity() {
var tag: String = "Teacher"
}
Dao(Kotlin)
/**
* dao层必须继承BaseDao<T>,T为对应数据类
* 继承之后即可实现简单的增删改
* 查找需要手动编写查询接口,如下方get(string)
*/
@Dao
interface TeacherDao: BaseDao<Teacher> {
/**
* 获取指定姓名的老师
* @return LiveData<Teacher>
*/
@Query("select * from treacher where name=:name")
fun get(name: String): LiveData<Teacher>
}
Repo(Kotlin)
class TeacherRepo: BaseRepo<TeacherDao>() {
private val map : HashMap<String, LiveData<Teacher>> = hashMapOf()
/**
* 获取指定姓名的老师
* @return LiveData<Teacher>
*/
fun get(name: String): LiveData<Teacher> {
if (map[name] == null) {
map[name] = dao.get(name)
}
return map[name]
}
}
Room配置类(Kotlin)
// AbstractDatabase根据自己的需要参考官方文档进行编写
// dao获取方法名必须为get+类名,否则反射可能拿不到该dao对象
abstract fun getTeacherDao(): TeacherDao
具体使用(Kotlin)
增删改
// 要在子线程中使用
val teacher = Teacher(id = 1, name = "张三", gender = 0)
teacher.insert()
teacher.name = "李四"
teacher.update()
teacher.delete()
Teacher(null).clear()
其他
Repo.get(TeacherRepo::class.java).get("张三").observe(this) {
Log.d("Teacher", it.gender.toString());
}
注意事项
- 在release打包时,添加混淆防止反射找不到类
// com.example为对应数据module包名 -keep public class * extends com.example.entity.* -keep class com.example.entity.* -dontwarn com.example.entity.** -keep class com.example.entity.** { *;}
- 在BaseEntity.getRepo()方法中一定要将当中反射的包名修改为自己项目数据module所在类