diff options
Diffstat (limited to 'org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/SmartPreparedStatementCache.java')
-rw-r--r-- | org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/SmartPreparedStatementCache.java | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/SmartPreparedStatementCache.java b/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/SmartPreparedStatementCache.java new file mode 100644 index 0000000000..0ea0e9109b --- /dev/null +++ b/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/SmartPreparedStatementCache.java @@ -0,0 +1,291 @@ +/** + * Copyright (c) 2004 - 2011 Eike Stepper (Berlin, Germany) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Stefan Winkler - initial API and implementation + */ +package org.eclipse.emf.cdo.server.internal.db; + +import org.eclipse.emf.cdo.server.internal.db.bundle.OM; + +import org.eclipse.net4j.db.DBException; +import org.eclipse.net4j.util.ImplementationError; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.HashMap; + +/** + * @author Stefan Winkler + * @since 2.0 + */ +public class SmartPreparedStatementCache extends AbstractPreparedStatementCache +{ + private Cache cache; + + private HashMap<PreparedStatement, CachedPreparedStatement> checkedOut = new HashMap<PreparedStatement, CachedPreparedStatement>(); + + public SmartPreparedStatementCache(int capacity) + { + cache = new Cache(capacity); + } + + public PreparedStatement getPreparedStatement(String sql, ReuseProbability reuseProbability) + { + CachedPreparedStatement cachedStatement = cache.remove(sql); + if (cachedStatement == null) + { + cachedStatement = createCachedPreparedStatement(sql, reuseProbability); + } + + PreparedStatement result = cachedStatement.getPreparedStatement(); + checkedOut.put(result, cachedStatement); + + return result; + } + + /** + * @param ps + * the prepared statement to be released to the cache, or <code>null</code>. + */ + public void releasePreparedStatement(PreparedStatement ps) + { + if (ps != null) // Bug 276926: Silently accept ps == null and do nothing. + { + CachedPreparedStatement cachedStatement = checkedOut.remove(ps); + cache.put(cachedStatement); + } + } + + @Override + protected void doBeforeDeactivate() throws Exception + { + if (!checkedOut.isEmpty()) + { + OM.LOG.warn("Statement leak detected"); //$NON-NLS-1$ + } + } + + private CachedPreparedStatement createCachedPreparedStatement(String sql, ReuseProbability reuseProbability) + { + try + { + Connection connection = getConnection(); + PreparedStatement stmt = connection.prepareStatement(sql); + return new CachedPreparedStatement(sql, reuseProbability, stmt); + } + catch (SQLException ex) + { + throw new DBException(ex); + } + } + + /** + * @author Stefan Winkler + */ + private static final class Cache + { + private CacheList lists[]; + + private HashMap<String, CachedPreparedStatement> lookup; + + private int capacity; + + public Cache(int capacity) + { + this.capacity = capacity; + + lookup = new HashMap<String, CachedPreparedStatement>(capacity); + + lists = new CacheList[ReuseProbability.values().length]; + for (ReuseProbability prob : ReuseProbability.values()) + { + lists[prob.ordinal()] = new CacheList(); + } + } + + public void put(CachedPreparedStatement cachedStatement) + { + // refresh age + cachedStatement.touch(); + + // put into appripriate list + lists[cachedStatement.getProbability().ordinal()].add(cachedStatement); + + // put into lookup table + if (lookup.put(cachedStatement.getSQL(), cachedStatement) != null) + { + throw new ImplementationError(cachedStatement.getSQL() + " already in cache"); //$NON-NLS-1$ + } + + // handle capacity overflow + if (lookup.size() > capacity) + { + evictOne(); + } + } + + private void evictOne() + { + long maxAge = -1; + int ordinal = -1; + + for (ReuseProbability prob : ReuseProbability.values()) + { + if (!lists[prob.ordinal()].isEmpty()) + { + long age = lists[prob.ordinal()].tail().getAge(); + if (maxAge < age) + { + maxAge = age; + ordinal = prob.ordinal(); + } + } + } + + remove(lists[ordinal].tail().getSQL()); + } + + public CachedPreparedStatement remove(String sql) + { + CachedPreparedStatement result = lookup.remove(sql); + if (result == null) + { + return null; + } + + lists[result.getProbability().ordinal()].remove(result); + return result; + } + + /** + * @author Stefan Winkler + */ + private class CacheList + { + private CachedPreparedStatement first; + + private CachedPreparedStatement last; + + public CacheList() + { + } + + public void add(CachedPreparedStatement s) + { + if (first == null) + { + first = s; + last = s; + s.previous = null; + s.next = null; + } + else + { + first.previous = s; + s.next = first; + first = s; + } + } + + public void remove(CachedPreparedStatement s) + { + if (s == first) + { + first = s.next; + } + + if (s.next != null) + { + s.next.previous = s.previous; + } + + if (s == last) + { + last = s.previous; + } + + if (s.previous != null) + { + s.previous.next = s.next; + } + + s.previous = null; + s.next = null; + } + + public CachedPreparedStatement tail() + { + return last; + } + + public boolean isEmpty() + { + return first == null; + } + } + } + + /** + * @author Stefan Winkler + */ + private static final class CachedPreparedStatement + { + private long timeStamp; + + private String sql; + + private ReuseProbability probability; + + private PreparedStatement statement; + + /** + * DL field + */ + private CachedPreparedStatement previous; + + /** + * DL field + */ + private CachedPreparedStatement next; + + public CachedPreparedStatement(String sql, ReuseProbability prob, PreparedStatement stmt) + { + this.sql = sql; + probability = prob; + statement = stmt; + timeStamp = System.currentTimeMillis(); + } + + public PreparedStatement getPreparedStatement() + { + return statement; + } + + public long getAge() + { + long currentTime = System.currentTimeMillis(); + return (currentTime - timeStamp) * probability.ordinal(); + } + + public void touch() + { + timeStamp = System.currentTimeMillis(); + } + + public String getSQL() + { + return sql; + } + + public ReuseProbability getProbability() + { + return probability; + } + } +} |