diff --git a/src/helma/objectmodel/Key.java b/src/helma/objectmodel/Key.java index 5ea5f9e3..73853b49 100644 --- a/src/helma/objectmodel/Key.java +++ b/src/helma/objectmodel/Key.java @@ -23,7 +23,7 @@ public final class Key implements Serializable { public Key (DbMapping dbmap, String id) { this.type = dbmap == null ? "" : dbmap.typename; this.id = id; - hash = this.id.hashCode (); + hash = id.hashCode (); } public Key (String type, String id) { @@ -33,36 +33,46 @@ public final class Key implements Serializable { } public boolean equals (Object what) { - if (what == this) - return true; - if (what == null || !(what instanceof Key)) + try { + Key k = (Key) what; + return (id == k.id || id.equals (k.id)) && (type == k.type || type.equals (k.type)); + } catch (Exception x) { return false; - Key other = (Key) what; - if (type == null) - return (id.equals (other.id) && other.type == null); - else - return (id.equals (other.id) && type.equals (other.type)); + } } public int hashCode () { return hash; } + public void recycle (DbMapping dbmap, String id) { + this.type = dbmap == null ? "" : dbmap.typename; + this.id = id; + hash = id.hashCode (); + } + + public Key duplicate () { + return new Key (type, id); + } + /** * Get the Key for a virtual node contained by this node, that is, a node that does * not represent a record in the database. The main objective here is to generate * a key that can't be mistaken for a relational db key. */ public Key getVirtualKey (String sid) { - return new Key ("", getVirtualID (type, id, sid)); + return new Key ("", makeVirtualID (type, id, sid)); } - public static String getVirtualID (DbMapping pmap, String pid, String sid) { - String ptype = pmap == null ? "" : pmap.typename; - return ptype+"/"+pid + "*h~v*" + sid; + public String getVirtualID (String sid) { + return makeVirtualID (type, id, sid); } - public static String getVirtualID (String ptype, String pid, String sid) { + public static String makeVirtualID (DbMapping pmap, String pid, String sid) { + return makeVirtualID (pmap == null ? "" : pmap.typename, pid, sid); + } + + public static String makeVirtualID (String ptype, String pid, String sid) { return ptype+"/"+pid + "*h~v*" + sid; } diff --git a/src/helma/objectmodel/db/Node.java b/src/helma/objectmodel/db/Node.java index c0ed8a6b..9571560a 100644 --- a/src/helma/objectmodel/db/Node.java +++ b/src/helma/objectmodel/db/Node.java @@ -123,7 +123,7 @@ public class Node implements INode, Serializable { setParent (home); // this.dbmap = null; // generate a key for the virtual node that can't be mistaken for a JDBC-URL - this.id = Key.getVirtualID (parentmap, parentID, propname); + this.id = Key.makeVirtualID (parentmap, parentID, propname); this.name = propname; this.anonymous = false; if (prototype == null) diff --git a/src/helma/objectmodel/db/NodeManager.java b/src/helma/objectmodel/db/NodeManager.java index 0ebad4ea..5ee26179 100644 --- a/src/helma/objectmodel/db/NodeManager.java +++ b/src/helma/objectmodel/db/NodeManager.java @@ -42,7 +42,8 @@ public final class NodeManager { public NodeManager (Application app, String dbHome, Properties props) throws DbException { this.app = app; int cacheSize = Integer.parseInt (props.getProperty ("cachesize", "1000")); - cache = new CacheMap (cacheSize, 0.8f); + // Make actual cache size bigger, since we use it only up to the threshold + cache = new CacheMap ((int) Math.ceil (cacheSize/0.75f), 0.75f); IServer.getLogger().log ("set up node cache ("+cacheSize+")"); safe = new WrappedNodeManager (this); @@ -139,11 +140,11 @@ public final class NodeManager { if (kstr == null) return null; - Key key = new Key (dbmap, kstr); Transactor tx = (Transactor) Thread.currentThread (); // tx.timer.beginEvent ("getNode "+kstr); - + Key key = tx.key; + key.recycle (dbmap, kstr); // See if Transactor has already come across this node Node node = tx.getVisitedNode (key); @@ -169,10 +170,12 @@ public final class NodeManager { } } // synchronized } + } else { + // cache hit } if (node != null) - tx.visitCleanNode (node.getKey (), node); + tx.visitCleanNode (key.duplicate(), node); // tx.timer.endEvent ("getNode "+kstr); return node; @@ -183,21 +186,23 @@ public final class NodeManager { if (kstr == null) return null; - Key key; - // If what we want is a virtual node create a "synthetic" key - if (rel.virtual /*&& home.getState() != INode.VIRTUAL */ || rel.groupby != null) - key = home.getKey ().getVirtualKey (kstr); - // if a key for a node from within the DB - else - key = new Key (rel.other, rel.getKeyID (home, kstr)); - Transactor tx = (Transactor) Thread.currentThread (); // tx.timer.beginEvent ("getNode "+kstr); + Key key = tx.key; + // If what we want is a virtual node create a "synthetic" key + if (rel.virtual /*&& home.getState() != INode.VIRTUAL */ || rel.groupby != null) + key.recycle (null, home.getKey ().getVirtualID (kstr)); + // if a key for a node from within the DB + else + key.recycle (rel.other, rel.getKeyID (home, kstr)); + + // See if Transactor has already come across this node Node node = tx.getVisitedNode (key); if (node != null && node.getState() != Node.INVALID) { +// System.err.println ("CACHE HIT THREAD 2"); // tx.timer.endEvent ("getNode "+kstr); // if we didn't fetch the node via its primary key, refresh the primary key in the cache. // otherwise we risk cache corroption (duplicate node creation) if the node is fetched by its primary key @@ -206,7 +211,7 @@ public final class NodeManager { Node oldnode = (Node) cache.put (node.getKey (), node); if (oldnode != null && oldnode.getState () != Node.INVALID) { cache.put (node.getKey (), oldnode); - cache.put (key, oldnode); + cache.put (key.duplicate(), oldnode); node = oldnode; } } @@ -218,38 +223,44 @@ public final class NodeManager { node = (Node) cache.get (key); if (node == null || node.getState() == Node.INVALID) { +// System.err.println ("CACHE MISS 2"); // The requested node isn't in the shared cache. Synchronize with key to make sure only one // version is fetched from the database. node = getNodeByRelation (db, tx.txn, home, kstr, rel); if (node != null) { Key primKey = node.getKey (); + boolean keyIsPrimary = primKey.equals (key); synchronized (cache) { + // check if node is already in cache with primary key Node oldnode = (Node) cache.put (primKey, node); if (oldnode != null && oldnode.getState () != Node.INVALID) { cache.put (primKey, oldnode); - cache.put (key, oldnode); + if (!keyIsPrimary) + cache.put (key.duplicate(), oldnode); node = oldnode; - } + } else if (!keyIsPrimary) // cache node with secondary key + cache.put (key.duplicate(), node); } // synchronized } - } - - if (node != null) { + } else { +// System.err.println ("CACHE HIT 2"); // update primary key in cache, see above if (!rel.usesPrimaryKey ()) { synchronized (cache) { Node oldnode = (Node) cache.put (node.getKey (), node); if (oldnode != null && oldnode.getState () != Node.INVALID) { cache.put (node.getKey (), oldnode); - cache.put (key, oldnode); + cache.put (key.duplicate(), oldnode); node = oldnode; } } } - tx.visitCleanNode (node.getKey (), node); } + if (node != null) + tx.visitCleanNode (key.duplicate(), node); + // tx.timer.endEvent ("getNode "+kstr); return node; } diff --git a/src/helma/objectmodel/db/Transactor.java b/src/helma/objectmodel/db/Transactor.java index 56d4e806..8398ae87 100644 --- a/src/helma/objectmodel/db/Transactor.java +++ b/src/helma/objectmodel/db/Transactor.java @@ -28,6 +28,9 @@ public class Transactor extends Thread { private volatile boolean active; private volatile boolean killed; + // the transactor reuses a key object to avoid unnecessary object creation + protected Key key; + // Transaction for the embedded database protected DbTxn txn; // Transactions for SQL data sources @@ -49,6 +52,7 @@ public class Transactor extends Thread { active = false; killed = false; timer = new Timer(); + key = new Key ("", ""); } public void visitNode (Node node) {