Skip to content

Preliminary support for Panache2 #10411

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 8 commits into
base: main
Choose a base branch
from

Conversation

FroMage
Copy link
Contributor

@FroMage FroMage commented Jun 25, 2025

Supports Panache2, which comes in a new package io.quarkus.hibernate.panache (since it supports both ORM and HR, they are not in the package name anymore). With new entity supertype, as well as repository supertypes and way of working.

Rough example entity:

@Entity
public class MyEntity extends WithId.AutoLong implements PanacheEntity.Stateless {
  public String name;

  public interface Repo {
    @Find
    MyEntity findSomeone(String name);
  }
}

This will generate the following:

@StaticMetamodel(MyEntity.class)
@Generated("org.hibernate.processor.HibernateProcessor")
public abstract class MyEntity_ extends WithId_ {

	/**
	 * Implements repository {@link org.acme.MyEntity.Repo}
	 **/
	@Dependent
	@Generated("org.hibernate.processor.HibernateProcessor")
	public static class Repo_ implements Repo {
	
	
		
		protected final @Nonnull Session session;
		
		@Inject
		public Repo_(@Nonnull Session session) {
			this.session = session;
		}
		
		public @Nonnull Session getSession() {
			return session;
		}
		
		/**
		 * Find {@link MyEntity} by {@link MyEntity#name name}.
		 *
		 * @see org.acme.MyEntity.Repo#findByName(String)
		 **/
		@Override
		public MyEntity findByName(String name) {
			var _builder = session.getCriteriaBuilder();
			var _query = _builder.createQuery(MyEntity.class);
			var _entity = _query.from(MyEntity.class);
			_query.where(
					name==null
						? _entity.get(MyEntity_.name).isNull()
						: _builder.equal(_entity.get(MyEntity_.name), name)
			);
			return session.createSelectionQuery(_query)
					.getSingleResult();
		}
	
	}
	
	/**
	 * @see #name
	 **/
	public static final String NAME = "name";

	
	/**
	 * Static metamodel type for {@link org.acme.MyEntity}
	 **/
	public static volatile EntityType<MyEntity> class_;
	
	/**
	 * Static metamodel for attribute {@link org.acme.MyEntity#name}
	 **/
	public static volatile SingularAttribute<MyEntity, String> name;
	
	public static Repo repo() {
		return CDI.current().select(Repo.class).get();
	}
	
	public static PanacheManagedBlockingRepository managedBlocking() {
		return CDI.current().select(PanacheManagedBlockingRepository.class).get();
	}
	
	@Dependent
	@Generated("org.hibernate.processor.HibernateProcessor")
	public static class PanacheManagedBlockingRepository implements io.quarkus.hibernate.panache.managed.blocking.PanacheManagedBlockingRepositoryBase<MyEntity, java.lang.Long> {
	}
	
	public static PanacheStatelessBlockingRepository statelessBlocking() {
		return CDI.current().select(PanacheStatelessBlockingRepository.class).get();
	}
	
	@Dependent
	@Generated("org.hibernate.processor.HibernateProcessor")
	public static class PanacheStatelessBlockingRepository implements io.quarkus.hibernate.panache.stateless.blocking.PanacheStatelessBlockingRepositoryBase<MyEntity, java.lang.Long> {
	}
	
	public static PanacheManagedReactiveRepository managedReactive() {
		return CDI.current().select(PanacheManagedReactiveRepository.class).get();
	}
	
	@Dependent
	@Generated("org.hibernate.processor.HibernateProcessor")
	public static class PanacheManagedReactiveRepository implements io.quarkus.hibernate.panache.managed.reactive.PanacheManagedReactiveRepositoryBase<MyEntity, java.lang.Long> {
	}
	
	public static PanacheStatelessReactiveRepository statelessReactive() {
		return CDI.current().select(PanacheStatelessReactiveRepository.class).get();
	}
	
	@Dependent
	@Generated("org.hibernate.processor.HibernateProcessor")
	public static class PanacheStatelessReactiveRepository implements io.quarkus.hibernate.panache.stateless.reactive.PanacheStatelessReactiveRepositoryBase<MyEntity, java.lang.Long> {
	}

}

To recap:

  • Detects Panache2 package and types
  • Scans nested interfaces in entities to find repositories
  • Generates accessors for nested repos
  • Generates implementations and accessors for managed/stateless/blocking/reactive repos (reactive only if HR is detected)
  • Switch to using Session instead of EntityManager for Panache1&2 since that's what Quarkus switched to a while ago

There are tests for all this, but they're in Quarkus, and can only be merged once this gets merged. This is because @yrodiere gets an allergic skin reaction with cyclic dependencies, and assures me that the ORM CI now invokes the Quarkus CI, so even though you won't see the tests when you run them locally, they will eventually be run as part of the ORM CI.

Let me know what you think, @gavinking and if you want me to change anything.


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license
and can be relicensed under the terms of the LGPL v2.1 license in the future at the maintainers' discretion.
For more information on licensing, please check here.


