Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien
Toon posts:

[jpa/legacy JDBC] Access rules per entity

Pagina: 1
Acties:

Verwijderd

Topicstarter
Hoi,

Ik ben op dit moment bezig met een JDBC legacy project waarvoor een redesign moet komen. Nu wordt veel data opgehaald met SQL queries, waarbij in die queries de access rules (voor een user) embedded zijn.

Dit heeft grofweg de volgende vorm:

SQL:
1
2
select * from main_table, access_table1, access_table2, access_table3
where [complicated set of rules]


Dit is hier heel abstract weergegeven; waar het neer komt is dat 1 deel van de query de data ophaalt die daadwerkelijk opgehaald moet worden, terwijl een serie joins met andere tabellen en een reeks condities in de where clause bepalen of een user een bepaalde row wel of niet te zien krijgt.

Het probleem is natuurlijk dat dit slecht onderhoudbaar is. Access rules embedded in de queries staan op diverse plekken dubbel, plus dat je niet los kunt opvragen of user A toegang of niet heeft tot row X.

Het voordeel van deze aanpak is wel dat het snel en direct is. Als ik dit ga omzetten naar een JPA (Hibernate/Toplink) gebasseerde oplossing, dan vraag ik me af hoe ik dit ga vertalen. Eerst alle entities opvragen, en de lijst die daar weer uit voortkomt filteren zou een oplossing kunnen zijn. Het punt is daarbij wel dat alle access rules in memory moeten staan; 1 query per row doen is natuurlijk onbegonnen werk.

Een andere oplossing is om de access code weer als een JPQL query te schrijven; dan beperk ik het aantal rijen snel, maar zit ik wel weer met de rules in een query. Conceptueel gezien zou ik het liefst zoiets willen:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
class XyzDAO {

   List<Xyz> getXYZByAccess(XyzAccessManager am) {
        List<Xyz> filteredList = new ArrayList<Xyz>();
        for (Xyz xyz : getAllXyz()) {
           if (am.hasAccess(xyz) {
              filteredList.add(xyz);
           }
        }
        return filteredList; 
   }
}


Zou dit ongeveer een goede aanpak zijn, of zijn er betere methoden?

  • JKVA
  • Registratie: Januari 2004
  • Niet online

JKVA

Design-by-buzzword fanatic

Bij Hibernate kun je een where clause opnemen in de mapping per entiteit.

http://www.hibernate.org/...mapping-declaration-class, bullet 12.

Fat Pizza's pizza, they are big and they are cheezy


  • flowerp
  • Registratie: September 2003
  • Laatst online: 11-09 18:20
JKVA schreef op dinsdag 08 april 2008 @ 13:51:
Bij Hibernate kun je een where clause opnemen in de mapping per entiteit.

http://www.hibernate.org/...mapping-declaration-class, bullet 12.
Is dat niet precies wat ie al aangeeft niet meer te willen:
Een andere oplossing is om de access code weer als een JPQL query te schrijven; dan beperk ik het aantal rijen snel, maar zit ik wel weer met de rules in een query.
Dit is gewoon een vrij moeilijk probleem. Sommige databases supporten een meer of minder uitgebreid systeem van row level access, wat ook een beetje in deze richting ligt. Het punt waar je mee zit is dat je je access wilt centraliseren. 1 business class met daarin embedded rules die centraal bepaalt onder welke condities iemand een entity wel of niet in een result mag krijgen.

Als het echt om triviale dingen gaat (b.v. alleen checken of iemand een bepaalde rol heeft), dan kun je dat nog wel in een where clause zetten telkens. Maar zodra de rules "complex" worden (zoals TS aangeeft), dan wil je niet diezelfde rules in 20 queries telkens in SQL dupliceren, zeker niet als er een X aantal joins met andere tabellen nodig is voor de bepaling.

De filtering die hierboven aangegeven wordt kan heel goed werken, als ik me niet vergis beschrijft het J2EE DAO pattern van Sun ook ongeveer zo'n aanpak. Het werkt natuurlijk wel alleen goed als het aantal entities waar een gebruiker toegang tot heeft ongeveer in dezelfde orde van grote ligt als het aantal entities wat er in het totaal is. Eerst een set van 1000.000 entities ophalen, om er na filtering met Java code vervolgens 10 van over te houden is misschien niet de beste aanpak.

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


Verwijderd

Topicstarter
JKVA en flowerp, bedankt voor jullie replies.

Wat ik me afvraag, wat is nu zo'n beetje de gangbare methode om zoiets te doen bij JPA? Is het filteren in Java code nou de meest gebruikte aanpak, of de condities in de where clause zetten?

Verwijderd

Topicstarter
Heeft iemand hier nog een idee over?

