RoomExtension: 基于Room的扩展,像greenDAO一样用代码查询数据库,告别手写SQL

1,051 阅读4分钟

前言

之前项目中用了greenDAO操作数据库,已经很久不更新了。后来Google官方推出了Room,便准备迁移到Room,不过Room需要自己写SQL。为了迁移的轻松一点,想着自己能否封装一下使用代码查询数据库。但Room使用的为编译时注解,反射不可见,无法在运行时使用代码查询。于是了解到KSP,并使用KSP在编译时生成模板代码。

RoomExtension简介

  • RoomExtension使用KSP自动生成insert、update、query、delete等方法,可以以DSL查询数据库
  • 支持多表联查、子查询、聚合函数、字符函数、数值函数

项目地址:github.com/sumuve/Room…

使用方式:

初始化

//初始化Room
val db = Room.databaseBuilder(
    applicationContext,
    RoomExtensionDatabase::class.java, "database-name"
).build()
//初始化RoomExtension
ReManager.init(
    db = database,      //数据库
    isLog = true,       //是否打印日志,默认为false
    isSqlFormat = false  //打印SQL时是否格式化,默认为false
)

isSqlFormat为false时,sql参数将显示?

SELECT Student.* FROM Student WHERE (Student.classId =  ? )
[1]
query count:4  time:1ms

isSqlFormat为true时?将被参数替代

SELECT Student.* FROM Student WHERE (Student.classId =  1 )
query count:4  time:1ms

insert、update、delete

插入数据

//插入一条或者多条数据
val rows:LongArray= ReDao.insert(Student(),Student())
//插入列表
val students: List<Student> = listOf()
val rows: LongArray = ReDao.insert(students)

rows为插入成功的行列表


更新数据

//一条或者多条数据
val count:Int = ReDao.update(student)
//更新列表
val students: List<Student> = listOf()
val count:Int = ReDao.insert(students)

count为更新成功的条目数量


删除数据

//一条或者多条数据
val count:Int = ReDao.delete(student)
//删除所有数据
val count:Int = ReDao.delete(ReDao.query<Student> {})

count为删除成功的条目数量


查询数据

查询所有数据

val queryList: List<Student> = ReDao.query<Student> {}

按条件查询

//查询student表中classId为1、2,6<=age<=7的数据列表
val queryList: List<Student> = ReDao.query<Student> {
    where {
        `in`(Student::classId, 1, 2)
        between(Student::age, 6, 7)
    }
}

子查询

//查询student表中关联的班级grade为1、2的的数据列表
val queryList: List<Student> = ReDao.query<Student> {
    where {
        `in`(
            Student::classId, QueryWrapper(ClassInfo::class)
                .queryColumn(ClassInfo::id)
                .where {
                    `in`(ClassInfo::grade, 1, 2)
                })
    }
}

使用子查询时,必须添加queryColumn方法指定查询的列

多表联查

//查询班级grade为1的student列表
val queryList: List<Student> = ReDao.query<Student> {
    join(ClassInfo::class, ClassInfo::id, Student::classId)
    //或者
    //join(ClassInfo::class){
    //    eq(ClassInfo::id, Student::classId)
    //}
    where {
        eq(ClassInfo::grade, 1)
    }
}

数量查询

////查询student表中id=1数据数量
val count:Int = ReDao.count<Student> { 
    where {
        eq(Student::id, 1)
    }
}

聚合查询

//总数分组查询
//查询每个班age>=7的学生总人数
val groupByCount: Map<Student, Long> = ReDao.aggregate<Student>()
    .long()
    .groupByCount(Student::id) {
        where {//查询条件
            ge(Student::age, 7)
        }
        groupBy(Student::classId) {//分组结果过滤,等效为
            //只查询总人数>=2的结果
            //等效为 GROUP BY Student.classId HAVING (COUNT(Student.id) >= 2)
            //AggregateFunctions.COUNT 总数查询
            //AggregateFunctions.SUM   和查询
            //AggregateFunctions.AVG   平均值查询
            //AggregateFunctions.MIN   最小值查询
            //AggregateFunctions.MAX   最大值查询
            ge(Student::id, 2, AggregateFunctions.COUNT)
        }
    }

groupByCount方法只能在返回的类型为long时使用

//查询每个班age最大的人
val groupByMax: Map<Student, Long> = ReDao.aggregate<Student>()
    .long()//支持int()、long()、float()、double()四种基本数据
    .groupByMax(Student::age) {//支持groupByMax(),groupByMin(),groupByAvg(),groupBySum()四种函数
        groupBy(Student::classId)
    }
//age最大值
val max: Long   = ReDao.aggregate<Student>().long().max(Student::age)
//min最小值
val min: Int    = ReDao.aggregate<Student>().int().min(Student::age)
//avg平均数
val avg: Double = ReDao.aggregate<Student>().double().avg(Student::age)
//sum和
val sum: Float  = ReDao.aggregate<Student>().float().sum(Student::age)

