Skip to content

Commit cb87ad8

Browse files
committed
fix #951 for merging transient instance
1 parent 4058a85 commit cb87ad8

File tree

3 files changed

+129
-33
lines changed

3 files changed

+129
-33
lines changed

hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister;
1818
import org.hibernate.reactive.session.ReactiveQueryExecutor;
1919
import org.hibernate.type.EntityType;
20+
import org.hibernate.type.ForeignKeyDirection;
2021
import org.hibernate.type.OneToOneType;
2122
import org.hibernate.type.Type;
2223
import org.hibernate.type.TypeHelper;
@@ -168,7 +169,71 @@ public static CompletionStage<Object[]> replace(
168169
owner,
169170
copyCache
170171
).thenAccept( copy -> copied[i] = copy )
171-
).thenApply(v -> copied );
172+
).thenApply( v -> copied );
173+
}
174+
175+
/**
176+
* @see TypeHelper#replace(Object[], Object[], Type[], SharedSessionContractImplementor, Object, Map, ForeignKeyDirection)
177+
*/
178+
public static CompletionStage<Object[]> replace(
179+
final Object[] original,
180+
final Object[] target,
181+
final Type[] types,
182+
final SessionImplementor session,
183+
final Object owner,
184+
final Map copyCache,
185+
final ForeignKeyDirection foreignKeyDirection) {
186+
Object[] copied = new Object[original.length];
187+
for ( int i=0; i<types.length; i++ ) {
188+
if ( original[i] == UNFETCHED_PROPERTY || original[i] == UNKNOWN ) {
189+
copied[i] = target[i];
190+
}
191+
else {
192+
if ( !(types[i] instanceof EntityType) ) {
193+
copied[i] = types[i].replace(
194+
original[i],
195+
target[i] == UNFETCHED_PROPERTY ? null : target[i],
196+
session,
197+
owner,
198+
copyCache,
199+
foreignKeyDirection
200+
);
201+
}
202+
}
203+
}
204+
return loop(0, types.length,
205+
i -> original[i] != UNFETCHED_PROPERTY && original[i] != UNKNOWN
206+
&& types[i] instanceof EntityType,
207+
i -> replace(
208+
(EntityType) types[i],
209+
original[i],
210+
target[i] == UNFETCHED_PROPERTY ? null : target[i],
211+
session,
212+
owner,
213+
copyCache,
214+
foreignKeyDirection
215+
).thenAccept( copy -> copied[i] = copy )
216+
).thenApply( v -> copied );
217+
}
218+
219+
/**
220+
* @see org.hibernate.type.AbstractType#replace(Object, Object, SharedSessionContractImplementor, Object, Map, ForeignKeyDirection)
221+
*/
222+
private static CompletionStage<Object> replace(
223+
EntityType entityType,
224+
Object original,
225+
Object target,
226+
SessionImplementor session,
227+
Object owner,
228+
Map copyCache,
229+
ForeignKeyDirection foreignKeyDirection)
230+
throws HibernateException {
231+
boolean include = entityType.isAssociationType()
232+
? entityType.getForeignKeyDirection() == foreignKeyDirection
233+
: ForeignKeyDirection.FROM_PARENT == foreignKeyDirection;
234+
return include
235+
? replace( entityType, original, target, session, owner, copyCache )
236+
: completedFuture(target);
172237
}
173238

