← Назад к вопросам

Что такое Panache в MongoDB?

2.0 Middle🔥 121 комментариев
#Кэширование и NoSQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Panache в MongoDB: упрощённое ORM для Quarkus

Panache — это проект в экосистеме Quarkus, который предоставляет упрощённое ORM (Object-Relational Mapping) для работы с MongoDB. Panache делает работу с MongoDB намного проще, чем использование низкоуровневого драйвера, предоставляя удобный API для CRUD операций и запросов.

Паначе поддерживает два подхода:

  1. Active Record паттерн — методы на самом entity классе
  2. Repository паттерн — отдельный repository класс

Основная идея

Вместо написания boilerplate кода для подключения, сериализации, десериализации и выполнения запросов, Panache предоставляет удобные методы:

// БЕЗ Panache (низкоуровневый MongoDB драйвер)
MongoClient client = MongoClients.create();
MongoDatabase database = client.getDatabase("mydb");
MongoCollection<Document> collection = database.getCollection("users");
Document doc = new Document("name", "John").append("email", "john@example.com");
collection.insertOne(doc);

// С Panache (высокоуровневый API)
User user = new User();
user.name = "John";
user.email = "john@example.com";
user.persist();

Установка Panache

<!-- pom.xml -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-mongodb-panache</artifactId>
</dependency>

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
# application.properties
quarkus.mongodb.connection-string=mongodb://localhost:27017
quarkus.mongodb.database=mydb

Active Record Паттерн

При Active Record паттерне entity наследует PanacheMongoEntity и получает CRUD методы:

import io.quarkus.mongodb.panache.PanacheMongoEntity;
import org.bson.types.ObjectId;

public class User extends PanacheMongoEntity {
    public String name;
    public String email;
    public int age;
    public String city;
    
    @Override
    public String toString() {
        return "User(id=" + id + ", name='" + name + "', email='" + email + "')";
    }
}

CRUD операции

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    // CREATE - Создать пользователя
    @PostMapping
    public ResponseEntity<User> createUser(User user) {
        user.persist(); // Сохранить в БД
        return ResponseEntity.status(201).body(user);
    }
    
    // READ - Получить пользователя по ID
    @GetMapping("/{id}")
    public User getUser(@PathVariable String id) {
        User user = User.findById(new ObjectId(id));
        if (user == null) {
            throw new NotFoundException("User not found");
        }
        return user;
    }
    
    // READ ALL - Получить всех пользователей
    @GetMapping
    public List<User> getAllUsers() {
        return User.listAll();
    }
    
    // UPDATE - Обновить пользователя
    @PutMapping("/{id}")
    public User updateUser(@PathVariable String id, User updateData) {
        User user = User.findById(new ObjectId(id));
        if (user == null) {
            throw new NotFoundException("User not found");
        }
        user.name = updateData.name;
        user.email = updateData.email;
        user.age = updateData.age;
        user.update(); // Обновить в БД
        return user;
    }
    
    // DELETE - Удалить пользователя
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable String id) {
        boolean deleted = User.deleteById(new ObjectId(id));
        if (!deleted) {
            throw new NotFoundException("User not found");
        }
        return ResponseEntity.noContent().build();
    }
}

Запросы с Panache

Поиск по полям

public class UserController {
    
    // Найти пользователя по email
    @GetMapping("/by-email")
    public User findByEmail(@RequestParam String email) {
        return User.find("email", email).firstResult();
    }
    
    // Найти всех пользователей старше 18 лет
    @GetMapping("/adults")
    public List<User> getAdults() {
        return User.find("age > ?1", 18).list();
    }
    
    // Найти пользователей в городе
    @GetMapping("/by-city/{city}")
    public List<User> findByCity(@PathVariable String city) {
        return User.find("city", city).list();
    }
    
    // Сложный запрос с несколькими условиями
    @GetMapping("/search")
    public List<User> search(
        @RequestParam(required = false) String city,
        @RequestParam(required = false) Integer minAge) {
        
        if (city != null && minAge != null) {
            return User.find("city = ?1 and age >= ?2", city, minAge).list();
        } else if (city != null) {
            return User.find("city", city).list();
        } else {
            return User.listAll();
        }
    }
}

Пагинация

@GetMapping("/paginated")
public Page<User> getPaginatedUsers(
    @RequestParam(defaultValue = "0") int page,
    @RequestParam(defaultValue = "10") int pageSize) {
    
    return User.findAll()
        .page(Page.of(page, pageSize))
        .pageAndCount();
}

// Или простая версия
@GetMapping("/paginated")
public List<User> getPaginatedUsers(
    @RequestParam(defaultValue = "0") int page,
    @RequestParam(defaultValue = "10") int pageSize) {
    
    return User.findAll()
        .page(page * pageSize, pageSize)
        .list();
}

