/** * Deletes the specified entities from the database. DELETE statements are called on the rows in * the corresponding tables and the entities are removed from the instance cache. The entity * instances themselves are not invalidated, but it doesn't even make sense to continue using the * instance without a row with which it is paired. * * <p>This method does attempt to group the DELETE statements on a per-type basis. Thus, if you * pass 5 instances of <code>EntityA</code> and two instances of <code>EntityB</code>, the * following SQL prepared statements will be invoked: * * <pre>DELETE FROM entityA WHERE id IN (?,?,?,?,?); * DELETE FROM entityB WHERE id IN (?,?);</pre> * * <p>Thus, this method scales very well for large numbers of entities grouped into types. * However, the execution time increases linearly for each entity of unique type. * * @param entities A varargs array of entities to delete. Method returns immediately if length == * 0. */ @SuppressWarnings("unchecked") public void delete(RawEntity<?>... entities) throws SQLException { if (entities.length == 0) { return; } Map<Class<? extends RawEntity<?>>, List<RawEntity<?>>> organizedEntities = new HashMap<Class<? extends RawEntity<?>>, List<RawEntity<?>>>(); for (RawEntity<?> entity : entities) { verify(entity); Class<? extends RawEntity<?>> type = getProxyForEntity(entity).getType(); if (!organizedEntities.containsKey(type)) { organizedEntities.put(type, new LinkedList<RawEntity<?>>()); } organizedEntities.get(type).add(entity); } entityCacheLock.writeLock().lock(); try { DatabaseProvider provider = getProvider(); Connection conn = provider.getConnection(); try { for (Class<? extends RawEntity<?>> type : organizedEntities.keySet()) { List<RawEntity<?>> entityList = organizedEntities.get(type); StringBuilder sql = new StringBuilder("DELETE FROM "); tableNameConverterLock.readLock().lock(); try { sql.append(provider.processID(tableNameConverter.getName(type))); } finally { tableNameConverterLock.readLock().unlock(); } sql.append(" WHERE ") .append(provider.processID(Common.getPrimaryKeyField(type, getFieldNameConverter()))) .append(" IN (?"); for (int i = 1; i < entityList.size(); i++) { sql.append(",?"); } sql.append(')'); Logger.getLogger("net.java.ao").log(Level.INFO, sql.toString()); PreparedStatement stmt = conn.prepareStatement(sql.toString()); int index = 1; for (RawEntity<?> entity : entityList) { TypeManager.getInstance() .getType((Class) entity.getEntityType()) .putToDatabase(this, stmt, index++, entity); } relationsCache.remove(type); stmt.executeUpdate(); stmt.close(); } } finally { conn.close(); } for (RawEntity<?> entity : entities) { entityCache.remove(new CacheKey(Common.getPrimaryKeyValue(entity), entity.getEntityType())); } proxyLock.writeLock().lock(); try { for (RawEntity<?> entity : entities) { proxies.remove(entity); } } finally { proxyLock.writeLock().unlock(); } } finally { entityCacheLock.writeLock().unlock(); } }
/** * Executes the specified SQL and extracts the given key field, wrapping each row into a instance * of the specified type. The SQL itself is executed as a {@link PreparedStatement} with the given * parameters. * * <p>Example: * * <pre> * manager.findWithSQL(Person.class, "personID", "SELECT personID FROM chairs WHERE position < ? LIMIT ?", 10, 5); * </pre> * * <p>The SQL is not parsed or modified in any way by ActiveObjects. As such, it is possible to * execute database-specific queries using this method without realizing it. For example, the * above query will not run on MS SQL Server or Oracle, due to the lack of a LIMIT clause in their * SQL implementation. As such, be extremely careful about what SQL is executed using this method, * or else be conscious of the fact that you may be locking yourself to a specific DBMS. * * @param type The type of the entities to retrieve. * @param keyField The field value to use in the creation of the entities. This is usually the * primary key field of the corresponding table. * @param sql The SQL statement to execute. * @param parameters A varargs array of parameters to be passed to the executed prepared * statement. The length of this array <i>must</i> match the number of parameters (denoted by * the '?' char) in the <code>criteria</code>. * @return An array of entities of the given type which match the specified query. */ @SuppressWarnings("unchecked") public <T extends RawEntity<K>, K> T[] findWithSQL( Class<T> type, String keyField, String sql, Object... parameters) throws SQLException { List<T> back = new ArrayList<T>(); Connection conn = getProvider().getConnection(); try { Logger.getLogger("net.java.ao").log(Level.INFO, sql); PreparedStatement stmt = conn.prepareStatement(sql); TypeManager manager = TypeManager.getInstance(); for (int i = 0; i < parameters.length; i++) { Class javaType = parameters[i].getClass(); if (parameters[i] instanceof RawEntity) { javaType = ((RawEntity<?>) parameters[i]).getEntityType(); } manager.getType(javaType).putToDatabase(this, stmt, i + 1, parameters[i]); } ResultSet res = stmt.executeQuery(); while (res.next()) { back.add( peer( type, Common.getPrimaryKeyType(type) .pullFromDatabase(this, res, (Class<? extends K>) type, keyField))); } res.close(); stmt.close(); } finally { conn.close(); } return back.toArray((T[]) Array.newInstance(type, back.size())); }