we implement connection pooling ourselves, but why it always out of connection? - java

Following is the source code:
//public class ConnectionPool implements Runnable
public class ConnectionPool {
private static Logger loger_error = Logger.getLogger("error");
// JDBC Driver name
String driverName;
// JDBC Connection URL
String connectionURL;
// Minimum size of the pool
int connectionPoolSize;
// Maximum size of the pool
int connectionPoolMax;
// Maximum number of uses for a single connection, or -1 for none
int connectionUseCount;
// Maximum connection idle time (in minutes)
int connectionTimeout;
// Additional JDBC properties
String userName;
String password;
// The Connection pool. This is a vector of ConnectionObject
// objects
Vector pool;
// The maximum number of simultaneous connections as reported
// by the JDBC driver
int maxConnections = -1;
// Scheduler scheduler;
// Timeout value
public static int TIMEOUT_MS = 20000;
/**
* Initializes the ConnectionPool object using 'ConnectionPool.cfg' as the
* configuration file
*
* #return true if the ConnectionPool was initialized properly
*/
/*
* public boolean initialize() throws Exception { return
* initialize("com/omh/jdbc/ConnectionPool.cfg"); }
*/
/**
* Initializes the ConnectionPool object with the specified configuration
* file
*
* #param config
* Configuration file name
* #return true if the ConnectionPool was initialized properly
*/
public void initialize(String driverName, String connectionURL,
int connectionPoolSize, int connectionPoolMax,
int connectionUseCount, int connectionTimeout, String userName,
String password) throws Exception {
this.driverName = driverName;
this.connectionURL = connectionURL;
this.connectionPoolSize = connectionPoolSize;
this.connectionPoolMax = connectionPoolMax;
this.connectionUseCount = connectionUseCount;
this.connectionTimeout = connectionTimeout;
this.userName = userName;
this.password = password;
createPool();
// scheduler = new Scheduler();
// scheduler.schedule(this, TIMEOUT_MS);
}
/**
* Destroys the pool and it's contents. Closes any open JDBC connections and
* frees all resources
*/
public void destroy() {
try {
// Clear our pool
if (pool != null) {
// Loop throught the pool and close each connection
for (int i = 0; i < pool.size(); i++) {
((MangoDBConnection) pool.elementAt(i)).closeConnection();
}
}
pool = null;
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* Gets an available JDBC Connection. Connections will be created if
* necessary, up to the maximum number of connections as specified in the
* configuration file.
*
* #return JDBC Connection, or null if the maximum number of connections has
* been exceeded
*/
public synchronized MangoDBConnection getConnection() {
// If there is no pool it must have been destroyed
if (pool == null) {
return null;
}
MangoDBConnection connectionObject = null;
int poolSize = pool.size();
// Get the next available connection
for (int i = 0; i < poolSize; i++) {
// Get the ConnectionObject from the pool
MangoDBConnection co = (MangoDBConnection) pool.elementAt(i);
// If this is a valid connection and it is not in use,
// grab it
if (co.isAvailable()) {
connectionObject = co;
break;
}
}
// No more available connections. If we aren't at the
// maximum number of connections, create a new entry
// in the pool
if (connectionObject == null) {
if ((connectionPoolMax < 0)
|| ((connectionPoolMax > 0) && (poolSize < connectionPoolMax))) {
// Add a new connection.
int i = addConnection();
// If a new connection was created, use it
if (i >= 0) {
connectionObject = (MangoDBConnection) pool.elementAt(i);
}
} else {
LogManager.log("Maximum number of connections exceeded");
loger_error.error("Maximum number of connections exceeded");
}
}
// If we have a connection, set the last time accessed,
// the use count, and the in use flag
if (connectionObject != null) {
connectionObject.use();
connectionObject.touch();
}
return connectionObject;
}
/**
* Places the connection back into the connection pool, or closes the
* connection if the maximum use count has been reached
*
* #param Connection
* object to close
*/
public synchronized void releaseConnection(MangoDBConnection con) {
removeFromPool(con);
}
public synchronized void release(MangoDBConnection con) {
if ((connectionUseCount > 0)
&& (con.getUseCount() >= connectionUseCount)) {
removeFromPool(con);
// add new connection upon releasing one
addConnection();
} else {
con.touch();
con.free();
}
/*
* // Find the connection in the pool int index = find(con);
* System.out.println("close"); if (index != -1) { ConnectionObject co =
* (ConnectionObject) pool.elementAt(index);
* // If the use count exceeds the max, remove it from // the pool. if
* ((connectionUseCount > 0) && (co.useCount >= connectionUseCount)) {
* trace("Connection use count exceeded"); removeFromPool(index); } else { //
* Clear the use count and reset the time last used co.touch();
* co.free(); } }
*/
}
/**
* Prints the contents of the connection pool to the standard output device
*/
public void printPool() {
printPool(new PrintWriter(System.out));
}
/**
* Prints the contents of the connection pool to the given PrintWriter
*/
public void printPool(PrintWriter out) {
out.println("--ConnectionPool--");
if (pool != null) {
for (int i = 0; i < pool.size(); i++) {
MangoDBConnection co = (MangoDBConnection) pool.elementAt(i);
out.println("" + i + "=" + co);
}
}
}
/**
* Returns an enumeration of the ConnectionObject objects that represent the
* pool
*/
public Enumeration getConnectionPoolObjects() {
return pool.elements();
}
public int returnConnectionCount() {
return connectionUseCount;
}
public int returnMaxPoolSize() {
return connectionPoolMax;
}
public int returnInitPoolSize() {
return connectionPoolSize;
}
/**
* Removes the ConnectionObject from the pool at the given index
*
* #param index
* Index into the pool vector
*/
private synchronized void removeFromPool(MangoDBConnection con) {
// Make sure the pool and index are valid
if (pool != null) {
con.closeConnection();
pool.removeElement(con);
}
}
/**
* Creates the initial connection pool. A timer thread is also created so
* that connection timeouts can be handled.
*
* #return true if the pool was created
*/
private void createPool() throws Exception {
// Dump the parameters we are going to use for the pool.
// We don't know what type of servlet environment we will
// be running in - this may go to the console or it
// may be redirected to a log file
LogManager.log("JDBCDriver = " + driverName);
LogManager.log("JDBCConnectionURL = " + connectionURL);
LogManager.log("ConnectionPoolSize = " + connectionPoolSize);
LogManager.log("ConnectionPoolMax = " + connectionPoolMax);
LogManager.log("ConnectionUseCount = " + connectionUseCount);
LogManager.log("ConnectionTimeout = " + connectionTimeout + " seconds");
LogManager.log("Registering " + driverName);
Driver d = (Driver) Class.forName(driverName).newInstance();
// Create the vector for the pool
pool = new Vector();
// Bring the pool to the minimum size
fillPool(connectionPoolSize);
}
/**
* Adds a new connection to the pool
*
* #return Index of the new pool entry, or -1 if an error has occurred
*/
public int addConnection() {
int index = -1;
try {
// Calculate the new size of the pool
int size = pool.size() + 1;
// Create a new entry
fillPool(size);
// Set the index pointer to the new connection if one
// was created
if (size == pool.size()) {
index = size - 1;
}
} catch (Exception ex) {
System.out.println("SSSSSSS");
ex.printStackTrace();
}
return index;
}
/**
* Brings the pool to the given size
*/
private synchronized void fillPool(int size) throws Exception {
String userID = this.userName;
String password = this.password;
// userID = getPropertyIgnoreCase(JDBCProperties, "user");
// password = getPropertyIgnoreCase(JDBCProperties, "password");
// Loop while we need to create more connections
while (pool.size() < size) {
MangoDBConnection co = new MangoDBConnectionMSSQL();
// Create the connection
co.makeConnection(connectionURL, userID, password);
// Do some sanity checking on the first connection in
// the pool
if (pool.size() == 0) {
// Get the maximum number of simultaneous connections
// as reported by the JDBC driver
maxConnections = co.getMaxConnections();
}
// Give a warning if the size of the pool will exceed
// the maximum number of connections allowed by the
// JDBC driver
if ((maxConnections > 0) && (size > maxConnections)) {
LogManager
.log("WARNING: Size of pool will exceed safe maximum of "
+ maxConnections);
}
// Clear the in use flag
co.free();
// Set the last access time
co.touch();
pool.addElement(co);
}
} // fillPool()
/**
* Gets a the named propery, ignoring case. Returns null if not found
*
* #param p
* The property set
* #param name
* The property name
* #return The value of the propery, or null if not found
*/
private String getPropertyIgnoreCase(Properties p, String name) {
if ((p == null) || (name == null))
return null;
String value = null;
// Get an enumeration of the property names
Enumeration enumeration = p.propertyNames();
// Loop through the enum, looking for the given property name
while (enumeration.hasMoreElements()) {
String pName = (String) enumeration.nextElement();
if (pName.equalsIgnoreCase(name)) {
value = p.getProperty(pName);
break;
}
}
return value;
}
/**
* Called by the timer each time a clock cycle expires. This gives us the
* opportunity to timeout connections
*/
/*
* public synchronized void run() { // No pool means no work if (pool ==
* null) { return; }
* // Get the current time in milliseconds long now =
* System.currentTimeMillis();
* // Check for any expired connections and remove them for (int i =
* pool.size() - 1; i >= 0; i--) { ConnectionObject co = (ConnectionObject)
* pool.elementAt(i);
* // If the connection is not in use and it has not been // used recently,
* remove it if (!co.inUse) { if ((connectionTimeout > 0) && (co.lastAccess +
* (connectionTimeout * 1000) < now)) { removeFromPool(i); } } }
* // Remove any connections that are no longer open for (int i =
* pool.size() - 1; i >= 0; i--) { ConnectionObject co = (ConnectionObject)
* pool.elementAt(i); try { // If the connection is closed, remove it from
* the pool if (co.con.isClosed()) { trace("Connection closed
* unexpectedly"); removeFromPool(i); } } catch (Exception ex) { } }
* // Now ensure that the pool is still at it's minimum size try { if (pool !=
* null) { if (pool.size() < connectionPoolSize) {
* fillPool(connectionPoolSize); } } } catch (Exception ex) {
* ex.printStackTrace(); }
* // Reschedule ourselves scheduler.schedule(this, TIMEOUT_MS); }
*/
}
Anyone have good idea ?
How to implement connection pooling?

A common cause for leakage of resources in a resource pool (like your connection pool) is that some client of the pool is failing to release the resources under some circumstances. Here's an example:
Resource resource = pool.getResource();
...
// do stuff
...
pool.releaseResource(resource);
This will leak resources if an exception is thrown in the "do stuff" section and allowed to propagate. A non-leaky version of the above is:
Resource resource = pool.getResource();
try {
...
// do stuff
...
} finally {
pool.releaseResource(resource);
}
EDIT: As #Adamski points out, there is no "magic bullet" solution that will solve this kind of problem. The best that I can suggest is to do the following:
Search through your codebase to find all places where a resource is requested from the pool. Then starting at each point, check for leaks and fix; e.g. based on the above pattern.
Create a test suite that exercise all of your request types and run it repeatedly against your service
One more thing. Don't be tempted to try to "fix" the problem by using a finalizer to deal with lost resources. That may make the problem seem to go away, only to reappear later when your system is heavily loaded, or Someone Important is watching you do a demo.

Just as suggestion database pool management can be tricky, if you have further needs like adding connection timeout or adding pool management strategy (may be the case if you have several several thread using the same connection).
So I would suggest to use open source solution such as the Apache DHCP or other open source solution. You will still have to properly close your connections (as Suggested by Stephen C) but it will be give you more flexibility, if you need to implement more complex stuff.

I would store the point in time (and the requesting thread?), when a connection is borrowed in getConnection(), and add a thread to remove them from the pool after some minutes. This could help you to find the culprit :)
You shouldn't close the connection, except there won't be any long going task. I would simply log this.

Related

java ScheduledFuture getDelay return negative value

I am using ScheduledExecutorService, Semaphore and ScheduledFuture to write a rate limiting function, simply put, when a client reaches the limit, server will return error 429 with "msg please try after %d second".
I use scheduledFuture.getDelay(TimeUnit.SECONDS) to get value of %d. For the first or second attempts, it acts normal, i.e. allow access unit reach the limit and showing how many seconds to wait afterward. Then getDelay starts showing negative value. Does it mean the ScheduledExecutorService not working properly?
following is the snippet
public RateLimiter(int permits, long durationInMillis){
this.semaphore = new Semaphore(permits);
this.permits = permits;
this.durationInMillis = durationInMillis;
scheduleReplenishment();
}
public boolean allowAccess() {
return semaphore.tryAcquire();
}
public long nextReplenishmentTime() {
return scheduledFuture.getDelay(TimeUnit.SECONDS);
}
public void stop() {
scheduler.shutdownNow();
}
public void scheduleReplenishment() {
scheduledFuture = scheduler.schedule(() -> {
semaphore.release(permits - semaphore.availablePermits());
}, durationInMillis, TimeUnit.MILLISECONDS);
}
If the task has done, the getDelay(TimeUnit) will be negative. To show it, I add two parameters to scheduleReplenishment(), and change getReplenishmentTime() to printReplenishmentTime().
Note1: If you create a Future<>, and replace one with another, you should care about the deleted one...
Note2: If you want test Future<> and Semaphore, don't release the allocated resources immediately.
private final ConcurrentSkipListMap<String, ScheduledFuture<?>> scheduledFutures
= new ConcurrentSkipListMap<>();
private final AtomicInteger counter = new AtomicInteger();
public void printReplenishmentTime() {
scheduledFutures.forEach((name, f) -> {
final long delay = f.getDelay(TimeUnit.SECONDS);
System.out.println(name + " delay " + delay);
});
}
/**
* try acquire one permit once from {#code semaphore},
* then wait {#code waitInMillis}, until all permits used.
*
* #param waitInMillis after successfully used one permit, wait
* #param permits all permits to use, best if permits #gt; 2
*/
public void scheduleReplenishment(final long waitInMillis, final int permits) {
final String name = "future" + counter.getAndIncrement();
scheduledFutures.put(name, scheduler.schedule(() -> {
try {
for (int permit = permits; 0 < permit;) {
final boolean ack = semaphore.tryAcquire(1);
System.out.println(name + " " + (ack ? "acquire" : "not acquire")
+ " one, but need " + permit);
if (ack) {
permit--;
}
if (0 < permit) {
try {
Thread.sleep(waitInMillis);
} catch (final InterruptedException e) {
System.out.println(name + " interrupted, exiting...");
return;
}
}
}
System.out.println(name + " done");
} finally {
semaphore.release(permits - permit);
}
// BAD CODE: semaphore.availablePermits() for debugging purposes
// only, maybe 0 release...
// semaphore.release(permits - semaphore.availablePermits());
}, durationInMillis, TimeUnit.MILLISECONDS));
}
scheduler.schedule() is a one time go function, that's why it shows negative getDelay() value.

Interval lock Implementation

I am looking for an implementation of interval lock. Given an interval (x, y), a thread can acquire the lock if no-one else is acquiring any interval that contains point p where x <= p <= y.
My current idea is maintaining an array of existing granted intervals (x1, y1, x2, y2, ..., xn, yn) where x1 < y1 < x2 < y2 < ... < xn < yn and checks to see if (x, y) overlaps with any of those intervals.
The search takes O(logn) time which makes me happy. However, when the search returns that there is some overlaps, the lock function needs to somehow retry efficiently until it can acquire the lock when others release their interval locks. Busy-waiting or sleep seems not a good idea.
Is there a way to implement the retry efficiently?
As #c0der suggested I've made an implementation that simply tracks the locked intervals.
My code implies a Range class that ...
is immutable
has a lower and upper bound (extending to unbounded ranges shouldn't be too hard)
properly implements equals() and hashCode()
The RangeLock class currently only implements a blocking lock method. Unlocking is done through a returned Unlocker instance. This is to avoid threads not having acquired the lock, being able to unlock a given Range.
public class RangeLock<T extends Comparable<? super T>> {
private final SortedSet<Range<T>> locked = new TreeSet<>(Comparator.comparing(Range::lower));
private final Object lock = new Object();
public Unlocker lock(Range<T> range) throws InterruptedException {
synchronized (lock) {
while (!available(range)) {
lock.wait();
}
locked.add(range);
return () -> {
synchronized (lock) {
locked.remove(range);
lock.notifyAll();
}
};
}
}
private boolean available(Range<T> range) {
SortedSet<Range<T>> tailSet = locked.tailSet(range);
SortedSet<Range<T>> headSet = locked.headSet(range);
return (tailSet.isEmpty() || !tailSet.first().overlaps(range)) && (headSet.isEmpty() || !headSet.last().overlaps(range));
}
public interface Unlocker {
void unlock();
}
}
I think the question is essentially about an efficient way to have a thread wait and retry.
How about listening to changes in the
array of existing granted intervals
and retry only when it has changed ?
The following should not be considered a proper implementation (my experience with thread is very limited), but a demonstration of the proposed mechanism:
Ranges.java and Range.java
//represents all ranges
//see also: https://stackoverflow.com/a/7721388/3992939
public class Ranges {
private List<Range> ranges = new ArrayList<>();
private PropertyChangeSupport rangeChangedProperty = new PropertyChangeSupport(this);
public Range getRange(int rangeStart, int rangeEnd) {
if(contains(rangeStart) || contains(rangeEnd)) {
return null;
}
Range range = new Range(rangeStart, rangeEnd);
range.addListener( (observable, oldValue, newValue) -> {
rangeChangedProperty.firePropertyChange("Range", "-" , "changed");
}
);
ranges.add(range);
return range;
}
private boolean contains(int number){
for(Range range : ranges) {
if(range.contains(number)) {return true;}
}
return false;
}
public boolean removeRange(Range range) {
boolean isContains = ranges.remove(range);
rangeChangedProperty.firePropertyChange("Range", "-" , "removed");
return isContains;
}
/**
* Listen to {#link #rangeChangedProperty}. Fires whenever a range changes
* or removed.
* <br/>A client and a listener and when it fires, notify all threads.
*/
public void addChangeListener(PropertyChangeListener listener) {
rangeChangedProperty.addPropertyChangeListener(listener);
}
//represents a single range
//It is muttable
//can be implemented using ValueRange (https://stackoverflow.com/a/40716042/3992939)
class Range{
private SimpleIntegerProperty low = new SimpleIntegerProperty();
private SimpleIntegerProperty high = new SimpleIntegerProperty();
private SimpleObjectProperty<int[]> rangeProperty = new SimpleObjectProperty<>();
private Range(int rangeStart, int rangeEnd){
low.set(rangeStart) ; high.set(rangeEnd);
updateRange();
low.addListener((observable, oldValue, newValue) -> { updateRange(); });
high.addListener((observable, oldValue, newValue) -> { updateRange(); });
}
/**
* Listen to {#link #rangeProperty} that changes whenever the range changes
*/
void addListener(ChangeListener<int[]> listener) {
rangeProperty.addListener(listener);
}
private void updateRange() {rangeProperty.set(new int[] {low.get(), high.get()});}
public int getRangeStart() { return low.get(); }
public void setRangeStart(int rangeStart) { low.set(rangeStart);}
public int getRangeEnd() {return high.get();}
public void setRangeEnd(int rangeEnd) { high.set(rangeEnd);}
public boolean contains(int number){
int min = Math.min(low.get(), high.get());
int max = Math.max(low.get(), high.get());
return ((number >= min) && (number <= max));
}
}
}
GetRange.java
//used to simulate a thread trying to get a range
public class GetRange implements Runnable{
private Ranges ranges;
private int low, high;
private String id;
GetRange(Ranges ranges, int low, int high, String id) {
this.ranges = ranges;
this.low = low; this.high = high; this.id = id;
}
#Override
public void run() {
synchronized (ranges) {
while(ranges.getRange(low,high) == null) {
System.out.println("Tread "+ id + " is waiting");
try {
ranges.wait();
} catch (InterruptedException ex) { ex.printStackTrace();}
}
}
System.out.println("Tread "+ id + " got range. All done");
}
}
Test is with :
//test
public static void main(String[] args) throws InterruptedException {
Ranges ranges = new Ranges();
ranges.addChangeListener( (evt) -> {
synchronized (ranges) {
ranges.notifyAll();
System.out.println(evt.getPropertyName() + " "+ evt.getNewValue());
}
});
Range range1 = ranges.getRange(10,15);
Range range2 = ranges.getRange(20,25);
new Thread(new GetRange(ranges, 10, 12, "A")).start();
new Thread(new GetRange(ranges, 21, 28, "B")).start();
new Thread(new GetRange(ranges, 10, 12, "C")).start();
Thread.sleep(50);
System.out.println("-- Changing end of range 1. Threads notifyied and keep waiting -----");
range1.setRangeEnd(16); //no thread effected
Thread.sleep(50);
System.out.println("-- Changing start of range 1. Threads notifyied and A or C get range -----");
range1.setRangeStart(13); //effects thread A or C
Thread.sleep(50);
System.out.println("-- Removing range 2. Threads notifyied and B get range -----");
ranges.removeRange(range2);//effects thread B
Thread.sleep(50);
System.exit(1);
}
Output:
Tread A is waiting Tread C is waiting Tread B is waiting
-- Changing end of range 1. Threads notifyied and keep waiting -----
Range changed
Tread B is waiting
Tread C is waiting
Tread A is waiting
-- Changing start of range 1. Threads notifyied and A or C get range ----- Range changed Tread A got range. All done
Thread C is waiting
Tread B is waiting
-- Removing range 2. Threads notifyied and B get range -----
Range removed
Tread B got range. All done
Tread C is waiting
Guava's Striped locks may be of interest to you.
If you have a function int key(int p) which returns the index i of the interval [x_i,y_i] which p belongs to, you could probably use a Striped lock to achieve your goal.
For instance, if we had as interval bounds the points x_1, x_2, ... x_n such that x_i < x_(i+1) and x_(i+1) - x_i remains constant over all i from 1 to n, we could use something like key(p) = p -> (p - x_1) / n.
However, based on the notation you chose, this assumption may not hold and the function key be not as straightforward - but hopefully a lock striping solution will work for you.
This is my implementation for IntervalLock that supports Read and Write locks. Reads may acquire locks that have ranges overlapped, while a write must wait if its range overlaps with any other read or write. The basic idea is to use an interval tree to store the ranges. At a given time, each range may hold a write lock or multiple read locks. Insertion and deletion ranges from the tree must done carefully to prevent any race conditions. The code uses an implementation of interval tree from here.
SemaphoreInterval.java
package intervallock;
import java.util.ArrayList;
import java.util.concurrent.Semaphore;
import datastructures.Interval;
public class SemaphoreInterval implements Interval {
private ArrayList<Semaphore> semaphores;
private int start;
private int end;
private int mode;
public SemaphoreInterval(int start, int end, int mode) {
this.semaphores = new ArrayList<>(1);
this.start = start;
this.end = end;
this.mode = mode;
}
public int getMode() {
return mode;
}
public ArrayList<Semaphore> getSemaphores() {
return semaphores;
}
#Override
public int start() {
return start;
}
#Override
public int end() {
return end+1;
}
}
IntervalLock.java
package intervallock;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.Semaphore;
import datastructures.IntervalTree;
/**
* An implementation of Interval Lock
*
* #author Hieu
*
*/
public class IntervalLock {
public IntervalTree<SemaphoreInterval> tree;
private Semaphore treeLock;
private int maxPermits;
public static final int READ = 0;
public static final int WRITE = 1;
public IntervalLock(int maxPermits) {
tree = new IntervalTree<>();
treeLock = new Semaphore(1);
this.maxPermits = maxPermits;
}
/**
* Acquire a lock on range [start, end] with the specified mode.
* #param start The start of the interval
* #param end The end of the interval
* #param mode The mode, either IntervalLock.READ or IntervalLock.WRITE.
* #return The SemaphoreInterval instance.
*/
public SemaphoreInterval acquire(int start, int end, int mode) {
SemaphoreInterval si = new SemaphoreInterval(start, end, mode);
Set<Semaphore> semaphores = new HashSet<>();
try {
treeLock.acquire();
} catch (InterruptedException e) {
e.printStackTrace(System.out);
System.exit(-1);
}
Iterator<SemaphoreInterval> overlappers = tree.overlappers(si);
while (overlappers.hasNext()) {
SemaphoreInterval i = overlappers.next();
if (i == null) {
System.out.println("Error: Getting a null interval");
System.exit(-1);
}
if (i.compareTo(si) == 0)
continue;
switch (i.getMode()) {
case READ:
if (mode == WRITE)
semaphores.addAll(i.getSemaphores());
break;
case WRITE:
semaphores.addAll(i.getSemaphores());
break;
}
}
SemaphoreInterval result = tree.insert(si);
if (result != null)
si = result;
si.getSemaphores().add(new Semaphore(0));
treeLock.release();
for (Semaphore s: semaphores) {
try {
s.acquire();
} catch (InterruptedException e) {
e.printStackTrace(System.out);
System.exit(-1);
}
}
return si;
}
/**
* Release the range lock hold on specified SemaphoreInterval.
* #param si The semaphore interval returned by the acquire().
*/
public void release(SemaphoreInterval si) {
try {
treeLock.acquire();
} catch (InterruptedException e) {
e.printStackTrace(System.out);
System.exit(-1);
}
if (si.getSemaphores() == null || si.getSemaphores().size() == 0) {
System.out.println("Error: Empty array of semaphores");
treeLock.release();
return;
}
Semaphore sm = si.getSemaphores().remove(0);
if (si.getSemaphores().size() == 0) {
boolean success = tree.delete(si);
if (!success) {
System.out.println("Error: Cannot remove an interval.");
treeLock.release();
return;
}
}
treeLock.release();
sm.release(maxPermits);
}
}
Usage
// init the lock with the max permits per semaphore (should be the max number of threads)
public static final IntervalLock lock = new IntervalLock(1000);
// ...
// acquire the lock on range [a, b] (inclusive), with mode (either IntervalLock.READ or IntervalLock.WRITE)
// it returns a SemaphoreInterval instance
SemaphoreInterval si = lock.acquire(a, b, mode);
// ...
// release the acquired lock
lock.release(si);

Why the ClassLoader exception is raised in this situation?

I compiled successfully three files and when I tried to launch a class which contains a public static void main then I got errors. Here is the error :
C:\Documents and Settings\Ambre-28\Mes documents\JavaMESDKProjects\exempleRXTX\src\net\net>java Example
Exception in thread "main" java.lang.NoClassDefFoundError: Example (wrong name:
net/Example)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(Unknown Source)
at java.lang.ClassLoader.defineClass(Unknown Source)
at java.security.SecureClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.access$000(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
Could not find the main class: Example. Program will exit.
Here are the codes :
package net;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Vector;
/**
* This is a very simple example showing the most basic use of
* {#link net.Network} and {#link net.Network_iface}. Feel free to use,
* overwrite, or just ignore code as you like.
*
* As a default, a connection speed of 115200 baud is assumed. You can use a
* different speed by giving it as an <b>int</b> as the first command line
* argument or changing the default speed in the source code.
*
* #author Raphael Blatter (raphael#blatter.sg)
*/
public class Example implements net.Network_iface {
// set the speed of the serial port
public static int speed = 115200;
private static net.Network network;
private static boolean resend_active = false;
public static void main(String[] args) {
network = new net.Network(0, new net.Example(), 255);
// reading the speed if
if (args.length > 0) {
try {
speed = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
System.out.println("the speed must be an integer\n");
System.exit(1);
}
}
// initializing reader from command line
int i, inp_num = 0;
String input;
BufferedReader in_stream = new BufferedReader(new InputStreamReader(
System.in));
// getting a list of the available serial ports
Vector<String> ports = network.getPortList();
// choosing the port to connect to
System.out.println();
if (ports.size() > 0) {
System.out.println("the following serial ports have been detected:");
}
else {
System.out.println("sorry, no serial ports were found on your computer\n");
System.exit(0);
}
for (i = 0; i < ports.size(); ++i) {
System.out.println(" " + Integer.toString(i + 1) + ": " + ports.elementAt(i));
}
boolean valid_answer = false;
while (!valid_answer) {
System.out.println("enter the id (1,2,...) of the connection to connect to: ");
try {
input = in_stream.readLine();
inp_num = Integer.parseInt(input);
if ((inp_num < 1) || (inp_num >= ports.size() + 1))
System.out.println("your input is not valid");
else
valid_answer = true;
} catch (NumberFormatException ex) {
System.out.println("please enter a correct number");
} catch (IOException e) {
System.out.println("there was an input error\n");
System.exit(1);
}
}
// connecting to the selected port
if (network.connect(ports.elementAt(inp_num - 1), speed)) {
System.out.println();
} else {
System.out.println("sorry, there was an error connecting\n");
System.exit(1);
}
// asking whether user wants to mirror traffic
System.out.println("do you want this tool to send back all the received messages?");
valid_answer = false;
while (!valid_answer) {
System.out.println("'y' for yes or 'n' for no: ");
try {
input = in_stream.readLine();
if (input.equals("y")) {
resend_active = true;
valid_answer = true;
} else if (input.equals("n")) {
valid_answer = true;
} else if (input.equals("q")) {
System.out.println("example terminated\n");
System.exit(0);
}
} catch (IOException e) {
System.out.println("there was an input error\n");
System.exit(1);
}
}
// reading in numbers (bytes) to be sent over the serial port
System.out.println("type 'q' to end the example");
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
}
System.out.println("\nenter a number between 0 and 254 to be sent ('q' to exit): ");
try {
input = in_stream.readLine();
if (input.equals("q")) {
System.out.println("example terminated\n");
network.disconnect();
System.exit(0);
}
inp_num = Integer.parseInt(input);
if ((inp_num > 255) || (inp_num < 0)) {
System.out.println("the number you entered is not valid");
} else {
int temp[] = { inp_num };
network.writeSerial(1, temp); // ecriture dans le port série
System.out.println("sent " + inp_num + " over the serial port");
}
} catch (NumberFormatException ex) {
System.out.println("please enter a correct number");
} catch (IOException e) {
System.out.println("there was an input error");
}
}
}
/**
* Implementing {#link net.Network_iface#networkDisconnected(int)}, which is
* called when the connection has been closed. In this example, the program
* is ended.
*
* #see net.Network_iface
*/
public void networkDisconnected(int id) {
System.exit(0);
}
/**
* Implementing {#link net.Network_iface#parseInput(int, int, int[])} to
* handle messages received over the serial port. In this example, the
* received bytes are written to command line (0 to 254) and the message is
* sent back over the same serial port.
*
* #see net.Network_iface
*/
public void parseInput(int id, int numBytes, int[] message) {
if (resend_active) {
network.writeSerial(numBytes, message);
System.out.print("received and sent back the following message: ");
} else {
System.out.print("received the following message: ");
}
System.out.print(message[0]);
for (int i = 1; i < numBytes; ++i) {
System.out.print(", ");
System.out.print(message[i]);
}
System.out.println();
}
/**
* Implementing {#link net.Network_iface#writeLog(int, String)}, which is
* used to write information concerning the connection. In this example, all
* the information is simply written out to command line.
*
* #see net.Network_iface
*/
public void writeLog(int id, String text) {
System.out.println(" log: |" + text + "|");
}
}
package net;
import gnu.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Vector;
/**
* Used to simplify communication over a Serial port. Using the RXTX-library
* (rxtx.qbang.org), one connection per instance of this class can be handled.
* In addition to handling a connection, information about the available Serial
* ports can be received using this class.
*
* A separate {#link Thread} is started to handle messages that are being
* received over the Serial interface.
*
* This class also makes packages out of a stream of bytes received, using a
* {#link #divider}, and sending these packages as an array of <b>int</b>s (each
* between 0 and 255) to a function implemented by a class implementing the
* {#link net.Network_iface}-interface.
*
* #author Raphael Blatter (raphael#blatter.sg)
* #author heavily using code examples from the RXTX-website (rxtx.qbang.org)
*/
public class Network {
private InputStream inputStream;
private OutputStream outputStream;
/**
* The status of the connection.
*/
private boolean connected = false;
/**
* The Thread used to receive the data from the Serial interface.
*/
private Thread reader;
private SerialPort serialPort;
/**
* Communicating between threads, showing the {#link #reader} when the
* connection has been closed, so it can {#link Thread#join()}.
*/
private boolean end = false;
/**
* Link to the instance of the class implementing {#link net.Network_iface}.
*/
private Network_iface contact;
/**
* A small <b>int</b> representing the number to be used to distinguish
* between two consecutive packages. It can only take a value between 0 and
* 255. Note that data is only sent to
* {#link net.Network_iface#parseInput(int, int, int[])} once the following
* 'divider' could be identified.
*
* As a default, <b>255</b> is used as a divider (unless specified otherwise
* in the constructor).
*
* #see net.Network#Network(int, Network_iface, int)
*/
private int divider;
/**
* <b>int</b> identifying the specific instance of the Network-class. While
* having only a single instance, 'id' is irrelevant. However, having more
* than one open connection (using more than one instance of {#link Network}
* ), 'id' helps identifying which Serial connection a message or a log
* entry came from.
*/
private int id;
private int[] tempBytes;
int numTempBytes = 0, numTotBytes = 0;
/**
* #param id
* <b>int</b> identifying the specific instance of the
* Network-class. While having only a single instance,
* {#link #id} is irrelevant. However, having more than one open
* connection (using more than one instance of Network),
* {#link #id} helps identifying which Serial connection a
* message or a log entry came from.
*
* #param contact
* Link to the instance of the class implementing
* {#link net.Network_iface}.
*
* #param divider
* A small <b>int</b> representing the number to be used to
* distinguish between two consecutive packages. It can take a
* value between 0 and 255. Note that data is only sent to
* {#link net.Network_iface#parseInput(int, int, int[])} once the
* following {#link #divider} could be identified.
*/
public Network(int id, Network_iface contact, int divider) {
this.contact = contact;
this.divider = divider;
if (this.divider > 255)
this.divider = 255;
if (this.divider < 0)
this.divider = 0;
this.id = id;
tempBytes = new int[1024];
}
/**
* Just as {#link #Network(int, Network_iface, int)}, but with a default
* {#link #divider} of <b>255</b>.
*
* #see #Network(int, Network_iface, int)
*/
public Network(int id, Network_iface contact) {
this(id, contact, 255);
}
/**
* Just as {#link #Network(int, Network_iface, int)}, but with a default
* {#link #divider} of <b>255</b> and a default {#link #id} of 0. This
* constructor may mainly be used if only one Serial connection is needed at
* any time.
*
* #see #Network(int, Network_iface, int)
*/
public Network(Network_iface contact) {
this(0, contact);
}
/**
* This method is used to get a list of all the available Serial ports
* (note: only Serial ports are considered). Any one of the elements
* contained in the returned {#link Vector} can be used as a parameter in
* {#link #connect(String)} or {#link #connect(String, int)} to open a
* Serial connection.
*
* #return A {#link Vector} containing {#link String}s showing all available
* Serial ports.
*/
#SuppressWarnings("unchecked")
public Vector<String> getPortList() {
Enumeration<CommPortIdentifier> portList;
Vector<String> portVect = new Vector<String>();
portList = CommPortIdentifier.getPortIdentifiers();
CommPortIdentifier portId;
while (portList.hasMoreElements()) {
portId = (CommPortIdentifier) portList.nextElement();
if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
portVect.add(portId.getName());
}
}
contact.writeLog(id, "found the following ports:");
for (int i = 0; i < portVect.size(); i++) {
contact.writeLog(id, (" " + (String) portVect.elementAt(i)));
}
return portVect;
}
/**
* Just as {#link #connect(String, int)}, but using 115200 bps as a default
* speed of the connection.
*
* #param portName
* The name of the port the connection should be opened to (see
* {#link #getPortList()}).
* #return <b>true</b> if the connection has been opened successfully,
* <b>false</b> otherwise.
* #see #connect(String, int)
*/
public boolean connect(String portName) {
return connect(portName, 115200);
}
/**
* Opening a connection to the specified Serial port, using the specified
* speed. After opening the port, messages can be sent using
* {#link #writeSerial(String)} and received data will be packed into
* packets (see {#link #divider}) and forwarded using
* {#link net.Network_iface#parseInput(int, int, int[])}.
*
* #param portName
* The name of the port the connection should be opened to (see
* {#link #getPortList()}).
* #param speed
* The desired speed of the connection in bps.
* #return <b>true</b> if the connection has been opened successfully,
* <b>false</b> otherwise.
*/
public boolean connect(String portName, int speed) {
CommPortIdentifier portIdentifier;
boolean conn = false;
try {
portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
if (portIdentifier.isCurrentlyOwned()) {
contact.writeLog(id, "Error: Port is currently in use");
} else {
serialPort = (SerialPort) portIdentifier.open("RTBug_network",
2000);
serialPort.setSerialPortParams(speed, SerialPort.DATABITS_8,
SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
inputStream = serialPort.getInputStream();
outputStream = serialPort.getOutputStream();
reader = (new Thread(new SerialReader(inputStream)));
end = false;
reader.start();
connected = true;
contact.writeLog(id, "connection on " + portName
+ " established");
conn = true;
}
} catch (NoSuchPortException e) {
contact.writeLog(id, "the connection could not be made");
e.printStackTrace();
} catch (PortInUseException e) {
contact.writeLog(id, "the connection could not be made");
e.printStackTrace();
} catch (UnsupportedCommOperationException e) {
contact.writeLog(id, "the connection could not be made");
e.printStackTrace();
} catch (IOException e) {
contact.writeLog(id, "the connection could not be made");
e.printStackTrace();
}
return conn;
}
/**
* A separate class to use as the {#link net.Network#reader}. It is run as a
* separate {#link Thread} and manages the incoming data, packaging them
* using {#link net.Network#divider} into arrays of <b>int</b>s and
* forwarding them using
* {#link net.Network_iface#parseInput(int, int, int[])}.
*
*/
private class SerialReader implements Runnable {
InputStream in;
public SerialReader(InputStream in) {
this.in = in;
}
public void run() {
byte[] buffer = new byte[1024];
int len = -1, i, temp;
try {
while (!end) {
if ((in.available()) > 0) {
if ((len = this.in.read(buffer)) > -1) {
for (i = 0; i < len; i++) {
temp = buffer[i];
// adjust from C-Byte to Java-Byte
if (temp < 0)
temp += 256;
if (temp == divider) {
if (numTempBytes > 0) {
contact.parseInput(id, numTempBytes,
tempBytes);
}
numTempBytes = 0;
} else {
tempBytes[numTempBytes] = temp;
++numTempBytes;
}
}
}
}
}
} catch (IOException e) {
end = true;
try {
outputStream.close();
inputStream.close();
} catch (IOException e1) {
e1.printStackTrace();
}
serialPort.close();
connected = false;
contact.networkDisconnected(id);
contact.writeLog(id, "connection has been interrupted");
}
}
}
/**
* Simple function closing the connection held by this instance of
* {#link net.Network}. It also ends the Thread {#link net.Network#reader}.
*
* #return <b>true</b> if the connection could be closed, <b>false</b>
* otherwise.
*/
public boolean disconnect() {
boolean disconn = true;
end = true;
try {
reader.join();
} catch (InterruptedException e1) {
e1.printStackTrace();
disconn = false;
}
try {
outputStream.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
disconn = false;
}
serialPort.close();
connected = false;
contact.networkDisconnected(id);
contact.writeLog(id, "connection disconnected");
return disconn;
}
/**
* #return Whether this instance of {#link net.Network} has currently an
* open connection of not.
*/
public boolean isConnected() {
return connected;
}
/**
* This method is included as a legacy. Depending on the other side of the
* Serial port, it might be easier to send using a String. Note: this method
* does not add the {#link #divider} to the end.
*
* If a connection is open, a {#link String} can be sent over the Serial
* port using this function. If no connection is available, <b>false</b> is
* returned and a message is sent using
* {#link net.Network_iface#writeLog(int, String)}.
*
* #param message
* The {#link String} to be sent over the Serial connection.
* #return <b>true</b> if the message could be sent, <b>false</b> otherwise.
*/
public boolean writeSerial(String message) {
boolean success = false;
if (isConnected()) {
try {
outputStream.write(message.getBytes());
success = true;
} catch (IOException e) {
disconnect();
}
} else {
contact.writeLog(id, "No port is connected.");
}
return success;
}
/**
* If a connection is open, an <b>int</b> between 0 and 255 (except the
* {#link net.Network#divider}) can be sent over the Serial port using this
* function. The message will be finished by sending the
* {#link net.Network#divider}. If no connection is available, <b>false</b>
* is returned and a message is sent using
* {#link net.Network_iface#writeLog(int, String)}.
*
* #param numBytes
* The number of bytes to send over the Serial port.
* #param message
* [] The array of<b>int</b>s to be sent over the Serial
* connection (between 0 and 256).
* #return <b>true</b> if the message could be sent, <b>false</b> otherwise
* or if one of the numbers is equal to the #{#link Network#divider}
* .
*/
public boolean writeSerial(int numBytes, int message[]) {
boolean success = true;
int i;
for (i = 0; i < numBytes; ++i) {
if (message[i] == divider) {
success = false;
break;
}
}
if (success && isConnected()) {
try {
for (i = 0; i < numBytes; ++i) {
outputStream.write(changeToByte(message[i]));
}
outputStream.write(changeToByte(divider));
} catch (IOException e) {
success = false;
disconnect();
}
} else if (!success) {
// message contains the divider
contact.writeLog(id, "The message contains the divider.");
} else {
contact.writeLog(id, "No port is connected.");
}
return success;
}
private byte changeToByte(int num) {
byte number;
int temp;
temp = num;
if (temp > 255)
temp = 255;
if (temp < 0)
temp = 0;
number = (byte) temp;
return number;
}
}
package net;
/**
* An instance of a class implementing this interface has to be passed to the
* constructor of {#link net.Network}. It will be used by {#link net.Network} to
* forward received messages, write to a log and take action when the connection
* is closed.
*
* #see net.Network#Network(int, Network_iface, int)
*
* #author Raphael Blatter (raphael#blatter.sg)
*/
public interface Network_iface {
/**
* Is called to write connection information to the log. The information can
* either be ignored, directed to stdout or written out to a specialized
* field or file in the program.
*
* #param id
* The <b>int</b> passed to
* {#link net.Network#Network(int, Network_iface, int)} in the
* constructor. It can be used to identify which instance (which
* connection) a message comes from, when several instances of
* {#link net.Network} are connected to the same instance of a
* class implementing this interface.
* #param text
* The text to be written into the log in human readable form.
* Corresponds to information about the connection or ports.
*/
public void writeLog(int id, String text);
/**
* Is called when sequence of bytes are received over the Serial interface.
* It sends the bytes (as <b>int</b>s between 0 and 255) between the two
* {#link net.Network#divider}s passed via the constructor of
* {#link net.Network} (
* {#link net.Network#Network(int, Network_iface, int)}), without the
* {#link net.Network#divider}s. Messages are only forwarded using this
* function, once a {#link net.Network#divider} has been recognized in the
* incoming stream.
*
* #param id
* The <b>int</b> passed to
* {#link net.Network#Network(int, Network_iface, int)} in the
* constructor. It can be used to identify which instance a
* message comes from, when several instances of
* {#link net.Network} are connected to the same instance of a
* class implementing this interface.
* #param numBytes
* Number of valid bytes contained in the message
* #param message
* Message received over the Serial interface. The complete array
* of bytes (as <b>int</b>s between 0 and 255) between
* {#link net.Network#divider} is sent (without
* {#link net.Network#divider}s).
*/
public void parseInput(int id, int numBytes, int[] message);
/**
* Is called when the network has been disconnected. This call can e.g. be
* used to show the connection status in a GUI or inform the user using
* other means.
*
* #param id
* {#link net.Network#id} of the corresponding
* {#link net.Network} instance (see {#link net.Network#id}).
*/
public void networkDisconnected(int id);
}
Each class is a separate file. So why this error is raised ?
You should be launching it from the parent directory as
java net.Example
When you run the java command, it takes the full class name, including the package name. So in your case the JVM was trying to find a class just called Example - it found Example.class as a file, but then failed because that class file contains the class net.Example, not Example.
You need to be in the parent directory (...\exempleRXTX\src\net) so that when the JVM looks for net.Example it will look in the ...\exempleRXTX\src\net\net directory for a file called Example.class.
Check where the .class files are being generated and ensure that classpath settings are right, However it seems that ur nt running the `java command' from a proper directory as mentioned by #Jon.
Note: Specifying '.'(single dot) as the classpath value will configure java to search for the .class files in same directory (from where u r trying java command).

What is "Sim blocking" (seen in tomcat doc)?

I saw the following description in the official tomcat configuration documentation (APR connector description omitted):
Java Blocking Connector Java Nio Blocking Connector
Classname Http11Protocol Http11NioProtocol
Tomcat Version 3.x 4.x 5.x 6.x 6.x
Support Polling NO YES
Polling Size N/A Unlimited - Restricted by mem
Read HTTP Request Blocking Non Blocking
Read HTTP Body Blocking Sim Blocking
Write HTTP Response Blocking Sim Blocking
SSL Support Java SSL Java SSL
SSL Handshake Blocking Non blocking
Max Connections maxThreads See polling size
What does "Sim Blocking" mean?
According to Filip Hanik, a Tomcat committer, it means "simulated blocking". (Reference: Tomcat User Mailing list post)
Just a guess, but it could stand for simulated blocking, meaning a blocking api wrapped around the underlying non-blocking nio api.
SourceCode: https://github.com/apache/tomcat/blob/8.5.x/java/org/apache/tomcat/util/net/NioEndpoint.java
/**
* NioEndpoint.NioSocketWrapper.fillReadBuffer() 用于直接读取内容到 传入的任意 ByteBuffer 中
*
* #param block 是否阻塞读
* #param buffer 待接收数据的buffer
* #return 读取到的字节数
* #throws IOException
*/
private int fillReadBuffer(boolean block, ByteBuffer buffer) throws IOException {
int n = 0;
if (getSocket() == NioChannel.CLOSED_NIO_CHANNEL) {
throw new ClosedChannelException();
}
if (block) { // if no readListener. block variable is true.
long timeout = getReadTimeout();
long startNanos = 0;
do {
if (startNanos > 0) {
long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
if (elapsedMillis == 0) {
elapsedMillis = 1;
}
timeout -= elapsedMillis;
if (timeout <= 0) {
throw new SocketTimeoutException();
}
}
n = getSocket().read(buffer);
if (n == -1) {
throw new EOFException();
} else if (n == 0) {
if (!readBlocking) {
readBlocking = true;
registerReadInterest(); // <=============== (向关联的Poller)注册读事件.
}
synchronized (readLock) {
if (readBlocking) {
try {
if (timeout > 0) {
startNanos = System.nanoTime();
readLock.wait(timeout); // block itself here < ===================
} else {
readLock.wait();
}
} catch (InterruptedException e) {
// Continue
}
}
}
}
} while (n == 0); // TLS needs to loop as reading zero application bytes is possible
} else {
n = getSocket().read(buffer);
if (n == -1) {
throw new EOFException();
}
}
return n;
}

Using JDBC, how can I substitute multiple IDs into "DELETE FROM T WHERE id IN (?)"

I have some code that produces a set of primary key values that I want to delete from a database table.
long[] keysToDelete = { 0, 1, 2, 3 };
and I'd like to use a PreparedStatement to execute the equivalent of
DELETE FROM MyTable WHERE myPrimaryKey IN (0, 1, 2, 3);
Any idea how?
Two steps:
Build up the PreparedStatement SQL String with the appropriate # of parameters.
Loop over the array of values and bind each one to its parameter.
Unfortunately, there's no good way to bind an array all at once.
I've written a class to dynamically generate such a multi-parameter query. It currently has some limitations (for quickness of writing) and has not been thoroughly tested, but may be a good way to get you started. Limitations:
Only handles one multi-argument parameter (??)
Falsely recognizes question marks in quotes as parameters
API is not pretty but the alternative was writing a full-on PreparedStatement decorator with lots of state management and that was more work than I was willing to put into it.
Source:
/**
* A PreparedStatement decorator that can bind a set of arguments
*
* A specialized ?? placeholder in a string can be bound to a set of
* values instead of just single values. Currently, only one such
* specialized placeholder is supported, and you must bind it before
* obtaining the prepared statement.
*
* If you want to bind additional values to the PreparedStatement after
* producing it, you must run the parameter index through the param()
* method.
*
* Example use:
*
*
* MultiValueBinder binder = new MultiValueBinder(
* "UPDATE table SET value = ? WHERE id IN (??)", conn);
* binder.setInts(myIds);
*
* PreparedStatement stmt = binder.statement();
* stmt.setString(binder.param(1), "myValue");
*
* ResultSet rs = stmt.executeQuery();
*
* Note: this class is not robust against using question marks in literal
* strings. Don't use them :).
*/
public class MultiValueBinder {
private Connection connection;
private PreparedStatement statement;
private String sql;
private int argumentsBefore = 0;
private int setSize = 0;
public MultiValueBinder(String sql, Connection connection) {
this.sql = sql;
this.connection = connection;
}
/**
* Bind a collection of integers to the multi-valued argument
*/
public void setInts(Collection<Integer> ints) throws SQLException {
explodeQuery(ints.size());
buildStatement();
try {
int i = 0;
for (Integer integer: ints)
statement.setInt(1 + argumentsBefore + i++, integer);
} catch (Exception ex) {
cleanStatement();
throw (ex instanceof SQLException) ? (SQLException) ex : new SQLException(ex);
}
}
/**
* Bind a collection of strings to the multi-valued argument
*/
public void setStrings(Collection<String> strings) throws SQLException {
explodeQuery(strings.size());
buildStatement();
try {
int i = 0;
for (String str: strings)
statement.setString(1 + argumentsBefore + i++, str);
} catch (Exception ex) {
cleanStatement();
throw (ex instanceof SQLException) ? (SQLException) ex : new SQLException(ex);
}
}
/**
* Explode the multi-value parameter into a sequence of comma-separated
* question marks.
*/
private void explodeQuery(int size) throws SQLException {
int mix = sql.indexOf("??");
if (mix == -1) throw new SQLException("Query does not contain a multi-valued argument.");
if (size == 0) throw new SQLException("Can't bind an empty collection; generated SQL won't parse.");
// Count the number of arguments before the multi-marker
argumentsBefore = 0;
for (int i = 0; i < mix; i++) {
if (sql.charAt(i) == '?') argumentsBefore++;
}
setSize = size;
// Generate the exploded SQL query
StringBuilder sb = new StringBuilder(sql.substring(0, mix)); // Start
for (int i = 0; i < setSize; i++) { // ?, ?, ...
if (i > 0) sb.append(", ");
sb.append('?');
}
sb.append(sql.substring(mix + 2)); // Remainder
sql = sb.toString();
}
/**
* Create the statement if it hasn't been created yet
*/
private void buildStatement() throws SQLException {
if (statement != null) return;
if (sql.contains("??"))
throw new SQLException("Multi-valued argument not bound yet.");
statement = connection.prepareStatement(sql);
}
private void cleanStatement() {
if (statement != null) {
try {
statement.close();
} catch (Exception ex) {
/* Ignore */
}
statement = null;
}
}
public PreparedStatement statement() throws SQLException {
buildStatement();
return statement;
}
/**
* Transform the 1-based-index of the given argument before query expansion
* into the index after expansion.
*
* The ?? placeholder takes up one index slot.
*/
public int param(int ix) {
if (ix <= argumentsBefore) return ix;
if (ix == argumentsBefore + 1)
throw new RuntimeException(ix + " is the index of the multi-valued parameter.");
return argumentsBefore + 1 + setSize;
}
}
Not totally sure but this might help:
PreparedStatement pstmt = Connection.prepareStatement("DELETE FROM MyTable WHERE myPrimaryKey IN (?)");
pstmt.setArray(1, idArray);

Categories

Resources