Сортировка

@GetMapping("/sorted")
public List<User> getSortedUsers() {
    // Сортировка по имени (возрастающая)
    return User.find("1=1").sort("name").list();
}

@GetMapping("/sorted-desc")
public List<User> getSortedDescending() {
    // Сортировка по возрасту (убывающая)
    return User.find("1=1").sort("age desc").list();
}

@GetMapping("/top-users")
public List<User> getTopUsers() {
    // Самые молодые 5 пользователей
    return User.find("1=1").sort("age asc").page(0, 5).list();
}

Repository Паттерн

Вместо Active Record можно использовать Repository паттерн:

// Entity без наследования
public class Product {
    public ObjectId id;
    public String name;
    public Double price;
    public int stock;
}

// Repository
@ApplicationScoped
public class ProductRepository implements PanacheMongoRepository<Product> {
    
    // Автоматически унаследованы методы: persist, find, delete и т.д.
    
    // Кастомные методы
    public List<Product> findByPriceRange(double minPrice, double maxPrice) {
        return find("price between ?1 and ?2", minPrice, maxPrice).list();
    }
    
    public List<Product> findInStock() {
        return find("stock > 0").list();
    }
    
    public void reduceStock(ObjectId id, int quantity) {
        Product product = findById(id);
        if (product != null && product.stock >= quantity) {
            product.stock -= quantity;
            update(product);
        }
    }
}

// Использование в контроллере
@RestController
@RequestMapping("/api/products")
public class ProductController {
    @Inject
    ProductRepository productRepository;
    
    @GetMapping("/in-stock")
    public List<Product> getInStock() {
        return productRepository.findInStock();
    }
    
    @PostMapping("/{id}/buy")
    public ResponseEntity<Void> buyProduct(
        @PathVariable String id,
        @RequestParam int quantity) {
        
        productRepository.reduceStock(new ObjectId(id), quantity);
        return ResponseEntity.ok().build();
    }
}

Индексы и аннотации

import io.quarkus.mongodb.panache.MongoEntity;
import org.bson.codecs.pojo.annotations.BsonProperty;
import io.quarkus.mongodb.panache.PanacheMongoEntity;

public class User extends PanacheMongoEntity {
    public String name;
    
    // Укажи имя поля в MongoDB
    @BsonProperty("email_address")
    public String email;
    
    @BsonProperty("registration_date")
    public LocalDateTime createdAt;
}

Batch операции

public class UserService {
    
    // Массовое обновление
    @Transactional
    public void updateAllEmails(String newDomain) {
        User.update("email = concat(split(email, '@')[0], '@' + ?1)", newDomain).execute();
    }
    
    // Удаление по условию
    @Transactional
    public void deleteInactiveUsers(LocalDateTime inactiveDate) {
        User.delete("lastLoginDate < ?1", inactiveDate).execute();
    }
    
    // Подсчёт
    public long countAdults() {
        return User.count("age >= 18");
    }
}

Встроенные документы

public class Address {
    public String street;
    public String city;
    public String zipCode;
}

public class User extends PanacheMongoEntity {
    public String name;
    public String email;
    public Address address; // Встроенный документ
    public List<String> tags;
    public Map<String, String> metadata;
}

// Запросы на встроенные поля
@GetMapping("/by-city/{city}")
public List<User> findByCity(@PathVariable String city) {
    return User.find("address.city", city).list();
}

Преимущества Panache

  1. Меньше boilerplate — нет нужны в дополнительных классах
  2. Понятный API — интуитивные названия методов
  3. Интеграция с Quarkus — работает с dependency injection
  4. Производительность — оптимизировано для быстрого старта
  5. Type-safe — полная поддержка типов Java

Недостатки

  1. Привязка к Quarkus — не работает с Spring Boot
  2. Сложные запросы — для очень сложных запросов может быть недостаточно
  3. Меньше контроля — низкоуровневый доступ ограничен

Сравнение с другими подходами

ПодходПростотаКонтрольПроизводительность
Panache⭐⭐⭐⭐⭐⭐⭐⭐
MongoDB Reactive Streams⭐⭐⭐⭐⭐⭐
Spring Data MongoDB⭐⭐⭐⭐⭐⭐⭐
Raw MongoDB Driver⭐⭐⭐⭐⭐⭐

Заключение

Panache — это отличный выбор для проектов на Quarkus, где нужна простота использования MongoDB без сложного boilerplate. Это идеально подходит для микросервисов, REST API и приложений, где требуется быстрое прототипирование и развёртывание. Panache делает MongoDB работой удобной и быстрой, позволяя разработчикам сосредоточиться на бизнес-логике вместо технических деталей интеграции.