二次封装 Spring Data JPA/MongoDB,打造更易用的数据访问层( 二 )


@NoRepositoryBeaninterface BaseMongoRepository<T, ID> : MongoRepository<T, ID> {?fun listAll(condition: Criteria, pageable: Pageable): Page<T>?fun updateById(id: ID, update: Update): Long}复制代码我创建了一个新的接口:BaseMongoRepository,用它来继承 MongoRepository,接着定义我们需要的扩展的一些方法,这里我扩展类了两个方法:新的多条件分页方法和新的更新接口 。
其中 listAll 方法的第一个参数 Criteria 是 Spring Data 已经给我们提供好的类,它广泛运用于 MongoTemplate 里面,毕竟这层 CRUD 的封装底层其实还是 MongoTemplate 来操作 。
除了继承接口外,我们还需要对这两个方法进行实现,再创建一个 BaseMongoRepository 的实现类去继承 MongoRepository 的实现类——SimpleMongoRepository:
class BaseMongoRepositoryClass<T, ID>(private val metadata: MongoEntityInformation<T, ID>,private val mongoOperations: MongoOperations) :SimpleMongoRepository<T, ID>(metadata, mongoOperations), BaseMongoRepository<T, ID> {?private val clazz: Class<T> = metadata.javaType?override fun listAll(condition: Criteria, pageable: Pageable): Page<T> {val list = mongoOperations.find(Query(condition).with(pageable), this.clazz, metadata.collectionName)?return PageableExecutionUtils.getPage(list, pageable) {mongoOperations.count(Query(condition).limit(-1).skip(-1),clazz,metadata.collectionName)}}?override fun updateById(id: ID, update: Update): Long {if (update.updateObject.isEmpty()) return 0return mongoOperations.updateFirst(Query().addCriteria(Criteria.where("_id").`is`(id)),update,metadata.collectionName).modifiedCount}??}复制代码其中 BaseMongoRepositoryClass 需要两个参数,这两个参数直接从 SimpleMongoRepository 里面拷贝过来然后通过构造再传递给 SimpleMongoRepository 即可,反正都是从自动注入里面来 。
两个变量简单讲解一下都是什么意思:

  1. MongoEntityInformation:这个是 MongoEntity 的元信息,就是最上面用 @Document 注解标记的 PO 类的元信息,我们可以通过它拿到 PO 类的类型和数据表的名字 。
  2. MongoOperations:MongoTemplate 的实现类,这个我想不用多谈 。
接着就是方法实现,方法实现就是就是通过 MongoTemplate 操作了这个这个方法要做什么事,代码都比较简单因为不包含什么逻辑,熟悉 MongoTemplate 的一眼就可看懂 。
接下来就是最重要的一步,没有这一步一切都是白费,还会造成项目启动失败,那就是把这个新的基类告诉 Spring,这是新的基类,你可以在项目的入口中加上这一句注解:
@EnableMongoRepositories(basePackages = ["com.xxx.*"], repositoryBaseClass = BaseMongoRepositoryClass::class)class AdminApplication?fun main(args: Array<String>) {runApplication<AdminApplication>(*args)}复制代码指定一下 repositoryBaseClass,这样生成动态代理的时候会以这个类为基类,我们动态代理类也就具有了我们定义的两个方法的能力了,使用中和原来的一样,只不过继承的接口不同罢了:
interface UserRepository : BaseMongoRepository<User, String> {?}复制代码到这一步,我们可以完成这个效果:
fun test() {?userRepository.listAll(Criteria.where("account").`is`("admin").and("name").`is`("你的名字"))?}复制代码3. 实体类变量进行 lambda 封装接下来是对实体变量进行 lambda 封装,这个东西我觉得可以分为 Kotlin 和 Java 两个版本来说,两者各有千秋 。
先来说说Kotlin,因为 Kotlin 自身的语言特性的关系,实现起来比较简单,但也会拖一个尾巴,Kotlin 具有一个扩展函数的能力,简单点说就是直接给某个类加上一些自定义方法,比如 String 我们可以在不继承的情况下直接给 String 类加上一个新的方法,然后它就会出现在 String 对象可调用的函数列表中 。
所以我们如果想要 User::account.mongoFiled() 这种效果,就得先知道 User::account 返回值是什么,在 Kotlin 中,它的返回值是一个 KProperty 类对象,那么我们直接给这个类加上扩展如下:
fun KProperty<*>.mongoFiled(): String {if (this.hasAnnotation<Id>()) return "_id"return this.findAnnotation<Field>()?.run {this.name.ifEmpty { this@mongoFiled.name }} ?: this.name}复制代码这样在 lambda 调用下就可以再调用这个方法了,接着来看看方法内容 。
  1. 首先判断了是否存在 ID 注解,这个 ID 注解是用来标识 Mongo 的主键属性的注解,这种注解标识的变量在数据库中统一叫做 "_id",所以这里我也返回这个名字 。


    推荐阅读