From e2a8bf30ce6da6706fb3ed58973b800ebf64fff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Darri=20Sigur=C3=B0sson?= Date: Tue, 12 Sep 2023 19:59:40 +0200 Subject: [PATCH 1/3] EntityConnection.insertSelect() added --- changelog.txt | 3 +- .../codion/framework/db/EntityConnection.java | 18 +++++++++ .../db/http/DefaultHttpEntityConnection.java | 21 ++++++++++ .../db/http/JsonHttpEntityConnection.java | 23 +++++++++++ .../local/DefaultLocalEntityConnection.java | 10 +++++ .../db/rmi/RemoteEntityConnection.java | 20 ++++++++++ .../server/DefaultRemoteEntityConnection.java | 14 +++++++ .../framework/servlet/EntityService.java | 38 +++++++++++++++++++ .../framework/servlet/EntityServiceTest.java | 4 +- 9 files changed, 148 insertions(+), 3 deletions(-) diff --git a/changelog.txt b/changelog.txt index 6163423ed7..077d3b88a7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -9546,4 +9546,5 @@ LoadTestModel refactored, application initialization improved DefaultRemoteClient.withDatabaseUser() bug fixed, copy constructor improved ResultIterator no longer calls close() on exhaustion or error, related refactoring - Database.closeSilently() removed \ No newline at end of file + Database.closeSilently() removed + EntityConnection.insertSelect() added \ No newline at end of file diff --git a/framework/db-core/src/main/java/is/codion/framework/db/EntityConnection.java b/framework/db-core/src/main/java/is/codion/framework/db/EntityConnection.java index cdf5abed0b..8b22213c0f 100644 --- a/framework/db-core/src/main/java/is/codion/framework/db/EntityConnection.java +++ b/framework/db-core/src/main/java/is/codion/framework/db/EntityConnection.java @@ -153,6 +153,15 @@ public interface EntityConnection extends AutoCloseable { */ Entity.Key insert(Entity entity) throws DatabaseException; + /** + * Inserts the given entity, returning the inserted etity. + * Performs a commit unless a transaction is open. + * @param entity the entity to insert + * @return the inserted entity + * @throws DatabaseException in case of a database exception + */ + Entity insertSelect(Entity entity) throws DatabaseException; + /** * Inserts the given entities, returning the primary keys in the same order as they were received. * Performs a commit unless a transaction is open. @@ -162,6 +171,15 @@ public interface EntityConnection extends AutoCloseable { */ Collection insert(Collection entities) throws DatabaseException; + /** + * Inserts the given entities, returning the inserted entities. + * Performs a commit unless a transaction is open. + * @param entities the entities to insert + * @return the inserted entities + * @throws DatabaseException in case of a database exception + */ + Collection insertSelect(Collection entities) throws DatabaseException; + /** * Updates the given entity based on its attribute values. * Throws an exception if the given entity is unmodified. diff --git a/framework/db-http/src/main/java/is/codion/framework/db/http/DefaultHttpEntityConnection.java b/framework/db-http/src/main/java/is/codion/framework/db/http/DefaultHttpEntityConnection.java index 9df1716cae..2909828409 100644 --- a/framework/db-http/src/main/java/is/codion/framework/db/http/DefaultHttpEntityConnection.java +++ b/framework/db-http/src/main/java/is/codion/framework/db/http/DefaultHttpEntityConnection.java @@ -200,6 +200,11 @@ public Entity.Key insert(Entity entity) throws DatabaseException { return insert(singletonList(entity)).iterator().next(); } + @Override + public Entity insertSelect(Entity entity) throws DatabaseException { + return insertSelect(singletonList(entity)).iterator().next(); + } + @Override public Collection insert(Collection entities) throws DatabaseException { Objects.requireNonNull(entities); @@ -216,6 +221,22 @@ public Collection insert(Collection entities) thro } } + @Override + public Collection insertSelect(Collection entities) throws DatabaseException { + Objects.requireNonNull(entities); + try { + synchronized (this.entities) { + return onResponse(execute(createHttpPost("insertSelect", byteArrayEntity(entities)))); + } + } + catch (DatabaseException e) { + throw e; + } + catch (Exception e) { + throw logAndWrap(e); + } + } + @Override public void update(Entity entity) throws DatabaseException { update(singletonList(requireNonNull(entity, "entity"))); diff --git a/framework/db-http/src/main/java/is/codion/framework/db/http/JsonHttpEntityConnection.java b/framework/db-http/src/main/java/is/codion/framework/db/http/JsonHttpEntityConnection.java index 865a49fd9d..a11c857b34 100644 --- a/framework/db-http/src/main/java/is/codion/framework/db/http/JsonHttpEntityConnection.java +++ b/framework/db-http/src/main/java/is/codion/framework/db/http/JsonHttpEntityConnection.java @@ -216,6 +216,11 @@ public Entity.Key insert(Entity entity) throws DatabaseException { return insert(singletonList(entity)).iterator().next(); } + @Override + public Entity insertSelect(Entity entity) throws DatabaseException { + return insertSelect(singletonList(entity)).iterator().next(); + } + @Override public Collection insert(Collection entities) throws DatabaseException { Objects.requireNonNull(entities); @@ -234,6 +239,24 @@ public Collection insert(Collection entities) thro } } + @Override + public Collection insertSelect(Collection entities) throws DatabaseException { + Objects.requireNonNull(entities); + try { + synchronized (this.entities) { + return onJsonResponse(execute(createHttpPost("insertSelect", + stringEntity(entityObjectMapper.writeValueAsString(entities)))), + entityObjectMapper, EntityObjectMapper.ENTITY_LIST_REFERENCE); + } + } + catch (DatabaseException e) { + throw e; + } + catch (Exception e) { + throw logAndWrap(e); + } + } + @Override public void update(Entity entity) throws DatabaseException { update(singletonList(entity)); diff --git a/framework/db-local/src/main/java/is/codion/framework/db/local/DefaultLocalEntityConnection.java b/framework/db-local/src/main/java/is/codion/framework/db/local/DefaultLocalEntityConnection.java index 3f16c80ffd..87bb4199ca 100644 --- a/framework/db-local/src/main/java/is/codion/framework/db/local/DefaultLocalEntityConnection.java +++ b/framework/db-local/src/main/java/is/codion/framework/db/local/DefaultLocalEntityConnection.java @@ -208,6 +208,11 @@ public Entity.Key insert(Entity entity) throws DatabaseException { return insert(singletonList(requireNonNull(entity, "entity"))).iterator().next(); } + @Override + public Entity insertSelect(Entity entity) throws DatabaseException { + return select(insert(entity)); + } + @Override public Collection insert(Collection entities) throws DatabaseException { if (requireNonNull(entities, ENTITIES).isEmpty()) { @@ -259,6 +264,11 @@ public Collection insert(Collection entities) thro } } + @Override + public Collection insertSelect(Collection entities) throws DatabaseException { + return select(insert(entities)); + } + @Override public void update(Entity entity) throws DatabaseException { update(singletonList(requireNonNull(entity, "entity"))); diff --git a/framework/db-rmi/src/main/java/is/codion/framework/db/rmi/RemoteEntityConnection.java b/framework/db-rmi/src/main/java/is/codion/framework/db/rmi/RemoteEntityConnection.java index 7bc9ae0701..25fea6dbdd 100644 --- a/framework/db-rmi/src/main/java/is/codion/framework/db/rmi/RemoteEntityConnection.java +++ b/framework/db-rmi/src/main/java/is/codion/framework/db/rmi/RemoteEntityConnection.java @@ -153,6 +153,16 @@ public interface RemoteEntityConnection extends Remote, AutoCloseable { */ Entity.Key insert(Entity entity) throws RemoteException, DatabaseException; + /** + * Inserts the given entity, returning the inserted etity. + * Performs a commit unless a transaction is open. + * @param entity the entity to insert + * @return the inserted entity + * @throws DatabaseException in case of a database exception + * @throws RemoteException in case of a remote exception + */ + Entity insertSelect(Entity entity) throws RemoteException, DatabaseException; + /** * Inserts the given entities, returning the primary keys in the same order as they were received. * Performs a commit unless a transaction is open. @@ -163,6 +173,16 @@ public interface RemoteEntityConnection extends Remote, AutoCloseable { */ Collection insert(Collection entities) throws RemoteException, DatabaseException; + /** + * Inserts the given entities, returning the inserted entities. + * Performs a commit unless a transaction is open. + * @param entities the entities to insert + * @return the inserted entities + * @throws DatabaseException in case of a database exception + * @throws RemoteException in case of a remote exception + */ + Collection insertSelect(Collection entities) throws RemoteException, DatabaseException; + /** * Updates the given entity based on its attribute values. Returns the updated entity. * Throws an exception if the given entity is unmodified. diff --git a/framework/server/src/main/java/is/codion/framework/server/DefaultRemoteEntityConnection.java b/framework/server/src/main/java/is/codion/framework/server/DefaultRemoteEntityConnection.java index 9876baef5b..a107054e6a 100644 --- a/framework/server/src/main/java/is/codion/framework/server/DefaultRemoteEntityConnection.java +++ b/framework/server/src/main/java/is/codion/framework/server/DefaultRemoteEntityConnection.java @@ -163,6 +163,13 @@ public Entity.Key insert(Entity entity) throws RemoteException, DatabaseExceptio } } + @Override + public Entity insertSelect(Entity entity) throws RemoteException, DatabaseException { + synchronized (connectionProxy) { + return connectionProxy.insertSelect(entity); + } + } + @Override public Collection insert(Collection entities) throws DatabaseException { synchronized (connectionProxy) { @@ -170,6 +177,13 @@ public Collection insert(Collection entities) thro } } + @Override + public Collection insertSelect(Collection entities) throws RemoteException, DatabaseException { + synchronized (connectionProxy) { + return connectionProxy.insertSelect(entities); + } + } + @Override public void update(Entity entity) throws RemoteException, DatabaseException { synchronized (connectionProxy) { diff --git a/framework/servlet/src/main/java/is/codion/framework/servlet/EntityService.java b/framework/servlet/src/main/java/is/codion/framework/servlet/EntityService.java index eadd8f131c..200264fb13 100644 --- a/framework/servlet/src/main/java/is/codion/framework/servlet/EntityService.java +++ b/framework/servlet/src/main/java/is/codion/framework/servlet/EntityService.java @@ -221,7 +221,9 @@ private void setupHandlers() { javalin.post(URL_JAVA_SERIALIZATION + "select", new SelectHandler()); javalin.post(URL_JSON_SERIALIZATION + "select", new SelectJsonHandler()); javalin.post(URL_JAVA_SERIALIZATION + "insert", new InsertHandler()); + javalin.post(URL_JAVA_SERIALIZATION + "insertSelect", new InsertSelectHandler()); javalin.post(URL_JSON_SERIALIZATION + "insert", new InsertJsonHandler()); + javalin.post(URL_JSON_SERIALIZATION + "insertSelect", new InsertSelectJsonHandler()); javalin.post(URL_JAVA_SERIALIZATION + "update", new UpdateHandler()); javalin.post(URL_JAVA_SERIALIZATION + "updateSelect", new UpdateSelectHandler()); javalin.post(URL_JSON_SERIALIZATION + "update", new UpdateJsonHandler()); @@ -671,6 +673,23 @@ public void handle(Context context) { } } + private final class InsertSelectHandler implements Handler { + + @Override + public void handle(Context context) { + try { + RemoteEntityConnection connection = authenticate(context); + Collection entities = connection.insertSelect((Collection) deserialize(context.req)); + context.status(HttpStatus.OK_200) + .contentType(ContentType.APPLICATION_OCTET_STREAM) + .result(Serializer.serialize(entities)); + } + catch (Exception e) { + handleException(context, e); + } + } + } + private final class InsertJsonHandler implements Handler { @Override @@ -690,6 +709,25 @@ public void handle(Context context) { } } + private final class InsertSelectJsonHandler implements Handler { + + @Override + public void handle(Context context) { + try { + RemoteEntityConnection connection = authenticate(context); + EntityObjectMapper mapper = entityObjectMapper(connection.entities()); + Collection entities = mapper.deserializeEntities(context.req.getInputStream()); + Collection inserted = connection.insertSelect(entities); + context.status(HttpStatus.OK_200) + .contentType(ContentType.APPLICATION_JSON) + .result(mapper.writeValueAsString(inserted)); + } + catch (Exception e) { + handleException(context, e); + } + } + } + private final class UpdateHandler implements Handler { @Override diff --git a/framework/servlet/src/test/java/is/codion/framework/servlet/EntityServiceTest.java b/framework/servlet/src/test/java/is/codion/framework/servlet/EntityServiceTest.java index 422df84430..356967e711 100644 --- a/framework/servlet/src/test/java/is/codion/framework/servlet/EntityServiceTest.java +++ b/framework/servlet/src/test/java/is/codion/framework/servlet/EntityServiceTest.java @@ -407,11 +407,11 @@ void insert() throws Exception { assertEquals(2, EntityServiceTest.>deserialize(response.getEntity().getContent()).size()); } entities.forEach(entity -> entity.put(Department.ID, entity.get(Department.ID) + 1)); - post = new HttpPost(createJsonURI("insert")); + post = new HttpPost(createJsonURI("insertSelect")); post.setEntity(new StringEntity(ENTITY_OBJECT_MAPPER.writeValueAsString(entities))); try (CloseableHttpResponse response = client.execute(TARGET_HOST, post, context)) { assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); - assertEquals(2, ENTITY_OBJECT_MAPPER.deserializeKeys(response.getEntity().getContent()).size()); + assertEquals(2, ENTITY_OBJECT_MAPPER.deserializeEntities(response.getEntity().getContent()).size()); } } } From 1a7c579121fc800f53dad35d3086b416695c93b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Darri=20Sigur=C3=B0sson?= Date: Tue, 12 Sep 2023 20:05:28 +0200 Subject: [PATCH 2/3] AbstractEntityEditModel.doInsert() now returns entities instead of keys --- changelog.txt | 3 ++- .../demos/chinook/model/InvoiceLineEditModel.java | 6 +++--- .../codion/framework/model/AbstractEntityEditModel.java | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/changelog.txt b/changelog.txt index 077d3b88a7..7a56f98a47 100644 --- a/changelog.txt +++ b/changelog.txt @@ -9547,4 +9547,5 @@ DefaultRemoteClient.withDatabaseUser() bug fixed, copy constructor improved ResultIterator no longer calls close() on exhaustion or error, related refactoring Database.closeSilently() removed - EntityConnection.insertSelect() added \ No newline at end of file + EntityConnection.insertSelect() added + AbstractEntityEditModel.doInsert() now returns entities instead of keys \ No newline at end of file diff --git a/demos/chinook/src/main/java/is/codion/framework/demos/chinook/model/InvoiceLineEditModel.java b/demos/chinook/src/main/java/is/codion/framework/demos/chinook/model/InvoiceLineEditModel.java index 465379a262..8f9507381a 100644 --- a/demos/chinook/src/main/java/is/codion/framework/demos/chinook/model/InvoiceLineEditModel.java +++ b/demos/chinook/src/main/java/is/codion/framework/demos/chinook/model/InvoiceLineEditModel.java @@ -30,15 +30,15 @@ void addTotalsUpdatedListener(Consumer> listener) { } @Override - protected Collection doInsert(Collection entities) throws DatabaseException { + protected Collection doInsert(Collection entities) throws DatabaseException { EntityConnection connection = connectionProvider().connection(); connection.beginTransaction(); try { - Collection keys = connection.insert(entities); + Collection inserted = connection.insertSelect(entities); updateTotals(entities, connection); connection.commitTransaction(); - return keys; + return inserted; } catch (DatabaseException e) { connection.rollbackTransaction(); diff --git a/framework/model/src/main/java/is/codion/framework/model/AbstractEntityEditModel.java b/framework/model/src/main/java/is/codion/framework/model/AbstractEntityEditModel.java index f61d533f5a..c128ea501a 100644 --- a/framework/model/src/main/java/is/codion/framework/model/AbstractEntityEditModel.java +++ b/framework/model/src/main/java/is/codion/framework/model/AbstractEntityEditModel.java @@ -737,11 +737,11 @@ public final void removeRefreshListener(Runnable listener) { /** * Inserts the given entities into the database * @param entities the entities to insert - * @return the primary keys of the inserted entities + * @return the inserted entities * @throws DatabaseException in case of a database exception */ - protected Collection doInsert(Collection entities) throws DatabaseException { - return connectionProvider.connection().insert(entities); + protected Collection doInsert(Collection entities) throws DatabaseException { + return connectionProvider.connection().insertSelect(entities); } /** @@ -890,7 +890,7 @@ private Collection insertEntities(Collection entities) //has not been performed, hence why this logging is performed after validation LOG.debug("{} - insert {}", this, entities); - return connectionProvider.connection().select(doInsert(entities)); + return doInsert(entities); } private boolean isSetEntityAllowed() { From 4b4d5f334575d6824101222114aa219192095f57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Darri=20Sigur=C3=B0sson?= Date: Tue, 12 Sep 2023 20:08:27 +0200 Subject: [PATCH 3/3] Code examples updated --- .../demos/chinook/manual/EntityConnectionDemo.java | 4 ++-- .../demos/manual/store/minimal/db/StoreDatabase.java | 6 ++---- .../docs/asciidoc/manual/framework-entity-connection.adoc | 4 ++++ .../main/java/is/codion/framework/db/EntityConnection.java | 2 +- readme.adoc | 6 ++---- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/demos/chinook/src/main/java/is/codion/framework/demos/chinook/manual/EntityConnectionDemo.java b/demos/chinook/src/main/java/is/codion/framework/demos/chinook/manual/EntityConnectionDemo.java index 0cc6fce9c8..68b66dcd44 100644 --- a/demos/chinook/src/main/java/is/codion/framework/demos/chinook/manual/EntityConnectionDemo.java +++ b/demos/chinook/src/main/java/is/codion/framework/demos/chinook/manual/EntityConnectionDemo.java @@ -212,7 +212,7 @@ static void insert(EntityConnectionProvider connectionProvider) throws DatabaseE .with(Artist.NAME, "My Band") .build(); - connection.insert(myBand); + myBand = connection.insertSelect(myBand); Entity firstAlbum = entities.builder(Album.TYPE) .with(Album.ARTIST_FK, myBand) @@ -223,7 +223,7 @@ static void insert(EntityConnectionProvider connectionProvider) throws DatabaseE .with(Album.TITLE, "Second album") .build(); - Collection keys = connection.insert(asList(firstAlbum, secondAlbum)); + Collection albumKeys = connection.insert(asList(firstAlbum, secondAlbum)); // end::insert[] } diff --git a/demos/manual/src/main/java/is/codion/framework/demos/manual/store/minimal/db/StoreDatabase.java b/demos/manual/src/main/java/is/codion/framework/demos/manual/store/minimal/db/StoreDatabase.java index daa11e3b0a..673e774030 100644 --- a/demos/manual/src/main/java/is/codion/framework/demos/manual/store/minimal/db/StoreDatabase.java +++ b/demos/manual/src/main/java/is/codion/framework/demos/manual/store/minimal/db/StoreDatabase.java @@ -63,9 +63,7 @@ static void storeEntityConnection() throws DatabaseException { .with(Customer.LAST_NAME, "Jackson") .build(); - Entity.Key customerKey = connection.insert(customer); - // select to get generated and default column values - customer = connection.select(customerKey); + customer = connection.insertSelect(customer); Entity address = entities.builder(Address.TYPE) .with(Address.CUSTOMER_FK, customer) @@ -79,7 +77,7 @@ static void storeEntityConnection() throws DatabaseException { customer = connection.updateSelect(customer); - connection.delete(asList(addressKey, customerKey)); + connection.delete(asList(addressKey, customer.primaryKey())); connection.close(); } diff --git a/documentation/src/docs/asciidoc/manual/framework-entity-connection.adoc b/documentation/src/docs/asciidoc/manual/framework-entity-connection.adoc index 1ef62fb1d9..9fe170735c 100644 --- a/documentation/src/docs/asciidoc/manual/framework-entity-connection.adoc +++ b/documentation/src/docs/asciidoc/manual/framework-entity-connection.adoc @@ -136,8 +136,12 @@ For inserting rows. ** {url-entity-connection}#insert{opar}is.codion.framework.domain.entity.Entity{cpar}[insert(Entity entity)] +** {url-entity-connection}#insertSelect{opar}is.codion.framework.domain.entity.Entity{cpar}[insertSelect(Entity entity)] + ** {url-entity-connection}#insert{opar}java.util.Collection{cpar}[insert(Collection entities)] +** {url-entity-connection}#insertSelect{opar}java.util.Collection{cpar}[insertSelect(Collection entities)] + [source,java,indent=0] ---- include::{dir-chinook-source}/is/codion/framework/demos/chinook/manual/EntityConnectionDemo.java[tags=insert] diff --git a/framework/db-core/src/main/java/is/codion/framework/db/EntityConnection.java b/framework/db-core/src/main/java/is/codion/framework/db/EntityConnection.java index 8b22213c0f..4035164076 100644 --- a/framework/db-core/src/main/java/is/codion/framework/db/EntityConnection.java +++ b/framework/db-core/src/main/java/is/codion/framework/db/EntityConnection.java @@ -154,7 +154,7 @@ public interface EntityConnection extends AutoCloseable { Entity.Key insert(Entity entity) throws DatabaseException; /** - * Inserts the given entity, returning the inserted etity. + * Inserts the given entity, returning the inserted entity. * Performs a commit unless a transaction is open. * @param entity the entity to insert * @return the inserted entity diff --git a/readme.adoc b/readme.adoc index bc836f6248..dcbf69be4c 100644 --- a/readme.adoc +++ b/readme.adoc @@ -492,9 +492,7 @@ Entity customer = entities.builder(Customer.TYPE) .with(Customer.LAST_NAME, "Jackson") .build(); -Entity.Key customerKey = connection.insert(customer); -// select to get generated and default column values -customer = connection.select(customerKey); +customer = connection.insertSelect(customer); Entity address = entities.builder(Address.TYPE) .with(Address.CUSTOMER_FK, customer) @@ -508,7 +506,7 @@ customer.put(Customer.EMAIL, "mail@email.com"); customer = connection.updateSelect(customer); -connection.delete(asList(addressKey, customerKey)); +connection.delete(asList(addressKey, customer.primaryKey())); connection.close(); ----