diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/StoreCollectionFieldStrategy.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/StoreCollectionFieldStrategy.java index 4cffba18a..504ae6c1b 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/StoreCollectionFieldStrategy.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/StoreCollectionFieldStrategy.java @@ -40,6 +40,7 @@ import org.apache.openjpa.jdbc.sql.Select; import org.apache.openjpa.jdbc.sql.SelectExecutor; import org.apache.openjpa.jdbc.sql.Union; import org.apache.openjpa.kernel.OpenJPAStateManager; +import org.apache.openjpa.kernel.StateManagerImpl; import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.meta.ClassMetaData; import org.apache.openjpa.meta.JavaTypes; @@ -345,8 +346,9 @@ public abstract class StoreCollectionFieldStrategy if (decMeta == null) return; - if (oid.equals(sm.getObjectId())) { - mappedByValue = sm.getPersistenceCapable(); + StateManagerImpl owner = ((StateManagerImpl)sm).getObjectIdOwner(); + if (oid.equals(owner.getObjectId())) { + mappedByValue = owner.getPersistenceCapable(); res.setMappedByFieldMapping(mappedByFieldMapping); res.setMappedByValue(mappedByValue); } else if (coll instanceof Collection && diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/InverseManager.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/InverseManager.java index 2c89e0766..457af24fc 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/InverseManager.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/InverseManager.java @@ -18,16 +18,21 @@ */ package org.apache.openjpa.kernel; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; +import java.util.Map; import java.util.Set; import org.apache.openjpa.conf.OpenJPAConfiguration; import org.apache.openjpa.datacache.DataCache; +import org.apache.openjpa.enhance.PersistenceCapable; import org.apache.openjpa.lib.conf.Configurable; import org.apache.openjpa.lib.conf.Configuration; import org.apache.openjpa.lib.log.Log; import org.apache.openjpa.lib.util.Localizer; +import org.apache.openjpa.meta.ClassMetaData; import org.apache.openjpa.meta.FieldMetaData; import org.apache.openjpa.meta.JavaTypes; import org.apache.openjpa.meta.ValueMetaData; @@ -131,7 +136,8 @@ public class InverseManager implements Configurable { public void correctRelations(OpenJPAStateManager sm, FieldMetaData fmd, Object value) { if (fmd.getDeclaredTypeCode() != JavaTypes.PC && - (fmd.getDeclaredTypeCode() != JavaTypes.COLLECTION || + ((fmd.getDeclaredTypeCode() != JavaTypes.COLLECTION && + fmd.getDeclaredTypeCode() != JavaTypes.MAP) || fmd.getElement().getDeclaredTypeCode() != JavaTypes.PC)) return; @@ -146,23 +152,43 @@ public class InverseManager implements Configurable { // clear any restorable relations clearInverseRelations(sm, fmd, inverses, value); - if (value != null) { - StoreContext ctx = sm.getContext(); - switch (fmd.getDeclaredTypeCode()) { - case JavaTypes.PC: - createInverseRelations(ctx, sm.getManagedInstance(), - value, fmd, inverses); - break; - case JavaTypes.COLLECTION: - for (Iterator itr = ((Collection) value).iterator(); - itr.hasNext();) - createInverseRelations(ctx, sm.getManagedInstance(), - itr.next(), fmd, inverses); - break; - } - } + // create inverse relations + createInverseRelations(sm, fmd, inverses, value); } + protected void createInverseRelations(OpenJPAStateManager sm, + FieldMetaData fmd, FieldMetaData[] inverses, Object value) { + if (value == null) + return; + StoreContext ctx = sm.getContext(); + if (isEmbedded(sm)) + return; + Object obj = sm.getManagedInstance(); + createInverseRelations(ctx, obj, fmd, inverses, value); + } + + protected void createInverseRelations(StoreContext ctx, Object obj, + FieldMetaData fmd, FieldMetaData[] inverses, Object value) { + switch (fmd.getDeclaredTypeCode()) { + case JavaTypes.PC: + createInverseRelations(ctx, obj, value, fmd, inverses); + break; + case JavaTypes.COLLECTION: + for (Iterator itr = ((Collection) value).iterator(); + itr.hasNext();) + createInverseRelations(ctx, obj, itr.next(), fmd, inverses); + break; + } + } + + protected boolean isEmbedded(OpenJPAStateManager sm) { + ClassMetaData meta = sm.getMetaData(); + ValueMetaData owner = meta.getEmbeddingMetaData(); + if (owner != null) + return true; + return false; + } + /** * Create the inverse relations for all the given inverse fields. * A relation exists from fromRef to toRef; this @@ -241,13 +267,22 @@ public class InverseManager implements Configurable { Object initial = sm.fetchInitialField(fmd.getIndex()); clearInverseRelations(sm, initial, fmd, inverses); } else { - Collection initial = (Collection) - sm.fetchInitialField(fmd.getIndex()); + Object obj = sm.fetchInitialField(fmd.getIndex()); + Collection initial = null; + if (obj instanceof Collection) + initial = (Collection) obj; + else if (obj instanceof Map) + initial = ((Map)obj).values(); + if (initial == null) return; // clear all relations not also in the new value - Collection coll = (Collection) newValue; + Collection coll = null; + if (newValue instanceof Collection) + coll = (Collection) newValue; + else if (newValue instanceof Map) + coll = ((Map)newValue).values(); Object elem; for (Iterator itr = initial.iterator(); itr.hasNext();) { elem = itr.next(); @@ -285,7 +320,10 @@ public class InverseManager implements Configurable { case JavaTypes.PC: if (!owned || inverses[i].getCascadeDelete() == ValueMetaData.CASCADE_AUTO) - storeNull(other, inverses[i], sm.getManagedInstance()); + if (fmd.getOrphanRemoval() || fmd.getElement().getOrphanRemoval()) + ((StateManagerImpl)other).delete(); + else + storeNull(other, inverses[i], sm.getManagedInstance()); break; case JavaTypes.COLLECTION: if (!owned || inverses[i].getElement().getCascadeDelete() diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/OrphanRemovalManager.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/OrphanRemovalManager.java new file mode 100644 index 000000000..feb1a529e --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/OrphanRemovalManager.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.kernel; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import org.apache.openjpa.meta.FieldMetaData; +import org.apache.openjpa.meta.JavaTypes; + +/** + * Class which manages orphanRemoval before flushing + * to the datastore. + * + * @author Fay Wang + */ +public class OrphanRemovalManager { + + /** + * Correct relations from the given dirty field to inverse instances. + * Field fmd of the instance managed by sm has + * value value. Ensure that all inverses relations from + * value are consistent with this. + */ + public static void correctRelations(OpenJPAStateManager sm, FieldMetaData fmd, + Object value) { + if (fmd.getDeclaredTypeCode() != JavaTypes.PC && + ((fmd.getDeclaredTypeCode() != JavaTypes.COLLECTION && + fmd.getDeclaredTypeCode() != JavaTypes.MAP) || + fmd.getElement().getDeclaredTypeCode() != JavaTypes.PC)) + return; + + FieldMetaData[] orphanRemoves = fmd.getOrphanRemovalMetaDatas(); + if (orphanRemoves.length == 0) + return; + + // clear any restorable relations + clearOrphanRemovalRelations(sm, fmd, orphanRemoves, value); + } + + + /** + * Remove all relations between the initial value of fmd for + * the instance managed by sm and its inverses. Relations + * shared with newValue can be left intact. + */ + protected static void clearOrphanRemovalRelations(OpenJPAStateManager sm, + FieldMetaData fmd, FieldMetaData[] orphanRemoves, Object newValue) { + // don't bother clearing unflushed new instances + if (sm.isNew() && !sm.getFlushed().get(fmd.getIndex())) + return; + if (fmd.getDeclaredTypeCode() == JavaTypes.PC) { + Object initial = sm.fetchInitialField(fmd.getIndex()); + clearInverseRelations(sm, initial, fmd, orphanRemoves); + } else { + Object obj = sm.fetchInitialField(fmd.getIndex()); + Collection initial = null; + if (obj instanceof Collection) + initial = (Collection) obj; + else if (obj instanceof Map) + initial = ((Map)obj).values(); + + if (initial == null) + return; + + // clear all relations not also in the new value + Collection coll = null; + if (newValue instanceof Collection) + coll = (Collection) newValue; + else if (newValue instanceof Map) + coll = ((Map)newValue).values(); + Object elem; + for (Iterator itr = initial.iterator(); itr.hasNext();) { + elem = itr.next(); + if (coll == null || !coll.contains(elem)) + clearInverseRelations(sm, elem, fmd, orphanRemoves); + } + } + } + + /** + * Clear all inverse the relations from val to the instance + * managed by sm. + */ + protected static void clearInverseRelations(OpenJPAStateManager sm, Object val, + FieldMetaData fmd, FieldMetaData[] orphanRemoves) { + if (val == null) + return; + OpenJPAStateManager other = sm.getContext().getStateManager(val); + if (other == null || other.isDeleted()) + return; + + for (int i = 0; i < orphanRemoves.length; i++) { + + // if this is the owned side of the relation and has not yet been + // loaded, no point in setting it now, cause it'll have the correct + // value the next time it is loaded after the flush + switch (orphanRemoves[i].getDeclaredTypeCode()) { + case JavaTypes.PC: + if (fmd.getOrphanRemoval() || fmd.getElement().getOrphanRemoval()) + ((StateManagerImpl)other).delete(); + break; + case JavaTypes.COLLECTION: + removeFromCollection(other, orphanRemoves[i], + sm.getManagedInstance()); + break; + } + } + } + + /** + * Remove the given instance from the collection. + */ + protected static void removeFromCollection(OpenJPAStateManager sm, + FieldMetaData fmd, Object val) { + Collection coll = (Collection) sm.fetchObjectField(fmd.getIndex()); + if (coll != null) + coll.remove(val); + } + + +} diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SaveFieldManager.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SaveFieldManager.java index 9a8b0f45b..0ab30f1a2 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SaveFieldManager.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SaveFieldManager.java @@ -113,11 +113,12 @@ public class SaveFieldManager mutable = true; } - // if this is not an inverse field and the proper restore flag is - // not set, skip it + // if this is not an inverse field or a field subject to orphanRemoval + // and the proper restore flag is not set, skip it - if (_sm.getBroker().getInverseManager() == null - || fmd.getInverseMetaDatas().length == 0) { + if ((_sm.getBroker().getInverseManager() == null + || fmd.getInverseMetaDatas().length == 0) && + (fmd.getOrphanRemovalMetaDatas().length == 0)) { // use sm's restore directive, not broker's int restore = _sm.getBroker().getRestoreState(); if (restore == RestoreState.RESTORE_NONE diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SingleFieldManager.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SingleFieldManager.java index b2f4e4071..7fa0bfed2 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SingleFieldManager.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SingleFieldManager.java @@ -481,6 +481,11 @@ class SingleFieldManager InverseManager manager = _broker.getInverseManager(); if (manager != null) manager.correctRelations(_sm, fmd, objval); + else { + FieldMetaData[] orphans = fmd.getOrphanRemovalMetaDatas(); + if (orphans.length > 0) + OrphanRemovalManager.correctRelations(_sm, fmd, objval); + } return ret; } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java index c9e7d3d68..e57158704 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java @@ -105,6 +105,7 @@ public class StateManagerImpl private static final int FLAG_VERSION_CHECK = 2 << 14; private static final int FLAG_VERSION_UPDATE = 2 << 15; private static final int FLAG_DETACHING = 2 << 16; + private static final int FLAG_ORPHAN_REMOVAL = 2 << 17; private static final Localizer _loc = Localizer.forPackage (StateManagerImpl.class); @@ -306,6 +307,10 @@ public class StateManagerImpl if (_broker.getInverseManager() != null && fmds[i].getInverseMetaDatas().length > 0) _flags |= FLAG_INVERSES; + // record whether there are any managed fields subject to + // orphan removal + if (fmds[i].getOrphanRemovalMetaDatas().length > 0) + _flags |= FLAG_ORPHAN_REMOVAL; } pc.pcSetDetachedState(null); @@ -510,6 +515,12 @@ public class StateManagerImpl ((OpenJPAId) oid).setManagedInstanceType(_meta.getDescribedType()); } + public StateManagerImpl getObjectIdOwner() { + StateManagerImpl sm = this; + while (sm.getOwner() != null) + sm = (StateManagerImpl) sm.getOwner(); + return sm; + } public boolean assignObjectId(boolean flush) { lock(); try { @@ -853,9 +864,12 @@ public class StateManagerImpl public Object fetchInitialField(int field) { FieldMetaData fmd = _meta.getField(field); + boolean hasInverse = (_flags & FLAG_INVERSES) != 0 + && fmd.getInverseMetaDatas().length > 0; + boolean hasOrphanRemoval = (_flags & FLAG_ORPHAN_REMOVAL) != 0 + && fmd.getOrphanRemovalMetaDatas().length > 0; if (_broker.getRestoreState() == RestoreState.RESTORE_NONE - && ((_flags & FLAG_INVERSES) == 0 - || fmd.getInverseMetaDatas().length == 0)) + && (!hasInverse && !hasOrphanRemoval)) throw new InvalidStateException(_loc.get("restore-unset")); switch (fmd.getDeclaredTypeCode()) { @@ -867,8 +881,7 @@ public class StateManagerImpl case JavaTypes.OBJECT: // if we're not saving mutable types, throw an exception if (_broker.getRestoreState() != RestoreState.RESTORE_ALL - && ((_flags & FLAG_INVERSES) == 0 - || fmd.getInverseMetaDatas().length == 0)) + && (!hasInverse && !hasOrphanRemoval)) throw new InvalidStateException(_loc.get ("mutable-restore-unset")); } @@ -2666,7 +2679,8 @@ public class StateManagerImpl */ void saveFields(boolean immediate) { if (_broker.getRestoreState() == RestoreState.RESTORE_NONE - && (_flags & FLAG_INVERSES) == 0) + && (((_flags & FLAG_INVERSES) == 0) + || ((_flags & FLAG_ORPHAN_REMOVAL) == 0))) return; _flags |= FLAG_SAVE; @@ -2694,8 +2708,12 @@ public class StateManagerImpl // if this is a managed inverse field, load it so we're sure to have // the original value - if (!_loaded.get(field) && ((_flags & FLAG_INVERSES) != 0 - && _meta.getField(field).getInverseMetaDatas().length > 0)) + FieldMetaData fmd = _meta.getField(field); + boolean hasInverse = (_flags & FLAG_INVERSES) != 0 + && fmd.getInverseMetaDatas().length > 0; + boolean hasOrphanRemoval = (_flags & FLAG_ORPHAN_REMOVAL) != 0 + && fmd.getOrphanRemovalMetaDatas().length > 0; + if (!_loaded.get(field) && (hasInverse || hasOrphanRemoval)) loadField(field, LockLevels.LOCK_NONE, false, false); // don't bother creating the save field manager if we're not going to diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/meta/FieldMetaData.java b/openjpa-kernel/src/main/java/org/apache/openjpa/meta/FieldMetaData.java index 151c53c04..dc7fcfaa2 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/meta/FieldMetaData.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/meta/FieldMetaData.java @@ -210,6 +210,7 @@ public class FieldMetaData // indicate if this field is used by other field as "order by" value private boolean _usedInOrderBy = false; private boolean _isElementCollection = false; + private FieldMetaData[] _orphanRemoves = null; /** * Constructor. @@ -911,6 +912,7 @@ public class FieldMetaData break; case JavaTypes.ARRAY: case JavaTypes.COLLECTION: + case JavaTypes.MAP: meta = _elem.getTypeMetaData(); break; } @@ -979,6 +981,72 @@ public class FieldMetaData return _inverses; } + /** + * Return all orphanRemoval meta data of this field. + */ + public FieldMetaData[] getOrphanRemovalMetaDatas() { + if (_orphanRemoves == null) { + // get the metadata for the type on the other side of this relation + ClassMetaData meta = null; + switch (getTypeCode()) { + case JavaTypes.PC: + meta = getTypeMetaData(); + break; + case JavaTypes.ARRAY: + case JavaTypes.COLLECTION: + case JavaTypes.MAP: + meta = _elem.getTypeMetaData(); + break; + } + Collection orphanRemoves = null; + if (meta != null) { + // scan rel type for fields that name this field as an inverse + FieldMetaData[] fields = meta.getFields(); + Class type = getOwnerType(); + //Class type = getDeclaringMetaData().getDescribedType(); + for (int i = 0; i < fields.length; i++) { + // skip fields that aren't compatible with our owning class + switch (fields[i].getTypeCode()) { + case JavaTypes.PC: + if (!type.isAssignableFrom(fields[i].getType())) + continue; + break; + case JavaTypes.COLLECTION: + case JavaTypes.ARRAY: + if (!type.isAssignableFrom(fields[i]. + getElement().getType())) + continue; + break; + default: + continue; + } + + if (orphanRemoves == null) + orphanRemoves = new ArrayList(3); + if (!orphanRemoves.contains(fields[i])) + orphanRemoves.add(fields[i]); + } + } + + MetaDataRepository repos = getRepository(); + if (orphanRemoves == null) + _orphanRemoves = repos.EMPTY_FIELDS; + else + _orphanRemoves = (FieldMetaData[]) orphanRemoves.toArray + (repos.newFieldMetaDataArray(orphanRemoves.size())); + } + return _orphanRemoves; + } + + public Class getOwnerType() { + ClassMetaData owner = getDefiningMetaData(); + ValueMetaData vm = owner.getEmbeddingMetaData(); + if (vm == null) + return owner.getDescribedType(); + FieldMetaData fmd = vm.getFieldMetaData(); + return fmd.getOwnerType(); + } + /** * The strategy to use for insert value generation. * One of the constants from {@link ValueStrategies}. @@ -1997,6 +2065,14 @@ public class FieldMetaData public void setCascadeDelete(int delete) { _val.setCascadeDelete(delete); } + + public boolean getOrphanRemoval() { + return _val.getOrphanRemoval(); + } + + public void setOrphanRemoval(boolean orphanRemoval) { + _val.setOrphanRemoval(orphanRemoval); + } public int getCascadePersist() { return _val.getCascadePersist(); diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueMetaData.java b/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueMetaData.java index ffe7ef46a..4d2f53123 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueMetaData.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueMetaData.java @@ -302,4 +302,14 @@ public interface ValueMetaData * information. */ public void copy(ValueMetaData vmd); + + /** + * Whether this value is subject to orphanRemoval. + */ + public void setOrphanRemoval(boolean orphanRemoval); + + /** + * Whether this value is subject to orphanRemoval. + */ + public boolean getOrphanRemoval(); } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueMetaDataImpl.java b/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueMetaDataImpl.java index ff289c8e3..1505662c6 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueMetaDataImpl.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueMetaDataImpl.java @@ -57,6 +57,7 @@ public class ValueMetaDataImpl private int _resMode = MODE_NONE; private String _mappedBy = null; private FieldMetaData _mappedByMeta = null; + private boolean _orphanRemoval = false; protected ValueMetaDataImpl(FieldMetaData owner) { _owner = owner; @@ -227,6 +228,14 @@ public class ValueMetaDataImpl _delete = delete; } + public boolean getOrphanRemoval() { + return _orphanRemoval; + } + + public void setOrphanRemoval(boolean orphanRemoval) { + _orphanRemoval = orphanRemoval; + } + public int getCascadePersist() { if (_owner.getManagement() != FieldMetaData.MANAGE_PERSISTENT) return CASCADE_NONE; diff --git a/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/AnnotationPersistenceMappingParser.java b/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/AnnotationPersistenceMappingParser.java index 9fd59aef7..b00badc85 100644 --- a/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/AnnotationPersistenceMappingParser.java +++ b/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/AnnotationPersistenceMappingParser.java @@ -1747,10 +1747,6 @@ public class AnnotationPersistenceMappingParser * Parse @MapKeyColumn. */ protected void parseMapKeyColumn(FieldMapping fm, MapKeyColumn anno) { - if (!fm.isElementCollection()) - throw new UnsupportedException(_loc.get("unsupported", fm, - anno.toString())); - int unique = 0; Column col = new Column(); setupMapKeyColumn(fm, col, anno); diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java index fe2a3a99f..dbd302557 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java @@ -1330,6 +1330,7 @@ public class AnnotationPersistenceMetaDataParser if (anno.targetEntity() != void.class) fmd.setTypeOverride(anno.targetEntity()); setCascades(fmd, anno.cascade()); + setOrphanRemoval(fmd, anno.orphanRemoval()); } /** @@ -1368,6 +1369,7 @@ public class AnnotationPersistenceMetaDataParser if (anno.targetEntity() != void.class) fmd.getElement().setDeclaredType(anno.targetEntity()); setCascades(fmd.getElement(), anno.cascade()); + setOrphanRemoval(fmd.getElement(), anno.orphanRemoval()); } /** @@ -1563,6 +1565,11 @@ public class AnnotationPersistenceMetaDataParser vmd.setCascadeRefresh(ValueMetaData.CASCADE_IMMEDIATE); } } + private void setOrphanRemoval(ValueMetaData vmd, boolean orphanRemoval) { + vmd.setOrphanRemoval(orphanRemoval); + if (orphanRemoval) + setCascades(vmd, new CascadeType[] {CascadeType.REMOVE}); + } /** * Parse @SequenceGenerator.