Sự khác biệt giữa MongoTemplate của Spring Data và MongoRepository là gì?


98

Tôi cần viết một ứng dụng mà tôi có thể thực hiện các truy vấn phức tạp bằng cách sử dụng spring-data và mongodb. Tôi đã bắt đầu bằng cách sử dụng MongoRepository nhưng phải vật lộn với các truy vấn phức tạp để tìm ví dụ hoặc để thực sự hiểu Cú pháp.

Tôi đang nói về các truy vấn như thế này:

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

hoặc việc sử dụng các truy vấn dựa trên JSON mà tôi đã thử bằng cách dùng thử và sai vì tôi không hiểu đúng cú pháp. Ngay cả sau khi đọc tài liệu mongodb (ví dụ không hoạt động do sai cú pháp).

@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);
} 

Sau khi đọc qua tất cả các tài liệu, có vẻ như đó mongoTemplatelà tài liệu tốt hơn nhiều MongoRepository. Tôi đang tham khảo tài liệu sau:

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

Bạn có thể cho tôi biết điều gì là thuận tiện và mạnh mẽ hơn để sử dụng? mongoTemplatehoặc MongoRepository? Cả hai đều trưởng thành như nhau hay một trong hai người thiếu nhiều tính năng hơn người kia?

Câu trả lời:


130

"Thuận tiện" và "mạnh mẽ để sử dụng" đang mâu thuẫn với mục tiêu ở một mức độ nào đó. Các kho lưu trữ thuận tiện hơn nhiều so với các mẫu nhưng dĩ nhiên, kho lưu trữ sau sẽ cung cấp cho bạn khả năng kiểm soát chi tiết hơn đối với những gì cần thực thi.

Vì mô hình lập trình kho lưu trữ có sẵn cho nhiều mô-đun Dữ liệu Mùa xuân, bạn sẽ tìm thấy tài liệu chuyên sâu hơn về mô hình này trong phần chung của tài liệu tham khảo Spring Data MongoDB .

TL; DR

Chúng tôi thường khuyến nghị cách tiếp cận sau:

  1. Bắt đầu với tóm tắt kho lưu trữ và chỉ cần khai báo các truy vấn đơn giản bằng cách sử dụng cơ chế dẫn xuất truy vấn hoặc các truy vấn được xác định thủ công.
  2. Đối với các truy vấn phức tạp hơn, hãy thêm các phương thức được triển khai thủ công vào kho lưu trữ (như được nêu ở đây). Đối với việc sử dụng thực hiện MongoTemplate.

Chi tiết

Đối với ví dụ của bạn, nó sẽ trông giống như sau:

  1. Xác định giao diện cho mã tùy chỉnh của bạn:

    interface CustomUserRepository {
    
      List<User> yourCustomMethod();
    }
    
  2. Thêm một triển khai cho lớp này và tuân theo quy ước đặt tên để đảm bảo chúng ta có thể tìm thấy lớp.

    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. Bây giờ hãy để giao diện kho lưu trữ cơ sở của bạn mở rộng giao diện tùy chỉnh và cơ sở hạ tầng sẽ tự động sử dụng triển khai tùy chỉnh của bạn:

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

Bằng cách này, về cơ bản bạn sẽ có được sự lựa chọn: mọi thứ dễ khai báo đều đi vào UserRepository, mọi thứ được triển khai thủ công tốt hơn sẽ đi vào CustomUserRepository. Các tùy chọn tùy chỉnh được ghi lại ở đây .


1
Xin chào Oliver, điều này thực sự không hoạt động. spring-data cố gắng tự động tạo một truy vấn từ tên tùy chỉnh. yourCustomMethod (). Nó sẽ cho biết "của bạn" không phải là trường hợp lệ trong lớp miền. Tôi đã làm theo hướng dẫn sử dụng và cũng đã kiểm tra lại cách bạn đang thực hiện với các ví dụ spring-data-jpa-. Không may mắn. spring-data luôn cố gắng tự động tạo ngay khi tôi mở rộng giao diện tùy chỉnh cho lớp kho lưu trữ. Sự khác biệt duy nhất là tôi đang sử dụng MongoRepository chứ không phải CrudRepository vì tôi không muốn làm việc với Iterator bây giờ. Nếu bạn có một gợi ý, nó sẽ được đánh giá cao.
Christopher Armstrong

