Zu Content springen
Shopware

Effiziente Datenbankabfragen mit Shopware 6 Associations

von Tim Kelle

Effiziente Datenbankabfragen mit Shopware 6 Associations
12:07

Es ist Montag, 8:30 Uhr. Lisa, Entwicklerin bei einem mittelständischen Online-Händler, starrt auf ihren Bildschirm. Die Aufgabe klingt eigentlich simpel: Für die neue Produktübersicht sollen neben den Produktdaten auch die zugehörigen Kategorien, Bewertungen und Hersteller-Informationen angezeigt werden. Doch wie holt man all diese verknüpften Daten effizient aus der Datenbank, ohne dabei in Performance-Probleme zu laufen?

Grafische Darstellung eines Datenbankmodells, das den Fluss von Bewertungen, Fabriken und Netzwerken zeigt. Dies symbolisiert die Analyse von Produktdaten.

Die Antwort liegt in den Shopware 6 Associations – einem mächtigen Feature des Data Abstraction Layers, das genau für solche Szenarien entwickelt wurde.

Was sind Shopware 6 Associations?

Shopware 6 Associations sind Verknüpfungen zwischen verschiedenen Entitäten in der Datenbank. Sie ermöglichen es dir, zusammengehörige Daten mit einer einzigen Anfrage zu laden, anstatt mehrere separate Datenbankabfragen ausführen zu müssen. Das spart nicht nur Performance, sondern macht deinen Code auch sauberer und wartbarer. Professionelle Shopware Entwicklung setzt genau hier an, um skalierbare Systeme zu schaffen.

Im Shopware-Universum gibt es drei Haupttypen von Associations:

  • OneToMany: Ein Produkt hat viele Bewertungen
  • ManyToOne: Viele Produkte gehören zu einem Hersteller
  • ManyToMany: Produkte können mehreren Kategorien zugeordnet sein

Associations in der Praxis laden

Grundlagen mit addAssociation()

Der einfachste Weg, Associations zu laden, ist die `addAssociation()`-Methode in den Shopware 6 Criteria. Eine saubere technische Umsetzung ist entscheidend für die spätere Wartbarkeit des Projekts:


use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;

public function loadProductWithCategories(Context $context): void
{
    $criteria = new Criteria();
    $criteria->addAssociation('categories');
    $criteria->addAssociation('manufacturer');
    $criteria->addAssociation('productReviews');
   
    $products = $this->productRepository->search($criteria, $context);
   
    foreach ($products as $product) {
        // Kategorien sind bereits geladen
        $categories = $product->getCategories();
       
        // Hersteller-Daten verfügbar
        $manufacturer = $product->getManufacturer();
       
        // Bewertungen laden
        $reviews = $product->getProductReviews();
    }
}

Verschachtelte Associations

Du kannst auch tief verschachtelte Datenstrukturen laden. Möchtest du beispielsweise nicht nur die Produktbewertungen, sondern auch die Kunden-Informationen der Bewertenden?


public function loadProductWithNestedData(Context $context): void
{
    $criteria = new Criteria();
   
    // Lade Bewertungen UND die dazugehörigen Kunden
    $criteria->addAssociation('productReviews.customer');
   
    // Lade Kategorien UND deren übergeordnete Kategorien
    $criteria->addAssociation('categories.parent');
   
    $products = $this->productRepository->search($criteria, $context);
}

Erweiterte Filterung mit getAssociation()

Hier wird es richtig interessant: Mit `getAssociation()` kannst du nicht nur Daten laden, sondern sie auch direkt filtern. Das ist besonders nützlich, wenn du nur bestimmte verknüpfte Datensätze benötigst.


use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\RangeFilter;

public function loadProductsWithGoodReviews(Context $context): void
{
    $criteria = new Criteria();
   
    // Lade nur Bewertungen mit 4+ Sternen
    $criteria->getAssociation('productReviews')
        ->addFilter(new RangeFilter('points', [
            RangeFilter::GTE => 4
        ]))
        ->addSorting(new FieldSorting('createdAt', FieldSorting::DESCENDING));
   
    $products = $this->productRepository->search($criteria, $context);
}

Praktisches Beispiel: Produktsuche mit Shopware Search Field

Lass uns ein realistisches Szenario durchspielen. Du entwickelst eine erweiterte Produktsuche, die nicht nur die Produktdaten, sondern auch relevante Zusatzinformationen liefert. Dies ist ein Kernaspekt für eine effektive Conversion-Optimierung innerhalb des Shops:


use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\ContainsFilter;

