Skip to content
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

Support new Hibernate 6.3 syntax for type-safe queries #36168

Open
FroMage opened this issue Sep 26, 2023 · 10 comments
Open

Support new Hibernate 6.3 syntax for type-safe queries #36168

FroMage opened this issue Sep 26, 2023 · 10 comments

Comments

@FroMage
Copy link
Member

FroMage commented Sep 26, 2023

Description

Hibernate 6.3 comes with experimental support for mapping HQL/SQL queries to methods, as well as finder methods, whose HQL can be inferred from the method signature.

Hibernate 6.3 Examples

interface BookRepository {
 @HQL("from Book where author = :author and language = :language")
 public List<Book> findBooks1(String author, String language);

 // the same, as inferred from the parameters and return type
 @Find
 public List<Book> findBooks2(String author, String language);
}

This will use Java compiler plugins (aka APT) to generate a BookRepository_ class with the implementation of the methods, as well as type-check the query and verify that all parameters exist and are of the right type at compile-time.

Here is an example of the generate code (when placed in the entity, so mixed with the entity metamodel):

@StaticMetamodel(Book.class)
@Generated("org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
public abstract class Book_ extends io.quarkus.hibernate.orm.panache.PanacheEntity_ {

	public static final String AUTHOR = "author";
	public static final String LANGUAGE = "language";
	static final String FIND_BOOKS1_String_String = "from Book where author = :author and language = :language";

	public static volatile SingularAttribute<Book, String> author;
	public static volatile SingularAttribute<Book, String> language;
	public static volatile EntityType<Book> class_;
	
	public static List<Book> findBooks2(@Nonnull EntityManager entityManager, String author, String language) {
		var builder = entityManager.getEntityManagerFactory().getCriteriaBuilder();
		var query = builder.createQuery(Book.class);
		var entity = query.from(Book.class);
		query.where(
				author==null
					? entity.get(Book_.author).isNull()
					: builder.equal(entity.get(Book_.author), author), 
				language==null
					? entity.get(Book_.language).isNull()
					: builder.equal(entity.get(Book_.language), language)
		);
		return entityManager.createQuery(query).getResultList();
	}
	
	public static List<Book> findBooks1(@Nonnull EntityManager entityManager, String author, String language) {
		return entityManager.createQuery(FIND_BOOKS1_String_String, Book.class)
				.setParameter("author", author)
				.setParameter("language", language)
				.getResultList();
	}
}

As you can see, this needs an EntityManager, which is not convenient, but also defines type-safe references to the attributes as String or SingularAttribute objects.

Current Hibernate ORM with Panache examples

The previous queries can currently be written as in Hibernate ORM with Panache as:

@Entity
public class Book extends PanacheEntity {
 // …

 public static List<Book> findBooks(String author, String language){
  return list("author = ?1 and language = ?2", author, language);
 }
}

This isn't necessarily more verbose, but it is not type-safe, and it's really hard to check the HQL at build-time (though we've had some success in the past).

Proposal

We would like to add support for the new type-safe queries to Panache entities and repositories, in addition to the current API, as follows:

public class BookRepository extends PanacheRepository<Book> {
 @HQL("from Book where author = :author and language = :language")
 public native List<Book> findBooks1(String author, String language);

 // the same, as inferred from the parameters and return type
 @Find
 public native List<Book> findBooks2(String author, String language);
}

@Entity
public class Book extends PanacheEntity {
 // …

 @HQL("from Book where author = :author and language = :language")
 public static native List<Book> findBooks1(String author, String language);

 // the same, as inferred from the parameters and return type
 @Find
 public static native List<Book> findBooks2(String author, String language);
}

As usual, we have to use the native trick to allow non-abstract classes to have generated method bodies. These methods will be static native for entities, and native for repositories.

These generated methods will be type-safe, and their implementation will forward to the generates Book_ and BookRepository_ classes.

Challenges

There are quite a number of challenges and questions to solve in order to support this, including:

  • Quarkus DEV mode does not run APT processors Annotation processors are not re-run in dev mode #1502
    • ✔ for Maven, supports annotationProcessorPaths and annotationProcessors, as well as generate the sources in the target/generated-sources/annotations folder (previously target/classes).
    • For Gradle
  • Make ORM extension automatically run the Hibernate metamodel generator JPA Static Metamodel Generator #29068
    • I can do it, using a variant of CodeGen but IDEs will not be able to understand that they have to run APT, which does not matter for IJ, but does for Eclipse. I need to ask our Tools people.
  • Adapt Hibernate metamodel generator for Panache entities, repositories
    • ✔ Done
  • We have duplicate Order/Page classes
  • There is some duplication between PanacheQuery, TypedQuery, SelectionQuery
  • Projection support is different between Hibernate and Panache
  • What do we do for MongoDB?
  • The current query generator supports also StatelessSession, for which we've no real support in Panache
  • Documentation needs to be adapted
  • @gavinking mentionned adding support for other types of methods, such as for insert/delete/persist, in line with the new Jakarta Data proposal

Implementation ideas

No response

@quarkus-bot
Copy link

quarkus-bot bot commented Sep 26, 2023

/cc @Sanne (hibernate-orm), @gsmet (hibernate-orm), @yrodiere (hibernate-orm)

@gavinking
Copy link

@gavinking mentionned adding support for other types of methods, such as for insert/delete/persist, in line with the new Jakarta Data proposal

The need for this is now much more acute, since Jakarta Data is almost here. What used to be called the "JPA Static Metamodel Generator" (#29068) is now so much more.

I have tested Hibernate Data Repositories with Quarkus, and it works perfectly (modulo a bug in how Quarkus handles @Transactional).

But, in IntelliJ, the processor does not get run on code changes when in dev mode, and so I have to explicitly request a build by hitting ⌘\ every time. That's not so awful, but users aren't going to know to do it, and it's something they're not used to needing in Quarkus.

@gavinking
Copy link

In fairness, I just tried a Maven project and it does work there, apparenlty perfectly, I guess because of the work @FroMage already did.

@gavinking
Copy link

gavinking commented Mar 24, 2024

In fairness, I just tried a Maven project and it does work there, apparenlty perfectly, I guess because of the work @FroMage already did.

Scratch that, actually in Maven it's way worse, because if I try to add a @Query method, it works at first and then I get into a crazy state where mvn just fails with:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.12.1:compile (default-compile) on project data-demo-quarkus-mvn: 
Fatal error compiling: java.lang.NoClassDefFoundError: org/jboss/jandex/IndexView: org.jboss.jandex.IndexView -> [Help 1]

and I have to delete the method to recover.

This doesn't happen with Gradle.

@gavinking
Copy link

gavinking commented Mar 24, 2024

Scratch that, actually in Maven it's way worse

This turned out to be a problem on my side. The HibernateProcessor was missing some dependencies that apparently aren't (explicitly) needed for Gradle, but are for Maven. After adding them, @Query works fine in Quarkus.

hibernate/hibernate-orm#8033

@FroMage
Copy link
Member Author

FroMage commented Mar 25, 2024

In fairness, I just tried a Maven project and it does work there, apparenlty perfectly, I guess because of the work @FroMage already did.

Right. And for Gradle it's not done yet: #38228

@yrodiere yrodiere removed the area/persistence OBSOLETE, DO NOT USE label Aug 14, 2024
@codespearhead
Copy link

I wonder how the fact that Hibernate 6.6.0.Final includes a complete implementation of the Jakarta Data 1.0 Release impacts this issue. Hibernate 6.6.0.Final and is available since Quarkus 3.14.0:

Hibernate 6.6 includes a complete implementation of the Jakarta Data 1.0 Release. As discussed here, our implementation:

  • is based on compile-time code generation via an annotation processor, enabling unprecedented compile-time type safety, and
  • is backed by Hibernate’s StatelessSession, which has been enhanced especially to meet the needs of Jakarta Data.

Hibernate 6.6 is certified as a compatible implementation.

@yrodiere
Copy link
Member

Jakarta Data is different from Hibernate ORM's own syntax. Jakarta Data focuses on stateless sessions, while Hibernate ORM's type-safe queries work with an EntityManager (and perhaps stateless sessions, I don't remember).

@FroMage is working on an integration in Panache, that would hopefull unify all this. But AFAIK it's not ready yet.

In the meantime you should already be able to use Jakarta Data in Quarkus, as it's a purely build-time (annotation processor) thing.

@yrodiere
Copy link
Member

yrodiere commented Oct 7, 2024

Hey @pipinet. Please open a GitHub Discussion instead of hijacking this issue: it's bad form.

@pipinet
Copy link

pipinet commented Oct 7, 2024

@yrodiere ok, i move reply to #29068, they are same issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants