У чому різниця між MongoTemplate Spring Data та MongoRepository?


97

Мені потрібно написати програму, за допомогою якої я можу робити складні запити, використовуючи 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? Вони обидва однакові зрілі, чи одному з них не вистачає більше функцій, ніж іншому?

Відповіді:


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-. Нещастить. spring-data завжди намагається автоматично згенерувати, як тільки я розширюю користувацький інтерфейс до класу сховища. Єдина відмінність полягає в тому, що я використовую MongoRepository, а не CrudRepository, оскільки наразі не хочу працювати з ітераторами. Якщо у вас є натяк, це буде вдячне.
Крістофер Армстронг

10
Найпоширенішою помилкою є неправильне назва класу реалізації: якщо викликається ваш базовий інтерфейс репо YourRepository, клас реалізації повинен бути названий YourRepositoryImpl. Так це? Якщо так, я з радістю подивлюсь на зразок проекту на GitHub або тому подібному ...
Олівер Дротбом

5
Привіт Олівере, клас Impl був названий неправильно, як ти припустив. Я відкоригував назву, і, схоже, вона працює зараз. Дякуємо за Ваш відгук. Це дуже здорово, що можна використовувати різні варіанти запитів таким чином. Добре продумано!
Крістофер Армстронг

Ця відповідь не така однозначна. Зробивши все за цим прикладом, я потрапляю до цієї проблеми: stackoverflow.com/a/13947263/449553 . Отже, дотримання імен є більш суворим, ніж це виглядає з цього прикладу.
msangel

1
Клас реалізації на №2 названий неправильно: має бути, CustomUserRepositoryа ні CustomerUserRepository.
Котта,

28

Ця відповідь може трохи затриматися, але я рекомендую уникати всього маршруту сховища. Ви отримуєте дуже мало впроваджених методів, що мають велику практичну цінність. Для того, щоб це працювало, ви натрапляєте на конфігураційну нісенітницю Java, на яку ви можете витратити дні та тижні без особливої ​​допомоги в документації.

Натомість ідіть MongoTemplateшляхом і створіть власний рівень доступу до даних, який позбавить вас від конфігураційних кошмарів, з якими стикаються програмісти Spring. MongoTemplateє справді порятунком для інженерів, яким зручно створювати власні класи та взаємодії, оскільки існує велика гнучкість. Структура може бути приблизно такою:

  1. Створіть MongoClientFactoryклас, який працюватиме на рівні програми та надасть вам MongoClientоб’єкт. Ви можете реалізувати це як синглтон або за допомогою Enum Singleton (це безпечно для потоку)
  2. Створіть базовий клас доступу до даних, з якого ви можете успадкувати об'єкт доступу до даних для кожного об'єкта домену). Базовий клас може реалізувати метод для створення об'єкта MongoTemplate, який ви використовуєте для кожного класу, методи, які можуть бути використані для всіх звернень до БД
  3. Кожен клас доступу до даних для кожного об’єкта домену може реалізувати основні методи або ви можете реалізувати їх у базовому класі
  4. Потім методи Controller можуть викликати методи в класах доступу до даних за потреби.

Привіт @rameshpa Чи можу я використовувати як MongoTemplate, так і сховище в одному проекті? .. Чи можна це використовувати
Gauranga

1
Ви можете, але MongoTemplate, який ви реалізуєте, матиме інше підключення до БД, ніж підключення, що використовується Repository. Атомність може бути проблемою. Також я б не рекомендував використовувати два різних з'єднання в одному потоці, якщо у вас є потреби в послідовності
rameshpa

24

FWIW, щодо оновлень у багатопотоковому середовищі:

  • MongoTemplateзабезпечує «атомний» поза коробки операцій updateFirst , updateMulti, findAndModify, upsert... , які дозволяють змінювати документ в одній операції. UpdateОб'єкт , який використовується ці методи також дозволяє налаштувати таргетинг тільки відповідні поля .
  • MongoRepositoryтільки дає вам основні операції CRUD find , insert, save, delete, які працюють з POJOs , що містить всі поля . Це змушує вас або оновлювати документи в кілька етапів (1. findдокумент, який потрібно оновити, 2. змінити відповідні поля з повернутого POJO, а потім 3. saveце), або визначити власні запити на оновлення вручну, використовуючи @Query.

У багатопотоковому середовищі, наприклад, наприклад, у фоновому режимі Java із кількома кінцевими точками REST, слід застосовувати оновлення з одним методом, щоб зменшити шанси двох паралельних оновлень перезаписати зміни одне одного.

Приклад: дано такий документ: { _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операція потрапляє в БД останньою.
(ПРИМІТКА. Навіть якби ми використовували анотацію Spring Data,@Version як пропонується в коментарях, мало що зміниться: одна з saveоперацій викине OptimisticLockingFailureException, а остаточний документ все одно буде однією з вищезазначених, лише замість обох оновлене одне поле. )

Тому я б сказав, що MongoTemplateце кращий варіант , якщо у вас немає дуже розробленої моделі POJO або вам з MongoRepositoryякихось причин не потрібні можливості користувацьких запитів .


Хороші моменти / приклади. Однак вашого прикладу стану гонки та небажаного результату можна уникнути, використовуючи @Version, щоб запобігти саме тому сценарію.
Madbreaks

@Madbreaks Чи можете ви надати будь-які ресурси щодо того, як цього досягти? Будь-який офіційний документ, мабуть?
Картікеян

Весняні документи щодо анотації @Version: docs.spring.io/spring-data/mongodb/docs/current/reference/html/…
Karim

1
@Madbreaks Дякую, що вказали на це. Так, @Version"уникнути" другого потоку перезапису даних, збережених першим - "уникати" в тому сенсі, що це відкине оновлення і OptimisticLockingFailureExceptionзамість нього викине . Тому вам доведеться застосувати механізм повторної спроби, якщо ви хочете, щоб оновлення пройшло успішно. MongoTemplate дозволяє уникнути всього сценарію.
walen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.