Skip to content

ORM 5.6.7.Final and Fix for non nullable associations #1241

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Hibernate Reactive has been tested with:
- CockroachDB 21.2
- MS SQL Server 2019
- Oracle 21.3
- [Hibernate ORM][] 5.6.6.Final
- [Hibernate ORM][] 5.6.7.Final
- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.2.4
- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.2.4
- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.2.4
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ version = projectVersion
// ./gradlew clean build -PhibernateOrmVersion=5.5.1-SNAPSHOT
ext {
if ( !project.hasProperty('hibernateOrmVersion') ) {
hibernateOrmVersion = '5.6.6.Final'
hibernateOrmVersion = '5.6.7.Final'
}
// For ORM, we need a parsed version (to get the family, ...)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import org.hibernate.action.spi.BeforeTransactionCompletionProcess;
import org.hibernate.action.spi.Executable;
import org.hibernate.cache.CacheException;
import org.hibernate.engine.internal.NonNullableTransientDependencies;
import org.hibernate.engine.spi.ActionQueue;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ExecutableList;
Expand Down Expand Up @@ -343,24 +342,25 @@ private CompletionStage<Void> addInsertAction(ReactiveEntityInsertAction insert)
ret = ret.thenCompose( v -> executeInserts() );
}

NonNullableTransientDependencies nonNullableTransientDependencies = insert.findNonNullableTransientEntities();
if ( nonNullableTransientDependencies == null ) {
LOG.tracev( "Adding insert with no non-nullable, transient entities: [{0}]", insert );
ret = ret.thenCompose( v -> addResolvedEntityInsertAction( insert ) );
}
else {
if ( LOG.isTraceEnabled() ) {
LOG.tracev( "Adding insert with non-nullable, transient entities; insert=[{0}], dependencies=[{1}]",
insert,
nonNullableTransientDependencies.toLoggableString( insert.getSession() )
);
}
if ( unresolvedInsertions == null ) {
unresolvedInsertions = new UnresolvedEntityInsertActions();
}
unresolvedInsertions.addUnresolvedEntityInsertAction( (AbstractEntityInsertAction) insert, nonNullableTransientDependencies );
}
return ret;
return ret
.thenCompose( v -> insert.reactiveFindNonNullableTransientEntities() )
.thenCompose( nonNullables -> {
if ( nonNullables == null ) {
LOG.tracev( "Adding insert with no non-nullable, transient entities: [{0}]", insert );
return addResolvedEntityInsertAction( insert );
}
else {
if ( LOG.isTraceEnabled() ) {
LOG.tracev( "Adding insert with non-nullable, transient entities; insert=[{0}], dependencies=[{1}]", insert, nonNullables.toLoggableString( insert.getSession() ) );
}
if ( unresolvedInsertions == null ) {
unresolvedInsertions = new UnresolvedEntityInsertActions();
}
unresolvedInsertions
.addUnresolvedEntityInsertAction( (AbstractEntityInsertAction) insert, nonNullables );
return voidFuture();
}
} );
}

private CompletionStage<Void> addResolvedEntityInsertAction(ReactiveEntityInsertAction insert) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
import org.hibernate.HibernateException;
import org.hibernate.TransientObjectException;
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
import org.hibernate.engine.internal.NonNullableTransientDependencies;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.SelfDirtinessTracker;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.HibernateProxy;
Expand Down Expand Up @@ -359,6 +361,92 @@ public static CompletionStage<Object> getEntityIdentifierIfNotUnsaved(
}
}

public static CompletionStage<NonNullableTransientDependencies> findNonNullableTransientEntities(
String entityName,
Object entity,
Object[] values,
boolean isEarlyInsert,
SharedSessionContractImplementor session) {

final EntityPersister persister = session.getEntityPersister( entityName, entity );
final Type[] types = persister.getPropertyTypes();
final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, (SessionImplementor) session, persister );
final String[] propertyNames = persister.getPropertyNames();
final boolean[] nullability = persister.getPropertyNullability();
final NonNullableTransientDependencies nonNullableTransientEntities = new NonNullableTransientDependencies();

return loop( 0, types.length,
i -> collectNonNullableTransientEntities(
nullifier,
values[i],
propertyNames[i],
types[i],
nullability[i],
session,
nonNullableTransientEntities
)
).thenApply( r -> nonNullableTransientEntities.isEmpty() ? null : nonNullableTransientEntities );
}

