抄来的ORM(

源起

泡泡:"温两碗酒,要一碟茴香豆"

寒雨:"你一定又偷人家东西了!"

泡泡:"你怎么这样凭空污人清白……"

泡泡:"窃 Code,不能叫偷,读书人的事,能叫偷么?"

(逃

由于接触数据库并不多,所以我操作数据库时大多数还是写个函数用SQL去操作,对 orm 的理解只处于"这是复杂的项目才会用到的技术"

不过当我看到这样的设计的时候

// 偷自 ColdRain
data class ExampleEntity(
    // PrimaryKey 不用写option
    @PrimaryKey(autoGenerate = true)
    @Column(name = "id", type = ColumnTypeSQL.INT)
    val id: Int? = null,
    @Column(name = "type", type = ColumnTypeSQL.TEXT, options = [ColumnOptionSQL.NOTNULL])
    val type: String,
    @Column(name = "user", type = ColumnTypeSQL.TEXT, options = [ColumnOptionSQL.NOTNULL])
    val user: String,
    @Column(name = "user", type = ColumnTypeSQL.TEXT, def = "null")
    val data: String?
)

interface ExampleDAO : IDao<ExampleEntity> {
    @Query("SELECT * FROM {table}")
    fun queryAll(): List<ExampleEntity>

    @Insert
    fun insert(entity: ExampleEntity)

    @Query("DELETE FROM {table}")
    fun delete()

    @Query("SELECT WHERE id > {id} FROM {table}")
    fun querySome(id: Int): List<ExampleEntity>

    // 使用TabooLib DSL语句控制
    @DSL
    fun selectUser(name: String): ExampleEntity? {
        return table.workspace(datasource) {
            select { where { "user" eq name } }
        }.firstOrNull {
            adaptResultSet()
        }
    }
}

object AppDatabase {
    /**
     * 也可通过ORMBuilder#buildFromConf(ConfigurationSection, String)直接构建
     */
    private val builder by lazy {
        ORMBuilder.newBuilder()
            .host("localhost")
            .port(3306)
            .user("root")
            .password("root")
            .database("database")
            .buildHost()
    }


    val exampleTableDao by lazy {
        // 表名 DAO类
        builder.build("exampleTable" ,ExampleDAO::class.java)
    }
}

?这好啊,DAO 都对象化了 不行,也整一个

过程

起初想用 golang 去实现一个类似这样的,毕竟 gorm 操作数据对象时还是函数式

但是发现

Go 没有注解!或者说,Go 函数没有注解,只有结构体那的 tag 可以反射拿数据

没有注解?反射 DAO 拿个啥?

解决思路

很显然比较困难,以后再来

然后就写了一个 Java 版本,接入了 nk 这边挺多人用的 EasyMySQL

地址: https://github.com/SmallasWater/EasyMySQL#orm-部分

遇到的一些坑

DAO 接口被代理后生成的一个对象,其中的 DAO 定义的 default 方法会被重写调用 invoke,然鹅 DAO 是一个接口没办法实例化,default 又被覆盖了,导致找不到对象执行 default,最终用动态字节码解决了(能用就行,谁在乎性能,逃

javassist 自带的 toClass 方法使用的是线程上下文的类加载器,这会导致好不容易构建好的字节码继承了一个"其他类型的 DAO"(jvm 判断类的类型取决于包名,类名和类加载器,前面两个都一样,但加载它们的类加载器不一样也会导致这两个类不是同一个类型)

javassist 提供的一个类加载器 Loader 使用的是自己的 classPool,里面找不到就抛异常,这打破了父类委托机制,虽然使用这个 Loader 可以解决 DAO 不一致问题,但是 DAO 里面要是引用了本项目其他类型的对象的话,会造成外面 new 的对象写不进动态字节码生成的类里,外面的类可以理解成 Loader 的父加载器加载的...而 Loader ban 掉了父类委托,一开始的方案是自己写个 ClassLoader 为 Loader "重建父类委托",写了老长一串,后来发现 toClass 里面可以传入一个 ClassLoader...(Loader 爬