  • JKVA
  • Registratie: Januari 2004
  • Niet online

JKVA

Design-by-buzzword fanatic

Verwijderd schreef op dinsdag 22 april 2008 @ 16:15:
Heeft iemand hier nog een idee over?
Het hangt maar net af van de situatie. Als je een beetje handig bent met queries bouwen, zul je de voorkeur geven aan een iets meer advanced query, als je SQL/HQL/JPQL lastig vindt, kun je in het begin beter in Java filteren.

De JPA spec zegt er iig niets over. Ook bij Hibernate zijn ze redelijk neutraal op dit gebied.

Als je gewoon met nette lagen werkt, dus je data access code in DAO's, kun je prima de boel achteraf tunen. "Premature optimization is the root of all evil."

Persoonlijk hou ik wel van queries. Ik vind ze erg expressief en je kunt in een paar regels dingen doen die je met plain Java vaak veel meer code kost. Maar goed, dat is smaak.

Fat Pizza's pizza, they are big and they are cheezy


  • flowerp
  • Registratie: September 2003
  • Laatst online: 11-09 18:20
JKVA schreef op dinsdag 22 april 2008 @ 19:26:
[...]
Persoonlijk hou ik wel van queries. Ik vind ze erg expressief en je kunt in een paar regels dingen doen die je met plain Java vaak veel meer code kost. Maar goed, dat is smaak.
Dat is waar. Een probleem is echter dat je queries niet zo makkelijk kunt combineren met elkaar. Als je je business of access rules in SQL of JPQL hebt staan, dan moet je deze voor elke combinatie dupliceren.

Stel ik heb in mijn database "customers", en een rule die bepaald of een account manager een bepaalde customer wel of niet mag zien. Neem ook even aan dat de rule 'complex' is en b.v. per customer en per account manager aan de hand van diverse condities dit bepaald.

Stel verder dat je nu 2 queries hebt, 1 die alle customers opvraagt die nog een bedrag open hebben staan en een andere query die alle customers opvraagt die ooit product A aangeschaft hebben.

Wat ga je nu doen? Als je de rule in SQL of JPQL uitdrukt, dan moet je deze in zovel query 1 als query 2 opnemen. In de meeste gevallen is SQL en ook JPQL niet van dien aart dat je de resultaten van 1 query makkelijker even weer als input van een 2de query kunt geven. Soms kan het met een view worden opgelost, maar dikwijls komt het neer op gewoon copy-pasten van het SQL fragment dat de rule implementeert in alles queries waar dat nodig is.

Zodra het aantal queries of het aantal rules toe neemt wordt dat erg onhandig om te onderhouden.

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • JKVA
  • Registratie: Januari 2004
  • Niet online

JKVA

Design-by-buzzword fanatic

True, maar daar valt vaak ook nog wel een mouw aan te passen. Bijvoorbeeld de daadwerkelijke check refactoren naar een aparte methode die de where clause als string teruggeeft en die in je query plakken. Bij de Java oplossing zul je waarschijnlijk ook bij een losse filter methode uitkomen.

Wat ik doorgaans het vervelendste vind, is (dit gebeurt weleens bij een niet-optimaal datamodel) dat je de tabellen met de benodigde info erbij moet joinen. Als je query al complex en traag is, wil je dat er doorgaans niet ook nog bij.

Fat Pizza's pizza, they are big and they are cheezy


  • flowerp
  • Registratie: September 2003
  • Laatst online: 11-09 18:20
JKVA schreef op woensdag 23 april 2008 @ 09:53:
True, maar daar valt vaak ook nog wel een mouw aan te passen. Bijvoorbeeld de daadwerkelijke check refactoren naar een aparte methode die de where clause als string teruggeeft en die in je query plakken.
Haha, ja dat ken ik ;) Ooit in een ver verleden toen web apps nog niet bestonden heb ik voor een C++ desktop applicatie naar een pg database eens zo iets gemaakt. Het kwam er ongeveer op neer dat er een query builder class was die een uiteindelijke query uit stukken samenstelde. De where clauses werden dan opgespoord, en met iets als add_to_where_clause(int nr, char* text); kon je dan per clause condities toevoegen. Ik had zelfs nog een speciale functie voor de outer most where clause, die niet alleen iets kon toevoegen aan een where maar ook slim genoeg was om de where zelf toe te voegen als die er nog niet was. 8)

