micronaut-projects / micronaut-data Goto Github PK
View Code? Open in Web Editor NEWAhead of Time Data Repositories
License: Apache License 2.0
Ahead of Time Data Repositories
License: Apache License 2.0
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:
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!
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" ...
Separate issue to add automate tests for Oracle. /cc @recursivecodes
I'm getting the error:
Cannot order by non-existent property: mypropAsc
‘ResultSet’ and PreparedStatement objects are not closed. They should be defined using a try expression.
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
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:
A new row is inserted. The saved entity is updated with the new id.
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
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...
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?
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/" }
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 = ?
In the Join Queries example https://micronaut-projects.github.io/micronaut-predator/snapshot/guide/#joinQueries
@Entity
data class Product(
@Id
@GeneratedValue
var id: Long?,
var name: String,
@ManyToOne(optional = false, fetch = FetchType.LAZY)
var manufacturer: Manufacturer
)
What means fetch = FetchType.LAZY
in this example? The field manufacturer
is not optional, how it could be lazy?
The Pet data class in JDBC examples has the pet name as a val but the type as a var. that does not seem to make sense.
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)
It does not appear that Predator honors or respects the options for this, such as @IdClass
or @EmbeddedId
. It'd be nice if it did. Thanks!
I'm following the examples, but when calling repository.findAll() I get the below exception:
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)
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 {
...
}
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;
}
}
It doesn't look like Predator supports it, but it sure would be nice
JOOQ is more concise than HQL, it will be a good alternative.
The query is also validated in compile time.
https://www.jooq.org/doc/3.11/manual/sql-building/sql-statements/select-statement/
@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?
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.
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
?
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.
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:
Thanks & best regards
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
.
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?
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.
PR will arrive shortly.
example-jdbc-kotlin throws the exception below when accessing http://localhost:8080/pets.
java.lang.UnsupportedOperationException: Cannot write a read-only property
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 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
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.
It will be very cool to have support for GraphQL https://graphql.org/ and automatically resolve queries.
We usually use cursor pagination https://dev.to/jackmarchant/offset-and-cursor-pagination-explained-b89
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!
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.
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
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
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)
@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
When predator will available in maven central?
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.
https://micronaut-projects.github.io/micronaut-predator/snapshot/guide/#dto
Can you please add some more description into documentation, it is not clear how
fun findOne(title: String): BookDTO
knows that it should extract data from book table?
How it will be if I need multiple dto projection to same entity/table?
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.
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)
For example
https://github.com/micronaut-projects/micronaut-predator/blob/master/examples/example-jdbc-kotlin/src/main/kotlin/example/repositories/PetRepository.kt#L20
here @Join
can be deduced, because owner
in Pet
object is not nullable, it should be passed to constructor, so we for sure need to join in order to construct the object.
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.
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.
I'll try to get a PR together soon, but just getting this recorded as a bug before I forget:
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.
It'd be nice to have an annotation like Hibernate's ColumnTransformer
annotation so that users can customize the read/write SQL for a given column (eg. call pgp_sym_decrypt
/pgp_sym_encrypt
on read/write).
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)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.