|
9 | 9 | import org.hibernate.CacheMode;
|
10 | 10 | import org.hibernate.HibernateException;
|
11 | 11 | import org.hibernate.LockMode;
|
| 12 | +import org.hibernate.NonUniqueObjectException; |
12 | 13 | import org.hibernate.TransientObjectException;
|
13 | 14 | import org.hibernate.action.internal.CollectionRemoveAction;
|
14 | 15 | import org.hibernate.action.internal.EntityDeleteAction;
|
|
44 | 45 | import org.hibernate.persister.entity.EntityPersister;
|
45 | 46 | import org.hibernate.pretty.MessageHelper;
|
46 | 47 | import org.hibernate.property.access.internal.PropertyAccessStrategyBackRefImpl;
|
47 |
| -import org.hibernate.proxy.HibernateProxy; |
48 | 48 | import org.hibernate.proxy.LazyInitializer;
|
49 | 49 | import org.hibernate.type.CollectionType;
|
50 | 50 | import org.hibernate.type.ComponentType;
|
51 | 51 | import org.hibernate.type.Type;
|
52 | 52 | import org.hibernate.type.TypeHelper;
|
53 | 53 |
|
54 | 54 | import static org.hibernate.engine.internal.Collections.skipRemoval;
|
| 55 | +import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; |
55 | 56 |
|
56 | 57 | /**
|
57 | 58 | * Defines the default delete event listener used by hibernate for deleting entities
|
@@ -102,7 +103,7 @@ public void onDelete(DeleteEvent event, DeleteContext transientEntities) throws
|
102 | 103 |
|
103 | 104 | private boolean optimizeUnloadedDelete(DeleteEvent event) {
|
104 | 105 | final Object object = event.getObject();
|
105 |
| - final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( object ); |
| 106 | + final LazyInitializer lazyInitializer = extractLazyInitializer( object ); |
106 | 107 | if ( lazyInitializer != null ) {
|
107 | 108 | if ( lazyInitializer.isUninitialized() ) {
|
108 | 109 | final EventSource source = event.getSession();
|
@@ -157,54 +158,76 @@ private void delete(DeleteEvent event, DeleteContext transientEntities) {
|
157 | 158 | final Object entity = persistenceContext.unproxyAndReassociate( event.getObject() );
|
158 | 159 | final EntityEntry entityEntry = persistenceContext.getEntry( entity );
|
159 | 160 | if ( entityEntry == null ) {
|
160 |
| - deleteTransientInstance( event, transientEntities, entity ); |
| 161 | + deleteUnmanagedInstance( event, transientEntities, entity ); |
161 | 162 | }
|
162 | 163 | else {
|
163 | 164 | deletePersistentInstance( event, transientEntities, entity, entityEntry );
|
164 | 165 | }
|
165 | 166 | }
|
166 | 167 |
|
167 |
| - private void deleteTransientInstance(DeleteEvent event, DeleteContext transientEntities, Object entity) { |
168 |
| - LOG.trace( "Entity was not persistent in delete processing" ); |
169 |
| - |
| 168 | + private void deleteUnmanagedInstance(DeleteEvent event, DeleteContext transientEntities, Object entity) { |
| 169 | + LOG.trace( "Deleted entity was not associated with current session" ); |
170 | 170 | final EventSource source = event.getSession();
|
171 |
| - |
172 | 171 | final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
|
173 | 172 | if ( ForeignKeys.isTransient( persister.getEntityName(), entity, null, source ) ) {
|
174 | 173 | deleteTransientEntity( source, entity, persister, transientEntities );
|
175 | 174 | }
|
176 | 175 | else {
|
177 | 176 | performDetachedEntityDeletionCheck( event );
|
| 177 | + deleteDetachedEntity( event, transientEntities, entity, persister, source ); |
| 178 | + } |
| 179 | + } |
178 | 180 |
|
179 |
| - final Object id = persister.getIdentifier( entity, source ); |
180 |
| - if ( id == null ) { |
181 |
| - throw new TransientObjectException( "Cannot delete instance of entity '" + persister.getEntityName() |
182 |
| - + "' because it has a null identifier" ); |
183 |
| - } |
| 181 | + private void deleteDetachedEntity(DeleteEvent event, DeleteContext transientEntities, Object entity, EntityPersister persister, EventSource source) { |
| 182 | + final Object id = persister.getIdentifier( entity, source ); |
| 183 | + if ( id == null ) { |
| 184 | + throw new TransientObjectException( "Cannot delete instance of entity '" |
| 185 | + + persister.getEntityName() + "' because it has a null identifier" ); |
| 186 | + } |
184 | 187 |
|
185 |
| - final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); |
186 |
| - final EntityKey key = source.generateEntityKey( id, persister ); |
| 188 | + final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); |
| 189 | + final EntityKey key = source.generateEntityKey( id, persister); |
| 190 | + final Object version = persister.getVersion(entity); |
187 | 191 |
|
188 |
| - persistenceContext.checkUniqueness( key, entity); |
| 192 | +// persistenceContext.checkUniqueness( key, entity ); |
| 193 | + flushAndEvictExistingEntity( key, version, persister, source ); |
189 | 194 |
|
190 |
| - new OnUpdateVisitor( source, id, entity ).process( entity, persister ); |
| 195 | + new OnUpdateVisitor( source, id, entity ).process( entity, persister ); |
191 | 196 |
|
192 |
| - final Object version = persister.getVersion( entity ); |
| 197 | + final EntityEntry entityEntry = persistenceContext.addEntity( |
| 198 | + entity, |
| 199 | + persister.isMutable() ? Status.MANAGED : Status.READ_ONLY, |
| 200 | + persister.getValues(entity), |
| 201 | + key, |
| 202 | + version, |
| 203 | + LockMode.NONE, |
| 204 | + true, |
| 205 | + persister, |
| 206 | + false |
| 207 | + ); |
| 208 | + persister.afterReassociate(entity, source); |
193 | 209 |
|
194 |
| - final EntityEntry entityEntry = persistenceContext.addEntity( |
195 |
| - entity, |
196 |
| - persister.isMutable() ? Status.MANAGED : Status.READ_ONLY, |
197 |
| - persister.getValues( entity ), |
198 |
| - key, |
199 |
| - version, |
200 |
| - LockMode.NONE, |
201 |
| - true, |
202 |
| - persister, |
203 |
| - false |
204 |
| - ); |
205 |
| - persister.afterReassociate( entity, source ); |
| 210 | + delete( event, transientEntities, source, entity, persister, id, version, entityEntry ); |
| 211 | + } |
206 | 212 |
|
207 |
| - delete( event, transientEntities, source, entity, persister, id, version, entityEntry ); |
| 213 | + /** |
| 214 | + * Since Hibernate 7, if a detached instance is passed to remove(), |
| 215 | + * and if there is already an existing managed entity with the same |
| 216 | + * id, flush and evict it, after checking that the versions match. |
| 217 | + */ |
| 218 | + private static void flushAndEvictExistingEntity( |
| 219 | + EntityKey key, Object version, EntityPersister persister, EventSource source) { |
| 220 | + final Object existingEntity = source.getPersistenceContextInternal().getEntity( key ); |
| 221 | + if ( existingEntity != null ) { |
| 222 | + source.flush(); |
| 223 | + if ( !persister.isVersioned() |
| 224 | + || persister.getVersionType() |
| 225 | + .isEqual( version, persister.getVersion( existingEntity ) ) ) { |
| 226 | + source.evict( existingEntity ); |
| 227 | + } |
| 228 | + else { |
| 229 | + throw new NonUniqueObjectException( key.getIdentifier(), key.getEntityName() ); |
| 230 | + } |
208 | 231 | }
|
209 | 232 | }
|
210 | 233 |
|
@@ -287,33 +310,31 @@ private static boolean hasCustomEventListeners(EventSource source) {
|
287 | 310 | }
|
288 | 311 |
|
289 | 312 | private boolean hasRegisteredRemoveCallbacks(EntityPersister persister) {
|
290 |
| - Class<?> mappedClass = persister.getMappedClass(); |
| 313 | + final Class<?> mappedClass = persister.getMappedClass(); |
291 | 314 | return callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.PRE_REMOVE )
|
292 | 315 | || callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.POST_REMOVE );
|
293 | 316 | }
|
294 | 317 |
|
295 | 318 | /**
|
296 | 319 | * Called when we have recognized an attempt to delete a detached entity.
|
297 | 320 | * <p>
|
298 |
| - * This is perfectly valid in Hibernate usage; JPA, however, forbids this. |
299 |
| - * Thus, this is a hook for HEM to affect this behavior. |
300 |
| - * |
301 |
| - * @param event The event. |
| 321 | + * This is perfectly legal in regular Hibernate usage; the JPA spec, |
| 322 | + * however, forbids it. |
302 | 323 | */
|
303 | 324 | protected void performDetachedEntityDeletionCheck(DeleteEvent event) {
|
304 | 325 | if ( jpaBootstrap ) {
|
305 | 326 | disallowDeletionOfDetached( event );
|
306 | 327 | }
|
307 |
| - // ok in normal Hibernate usage to delete a detached entity; JPA however |
308 |
| - // forbids it, thus this is a hook for HEM to affect this behavior |
309 | 328 | }
|
310 | 329 |
|
311 | 330 | private void disallowDeletionOfDetached(DeleteEvent event) {
|
312 |
| - EventSource source = event.getSession(); |
313 |
| - String entityName = event.getEntityName(); |
314 |
| - EntityPersister persister = source.getEntityPersister( entityName, event.getObject() ); |
315 |
| - Object id = persister.getIdentifier( event.getObject(), source ); |
316 |
| - entityName = entityName == null ? source.guessEntityName( event.getObject() ) : entityName; |
| 331 | + final EventSource source = event.getSession(); |
| 332 | + final String explicitEntityName = event.getEntityName(); |
| 333 | + final EntityPersister persister = source.getEntityPersister( explicitEntityName, event.getObject() ); |
| 334 | + final Object id = persister.getIdentifier( event.getObject(), source ); |
| 335 | + final String entityName = explicitEntityName == null |
| 336 | + ? source.guessEntityName( event.getObject() ) |
| 337 | + : explicitEntityName; |
317 | 338 | throw new IllegalArgumentException( "Given entity is not associated with the persistence context [" + entityName + "#" + id + "]" );
|
318 | 339 | }
|
319 | 340 |
|
|
0 commit comments