experimental support for reactive Jakarta Data repositories

this time with uni-fied access to the M.SS

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-03-26 00:57:24 +01:00
parent d52edeb0e5
commit c5f9ada2fc
8 changed files with 195 additions and 28 deletions

View File

@ -0,0 +1,106 @@
package org.hibernate.processor.test.data.reactive;
import io.smallrye.mutiny.Uni;
import jakarta.data.Limit;
import jakarta.data.Order;
import jakarta.data.Sort;
import jakarta.data.page.Page;
import jakarta.data.page.PageRequest;
import jakarta.data.repository.By;
import jakarta.data.repository.Delete;
import jakarta.data.repository.Find;
import jakarta.data.repository.Insert;
import jakarta.data.repository.OrderBy;
import jakarta.data.repository.Query;
import jakarta.data.repository.Repository;
import jakarta.data.repository.Save;
import jakarta.data.repository.Update;
import org.hibernate.reactive.mutiny.Mutiny;
import java.time.LocalDate;
import java.util.List;
@Repository
public interface Library2 {
Uni<Mutiny.StatelessSession> session(); //required
@Find
Uni<Book> book(String isbn);
@Find
Uni<List<Book>> books(@By("isbn") List<String> isbns);
@Find
Uni<Book> book(String title, LocalDate publicationDate);
@Find
Uni<List<Book>> publications(Type type, Sort<Book> sort);
@Find
@OrderBy("title")
Uni<List<Book>> booksByPublisher(String publisher_name);
@Query("where title like :titlePattern")
@OrderBy("title")
Uni<List<Book>> booksByTitle(String titlePattern);
// not required by Jakarta Data
record BookWithAuthor(Book book, Author author) {}
@Query("select b, a from Book b join b.authors a order by b.isbn, a.ssn")
Uni<List<BookWithAuthor>> booksWithAuthors();
@Insert
Uni<Void> create(Book book);
@Insert
Uni<Void> create(Book[] book);
@Update
Uni<Void> update(Book book);
@Update
Uni<Void> update(Book[] books);
@Delete
Uni<Void> delete(Book book);
@Delete
Uni<Void> delete(Book[] book);
@Save
Uni<Void> upsert(Book book);
@Find
Uni<Author> author(String ssn);
@Insert
Uni<Void> create(Author author);
@Update
Uni<Void> update(Author author);
@Insert
Uni<Publisher[]> insertAll(Publisher[] publishers);
@Save
Uni<Publisher> save(Publisher publisher);
@Delete
Uni<Publisher> delete(Publisher publisher);
@Find
@OrderBy("isbn")
Uni<Page<Book>> allBooks(PageRequest<Book> pageRequest);
@Find
@OrderBy("name")
@OrderBy("address.city")
Uni<List<Author>> allAuthors(Order<Author> order, Limit limit);
@Find
Uni<List<Author>> authorsByCity(@By("address.city") String city);
@Find
Uni<List<Author>> authorsByCityAndPostcode(String address_city, String address_postcode);
}

View File