Maar weet je wat het is Jan-Kees, op het laatst gaat dit toch niet werken. Zeker bij de wat complexere rules heb je inderdaad extra joins nodig. Voor sommige queries is het dan nodig dat je rule uit 2 fragmenten bestaat die op aparte plekken moeten komen in je query. Bij de oudere PG waar ik destijds mee werkte zaten we nog met het probleem dat dat ding amper kon optimaliseren. Als je de query iets anders opschreef was ie 10x sneller. Hoewel mijn systeempje een tijdje goed gelopen heeft, ging men op het laatst toch al die rule fragmenten weer met de hand in de query verwerken omdat dat veel sneller was, maar zodra er dan een rule veranderd moest worden was het natuurlijk 1 grote bak ellende.
Bij de Java oplossing zul je waarschijnlijk ook bij een losse filter methode uitkomen.
Het voordeel van het in Java doen is dat je rules en queries heel makkelijk kunt combineren. Stel je hebt iets als:

Java:
1
2
3
public ResultSet applyRule1(ResultSet set);
public ResultSet applyRule2(ResultSet set);
public ResultSet applyRule3(ResultSet set);


Dan is het triviaal om iets op te schrijven als:

Java:
1
2
3
ResultSet final1 = applyRule1(applyRule3(sourceSet));
ResultSet final2 = applyRule1(applyRule2(otherSourceSet));
// etc


Ook kun je dan voor 1 enkele class / entity heel snel checken of het aan de rule voldoet. In de praktijk kom je dat vaak tegen bij input validatie. Een account manager vraag een report voor customer 1607. Mag hij die customer zien? Dat is exact de zelfde rule die ook toegepast wordt als hij de hele lijst met customers opvraagt. Als je de rule toch al in Java hebt, kun je meteen iets doen als isValidRequest(1607);
Als je query al complex en traag is, wil je dat er doorgaans niet ook nog bij.
Performance is natuurlijk een key issue hier. Het kan sneller zijn als je meerdere rules tegelijk stopt in je query. De planner kan deze dikwijls heel snel combineren met elkaar en de query in z'n geheel dus veel sneller uitvoeren vergeleken met de situatie waarbij je eerst de query doet, en dan in meerdere passes er ongeldige rijen uit filtert.

Aan de andere kant...

Te veel joins in je query voeren de meeste planners niet goed uit. Er zit een limiet aan. Voorbij die limiet doen de meeste planners alleen nog een poging om het beste plan te vinden, maar dat is niet gegarandeerd het meest optimale plan. Het kan af en toe dan een heel slecht plan worden, waarbij eerst de query doen en dan in Java rijen weg filteren veel sneller kan zijn.

Voorwaarde is wel dat je de data voor de rule in het geheugen hebt. Als je voor elke rij die je filtert nog eens een query moet gaan doen dan is al je performance weg natuurlijk.

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • JKVA
  • Registratie: Januari 2004
  • Niet online

JKVA

Design-by-buzzword fanatic

Tja, het is niet nieuw dat Java voor de meeste soorten logica meer geschikt is dan een querytaal.

Maar zolang het beperkte checks blijven en je datamodel ondersteunt dergelijke filtering (bijvoorbeeld omdat je wat extra keys opneemt in je tabellen om op te filteren) is het een prima aanpak, zeker wanneer je een ORM gebruikt, want ORM's gebruiken relatief veel geheugen.

Maar het is idd afhankelijk van je situatie. Als het om security gaat, heb je vaak de velden waarop je de logica baseert, toch al in je tabel staan. Bij een werknemer sla je bijvoorbeeld vaak ook het werkgever ID op. Dan kun je daar al op filteren. Maar het wordt natuurlijk lastiger als je op een veld uit de werkgever tabel gaat checken. Of nog erger, op een aggregatie van velden. Of als er een N-M relatie tussen de tabellen ligt.

Ik denk dat het voor de TS het slimste is om gewoon aan de gang te gaan en op het moment dat hij duplicate code tegenkomt, het wegrefactored.

Fat Pizza's pizza, they are big and they are cheezy

Pagina: 1