private static CompletionStage<Void> collectNonNullableTransientEntities(
Nullifier nullifier,
Object value,
String propertyName,
Type type,
boolean isNullable,
SharedSessionContractImplementor session,
NonNullableTransientDependencies nonNullableTransientEntities) {

if ( value == null ) {
return voidFuture();
}

if ( type.isEntityType() ) {
final EntityType entityType = (EntityType) type;
if ( !isNullable && !entityType.isOneToOne() ) {
return nullifier
.isNullifiable( entityType.getAssociatedEntityName(), value )
.thenAccept( isNullifiable -> {
if ( isNullifiable ) {
nonNullableTransientEntities.add( propertyName, value );
}
} );
}
}
else if ( type.isAnyType() ) {
if ( !isNullable ) {
return nullifier
.isNullifiable( null, value )
.thenAccept( isNullifiable -> {
if ( isNullifiable ) {
nonNullableTransientEntities.add( propertyName, value );
}
} );
};
}
else if ( type.isComponentType() ) {
final CompositeType actype = (CompositeType) type;
final boolean[] subValueNullability = actype.getPropertyNullability();
if ( subValueNullability != null ) {
final String[] subPropertyNames = actype.getPropertyNames();
final Object[] subvalues = actype.getPropertyValues( value, session );
final Type[] subtypes = actype.getSubtypes();
return loop( 0, subtypes.length,
i -> collectNonNullableTransientEntities(
nullifier,
subvalues[i],
subPropertyNames[i],
subtypes[i],
subValueNullability[i],
session,
nonNullableTransientEntities
)
);
}
}

return voidFuture();
}

/**
* Disallow instantiation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,8 @@ default CompletionStage<Void> reactiveMakeEntityManaged() {
isVersionIncrementDisabled()
));
}

default CompletionStage<NonNullableTransientDependencies> reactiveFindNonNullableTransientEntities() {
return ForeignKeys.findNonNullableTransientEntities( getPersister().getEntityName(), getInstance(), getState(), isEarlyInsert(), getSession() );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/* Hibernate, Relational Persistence for Idiomatic Java
*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.reactive;

import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.hibernate.cfg.Configuration;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import io.vertx.ext.unit.TestContext;

public class NonNullableManyToOneTest extends BaseReactiveTest {

@Override
protected Configuration constructConfiguration() {
Configuration configuration = super.constructConfiguration();
configuration.addAnnotatedClass( Dealer.class );
configuration.addAnnotatedClass( Artist.class );
configuration.addAnnotatedClass( Painting.class );
return configuration;
}

@Before
public void populateDB(TestContext context) {
Artist artist = new Artist( "Grand Master Painter" );
artist.id = 1L;
Dealer dealer = new Dealer( "Dealer" );
dealer.id = 1L;
Painting painting = new Painting( "Mona Lisa");
painting.id = 2L;
artist.addPainting( painting );
dealer.addPainting( painting );

test( context, getMutinySessionFactory()
.withTransaction( s -> s.persistAll( painting, artist, dealer ) ) );
}

@After
public void cleanDB(TestContext context) {
test( context, deleteEntities( "Painting", "Artist" ) );
}

@Test
public void testNonNullableSuccess(TestContext context) {
test(
context,
getMutinySessionFactory().withTransaction( session -> session
.createQuery( "from Artist", Artist.class )
.getSingleResult().chain( a -> session.fetch( a.paintings ) )
.invoke( paintings -> {
context.assertNotNull( paintings );
context.assertEquals( 1, paintings.size() );
context.assertEquals( "Mona Lisa", paintings.get( 0 ).name );
} ) )
.chain( () -> getMutinySessionFactory().withTransaction( s1 -> s1
.createQuery( "from Dealer", Dealer.class )
.getSingleResult().chain( d -> s1.fetch( d.paintings ) )
.invoke( paintings -> {
context.assertNotNull( paintings );
context.assertEquals( 1, paintings.size() );
context.assertEquals( "Mona Lisa", paintings.get( 0 ).name );
} )
)
)
);
}

@Entity(name = "Painting")
@Table(name = "painting")
public static class Painting {
@Id
Long id;
String name;

@JoinColumn(nullable = false)
@ManyToOne(optional = true)
Artist author;

@JoinColumn(nullable = true)
@ManyToOne(optional = false)
Dealer dealer;

public Painting() {
}

public Painting(String name) {
this.name = name;
}
}

@Entity(name = "Artist")
@Table(name = "artist")
public static class Artist {

@Id
Long id;
String name;

@OneToMany(mappedBy = "author")
List<Painting> paintings = new ArrayList<>();

public Artist() {
}

public Artist(String name) {
this.name = name;
}

public void addPainting(Painting painting) {
this.paintings.add( painting );
painting.author = this;
}

}

@Entity(name = "Dealer")
@Table(name = "dealer")
public static class Dealer {

@Id
Long id;
String name;

@OneToMany(mappedBy = "dealer")
List<Painting> paintings = new ArrayList<>();

public Dealer() {
}

public Dealer(String name) {
this.name = name;
}

public void addPainting(Painting painting) {
this.paintings.add( painting );
painting.dealer = this;
}

}
}