class ProductSearchService
{
    public function searchProducts(string $searchTerm, Context $context): EntitySearchResult
    {
        $criteria = new Criteria();
       
        // Volltext-Suche im Produktnamen
        $criteria->addFilter(new ContainsFilter('name', $searchTerm));
       
        // Nur aktive Produkte
        $criteria->addFilter(new EqualsFilter('active', true));
       
        // Lade relevante Associations
        $criteria->addAssociation('manufacturer');
        $criteria->addAssociation('categories');
       
        // Lade nur die besten Bewertungen (4+ Sterne)
        $criteria->getAssociation('productReviews')
            ->addFilter(new RangeFilter('points', [RangeFilter::GTE => 4]))
            ->setLimit(3);
       
        // Sortierung nach Relevanz
        $criteria->addSorting(new FieldSorting('name', FieldSorting::ASCENDING));
       
        return $this->productRepository->search($criteria, $context);
    }
}

ManyToMany Associations richtig handhaben

Bei ManyToMany-Beziehungen wie Produkt-Kategorien gibt es ein paar Besonderheiten zu beachten:


public function handleProductCategories(Context $context): void
{
    $criteria = new Criteria(['your-product-id']);
    $criteria->addAssociation('categories');
   
    $product = $this->productRepository->search($criteria, $context)->first();
   
    if ($product) {
        // Kategorien durchlaufen
        foreach ($product->getCategories() as $category) {
            echo $category->getName();
        }
       
        // Neue Kategorie hinzufügen (für neue Entitäten)
        if (!$product->getId()) {
            $newCategory = $this->categoryRepository->create($context);
            $product->getCategories()->add($newCategory);
        }
    }
}

Performance-Optimierung mit Shopware 6 Criteria Filter

Eine der häufigsten Performance-Fallen ist das unnötige Laden von Daten. Wer eine exzellente Shop-Performance anstrebt, muss hier ansetzen:

1. Selektives Laden mit Filtern


public function loadOptimizedProductData(Context $context): void
{
    $criteria = new Criteria();
   
    // Nur notwendige Associations laden
    $criteria->addAssociation('manufacturer');
   
    // Bewertungen begrenzen (nur die neuesten 5)
    $criteria->getAssociation('productReviews')
        ->setLimit(5)
        ->addSorting(new FieldSorting('createdAt', FieldSorting::DESCENDING));
   
    // Pagination für große Datenmengen
    $criteria->setLimit(20);
    $criteria->setOffset(0);
   
    $products = $this->productRepository->search($criteria, $context);
}

2. Foreign Keys clever nutzen

Wenn du nur die ID einer verknüpften Entität benötigst, musst du nicht die komplette Association laden. Dies spielt auch im Kontext von technischem E-Commerce-SEO eine Rolle, um Crawl-Budgets durch schnelle Antwortzeiten zu schonen:


public function checkManufacturer(Context $context): void
{
    $criteria = new Criteria();
    // Kein addAssociation('manufacturer') nötig!
   
    $product = $this->productRepository->search($criteria, $context)->first();
   
    if ($product) {
        // Shopware Foreign Key ist bereits verfügbar
        $manufacturerId = $product->getManufacturerId();
       
        // Nur bei Bedarf den kompletten Hersteller laden
        if ($this->needsFullManufacturerData()) {
            $manufacturer = $this->manufacturerRepository->get($manufacturerId, $context);
        }
    }
}

Troubleshooting: Häufige Fallstricke

Problem 1: N+1 Query Problem

Sollten Sie bei der Fehlerbehebung Unterstützung benötigen, ist eine professionelle Wartung Ihres Systems ratsam.


// ❌ Schlecht: Führt zu vielen DB-Abfragen
public function badExample(Context $context): void
{
    $products = $this->productRepository->search(new Criteria(), $context);
   
    foreach ($products as $product) {
        // Jede Iteration = eine neue DB-Abfrage!
        $manufacturer = $this->manufacturerRepository->get(
            $product->getManufacturerId(),
            $context
        );
    }
}

// ✅ Gut: Eine Abfrage für alles
public function goodExample(Context $context): void
{
    $criteria = new Criteria();
    $criteria->addAssociation('manufacturer');
   
    $products = $this->productRepository->search($criteria, $context);
   
    foreach ($products as $product) {
        // Hersteller sind bereits geladen
        $manufacturer = $product->getManufacturer();
    }
}

Eine Frau betrachtet einen Computerbildschirm mit Produktinformationen, während sie nachdenklich über die dargestellten Daten nachdenkt. Der Raum hat große Fenster mit Tageslicht.

Problem 2: Memory-Probleme bei großen Datenmengen


use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;