@ -18,13 +18,14 @@ import static org.hibernate.processor.test.util.TestUtil.getMetaModelSourceAsStr
*/
public class ReactiveTest extends CompilationTest {
@Test
@WithClasses({ Publisher.class, Author.class, Address.class, Book.class, Library.class })
@WithClasses({ Publisher.class, Author.class, Address.class, Book.class, Library.class, Library2.class })
public void test() {
System.out.println( getMetaModelSourceAsString( Author.class ) );
System.out.println( getMetaModelSourceAsString( Book.class ) );
System.out.println( getMetaModelSourceAsString( Author.class, true ) );
System.out.println( getMetaModelSourceAsString( Book.class, true ) );
System.out.println( getMetaModelSourceAsString( Library.class ) );
System.out.println( getMetaModelSourceAsString( Library2.class ) );
assertMetamodelClassGeneratedFor( Author.class, true );
assertMetamodelClassGeneratedFor( Book.class, true );
assertMetamodelClassGeneratedFor( Publisher.class, true );
@ -32,5 +33,6 @@ public class ReactiveTest extends CompilationTest {
assertMetamodelClassGeneratedFor( Book.class );
assertMetamodelClassGeneratedFor( Publisher.class );
assertMetamodelClassGeneratedFor( Library.class );
assertMetamodelClassGeneratedFor( Library2.class );
}
}

View File

@ -181,23 +181,26 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
boolean isUsingStatelessSession() {
return HIB_STATELESS_SESSION.equals(sessionType)
|| MUTINY_STATELESS_SESSION.equals(sessionType);
|| MUTINY_STATELESS_SESSION.equals(sessionType)
|| UNI_MUTINY_STATELESS_SESSION.equals(sessionType);
}
boolean isReactive() {
return MUTINY_SESSION.equals(sessionType)
|| MUTINY_STATELESS_SESSION.equals(sessionType)
|| UNI_MUTINY_SESSION.equals(sessionType);
|| UNI_MUTINY_SESSION.equals(sessionType)
|| UNI_MUTINY_STATELESS_SESSION.equals(sessionType);
}
boolean isReactiveSession() {
return UNI_MUTINY_SESSION.equals(sessionType);
return UNI_MUTINY_SESSION.equals(sessionType)
|| UNI_MUTINY_STATELESS_SESSION.equals(sessionType);
}
String localSessionName() {
return isReactiveSession() ? "resolvedSession" : sessionName;
return isReactiveSession() ? "_session" : sessionName;
}
void chainSession(StringBuilder declaration) {
// Reactive calls always have a return type
if ( isReactiveSession() ) {
@ -206,7 +209,7 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
.append(sessionName)
.append(".chain(")
.append(localSessionName())
.append(" -> {\n\t");
.append(" -> {\n");
}
}
@ -655,9 +658,14 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
break;
case JD_PAGE:
if ( isReactive() ) {
if ( returnTypeName == null ) {
throw new AssertionFailure("entity class cannot be null");
}
declaration
.append("\t\t\t.getResultList()\n")
.append("\t\t\t.map(_results -> ");
.append("\t\t\t.map(_results -> (Page<")
.append(annotationMetaEntity.importType(returnTypeName))
.append(">)");
}
else {
declaration
@ -741,8 +749,8 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
boolean isUnifiableReturnType(@Nullable String containerType) {
return containerType == null
|| LIST.equals(containerType)
|| JD_PAGE.equals(containerType)
|| JD_CURSORED_PAGE.equals(containerType);
|| LIST.equals(containerType)
|| JD_PAGE.equals(containerType)
|| JD_CURSORED_PAGE.equals(containerType);
}
}

View File

@ -1152,7 +1152,8 @@ public class AnnotationMetaEntity extends AnnotationMeta {
private void addLifecycleMethod(ExecutableElement method) {
final TypeMirror returnType = ununi(method.getReturnType());
if ( !HIB_STATELESS_SESSION.equals(sessionType)
&& !MUTINY_STATELESS_SESSION.equals(sessionType) ) {
&& !MUTINY_STATELESS_SESSION.equals(sessionType)
&& !UNI_MUTINY_STATELESS_SESSION.equals(sessionType) ) {
context.message( method,
"repository must be backed by a 'StatelessSession'",
Diagnostic.Kind.ERROR );
@ -1593,6 +1594,8 @@ public class AnnotationMetaEntity extends AnnotationMeta {
case HIB_STATELESS_SESSION:
case MUTINY_SESSION:
case MUTINY_STATELESS_SESSION:
// case UNI_MUTINY_SESSION:
// case UNI_MUTINY_STATELESS_SESSION:
return "session";
default:
return sessionGetter;
@ -2622,11 +2625,13 @@ public class AnnotationMetaEntity extends AnnotationMeta {
private boolean usingReactiveSession(String sessionType) {
return MUTINY_SESSION.equals(sessionType)
|| MUTINY_STATELESS_SESSION.equals(sessionType)
|| UNI_MUTINY_SESSION.equals(sessionType);
|| UNI_MUTINY_SESSION.equals(sessionType)
|| UNI_MUTINY_STATELESS_SESSION.equals(sessionType);
}
private boolean usingStatelessSession(String sessionType) {
return HIB_STATELESS_SESSION.equals(sessionType)
|| MUTINY_STATELESS_SESSION.equals(sessionType);
|| MUTINY_STATELESS_SESSION.equals(sessionType)
|| UNI_MUTINY_STATELESS_SESSION.equals(sessionType);
}
}

View File

@ -17,6 +17,7 @@ import static org.hibernate.processor.util.Constants.MUTINY_SESSION;
import static org.hibernate.processor.util.Constants.MUTINY_SESSION_FACTORY;
import static org.hibernate.processor.util.Constants.MUTINY_STATELESS_SESSION;
import static org.hibernate.processor.util.Constants.UNI_MUTINY_SESSION;
import static org.hibernate.processor.util.Constants.UNI_MUTINY_STATELESS_SESSION;
/**
* Used by the container to instantiate a Jakarta Data repository.
@ -51,8 +52,9 @@ public class DefaultConstructor implements MetaAttribute {
private boolean isReactive() {
return MUTINY_SESSION.equals(sessionTypeName)
|| MUTINY_STATELESS_SESSION.equals(sessionTypeName)
|| UNI_MUTINY_SESSION.equals(sessionTypeName);
|| MUTINY_STATELESS_SESSION.equals(sessionTypeName)
|| UNI_MUTINY_SESSION.equals(sessionTypeName)
|| UNI_MUTINY_STATELESS_SESSION.equals(sessionTypeName);
}
@Override
@ -96,19 +98,24 @@ public class DefaultConstructor implements MetaAttribute {
.append("Factory.unwrap(")
.append(annotationMetaEntity.importType(isReactive() ? MUTINY_SESSION_FACTORY : HIB_SESSION_FACTORY))
.append(".class).openStatelessSession()");
if ( isReactive() ) {
if ( MUTINY_SESSION.equals(sessionTypeName)
|| MUTINY_STATELESS_SESSION.equals(sessionTypeName) ) {
// this is crap
declaration
.append(".await().indefinitely()");
}
declaration
.append(";\n}\n\n");
declaration.append('@')
.append(annotationMetaEntity.importType("jakarta.annotation.PreDestroy"))
.append("\nprivate void closeSession() {")
.append("\n\t")
.append(sessionVariableName)
.append(".close();")
.append("\n}\n\n");
// TODO: is it a problem that we never close the session?
if ( !isReactive() ) {
declaration.append('@')
.append(annotationMetaEntity.importType("jakarta.annotation.PreDestroy"))
.append("\nprivate void closeSession() {")
.append("\n\t")
.append(sessionVariableName)
.append(".close();")
.append("\n}\n\n");
}
}
inject( declaration );
declaration

View File

@ -89,11 +89,23 @@ public class IdFinderMethod extends AbstractFinderMethod {
}
private void findWithNoFetchProfiles(StringBuilder declaration) {
if ( isReactiveSession() ) {
declaration
.append(".chain(")
.append(localSessionName())
.append(" -> ")
.append(localSessionName());
}
declaration
.append(isUsingStatelessSession() ? ".get(" : ".find(")
.append(annotationMetaEntity.importType(entity))
.append(".class, ")
.append(paramName)
.append(paramName);
if ( isReactiveSession() ) {
declaration
.append(')');
}
declaration
.append(");\n");
}

View File

@ -13,6 +13,7 @@ import static org.hibernate.processor.util.Constants.MUTINY_SESSION;
import static org.hibernate.processor.util.Constants.MUTINY_STATELESS_SESSION;
import static org.hibernate.processor.util.Constants.UNI;
import static org.hibernate.processor.util.Constants.UNI_MUTINY_SESSION;
import static org.hibernate.processor.util.Constants.UNI_MUTINY_STATELESS_SESSION;
public class LifecycleMethod implements MetaAttribute {
private final AnnotationMetaEntity annotationMetaEntity;
@ -110,7 +111,22 @@ public class LifecycleMethod implements MetaAttribute {
}
private void delegateCall(StringBuilder declaration) {
if ( isReactive() ) {
if ( isReactiveSession() ) {
declaration
.append("\t\treturn ")
.append(sessionName)
.append(".chain(")
.append(localSessionName())
.append(" -> ")
.append(localSessionName())
.append('.')
.append(operationName)
.append('(')
.append(parameterName)
.append(')')
.append(')');
}
else if ( isReactive() ) {
declaration
.append("\t\treturn ")
.append(sessionName)
@ -229,6 +245,16 @@ public class LifecycleMethod implements MetaAttribute {
private boolean isReactive() {
return MUTINY_SESSION.equals(sessionType)
|| MUTINY_STATELESS_SESSION.equals(sessionType)
|| UNI_MUTINY_SESSION.equals(sessionType);
|| UNI_MUTINY_SESSION.equals(sessionType)
|| UNI_MUTINY_STATELESS_SESSION.equals(sessionType);
}
boolean isReactiveSession() {
return UNI_MUTINY_SESSION.equals(sessionType)
|| UNI_MUTINY_STATELESS_SESSION.equals(sessionType);
}
String localSessionName() {
return isReactiveSession() ? '_' + sessionName : sessionName;
}
}

View File

@ -106,7 +106,8 @@ public final class Constants {
public static final String TUPLE = "jakarta.persistence.Tuple";
public static final String UNI = "io.smallrye.mutiny.Uni";
public static final String UNI_MUTINY_SESSION = UNI+"<org.hibernate.reactive.mutiny.Mutiny.Session>";
public static final String UNI_MUTINY_SESSION = UNI + "<" + MUTINY_SESSION + ">";
public static final String UNI_MUTINY_STATELESS_SESSION = UNI + "<" + MUTINY_STATELESS_SESSION + ">";
public static final String UNI_INTEGER = UNI+"<java.lang.Integer>";
public static final String UNI_VOID = UNI+"<java.lang.Void>";
public static final String UNI_BOOLEAN = UNI+"<java.lang.Boolean>";