首页 » Web前端 » phpmongodb封装技巧_想要 Spring Data JPAMongoDB 更易用你应该这样封装

phpmongodb封装技巧_想要 Spring Data JPAMongoDB 更易用你应该这样封装

访客 2024-12-06 0

扫一扫用手机浏览

文章目录 [+]

注: MongoRepository / JPARepository 都继续自 PagingAndSortingRepository,除了对应的数据库不同之外,功能都基本相同,以是本文的二次封装也可以用于 JPARepository 上。

1. 我碰着的问题

问题一

phpmongodb封装技巧_想要 Spring Data JPAMongoDB 更易用你应该这样封装

在 Spring Data 中可以通过继续 MongoRepository / JPARepository 接口的办法得到 CRUD 和 分页的能力,但是这种能力也仅仅知足根本的 CRUD 操作和 分页,对付极其常用的两个操作比如:针对数据库某个字段进行更新 和 多条件查询,这个接口并没有供应。

phpmongodb封装技巧_想要 Spring Data JPAMongoDB 更易用你应该这样封装
(图片来自网络侵删)

准确的来说,多条件查询的能力是供应了,但是非常不宜用,它必须利用你的类做为查询条件,这个类的变量名还必须和数据库表中的字段名保持同等,这可以非常大略的让我们想到利用 PO 类当作这个查询条件。

但是在有些规范中,PO 类该当是一个拥有全参布局器的不可变类,这使得先创建这个类然后对应的查询字段进行赋值的操作变得不可行,这里我举一个大略的例子,我拥有一个数据表的映射工具:User,这便是俗称的 PO。

@Document("user")class User ( @Id val id : String,​ val account : String,​ val pwd : String,​ val name : String,)复制代码

然后我如果想要单独更新 name 这个字段时,我须要拥有全体 User 工具中的所有属性,由于 Repository 接口所供应的能力是把新增操作和更新操作放在一起的 (save 方法),每次更新都是所有字段的更新,这是我不愿意看到的,也是极其麻烦的。

接着便是多条件查询的问题,我们先来看下如果我想要利用多条件查询,它的参数是什么:

可以明显看到是一个叫 Example 的工具,如果我想利用,它该当是这样的:

fun test() { val user = CssUser() user.name = "我要查询的参数详细值"​ userRepository.findAll(Example.of(user)) }复制代码

这里我定义了一个 CssUser 去当它的查询条件的类,而且这个类和 User 类的内容险些一样,由于我的 User 类是一个全参布局器没办法直接创建一个空工具进行赋值,以是我不得不创建一个 CssUser 去当查询条件的类,对付程序员来讲,这很烦。

我想要的效果是什么样的呢?是这样的:

fun test() {​ userRepository.listAll(Criteria .where("account").`is`("admin") .and("name").`is`("你的名字") )​ }复制代码

通过 lambda 的办法直接获取到某个属性的名字,然后作为查询变量,然后随着链式调用可以随便在里面加上各样的查询条件,例子中的 Criteria 类是 Spring 已经为我们做好的,但是 Repository 接口并没有供应它,以是我们须要一层封装。

问题二

从上面的例子中我们可以看到在组装查询条件时,须要硬编码进去字段名,这对付程序员来说,是很烦的。

以是我们该当利用 lambda 的特性,帮助我们去获取某一个类的字段名,常日是 PO,由于它和数据库属性是逐一对应的,整体要达到的有点像 Mybatis-PLus 的效果,大概是这样:

fun test() {​ userRepository.listAll(Criteria .where(CssUser::account.mongoFiled()).`is`("admin") .and(CssUser::name.mongoFiled()).`is`("你的名字") )​ }复制代码

当然我的这个效果还没有 Mybatis-PLus 的效果好,它可以直接省略 .mongoFiled() 这个操作,这是由于我只加了三四行代码就能达到这个效果,对我而言够用了,而 Mybatis-PLus 则是有一套干系支持。

虽然我这是 Kotlin 示例,但随后也会给出 Java 语法中的干系思路。

2. Repository 接口封装

先来谈谈对 CRUD 的增强,正常情形下,我们只须要利用一个接口继续 MongoRepository 接口,然后 Spring Data 就会帮我们天生一个动态代理类,并声明为 Bean,直接注入就可以利用了,就像这样(代码中的 :语法是继续的意思):

interface UserMongoRepository : MongoRepository<User, String> {​}复制代码

现在既然我们要对 Repository 进行增强,就须要再抽象出一个类,作为我们新的基类,之后的自己的业务类须要继续这个接口,而非原来的 MongoRepository 接口,当然,我们这个新的基类接口还会去继续 MongoRepository 接口,然后在接口中定义我们须要的新操作即可:

@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 0 return mongoOperations.updateFirst( Query().addCriteria(Criteria.where("_id").`is`(id)), update, metadata.collectionName ).modifiedCount }​​}复制代码

个中 BaseMongoRepositoryClass 须要两个参数,这两个参数直接从 SimpleMongoRepository 里面拷贝过来然后通过布局再通报给 SimpleMongoRepository 即可,反正都是从自动注入里面来。

两个变量大略讲解一下都是什么意思:

MongoEntityInformation:这个是 MongoEntity 的元信息,便是最上面用 @Document 表明标记的 PO 类的元信息,我们可以通过它拿到 PO 类的类型和数据表的名字。
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 调用下就可以再调用这个方法了,接着来看看方法内容。

首先判断了是否存在 ID 表明,这个 ID 表明是用来标识 Mongo 的主键属性的表明,这种表明标识的变量在数据库中统一叫做 "_id",以是这里我也返回这个名字。
接着判断是否存在 Field 表明,它是用来标识数据库字段和类变量不一样的情形,如果涌现这种情形,我们利用表明所标识的字段名。
末了,以上两种情形打消后,我们直策应用这个字段的名字。

这样就可以达到如下效果了:

fun test() {​ userRepository.listAll(Criteria .where(CssUser::account.mongoFiled()).`is`("admin") .and(CssUser::name.mongoFiled()).`is`("你的名字") )​ }复制代码

接着我们可以来说说 Java 的做法,首先也须要一个方法通过 lambda 拿到字段名,这个方法网上有很多我不再赘述,但是拿到之后该怎么办呢?

你当然可以直接通过工具类的静态方法去拿,就像这样:

fun test() {​ userRepository.listAll(Criteria .where(Util.getName(CssUser::account).`is`("admin") .and(Util.getName(CssUser::name).`is`("你的名字") )​ }复制代码

可能到这一步看起来还是略微不雅观观,追求极致的小伙伴这个时候就可以再度发挥封装的本色,将 Criteria 类封装出一个新的查询条件类,比如叫 Condition,然后将 Criteria 装在里面再封装一下查询时的干系常用方法,就像这样(把稳此处的 Funtion 入参只是一个例子,实际该当是泛型):

public class Condition { private Criteria criteria = new Criteria();​ public Condition where(Function<String, String> function, String value) { criteria.andOperator(Criteria.where(Util.getName(function)).is(value)); return this; }}复制代码

除了 where 方法你还可以连续封装 gt、lt、or 等常用方法,并且它们还能形成链式调用,终极的效果是这样的:

public static void main(String[] args) { Criteria criteria = new Condition() .where(CssUser::getName, "你的名字") .where(CssUser::getAccount, "admin"); }复制代码

是不是更优雅了呢?

4. 末了

本日是满满的技能干货,希望 Get 到新技能的小伙伴可以积极的点赞,有什么问题都可以再评论区留言,下篇见。

链接:https://juejin.cn/post/7168133740093243423

标签:

相关文章