public function handleLargeDatasets(Context $context): void
{
    $criteria = new Criteria();
    $criteria->addAssociation('productReviews');
   
    // ❌ Kann Memory-Probleme verursachen
    // $criteria->setLimit(10000);
   
    // ✅ Besser: Pagination nutzen
    $criteria->setLimit(50);
    $criteria->setOffset(0);
   
    $result = $this->productRepository->search($criteria, $context);
    $this->processResults($result);
}

Associations in der Administration

Auch in der Administration von Shopware 6 spielen Associations eine wichtige Rolle. Hier ein Beispiel für das Laden von Produktdaten mit Associations:


const { Criteria } = Shopware.Data;

// Repository erstellen
const productRepository = this.repositoryFactory.create('product');

// Criteria mit Associations
const criteria = new Criteria();
criteria.addAssociation('manufacturer');
criteria.addAssociation('categories');
criteria.addAssociation('productReviews');

// Zusätzliche Filter für Associations
criteria.getAssociation('productReviews')
    .addSorting(Criteria.sort('createdAt', 'DESC'))
    .setLimit(5);

// Daten laden
productRepository.search(criteria, Shopware.Context.api)
    .then(result => {
        this.products = result;
    });

Best Practices für den Produktiveinsatz

Aspekt Do Don't
Performance Associations selektiv laden Alle möglichen Associations laden
Memory Pagination bei großen Datenmengen Unbegrenzte Ergebnismengen
Caching Criteria Shopware für konsistente Abfragen Unterschiedliche Filter für gleiche Daten
Code-Qualität Sprechende Association-Namen Verschachtelte Associations ohne Dokumentation

 

Checkliste für optimale Association-Nutzung:

Nur notwendige Associations laden
Filter und Limits auf Associations anwenden
Foreign Keys nutzen, wenn nur IDs benötigt werden
Pagination bei großen Datenmengen implementieren
Performance mit Profiler überwachen
Consistent Sorting für deterministische Ergebnisse

Erweiterte Anwendungsfälle

Custom Repository für komplexe Associations


class ProductWithDetailsRepository
{
    public function findProductsWithFullDetails(array $productIds, Context $context): EntitySearchResult
    {
        $criteria = new Criteria($productIds);
       
        // Standard-Associations
        $criteria->addAssociation('manufacturer');
        $criteria->addAssociation('categories');
       
        // Erweiterte Associations mit Filtern
        $criteria->getAssociation('productReviews')
            ->addFilter(new EqualsFilter('status', 'approved'))
            ->addSorting(new FieldSorting('points', FieldSorting::DESCENDING))
            ->setLimit(10);
           
        // Preise mit Währungs-Informationen
        $criteria->getAssociation('prices')
            ->addAssociation('currency')
            ->addSorting(new FieldSorting('quantityStart', FieldSorting::ASCENDING));
       
        return $this->productRepository->search($criteria, $context);
    }
}

Event-basierte Association-Erweiterung


use Shopware\Core\Framework\DataAbstractionLayer\Event\EntitySearchedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class ProductAssociationSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            EntitySearchedEvent::class => 'onEntitySearched'
        ];
    }
   
    public function onEntitySearched(EntitySearchedEvent $event): void
    {
        if ($event->getDefinition()->getEntityName() !== 'product') {
            return;
        }
       
        // Automatisch wichtige Associations hinzufügen
        $criteria = $event->getCriteria();
       
        if (!$criteria->hasAssociation('manufacturer')) {
            $criteria->addAssociation('manufacturer');
        }
    }
}

Fazit: Associations als Schlüssel zur Performance

Shopware 6 Associations sind weit mehr als nur eine praktische Funktion – sie sind der Schlüssel zu performanten und maintainbaren E-Commerce-Anwendungen. Wer sie richtig einsetzt, kann mit wenigen Datenbankabfragen komplexe Datenstrukturen laden und dabei sowohl die Server-Performance als auch die Benutzererfahrung optimieren.

Die wichtigste Erkenntnis: Weniger ist oft mehr. Anstatt reflexartig alle verfügbaren Associations zu laden, solltest du bewusst entscheiden, welche Daten wann benötigt werden. Mit den hier vorgestellten Techniken – von grundlegenden `addAssociation()`-Aufrufen bis hin zu komplexen gefilterten Associations – bist du bestens gerüstet, um auch die anspruchsvollsten Anforderungen zu meistern.

Lisa würde heute übrigens lächelnd in den Feierabend gehen. Die Produktübersicht lädt nicht nur blitzschnell, sondern der Code ist auch so sauber strukturiert, dass ihre Kolleginnen und Kollegen ihn problemlos erweitern können. Genau das macht den Unterschied zwischen funktionierendem und excellentem Shopware 6 Code aus.

Diesen Beitrag teilen