范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

想要SpringDataJPAMongoDB更易用,你应该这样封装

  最近我在做一个新项目,由于我们项目组一直使用的是 MongoDB 数据库,所以新项目我就打算上 Spring Data MongoDB 尝试一下,虽然我早就用过了 Spring Data JPA,对 Spring Data 的相关 CRUD 和 动态查询的封装也比较熟悉,但是自带的封装显然不能很好的满足我们的需求,本篇带大家讲述我所遇到的问题以及解决方案。
  注: MongoRepository / JPARepository 都继承自 PagingAndSortingRepository,除了对应的数据库不同之外,功能都基本相同,所以本文的二次封装也可以用于 JPARepository 上。1. 我遇到的问题
  问题一
  在 Spring Data 中可以通过继承 MongoRepository / JPARepository 接口的方式获得 CRUD 和 分页的能力,但是这种能力也仅仅满足基础的 CRUD 操作和 分页,对于极其常用的两个操作比如:针对数据库某个字段进行更新 和 多条件查询,这个接口并没有提供。
  准确的来说,多条件查询的能力是提供了,但是非常不宜用,它必须使用你的类做为查询条件,这个类的变量名还必须和数据库表中的字段名保持一致,这可以非常简单的让我们想到使用 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 { ​ } 复制代码
  现在既然我们要对 Repository 进行增强,就需要再抽象出一个类,作为我们新的基类,之后的自己的业务类需要继承这个接口,而非原来的 MongoRepository 接口,当然,我们这个新的基类接口还会去继承 MongoRepository 接口,然后在接口中定义我们需要的新操作即可:@NoRepositoryBean interface BaseMongoRepository : MongoRepository { ​     fun listAll(condition: Criteria, pageable: Pageable): Page ​     fun updateById(id: ID, update: Update): Long } 复制代码
  我创建了一个新的接口:BaseMongoRepository,用它来继承 MongoRepository,接着定义我们需要的扩展的一些方法,这里我扩展类了两个方法:新的多条件分页方法和新的更新接口。
  其中 listAll 方法的第一个参数 Criteria 是 Spring Data 已经给我们提供好的类,它广泛运用于 MongoTemplate 里面,毕竟这层 CRUD 的封装底层其实还是 MongoTemplate 来操作。
  除了继承接口外,我们还需要对这两个方法进行实现,再创建一个 BaseMongoRepository 的实现类去继承 MongoRepository 的实现类——SimpleMongoRepository:class BaseMongoRepositoryClass(     private val metadata: MongoEntityInformation,     private val mongoOperations: MongoOperations ) :     SimpleMongoRepository(metadata, mongoOperations), BaseMongoRepository { ​     private val clazz: Class = metadata.javaType ​     override fun listAll(condition: Criteria, pageable: Pageable): Page {         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) {     runApplication(*args) } 复制代码
  指定一下 repositoryBaseClass,这样生成动态代理的时候会以这个类为基类,我们动态代理类也就具有了我们定义的两个方法的能力了,使用中和原来的一样,只不过继承的接口不同罢了:interface UserRepository : BaseMongoRepository { ​ } 复制代码
  到这一步,我们可以完成这个效果:    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()) return "_id"     return this.findAnnotation()?.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 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