11
Sai lầm phổ biến nhất là đặt tên sai lớp thực thi: nếu giao diện repo cơ sở của bạn được gọi YourRepository, thì lớp thực thi phải được đặt tên YourRepositoryImpl. Đó là trường hợp? Nếu vậy, tôi rất vui khi xem qua một dự án mẫu trên GitHub hoặc những thứ tương tự…
Oliver Drotbohm.

5
Xin chào Oliver, lớp Impl được đặt tên sai như bạn đã nghĩ. Tôi đã điều chỉnh tên và có vẻ như nó đang hoạt động. Cảm ơn rất nhiều vì phản hồi của bạn. Thật tuyệt khi có thể sử dụng các loại tùy chọn truy vấn khác nhau theo cách này. Cũng đã nghĩ kỹ!
Christopher Armstrong

Câu trả lời này không quá rõ ràng. Sau khi thực hiện mọi thứ theo ví dụ này, tôi rơi vào vấn đề này: stackoverflow.com/a/13947263/449553 . Vì vậy, quy ước đặt tên nghiêm ngặt hơn so với ví dụ này.
msangel

1
Lớp thực thi trên # 2 được đặt tên sai: nên có CustomUserRepositoryvà không CustomerUserRepository.
Cotta

28

Câu trả lời này có thể hơi chậm trễ, nhưng tôi khuyên bạn nên tránh toàn bộ tuyến đường lưu trữ. Bạn nhận được rất ít phương pháp thực hiện có giá trị thực tế lớn. Để làm cho nó hoạt động, bạn chạy vào cấu hình Java vô nghĩa mà bạn có thể mất hàng ngày và hàng tuần mà không cần trợ giúp nhiều trong tài liệu.

Thay vào đó, hãy đi theo MongoTemplatelộ trình và tạo lớp truy cập Dữ liệu của riêng bạn để giải phóng bạn khỏi cơn ác mộng cấu hình mà các lập trình viên Spring phải đối mặt. MongoTemplatethực sự là vị cứu tinh cho các kỹ sư cảm thấy thoải mái khi cấu trúc các lớp và tương tác của riêng họ vì có rất nhiều tính linh hoạt. Cấu trúc có thể giống như sau:

  1. Tạo một MongoClientFactorylớp sẽ chạy ở cấp ứng dụng và cung cấp cho bạn một MongoClientđối tượng. Bạn có thể triển khai điều này dưới dạng Singleton hoặc sử dụng Enum Singleton (đây là luồng an toàn)
  2. Tạo một lớp cơ sở truy cập dữ liệu mà từ đó bạn có thể kế thừa một đối tượng truy cập dữ liệu cho từng đối tượng miền). Lớp cơ sở có thể triển khai một phương thức để tạo một đối tượng MongoTemplate mà các phương thức cụ thể của lớp bạn có thể sử dụng cho tất cả các truy cập DB
  3. Mỗi lớp truy cập dữ liệu cho mỗi đối tượng miền có thể triển khai các phương thức cơ bản hoặc bạn có thể triển khai chúng trong lớp cơ sở
  4. Các phương thức Bộ điều khiển sau đó có thể gọi các phương thức trong các lớp Truy cập dữ liệu khi cần thiết.

Hi @rameshpa Tôi có thể sử dụng cả hai kho MongoTemplate & trong cùng một dự án .. Có thể sử dụng không?
Gauranga

1
Bạn có thể ngoại trừ MongoTemplate mà bạn triển khai sẽ có một kết nối đến DB khác với kết nối được Repository sử dụng. Tính nguyên tử có thể là một vấn đề. Ngoài ra tôi sẽ không khuyên bạn sử dụng hai kết nối khác nhau trên một thread nếu bạn có nhu cầu trình tự
rameshpa