API

ReDao.query<QueryClassName> {
    join(JoinClassName::class) {//join操作符 join JoinClassName
        //on JoinClassName.column = QueryClassName.column
        eq(JoinClassName::column, QueryClassName::column)
    }
    where {
        and {//and运算符 where中第一个and、or运算符不生效
            eq(column, xxx)                         // column = xxx
            ne(column, xxx)                         // column <> xxx
            like(column, xxx)                       // column like %xxx%
            likeRight(column, xxx)                  // column like xxx%
            likeLeft(column, xxx)                   // column like %xxx
            notLike(column, xxx)                    // column not like %xxx%
            notLikeRight(column, xxx)               // column not like xxx%
            gt(column, xxx)                         // column > xxx
            ge(column, xxx)                         // column >= xxx
            lt(column, xxx)                         // column < xxx
            le(column, xxx)                         // column <= xxx
            isNull(column)                          // column is null
            isNotNull(column)                       // column is not null
            `in`(column, 1, 2, 3)                   // column in (1,2,3)
            notIn(column, 1, 2, 3)                  // column not in (1,2,3)
            between(column, 1, 10)                  // column between 1 and 10  1<=column<=10
            notBetween(column, 1, 10)               // column not between 1 and 10
            //使用列函数
            //字符串函数
            eq(column, xxx, StringFunctions.UPPER)  //将字符串转换为大写    upper(column) = xxx
            eq(column, xxx, StringFunctions.LOWER)  //将字符串转换为小写    lower(column) = xxx
            eq(column, xxx, StringFunctions.TRIM)   //去除字符串首尾的空格   trim(column) = xxx
            eq(column, xxx, StringFunctions.LENGTH) //返回字符串的长度      length(column) = xxx
            //数值函数
            eq(column, xxx, NumericFunctions.ROUND) //四舍五入 round(column) = xxx
            eq(column, xxx, NumericFunctions.CEIL)  //向上取整 ceil(column) = xxx
            eq(column, xxx, NumericFunctions.FLOOR) //向下取整 floor(column) = xxx
            eq(column, xxx, NumericFunctions.ABS)   //绝对值   abs(column) = xxx
            eq(column, xxx, NumericFunctions.SQRT)  //平方根   sqrt(column) = xxx
        }
        and {//and运算符
            //以下两个条件满足一项
            or {//or运算符
                eq(column, xxx)
            }
            or {//or运算符
                eq(column, xxx)
            }
        }
    }
    groupBy(xxx) {//分组 groupBy xxx
        //分组过滤 having ...
        eq(column, xxx, AggregateFunctions.COUNT)   //总数   count(column) = xxx
        eq(column, xxx, AggregateFunctions.SUM)     //和     sum(column) = xxx
        eq(column, xxx, AggregateFunctions.AVG)     //平均值  avg(column) = xxx
        eq(column, xxx, AggregateFunctions.MIN)     //最小值  min(column) = xxx
        eq(column, xxx, AggregateFunctions.MAX)     //最大值  max(column) = xxx
    }
    //分页相关
    limit(1)
    offset(1)
    //排序相关,可多字段排序
    orderByAsc(column)//升序排序
    orderByDesc(column)//降序排序
}

集成方式:

添加KSP插件

在项目下的build.gradle添加

plugins {
    id 'com.google.devtools.ksp' version '2.0.0-1.0.23' apply false
}

或者build.gradle.kts

plugins {
    id("com.google.devtools.ksp") version "2.0.0-1.0.23" apply false
}

然后,在模块级 build.gradle 文件中启用 KSP:

plugins {
    id 'com.google.devtools.ksp'
}

或者build.gradle.kts

plugins {
    id("com.google.devtools.ksp")
}

添加RoomExtension

dependencies {
    //添加RoomExtension
    implementation "io.github.sumuve:room-extension:1.0.0"
    ksp "io.github.sumuve:room-extension-compiler:1.0.0"
    //添加Room
    implementation "androidx.room:room-runtime:2.6.1"
    ksp "androidx.room:room-compiler:2.6.1"
    implementation "androidx.room:room-ktx:2.6.1"
}

或者build.gradle.kts

dependencies {
    //添加RoomExtension
    implementation("io.github.sumuve:room-extension:1.0.0")
    ksp("io.github.sumuve:room-extension-compiler:1.0.0")
    //添加Room
    implementation("androidx.room:room-runtime:2.6.1")
    ksp("androidx.room:room-compiler:2.6.1")
    implementation("androidx.room:room-ktx:2.6.1")
}

添加混淆:

-keep class com.cjj.re.dao.**{
    public static ** INSTANCE;
}
-keep class com.cjj.re.tableinfo.**{
    public static ** INSTANCE;
}

自己项目里面被Entity注解修饰的类也需要不被混淆

-keep class packageName.** {*;}  //packageName换成自己项目里的包名