跟着成功学大师控制自己,走向成功自控力是一个人可以成功的关键,只有能控制自己的人才有可能控制别人。对,如果一个人不能很好的控制自我,他就会被轻易的打败。如果控制不好自己,就很容易被情绪控制,自己的感觉去做事,这样月亮与六便士8句话告诉我们,不忠于自己,一辈子都不会快乐马克吐温有一句名言人生最重要的两天,是你出生的那天,和你明白你为何活着的那天。出生的那天,我们有了生命,知道自己为何活着的那天,我们成了真正的自己。这两天一天是生命的新生,另一天是夜读别辜负那个爱你的人爱,值得珍惜第021期有人说,人之至福,是确信有人爱着你。生命因爱而美好,生活因情而温暖。若能一直活在爱中,有情相伴到老,此生幸事,莫过于此。但有时候,我们很容易犯习以为常的错,把怎么办我想你了怎么办我想你了闭上眼睛触摸不到你睁开眼睛根本没有你每时每刻每分每秒都在想你好想在我抬头的一瞬间看到你你能出现在我面前吗好想给你打个视频看看你好想在半梦半醒中叫一声亲爱的好想在半梦半散文叶子叶子李广聚叶子大小不等,形状各异,颜色也不尽相同有的四季常青有的春绿秋黄也有红得像团火更有美得像朵花。但在这千差万别的之中,它的作用却又是相同的。在春天里,叶子的淡黄葱绿给大地带来相思挂月,风归苍凉灯下望着远方,秋色淡淡。薄衣微凉,迎风而感伤,思旧日,无言盼往事,人间悲欢又是一秋。来晚思绪多,看寒烟,袅袅梦故人。有你,有我,在这荒凉的月色下,与云书书的相思二字,散落在梦外的江美国智库从中国购买半导体,装备到美国武器中,不靠谱半导体(通常称为微芯片或简称为芯片)已成为当代国力的核心竞争力之一。正如半导体领域的科学家们所指出的那样,我们正生活在一场夺取全球主导地位的芯片战争当中。谁能成为控制最先进半导体技最新研究细菌感染已成为全球第二大死因,2019年有18的死亡与此相关柳叶刀(TheLancet)发表一项最新研究,是首个针对33种细菌性病原体和11种感染类型的全球死亡率估计。分析表明,2019年有770万例死亡与33种常见细菌感染有关,仅其中的五这个黑五,亚马逊被中国大厂围攻作者麻吉编辑宋函一年一度的黑五大促已经启幕,对多年占据美国黑五榜首的亚马逊来说,对手正在悄悄变多。今年9月,拼多多Temu登陆美国,半个月时间即拿下安卓商店和GooglePlay商市场占有率突破50,自主品牌囊括10月销量前三今年十月,随着芯片断供的影响逐步消除,物流和供应链的持续改善,促销力度继续保持在高位运行,车市保持着同比较大幅度的增长势头。据中国汽车流通协会汽车市场研究分会(乘联会)最新零售销量中国牛市开启收益率回归!高盛2023年全球十大主题(下)针对2023年全球十大主题前五大主题,高盛认为,美联储将在2023年到来之前就开始放缓加息步伐,但将延长加息周期。在此过程中,美国经济显示出持续的韧性,能够使其避免陷入全面衰退。后
怀孕几个月生产?怀孕几个月生产?怀孕一般10个月生产,医学上常以周为单位,正常孕期为40周,十个月。预产期也是从末次月经第1天开始,向后推算40周得到的。医学上,从最后1次正常月经第1天起到分娩时5G手机红米k30和红米10X选哪个?数码新风暴,带你聊数码!5G手机红米K30和红米10X选择哪个好?在红米10X的发布会上,红米梳理了4和品牌系列,从高到底分别是K系列,X系列,note系列以及入门的数字N系列。可各位买过二手手机没?是一种什么样的体验?感谢您的阅读!我的第1款安卓手机就是二手手机,我的第1款还是手机的名字叫做摩托罗拉MB200。这是我的第1款iPhone手机,当时使用的具体情况,其实是因为想要体验一下安卓系统,同VIVOZ5手机怎么样?vivoZ5这款手机怎么样,今天就从我个人方面来简单评价一下这款手机。价格方面vivo官方商城Z5共五个不同的存储版本分别为(664GB)(6128GB)(6256GB)(梦幻西游科比布莱恩特和克里斯保罗联手有多强?个人是这么认为的。没有发生的交易,只能当谈资罢了。先谈谈科比的技术特点吧,科比既可以打无球,也可以打有球,只要科比放下身段好好磨合,两个联手创佳绩是没问题的,也许科比的冠军可以和乔民国时存在的姨太太问题,建国后咋解决的?那些姨太太都去哪了?建国前夕,达官贵人逃亡台湾,大多携正房丶元配走,某书写一个国军将领曾救下一个风尘女子,给他找了一个房子金屋藏娇。在逃亡前和风尘女告别,风尘女讲道他平时文温尔雅,临走时也好聚好散。老你知道紫苏有哪些功效吗?你知道紫苏有哪些功效吗?以前的野菜人们嫌弃,现在的野菜可以做菜,而且美味改头换面。现在的野菜可不比当年了,现在的科技越来越发达,所以就发现了许多野菜的价值,越来越多以前被人们嫌弃的枫林谷和老边沟哪个更好?看枫叶已经疲劳了,我觉得都人太多就没有啥意思了,每到十一高峰出行,人头攒动,基本就是照人山人海了,走一圈红叶大道就行了,何必花钱去在沟里呢。我其实很喜欢原生态的沟渠,不喜欢人造的景摄影师在旅行摄影拍摄中怎样避免拍出游客照?十一期间我去了一趟曲阜的孔庙,同样是人山人海,那么怎么样避开游客拍出与众不同的照片呢?下面我用一些实例做一个说明。首先是拍摄角度,尤其是仰拍,可以避免游客进入镜头。比如下面这张照片你用的跑步软件是什么?在用的有五款产品,华为运动健康,keep,咕咚,悦跑圈,悦动圈。手机是华为的,自带的华为运动健康APP,也可以连接华为运动手表,手环来使用keep,安装了好久了,以前主要运来健身运怎样查华为荣耀手机生产日期?你的手机是哪一天被生产出来的呢?绝大部分的人,都不知道的一个问题?很多人都会玩手机,但是绝大部分人都不知道自己的手机,到底是哪一天生产的?今天就来给大家分享一个,平时我们该怎样去查