Spring Data 的 MongoTemplate 和 MongoRepository 有啥区别?

Posted

技术标签:

【中文标题】Spring Data 的 MongoTemplate 和 MongoRepository 有啥区别?【英文标题】:What's the difference between Spring Data's MongoTemplate and MongoRepository?Spring Data 的 MongoTemplate 和 MongoRepository 有什么区别? 【发布时间】:2013-06-05 05:27:18 【问题描述】:

我需要编写一个应用程序,我可以使用 spring-data 和 mongodb 进行复杂的查询。我从使用 MongoRepository 开始,但在查找示例或实际理解语法时遇到了复杂的查询。

我说的是这样的查询:

@Repository
public interface UserRepositoryInterface extends MongoRepository<User, String> 
    List<User> findByEmailOrLastName(String email, String lastName);

或使用基于 JSON 的查询,我通过反复试验尝试过,因为我没有正确理解语法。即使在阅读了 mongodb 文档之后(由于语法错误导致的非工作示例)。

@Repository
public interface UserRepositoryInterface extends MongoRepository<User, String> 
    @Query("'$or':['firstName':'$regex':?0,'$options':'i','lastName':'$regex':?0,'$options':'i']")
    List<User> findByEmailOrFirstnameOrLastnameLike(String searchText);
 

通读所有文档后,似乎mongoTemplate 的文档记录要好于MongoRepository。我指的是以下文档:

http://static.springsource.org/spring-data/data-mongodb/docs/current/reference/html/

你能告诉我用什么更方便、更强大吗? mongoTemplate 还是 MongoRepository?两者都一样成熟还是其中一个比另一个缺少更多功能?

【问题讨论】:

【参考方案1】:

“方便”和“使用强大”在某种程度上是相互矛盾的目标。存储库比模板方便得多,但后者当然可以让您更精细地控制要执行的内容。

由于存储库编程模型可用于多个 Spring Data 模块,您可以在 Spring Data MongoDB reference docs 的常规部分找到更深入的文档。

TL;DR

我们一般推荐以下方法:

    从存储库摘要开始,只需使用查询派生机制或手动定义的查询声明简单查询。 对于更复杂的查询,将手动实现的方法添加到存储库(如此处所述)。对于实施使用MongoTemplate

详情

对于您的示例,这看起来像这样:

    为您的自定义代码定义一个接口:

    interface CustomUserRepository 
    
      List<User> yourCustomMethod();
    
    

    为这个类添加一个实现并遵循命名约定以确保我们可以找到这个类。

    class UserRepositoryImpl implements CustomUserRepository 
    
      private final MongoOperations operations;
    
      @Autowired
      public UserRepositoryImpl(MongoOperations operations) 
    
        Assert.notNull(operations, "MongoOperations must not be null!");
        this.operations = operations;
      
    
      public List<User> yourCustomMethod() 
        // custom implementation here
      
    
    

    现在让您的基础存储库接口扩展自定义接口,基础架构将自动使用您的自定义实现:

    interface UserRepository extends CrudRepository<User, Long>, CustomUserRepository 
    
    
    

通过这种方式,您基本上可以选择:所有易于声明的内容都进入UserRepository,所有更好地手动实现的内容都进入CustomUserRepository。自定义选项记录在 here。

【讨论】:

嗨奥利弗,这实际上是行不通的。 spring-data 尝试从自定义名称中自动生成查询。你的自定义方法()。它会说“你的”不是域类中的有效字段。我按照手册进行操作,并仔细检查了 spring-data-jpa-examples 的操作方式。没有运气。一旦我将自定义接口扩展到存储库类,spring-data 总是会尝试自动生成。唯一的区别是我使用的是 MongoRepository 而不是 CrudRepository,因为我现在不想使用迭代器。如果您有提示,将不胜感激。 最常见的错误是错误的实现类命名:如果你的基础repo接口被称为YourRepository,实现类必须命名为YourRepositoryImpl。是这样吗?如果是这样,我很乐意在 GitHub 等上查看示例项目…… 嗨,Oliver,Impl 类的名称如您所料错误。我调整了名称,它看起来现在可以工作了。非常感谢您的反馈。能够以这种方式使用不同类型的查询选项真的很酷。深思熟虑! 这个答案不是很清楚。通过这个例子做完所有事情后,我遇到了这个问题:***.com/a/13947263/449553。所以命名约定比这个例子看起来更严格。 #2 上的实现类命名错误:应该是CustomUserRepository 而不是CustomerUserRepository【参考方案2】:

FWIW,关于多线程环境中的更新:

