Concurrency Control – Optimistic vs. Pessimistic lock
2010. Március 18.
Az előző poszthoz kerestem dolgokat a neten, és találtam egy nagyon jó kis leírást az optimistic és a pessimistic lock-ról (miközben olvasgattam, eszembe jutott, hogy ez az egyik kedvenc kérdés az állásinterjúkon
). Úgy gondoltam megpróbálom összefoglalni magyarul is a dolgot (az eredeti jobb egyébként).
1. Ütközések (collisions) típusai
A tranzakció izolációs szinteknél már volt szó róluk, ezért csak felsorolnám őket:
- Dirty read
- Non-repeatable read
- Phantom read
2. A lock-olás
A lock más userek/alkalmazások hozzáférését limitálja egy adatbázis entity-hez (rekordhoz, táblához, page-hez, …). Két típusa van:
- Write lock: Ebben az esetben a lockoló user vagy alkalmazás szeretné updatelni/törölni az adott entity-t, ezért nem enged read/update/delete hozzáférést senki másnak.
- Read lock: Itt a lock holder csak olvassa az adott entity-t, nem fogja módosítani. Ezért mások is olvashatják az adott entity-t, de senki nem updatelheti vagy törölheti azt.
3. Pessimistic lock
Ebben az esetben az entity lockolva lesz egészen addíg, amíg az alkalmazás memóriájában van (általában objektumként). Tehát amikor az alkalmazásnak szüksége van pl.: egy user objectre, akkor az alkalmazás lockolja az adott user rekordját a DB-ben. A lock mindaddig megmarad, amíg a user object az alkalmazás memóriájában van, és az alkalmazás műveleteket végezhet vele. Így nem lehet ütközésünk.
4. Optimistic lock
- Egyedi azonosító: A rekordot egyedi azonosítóval látjuk el – A forrás rekordnak lesz egy egyedi azonosítója, amely minden updatekor változik.
- Timestamp: A DB-nek kell updatelnie, mert mi kódból nem tudjuk megfelelően…
- Incremental counter: Növeljünk egy számot…
- User Id: Ha az ütközések csak különböző userek tranzakcióikor fordulhatnak elő.
- Generated UUID: Minden updatekor új egyedi azonosítót kap.
- Összehasonlítós módszer: Újra lekérjük az adott objectet a DB-től és összehasonlítjuk a property-jeit az eredetileg lekérttel.
5. Overly optimistic lock
Abban az esetben ha biztosan tudjuk, hogy soha nem lesznek ütközések (pl mert csak 1 user/alkalmazás használja a DB-t vagy csak olvasható tábláink vannak a DB-ben), akkor egyáltalán nem kell foglalkoznuk sem az ütközések detektálásával sem a kezelésével.
Java – Tranzakció kezelés és tranzakció izolációs szintek
2010. Március 17.
1. Tranzakció kezelés
Akkor lehet rá szükség ha több logikailag öszefüggő SQL utasítást szeretnénk egyszerre (közvetlenül egymás után, egy tranzakcióként) végrehajtani. Ezeknek az SQL utasításoknak általában van egy logikai sorrendje. Ha bármelyik SQL utasítás bukik akkor a logikailag ráépülő többi eljárás is bukik. Például:
- Több oldalas regisztráció, ahol minden oldalhoz tartozó adat külön SQL utasítással bekerül az adatbázisba. Ha az x. oldal után a felhasználó nyom egy cancel-t, akkor az eddig végrehajtott SQL insert-eket is érvénytelenítenünk kell
- Banki tranzakciók – átutalás egy számláról egy másikra, vagy kézpénzfelvét az autómatából: Először megterhelődik az egyik számla a kívánt összeggel, ezután jóváíródik a másik számlán, vagy kiadja az autómata. Ha a második lépés meghiúsul (pl.: Az autómata technikai okok miatt nem tud pénzt kiadni) akkor az első tranzakciót is vissza kell vonni.
Ezekben az esetekben az SQL utasításokat egy tranzakcióban kezeljük. Ez a teljes tranzakció csak akkor hajtódik végre, ha mi erre a programunkból direkt felszólítjuk, egészen addig az adatbázis-kezelő elkülönítve kezeli őket. A tranzakció közben bármikor lehetőség van arra, hogy az addig kiadott SQL utasításokat érvénytelenítsük, ezzel az adatbázis visszaáll az eredeti állapotához.
A tranzakciók kezelésének módját a Connection object-en a setAutoCommit() metódussal tudjuk megváltoztatni:
- connection.setAutoCommit(true): Az új adatbázis kapcsolatok esetén ez a default beállítás. Ha az AutoCommit mód be van kapcsolva akkor minden SQL utasítás különálló tranzakcióként lesz kezelve és végrahajtva.
- connection.setAutoCommit(false): Ebben az esetben (AutoCommit mód kikapcsolva), az SQL utasítások csoportokba rendezhetőek, és csak akkor hajtódnak végre/fejeződnek be, ha a connection-ön meghívjuk a commit() vagy rollback() metódusokat. A commit() hatására a módosítások véglegesen bekerülnek az adatbázisba, míg a rollback() visszaállítja az adatbázist a tranzakció kezdetekori állapotába.
2. Tranzakció izolációs szintek
Azt határozzák meg, hogy hogyan kezelje a szerver az egyidejű hozzáférési kérelmeket (read, update, insert) ugyanahhoz az objektumhoz (tábla, rekord, …).
- TRANSACTION_NONE: A tranzakciók nem támogatottak, nincs tranzakciókezelés.
- TRANSACTION_READ_UNCOMMITED: Olvasáskor mindig az aktuális (módosított) értéket kapjuk, még akkor is, ha az adott insert/update tranzakciót a kezdeményező nem commit-olta. A következő problémák léphetnek fel:
- Dirty reads: A kiolvasott rekord változhat más tranzakciók által a mi tranzakciónk ideje alatt. Tehát előfordulhat, hogy a tranzakciónk elején és végén az adott rekordban más értékek szerepelnek, holott mi nem is változtattuk, csak olvastuk…
- Non-repeatable reads: A tábla egy adott sora törlődhet a mi tranzakciónk közben. Így amikor mi a tranzakciónk elején és végén futtatunk egy-egy select-et, akkor a második esetben hiányozhatnak sorok az eredményből.
- Phantom reads: Hasonló az előzőhöz, csak insert-tel. A táblába új sor (fantom sor) kerülhet egy másik tranzakció által miközben a mi tranzakciónk még fut. Így a tranzakciónk végén több sor lesz az adott táblábam, mint amivel számolhattunk az elején…
- TRANSACTION_READ_COMMITED: Olvasáskor mindig az adott rekord véglegesített értéket kapjuk. Ez az esetek 99%-ra használható, a tranzakcióink mindig csak olyan rekordokat olvasnak, amik kommittálva vannak, azaz nincs nyitott tranzakció, ami dolgozna rajtuk. A baj ezzel az, hogy ha sokan írják és olvassák az adott rekordot vagy táblát akkor könnyen kialakulhat az a helyzet, hogy az olvasó tranzakciók arra várnak hogy az írás (pl egy nagy tábla update-je) befejeződjön. Ez a főleg a rekordokra vonatkozik így csak a dirty read-től véd, probléma lehet:
- a non-repeatable read
- és a phantom read
- TRANSACTION_REPEATABLE_READ: Ez annyival jobb a READ_COMMITTED-nél, hogy már a non-repeatable read hibát is képes kiszűrni a tranzakcióból. Egyszerűbben: csak a recordok commit-olt értékeit használja és a recordok tranzakció közbeni törlése nem befolyásolja a select-eket. Ebben az esetben csak egy probléma marad:
- a phantom read
- TRANSACTION_SERIALIZABLE: Annyival több a REPEATABLE_READ-től, hogy más tranzakció nem írhatja felül a mi tranzakciónk által olvasott értékeket, azaz addig várakoztatja azokat míg be nem fejeződik a tranzakciónk. Így nem fognak tranzakció közben fantom sorok keletkezni a táblában. Itt elég problémás lehet, ha több résztvevő folyamatosan olvas egy táblát, amíg az updatelő szál várakozik, mert a tábla lock-olva van és nem tud bele írni…
Ezek szintek föntről lefelé egyre költségesebbek lesznek az adatbázisnak, így a tranzakciók végrehajtási sebessége lassul. Az izolációs szinteket a Connection objektumon a setTransactionIsolation() metódussal állíthatjuk be a tranzakció megkezdése előtt. Például:
con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
C# WS Client Generálása (Service Reference solution)
2010. Március 12.
Ez az újabb megoldás WS Client készítésére C#-ban. A régi “Web Reference” megoldás megtalálható itt. A .NET 3.0-ás verziójában már Service Reference-eket tudunk hozzáadni a project-hez. A lényeg, hogy itt egyszerűbb lesz a SOAP Header-t módosítani az authentikáció érdekében. Egy nagyon jó leírás a Web reference és a Service Reference közötti különbségről: http://andrewtokeley.net/archive/2008/07/10/the-difference-between-ldquoadd-web-referencerdquo-and-ldquoadd-service-referencerdquo.aspx
1. Készítsünk egy új projectet a Visual Studióban.
Legyen ez egy egyszerű Konzolos alkalmazás:
2. Adjuk hozzá a Service Reference-t a Projectünkhöz:
Ekkor a Solution Explorerben megjelenik a behivatkozott Service.
3. A kliens létrehozása:
Az autómatikusan legenerált Program.cs-t egésszítsük ki a következőképpen:
Ez csak egy teszt a WS methodok meghívására. Ez így már működne is, ha nem kérne a WS Server SoapHeader authentikációt. Viszont a mi szerverünk a következő formátumú Soap Header-t kér:
4. A LoginCredentials class
Csinálnunk kell egy Data class-t a login credentials -hoz. Legyen benne userid, password és groupid property (Ezek szükségesek a header-be). Ez a LoginCredentials class származzon a SoapHeader ősosztályból (Ahhoz, hogy ez így működjön, hozzá kell adnunk a System.Web.Services referenciát). Vigyázni! A LoginCredentials class legyen publikus, különben nem lehet serializálni.
5. A “behavior” class
Egy jó leírás a következő linken: http://weblogs.asp.net/jdanforth/archive/2008/10/27/wcf-client-calling-asmx-service-with-soap-headers.aspx.
Kell csinálnunk egy ServiceBehavior classt, amivel manipulálni tudjuk a kimenő SOAP messageket. Ehhez a következők kellenek:
- BehaviorExtensionElement: Ezen osztály segítségével tudjuk módosítani a kliens végpont tulajdonságait.
- IClientMessageInspector: Messagek módosítására.
- IEndpointBehavior: Kiterjeszthető vele a végpont viselkedése.
Példa:
6. Az app.config: EndPoitBehavior beállítása:
Ha megvan a “behavior” class-unk, akkor már csak be kell állítanunk az app config-ban, hogy az alkalmazás használja is ezt az osztályt:
Mostmár futtathatjuk is az alkalmazást. A kliens által elküldött SOAP message-eknek mostmár lesz header-je és a header tartalmazni fogja a credentials-t.
Property validálás Java-ban
2010. Március 10.
Az egész ott kezdődött, hogy a Kanadaiak kedvéért kénytelen voltam megnézni hogyan validálja az Amerikaiak indiaiakkal íratott csodarendszere a first/last name property-ket. És kicsit megrémültem:
Rossz példa, ne használd!
private static Vector getFirstLastNameAlphaChars(Vector inputvectors){
for(int i=65; i < 91; i++){
char[] charbuff = new char[1];
char ch = (char) i;
charbuff[0] = ch;
String letters = new String(charbuff);
inputvectors.add(letters);
}
for(int i=97; i < 123; i++){
char[] charbuff = new char[1];
char ch = (char) i;
charbuff[0] = ch;
String letters = new String(charbuff);
inputvectors.add(letters);
}
String exStr = new String(new char[]{'!'});
inputvectors.add(exStr);
String aposStr = new String(new char[]{'\''});
inputvectors.add(aposStr);
String dotStr = new String(new char[]{'.'});
inputvectors.add(dotStr);
String plusStr = new String(new char[]{'+'});
inputvectors.add(plusStr);
String hypenStr = new String(new char[]{'-'});
inputvectors.add(hypenStr);
String underScoreStr = new String(new char[]{'_'});
inputvectors.add(underScoreStr);
String apos1Str = new String(new char[]{'`'});
inputvectors.add(apos1Str);
String atStr = new String(new char[]{'@'});
inputvectors.add(atStr);
String spaceStr = new String(new char[]{' '});
inputvectors.add(spaceStr);
return inputvectors;
}
public static boolean validateString(String inputString) {
boolean result = true;
Vector allowedchars = getFirstLastNameAlphaChars(new Vector());
allowedchars = getNumericChars(allowedchars);
if(inputString!=null && inputString.trim()!=null)
{
inputString = inputString.trim();
for(int i = 0; i < inputString.length(); i++)
{
char letters = inputString.charAt(i);
char[] charbuff = {letters};
if(!allowedchars.contains(new String(charbuff)))
{
result = false;
break;
}
}
}
return result;
}
Nem is részletezném a hibákat, amik első ránézésre látszanak. Az hogy ez a kód lassú, az egy dolog. De! Ha engedélyezni kellene egy új karaktert, akkor rögtön kód módosítás + új release kell. Ami sok idő.
1. Property validálás Regexp-pel:
Egy jó leírás a regexpekről: http://www.regular-expressions.info/java.html
2. OVal (Object Validation) Framework:
Nagyon jó kis cucc, a Java objectek property-jeire és a method-ok argumentumaira tudunk annotációkban feltételeket tenni. Itt találhatunk bővebb leírást róla. Csak annyit kell tenni, hogy az OVal jar file-ját felvesszük a projectre. Rögtön két lehetséges megoldást találtam a fenti problémára:
a.) Business Object Property validálása:
Kell egy Business Object, amit annotációkkal preparálunk:
Majd amikor ezt létrehozzuk egy másik class-ban, a következő képpen tudjuk validálni:
b.) Method Argumentum validálása:
Egy kicsit bonyolultabb a helyzet ha a methodok argumentumait szeretnénk validálni. Ehhez az Eclips-es projectet kicsit preparálnunk kell. Ehhez nem árt elolvasnunk a következő leírást: http://oval.sourceforge.net/userguide.html#d4e183 (Telepítsük az AspectJ plugin-t, Konvertáljuk a projectet AspectJ projectre, hozzunk létre egy új Aspect-et a projecten, amely a net.sf.oval.guard.GuardAspect -ből származik. )
A validálni kívánt methodunk a következőképpen néz ki:
Ha a project megfelelően van preparálva, akkor ha ezt a method-ot egy invalid (pl.: “ááá”) stringgel hívjuk meg, akkor ConstraintsViolatedException-t fog dobni a method.
3. Statisztikák:
14 db különböző (valid/invalid vegyesen) string-et validáltam mind a 4 megoldással 10.000-szer egymás után. Az idők ms-ben a következők lettek:
- Bad Solution: 2000 ms
- RegExp validation: 312 ms
- OVal property validation: 1844 ms
- OVal method argument validation: 1813 ms
4. Miért lehet mégis jó választás az OVal:
Természetesen az OVal esetében is van lehetőség kölső XML fileban definiálni a validációs szabályokat, így elég konfigurálhatóvá válik a validálás…
Tárolt eljárás meghívása C# és Java kódból
2010. Március 3.
1. Feladat:
Az ügyfél csodálatos alkalmazása Back Office adatok szinkronizálásra biztosít tárolt eljárásokat. (Eredetileg úgy wolt, hogy a WS getter metódusai szarok, hibásak és lassúak voltak. Erre kitalálták, hogy úr isten, itt nagyon nagy mennyiségű adat van, meg hogy milyen nehéz már a WS-t kijavítani, és mennyivel egyszerűbb írni néhány tárolt eljárást. A business unit érje el közvetlenül a DB-t, és hívja meg az erre gyártott tárolt eljárásokat. Advanced Architecture!) A verzióváltás miatt ezeket is le akartam tesztelni. Még a végén nem működik az új rendszeren valamelyik, és a BU jön nekem sírni, hogy de miért…
2. A tárolt eljárás
A tárolt eljárás egy kicsit egyszerűsítve következőképpen van létrehozva:
Ahogy látható, a tárolt eljárásunk belső tárolt eljárásokat hív meg. A paraméterei:
- “RequestType” input paraméter: Melyik belső SP-t hívja meg.
- “RequestFlag” in/out paramétere: Pager megoldás. Ha több mint X record van amit vissza kellene adni, akkor visszaadja az első X-et, és a “RequestFlag” false lesz. Ilyenkor akkor újra meg kell hívni az SP-nket az előzőleg használt “RequestType”-pal és erre visszatér a következő X recorddal.
- XML visszatérési érték.
3. C# megoldás
Először lefuttattam azt a C# kódot, amit még az előző verzióhoz csináltam a business unitnak. (Igen, kicsit kézi vezérlésűek, meg kell nekik mutatni, hogy hogyan kell csinálni.) A pagerrel (“RequestFlag”) most nem foglalkozunk. Ez a következőképpen néz ki:
Ne felejtsük el az App.config-ba betenni a DB connection részleteit:
Ha lefuttatjuk pl.: “Users” requestType-pal, akkor az XmlDocument-ünk tartalmazni fogja a usereket.
4. A Java megoldás
Kis naívan azt hittem, hogy a Connection-ön meghívom a prepareCall() metódust ami ugye létrehozza nekem a CallableStatementet, ezen beállítom a paramétereit, hívok rajta egy execute()-ot, és a ResultSet-ből kiolvasom a végeredményt. Csak hogy ez így nem működik. Futottam vele néhány kört, mire debuggolás közben rájöttem, hogy ha sikerül feltartanom a programot az execute és a ResultSet processzálása között, akkor a ResultSet-ben feltűnik egy “XmlData” property amiben benne van a várt eredmény. Megpróbáltam az MS jdbc drivere helyett a JTDS jdbc drivert is használni, de azzal is hasonló lett az eredmény.
A következző thread segített megérteni mi is a probléma: http://dbaspot.com/forums/ms-sqlserver/223916-problem-boolean-return-parameter-callablestatement-execute.html (Thanks to Joe Weinstein @ BEA Systems).
A mi esetünkben a példa Java kód a következő:
Némi magyarázat: Azért van erre az egészre szükség, mert a tárolt eljárás végrehajtása elég sokáig is eltarthat. A CallableStatement getMoreResults() metódusa true-val tér vissza, ha a következő result egy ResultSet object. Az getUpdateCount() -1 -gyel tér vissza, ha befejeződött a tárolt eljárás. Tehát a kilépési feltételünk: nincs több result (a getMoreResult() false-sal tért vissza), és nincs is több updatelendő sora az SP-nek (a getUpdateCount() -1-et adott.)
A szép az egészben hogy a coding standards alapján ezért a while(true)/break megoldásért minimum nyilvánosan kivégeznének (De ahogy Joe@BEA írja, ez a legjobb kód feldolgozni bármilyen statementet).