174239
/**

hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,11 @@ protected CompletionStage<Void> entityIsTransient(MergeEvent event, MergeContext
253253
// copy created before we actually copy
254254
//cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE);
255255
return super.cascadeBeforeSave( session, persister, entity, copyCache )
256-
.thenAccept( v -> copyValues( persister, entity, copy, session, copyCache, FROM_PARENT ) )
256+
.thenCompose( v -> copyValues( persister, entity, copy, session, copyCache, FROM_PARENT ) )
257257
.thenCompose( v -> saveTransientEntity( copy, entityName, event.getRequestedId(), session, copyCache ) )
258258
.thenCompose( v -> super.cascadeAfterSave( session, persister, entity, copyCache ) )
259+
.thenCompose( v -> copyValues( persister, entity, copy, session, copyCache, TO_PARENT ) )
259260
.thenAccept( v -> {
260-
copyValues( persister, entity, copy, session, copyCache, TO_PARENT );
261-
262261
event.setResult(copy);
263262

264263
if (copy instanceof PersistentAttributeInterceptable) {
@@ -280,12 +279,9 @@ private CompletionStage<Void> saveTransientEntity(
280279
//this bit is only *really* absolutely necessary for handling
281280
//requestedId, but is also good if we merge multiple object
282281
//graphs, since it helps ensure uniqueness
283-
if ( requestedId == null ) {
284-
return reactiveSaveWithGeneratedId( entity, entityName, copyCache, source, false );
285-
}
286-
else {
287-
return reactiveSaveWithRequestedId( entity, requestedId, entityName, copyCache, source );
288-
}
282+
return requestedId == null
283+
? reactiveSaveWithGeneratedId( entity, entityName, copyCache, source, false )
284+
: reactiveSaveWithRequestedId( entity, requestedId, entityName, copyCache, source );
289285
}
290286

291287
protected CompletionStage<Void> entityIsDetached(MergeEvent event, MergeContext copyCache) {
@@ -414,10 +410,11 @@ private void markInterceptorDirty(final Object entity, final Object target, Enti
414410
// for enhanced entities, copy over the dirty attributes
415411
if ( entity instanceof SelfDirtinessTracker && target instanceof SelfDirtinessTracker ) {
416412
// clear, because setting the embedded attributes dirties them
417-
( (SelfDirtinessTracker) target ).$$_hibernate_clearDirtyAttributes();
418-
419-
for ( String fieldName : ( (SelfDirtinessTracker) entity ).$$_hibernate_getDirtyAttributes() ) {
420-
( (SelfDirtinessTracker) target ).$$_hibernate_trackChange( fieldName );
413+
SelfDirtinessTracker entityTracker = (SelfDirtinessTracker) entity;
414+
SelfDirtinessTracker targetTracker = (SelfDirtinessTracker) target;
415+
targetTracker.$$_hibernate_clearDirtyAttributes();
416+
for ( String fieldName : entityTracker.$$_hibernate_getDirtyAttributes() ) {
417+
targetTracker.$$_hibernate_trackChange( fieldName );
421418
}
422419
}
423420
}
@@ -504,21 +501,19 @@ protected CompletionStage<Void> copyValues(
504501
).thenAccept( copiedValues -> persister.setPropertyValues( target, copiedValues ) );
505502
}
506503

507-
protected void copyValues(
504+
protected CompletionStage<Void> copyValues(
508505
final EntityPersister persister,
509506
final Object entity,
510507
final Object target,
511508
final SessionImplementor source,
512509
final MergeContext copyCache,
513510
final ForeignKeyDirection foreignKeyDirection) {
514511

515-
final Object[] copiedValues;
516-
517512
if ( foreignKeyDirection == TO_PARENT ) {
518513
// this is the second pass through on a merge op, so here we limit the
519514
// replacement to associations types (value types were already replaced
520515
// during the first pass)
521-
copiedValues = TypeHelper.replaceAssociations(
516+
Object[] copiedValues = TypeHelper.replaceAssociations(
522517
persister.getPropertyValues( entity ),
523518
persister.getPropertyValues( target ),
524519
persister.getPropertyTypes(),
@@ -527,20 +522,20 @@ protected void copyValues(
527522
copyCache,
528523
foreignKeyDirection
529524
);
525+
persister.setPropertyValues( target, copiedValues );
526+
return voidFuture();
530527
}
531528
else {
532-
copiedValues = TypeHelper.replace(
529+
return EntityTypes.replace(
533530
persister.getPropertyValues( entity ),
534531
persister.getPropertyValues( target ),
535532
persister.getPropertyTypes(),
536533
source,
537534
target,
538535
copyCache,
539536
foreignKeyDirection
540-
);
537+
).thenAccept( copiedValues -> persister.setPropertyValues( target, copiedValues ) );
541538
}
542-
543-
persister.setPropertyValues( target, copiedValues );
544539
}
545540

546541
/**
@@ -576,17 +571,15 @@ protected CascadingAction<MergeContext> getCascadeReactiveAction() {
576571
* Cascade behavior is redefined by this subclass, disable superclass behavior
577572
*/
578573
@Override
579-
protected CompletionStage<Void> cascadeAfterSave(EventSource source, EntityPersister persister, Object entity, MergeContext anything)
580-
throws HibernateException {
574+
protected CompletionStage<Void> cascadeAfterSave(EventSource source, EntityPersister persister, Object entity, MergeContext anything) {
581575
return voidFuture();
582576
}
583577

584578
/**
585579
* Cascade behavior is redefined by this subclass, disable superclass behavior
586580
*/
587581
@Override
588-
protected CompletionStage<Void> cascadeBeforeSave(EventSource source, EntityPersister persister, Object entity, MergeContext anything)
589-
throws HibernateException {
582+
protected CompletionStage<Void> cascadeBeforeSave(EventSource source, EntityPersister persister, Object entity, MergeContext anything) {
590583
return voidFuture();
591584
}
592585

hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToOneMergeTest.java

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import org.hibernate.cfg.Configuration;
1818

19+
import org.junit.After;
1920
import org.junit.Before;
2021
import org.junit.Test;
2122

@@ -48,10 +49,21 @@ public void populateDb(TestContext context) {
4849
academicYearDetailsDBO.setRecordStatus( 'F' );
4950
academicYearDetailsDBO.setModifiedUsersId( 66 );
5051
test( context,
51-
getMutinySessionFactory().withTransaction( (session, transaction) -> session.persistAll(
52-
campusDBO, campusDBO2,
53-
academicYearDetailsDBO
54-
) )
52+
getMutinySessionFactory()
53+
.withTransaction( (session, transaction) -> session.persistAll(
54+
campusDBO, campusDBO2,
55+
academicYearDetailsDBO
56+
) )
57+
);
58+
}
59+
60+
@After
61+
public void cleanDb(TestContext context) {
62+
test( context,
63+
getMutinySessionFactory()
64+
.withTransaction( (session, transaction) ->
65+
session.createQuery("delete from AcademicYearDetailsDBO").executeUpdate()
66+
.chain( v -> session.createQuery("delete from CampusDBO").executeUpdate() ) )
5567
);
5668
}
5769

@@ -64,9 +76,9 @@ public void test(TestContext context) {
6476
.chain( dbo -> {
6577
dbo.setRecordStatus( 'A' );
6678
System.out.println( dbo.getCampusId().getId() );//for example here campus id is 11
67-
CampusDBO campusDBO = new CampusDBO();
68-
campusDBO.setId( 5 );
69-
dbo.setCampusId( campusDBO ); // need to update as 5
79+
// CampusDBO campusDBO = new CampusDBO();
80+
// campusDBO.setId( 5 );
81+
// dbo.setCampusId( campusDBO ); // need to update as 5
7082
return getMutinySessionFactory()
7183
.withTransaction( (session, transaction) -> {
7284
dbo.setCampusId( session.getReference( CampusDBO.class, 42 ) );
@@ -76,6 +88,32 @@ public void test(TestContext context) {
7688
);
7789
}
7890

91+
@Test
92+
public void testTransient(TestContext context) {
93+
CampusDBO campusDBO = new CampusDBO();
94+
campusDBO.setId( 77 );
95+
campusDBO.setCampusName( "Qualunquelandia" );
96+
97+
AcademicYearDetailsDBO dbo = new AcademicYearDetailsDBO();
98+
dbo.setId( 88 );
99+
dbo.setCampusId( campusDBO );
100+
dbo.setCreatedUsersId( 12 );
101+
dbo.setRecordStatus( 'F' );
102+
dbo.setModifiedUsersId( 66 );
103+
104+
test( context, getMutinySessionFactory()
105+
.withSession( session -> {
106+
dbo.setRecordStatus( 'A' );
107+
System.out.println( dbo.getCampusId().getId() );//for example here campus id is 11
108+
// CampusDBO cdbo = new CampusDBO();
109+
// cdbo.setId( 5 );
110+
// dbo.setCampusId( cdbo ); // need to update as 5
111+
dbo.setCampusId( session.getReference( CampusDBO.class, 42 ) );
112+
return session.merge( dbo );
113+
} )
114+
);
115+
}
116+
79117
@Entity(name = "AcademicYearDetailsDBO")
80118
@Table(name = "erp_academic_year_detail")
81119
static class AcademicYearDetailsDBO implements Serializable {

0 commit comments

Comments
 (0)