Datenbankzugriffe sind teurer als Speicherzugriffe, das ist nicht neu. Viele Frameworks wie Hibernate setzen deswegen auf einen Cache, um die Anzahl der Datenbankzugriffe zu minimieren und damit die Performance zu erhöhen. Das Caching passiert dabei meißt unter der Haube und lässt den Entwickler den Fokus auf die eigentlichen Arbeiten behalten.
Mit Google App Engine und Memcached und dem verteilten Cache ist Caching auch in der Cloud möglich. Doch auch wenn bei App Engine keine Persistenzlösung wie Hibernate oder EclipseLink möglich ist, kann man auf eine einfache Weise Caching nachrüsten.
Eine einfache DAO-Klasse
public class NodeDAO { public Node load(long id) { // code } public Node save(Node node) { if (node.getId() == 0) { insert(node); } else { update(node); } return node; } public void delete(Node node) { // code } private void insert(Node node) { // code } private void update(Node node) { // code } }
Relativ einfach, aber erfüllt den Zweck.
Zunächst erstellen wir ein Interface Indexed, damit die Lösung später für verschiedene Klassen möglich ist.
public interface Indexed { public long getId(); }
Und einen Simplen Cache (hier kann man später auf MemCached umrüsten)
public class SimpleCache { static HashMap cache; public static HashMap getInstance(){ if (cache==null){ cache = new HashMap(); } return cache; } }
Als Schlüssel soll hier Klassenname und ID fungieren
Noch ein paar kleine Interfaces für die 3 Cache- bzw Datenbankfunktionen
@Retention(RetentionPolicy.RUNTIME) public @interface SelectCachable {} @Retention(RetentionPolicy.RUNTIME) public @interface SaveCachable {} @Retention(RetentionPolicy.RUNTIME) public @interface DeleteCachable {}
Ein bisschen Guice-Magie
Starten wir mit dem SELECT oder GET – Befehl. Wenn wir einen Datensatz geladen haben, soll der im Cache verewigt werden. Ist er schon im Cache, soll keine Datenbankverbindung hergestellt werden. Wir benutzen den MethodInterceptor.
public class SelectCacher implements MethodInterceptor{ public Object invoke(MethodInvocation call) throws Throwable { Object[] args = call.getArguments(); Object result; if (args.length==1 && args[0] instanceof Long && SimpleCache.getInstance().containsKey(call.getMethod().getReturnType()+ "@" + args[0])){ // aus dem Cache holen result = SimpleCache.getInstance().get( call.getMethod().getReturnType()+ "@" + args[0]); }else{ // Aus der DB Holen result = call.proceed(); SimpleCache.getInstance().put( call.getMethod().getReturnType()+ "@" + args[0],(Indexed) result); } return result; } }
Auch der DELETE-Befehl ist simpel:
public class DeleteCacher implements MethodInterceptor { public Object invoke(MethodInvocation call) throws Throwable { Object[] args = call.getArguments(); Indexed indexer = (Indexed) args[0]; if (args.length==1 && SimpleCache.getInstance().containsKey(indexer.getClass()+ "@" + indexer.getId())){ SimpleCache.getInstance().remove( indexer.getClass()+ "@" + indexer.getId()); } return call.proceed(); } }
Zuletzt noch der PUT Befehl, hier muss man dazu sagen, dass aufgrund der ID entschieden wird, ob ein Insert oder Update geschieht, beim Insert wird bei @@IDENTITY die ID bestimmt und dem Objekt zugewiesen.
public Object invoke(MethodInvocation call) throws Throwable { Object result = call.proceed(); Indexed indexer = (Indexed) result; SimpleCache.getInstance().put(indexer.getClass() + "@" + indexer.getId(), indexer); return result; }
Nun müssen wir noch die Annotationen mit den Interceptors verbinden. Das passiert bei Guice so:
public class CacheModule extends AbstractModule{ protected void configure() { bindInterceptor(any(), annotatedWith(SelectCachable.class), new SelectCacher()); bindInterceptor(any(), annotatedWith(SaveCachable.class), new SaveCacher()); bindInterceptor(any(), annotatedWith(DeleteCachable.class), new DeleteCacher()); } }
Der finale Schritt, jetzt verbinden, oder besser gesagt injizieren, wir alles, was bisher erstellt wurde. Per Factory-Pattern erhalten wir wir nun eine Erweiterte Variante der Basisklasse. Per Annotationen bestimmen wir, welcher Methode welcher Interceptor zugewiesen wird.
public class NodeDAO { public static NodeDAO create() { Injector inj = Guice.createInjector(new CacheModule()); NodeDAO ret = inj.getInstance(NodeDAO.class); return ret; } @SelectCachable public Node load(int id) { // Code } @SaveCachable public Node save(Node node) { if (node.getId() == 0) { insert(node); } else { update(node); } return node; } @DeleteCachable public void delete(Node node) { // Code } private void insert(Node node) { // Code } private void update(Node node) { // Code } }
Mit Google Guice Funktionalitäten nachrüsten ist recht einfach. Die Lösung ist noch nicht ideal, aber ein guter Start, da alle Komponenten austauschbar sind.
Mein kleines Projekt staemmebbcodes hat sich in einem Jahr sehr entwickelt. Etwa 16.000 Benutzer, 5 Sprachen und und und…
Um da den Ueberblick zu behalten und auch anderen die Moeglichkeit zu geben, mal in meinen Code zu schauen, habe ich mich (endlich) entschieden, mal code.google auszuprobieren. Ein Projekt ist ja schnell angelegt, nun wollte ich es nur noch mit meinem Ecclipse verbinden, um nicht alles Webbasiert zu gestalten.
Und das ging erstaunlich einfach, fuer viele Aufgaben gibt es schon vorkunfigurierte Plug-ins.
Subclipse ist das erste Plugin, was schon im Handumdrehen konfiguriert ist, man muss nur beachten, das man zum commiten https statt http benutzt. Das wars schon !
Mylin ist das zweite Plugin, welches wunderbar mit code.google harmoniert. Auf die entsprechene Perspektive wechseln, Refresh druecken und die Offenen Tasks flattern einem um die Ohren (leider !!).
Um interessierten das Ausprobieren zu erleichtern, habe ich die XML mit den entsprechenden Ecclipse-Update-Mirrors verlinkt, damit ihr nicht im Web danach suchen muesst.
Evtl hat ja jetzt sogar einer Lust, bei dem einem oder anderen Projekt mitzuarbeiten, vllt sogar bei Meinem.

Categories
Tag Cloud
Blog RSS
Comments RSS
Last 50 Posts
Back
Back
Void « Default
Life
Earth
Wind
Water
Fire
Light 