24

FWIW, liên quan đến các bản cập nhật trong môi trường đa luồng:

  • MongoTemplatecung cấp "nguyên tử" out-of-the-box hoạt động updateFirst , updateMulti, findAndModify, upsert... cho phép bạn chỉnh sửa một tài liệu trong một hoạt động đơn lẻ. Đối Updatetượng được sử dụng bởi các phương pháp này cũng cho phép bạn chỉ nhắm mục tiêu các trường có liên quan .
  • MongoRepositorychỉ cung cấp cho bạn những hoạt động cơ bản CRUD find , insert, save, delete, mà làm việc với các POJO chứa tất cả các lĩnh vực . Điều này buộc bạn phải cập nhật tài liệu theo nhiều bước (1. findtài liệu cần cập nhật, 2. sửa đổi các trường liên quan từ POJO trả về, và sau đó 3. savenó) hoặc xác định các truy vấn cập nhật của riêng bạn bằng cách sử dụng @Query.

Trong môi trường đa luồng, chẳng hạn như một back-end Java với một số điểm cuối REST, cập nhật phương pháp đơn là cách để thực hiện, để giảm khả năng hai bản cập nhật đồng thời ghi đè các thay đổi của nhau.

Ví dụ: cho một tài liệu như thế này: { _id: "ID1", field1: "a string", field2: 10.0 }và hai luồng khác nhau đồng thời cập nhật nó ...

Với MongoTemplatenó sẽ trông giống như sau:

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

và trạng thái cuối cùng của tài liệu luôn là { _id: "ID1", field1: "another string", field2: 15.0 }vì mỗi luồng chỉ nạp vào DB một lần chỉ trường được chỉ định được thay đổi.

Trong khi tình huống tương tự với MongoRepositorysẽ như thế này:

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)
|                                                               |
|                                                               |

và tài liệu cuối cùng là một trong hai { _id: "ID1", field1: "another string", field2: 10.0 }hoặc { _id: "ID1", field1: "a string", field2: 15.0 }tùy thuộc vào savethao tác nào truy cập DB cuối cùng.
(LƯU Ý: Ngay cả khi chúng tôi đã sử dụng chú thích của Spring Data@Version như được đề xuất trong các nhận xét, thì sẽ không có nhiều thay đổi: một trong các savethao tác sẽ ném ra một OptimisticLockingFailureExceptionvà tài liệu cuối cùng sẽ vẫn là một trong các thao tác trên, với chỉ một trường được cập nhật thay vì cả hai. )

Vì vậy, tôi muốn nói rằng đó MongoTemplatelà một lựa chọn tốt hơn , trừ khi bạn có một mô hình POJO rất phức tạp hoặc cần các khả năng truy vấn tùy chỉnh MongoRepositoryvì lý do nào đó.


Điểm tốt / ví dụ. Tuy nhiên, bạn có thể tránh được ví dụ về điều kiện cuộc đua và kết quả không mong muốn bằng cách sử dụng @Version để ngăn chặn tình huống đó.
Madbreaks

@Madbreaks Bạn có thể cung cấp bất kỳ tài nguyên nào về cách đạt được điều này không? Bất kỳ tài liệu chính thức có lẽ?
Karthikeyan

Tài liệu về dữ liệu mùa xuân về chú thích @Version
Karim Tawfik

1
@Madbreaks Cảm ơn bạn đã chỉ ra điều đó. Có, @Versionsẽ "tránh" luồng thứ hai ghi đè lên dữ liệu được lưu bởi luồng đầu tiên - "tránh" theo nghĩa là nó sẽ loại bỏ bản cập nhật và OptimisticLockingFailureExceptionthay vào đó ném một luồng . Vì vậy, bạn phải triển khai cơ chế thử lại nếu bạn muốn cập nhật thành công. MongoTemplate cho phép bạn tránh toàn bộ tình huống.
walen
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.