MongoTemplate 提供“原子”开箱即用操作 updateFirstupdateMultifindAndModifyupsert... 允许您在单个文件中修改文档手术。这些方法使用的Update 对象还允许您仅定位相关字段MongoRepository 只为您提供基本的 CRUD 操作 findinsertsavedelete,它们适用于包含 所有字段的 POJO。这迫使您分几个步骤更新文档(1.find 要更新的文档,2. 从返回的 POJO 中修改相关字段,然后 3.save 它),或者定义您自己的更新查询手动使用@Query

在多线程环境中,例如具有多个 REST 端点的 Java 后端,单方法更新是可行的方法,以减少两个并发更新覆盖彼此更改的机会。

示例:给定这样的文档: _id: "ID1", field1: "a string", field2: 10.0 和两个不同的线程同时更新它...

MongoTemplate 看起来有点像这样:

THREAD_001                                                      THREAD_002
|                                                               |
|update(query("ID1"), Update().set("field1", "another string")) |update(query("ID1"), Update().inc("field2", 5))
|                                                               |
|                                                               |

并且文档的最终状态始终为 _id: "ID1", field1: "another string", field2: 15.0 ,因为每个线程仅访问数据库一次并且仅更改了指定的字段。

而与MongoRepository 相同的情况如下所示:

THREAD_001                                                      THREAD_002
|                                                               |
|pojo = findById("ID1")                                         |pojo = findById("ID1")
|pojo.setField1("another string") /* field2 still 10.0 */       |pojo.setField2(pojo.getField2()+5) /* field1 still "a string" */
|save(pojo)                                                     |save(pojo)
|                                                               |
|                                                               |

最终文档是 _id: "ID1", field1: "another string", field2: 10.0 _id: "ID1", field1: "a string", field2: 15.0 ,具体取决于哪个save 操作最后命中数据库。(注意:即使我们按照cmets 中的建议使用Spring Data's @Version annotation,不会有太大变化:save 操作之一将抛出OptimisticLockingFailureException,最终文档仍将是上述之一,仅更新一个字段而不是两个字段。)

所以我会说 MongoTemplate 是一个更好的选择,除非您有一个非常精细的 POJO 模型或出于某种原因需要 MongoRepository 的自定义查询功能。

【讨论】:

好点/例子。但是,使用 @Version 可以避免您的竞争条件示例和不希望的结果,以防止这种情况发生。 @Madbreaks 您能否提供有关如何实现这一目标的任何资源?可能有任何官方文档吗? 关于@Version注解的Spring数据文档:docs.spring.io/spring-data/mongodb/docs/current/reference/html/… @Madbreaks 感谢您指出这一点。是的,@Version 会“避免”第二个线程覆盖第一个线程保存的数据——“避免”的意思是它会丢弃更新并抛出OptimisticLockingFailureException。因此,如果您希望更新成功,则必须实施重试机制。 MongoTemplate 可以让你避免整个场景。【参考方案3】:

这个答案可能有点延迟,但我建议避免使用整个存储库路线。你得到的实现的方法很少有任何很大的实用价值。为了使它工作,您会遇到 Java 配置废话,您可能会花费数天或数周的时间在文档中没有太多帮助。

相反,使用MongoTemplate 路由并创建您自己的数据访问层,让您摆脱 Spring 程序员面临的配置噩梦。 MongoTemplate 确实是工程师的救星,他们可以轻松地构建自己的课程和交互,因为它具有很大的灵活性。结构可以是这样的:

    创建一个将在应用程序级别运行的MongoClientFactory 类,并为您提供一个MongoClient 对象。您可以将其实现为单例或使用枚举单例(这是线程安全的) 创建一个数据访问基类,您可以从该基类中为每个域对象继承一个数据访问对象)。基类可以实现创建 MongoTemplate 对象的方法,您可以将特定方法用于所有 DB 访问 每个域对象的每个数据访问类都可以实现基本方法,也可以在基类中实现它们 然后控制器方法可以根据需要调用数据访问类中的方法。

【讨论】:

嗨@rameshpa 我可以在同一个项目中同时使用MongoTemplate和存储库吗?..可以使用 您可以,但您实现的 MongoTemplate 与数据库的连接与存储库使用的连接不同。原子性可能是一个问题。另外,如果您有排序需求,我不建议在一个线程上使用两个不同的连接

以上是关于Spring Data 的 MongoTemplate 和 MongoRepository 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

初探 spring data--- spring data 概述

spring-data详解之spring-data-jpa:简单三步快速上手spring-data-jpa开发

@Tailable(spring-data-reactive-mongodb) 等效于 spring-data-r2dbc

spring-data-jpa 和 spring-boot-starter-data-jpa 的区别

是否有适用于 JPA、spring-data、spring-data-rest 的通用 REST 查询语言

Spring之Redis访问(Spring-data-redis)