FroMage added 7 commits June 25, 2025 15:41
- Detect Panache2 in classpath
- Detect Panache2 types for entities and repositories
Since repositories can be nested in entities, we can use the outer type
Comes with 4 out of the box:
- managed/blocking (generated)
- managed/reactive (generated if reactive-common is in the CP)
- stateless/blocking (generated)
- stateless/reactive (generated if reactive-common is in the CP)
- whatever nested repositories from the entity
-- and if they implement one of the first four, we use this instead of
   the generated one
@hibernate-github-bot
Copy link

hibernate-github-bot bot commented Jun 25, 2025

Thanks for your pull request!

This pull request does not follow the contribution rules. Could you have a look?

❌ All commit messages should start with a JIRA issue key matching pattern HHH-\d+
    ↳ Offending commits: [7205879, c3e659d, 12697ff, dbcbc50, 623cbf8, 41f262b, 1a8fbb7, aaa58e5]

› This message was automatically generated.

@@ -373,7 +382,7 @@ private void processClasses(RoundEnvironment roundEnvironment) {
try {
final AnnotationMetaEntity metaEntity =
AnnotationMetaEntity.create( typeElement, context,
parentMetadata( typeElement, context::getMetaEntity ) );
parentMetadata( typeElement, context::getMetaEntity ), null );
Copy link
Member

Choose a reason for hiding this comment

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

How about overloading AnnotationMetaEntity.create() instead of passing null?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was waiting for Java to get defaulted params.

Copy link
Member

Choose a reason for hiding this comment

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

Good plan.

Comment on lines +705 to +718
switch(typedIdMember.getKind()) {
case ARRAY:
case DECLARED:
case BOOLEAN:
case BYTE:
case CHAR:
case SHORT:
case INT:
case LONG:
case FLOAT:
case DOUBLE:
return typedIdMember;
case EXECUTABLE:
return ((ExecutableType) typedIdMember).getReturnType();
Copy link
Member

Choose a reason for hiding this comment

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

switch expression?

Comment on lines +729 to +745
private @Nullable Element findIdMember() {
if ( primaryEntity == null ) {
message( element,
"No primary entity defined to find id member",
Diagnostic.Kind.ERROR );
return null;
}
for ( Element member : context.getAllMembers( primaryEntity ) ) {
if ( hasAnnotation( member, ID, EMBEDDED_ID ) ) {
return member;
}
}
message( element,
"Could not find any member annotated with @Id or @EmbeddedId",
Diagnostic.Kind.ERROR );
return null;
}
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't you also look for an @IdClass here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I should definitely.

@gavinking
Copy link
Member

Rough example entity:

@Entity
public class MyEntity extends WithId.AutoLong implements PanacheEntity.Stateless {
  public String name;

  public interface Repo {
    @Find
    MyEntity findSomeone(String name);
  }
}

Is this code example missing a @Repository annotation?

@@ -253,9 +253,16 @@ private boolean handleSettings(ProcessingEnvironment environment) {
PackageElement quarkusOrmPanachePackage =
context.getProcessingEnvironment().getElementUtils()
.getPackageElement( "io.quarkus.hibernate.orm.panache" );
PackageElement quarkusPanache2Package =
context.getProcessingEnvironment().getElementUtils()
.getPackageElement( "io.quarkus.hibernate.panache" );
Copy link
Member

Choose a reason for hiding this comment

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

We discussed at some point going with "Quarkus Data" instead of "Panache 2"... Are you sure you still want to go with this name and package? io.quarkus.data.hibernate could work too, in that case?

Maybe this is a discussion we should take offline.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Did we? Or are you just trying to trick me because you know my memory doesn't go that far? 🤔

@FroMage
Copy link
Contributor Author

FroMage commented Jun 26, 2025

Rough example entity:
Is this code example missing a @Repository annotation?

Well no, this is with the ORM @Find annotation, so I don't need a @Repository (JD)

@gavinking
Copy link
Member

Well no, this is with the ORM @Find annotation, so I don't need a @Repository (JD)

Ah OK, I see.

@gavinking
Copy link
Member

@FroMage the bot is nagging you about your } else if (s.

@Sanne
Copy link
Member

Sanne commented Jun 26, 2025

@FroMage the bot is also nagging you about not removing the dual-licensing notice in the PR description ;)

…sor/util/TypeUtils.java

Co-authored-by: Gavin King <gavin@hibernate.org>
TypeElement primaryEntityForTest = primaryEntity;
if ( idMember != null && primaryEntityForTest != null ) {
TypeMirror typedIdMember = this.context.getTypeUtils().asMemberOf((DeclaredType) primaryEntityForTest.asType(), idMember);
TypeMirror idType = null;

Check notice

Code scanning / CodeQL

Unread local variable Note

Variable 'TypeMirror idType' is never read.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants