Skip to content

Support @IdGeneratorType #2380

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
package org.hibernate.reactive.id;

import org.hibernate.Incubating;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.EventType;
import org.hibernate.generator.Generator;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.reactive.logging.impl.Log;
import org.hibernate.reactive.logging.impl.LoggerFactory;
import org.hibernate.reactive.session.ReactiveConnectionSupplier;

import java.lang.invoke.MethodHandles;
import java.util.concurrent.CompletionStage;

/**
Expand All @@ -26,7 +29,7 @@
* @see IdentifierGenerator
*/
@Incubating
public interface ReactiveIdentifierGenerator<Id> extends Generator {
public interface ReactiveIdentifierGenerator<Id> extends IdentifierGenerator {

/**
* Returns a generated identifier, via a {@link CompletionStage}.
Expand All @@ -38,4 +41,18 @@
default CompletionStage<Id> generate(ReactiveConnectionSupplier session, Object owner, Object currentValue, EventType eventType) {
return generate( session, owner );
}

@Override
default Id generate(

Check notice

Code scanning / CodeQL

Confusing overloading of methods Note

Method ReactiveIdentifierGenerator.generate(..) could be confused with overloaded method
generate
, since dispatch depends on static types.
SharedSessionContractImplementor session,
Object owner,
Object currentValue,
EventType eventType){
throw LoggerFactory.make( Log.class, MethodHandles.lookup() ).nonReactiveMethodCall( "generate(ReactiveConnectionSupplier, Object, Object, EventType)" );
}

@Override
default Object generate(SharedSessionContractImplementor session, Object object){

Check notice

Code scanning / CodeQL

Confusing overloading of methods Note

Method ReactiveIdentifierGenerator.generate(..) could be confused with overloaded method
generate
, since dispatch depends on static types.
throw LoggerFactory.make( Log.class, MethodHandles.lookup() ).nonReactiveMethodCall( "generate(ReactiveConnectionSupplier, Object)" );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* Hibernate, Relational Persistence for Idiomatic Java
*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.reactive.id;

import org.hibernate.dialect.Dialect;
import org.hibernate.generator.OnExecutionGenerator;
import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.reactive.id.insert.ReactiveInsertReturningDelegate;

public interface ReactiveOnExecutionGenerator extends OnExecutionGenerator {

@Override
default InsertGeneratedIdentifierDelegate getGeneratedIdentifierDelegate(EntityPersister persister) {
Dialect dialect = persister.getFactory().getJdbcServices().getDialect();
// Hibernate ORM allows the selection of different strategies based on the property `hibernate.jdbc.use_get_generated_keys`.
// But that's a specific JDBC property and with Vert.x we only have one viable option for each supported database.
return new ReactiveInsertReturningDelegate( persister, dialect );
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
import org.hibernate.reactive.query.sql.spi.ReactiveNativeQueryImplementor;
import org.hibernate.reactive.query.sqm.internal.ReactiveQuerySqmImpl;
import org.hibernate.reactive.query.sqm.internal.ReactiveSqmSelectionQueryImpl;
import org.hibernate.reactive.session.ReactiveConnectionSupplier;
import org.hibernate.reactive.session.ReactiveSqmQueryImplementor;
import org.hibernate.reactive.session.ReactiveStatelessSession;
import org.hibernate.reactive.util.impl.CompletionStages.Completable;
Expand Down Expand Up @@ -438,7 +439,7 @@ private CompletionStage<?> generatedIdBeforeInsert(

private CompletionStage<?> generateIdForInsert(Object entity, Generator generator, ReactiveEntityPersister persister) {
if ( generator instanceof ReactiveIdentifierGenerator<?> reactiveGenerator ) {
return reactiveGenerator.generate( this, this )
return reactiveGenerator.generate( (ReactiveConnectionSupplier) this, this )
.thenApply( id -> castToIdentifierType( id, persister ) );
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/* Hibernate, Relational Persistence for Idiomatic Java
*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.reactive;

import org.hibernate.annotations.IdGeneratorType;
import org.hibernate.generator.EventType;
import org.hibernate.reactive.id.ReactiveIdentifierGenerator;
import org.hibernate.reactive.session.ReactiveConnectionSupplier;
import org.hibernate.reactive.util.impl.CompletionStages;

import org.junit.jupiter.api.Test;

import io.vertx.junit5.Timeout;
import io.vertx.junit5.VertxTestContext;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicLong;

import static java.util.concurrent.TimeUnit.MINUTES;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

@Timeout(value = 10, timeUnit = MINUTES)
public class BeforeExecutionIdGeneratorTypeTest extends BaseReactiveTest {

@Override
protected Collection<Class<?>> annotatedEntities() {
return List.of( Person.class );
}

@Test
public void testPersist(VertxTestContext context) {
Person person = new Person();
test(
context, openSession()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use getMutinySessionFactory.withTransaction?
I think it's more similar to a typical use case.

Maybe we could also test without transactions, but we should at least add a comment to explain what's going on

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to also read the value from the database to make sure that they match? Maybe it's overkill

.thenCompose( session -> session.persist( person ) )
.thenAccept( v -> {
assertThat( person.getId() ).isNotNull();
} )
);
}

@Entity(name = "Person")
public static class Person {
@Id
@SimpleId
long id;

String name;

public Person() {
}

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

public long getId() {
return id;
}

public String getName() {
return name;
}
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@IdGeneratorType(SimpleGenerator.class)
public @interface SimpleId {
}

public static class SimpleGenerator implements ReactiveIdentifierGenerator<Long> {

private AtomicLong sequence = new AtomicLong( 1 );

public SimpleGenerator() {
}

@Override
public boolean generatedOnExecution() {
return false;
}

@Override
public EnumSet<EventType> getEventTypes() {
return EnumSet.of( EventType.INSERT );
}


@Override
public CompletionStage<Long> generate(ReactiveConnectionSupplier session, Object entity) {
return CompletionStages.completedFuture( sequence.getAndIncrement() );
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/* Hibernate, Relational Persistence for Idiomatic Java
*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.reactive;

import org.hibernate.annotations.IdGeneratorType;
import org.hibernate.annotations.ValueGenerationType;
import org.hibernate.dialect.Dialect;
import org.hibernate.generator.EventType;
import org.hibernate.generator.EventTypeSets;
import org.hibernate.reactive.id.ReactiveOnExecutionGenerator;

import org.junit.jupiter.api.Test;

import io.vertx.junit5.Timeout;
import io.vertx.junit5.VertxTestContext;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collection;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;

import static java.util.concurrent.TimeUnit.MINUTES;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

@Timeout(value = 10, timeUnit = MINUTES)
public class OnExecutionGeneratorTypeTest extends BaseReactiveTest {

@Override
protected Collection<Class<?>> annotatedEntities() {
return List.of( Person.class );
}

@Test
public void testPersist(VertxTestContext context) {
Person person = new Person( "Davide" );
test( context, getSessionFactory()
.withTransaction( session -> session.persist( person ) )
.thenAccept( v -> {
assertThat( person.getId() ).isNotNull();
assertThat( person.getCreated() ).isNotNull();
} )
);
}

@Entity(name = "Person")
public static class Person {
@Id
@FunctionCreatedValueId
Date id;

String name;

@FunctionCreatedValue
Date created;

public Person() {
}

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

public Date getId() {
return id;
}

public String getName() {
return name;
}

public Date getCreated() {
return created;
}
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@IdGeneratorType(FunctionCreationValueGeneration.class)
public @interface FunctionCreatedValueId {
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@ValueGenerationType(generatedBy = FunctionCreationValueGeneration.class)
public @interface FunctionCreatedValue {}

public static class FunctionCreationValueGeneration
implements ReactiveOnExecutionGenerator {

@Override
public boolean referenceColumnsInSql(Dialect dialect) {
return true;
}

@Override
public boolean writePropertyValue() {
return false;
}

@Override
public String[] getReferencedColumnValues(Dialect dialect) {
return new String[] { dialect.currentTimestamp() };
}

@Override
public EnumSet<EventType> getEventTypes() {
return EventTypeSets.INSERT_ONLY;
}
}

}
Loading