/** * Flushes all value caches contained within entities controlled by this <code>EntityManager * </code> instance. This does not actually remove the entities from the instance cache maintained * within this class. Rather, it simply dumps all of the field values cached within the entities * themselves (with the exception of the primary key value). This should be used in the case of a * complex process outside AO control which may have changed values in the database. If it is at * all possible to determine precisely which rows have been changed, the {@link * #flush(RawEntity...)} method should be used instead. */ public void flushAll() { List<Map.Entry<RawEntity<?>, EntityProxy<? extends RawEntity<?>, ?>>> toFlush = new LinkedList<Map.Entry<RawEntity<?>, EntityProxy<? extends RawEntity<?>, ?>>>(); proxyLock.readLock().lock(); try { for (Map.Entry<RawEntity<?>, EntityProxy<? extends RawEntity<?>, ?>> proxy : proxies.entrySet()) { toFlush.add(proxy); } } finally { proxyLock.readLock().unlock(); } for (Map.Entry<RawEntity<?>, EntityProxy<? extends RawEntity<?>, ?>> entry : toFlush) { entry.getValue().flushCache(entry.getKey()); } relationsCache.flush(); }
/** * Flushes the value caches of the specified entities along with all of the relevant relations * cache entries. This should be called after a process outside of AO control may have modified * the values in the specified rows. This does not actually remove the entity instances themselves * from the instance cache. Rather, it just flushes all of their internally cached values (with * the exception of the primary key). */ public void flush(RawEntity<?>... entities) { List<Class<? extends RawEntity<?>>> types = new ArrayList<Class<? extends RawEntity<?>>>(entities.length); Map<RawEntity<?>, EntityProxy<?, ?>> toFlush = new HashMap<RawEntity<?>, EntityProxy<?, ?>>(); proxyLock.readLock().lock(); try { for (RawEntity<?> entity : entities) { verify(entity); types.add(entity.getEntityType()); toFlush.put(entity, proxies.get(entity)); } } finally { proxyLock.readLock().unlock(); } for (Entry<RawEntity<?>, EntityProxy<?, ?>> entry : toFlush.entrySet()) { entry.getValue().flushCache(entry.getKey()); } relationsCache.remove(types.toArray(new Class[types.size()])); }
/** * 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(); } }
/** * Creates a new entity of the specified type with the optionally specified initial parameters. * This method actually inserts a row into the table represented by the entity type and returns * the entity instance which corresponds to that row. * * <p>The {@link DBParam} object parameters are designed to allow the creation of entities which * have non-null fields which have no defalut or auto-generated value. Insertion of a row without * such field values would of course fail, thus the need for db params. The db params can also be * used to set the values for any field in the row, leading to more compact code under certain * circumstances. * * <p>Unless within a transaction, this method will commit to the database immediately and exactly * once per call. Thus, care should be taken in the creation of large numbers of entities. There * doesn't seem to be a more efficient way to create large numbers of entities, however one should * still be aware of the performance implications. * * <p>This method delegates the action INSERT action to {@link * DatabaseProvider#insertReturningKey(EntityManager, Connection, Class, String, boolean, String, * DBParam...)}. This is necessary because not all databases support the JDBC <code> * RETURN_GENERATED_KEYS</code> constant (e.g. PostgreSQL and HSQLDB). Thus, the database provider * itself is responsible for handling INSERTion and retrieval of the correct primary key value. * * @param type The type of the entity to INSERT. * @param params An optional varargs array of initial values for the fields in the row. These * values will be passed to the database within the INSERT statement. * @return The new entity instance corresponding to the INSERTed row. * @see net.java.ao.DBParam * @see net.java.ao.DatabaseProvider#insertReturningKey(EntityManager, Connection, Class, String, * boolean, String, DBParam...) */ public <T extends RawEntity<K>, K> T create(Class<T> type, DBParam... params) throws SQLException { T back = null; String table = null; tableNameConverterLock.readLock().lock(); try { table = tableNameConverter.getName(type); } finally { tableNameConverterLock.readLock().unlock(); } Set<DBParam> listParams = new HashSet<DBParam>(); listParams.addAll(Arrays.asList(params)); fieldNameConverterLock.readLock().lock(); try { for (Method method : MethodFinder.getInstance().findAnnotation(Generator.class, type)) { Generator genAnno = method.getAnnotation(Generator.class); String field = fieldNameConverter.getName(method); ValueGenerator<?> generator; valGenCacheLock.writeLock().lock(); try { if (valGenCache.containsKey(genAnno.value())) { generator = valGenCache.get(genAnno.value()); } else { generator = genAnno.value().newInstance(); valGenCache.put(genAnno.value(), generator); } } catch (InstantiationException e) { continue; } catch (IllegalAccessException e) { continue; } finally { valGenCacheLock.writeLock().unlock(); } listParams.add(new DBParam(field, generator.generateValue(this))); } // <ian> Version version = type.getAnnotation(Version.class); if (version != null) { // Initialize version upon creation. String field = version.value(); int initial = version.initial(); listParams.add(new DBParam(field, initial)); } // </ian> } finally { fieldNameConverterLock.readLock().unlock(); } Connection conn = getProvider().getConnection(); try { Method pkMethod = Common.getPrimaryKeyMethod(type); back = peer( type, provider.insertReturningKey( this, conn, Common.getPrimaryKeyClassType(type), Common.getPrimaryKeyField(type, getFieldNameConverter()), pkMethod.getAnnotation(AutoIncrement.class) != null, table, listParams.toArray(new DBParam[listParams.size()]))); } finally { conn.close(); } relationsCache.remove(type); back.init(); return back; }