9 Stimmen

Offenlegung von Hibernate-Kriterien über die Dienst-API

Dies ist eher eine Frage des Designs als der Implementierung, und es wird lang werden, also haben Sie Geduld mit mir. Es ist am besten mit einem Beispiel zu erklären:

Nehmen wir an, ich habe eine Wirtschaftseinheit namens Produkt mit einer Reihe von Eigenschaften ( Name , Preis , Anbieter usw...).

Es wird durch eine Schnittstelle dargestellt ( Produkt ) und Umsetzung ( ProductImpl , abgebildet in Hibernate) sowie eine grundlegende CRUD-Dienstschnittstelle ( ProduktDienstleistung ) und Umsetzung ( ProductServiceImpl ).
Produkt y ProduktDienstleistung sind als API offengelegt, ihre Implementierungen jedoch nicht.

Ich möchte eine List findProducts(QueryCriteria Kriterien) Methode zu ProduktDienstleistung die eine Liste von Produkten zurückgeben würde, die bestimmte Kriterien erfüllen. Die Anforderungen sind:

  1. Abfrage durch direkte Produkt Eigenschaften (z.B. product.price gt 50.0 )
  2. Abfrage durch Assoziation (z.B. product.vendor.name = "Oracle" )
  3. Ergebnisse sortieren (z.B. order by product.vendor.name desc, product.price asc" )
  4. Zusätzliche Filter anwenden. Im Gegensatz zu den obigen 3 Punkten, die alle vom API-Kunden angegeben werden, kann der Dienst zusätzliche Filter auf der Grundlage der Identität des Kunden anwenden (z. B. kann der Kunde, der diese Methode aufruft, darauf beschränkt sein, nur Produkte eines bestimmten Herstellers zu sehen). Solche Filter haben Vorrang vor allen vom Kunden angegebenen Kriterien (z. B. wenn der Filter auf product.vendor.name = "Microsoft" sollte die Abfrage in (2) eine leere Ergebnismenge ergeben.

Die Frage ist also, was sollte QueryCriteria Schnittstelle, die von einer solchen Methode verwendet wird, aussehen? Mir fallen 3 Lösungen ein, und keine von ihnen gefällt mir:

  • Erlauben Sie den Kunden, HQL (beginnend mit der "where"-Klausel) direkt anzugeben. Dies ist die einfachste Lösung, aber auch die problematischste in Bezug auf die Sicherheit. Selbst wenn man davon ausgeht, dass Filter (#4 oben) einfach genug sind, um über die Session-Filter von Hibernate implementiert zu werden, muss HQL immer noch geparst werden, um - zumindest - sicherzustellen, dass Abfrageparameter als Parameter angegeben und nicht inlined werden.
  • Verwenden Sie die dünn verpackte Hibernate FreistehendKriterien anstelle von QueryCriteria . "Thinly wrapped", weil es dem Client nicht erlaubt werden kann, eine FreistehendKriterien direkt, denn es gäbe keine Möglichkeit zu kontrollieren, für welche zugeordnete Entität sie erstellt wurde. Außerdem wäre dies nicht so flexibel wie HQL, da einige Abfragen nicht einfach (oder gar nicht) über die Criteria-API ausgedrückt werden können. Wie beim HQL-Ansatz sind die Filter (Nr. 4 oben) auf Hibernate-Sitzungsfilter beschränkt.
  • Mein eigenes schreiben QueryCriteria Schnittstelle/Implementierung, die hinter den Kulissen entweder DetachedCriteria oder HQL bilden wird. Dies ist zwar wahrscheinlich die flexibelste Lösung, aber es muss eine Menge Code aus der Criteria-API dupliziert werden, was nicht gerade ideal ist.

Für Kommentare zur Gültigkeit der oben genannten Ansätze oder - ich drücke die Daumen - für einfache, elegante Lösungen, die mir nicht eingefallen sind, wäre ich sehr dankbar.

P.S. In meinem speziellen Fall sind alle API-Clients intern und "halb-vertrauenswürdig" - das heißt, ich mache mir weniger Sorgen darüber, dass jemand absichtlich etwas kaputt macht, als über schlechte Programmierung, die zu einem kartesischen Produkt aus 5 Tabellen führt :-) Es wäre jedoch schön, eine Lösung zu finden, die der öffentlichen Nutzung der API standhält.

3voto

ChssPly76 Punkte 97241

Die aktuelle Lösung, die ich implementiert habe, verwendet einen hybriden Ansatz.

Methoden, die wohldefinierte Abfragen verwenden (z. B. Methoden, die intern von anderen Diensten, vordefinierten Berichten usw. verwendet werden), haben eine ähnliche Signatur wie die findBy-Methoden von HibernateTemplate:

public List<Entity> findEntities(String queryName, QueryParameters parameters);

wobei QueryParameters ist eine Komfortklasse, um benannte Parameter explizit anzugeben oder sie von einer Bean zu übernehmen. Ein Beispiel für die Verwendung ist:

List<Product> products = findProducts("latestUpdates",
 new QueryParameters()
  .add("vendor", "Oracle")
  .add("price", "50.0")
);

oder

List<Product> products = findProducts("latestUpdates",
 new QueryParameters(product, "vendor", "price"));

Der Zugriff auf solche Methoden ist auf "vertrauenswürdigen" Code beschränkt; die verwendeten Abfragen müssen natürlich in Hibernate-Mappings definiert werden. Filter werden in Abfragen eingebaut oder als Sitzungsfilter definiert. Die Vorteile sind sauberer Code (kein Criteria-ähnliches Zeug, das über eine halbe Seite verteilt ist) und klar definierte HQL (leichter zu optimieren und mit Cache zu arbeiten, wenn nötig).


Methoden, die der Benutzeroberfläche ausgesetzt sind oder anderweitig dynamischer sein müssen, verwenden Suche Schnittstelle von Hibernate-Generic-DAO Projekt. Es ist dem DetachedCriteria von Hibernate ähnlich, hat aber mehrere Vorteile:

  1. Sie kann ohne Bindung an ein bestimmtes Unternehmen erstellt werden. Für mich ist das eine große Sache, weil die Entitätsschnittstelle (Teil der API, der für die Benutzer sichtbar ist) und die Implementierung (POJO, das in Hibernate abgebildet wird) zwei verschiedene Klassen sind und die Implementierung dem Benutzer zur Kompilierungszeit nicht zur Verfügung steht.

  2. Es handelt sich um eine gut durchdachte, offene Schnittstelle, ganz im Gegensatz zu DetachedCriteria, aus der es fast unmöglich ist, irgendetwas zu extrahieren (ja, ich weiß, DC wurde nicht dafür entwickelt, aber trotzdem).

  3. Integrierte Paginierung / Ergebnisse mit Gesamtzahl / eine Reihe anderer kleiner Nettigkeiten.

  4. Keine explizite Bindung an Hibernate (obwohl mir persönlich das egal ist; ich werde Hibernate nicht plötzlich fallen lassen und morgen mit EclipseLink arbeiten); es sind sowohl Hibernate- als auch generische JPA-Implementierungen verfügbar.

Filter können der Suche auf der Service-Seite hinzugefügt werden; das ist der Fall, wenn auch die Entitätsklasse angegeben wird. Das einzige, was fehlt, ist Quick-Fail auf der Client-Seite, wenn ungültige Eigenschaft Name angegeben wird und das kann durch das Schreiben meiner eigenen ISearch / IMutableSearch-Implementierung gelöst werden, aber ich habe nicht dazu kommen, dass noch.

2voto

dustmachine Punkte 10153

Option Eins: Wenn es möglich ist, Ihre API zu erweitern, schlage ich vor, Ihre API "reichhaltiger" zu gestalten, d. h. mehr Methoden hinzuzufügen, wie z. B. einige der unten aufgeführten, damit Ihr Dienst natürlicher klingt. Es kann schwierig sein, Ihre API zu erweitern, ohne dass sie aufgebläht wirkt, aber wenn Sie ein ähnliches Benennungsschema befolgen, wird die Verwendung natürlich erscheinen.

productService.findProductsByName("Widget")
productService.findProductsByName(STARTS_WITH,"Widg")
productService.findProductsByVendorName("Oracle")
productService.findProductsByPrice(OVER,50)

Das Kombinieren der Ergebnisse (Anwendung mehrerer Einschränkungen) könnte den Clients überlassen werden, nachdem sie die Ergebnismenge unter Verwendung von CollectionUtils und Predicates erhalten haben. Sie könnten sogar ein paar gemeinsame Predicates für die Nutzer Ihrer API erstellen, nur um nett zu sein. CollectionUtils.select() macht Spaß.

Option 2: Wenn es nicht möglich ist, die API zu erweitern, würde ich mich für Ihren dritten Punkt entscheiden.

  • Schreiben Sie meine eigene QueryCriteria-Schnittstelle/Implementierung, die entweder DetachedCriteria oder HQL hinter den Kulissen bilden wird...

Sie könnten versuchen, einen DSL-ähnlichen Ansatz für die Benennung zu verwenden, der dem Builder-Muster ähnelt, um die Dinge lesbarer und natürlicher zu machen. Das wird in Java mit all den Punkten und Parens ein wenig unhandlich, aber vielleicht so etwas wie:

Product.Restriction restriction = new Product.Restriction().name("Widget").vendor("Oracle").price(OVER,50) );
productService.findProducts(restriction);

Option Drei: Kombinieren Sie die beiden Ansätze, indem Sie ein Kriterium im Stil einer Einschränkung zusammen mit einer reichhaltigeren API anbieten. Diese Lösungen wären insofern sauber, als sie die Details der Hibernate-Implementierung vor den Nutzern Ihrer API verbergen. (Nicht, dass jemand auf die Idee käme, von Hibernate wegzugehen.)

1voto

Pablojim Punkte 8312

Hmm - interessante Frage.

Nach reiflicher Überlegung ist es wahrscheinlich der beste Weg, eine eigene Kriterienschnittstelle zu schreiben. Damit sind Sie nicht an eine Implementierung gebunden und haben weniger Sicherheitsbedenken.

Je nachdem, wie viele Objekte betroffen sind, habe ich auch erwogen, den gesamten Produktsatz (mit den erforderlichen Filtern) zurückzugeben und dann den Endbenutzer mit Lambdaj oder ähnlichem filtern zu lassen. Siehe:

http://code.google.com/p/lambdaj/

0voto

Arne Burmeister Punkte 19217

Es ist nie eine gute Idee, solche Implementierungsdetails preiszugeben. Von da an sind Sie an diese Bibliothek gebunden. Schlimmer noch, jede API-Änderung der Bibliothek wird eine API-Änderung Ihres Dienstes nach sich ziehen. Alle Sicherheitserwägungen bleiben auf der Strecke...

Was ist mit den Namen der Bohneneigenschaften, die in den Kriterien verwendet werden (ein Tripel aus Eigenschaftsname, Enum mit less, equal und more und value). Mit einem Bean-Wrapper auf Ihrem Modell können Sie dies in ein Hibernate-Kriterium umwandeln.

Es wird auch möglich sein, diese Eigenschaftsnamen nach einer Modelländerung in eine neue Version umzuwandeln.

0voto

Yuval Punkte 7817

Hibernate ist ein Low-Level-Infrastruktur-Framework und sollte als solches hinter den Kulissen verborgen bleiben. Wenn Ihre Anwendung im nächsten Monat aus irgendeinem Grund zu einem anderen ORM-Framework wechseln muss, wird Ihre API nutzlos sein. Die Kapselung, auch zwischen verschiedenen Schichten innerhalb derselben Anwendung, ist von entscheidender Bedeutung.

Nach all dem sollte Ihre Methode eine Abstraktion der Informationen erhalten, die Sie für die Suche benötigen. Ich rate Ihnen, ein Enum von Product und implementieren eine oder zwei einfache Versionen von Restriction.

Die Parameter der Methode können eine Liste von Gleichheitseinschränkungen, eine weitere Liste von relativen Einschränkungen und natürlich ein Ordnungsindikator (einer der Enum-Werte plus ein Flag für asc/desc) sein.

Dies ist nur eine allgemeine Richtung, ich hoffe, ich habe mich klar ausgedrückt =8-)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X