Spring Data的MongoTemplate和MongoRepository有什么区别?


96

我需要编写一个应用程序,可以使用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?两者都是成熟的,还是其中一个比另一个缺少更多的功能?

Answers:


130

“方便”和“使用功能强大”在某种程度上是相互矛盾的目标。到目前为止,存储库比模板方便得多,但是后者当然可以为您提供对执行内容的更细粒度的控制。

由于存储库编程模型可用于多个Spring Data模块,因此您可以在Spring Data MongoDB 参考文档的常规部分中找到有关它的更多详细文档

TL; DR

我们通常建议采用以下方法:

  1. 从存储库摘要开始,仅使用查询派生机制或手动定义的查询声明简单查询。
  2. 对于更复杂的查询,请向存储库添加手动实现的方法(如此处所述)。供实现使用MongoTemplate

细节

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

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

    interface CustomUserRepository {
    
      List<User> yourCustomMethod();
    }
  2. 为此类添加一个实现,并遵循命名约定以确保我们可以找到该类。

    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
      }
    }
  3. 现在,让您的基本存储库界面扩展自定义界面,基础结构将自动使用您的自定义实现:

    interface UserRepository extends CrudRepository<User, Long>, CustomUserRepository {
    
    }

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


您好奥利弗,这实际上是行不通的。spring-data尝试使用自定义名称自动生成查询。yourCustomMethod()。它将说“您的”不是域类中的有效字段。我遵循了手册,并再次检查spring-data-jpa-examples的工作方式。没运气。我将自定义接口扩展到存储库类后,spring-data始终尝试自动生成。唯一的区别是我使用的是MongoRepository,而不是CrudRepository,因为我现在不想使用Iterators。如果您有任何提示,将不胜感激。
Christopher Armstrong

10
最常见的错误是错误地将实现类命名为:如果调用YourRepository了基本repo接口,则必须命名实现类YourRepositoryImpl。是这样吗 如果是这样,我很高兴在GitHub或类似网站上看一个示例项目…
Oliver Drotbohm

5
嗨,奥利弗(Oliver),您假设的Impl类名称错误。我调整了名称,看起来现在可以正常工作了。非常感谢您的反馈。能够以这种方式使用不同种类的查询选项真的很酷。深思熟虑!
Christopher Armstrong

这个答案不太清楚。在通过此示例完成所有操作后,我陷入了这个问题:stackoverflow.com/a/13947263/449553。因此,命名约定比本示例看起来更严格。
msangel 2013年

1
#2上的实现类被命名为错误:应该是CustomUserRepository和不是CustomerUserRepository
哥塔

28

这个答案可能有点延迟,但是我建议避免使用整个存储库路径。您获得的具有重大实用价值的实现方法很少。为了使它起作用,您会遇到Java配置的废话,您可以花数天甚至数周的时间而无需在文档中提供太多帮助。

相反,请选择 MongoTemplate条路线,并创建自己的数据访问层,这使您摆脱了Spring程序员面临的配置噩梦。MongoTemplate对于那些能够灵活地构建自己的类和交互的工程师来说,它确实是一个救星,因为它具有很大的灵活性。结构可以是这样的:

  1. 创建一个 MongoClientFactory将在应用程序级别运行类,并为您提供一个MongoClient对象。您可以将其实现为Singleton或使用Enum Singleton(这是线程安全的)
  2. 创建一个数据访问基类,您可以从中继承每个域对象的数据访问对象。基类可以实现一种用于创建MongoTemplate对象的方法,您可以将此类的特定方法用于所有数据库访问
  3. 每个域对象的每个数据访问类都可以实现基本方法,也可以在基类中实现它们
  4. 然后,Controller方法可以根据需要调用数据访问类中的方法。

您好@rameshpa我可以在同一个项目中同时使用MongoTemplate和存储库吗?。是否可以使用
Gauranga

1
您可以,但是实现的MongoTemplate与数据库的连接将不同于存储库使用的连接。原子性可能是一个问题。另外,如果您有排序需求,我也不建议在一个线程上使用两个不同的连接
rameshpa

23

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

  • MongoTemplate提供“原子”外的开箱操作 updateFirstupdateMultifindAndModifyupsert...,让你可以在一个单一的操作修改文档。Update这些方法使用的对象还允许您仅定位相关字段
  • MongoRepository只给你基本的CRUD操作 findinsertsavedelete,其作品包含的POJO 的所有字段。这迫使您要么分几个步骤find来更新文档(1. 要更新的文档,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 }因为每个线程仅访问DB一次,并且仅更改了指定字段。

与此相同的情况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操作最后命中数据库。
(注意:即使我们按照注释中的建议使用Spring Data的@Version注释,也不会有太大变化:其中一个save操作将抛出OptimisticLockingFailureException,最终文档仍将是上述之一,并且仅更新一个字段而不是两个字段。 )

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


好点/例子。但是,可以使用@Version避免您的比赛条件示例和不良结果,以防止出现这种情况。
Madbreaks '18

@Madbreaks您能否提供有关实现此目标的任何资源?任何官方文件可能吗?
卡西凯扬


1
@Madbreaks感谢您指出这一点。是,@Version将“避免”第二个线程覆盖第一个线程保存的数据,即“避免”,因为它将丢弃更新并抛出一个更新OptimisticLockingFailureException。因此,如果您希望更新成功,则必须实现重试机制。MongoTemplate使您可以避免整个情况。
walen
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.