Coder Social home page Coder Social logo

micronaut-projects / micronaut-data Goto Github PK

View Code? Open in Web Editor NEW
459.0 42.0 194.0 188 MB

Ahead of Time Data Repositories

License: Apache License 2.0

Java 68.32% Groovy 25.46% Dockerfile 0.01% Kotlin 6.21%
data jdbc sql hibernate jpa micronaut relational-databases java kotlin groovy

micronaut-data's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

micronaut-data's Issues

More flexible control over statements in JdbcOperations

While trying to work around issue #79, I decided to overload my repository's save method and just write the the SQL out directly. This mostly worked, but I bumped into what I believe is a bug / opportunity for improvement: easy support for getting the generated ID back.

Specifically, this is what my save method looks like:

    public <S extends Book> S save(@NonNull @Valid @NotNull S book) {
        final String sql = "insert into books (title, data) values (?, ?)";

        return jdbcOperations.prepareStatement(sql, statement -> {
            statement.setString(1, book.getTitle());

            try {
                PGobject json = new PGobject();
                json.setType("jsonb");
                json.setValue(objectMapper.writeValueAsString(book.getData()));
                statement.setObject(2, json);
                statement.execute();

                // TODO: this isn't actually working yet, because we can't pass Statement.RETURN_GENERATED_KEYS through up above (limitation of Predator wrapper)
                ResultSet generatedKeys = statement.getGeneratedKeys();
                if (generatedKeys.next()) {
                    book.setId(generatedKeys.getLong(1));
                }
                
                return book;
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        });
    }

Unfortunately, the call to statement.getGeneratedKeys() won't, at least with the standard Postgres JDBC driver, get me back what I want unless I passed Statement.RETURN_GENERATED_KEYS into the connection.prepareStatement() call, which I can't do because JdbcOperations doesn't expose that API:

return callback.call(transactionOperations.getConnection().prepareStatement(sql));

As such, it means I'm hamstrung and need to issue a second query, which is a bummer. So this is a request to consider expanding the JdbcOperations API in some way to give me a tiny bit more control to do stuff like this. Thanks!

Jdbc: Annotation processor doesn't detect DataType correctly

I'm using PostgresSQL native UUID type, somehow default Micronaut's type for UUID is String so I need to change the type:

    @Id
    @Column(name = "id")
    @TypeDef(type = DataType.OBJECT)
    protected UUID id = UUID.randomUUID();

With repository:

@JdbcRepository(dialect = Dialect.POSTGRES)
public interface PersonRepository extends CrudRepository<Person, UUID> {

    List<Person> findByIdIn(List<UUID> id);

}

It looks like the annotation processor is incorrectly creating metadata with mapped id as DataType.String instead of DataType.Object.

new Object[]{"typeDefs", new AnnotationValue[]{new AnnotationValue("io.micronaut.data.annotation.TypeDef", AnnotationUtil.internMapOf(new Object[]{"names", "1", "type", DataType.STRING}))}, "parameterBinding",
new AnnotationValue[]{new AnnotationValue("io.micronaut.context.annotation.Property", AnnotationUtil.internMapOf(new Object[]{"name", "1", "value", "id"}))}, "resultDataType", DataType.ENTITY, "idType", "java.util.UUID" ...

JPA: Update Entity leads to "detached entity passed to persist"

Hello,

if i use the Repository directly to update an Entity it's works. But if the Repository a Depency inside a Controller i always get the following Error:
Unexpected error occurred: org.hibernate.PersistentObjectException: detached entity passed to persist: example.domain.Pet

I extend the given Example (example-jpa) with the PetRepository. I create a update method inside the Controller:

@Put("/{petId}")`
    HttpStatus update(@QueryValue("petId") Long petId, Pet pet) {
        Optional<Pet> result = petRepository.findById(petId);
        if (result.isPresent()) {
            Pet current = result.get();
            current.setName(pet.getName());
            petRepository.persist(current);

            return HttpStatus.CREATED;
        }

        return HttpStatus.NOT_FOUND;
    }

I added the method as described in the documentation:
Pet persist(Pet entity);

And create a Test for the PUT endpoint:

@MicronautTest
public class PetUpdateControllerTest {

    @Inject
    @Client("/")
    RxHttpClient client;

    @Test
    void test() {
        Pet result = client.toBlocking().retrieve(HttpRequest.GET("/pets/Dino"), Pet.class);

        assertNotNull(result);
        assertEquals("Dino", result.getName());

        result.setName("Heino");
        String url = "/pets/" + result.getId();
        HttpStatus status = client.toBlocking().retrieve(HttpRequest.PUT(url, result), HttpStatus.class);
        assertEquals(204, status.getCode());
    }
}

Then i got this Error:

javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: example.domain.Pet
	at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:154)
	at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
	at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
	at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:810)
	at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:788)

I discover the same error in my own project.
A direct Test with the Repository works.

Regards
Jan

JDBC: Save entity inserts a new row when updating an entity

Task List

  • Steps to reproduce provided
  • Stacktrace (if present) provided
  • Example that reproduces the problem uploaded to Github
  • Full description of the issue provided (see below)

Steps to Reproduce

  1. Using JDBC and a CrudRepocitory
  2. Create and save entity
  3. Update and save entity again

Expected Behaviour

Expected behaviour is that the existing table row is updated.

According to the documentation:

To update an entity you can once again pass the entity to the the save method:

Actual Behaviour

A new row is inserted. The saved entity is updated with the new id.

Environment Information

  • Operating System: OSX
  • Micronaut Version: 1.2.0-RC2
  • Predator Version: 1.0.0.BUILD-SNAPSHOT from July 23 2019.
  • JDK Version: Oracle JDK 1.8.0_191

Example Application

I've added test case that reproduces the issue in the example-jdbc-project in fork https://github.com/Uniqen/micronaut-predator

See examples/example-jdbc/src/test/java/example/repositories/OwnerRepositoryTest.java

Unable to set PreparedStatement: Invalid column type

I'm getting:

Caused by: io.micronaut.data.exceptions.DataAccessException: Unable to set PreparedStatement value: Invalid column type
	at io.micronaut.data.jdbc.mapper.JdbcQueryStatement.newDataAccessException(JdbcQueryStatement.java:253)
	at io.micronaut.data.jdbc.mapper.JdbcQueryStatement.setValue(JdbcQueryStatement.java:108)
	at io.micronaut.data.jdbc.mapper.JdbcQueryStatement.setValue(JdbcQueryStatement.java:18)
	at io.micronaut.data.runtime.mapper.QueryStatement.setDynamic(QueryStatement.java:168)
	at io.micronaut.data.jdbc.mapper.JdbcQueryStatement.setDynamic(JdbcQueryStatement.java:76)
	at io.micronaut.data.jdbc.mapper.JdbcQueryStatement.setDynamic(JdbcQueryStatement.java:18)

When trying a simple many-to-one with the following models:

https://gist.github.com/b4b2f8b37ce8b9f2a16781af13b2db87
https://gist.github.com/131df4d3f30be0d110f7960ae8c9ff87

Meal meal = new Meal(100);
mealRepository.save(meal);

Food food = new Food("test", 100, 100, meal);
foodRepository.save(food);

Seems what is happening is that when Predator tries to bind the meal_id on Food it sees it as an "ENTITY" type and the statement.setObject() seems to be failing. Have I modeled this incorrectly?

Edit: I added @MappedProperty hoping that it would understand that the Meal id should be treated as a string, but no luck...

Groovy: No backing RepositoryOperations configured for repository

I am trying to follow the instructions as best I can but cannot get an example using Groovy to work. Here is what I do:

Create an application using Micronaut 1.2.0-RC2

mn create-app --lang groovy groovypredator
cd groovypredator

Edit build.gradle to add Predator support

Add these lines to dependencies:

    compileOnly 'io.micronaut.data:micronaut-predator-processor:1.0.0.BUILD-SNAPSHOT'
    compileOnly "io.micronaut.data:micronaut-predator-jdbc:1.0.0.BUILD-SNAPSHOT"     
    implementation 'io.micronaut.data:micronaut-predator-model:1.0.0.BUILD-SNAPSHOT' 
    implementation "jakarta.persistence:jakarta.persistence-api:2.2.2"  

(note that some of these weren't in the instructions, but I added them to get the compile to work).

Add this to repositories:

  maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }        

Add to application.yml:

datasources:
    default:
        url: jdbc:h2:file:./testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
        username: sa
        password: ""                                                        
        driverClassName: org.h2.Driver

Create Basic Controller and Domain Class

Then create a domain class and controller (for convenience, all in 1 file):

cat > src/main/groovy/groovypredator/TestController.groovy
package groovypredator

import javax.persistence.Entity         
import javax.persistence.GeneratedValue 
import javax.persistence.Id             
import javax.inject.Inject
import javax.sql.DataSource

import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.data.annotation.Repository
import io.micronaut.data.repository.CrudRepository
import io.micronaut.data.jdbc.annotation.JdbcRepository 
import io.micronaut.data.model.query.builder.sql.Dialect

@Entity                                 
class Thing {                      
    @Id                                 
    @GeneratedValue                     
    Long id                             
                                        
    Thing(Long id) {               
        this.id = id                    
    }                                   
                                        
    Thing() {                      
    }                                   
}                                       

@JdbcRepository(dialect=Dialect.H2)  
interface ThingRepository extends CrudRepository<Thing,Long> {
}

@Controller("/")
class TestController {

    @Inject ThingRepository things

    @Get("/test") 
    def getthing() {

        Thing thing = things.findById(884042)

        "\nFound a thing: hello\n"
    }
}

Error

I can compile and run the application, but I get this if I hit the URL:

curl http://localhost:8080/test/ ; echo
{"message":"Internal Server Error: No backing RepositoryOperations configured for repository. Check your configuration and try again"}

With stack trace:

12:05:41.637 [pool-1-thread-2] ERROR i.m.h.s.netty.RoutingInBoundHandler - Unexpected error occurred: No backing RepositoryOperations configured for repository. Check your configuration and try again
io.micronaut.context.exceptions.ConfigurationException: No backing RepositoryOperations configured for repository. Check your configuration and try again
	at io.micronaut.data.intercept.PredatorIntroductionAdvice.findInterceptor(PredatorIntroductionAdvice.java:102)
	at io.micronaut.data.intercept.PredatorIntroductionAdvice.intercept(PredatorIntroductionAdvice.java:74)
	at io.micronaut.aop.MethodInterceptor.intercept(MethodInterceptor.java:40)
	at io.micronaut.aop.chain.InterceptorChain.proceed(InterceptorChain.java:146)
	at schism.domain.BreakpointRepository$Intercepted.findById(Unknown Source)
	at io.micronaut.data.repository.CrudRepository$findById.call(Unknown Source)
...
Caused by: io.micronaut.context.exceptions.NoSuchBeanException: No bean of type [io.micronaut.data.operations.RepositoryOperations] exists. Make sure the bean is not disabled by bean requirements (enable trace logging for 'io.micronaut.context.condition' to check) and if the bean is enabled then ensure the class is declared a bean and annotation processing is enabled (for Java and Kotlin the 'micronaut-inject-java' dependency should be configured as an annotation processor).
	at io.micronaut.context.DefaultBeanContext.getBeanInternal(DefaultBeanContext.java:1842)
	at io.micronaut.context.DefaultBeanContext.getBean(DefaultBeanContext.java:582)
	at io.micronaut.data.intercept.PredatorIntroductionAdvice.findInterceptor(PredatorIntroductionAdvice.java:99)

I'm not sure if I'm missing something obvious or if there is actually something missing from the support or the documentation?

Documentation: Missing SNAPSHOT repository

The documentation Quick Start section describes how to add Gradle SNAPSHOT dependencies

kapt "io.micronaut.data:micronaut-predator-processor:1.0.0.BUILD-SNAPSHOT"
compile 'io.micronaut.data:micronaut-predator-hibernate-jpa:1.0.0.BUILD-SNAPSHOT'

but does not mention the SNAPSHOT repository

maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }

Add support for soft-delete

In many cases, people will want to soft-delete records instead of hard-deleting them, i.e. execute:

-- This:
UPDATE t SET t.deleted = true WHERE t.id = ?

-- Instead of this:
DELETE t WHERE t.id = ?

Page cannot be deserialized back to a POJO

Trying to deserialize a JSON object back into a Page object (useful in testing) fails with something like

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `io.micronaut.data.model.Page` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: (String)"{"content":[{"id":1,"name":"DevOps"},{"id":2,"name":"Micro-services"}],"pageable":{"number":0,"sort":{"sorted":false},"size":100,"offset":0,"sorted":false},"totalSize":2,"totalPages":1,"empty":false,"size":100,"offset":0,"pageNumber":0,"numberOfElements":2,"sort":{"number":0,"sort":{"sorted":false},"size":100,"offset":0,"sorted":false}}"; line: 1, column: 1]

	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
	at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1452)
	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1028)
	at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:265)

Error using Repository interface: java.lang.IncompatibleClassChangeError

I'm following the examples, but when calling repository.findAll() I get the below exception:

  • Java 8/11
  • micronaut 1.2.0
  • micronaut-data 1.0.0-SNAPSHOT
  • lombok
  • Postgres
  • Maven
  • Eclipse
java.lang.IncompatibleClassChangeError: Method com.example.repository.UserRepository.findAll()Ljava/util/List; must be InterfaceMethodref constant
	at com.example.repository.UserRepository$Intercepted.$$access0(Unknown Source)
	at com.example.repository.UserRepository$Intercepted$$proxy0.invokeInternal(Unknown Source)
	at io.micronaut.context.AbstractExecutableMethod.invoke(AbstractExecutableMethod.java:144)
	at io.micronaut.aop.chain.InterceptorChain.lambda$new$0(InterceptorChain.java:87)
	at io.micronaut.aop.chain.InterceptorChain.proceed(InterceptorChain.java:150)
	at io.micronaut.data.intercept.DataIntroductionAdvice.intercept(DataIntroductionAdvice.java:78)
	at io.micronaut.aop.MethodInterceptor.intercept(MethodInterceptor.java:40)
	at io.micronaut.aop.chain.InterceptorChain.proceed(InterceptorChain.java:150)
	at com.example.repository.UserRepository$Intercepted.findAll(Unknown Source)
	at com.example.controller.UserController.test(UserController.java:85)
	at com.example.controller.$UserControllerDefinition$$exec2.invokeInternal(Unknown Source)
	at io.micronaut.context.AbstractExecutableMethod.invoke(AbstractExecutableMethod.java:144)
	at io.micronaut.context.DefaultBeanContext$BeanExecutionHandle.invoke(DefaultBeanContext.java:2792)
	at io.micronaut.web.router.AbstractRouteMatch.execute(AbstractRouteMatch.java:235)
	at io.micronaut.web.router.RouteMatch.execute(RouteMatch.java:122)
	at io.micronaut.http.server.netty.RoutingInBoundHandler.lambda$buildResultEmitter$19(RoutingInBoundHandler.java:1408)
	at io.reactivex.internal.operators.flowable.FlowableCreate.subscribeActual(FlowableCreate.java:71)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.internal.operators.flowable.FlowableMap.subscribeActual(FlowableMap.java:37)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.internal.operators.flowable.FlowableSwitchIfEmpty.subscribeActual(FlowableSwitchIfEmpty.java:32)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.reactivex.internal.operators.flowable.FlowableFlatMap$MergeSubscriber.onNext(FlowableFlatMap.java:163)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.lambda$onNext$0(InstrumentedSubscriber.java:80)
	at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:52)
	at io.micronaut.http.context.ServerRequestContext.lambda$instrument$0(ServerRequestContext.java:68)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.onNext(InstrumentedSubscriber.java:84)
	at io.reactivex.internal.subscriptions.DeferredScalarSubscription.complete(DeferredScalarSubscription.java:132)
	at io.reactivex.internal.operators.maybe.MaybeToFlowable$MaybeToFlowableSubscriber.onSuccess(MaybeToFlowable.java:70)
	at io.micronaut.reactive.rxjava2.RxInstrumentedMaybeObserver.lambda$onSuccess$1(RxInstrumentedMaybeObserver.java:80)
	at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:52)
	at io.micronaut.http.context.ServerRequestContext.lambda$instrument$0(ServerRequestContext.java:68)
	at io.micronaut.reactive.rxjava2.RxInstrumentedMaybeObserver.onSuccess(RxInstrumentedMaybeObserver.java:84)
	at io.reactivex.internal.operators.maybe.MaybeDoOnEvent$DoOnEventMaybeObserver.onSuccess(MaybeDoOnEvent.java:86)
	at io.micronaut.reactive.rxjava2.RxInstrumentedMaybeObserver.lambda$onSuccess$1(RxInstrumentedMaybeObserver.java:80)
	at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:52)
	at io.micronaut.http.context.ServerRequestContext.lambda$instrument$0(ServerRequestContext.java:68)
	at io.micronaut.reactive.rxjava2.RxInstrumentedMaybeObserver.onSuccess(RxInstrumentedMaybeObserver.java:84)
	at io.reactivex.internal.operators.flowable.FlowableElementAtMaybe$ElementAtSubscriber.onNext(FlowableElementAtMaybe.java:80)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.lambda$onNext$0(InstrumentedSubscriber.java:80)
	at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:52)
	at io.micronaut.http.context.ServerRequestContext.lambda$instrument$0(ServerRequestContext.java:68)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.onNext(InstrumentedSubscriber.java:84)
	at io.reactivex.internal.operators.flowable.FlowableFlatMap$MergeSubscriber.tryEmit(FlowableFlatMap.java:282)
	at io.reactivex.internal.operators.flowable.FlowableFlatMap$InnerSubscriber.onNext(FlowableFlatMap.java:663)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.lambda$onNext$0(InstrumentedSubscriber.java:80)
	at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:52)
	at io.micronaut.http.context.ServerRequestContext.lambda$instrument$0(ServerRequestContext.java:68)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.onNext(InstrumentedSubscriber.java:84)
	at io.reactivex.internal.operators.flowable.FlowableSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FlowableSwitchIfEmpty.java:59)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.lambda$onNext$0(InstrumentedSubscriber.java:80)
	at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:52)
	at io.micronaut.http.context.ServerRequestContext.lambda$instrument$0(ServerRequestContext.java:68)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.onNext(InstrumentedSubscriber.java:84)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.lambda$onNext$0(InstrumentedSubscriber.java:80)
	at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:52)
	at io.micronaut.http.context.ServerRequestContext.lambda$instrument$0(ServerRequestContext.java:68)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.onNext(InstrumentedSubscriber.java:84)
	at io.reactivex.internal.operators.flowable.FlowableMap$MapSubscriber.onNext(FlowableMap.java:68)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.lambda$onNext$0(InstrumentedSubscriber.java:80)
	at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:52)
	at io.micronaut.http.context.ServerRequestContext.lambda$instrument$0(ServerRequestContext.java:68)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.onNext(InstrumentedSubscriber.java:84)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.lambda$onNext$0(InstrumentedSubscriber.java:80)
	at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:52)
	at io.micronaut.http.context.ServerRequestContext.lambda$instrument$0(ServerRequestContext.java:68)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.onNext(InstrumentedSubscriber.java:84)
	at io.reactivex.internal.subscriptions.ScalarSubscription.request(ScalarSubscription.java:55)
	at io.reactivex.internal.subscribers.BasicFuseableSubscriber.request(BasicFuseableSubscriber.java:153)
	at io.reactivex.internal.subscriptions.SubscriptionArbiter.setSubscription(SubscriptionArbiter.java:99)
	at io.reactivex.internal.operators.flowable.FlowableSwitchIfEmpty$SwitchIfEmptySubscriber.onSubscribe(FlowableSwitchIfEmpty.java:51)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.onSubscribe(InstrumentedSubscriber.java:75)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.onSubscribe(InstrumentedSubscriber.java:75)
	at io.reactivex.internal.subscribers.BasicFuseableSubscriber.onSubscribe(BasicFuseableSubscriber.java:67)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.onSubscribe(InstrumentedSubscriber.java:75)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.onSubscribe(InstrumentedSubscriber.java:75)
	at io.reactivex.internal.operators.flowable.FlowableJust.subscribeActual(FlowableJust.java:34)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedCallableFlowable.subscribeActual(RxInstrumentedCallableFlowable.java:65)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedCallableFlowable.subscribeActual(RxInstrumentedCallableFlowable.java:65)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.internal.operators.flowable.FlowableMap.subscribeActual(FlowableMap.java:37)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.reactivex.internal.operators.flowable.FlowableScalarXMap.tryScalarXMapSubscribe(FlowableScalarXMap.java:93)
	at io.reactivex.internal.operators.flowable.FlowableSwitchMap.subscribeActual(FlowableSwitchMap.java:46)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.internal.operators.flowable.FlowableSwitchIfEmpty.subscribeActual(FlowableSwitchIfEmpty.java:32)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.reactivex.internal.operators.flowable.FlowableFlatMap$MergeSubscriber.onNext(FlowableFlatMap.java:163)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.lambda$onNext$0(InstrumentedSubscriber.java:80)
	at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:52)
	at io.micronaut.http.context.ServerRequestContext.lambda$instrument$0(ServerRequestContext.java:68)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.onNext(InstrumentedSubscriber.java:84)
	at io.reactivex.internal.operators.flowable.FlowableFromIterable$IteratorSubscription.slowPath(FlowableFromIterable.java:236)
	at io.reactivex.internal.operators.flowable.FlowableFromIterable$BaseRangeSubscription.request(FlowableFromIterable.java:124)
	at io.reactivex.internal.operators.flowable.FlowableFlatMap$MergeSubscriber.onSubscribe(FlowableFlatMap.java:117)
	at io.micronaut.reactive.rxjava2.InstrumentedSubscriber.onSubscribe(InstrumentedSubscriber.java:75)
	at io.reactivex.internal.operators.flowable.FlowableFromIterable.subscribe(FlowableFromIterable.java:69)
	at io.reactivex.internal.operators.flowable.FlowableFromIterable.subscribeActual(FlowableFromIterable.java:47)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.internal.operators.flowable.FlowableFlatMap.subscribeActual(FlowableFlatMap.java:53)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.internal.operators.flowable.FlowableElementAtMaybe.subscribeActual(FlowableElementAtMaybe.java:36)
	at io.reactivex.Maybe.subscribe(Maybe.java:4290)
	at io.micronaut.reactive.rxjava2.RxInstrumentedMaybe.subscribeActual(RxInstrumentedMaybe.java:64)
	at io.reactivex.Maybe.subscribe(Maybe.java:4290)
	at io.reactivex.internal.operators.maybe.MaybeDoOnEvent.subscribeActual(MaybeDoOnEvent.java:39)
	at io.reactivex.Maybe.subscribe(Maybe.java:4290)
	at io.micronaut.reactive.rxjava2.RxInstrumentedMaybe.subscribeActual(RxInstrumentedMaybe.java:64)
	at io.reactivex.Maybe.subscribe(Maybe.java:4290)
	at io.reactivex.internal.operators.maybe.MaybeToFlowable.subscribeActual(MaybeToFlowable.java:45)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.internal.operators.flowable.FlowableFlatMap.subscribeActual(FlowableFlatMap.java:53)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.internal.operators.flowable.FlowableSwitchIfEmpty.subscribeActual(FlowableSwitchIfEmpty.java:32)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14868)
	at io.micronaut.http.context.ServerRequestTracingPublisher.lambda$subscribe$0(ServerRequestTracingPublisher.java:52)
	at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:52)
	at io.micronaut.http.context.ServerRequestTracingPublisher.subscribe(ServerRequestTracingPublisher.java:52)
	at io.reactivex.internal.operators.flowable.FlowableFromPublisher.subscribeActual(FlowableFromPublisher.java:29)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.reactivex.internal.operators.flowable.FlowableSubscribeOn$SubscribeOnSubscriber.run(FlowableSubscribeOn.java:82)
	at io.reactivex.internal.schedulers.ExecutorScheduler$ExecutorWorker$BooleanRunnable.run(ExecutorScheduler.java:288)
	at io.reactivex.internal.schedulers.ExecutorScheduler$ExecutorWorker.run(ExecutorScheduler.java:253)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)

Relation "pets" does not exist (Postgres)

Hi,
I think I found a bug.

Here you can find a minimal setup to replicate the issue:
https://github.com/jachinte/micronaut-predator-issue
Note: you need Docker to run the tests

I'm using liquibase to create a postgres database and populate the initial data. And I'm also using predator jdbc.

Liquibase is working well. Here is part of the log for gradle test --info:

example.repositories.PetRepositoryTest STANDARD_OUT
    20:05:48.434 [Test worker] INFO  i.m.context.env.DefaultEnvironment - Established active environments: [test]
    20:05:52.391 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - SELECT COUNT(*) FROM public.databasechangeloglock
    20:05:52.413 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - CREATE TABLE public.databasechangeloglock (ID INTEGER NOT NULL, LOCKED BOOLEAN NOT NULL, LOCKGRANTED TIMESTAMP WITHOUT TIME ZONE, LOCKEDBY VARCHAR(255), CONSTRAINT DATABASECHANGELOGLOCK_PKEY PRIMARY KEY (ID))
    20:05:52.435 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - SELECT COUNT(*) FROM public.databasechangeloglock
    20:05:52.442 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - DELETE FROM public.databasechangeloglock
    20:05:52.444 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - INSERT INTO public.databasechangeloglock (ID, LOCKED) VALUES (1, FALSE)
    20:05:52.452 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - SELECT LOCKED FROM public.databasechangeloglock WHERE ID=1
    20:05:52.466 [Test worker] INFO  l.lockservice.StandardLockService - Successfully acquired change log lock
    20:05:53.989 [Test worker] INFO  l.c.StandardChangeLogHistoryService - Creating database history table with name: public.databasechangelogtests completed
    20:05:53.992 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - CREATE TABLE public.databasechangelog (ID VARCHAR(255) NOT NULL, AUTHOR VARCHAR(255) NOT NULL, FILENAME VARCHAR(255) NOT NULL, DATEEXECUTED TIMESTAMP WITHOUT TIME ZONE NOT NULL, ORDEREXECUTED INTEGER NOT NULL, EXECTYPE VARCHAR(10) NOT NULL, MD5SUM VARCHAR(35), DESCRIPTION VARCHAR(255), COMMENTS VARCHAR(255), TAG VARCHAR(255), LIQUIBASE VARCHAR(20), CONTEXTS VARCHAR(255), LABELS VARCHAR(255), DEPLOYMENT_ID VARCHAR(10))
    20:05:54.008 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - SELECT COUNT(*) FROM public.databasechangelog
    20:05:54.011 [Test worker] INFO  l.c.StandardChangeLogHistoryService - Reading from public.databasechangelog
    20:05:54.012 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - SELECT * FROM public.databasechangelog ORDER BY DATEEXECUTED ASC, ORDEREXECUTED ASC
    20:05:54.017 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - SELECT COUNT(*) FROM public.databasechangeloglock
    20:05:54.077 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - CREATE SCHEMA myschema
    20:05:54.081 [Test worker] INFO  liquibase.changelog.ChangeSet - Custom SQL executed
    20:05:54.085 [Test worker] INFO  liquibase.changelog.ChangeSet - ChangeSet classpath:db/changelog/01-create-schema.xml::01::miguel ran successfully in 9ms
    20:05:54.088 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - SELECT MAX(ORDEREXECUTED) FROM public.databasechangelog
    20:05:54.096 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - INSERT INTO public.databasechangelog (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM, DESCRIPTION, COMMENTS, EXECTYPE, CONTEXTS, LABELS, LIQUIBASE, DEPLOYMENT_ID) VALUES ('01', 'miguel', 'classpath:db/changelog/01-create-schema.xml', NOW(), 1, '8:d75ddf2fc8a50d9d50a7eb903110a02c', 'sql', '', 'EXECUTED', NULL, NULL, '3.6.3', '3678354023')
    20:05:54.105 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - CREATE TABLE myschema.owners (id BIGSERIAL NOT NULL, name VARCHAR(25), age INTEGER, CONSTRAINT OWNERS_PKEY PRIMARY KEY (id), UNIQUE (id))
    20:05:54.115 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - COMMENT ON TABLE myschema.owners IS 'A table for owners'
    20:05:54.118 [Test worker] INFO  liquibase.changelog.ChangeSet - Table owners created
    20:05:54.120 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - CREATE TABLE myschema.pets (id UUID NOT NULL, name VARCHAR(25), owner_id BIGINT NOT NULL, type VARCHAR(3), CONSTRAINT PETS_PKEY PRIMARY KEY (id), CONSTRAINT fk_owner_id FOREIGN KEY (owner_id) REFERENCES myschema.owners(id), UNIQUE (id))
    20:05:54.130 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - COMMENT ON TABLE myschema.pets IS 'A table for pets'
    20:05:54.132 [Test worker] INFO  liquibase.changelog.ChangeSet - Table pets created
    20:05:54.138 [Test worker] INFO  liquibase.changelog.ChangeSet - ChangeSet classpath:db/changelog/01-create-schema.xml::02::miguel ran successfully in 34ms
    20:05:54.140 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - INSERT INTO public.databasechangelog (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM, DESCRIPTION, COMMENTS, EXECTYPE, CONTEXTS, LABELS, LIQUIBASE, DEPLOYMENT_ID) VALUES ('02', 'miguel', 'classpath:db/changelog/01-create-schema.xml', NOW(), 2, '8:8609a61f2b219fd8e5fb0bb2c4168e4a', 'createTable tableName=owners; createTable tableName=pets', '', 'EXECUTED', NULL, NULL, '3.6.3', '3678354023')
    20:05:54.148 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - INSERT INTO myschema.owners (id, name, age) VALUES ('1', 'Fred', '45')
    20:05:54.151 [Test worker] INFO  liquibase.changelog.ChangeSet - New row inserted into owners
    20:05:54.152 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - INSERT INTO myschema.owners (id, name, age) VALUES ('2', 'Barney', '40')
    20:05:54.157 [Test worker] INFO  liquibase.changelog.ChangeSet - New row inserted into owners
    20:05:54.161 [Test worker] INFO  liquibase.changelog.ChangeSet - ChangeSet classpath:db/changelog/02-insert-initial-data.xml::01::miguel ran successfully in 14ms
    20:05:54.163 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - INSERT INTO public.databasechangelog (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM, DESCRIPTION, COMMENTS, EXECTYPE, CONTEXTS, LABELS, LIQUIBASE, DEPLOYMENT_ID) VALUES ('01', 'miguel', 'classpath:db/changelog/02-insert-initial-data.xml', NOW(), 3, '8:f2d5496329d97e4ca52877cedfdd3248', 'insert tableName=owners; insert tableName=owners', '', 'EXECUTED', NULL, NULL, '3.6.3', '3678354023')
    20:05:54.170 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - INSERT INTO myschema.pets (id, name, type, owner_id) VALUES ('54529912-5932-4337-94e9-b395187ba597', 'Dino', 'DOG', '1')
    20:05:54.178 [Test worker] INFO  liquibase.changelog.ChangeSet - New row inserted into pets
    20:05:54.181 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - INSERT INTO myschema.pets (id, name, type, owner_id) VALUES ('23bc3100-9976-418f-87f8-6cf5a693abfc', 'Baby Puss', 'CAT', '1')
    20:05:54.184 [Test worker] INFO  liquibase.changelog.ChangeSet - New row inserted into pets
    20:05:54.185 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - INSERT INTO myschema.pets (id, name, type, owner_id) VALUES ('d1e6008e-b0fe-4380-8be5-0a99162e75d4', 'Hoppy', 'DOG', '2')
    20:05:54.188 [Test worker] INFO  liquibase.changelog.ChangeSet - New row inserted into pets
    20:05:54.194 [Test worker] INFO  liquibase.changelog.ChangeSet - ChangeSet classpath:db/changelog/02-insert-initial-data.xml::02::miguel ran successfully in 24mspositories.PetRepositoryTest
    20:05:54.196 [Test worker] INFO  liquibase.executor.jvm.JdbcExecutor - INSERT INTO public.databasechangelog (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM, DESCRIPTION, COMMENTS, EXECTYPE, CONTEXTS, LABELS, LIQUIBASE, DEPLOYMENT_ID) VALUES ('02', 'miguel', 'classpath:db/changelog/02-insert-initial-data.xml', NOW(), 4, '8:a38b25b74e768484a17fae0f0fecd93d', 'insert tableName=pets; insert tableName=pets; insert tableName=pets', '', 'EXECUTED', NULL, NULL, '3.6.3', '3678354023')
    20:05:54.207 [Test worker] INFO  l.lockservice.StandardLockService - Successfully released change log lock

However, the repositories are not working. There are SQL exceptions indicating that relations "owners" and "pets" do not exist. I believe this is because the SQL queries do not include the schema name.

Here is one of the exceptions:

example.repositories.PetRepositoryTest > testRetrievePetAndOwner() STANDARD_OUT
    20:05:55.429 [Test worker] DEBUG io.micronaut.data.query - Executing Query: SELECT pets_.id,pets_.owner_id,pets_.name,pets_.type,pets_owner_.id AS _owner_id,pets_owner_.age AS _owner_age,pets_owner_.name AS _owner_name FROM pets AS pets_ INNER JOIN owners pets_owner_ ON pets_.owner_id=pets_owner_.id WHERE (pets_.name = ?)

example.repositories.PetRepositoryTest > testRetrievePetAndOwner() FAILED
    io.micronaut.data.exceptions.DataAccessException: Error executing SQL Query: ERROR: relation "pets" does not exist
      Position: 149
        at io.micronaut.data.jdbc.operations.DefaultJdbcRepositoryOperations.lambda$findOne$2(DefaultJdbcRepositoryOperations.java:179)
        at io.micronaut.data.jdbc.runtime.spring.SpringJdbcTransactionOperations.lambda$executeRead$1(SpringJdbcTransactionOperations.java:63)
        at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140)
        at io.micronaut.data.jdbc.runtime.spring.SpringJdbcTransactionOperations.executeRead(SpringJdbcTransactionOperations.java:60)
        at io.micronaut.data.jdbc.operations.DefaultJdbcRepositoryOperations.findOne(DefaultJdbcRepositoryOperations.java:139)
        at io.micronaut.data.runtime.intercept.DefaultFindOptionalInterceptor.intercept(DefaultFindOptionalInterceptor.java:46)
        at io.micronaut.data.runtime.intercept.DefaultFindOptionalInterceptor.intercept(DefaultFindOptionalInterceptor.java:33)
        at io.micronaut.data.intercept.PredatorIntroductionAdvice.intercept(PredatorIntroductionAdvice.java:75)
        at io.micronaut.aop.MethodInterceptor.intercept(MethodInterceptor.java:40)
        at io.micronaut.aop.chain.InterceptorChain.proceed(InterceptorChain.java:146)
        at example.repositories.PetRepository$Intercepted.findByName(Unknown Source)
        at example.repositories.PetRepositoryTest.testRetrievePetAndOwner(PetRepositoryTest.java:36)

        Caused by:
        org.postgresql.util.PSQLException: ERROR: relation "pets" does not exist
          Position: 149
            at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2440)
            at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2183)
            at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:308)
            at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:441)
            at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:365)
            at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:143)
            at org.postgresql.jdbc.PgPreparedStatement.executeQuery(PgPreparedStatement.java:106)
            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
            at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
            at java.lang.reflect.Method.invoke(Method.java:498)
            at org.apache.tomcat.jdbc.pool.StatementFacade$StatementProxy.invoke(StatementFacade.java:114)
            at com.sun.proxy.$Proxy52.executeQuery(Unknown Source)
            at io.micronaut.data.jdbc.operations.DefaultJdbcRepositoryOperations.lambda$findOne$2(DefaultJdbcRepositoryOperations.java:143)
            ... 11 more

I'm using annotation "Table" to indicate the table name, schema and catalog:

import javax.persistence.Entity;
import javax.persistence.Table;

@Entity
@Table(
    name = "pets",
    schema = "myschema",
    catalog = "mycatalog"
)
public class Pet {
    ...
}

Support for RxJava + @Transactional

Currently @Transactional annotation seems to be supported only for synchronous calls to Hibernate ops. Using reactive programming seems to escape transaction bounds. The only remaining option when using RxJava is then to manage transactions manually.

A request to add support for using RxJava with @Transactional annotation to allow for something like this:

import io.micronaut.spring.tx.annotation.Transactional;
import io.reactivex.Flowable;
import io.reactivex.schedulers.Schedulers;
import java.util.List;
import javax.inject.Inject;

class Processor {
  @Inject
  Repository repository;

  @Transactional
  public Flowable<Message> process(Message message) {
    return Flowable.just(message)
        .observeOn(Schedulers.io())
        .doOnNext(message -> {
          List<MessageTrace> traces = message.getTraces();
          // all of these should be rolled back on downstream exception
          traces.forEach(repository::saveMessageTrace);
        })
        .observeOn(Schedulers.computation())
        .map(this::toModel)
        .observeOn(Schedulers.io())
        .flatMap(model -> {
          Model stored = repository.findById(model.getId());
          if (model.getVersion() < stored.getVersion()) {
            return Flowable.just(stored);
          }

          return Flowable.just(model);
        })
        .doOnNext(model -> {
          // this should roll back all upstream db modifications
          throw new RuntimeException("exception");
        });
  }

  private Model toModel(Message message) {
    // ... computation-intensive
    return model;
  }
}

Conditional queries

@Entity
data class Product(
    @Id
    @GeneratedValue
    var id: Long?,
    var name: String,
    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    var manufacturer: Manufacturer
)

@Entity
data class Manufacturer(
    @Id
    @GeneratedValue
    var id: Long?,
    var name: String
)

User may want to search the products by manufacturer name, in this case we need to join manufacturer and to add restriction to criteria, but in case it user do not search by manufacturer name, than we do not need to join and to add restriction.
So, how to handle conditional queries?

Question: how to use @Join for multi level?

Let’s say I have Country / State / City entities, how do I use @Join to fetch the second level?

I tried something like:

@Join(“state.city”) 
Optional<Country> findByName(String name)

and it didn’t work. Is it possible?

Thanks in advance.

Custom column type is not working correctly

I have defined a custom field like this according to JPA documentation

    @Column(columnDefinition = "TIMESTAMP WITH TIME ZONE")
    @DateCreated
    private LocalDateTime dateCreated;

However SqlQueryBuilder.addTypeToColumn omits the column name. Is this method not supposed to return column + " " + definition?

Micronaut - Embedded kafka - Consume from same topic

I've created a sample project to prove I can produce and consume the messages of the same topic with micronaut, you can find this in: https://github.com/MicroHero-blip/micronaut-ref-data
Everything is implemented in the tests but particularly if you run the test ReferenceDataKafkaClientSpec, in a mac it will pass immediately but if you run all tests it always fails.In linux it will always fail in my laptop but had a colleague that gets results all over the place. In Windows it always passes
The thing that makes the test fail is having a test that has @MicronautTest or embedded server before the ReferenceDataKafkaClientSpec test. For example if I rename my tests so ReferenceDataKafkaClientSpec is first everything will work.
If I replace the embedded kafka with a real one everything is fine in all 4 machines we used to test this.

Feature request: @TransactionalEventListener

Both Spring and CDI provide a mechanism that triggers event listeners based on transaction status. Since Micronaut kinda follows Spring's approach for event listeners, I'd suggest that there should be an annotation TransactionalEventListener that has a similar API to org.springframework.transaction.event.TransactionalEventListener.

Example usages:

  • Publishing Kafka events only after transaction succeeds
  • Compensation for failed transactions (e.g. delete created objects in other micro services)

Thanks & best regards

Jdbc ColumnNameResultSetReader error

With the following class using jdbc:

 @Entity
 data class Student(
     @GeneratedValue
     @Id
     var id: Long? = null,
     var guardianId: Long? = null,
     @get:NotBlank(message = "First Name is mandatory")
     @get:Size(min = 3, max = 25)
     var firstName: String = "",
     var initials: String? = null,
     @get:NotEmpty(message = "Last Name is Mandatory")
     @get:Size(min = 1, max = 25)
     var lastName: String = "",
     var birthDate: LocalDate? = null )

am getting the following error:

java.lang.NullPointerException: null
	at io.micronaut.data.jdbc.mapper.ColumnNameResultSetReader.lambda$convertRequired$0(ColumnNameResultSetReader.java:37)

However the same class is working with hibernate.

Can't run examples in IDEA 2019.2

I try to run kotlin-jdbc examples in IDEA (with enabled annotation processing). Unfourtanetely i can't use controllers after a such run. I should build jar and run it. Only after that i can use controllers.

Is it IDEA's issue or example's?

Oracle Doesn't Use "AS" for Aliases

When trying to run a simple findById with Predator, I receive the following exception:

22:21:38.621 [main] DEBUG io.micronaut.data.query - Executing Query: SELECT meal_.id,meal_,meal_.created_on,meal_.updated_on FROM meal AS meal_ WHERE (meal_.id = ?) FETCH NEXT 1 ROWS ONLY 
22:21:38.622 [main] TRACE io.micronaut.data.query - Binding parameter at position 1 to value 49ab813b-b588-4e35-bfdc-1c191b2e03e3
22:21:38.772 [main] ERROR i.m.h.server.netty.NettyHttpServer - Error starting Micronaut server: Error executing SQL Query: ORA-00933: SQL command not properly ended

The issue here is that Oracle doesn't support using "AS" for the table alias (reference). I'm figure the fix is to override AS_CLAUSE if the Dialect is ORACLE, but I'm not sure where you want to do that.

Support for Spring Data JPA Specifications

Hi there!

In Spring Data JPA they have a JpaSpecificationExecutor, which allows to query a repository using a specification. This specification can be dynamically build, so I can query something like price > 10 and maybe later query something like price <= 10 depending on the user input.

Would be possible for Micronaut Data JPA to have a feature like this? Maybe it would extend the Hibernate Criteria API, which I think is the underlying API JpaSpecificationExecutor uses.

Nested 'id' property resolution in Repository methods

Nested 'id' property resolution in Repository query methods seem to not work as expected

@Entity
@Table(name = "player")
public class Player {

    private Integer id;
    private String name;

    @ManyToOne
    private Team team;
}

@Entity
@Table(name = "team")
public class Team {

    private Integer id;
    private String name;
}

@Repository
public interface PlayerRepository extends JpaRepository<Player, Integer> {

    Collection<Player> findByTeamName(String name);

    Collection<Player> findByTeamId(Integer id);
}

Error:

Unable to implement Repository method: PlayerRepository.findByTeamId(Integer id). Cannot query entity [Player] on non-existent property: teamId

Repository always needs an ID column

I'm getting the following error while running a simple query with Predator JDBC and Postgress

error: Unable to implement Repository method: TaskRepository.findById(Object arg0). Cannot query entity [Task] on non-existent property: id error: Unable to implement Repository method: TaskRepository.existsById(Object arg0). Cannot query entity [Task] on non-existent property: id error: Unable to implement Repository method: TaskRepository.deleteById(Object arg0). Cannot query entity [Task] on non-existent property: id error: Unable to implement Repository method: TaskRepository.delete(Object arg0). Delete all not supported for entities with no ID error: Unable to implement Repository method: TaskRepository.delete(Object arg0). No possible implementations found.

Here is my Entity Class:
`
@entity
@DaTa
@NoArgsConstructor
//@MappedEntity(namingStrategy = NamingStrategies.Raw.class)
public class Task implements Serializable {

private static long serialVersionUID = 102843515;

@Id
@Column(name = "task_id")
@JsonProperty("task_id")
private Long taskId;

@Column(name = "assigned_workcenter")
@JsonProperty("assigned_workcenter")
@NotNull(message = "workcenter cannot be null")
private Integer assignedWorkcenter;
    ....

}`

Repositiry class:

` @JdbcRepository(dialect = Dialect.POSTGRES)
public abstract class TaskRepository implements CrudRepository<Task, Long> {

private final JdbcOperations jdbcOperations;

public TaskRepository(JdbcOperations jdbcOperations) {
    this.jdbcOperations = jdbcOperations;
}

@Transactional
public List<Task> findByTaskId(long taskId) {
    String sql = "SELECT * FROM task AS task WHERE task.task_id = ?";
    return jdbcOperations.prepareStatement(sql, statement -> {
        statement.setLong(1, taskId);
        ResultSet resultSet = statement.executeQuery();
        return jdbcOperations.entityStream(resultSet, Task.class).collect(Collectors.toList());
    });
}

} `

but if the table has the column name id then it works fine.

TypeConverter not used for inserting/updating type Object

TypeConverters are working wonderfully for reading/querying and writing/inserting/updating data except for one case: when you're trying to insert/update and expect a value to be converted before being bound to the statement.

You can see in this code where the problem is:

All other data types have a nice callout to convertRequired except for the Object. This means my TypeConvert doesn't get utilized, resulting in a JDBC error. The specific use case is I'm trying to convert into JSONB type in Postgres. Interestingly, it works on queries because the code doesn't even try to understand the underlying type, it simply passes all ResultSet values off to the type converters:

I'll see if I can get you a patch soon!

Entity changes not picked up by IDE compiler

hello-world.zip

The attached project reliably demonstrates the problem I am facing when doing development in my IDE: IntelliJ IDEA 2019.2 (also verified same behavior in 2019.1).

In case the IDEA project file didn't come through, I did what the main Micronaut instructions suggest, namely ensuring that annotation processors are enabled.

The steps to reproduce are as follows:

First, to see this does not happen using the Gradle build run ./gradlew run and note this log message:

DEBUG io.micronaut.data.query - Executing SQL Insert: INSERT INTO book (title,id) VALUES (?,?)

Then, edit src/main/java/hello/world/Book.java and remove the title field by commenting the four areas identified. The result should be that the only reference to title remains as the second constructor argument.

Now re-run ./gradlew run and note this log message:

DEBUG io.micronaut.data.query - Executing SQL Insert: INSERT INTO book (id) VALUES (?)

This is expected: we've removed a field so the new schema is just a single column.

Next, demonstrate the problem in the IDE environment. Run ./gradlew clean just to be sure there is no conflict with the IDE's build (it stores in out but this ensures no confusion).

Now open the project in IntelliJ IDEA if you haven't already. Remove the comments you just added to the Book class so that it once again has two fields. Now find the Application class, right click, and run it. You will see the following log output:

DEBUG io.micronaut.data.query - Executing SQL Insert: INSERT INTO book (title,id) VALUES (?,?)

Next, re-comment out those same lines in Book and re-run Application. Instead of the expected INSERT statement we saw above in the Gradle build, we instead see this error:

16:01:13.631 [main] DEBUG io.micronaut.data.query - Executing SQL Insert: INSERT INTO book (title,id) VALUES (?,?)
16:01:13.646 [main] ERROR io.micronaut.runtime.Micronaut - Error starting Micronaut server: SQL Error executing INSERT: Column "TITLE" not found; SQL statement:
INSERT INTO book (title,id) VALUES (?,?) [42122-199]

What seems to be happening is that the IntelliJ IDEA build system and/or the annotation processors are not correctly doing the right thing once Book.java was changed. The Book class clearly gets recompiled, but it seems that some of the generated classes are not. The result is the Predator thinks there is still a title field when doing an insert, but not when creating the schema.

Parameter [type] of method [countByType] is not compatible with property [type] of entity

Hi,
I'm not sure if I'm doing this the wrong way or this is a bug.

You can use the following project to replicate this issue:
https://github.com/jachinte/micronaut-predator-issue-2

I want to count the number of records of a given type, so I'm adding the following method to repository PetRepository:

Integer countByType(Pet.PetType type);

This is the error I'm getting:

PetRepository.java:23: error: Unable to implement Repository method: PetRepository.countByType(Pet$PetType type). Parameter [type] of method [countByType] is not compatible with property [type] of entity: example.domain.Pet
    Integer countByType(Pet.PetType type);
            ^
Note: Writing native-image.properties file to destination: META-INF/native-image/example/example/native-image.properties
Note: Writing reflection-config.json file to destination: META-INF/native-image/example/example/reflection-config.json
Note: Creating bean classes for 7 type elements
1 error

FAILURE: Build failed with an exception.

However, entity Pet does have a PetType property:

package example.domain;

import io.micronaut.core.annotation.Creator;
import io.micronaut.data.annotation.AutoPopulated;

import javax.annotation.Nullable;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import java.util.UUID;

@Entity
public class Pet {

    @Id
    @AutoPopulated
    private UUID id;
    private String name;
    @ManyToOne
    private Owner owner;
    private PetType type = PetType.DOG;

    @Creator
    public Pet(String name, @Nullable Owner owner) {
        this.name = name;
        this.owner = owner;
    }

    public Owner getOwner() {
        return owner;
    }

    public String getName() {
        return name;
    }

    public UUID getId() {
        return id;
    }

    public PetType getType() {
		return type;
	}

	public void setType(PetType type) {
		this.type = type;
	}

	public void setId(UUID id) {
        this.id = id;
    }


    public enum PetType {
        DOG,
        CAT
    }
}

Thanks,
Miguel

JDBC: Boolean generated as bit when using Postgres

Attempting to use JDBC functionality, with the JPA annotations, the generated table uses bit for Boolean while the query generation uses Boolean for Boolean in a DataAccessException (below)

Entity defintion:

@Entity
data class Card(@Id @GeneratedValue var id: Long?,
                var pan: String,
                var activationCode: String,
                var temporaryPassword: String,
                var issued: Boolean = false,
                var active: Boolean = false)

Repository definition:

@JdbcRepository(dialect = Dialect.POSTGRES)
interface CardRepository : CrudRepository<Card, Long> {
    fun find(pan: String, activationCode: String): Card?

    fun find(issued: Boolean): List<Card>
}

DataAccessException

io.micronaut.data.exceptions.DataAccessException: SQL Error executing Query: ERROR: operator does not exist: bit = boolean
  Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts.
  Position: 136
        at io.micronaut.data.jdbc.operations.DefaultJdbcRepositoryOperations.lambda$findIterable$8(DefaultJdbcRepositoryOperations.java:331)
        at io.micronaut.data.jdbc.runtime.spring.SpringJdbcTransactionOperations.lambda$executeRead$1(SpringJdbcTransactionOperations.java:63)
        at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140)
        at io.micronaut.data.jdbc.runtime.spring.SpringJdbcTransactionOperations.executeRead(SpringJdbcTransactionOperations.java:60)
        at io.micronaut.data.jdbc.operations.DefaultJdbcRepositoryOperations.findIterable(DefaultJdbcRepositoryOperations.java:317)
        at io.micronaut.data.jdbc.operations.DefaultJdbcRepositoryOperations.findAll(DefaultJdbcRepositoryOperations.java:248)
        at io.micronaut.data.runtime.intercept.DefaultFindAllInterceptor.intercept(DefaultFindAllInterceptor.java:51)
        at io.micronaut.data.runtime.intercept.DefaultFindAllInterceptor.intercept(DefaultFindAllInterceptor.java:35)
        at io.micronaut.data.intercept.PredatorIntroductionAdvice.intercept(PredatorIntroductionAdvice.java:75)
        at io.micronaut.aop.MethodInterceptor.intercept(MethodInterceptor.java:40)
        at io.micronaut.aop.chain.InterceptorChain.proceed(InterceptorChain.java:146)
        at predator.exp.CardRepository$Intercepted.find(Unknown Source)
        at predator.exp.CardController.unissuedParkingCards(CardController.kt:18)
        at predator.exp.$CardControllerDefinition$Intercepted.$$access0(Unknown Source)
        at predator.exp.$CardControllerDefinition$Intercepted$$proxy0.invokeInternal(Unknown Source)
        at io.micronaut.context.AbstractExecutableMethod.invoke(AbstractExecutableMethod.java:144)
        at io.micronaut.aop.chain.InterceptorChain.lambda$new$1(InterceptorChain.java:89)
        at io.micronaut.aop.chain.InterceptorChain.proceed(InterceptorChain.java:146)
        at io.micronaut.validation.ValidatingInterceptor.intercept(ValidatingInterceptor.java:122)
        at io.micronaut.aop.MethodInterceptor.intercept(MethodInterceptor.java:40)
        at io.micronaut.aop.chain.InterceptorChain.proceed(InterceptorChain.java:146)
        at predator.exp.$CardControllerDefinition$Intercepted.unissuedParkingCards(Unknown Source)
        at predator.exp.$$CardControllerDefinition$InterceptedDefinition$$exec1.invokeInternal(Unknown Source)
        at io.micronaut.context.AbstractExecutableMethod.invoke(AbstractExecutableMethod.java:144)
        at io.micronaut.context.DefaultBeanContext$BeanExecutionHandle.invoke(DefaultBeanContext.java:2759)
        at io.micronaut.web.router.AbstractRouteMatch.execute(AbstractRouteMatch.java:235)
        at io.micronaut.web.router.RouteMatch.execute(RouteMatch.java:122)
        at io.micronaut.http.server.netty.RoutingInBoundHandler.lambda$buildResultEmitter$18(RoutingInBoundHandler.java:1388)
        at io.reactivex.internal.operators.flowable.FlowableCreate.subscribeActual(FlowableCreate.java:71)
        at io.reactivex.Flowable.subscribe(Flowable.java:14826)
        at io.reactivex.Flowable.subscribe(Flowable.java:14773)
        at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
        at io.reactivex.Flowable.subscribe(Flowable.java:14826)
        at io.reactivex.internal.operators.flowable.FlowableMap.subscribeActual(FlowableMap.java:37)
        at io.reactivex.Flowable.subscribe(Flowable.java:14826)
        at io.reactivex.Flowable.subscribe(Flowable.java:14773)
        at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
        at io.reactivex.Flowable.subscribe(Flowable.java:14826)
        at io.reactivex.internal.operators.flowable.FlowableSwitchIfEmpty.subscribeActual(FlowableSwitchIfEmpty.java:32)
        at io.reactivex.Flowable.subscribe(Flowable.java:14826)
        at io.reactivex.Flowable.subscribe(Flowable.java:14773)
        at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
        at io.reactivex.Flowable.subscribe(Flowable.java:14826)
        at io.reactivex.Flowable.subscribe(Flowable.java:14776)
        at io.micronaut.http.context.ServerRequestTracingPublisher.lambda$subscribe$0(ServerRequestTracingPublisher.java:52)
        at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:52)
        at io.micronaut.http.context.ServerRequestTracingPublisher.subscribe(ServerRequestTracingPublisher.java:52)
        at io.reactivex.internal.operators.flowable.FlowableFromPublisher.subscribeActual(FlowableFromPublisher.java:29)
        at io.reactivex.Flowable.subscribe(Flowable.java:14826)
        at io.reactivex.Flowable.subscribe(Flowable.java:14773)
        at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
        at io.reactivex.Flowable.subscribe(Flowable.java:14826)
        at io.reactivex.Flowable.subscribe(Flowable.java:14773)
        at io.reactivex.internal.operators.flowable.FlowableSubscribeOn$SubscribeOnSubscriber.run(FlowableSubscribeOn.java:82)
        at io.reactivex.internal.schedulers.ExecutorScheduler$ExecutorWorker$BooleanRunnable.run(ExecutorScheduler.java:288)
        at io.reactivex.internal.schedulers.ExecutorScheduler$ExecutorWorker.run(ExecutorScheduler.java:253)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at java.base/java.lang.Thread.run(Thread.java:835)
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: bit = boolean
  Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts.
  Position: 136
        at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2440)
        at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2183)
        at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:308)
        at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:441)
        at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:365)
        at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:150)
        at org.postgresql.jdbc.PgPreparedStatement.executeQuery(PgPreparedStatement.java:113)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:567)
        at org.apache.tomcat.jdbc.pool.StatementFacade$StatementProxy.invoke(StatementFacade.java:114)
        at com.sun.proxy.$Proxy12.executeQuery(Unknown Source)
        at io.micronaut.data.jdbc.operations.DefaultJdbcRepositoryOperations.lambda$findIterable$8(DefaultJdbcRepositoryOperations.java:329)
        ... 58 common frames omitted

Dynamic query criteria

At the moment query criteria is hard coded in the method name, so if we need in some cases to find products by created time starts with, and in another case to find products by created time ends with, we need to have 2 methods.

fun findByCreatedTimeStartsWith(createdTime: DateTime)
fun findByCreatedTimeEndsWith(createdTime: DateTime)

As solution can be dynamic query criteria

sealed class OrderedCriteria<T> {
    class Gt<T>(value: T) : OrderedCriteria // greater than this value
    class Gte<T>(value: T) : OrderedCriteria // greater than or equal to this value
    class Lt<T>(value: T) : OrderedCriteria // less than this value
    class Lte<T>(value: T) : OrderedCriteria // less than or equal to this value
    class Match<T>(value: T) : OrderedCriteria // match the value
    // ...
}

fun findBy(createdTime: OrderedCriteria<DateTime>)
fun countBy(createdTime: OrderedCriteria<DateTime>)

In this case we can have just one method for searching and cover all the cases.
This solution is just an alternative way, it doesn't mean the actual approach should be deleted.

Also, we can add sorting

enum class Sorting {
    ASC, DESC, NONE
}

sealed class OrderedCriteria<T> {
    class Gt<T>(value: T, sort: Sorting = Sorting.NONE) : OrderedCriteria
    // ...
}

And we also can add some annotation to method parameters to configure more specific projection if needed.

Projection Group

In Spring we have validation groups

public class User {

    interface New {
    }

    interface Exist {
    }
    
    interface UpdateName extends Exist {
    }

    @Null(groups = {New.class})
    private Long id;

    @NotNull(groups = {New.class})
    private String name;

    @NotNull(groups = {New.class})
    private String login;

    @NotNull(groups = {New.class})
    private String password;

    @NotNull(groups = {New.class})
    @Email(groups = {New.class})
    private String email;
}

public ResponseEntity<UserDto> create(@Validated(User.New.class) @RequestBody User dto) {
    return new ResponseEntity<>(service.save(dto), HttpStatus.OK);
}

What do you think to have same approach with projection?

public class User {

    interface List {
    }

    interface Detailed {
    }

    @ProjectionGroup(groups = {List.class, Detailed.class})
    private Long id;

    @ProjectionGroup(groups = {List.class, Detailed.class})
    private String name;

    @ProjectionGroup(groups = {List.class})
    private String login;

    @ProjectionGroup(groups = {Detailed.class})
    private String email;
}

and than in repository just to specify the projection

@Projection(User.List.class)
fun list(): List<User>

and just the fields specified in the projection group will be fetched.

@Transactional not working on repository's custom query

In the docs, section 8.1.3 JDBC Repositories, this example is highlighted:

    @Transactional
    public List<Book> findByTitle(String title) {
        String sql = "SELECT * FROM Book AS book WHERE book.title = ?";
        return jdbcOperations.prepareStatement(sql, statement -> {
            statement.setString(1, title);
            ResultSet resultSet = statement.executeQuery();
            return jdbcOperations.entityStream(resultSet, Book.class).collect(Collectors.toList());
        });
    }

But the as you can see in this example (hello-world.zip) the @Transactional annotation doesn't seem to be honored when it's applied directly to the BookRepository method. Rather, I could only get it to work if I wrapped the call with another singleton that had its own @Transactional annotation. See src/main/java/hello/world/Application.java:

    @EventListener
    void init(StartupEvent event) {
        final Book book = new Book("9780545010221", "Harry Potter");
        repository.save(book);

        // find books through the wrapping service
        List<Book> coolBooks = singleton.findCoolBooks();
        System.out.println("How many cool books? " + coolBooks.size());

        // now do it directly against the repository and this fails
        coolBooks = repository.findCoolBooks();
        System.out.println("How many cool books? " + coolBooks.size());

    }

The first query, singleton.findCoolBooks() succeeds but the second query, repository.findCoolBooks() blows up with the following exception:

io.micronaut.data.transaction.exceptions.NoTransactionException: No transaction declared. Define @Transactional on the surrounding method prior to calling getConnection()
        at io.micronaut.data.jdbc.runtime.spring.SpringJdbcTransactionOperations.getConnection(SpringJdbcTransactionOperations.java:79)
        at io.micronaut.data.jdbc.runtime.spring.SpringJdbcTransactionOperations.getConnection(SpringJdbcTransactionOperations.java:28)
        at io.micronaut.data.jdbc.operations.DefaultJdbcRepositoryOperations.prepareStatement(DefaultJdbcRepositoryOperations.java:574)
        at hello.world.BookRepository.findCoolBooks(BookRepository.java:23)
        at hello.world.Application.init(Application.java:32)
        at hello.world.$ApplicationDefinition$$exec1.invokeInternal(Unknown Source)
        at io.micronaut.context.AbstractExecutableMethod.invoke(AbstractExecutableMethod.java:144)
        at io.micronaut.context.DefaultBeanContext$BeanExecutionHandle.invoke(DefaultBeanContext.java:2761)
        at io.micronaut.aop.chain.AdapterIntroduction.intercept(AdapterIntroduction.java:81)
        at io.micronaut.aop.MethodInterceptor.intercept(MethodInterceptor.java:40)
        at io.micronaut.aop.chain.InterceptorChain.proceed(InterceptorChain.java:146)
        at hello.world.Application$ApplicationEventListener$init1$Intercepted.onApplicationEvent(Unknown Source)
        at io.micronaut.context.DefaultBeanContext.lambda$publishEvent$17(DefaultBeanContext.java:1063)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
        at io.micronaut.context.DefaultBeanContext.publishEvent(DefaultBeanContext.java:1057)
        at io.micronaut.context.DefaultBeanContext.start(DefaultBeanContext.java:214)
        at io.micronaut.context.DefaultApplicationContext.start(DefaultApplicationContext.java:187)
        at io.micronaut.runtime.Micronaut.start(Micronaut.java:69)
        at io.micronaut.runtime.Micronaut.run(Micronaut.java:303)
        at io.micronaut.runtime.Micronaut.run(Micronaut.java:289)
        at hello.world.Application.main(Application.java:11)

Add support for transaction by method + lambda

Besides @Transactional annotation, add support for transaction by method + lambda.

connection.transaction(lambda)

or in Kotlin

connection.transaction {
}

Examples
https://www.jooq.org/doc/3.11/manual/sql-execution/transaction-management/
https://www.playframework.com/documentation/2.5.x/JavaJPA#Running-transactions-decoupled-from-requests
https://github.com/JetBrains/Exposed/wiki/Transactions

In this case we can write 2 separate transaction in one method, and no need to make 2 separate methods just for adding @Transactional.
Also no need for annotation processing.

Use Kotlin DSL for Kotlin examples

I have a working build.gradle.kts written in Kotlin DSL for example-jdbc-kotlin. If you like, I can submit it in a PR.

Of course, if we switch from Groovy to Kotlin for this example, we should probably do it for all Kotlin examples across the Micronaut ecosystem. If you're interested in changing them, I can take a look at the others and probably submit PRs for those as well.

Postgres auto-generated ID schema not working

I'll try to get a PR together soon, but just getting this recorded as a bug before I forget:

https://github.com/micronaut-projects/micronaut-predator/blob/233bbb94d3e8fe23fe089f1bdcbca7600d1f6659/predator-model/src/main/java/io/micronaut/data/model/query/builder/sql/SqlQueryBuilder.java#L245

The code clearly things the keyword "GENERATED" is the right way to declare an auto incrementing ID in Postgres. But that isn't true :) Instead, they recommend the data type be SERIAL or BIGSERIAL as per these docs:

https://www.postgresql.org/docs/8.1/datatype.html#DATATYPE-SERIAL

Unfortunately, the way the code is currently structured it makes it a bit hard to quickly patch this so I'll have to spent more time before I can send back a test + PR.

Query Method Pattern

At he moment query patterns are written in the method name.
I think it will be better to write patterns in parameters name.
Instead of

findByTitleStartsWith(String title)

we always can use a more general find or findBy method name

findBy(String titleStartsWith)
findBy(String title)

In this case no need to repeat title in method name and for parameter.

Also we can add some annotations to the parameters, something like

findBy(@Pattern(StartsWith) String title)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.