This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch past-M2
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit f797ef016a356c189651a0234809ca10a60f124c
Author: Andrus Adamchik <[email protected]>
AuthorDate: Sun May 31 13:12:29 2026 -0400

    Translators as stateless services producing immutable TranslatedXyz records
    
    * batch translators
---
 .../org/apache/cayenne/crypto/CryptoModule.java    |   9 +-
 .../crypto/batch/CryptoBatchTranslator.java        |  66 ++++++++++
 .../CryptoBatchTranslatorFactoryDecorator.java     |  88 -------------
 .../dbsync/reverse/configuration/ToolsModule.java  |  10 +-
 .../java/org/apache/cayenne/access/DataNode.java   |  61 ++++-----
 .../apache/cayenne/access/jdbc/BaseSQLAction.java  |   2 +-
 .../apache/cayenne/access/jdbc/BatchAction.java    |  36 +++---
 .../cayenne/access/jdbc/SQLTemplateAction.java     |   3 +-
 .../apache/cayenne/access/jdbc/SelectAction.java   |   5 +-
 .../translator/batch/BaseBatchTranslator.java      |  54 ++++----
 ...hTranslatorFactory.java => BatchRowBinder.java} |  22 ++--
 .../access/translator/batch/BatchTranslator.java   |  44 ++++---
 .../batch/DefaultBatchTranslatorFactory.java       |  57 ---------
 .../translator/batch/DeleteBatchTranslator.java    |  33 ++---
 .../translator/batch/InsertBatchTranslator.java    |  22 ++--
 .../batch/SoftDeleteBatchTranslator.java           |  54 ++++++--
 .../batch/SoftDeleteTranslatorFactory.java         |  69 ----------
 .../translator/batch/TranslatedBatch.java}         |  27 ++--
 .../translator/batch/UpdateBatchTranslator.java    |  18 +--
 .../batch/legacy/DefaultBatchTranslator.java       | 112 ----------------
 .../cayenne/configuration/runtime/CoreModule.java  |  10 +-
 .../runtime/DefaultDataNodeFactory.java            |  21 ++-
 .../cayenne/dba/oracle/OracleActionBuilder.java    |  13 --
 .../cayenne/access/jdbc/BatchActionLockingIT.java  |   9 +-
 .../translator/batch/DeleteBatchTranslatorIT.java  |  24 +---
 .../translator/batch/InsertBatchTranslatorIT.java  |  19 +--
 .../batch/SoftDeleteBatchTranslatorIT.java         |  23 ++--
 .../translator/batch/UpdateBatchTranslatorIT.java  |  23 +---
 .../batch/legacy/DefaultBatchTranslatorIT.java     | 141 ---------------------
 .../runtime/DataDomainProviderTest.java            |  10 +-
 .../runtime/DefaultDbAdapterFactoryTest.java       |  18 ++-
 31 files changed, 362 insertions(+), 741 deletions(-)

diff --git 
a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModule.java 
b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModule.java
index 9f619393e..d8beb738a 100644
--- a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModule.java
+++ b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModule.java
@@ -19,9 +19,10 @@
 package org.apache.cayenne.crypto;
 
 import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
-import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
+import org.apache.cayenne.access.translator.batch.BatchTranslator;
 import org.apache.cayenne.configuration.DataMapLoader;
-import org.apache.cayenne.crypto.batch.CryptoBatchTranslatorFactoryDecorator;
+import org.apache.cayenne.crypto.batch.CryptoBatchTranslator;
+import org.apache.cayenne.di.Key;
 import org.apache.cayenne.crypto.cipher.DefaultCipherFactory;
 import org.apache.cayenne.crypto.key.JceksKeySource;
 import org.apache.cayenne.crypto.map.CryptoDataMapLoader;
@@ -152,7 +153,9 @@ public class CryptoModule implements Module {
         
binder.bind(TransformerFactory.class).to(DefaultTransformerFactory.class);
 
         binder.decorate(DataMapLoader.class).before(CryptoDataMapLoader.class);
-        
binder.decorate(BatchTranslatorFactory.class).before(CryptoBatchTranslatorFactoryDecorator.class);
+        binder.decorate(Key.get(BatchTranslator.class, 
BatchTranslator.INSERT)).before(CryptoBatchTranslator.class);
+        binder.decorate(Key.get(BatchTranslator.class, 
BatchTranslator.UPDATE)).before(CryptoBatchTranslator.class);
+        binder.decorate(Key.get(BatchTranslator.class, 
BatchTranslator.DELETE)).before(CryptoBatchTranslator.class);
         
binder.bind(RowReaderFactory.class).to(CryptoRowReaderFactoryDecorator.class);
 
         // decorate Crypto's own services to allow Cayenne to operate over 
plaintext entities even if crypto keys are
diff --git 
a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/batch/CryptoBatchTranslator.java
 
b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/batch/CryptoBatchTranslator.java
new file mode 100644
index 000000000..c27423bcd
--- /dev/null
+++ 
b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/batch/CryptoBatchTranslator.java
@@ -0,0 +1,66 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.crypto.batch;
+
+import org.apache.cayenne.access.translator.ParameterBinding;
+import org.apache.cayenne.access.translator.batch.BatchTranslator;
+import org.apache.cayenne.access.translator.batch.TranslatedBatch;
+import org.apache.cayenne.crypto.transformer.BindingsTransformer;
+import org.apache.cayenne.crypto.transformer.TransformerFactory;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.query.BatchQuery;
+import org.apache.cayenne.query.BatchQueryRow;
+
+/**
+ * @since 5.0
+ */
+public class CryptoBatchTranslator<T extends BatchQuery> implements 
BatchTranslator<T> {
+
+    private final TransformerFactory transformerFactory;
+    private final BatchTranslator<T> delegate;
+
+    public CryptoBatchTranslator(
+            @Inject BatchTranslator<T> delegate,
+            @Inject TransformerFactory transformerFactory) {
+
+        this.transformerFactory = transformerFactory;
+        this.delegate = delegate;
+    }
+
+    @Override
+    public TranslatedBatch translate(T query, DbAdapter adapter) {
+        TranslatedBatch translated = delegate.translate(query, adapter);
+        BindingsTransformer encryptor = 
transformerFactory.encryptor(translated.bindings(), adapter.getExtendedTypes());
+
+        return encryptor != null
+                ? new TranslatedBatch(translated.sql(), translated.bindings(), 
(bindings, row) -> transform(bindings, row, translated, encryptor))
+                : translated;
+    }
+
+    private static ParameterBinding[] transform(
+            ParameterBinding[] bindings,
+            BatchQueryRow row,
+            TranslatedBatch translated,
+            BindingsTransformer encryptor) {
+        ParameterBinding[] updated = translated.binder().bind(bindings, row);
+        encryptor.transform(updated);
+        return updated;
+    }
+}
diff --git 
a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/batch/CryptoBatchTranslatorFactoryDecorator.java
 
b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/batch/CryptoBatchTranslatorFactoryDecorator.java
deleted file mode 100644
index ddc021d6c..000000000
--- 
a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/batch/CryptoBatchTranslatorFactoryDecorator.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.crypto.batch;
-
-import org.apache.cayenne.access.translator.ParameterBinding;
-import org.apache.cayenne.access.translator.batch.BatchTranslator;
-import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
-import org.apache.cayenne.crypto.transformer.BindingsTransformer;
-import org.apache.cayenne.crypto.transformer.TransformerFactory;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.query.BatchQuery;
-import org.apache.cayenne.query.BatchQueryRow;
-
-/**
- * @since 4.0
- */
-public class CryptoBatchTranslatorFactoryDecorator implements 
BatchTranslatorFactory {
-
-    private TransformerFactory cryptoFactory;
-    private BatchTranslatorFactory delegate;
-
-    public CryptoBatchTranslatorFactoryDecorator(@Inject 
BatchTranslatorFactory delegate,
-            @Inject TransformerFactory cryptoFactory) {
-
-        this.cryptoFactory = cryptoFactory;
-        this.delegate = delegate;
-    }
-
-    @Override
-    public BatchTranslator translator(BatchQuery query, final DbAdapter 
adapter, String trimFunction) {
-        final BatchTranslator delegateTranslator = delegate.translator(query, 
adapter, trimFunction);
-
-        return new BatchTranslator() {
-
-            private boolean encryptorCompiled;
-            private BindingsTransformer encryptor;
-
-            private void ensureEncryptorCompiled() {
-                if (!encryptorCompiled) {
-                    encryptor = cryptoFactory.encryptor(getBindings(), 
adapter.getExtendedTypes());
-                    encryptorCompiled = true;
-                }
-            }
-
-            @Override
-            public String getSql() {
-                return delegateTranslator.getSql();
-            }
-
-            @Override
-            public ParameterBinding[] getBindings() {
-                return delegateTranslator.getBindings();
-            }
-
-            @Override
-            public ParameterBinding[] updateBindings(BatchQueryRow row) {
-
-                ensureEncryptorCompiled();
-
-                ParameterBinding[] bindings = 
delegateTranslator.updateBindings(row);
-
-                if (encryptor != null) {
-                    encryptor.transform(bindings);
-                }
-
-                return bindings;
-            }
-        };
-    }
-
-}
diff --git 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/configuration/ToolsModule.java
 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/configuration/ToolsModule.java
index 033d39187..1515f5c14 100644
--- 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/configuration/ToolsModule.java
+++ 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/configuration/ToolsModule.java
@@ -23,8 +23,10 @@ import 
org.apache.cayenne.access.flush.DataDomainFlushActionFactory;
 import org.apache.cayenne.access.flush.DefaultDataDomainFlushActionFactory;
 import org.apache.cayenne.access.flush.operation.DbRowOpSorter;
 import org.apache.cayenne.access.flush.operation.DefaultDbRowOpSorter;
-import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
-import 
org.apache.cayenne.access.translator.batch.DefaultBatchTranslatorFactory;
+import org.apache.cayenne.access.translator.batch.BatchTranslator;
+import org.apache.cayenne.access.translator.batch.DeleteBatchTranslator;
+import org.apache.cayenne.access.translator.batch.InsertBatchTranslator;
+import org.apache.cayenne.access.translator.batch.UpdateBatchTranslator;
 import org.apache.cayenne.access.types.DefaultValueObjectTypeRegistry;
 import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
@@ -153,7 +155,9 @@ public class ToolsModule implements Module {
         binder.bind(Key.get(ResourceLocator.class, 
Constants.RESOURCE_LOCATOR)).to(ClassLoaderResourceLocator.class);
 
         
binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class);
-        
binder.bind(BatchTranslatorFactory.class).to(DefaultBatchTranslatorFactory.class);
+        binder.bind(Key.get(BatchTranslator.class, 
BatchTranslator.INSERT)).to(InsertBatchTranslator.class);
+        binder.bind(Key.get(BatchTranslator.class, 
BatchTranslator.UPDATE)).to(UpdateBatchTranslator.class);
+        binder.bind(Key.get(BatchTranslator.class, 
BatchTranslator.DELETE)).to(DeleteBatchTranslator.class);
         binder.bind(JdbcEventLogger.class).to(Slf4jJdbcEventLogger.class);
         
binder.bind(PkGeneratorFactoryProvider.class).to(PkGeneratorFactoryProvider.class);
         binder.bind(PkGenerator.class).to(JdbcPkGenerator.class);
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/DataNode.java 
b/cayenne/src/main/java/org/apache/cayenne/access/DataNode.java
index 5ba5b83b3..739140ed5 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/DataNode.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataNode.java
@@ -21,24 +21,20 @@ package org.apache.cayenne.access;
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.dbsync.SchemaUpdateStrategy;
-import org.apache.cayenne.access.jdbc.RowDescriptor;
-import org.apache.cayenne.access.translator.sqltemplate.SQLTemplateTranslator;
-import org.apache.cayenne.access.jdbc.reader.RowReader;
 import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
 import org.apache.cayenne.access.translator.batch.BatchTranslator;
-import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
 import org.apache.cayenne.access.translator.select.SelectTranslator;
-import org.apache.cayenne.access.translator.select.TranslatedSelect;
+import org.apache.cayenne.access.translator.sqltemplate.SQLTemplateTranslator;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.JdbcAdapter;
 import org.apache.cayenne.log.JdbcEventLogger;
 import org.apache.cayenne.log.NoopJdbcEventLogger;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.BatchQuery;
+import org.apache.cayenne.query.DeleteBatchQuery;
+import org.apache.cayenne.query.InsertBatchQuery;
 import org.apache.cayenne.query.Query;
-import org.apache.cayenne.query.QueryMetadata;
-import org.apache.cayenne.query.Select;
+import org.apache.cayenne.query.UpdateBatchQuery;
 import org.apache.cayenne.tx.BaseTransaction;
 import org.apache.cayenne.tx.Transaction;
 import org.apache.cayenne.util.ToStringBuilder;
@@ -70,7 +66,9 @@ public class DataNode {
 
        private JdbcEventLogger jdbcEventLogger;
        private RowReaderFactory rowReaderFactory;
-       private BatchTranslatorFactory batchTranslatorFactory;
+       private BatchTranslator<InsertBatchQuery> insertBatchTranslator;
+       private BatchTranslator<UpdateBatchQuery> updateBatchTranslator;
+       private BatchTranslator<DeleteBatchQuery> deleteBatchTranslator;
        private SelectTranslator selectTranslator;
        private SQLTemplateTranslator sqlTemplateTranslator;
 
@@ -316,54 +314,59 @@ public class DataNode {
 
 
        /**
-        * Creates a {@link RowReader} using internal {@link RowReaderFactory}.
-        *
         * @since 4.0
         */
-       public RowReader<?> rowReader(RowDescriptor descriptor, QueryMetadata 
queryMetadata) {
-               return rowReaderFactory.rowReader(descriptor, queryMetadata, 
getAdapter());
+       public RowReaderFactory getRowReaderFactory() {
+               return rowReaderFactory;
        }
 
        /**
         * @since 4.0
         */
-       public BatchTranslator batchTranslator(BatchQuery query, String 
trimFunction) {
-               return batchTranslatorFactory.translator(query, getAdapter(), 
trimFunction);
+       public void setRowReaderFactory(RowReaderFactory rowReaderFactory) {
+               this.rowReaderFactory = rowReaderFactory;
        }
 
        /**
         * @since 5.0
         */
-       public TranslatedSelect translateSelect(Select<?> query) {
-               return selectTranslator.translate(query, getAdapter(), 
getEntityResolver());
+       public BatchTranslator<InsertBatchQuery> getInsertBatchTranslator() {
+               return insertBatchTranslator;
        }
 
        /**
-        * @since 4.0
+        * @since 5.0
         */
-       public RowReaderFactory getRowReaderFactory() {
-               return rowReaderFactory;
+       public void setInsertBatchTranslator(BatchTranslator<InsertBatchQuery> 
insertBatchTranslator) {
+               this.insertBatchTranslator = insertBatchTranslator;
        }
 
        /**
-        * @since 4.0
+        * @since 5.0
         */
-       public void setRowReaderFactory(RowReaderFactory rowReaderFactory) {
-               this.rowReaderFactory = rowReaderFactory;
+       public BatchTranslator<UpdateBatchQuery> getUpdateBatchTranslator() {
+               return updateBatchTranslator;
        }
 
        /**
-        * @since 4.0
+        * @since 5.0
         */
-       public BatchTranslatorFactory getBatchTranslatorFactory() {
-               return batchTranslatorFactory;
+       public void setUpdateBatchTranslator(BatchTranslator<UpdateBatchQuery> 
updateBatchTranslator) {
+               this.updateBatchTranslator = updateBatchTranslator;
        }
 
        /**
-        * @since 4.0
+        * @since 5.0
+        */
+       public BatchTranslator<DeleteBatchQuery> getDeleteBatchTranslator() {
+               return deleteBatchTranslator;
+       }
+
+       /**
+        * @since 5.0
         */
-       public void setBatchTranslatorFactory(BatchTranslatorFactory 
batchTranslatorFactory) {
-               this.batchTranslatorFactory = batchTranslatorFactory;
+       public void setDeleteBatchTranslator(BatchTranslator<DeleteBatchQuery> 
deleteBatchTranslator) {
+               this.deleteBatchTranslator = deleteBatchTranslator;
        }
 
        /**
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BaseSQLAction.java 
b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BaseSQLAction.java
index d81eb258e..1a63b8610 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BaseSQLAction.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BaseSQLAction.java
@@ -56,7 +56,7 @@ public abstract class BaseSQLAction implements SQLAction {
 
         QueryMetadata metadata = 
query.getMetaData(dataNode.getEntityResolver());
 
-        RowReader<?> rowReader = dataNode.rowReader(descriptor, metadata);
+        RowReader<?> rowReader = 
dataNode.getRowReaderFactory().rowReader(descriptor, metadata, 
dataNode.getAdapter());
 
         JDBCResultIterator resultReader = new JDBCResultIterator(null, 
resultSet, rowReader);
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java 
b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java
index 203f07693..ada634f2a 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.access.jdbc;
 
+import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.ResultIterator;
 import org.apache.cayenne.access.DataNode;
@@ -26,7 +27,7 @@ import org.apache.cayenne.access.OperationObserver;
 import org.apache.cayenne.access.OptimisticLockException;
 import org.apache.cayenne.access.jdbc.reader.RowReader;
 import org.apache.cayenne.access.translator.ParameterBinding;
-import org.apache.cayenne.access.translator.batch.BatchTranslator;
+import org.apache.cayenne.access.translator.batch.TranslatedBatch;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.log.JdbcEventLogger;
 import org.apache.cayenne.map.DbAttribute;
@@ -34,7 +35,9 @@ import org.apache.cayenne.map.ObjAttribute;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.query.BatchQuery;
 import org.apache.cayenne.query.BatchQueryRow;
+import org.apache.cayenne.query.DeleteBatchQuery;
 import org.apache.cayenne.query.InsertBatchQuery;
+import org.apache.cayenne.query.UpdateBatchQuery;
 
 import java.sql.Connection;
 import java.sql.PreparedStatement;
@@ -73,15 +76,21 @@ public class BatchAction extends BaseSQLAction {
 
        @Override
        public void performAction(Connection connection, OperationObserver 
observer) throws Exception {
-               BatchTranslator translator = createTranslator();
+               DbAdapter adapter = dataNode.getAdapter();
+               TranslatedBatch translated = switch (query) {
+                       case InsertBatchQuery insert -> 
dataNode.getInsertBatchTranslator().translate(insert, adapter);
+                       case UpdateBatchQuery update -> 
dataNode.getUpdateBatchTranslator().translate(update, adapter);
+                       case DeleteBatchQuery delete -> 
dataNode.getDeleteBatchTranslator().translate(delete, adapter);
+                       case null, default -> throw new 
CayenneRuntimeException("Unsupported batch query: %s", query);
+               };
 
                boolean isBatch = canRunAsBatch();
                boolean generatesKeys = hasGeneratedKeys() && 
supportsGeneratedKeys(isBatch);
 
                if (isBatch) {
-                       runAsBatch(connection, translator, observer, 
generatesKeys);
+                       runAsBatch(connection, translated, observer, 
generatesKeys);
                } else {
-                       runAsIndividualQueries(connection, translator, 
observer, generatesKeys);
+                       runAsIndividualQueries(connection, translated, 
observer, generatesKeys);
                }
        }
 
@@ -99,14 +108,10 @@ public class BatchAction extends BaseSQLAction {
                return true;
        }
 
-       protected BatchTranslator createTranslator() {
-               return dataNode.batchTranslator(query, null);
-       }
-
-       protected void runAsBatch(Connection con, BatchTranslator translator, 
OperationObserver delegate, boolean generatesKeys)
+       protected void runAsBatch(Connection con, TranslatedBatch translated, 
OperationObserver delegate, boolean generatesKeys)
                        throws Exception {
 
-               String sql = translator.getSql();
+               String sql = translated.sql();
                JdbcEventLogger logger = dataNode.getJdbcEventLogger();
                boolean isLoggable = logger.isLoggable();
 
@@ -120,7 +125,7 @@ public class BatchAction extends BaseSQLAction {
                try (PreparedStatement statement = prepareStatement(con, sql, 
adapter, generatesKeys)) {
                        for (BatchQueryRow row : query.getRows()) {
 
-                               ParameterBinding[] bindings = 
translator.updateBindings(row);
+                               ParameterBinding[] bindings = 
translated.updateBindings(row);
                                logger.logQueryParameters("batch bind", 
bindings);
                                for (ParameterBinding b : bindings) {
                                        adapter.bindParameter(statement, b);
@@ -159,7 +164,7 @@ public class BatchAction extends BaseSQLAction {
        /**
         * Executes batch as individual queries over the same prepared 
statement.
         */
-       protected void runAsIndividualQueries(Connection connection, 
BatchTranslator translator,
+       protected void runAsIndividualQueries(Connection connection, 
TranslatedBatch translated,
                        OperationObserver delegate, boolean generatesKeys) 
throws Exception {
 
                if(query.getRows().isEmpty()) {
@@ -169,7 +174,7 @@ public class BatchAction extends BaseSQLAction {
                JdbcEventLogger logger = dataNode.getJdbcEventLogger();
                boolean useOptimisticLock = query.isUsingOptimisticLocking();
 
-               String queryStr = translator.getSql();
+               String queryStr = translated.sql();
 
                // log batch SQL execution
                logger.log(queryStr);
@@ -181,7 +186,7 @@ public class BatchAction extends BaseSQLAction {
                try (PreparedStatement statement = prepareStatement(connection, 
queryStr, adapter, generatesKeys)) {
                        for (BatchQueryRow row : query.getRows()) {
 
-                               ParameterBinding[] bindings = 
translator.updateBindings(row);
+                               ParameterBinding[] bindings = 
translated.updateBindings(row);
                                logger.logQueryParameters("bind", bindings);
 
                                for (ParameterBinding b : bindings) {
@@ -283,7 +288,8 @@ public class BatchAction extends BaseSQLAction {
                        this.keyRowDescriptor = 
builder.getDescriptor(dataNode.getAdapter().getExtendedTypes());
                }
 
-               RowReader<?> rowReader = dataNode.rowReader(keyRowDescriptor, 
query.getMetaData(dataNode.getEntityResolver()));
+               RowReader<?> rowReader = dataNode.getRowReaderFactory()
+                               .rowReader(keyRowDescriptor, 
query.getMetaData(dataNode.getEntityResolver()), dataNode.getAdapter());
                ResultIterator iterator = new JDBCResultIterator(null, keysRS, 
rowReader);
 
                List<ObjectId> objectIds = new ArrayList<>(rows.size());
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java 
b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java
index c4bd62c6d..5f8a22974 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java
@@ -239,7 +239,8 @@ public class SQLTemplateAction implements SQLAction {
         ExtendedTypeMap types = dataNode.getAdapter().getExtendedTypes();
         RowDescriptorBuilder builder = configureRowDescriptorBuilder(compiled, 
resultSet);
         recreateQueryMetadata(resultSet);
-        RowReader<?> rowReader = 
dataNode.rowReader(builder.getDescriptor(types), queryMetadata);
+        RowReader<?> rowReader = dataNode.getRowReaderFactory()
+                .rowReader(builder.getDescriptor(types), queryMetadata, 
dataNode.getAdapter());
         ResultIterator<?> it = new JDBCResultIterator<>(statement, resultSet, 
rowReader);
 
         if (iteratedResult) {
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java 
b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java
index 7553e7aa9..e3e2b5aeb 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java
@@ -79,7 +79,8 @@ public class SelectAction extends BaseSQLAction {
         long t1 = System.currentTimeMillis();
 
         JdbcEventLogger logger = dataNode.getJdbcEventLogger();
-        TranslatedSelect translated = dataNode.translateSelect(query);
+        TranslatedSelect translated = dataNode.getSelectTranslator()
+                .translate(query, dataNode.getAdapter(), 
dataNode.getEntityResolver());
 
         logger.logQuery(translated.sql(), translated.bindings());
 
@@ -111,7 +112,7 @@ public class SelectAction extends BaseSQLAction {
                 .setColumns(translated.resultColumns())
                 .getDescriptor(dataNode.getAdapter().getExtendedTypes());
 
-        RowReader<?> rowReader = dataNode.rowReader(descriptor, queryMetadata);
+        RowReader<?> rowReader = 
dataNode.getRowReaderFactory().rowReader(descriptor, queryMetadata, 
dataNode.getAdapter());
 
         ResultIterator<?> it = new JDBCResultIterator<>(statement, rs, 
rowReader);
         it = forIteratedResult(it, observer, connection, t1, translated.sql());
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java
index d5ab3fa1b..365d3c924 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java
@@ -19,8 +19,6 @@
 
 package org.apache.cayenne.access.translator.batch;
 
-import java.util.List;
-
 import org.apache.cayenne.access.sqlbuilder.ExpressionNodeBuilder;
 import org.apache.cayenne.access.sqlbuilder.NodeBuilder;
 import org.apache.cayenne.access.sqlbuilder.SQLBuilder;
@@ -31,54 +29,56 @@ import 
org.apache.cayenne.access.translator.select.DefaultQuotingAppendable;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.query.BatchQuery;
+import org.apache.cayenne.query.BatchQueryRow;
+
+import java.util.List;
 
 /**
- * @since 4.2
  * @param <T> type of the batch query to translate
+ * @since 4.2
  */
-public abstract class BaseBatchTranslator<T extends BatchQuery> {
+public abstract class BaseBatchTranslator<T extends BatchQuery> implements 
BatchTranslator<T> {
 
-    protected final BatchTranslatorContext<T> context;
+    @Override
+    public TranslatedBatch translate(T query, DbAdapter adapter) {
+        BatchTranslatorContext<T> context = new 
BatchTranslatorContext<>(query, adapter);
 
-    protected ParameterBinding[] bindings;
+        String sql = createSql(context);
+        ParameterBinding[] bindings = createBindings(context);
 
-    public BaseBatchTranslator(T query, DbAdapter adapter) {
-        this.context = new BatchTranslatorContext<>(query, adapter);
+        return new TranslatedBatch(sql, bindings, (b, row) -> 
updateBindings(context, b, row));
     }
 
-    public ParameterBinding[] getBindings() {
-        return bindings;
+    protected abstract String createSql(BatchTranslatorContext<T> context);
+
+    protected ParameterBinding[] createBindings(BatchTranslatorContext<T> 
context) {
+        return context.getBindings().toArray(new ParameterBinding[0]);
     }
 
-    /**
-     * This method applies {@link 
org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor} to the
-     * provided SQL tree node and generates SQL string from it.
-     *
-     * @param nodeBuilder SQL tree node builder
-     * @return SQL string
-     */
-    protected String doTranslate(NodeBuilder nodeBuilder) {
-        Node node = nodeBuilder.build();
-        // convert to database flavour
-        node = context.getAdapter().getSqlTreeProcessor().process(node);
-        // generate SQL
+    protected abstract ParameterBinding[] updateBindings(
+            BatchTranslatorContext<T> context,
+            ParameterBinding[] bindings,
+            BatchQueryRow row);
+
+    protected String doTranslate(BatchTranslatorContext<T> context, 
NodeBuilder nodeBuilder) {
+        Node node = 
context.getAdapter().getSqlTreeProcessor().process(nodeBuilder.build());
+
         SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultQuotingAppendable(context));
         node.visit(visitor);
 
-        bindings = context.getBindings().toArray(new ParameterBinding[0]);
         return visitor.getSQLString();
     }
 
-    abstract protected boolean isNullAttribute(DbAttribute attribute);
+    protected abstract boolean isNullAttribute(BatchTranslatorContext<T> 
context, DbAttribute attribute);
 
-    protected ExpressionNodeBuilder buildQualifier(List<DbAttribute> 
attributeList) {
+    protected ExpressionNodeBuilder buildQualifier(BatchTranslatorContext<T> 
context, List<DbAttribute> attributeList) {
         ExpressionNodeBuilder eq = null;
         for (DbAttribute attr : attributeList) {
-            Integer value = isNullAttribute(attr) ? null : 1;
+            Integer value = isNullAttribute(context, attr) ? null : 1;
             ExpressionNodeBuilder next = SQLBuilder
                     .column(attr.getName()).attribute(attr)
                     .eq(SQLBuilder.value(value).attribute(attr));
-            if(eq == null) {
+            if (eq == null) {
                 eq = next;
             } else {
                 eq = eq.and(next);
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorFactory.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchRowBinder.java
similarity index 66%
rename from 
cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorFactory.java
rename to 
cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchRowBinder.java
index 951ef9482..f0f8a3329 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorFactory.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchRowBinder.java
@@ -16,21 +16,21 @@
  *  specific language governing permissions and limitations
  *  under the License.
  ****************************************************************/
+
 package org.apache.cayenne.access.translator.batch;
 
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.query.BatchQuery;
+import org.apache.cayenne.access.translator.ParameterBinding;
+import org.apache.cayenne.query.BatchQueryRow;
 
 /**
- * Factory which creates BatchQueryBuilders for different types of queries,
- * which, in their turn, create SQL strings for batch queries.
- * 
- * @since 4.0
+ * A stateless strategy that populates a batch query's binding template with 
the values of a single
+ * {@link BatchQueryRow}. The same template array is reused for every row, so 
the returned array is the
+ * one passed in, only with the per-row state applied.
+ *
+ * @since 5.0
  */
-public interface BatchTranslatorFactory {
+@FunctionalInterface
+public interface BatchRowBinder {
 
-    /**
-     * Creates a proper translator for a BatchQuery
-     */
-    BatchTranslator translator(BatchQuery query, DbAdapter adapter, String 
trimFunction);
+    ParameterBinding[] bind(ParameterBinding[] bindings, BatchQueryRow row);
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslator.java
index 73a75fdbc..d6150c1b0 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslator.java
@@ -19,33 +19,45 @@
 
 package org.apache.cayenne.access.translator.batch;
 
-import org.apache.cayenne.access.translator.ParameterBinding;
-import org.apache.cayenne.query.BatchQueryRow;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.query.BatchQuery;
 
 /**
- * Superclass of batch query translators.
- * 
+ * A stateless service that translates a batch query of type {@code T} into an 
immutable
+ * {@link TranslatedBatch}. Each batch query flavor (insert, update, delete) 
is handled by its own
+ * translator, configured individually in the DI container under the {@link 
#INSERT}, {@link #UPDATE}
+ * and {@link #DELETE} binding names.
+ *
+ * @param <T> type of the batch query to translate
  * @since 4.0
  */
-public interface BatchTranslator {
+public interface BatchTranslator<T extends BatchQuery> {
+
+    /**
+     * DI binding name of the translator for {@link 
org.apache.cayenne.query.InsertBatchQuery}.
+     *
+     * @since 5.0
+     */
+    String INSERT = "insert";
 
     /**
-     * Returns SQL String that can be used to init a PreparedStatement.
+     * DI binding name of the translator for {@link 
org.apache.cayenne.query.UpdateBatchQuery}.
+     *
+     * @since 5.0
      */
-    String getSql();
+    String UPDATE = "update";
 
     /**
-     * Returns the widest possible array of bindings for this query.
+     * DI binding name of the translator for {@link 
org.apache.cayenne.query.DeleteBatchQuery}.
+     *
+     * @since 5.0
      */
-    ParameterBinding[] getBindings();
+    String DELETE = "delete";
 
     /**
-     * Updates internal bindings to be used with a given row, returning updated
-     * bindings array. This method guarantees that the returned array contains
-     * the same bindings in the same order as in the array returned from
-     * {@link #getBindings()} (but in a state corresponding to the 'row'
-     * parameter). Usually the returned array is actually the same object 
reused
-     * for every iteration, only with changed object state.
+     * Translates the provided batch query into an immutable {@link 
TranslatedBatch}.
+     *
+     * @since 5.0
      */
-    ParameterBinding[] updateBindings(BatchQueryRow row);
+    TranslatedBatch translate(T query, DbAdapter adapter);
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslatorFactory.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslatorFactory.java
deleted file mode 100644
index 56ff01969..000000000
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslatorFactory.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.access.translator.batch;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.query.BatchQuery;
-import org.apache.cayenne.query.DeleteBatchQuery;
-import org.apache.cayenne.query.InsertBatchQuery;
-import org.apache.cayenne.query.UpdateBatchQuery;
-
-/**
- * Default implementation of {@link BatchTranslatorFactory}.
- * 
- * @since 4.2
- */
-public class DefaultBatchTranslatorFactory implements BatchTranslatorFactory {
-
-    @Override
-    public BatchTranslator translator(BatchQuery query, DbAdapter adapter, 
String trimFunction) {
-        return switch (query) {
-            case InsertBatchQuery insertBatchQuery -> 
insertTranslator(insertBatchQuery, adapter);
-            case UpdateBatchQuery updateBatchQuery -> 
updateTranslator(updateBatchQuery, adapter);
-            case DeleteBatchQuery deleteBatchQuery -> 
deleteTranslator(deleteBatchQuery, adapter);
-            case null, default -> throw new 
CayenneRuntimeException("Unsupported batch query: %s", query);
-        };
-    }
-
-    protected BatchTranslator deleteTranslator(DeleteBatchQuery query, 
DbAdapter adapter) {
-        return new DeleteBatchTranslator(query, adapter);
-    }
-
-    protected BatchTranslator insertTranslator(InsertBatchQuery query, 
DbAdapter adapter) {
-        return new InsertBatchTranslator(query, adapter);
-    }
-
-    protected BatchTranslator updateTranslator(UpdateBatchQuery query, 
DbAdapter adapter) {
-        return new UpdateBatchTranslator(query, adapter);
-    }
-
-}
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java
index 414b86cbb..830a3e7af 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java
@@ -23,7 +23,6 @@ import org.apache.cayenne.access.sqlbuilder.DeleteBuilder;
 import org.apache.cayenne.access.sqlbuilder.SQLBuilder;
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.types.ExtendedType;
-import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.query.BatchQueryRow;
 import org.apache.cayenne.query.DeleteBatchQuery;
@@ -31,37 +30,41 @@ import org.apache.cayenne.query.DeleteBatchQuery;
 /**
  * @since 4.2
  */
-public class DeleteBatchTranslator extends 
BaseBatchTranslator<DeleteBatchQuery> implements BatchTranslator {
-
-    public DeleteBatchTranslator(DeleteBatchQuery query, DbAdapter adapter) {
-        super(query, adapter);
-    }
+public class DeleteBatchTranslator extends 
BaseBatchTranslator<DeleteBatchQuery> {
 
     @Override
-    public String getSql() {
+    protected String createSql(BatchTranslatorContext<DeleteBatchQuery> 
context) {
         DeleteBuilder deleteBuilder = SQLBuilder
                 .delete(context.getRootDbEntity())
-                .where(buildQualifier(context.getQuery().getDbAttributes()));
-        return doTranslate(deleteBuilder);
+                .where(buildQualifier(context, 
context.getQuery().getDbAttributes()));
+        return doTranslate(context, deleteBuilder);
     }
 
     @Override
-    protected boolean isNullAttribute(DbAttribute attribute) {
+    protected boolean isNullAttribute(BatchTranslatorContext<DeleteBatchQuery> 
context, DbAttribute attribute) {
         return context.getQuery().isNull(attribute);
     }
 
     @Override
-    public ParameterBinding[] updateBindings(BatchQueryRow row) {
+    protected ParameterBinding[] updateBindings(
+            BatchTranslatorContext<DeleteBatchQuery> context,
+            ParameterBinding[] bindings,
+            BatchQueryRow row) {
+
         DeleteBatchQuery deleteBatch = context.getQuery();
-        for(int i=0, position=0; i<deleteBatch.getDbAttributes().size(); i++) {
-            position = updateBinding(row.getValue(i), position);
+        for (int i = 0, position = 0; i < 
deleteBatch.getDbAttributes().size(); i++) {
+            position = updateBinding(context, bindings, row.getValue(i), 
position);
         }
         return bindings;
     }
 
-    protected int updateBinding(Object value, int position) {
+    protected int updateBinding(
+            BatchTranslatorContext<DeleteBatchQuery> context,
+            ParameterBinding[] bindings,
+            Object value, int position) {
+        
         // skip null attributes... they are translated as "IS NULL"
-        if(value != null) {
+        if (value != null) {
             ExtendedType<?> extendedType = 
context.getAdapter().getExtendedTypes().getRegisteredType(value.getClass());
             bindings[position].reset(++position, value, extendedType);
         }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java
index 2330ef872..f67545e26 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java
@@ -23,7 +23,6 @@ import org.apache.cayenne.access.sqlbuilder.InsertBuilder;
 import org.apache.cayenne.access.sqlbuilder.SQLBuilder;
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.types.ExtendedType;
-import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.query.BatchQueryRow;
 import org.apache.cayenne.query.InsertBatchQuery;
@@ -31,20 +30,16 @@ import org.apache.cayenne.query.InsertBatchQuery;
 /**
  * @since 4.2
  */
-public class InsertBatchTranslator extends 
BaseBatchTranslator<InsertBatchQuery> implements BatchTranslator {
-
-    public InsertBatchTranslator(InsertBatchQuery query, DbAdapter adapter) {
-        super(query, adapter);
-    }
+public class InsertBatchTranslator extends 
BaseBatchTranslator<InsertBatchQuery> {
 
     @Override
-    public String getSql() {
+    protected String createSql(BatchTranslatorContext<InsertBatchQuery> 
context) {
         InsertBatchQuery query = context.getQuery();
         InsertBuilder insertBuilder = 
SQLBuilder.insert(context.getRootDbEntity());
 
         for(DbAttribute attribute : query.getDbAttributes()) {
             // skip generated attributes, if needed
-            if(excludeInBatch(attribute)) {
+            if(excludeInBatch(context, attribute)) {
                 continue;
             }
             insertBuilder
@@ -54,16 +49,17 @@ public class InsertBatchTranslator extends 
BaseBatchTranslator<InsertBatchQuery>
                     .value(SQLBuilder.value(1).attribute(attribute));
         }
 
-        return doTranslate(insertBuilder);
+        return doTranslate(context, insertBuilder);
     }
 
     @Override
-    public ParameterBinding[] updateBindings(BatchQueryRow row) {
+    protected ParameterBinding[] 
updateBindings(BatchTranslatorContext<InsertBatchQuery> context,
+                                                ParameterBinding[] bindings, 
BatchQueryRow row) {
         InsertBatchQuery query = context.getQuery();
         int i=0;
         int j=0;
         for(DbAttribute attribute : query.getDbAttributes()) {
-            if(excludeInBatch(attribute)) {
+            if(excludeInBatch(context, attribute)) {
                 i++;
                 continue;
             }
@@ -77,7 +73,7 @@ public class InsertBatchTranslator extends 
BaseBatchTranslator<InsertBatchQuery>
         return bindings;
     }
 
-    protected boolean excludeInBatch(DbAttribute attribute) {
+    protected boolean excludeInBatch(BatchTranslatorContext<InsertBatchQuery> 
context, DbAttribute attribute) {
         // attribute inclusion rule - one of the rules below must be true:
         //  (1) attribute not generated
         //  (2) attribute is generated and PK and adapter does not support 
generated keys
@@ -85,7 +81,7 @@ public class InsertBatchTranslator extends 
BaseBatchTranslator<InsertBatchQuery>
     }
 
     @Override
-    protected boolean isNullAttribute(DbAttribute attribute) {
+    protected boolean isNullAttribute(BatchTranslatorContext<InsertBatchQuery> 
context, DbAttribute attribute) {
         return false;
     }
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java
index d866fdadf..ced812e00 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java
@@ -23,51 +23,85 @@ import org.apache.cayenne.access.sqlbuilder.SQLBuilder;
 import org.apache.cayenne.access.sqlbuilder.UpdateBuilder;
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.types.ExtendedType;
-import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.query.BatchQueryRow;
 import org.apache.cayenne.query.DeleteBatchQuery;
 
+import java.sql.Types;
+
 import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.*;
 
 /**
+ * A delete {@link BatchTranslator} that performs a 'soft' delete (an UPDATE 
setting the 'deleted' field
+ * to true) for entities that have a boolean 'deleted' field, and falls back 
to the regular SQL DELETE of
+ * the {@link DeleteBatchTranslator} superclass for the rest. Bind it under 
the {@link BatchTranslator#DELETE}
+ * name to enable soft deletes.
+ *
  * @since 4.2
  */
 public class SoftDeleteBatchTranslator extends DeleteBatchTranslator {
 
+    public static final String DEFAULT_DELETED_FIELD_NAME = "DELETED";
+
     private final String deletedFieldName;
 
-    public SoftDeleteBatchTranslator(DeleteBatchQuery query, DbAdapter 
adapter, String deletedFieldName) {
-        super(query, adapter);
+    public SoftDeleteBatchTranslator() {
+        this(DEFAULT_DELETED_FIELD_NAME);
+    }
+
+    public SoftDeleteBatchTranslator(String deletedFieldName) {
         this.deletedFieldName = deletedFieldName;
     }
 
+    protected boolean isHardDelete(DeleteBatchQuery query) {
+        DbAttribute attr = query.getDbEntity().getAttribute(deletedFieldName);
+        return attr == null || attr.getType() != Types.BOOLEAN;
+    }
+
     @Override
-    public String getSql() {
+    protected String createSql(BatchTranslatorContext<DeleteBatchQuery> 
context) {
         DeleteBatchQuery query = context.getQuery();
-        DbAttribute deleteAttribute = 
query.getDbEntity().getAttribute(deletedFieldName);
+        if (isHardDelete(query)) {
+            return super.createSql(context);
+        }
 
+        DbAttribute deleteAttribute = 
query.getDbEntity().getAttribute(deletedFieldName);
         UpdateBuilder updateBuilder = update(context.getRootDbEntity())
                 .set(column(deletedFieldName).attribute(deleteAttribute)
                         .eq(SQLBuilder.value(true).attribute(deleteAttribute)))
-                .where(buildQualifier(query.getDbAttributes()));
+                .where(buildQualifier(context, query.getDbAttributes()));
+
+        return doTranslate(context, updateBuilder);
+    }
 
-        String sql = doTranslate(updateBuilder);
+    @Override
+    protected ParameterBinding[] 
createBindings(BatchTranslatorContext<DeleteBatchQuery> context) {
+        ParameterBinding[] bindings = super.createBindings(context);
+        if (isHardDelete(context.getQuery())) {
+            return bindings;
+        }
 
+        // the 'deleted' flag is the first binding and stays constant across 
all rows of the batch
+        DbAttribute deleteAttribute = 
context.getQuery().getDbEntity().getAttribute(deletedFieldName);
         String typeName = TypesMapping.getJavaBySqlType(deleteAttribute);
         ExtendedType<?> extendedType = 
context.getAdapter().getExtendedTypes().getRegisteredType(typeName);
         bindings[0].reset(1, true, extendedType);
 
-        return sql;
+        return bindings;
     }
 
     @Override
-    public ParameterBinding[] updateBindings(BatchQueryRow row) {
+    protected ParameterBinding[] 
updateBindings(BatchTranslatorContext<DeleteBatchQuery> context,
+                                                ParameterBinding[] bindings, 
BatchQueryRow row) {
         DeleteBatchQuery deleteBatch = context.getQuery();
+        if (isHardDelete(deleteBatch)) {
+            return super.updateBindings(context, bindings, row);
+        }
 
+        // bindings[0] holds the constant 'deleted' flag, so qualifier values 
start at position 1
         for(int i=0, position=1; i<deleteBatch.getDbAttributes().size(); i++) {
-            position = updateBinding(row.getValue(i), position);
+            position = updateBinding(context, bindings, row.getValue(i), 
position);
         }
 
         return bindings;
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteTranslatorFactory.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteTranslatorFactory.java
deleted file mode 100644
index c0eec5aad..000000000
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteTranslatorFactory.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.access.translator.batch;
-
-import java.sql.Types;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.query.DeleteBatchQuery;
-
-/**
- * Implementation of {link #BatchTranslator}, which uses 'soft' delete
- * (runs UPDATE and sets 'deleted' field to true instead-of running SQL DELETE)
- * 
- * @since 4.2
- */
-public class SoftDeleteTranslatorFactory extends DefaultBatchTranslatorFactory 
{
-    /**
-     * Default name of 'deleted' field
-     */
-    public static final String DEFAULT_DELETED_FIELD_NAME = "DELETED";
-
-    /**
-     * Name of 'deleted' field
-     */
-    private String deletedFieldName;
-
-    public SoftDeleteTranslatorFactory() {
-        this(DEFAULT_DELETED_FIELD_NAME);
-    }
-
-    public SoftDeleteTranslatorFactory(String deletedFieldName) {
-        this.deletedFieldName = deletedFieldName;
-    }
-
-    @Override
-    protected BatchTranslator deleteTranslator(DeleteBatchQuery query, 
DbAdapter adapter) {
-
-        DbAttribute attr = query.getDbEntity().getAttribute(deletedFieldName);
-        boolean needsSoftDelete = attr != null && attr.getType() == 
Types.BOOLEAN;
-
-        return needsSoftDelete
-                ? new SoftDeleteBatchTranslator(query, adapter, 
deletedFieldName)
-                : super.deleteTranslator(query, adapter);
-    }
-
-    /**
-     * @return name of 'deleted' field
-     */
-    public String getDeletedFieldName() {
-        return deletedFieldName;
-    }
-}
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleBatchAction.java 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/TranslatedBatch.java
similarity index 57%
rename from 
cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleBatchAction.java
rename to 
cayenne/src/main/java/org/apache/cayenne/access/translator/batch/TranslatedBatch.java
index f70d2a95e..4c81af7b2 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleBatchAction.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/TranslatedBatch.java
@@ -17,27 +17,24 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.dba.oracle;
+package org.apache.cayenne.access.translator.batch;
 
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.jdbc.BatchAction;
-import org.apache.cayenne.access.translator.batch.BatchTranslator;
-import org.apache.cayenne.query.BatchQuery;
+import org.apache.cayenne.access.translator.ParameterBinding;
+import org.apache.cayenne.query.BatchQueryRow;
 
 /**
- * @since 1.2
+ * An immutable result of translating a batch query: the SQL String shared by 
all rows of the batch,
+ * the widest possible array of parameter bindings, and a stateless {@link 
BatchRowBinder} that applies
+ * a single row's state to those bindings.
+ *
+ * @since 5.0
  */
-class OracleBatchAction extends BatchAction {
+public record TranslatedBatch(String sql, ParameterBinding[] bindings, 
BatchRowBinder binder) {
 
     /**
-     * @since 4.0
+     * Applies the given row's state to the binding template, returning the 
(reused) bindings array.
      */
-    OracleBatchAction(BatchQuery batchQuery, DataNode dataNode, boolean 
runningAsBatch) {
-        super(batchQuery, dataNode, runningAsBatch);
-    }
-
-    @Override
-    protected BatchTranslator createTranslator() {
-        return dataNode.batchTranslator(query, OracleAdapter.TRIM_FUNCTION);
+    public ParameterBinding[] updateBindings(BatchQueryRow row) {
+        return binder.bind(bindings, row);
     }
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java
index 6c9eca408..7adf35a0a 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java
@@ -23,7 +23,6 @@ import org.apache.cayenne.access.sqlbuilder.SQLBuilder;
 import org.apache.cayenne.access.sqlbuilder.UpdateBuilder;
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.types.ExtendedType;
-import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.query.BatchQueryRow;
 import org.apache.cayenne.query.UpdateBatchQuery;
@@ -31,14 +30,10 @@ import org.apache.cayenne.query.UpdateBatchQuery;
 /**
  * @since 4.2
  */
-public class UpdateBatchTranslator extends 
BaseBatchTranslator<UpdateBatchQuery> implements BatchTranslator {
-
-    public UpdateBatchTranslator(UpdateBatchQuery query, DbAdapter adapter) {
-        super(query, adapter);
-    }
+public class UpdateBatchTranslator extends 
BaseBatchTranslator<UpdateBatchQuery> {
 
     @Override
-    public String getSql() {
+    protected String createSql(BatchTranslatorContext<UpdateBatchQuery> 
context) {
         UpdateBatchQuery query = context.getQuery();
 
         UpdateBuilder updateBuilder = 
SQLBuilder.update(context.getRootDbEntity());
@@ -48,18 +43,19 @@ public class UpdateBatchTranslator extends 
BaseBatchTranslator<UpdateBatchQuery>
                     .eq(SQLBuilder.value(1).attribute(attr))
             );
         }
-        updateBuilder.where(buildQualifier(query.getQualifierAttributes()));
+        updateBuilder.where(buildQualifier(context, 
query.getQualifierAttributes()));
 
-        return doTranslate(updateBuilder);
+        return doTranslate(context, updateBuilder);
     }
 
     @Override
-    protected boolean isNullAttribute(DbAttribute attribute) {
+    protected boolean isNullAttribute(BatchTranslatorContext<UpdateBatchQuery> 
context, DbAttribute attribute) {
         return context.getQuery().isNull(attribute);
     }
 
     @Override
-    public ParameterBinding[] updateBindings(BatchQueryRow row) {
+    protected ParameterBinding[] 
updateBindings(BatchTranslatorContext<UpdateBatchQuery> context,
+                                                ParameterBinding[] bindings, 
BatchQueryRow row) {
         UpdateBatchQuery updateBatch = context.getQuery();
 
         int i = 0;
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslator.java
deleted file mode 100644
index aa8435253..000000000
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslator.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.access.translator.batch.legacy;
-
-import java.sql.Types;
-
-import org.apache.cayenne.access.translator.ParameterBinding;
-import org.apache.cayenne.access.translator.batch.BatchTranslator;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.query.BatchQuery;
-import org.apache.cayenne.query.BatchQueryRow;
-
-/**
- * Superclass of batch query translators.
- * 
- * @since 4.0
- * @deprecated since 4.2
- */
-@Deprecated(since = "4.2")
-public abstract class DefaultBatchTranslator implements BatchTranslator {
-
-    protected BatchQuery query;
-    protected DbAdapter adapter;
-    protected String trimFunction;
-
-    protected boolean translated;
-    protected String sql;
-    protected ParameterBinding[] bindings;
-
-    public DefaultBatchTranslator(BatchQuery query, DbAdapter adapter, String 
trimFunction) {
-        this.query = query;
-        this.adapter = adapter;
-        this.trimFunction = trimFunction;
-    }
-
-    protected void ensureTranslated() {
-        if (!translated) {
-            this.sql = createSql();
-            this.bindings = createBindings();
-            translated = true;
-        }
-    }
-
-    /**
-     * Translates BatchQuery into an SQL string formatted to use in a
-     * PreparedStatement.
-     */
-    @Override
-    public String getSql() {
-        ensureTranslated();
-        return sql;
-    }
-
-    @Override
-    public ParameterBinding[] getBindings() {
-        ensureTranslated();
-        return bindings;
-    }
-    
-    @Override
-    public ParameterBinding[] updateBindings(BatchQueryRow row) {
-        ensureTranslated();
-        return doUpdateBindings(row);
-    }
-
-    protected abstract String createSql();
-
-    protected abstract ParameterBinding[] createBindings();
-    
-    protected abstract ParameterBinding[] doUpdateBindings(BatchQueryRow row);
-
-    /**
-     * Appends the name of the column to the query buffer. Subclasses use this
-     * method to append column names in the WHERE clause, i.e. for the columns
-     * that are not being updated.
-     */
-    protected void appendDbAttribute(StringBuilder buf, DbAttribute 
dbAttribute) {
-
-        // TODO: (Andrus) is there a need for trimming binary types?
-        boolean trim = dbAttribute.getType() == Types.CHAR && trimFunction != 
null;
-        if (trim) {
-            buf.append(trimFunction).append('(');
-        }
-
-        QuotingStrategy strategy = adapter.getQuotingStrategy();
-
-        buf.append(strategy.quotedName(dbAttribute));
-
-        if (trim) {
-            buf.append(')');
-        }
-    }
-
-}
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java
 
b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java
index 50dc4f8df..562ba503c 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java
@@ -38,8 +38,10 @@ import 
org.apache.cayenne.access.flush.operation.DefaultDbRowOpSorter;
 import org.apache.cayenne.access.translator.sqltemplate.SQLTemplateTranslator;
 import org.apache.cayenne.access.jdbc.reader.DefaultRowReaderFactory;
 import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
-import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
-import 
org.apache.cayenne.access.translator.batch.DefaultBatchTranslatorFactory;
+import org.apache.cayenne.access.translator.batch.BatchTranslator;
+import org.apache.cayenne.access.translator.batch.DeleteBatchTranslator;
+import org.apache.cayenne.access.translator.batch.InsertBatchTranslator;
+import org.apache.cayenne.access.translator.batch.UpdateBatchTranslator;
 import 
org.apache.cayenne.access.translator.select.DbAdapterDelegatedSelectTranslator;
 import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.*;
@@ -461,7 +463,9 @@ public class CoreModule implements Module {
         // configured by the owning domain
         
binder.bind(EntitySorter.class).to(AshwoodEntitySorter.class).withoutScope();
 
-        
binder.bind(BatchTranslatorFactory.class).to(DefaultBatchTranslatorFactory.class);
+        binder.bind(Key.get(BatchTranslator.class, 
BatchTranslator.INSERT)).to(InsertBatchTranslator.class);
+        binder.bind(Key.get(BatchTranslator.class, 
BatchTranslator.UPDATE)).to(UpdateBatchTranslator.class);
+        binder.bind(Key.get(BatchTranslator.class, 
BatchTranslator.DELETE)).to(DeleteBatchTranslator.class);
         
binder.bind(SelectTranslator.class).to(DbAdapterDelegatedSelectTranslator.class);
 
         // a default ObjectMapRetainStrategy used to create objects map for
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/DefaultDataNodeFactory.java
 
b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/DefaultDataNodeFactory.java
index 765e3a2e7..fa818cd2e 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/DefaultDataNodeFactory.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/DefaultDataNodeFactory.java
@@ -22,8 +22,11 @@ import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.dbsync.SchemaUpdateStrategyFactory;
 import org.apache.cayenne.access.translator.sqltemplate.SQLTemplateTranslator;
 import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
-import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
+import org.apache.cayenne.access.translator.batch.BatchTranslator;
 import org.apache.cayenne.access.translator.select.SelectTranslator;
+import org.apache.cayenne.query.DeleteBatchQuery;
+import org.apache.cayenne.query.InsertBatchQuery;
+import org.apache.cayenne.query.UpdateBatchQuery;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Inject;
@@ -45,9 +48,15 @@ public class DefaultDataNodeFactory implements 
DataNodeFactory {
     @Inject
     protected DataSourceFactory dataSourceFactory;
 
-    @Inject
-    protected BatchTranslatorFactory batchTranslatorFactory;
-    
+    @Inject(BatchTranslator.INSERT)
+    protected BatchTranslator<InsertBatchQuery> insertBatchTranslator;
+
+    @Inject(BatchTranslator.UPDATE)
+    protected BatchTranslator<UpdateBatchQuery> updateBatchTranslator;
+
+    @Inject(BatchTranslator.DELETE)
+    protected BatchTranslator<DeleteBatchQuery> deleteBatchTranslator;
+
     @Inject
     protected SelectTranslator selectTranslator;
 
@@ -70,7 +79,9 @@ public class DefaultDataNodeFactory implements 
DataNodeFactory {
 
         dataNode.setJdbcEventLogger(jdbcEventLogger);
         dataNode.setRowReaderFactory(rowReaderFactory);
-        dataNode.setBatchTranslatorFactory(batchTranslatorFactory);
+        dataNode.setInsertBatchTranslator(insertBatchTranslator);
+        dataNode.setUpdateBatchTranslator(updateBatchTranslator);
+        dataNode.setDeleteBatchTranslator(deleteBatchTranslator);
         dataNode.setSelectTranslator(selectTranslator);
         dataNode.setSqlTemplateTranslator(sqlTemplateTranslator);
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleActionBuilder.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleActionBuilder.java
index 94fa21547..02537b827 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleActionBuilder.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleActionBuilder.java
@@ -21,7 +21,6 @@ package org.apache.cayenne.dba.oracle;
 
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.dba.JdbcActionBuilder;
-import org.apache.cayenne.query.BatchQuery;
 import org.apache.cayenne.query.FluentSelect;
 import org.apache.cayenne.query.ProcedureQuery;
 import org.apache.cayenne.query.SQLAction;
@@ -41,18 +40,6 @@ class OracleActionBuilder extends JdbcActionBuilder {
         return new OracleSQLTemplateAction(query, dataNode);
     }
 
-    @Override
-    public SQLAction batchAction(BatchQuery query) {
-
-        // optimistic locking is not supported in batches due to JDBC driver
-        // limitations
-        // TODO: is this still true with ojdbc6.jar?
-        boolean useOptimisticLock = query.isUsingOptimisticLocking();
-        boolean runningAsBatch = !useOptimisticLock && 
dataNode.getAdapter().supportsBatchUpdates();
-
-        return new OracleBatchAction(query, dataNode, runningAsBatch);
-    }
-
     @Override
     public SQLAction procedureAction(ProcedureQuery query) {
         return new OracleProcedureAction(query, dataNode);
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/jdbc/BatchActionLockingIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/jdbc/BatchActionLockingIT.java
index 87fb2996d..5ecf042d6 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/jdbc/BatchActionLockingIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/jdbc/BatchActionLockingIT.java
@@ -24,6 +24,7 @@ import org.apache.cayenne.access.MockOperationObserver;
 import org.apache.cayenne.access.OptimisticLockException;
 import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
 import org.apache.cayenne.access.translator.batch.DeleteBatchTranslator;
+import org.apache.cayenne.access.translator.batch.TranslatedBatch;
 import org.apache.cayenne.dba.JdbcAdapter;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
@@ -72,7 +73,7 @@ public class BatchActionLockingIT {
                batchQuery.setUsingOptimisticLocking(true);
                batchQuery.add(qualifierSnapshot);
 
-               DeleteBatchTranslator batchQueryBuilder = new 
DeleteBatchTranslator(batchQuery, adapter);
+               TranslatedBatch translated = new 
DeleteBatchTranslator().translate(batchQuery, adapter);
 
                TestConnection mockConnection = new TestConnection();
                mockConnection.prepareUpdateCount("DELETE", 1);
@@ -84,7 +85,7 @@ public class BatchActionLockingIT {
                node.setEntityResolver(resolver);
                node.setRowReaderFactory(mock(RowReaderFactory.class));
                BatchAction action = new BatchAction(batchQuery, node, false);
-               action.runAsIndividualQueries(mockConnection, 
batchQueryBuilder, new MockOperationObserver(), generatesKeys);
+               action.runAsIndividualQueries(mockConnection, translated, new 
MockOperationObserver(), generatesKeys);
                assertEquals(0, mockConnection.getNumberCommits());
                assertEquals(0, mockConnection.getNumberRollbacks());
        }
@@ -110,7 +111,7 @@ public class BatchActionLockingIT {
                batchQuery.setUsingOptimisticLocking(true);
                batchQuery.add(qualifierSnapshot);
 
-               DeleteBatchTranslator batchQueryBuilder = new 
DeleteBatchTranslator(batchQuery, adapter);
+               TranslatedBatch translated = new 
DeleteBatchTranslator().translate(batchQuery, adapter);
 
                TestConnection mockConnection = new TestConnection();
                mockConnection.prepareUpdateCount("DELETE", 0);
@@ -122,7 +123,7 @@ public class BatchActionLockingIT {
                node.setRowReaderFactory(mock(RowReaderFactory.class));
                BatchAction action = new BatchAction(batchQuery, node, false);
                assertThrows(OptimisticLockException.class, () ->
-                       action.runAsIndividualQueries(mockConnection, 
batchQueryBuilder, new MockOperationObserver(), generatesKeys));
+                       action.runAsIndividualQueries(mockConnection, 
translated, new MockOperationObserver(), generatesKeys));
                assertEquals(0, mockConnection.getNumberCommits());
                assertEquals(0, mockConnection.getNumberRollbacks());
        }
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslatorIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslatorIT.java
index 6814d1f8d..ba646a9a1 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslatorIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslatorIT.java
@@ -40,7 +40,6 @@ import java.util.Collections;
 import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.mock;
 
 public class DeleteBatchTranslatorIT {
 
@@ -61,17 +60,6 @@ public class DeleteBatchTranslatorIT {
         objectFactory = env.adhocObjectFactory();
     }
 
-    @Test
-    public void constructor() {
-        DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, 
JdbcAdapter.class.getName());
-
-        DeleteBatchQuery query = mock(DeleteBatchQuery.class);
-        DeleteBatchTranslator builder = new DeleteBatchTranslator(query, 
adapter);
-
-        assertSame(adapter, builder.context.getAdapter());
-        assertSame(query, builder.context.getQuery());
-    }
-
     @Test
     public void createSqlString() {
         DbEntity entity = 
runtime.getDataDomain().getEntityResolver().getObjEntity(SimpleLockingTestEntity.class)
@@ -82,8 +70,7 @@ public class DeleteBatchTranslatorIT {
         DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, 
idAttributes, Collections.emptySet(), 1);
 
         DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, 
JdbcAdapter.class.getName());
-        DeleteBatchTranslator builder = new DeleteBatchTranslator(deleteQuery, 
adapter);
-        String generatedSql = builder.getSql();
+        String generatedSql = new 
DeleteBatchTranslator().translate(deleteQuery, adapter).sql();
         assertNotNull(generatedSql);
         assertEquals("DELETE FROM " + entity.getName() + " WHERE 
LOCKING_TEST_ID = ?", generatedSql);
     }
@@ -101,8 +88,7 @@ public class DeleteBatchTranslatorIT {
         DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, 
idAttributes, nullAttributes, 1);
 
         DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, 
JdbcAdapter.class.getName());
-        DeleteBatchTranslator builder = new DeleteBatchTranslator(deleteQuery, 
adapter);
-        String generatedSql = builder.getSql();
+        String generatedSql = new 
DeleteBatchTranslator().translate(deleteQuery, adapter).sql();
         assertNotNull(generatedSql);
         assertEquals("DELETE FROM " + entity.getName() + " WHERE ( 
LOCKING_TEST_ID = ? ) AND ( NAME IS NULL )", generatedSql);
     }
@@ -117,8 +103,7 @@ public class DeleteBatchTranslatorIT {
             List<DbAttribute> idAttributes = 
Collections.singletonList(entity.getAttribute("LOCKING_TEST_ID"));
 
             DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, 
idAttributes, Collections.emptySet(), 1);
-            DeleteBatchTranslator builder = new 
DeleteBatchTranslator(deleteQuery, adapter);
-            String generatedSql = builder.getSql();
+            String generatedSql = new 
DeleteBatchTranslator().translate(deleteQuery, adapter).sql();
 
             String charStart = unitAdapter.getIdentifiersStartQuote();
             String charEnd = unitAdapter.getIdentifiersEndQuote();
@@ -147,8 +132,7 @@ public class DeleteBatchTranslatorIT {
 
             DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, 
idAttributes, nullAttributes, 1);
 
-            DeleteBatchTranslator builder = new 
DeleteBatchTranslator(deleteQuery, adapter);
-            String generatedSql = builder.getSql();
+            String generatedSql = new 
DeleteBatchTranslator().translate(deleteQuery, adapter).sql();
 
             String charStart = unitAdapter.getIdentifiersStartQuote();
             String charEnd = unitAdapter.getIdentifiersEndQuote();
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslatorIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslatorIT.java
index 4efb2566f..fcd4e7a9f 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslatorIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslatorIT.java
@@ -34,8 +34,6 @@ import org.junit.jupiter.api.BeforeEach;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.mockito.Mockito.mock;
 
 public class InsertBatchTranslatorIT {
 
@@ -56,17 +54,6 @@ public class InsertBatchTranslatorIT {
         objectFactory = env.adhocObjectFactory();
     }
 
-    @Test
-    public void constructor() {
-        DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, 
JdbcAdapter.class.getName());
-
-        InsertBatchQuery query = mock(InsertBatchQuery.class);
-        InsertBatchTranslator builder = new InsertBatchTranslator(query, 
adapter);
-
-        assertSame(adapter, builder.context.getAdapter());
-        assertSame(query, builder.context.getQuery());
-    }
-
     @Test
     public void createSqlString() {
         DbEntity entity = runtime.getDataDomain().getEntityResolver()
@@ -75,8 +62,7 @@ public class InsertBatchTranslatorIT {
 
         DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, 
JdbcAdapter.class.getName());
         InsertBatchQuery insertQuery = new InsertBatchQuery(entity, 1);
-        InsertBatchTranslator builder = new InsertBatchTranslator(insertQuery, 
adapter);
-        String generatedSql = builder.getSql();
+        String generatedSql = new 
InsertBatchTranslator().translate(insertQuery, adapter).sql();
         assertNotNull(generatedSql);
         assertEquals("INSERT INTO " + entity.getName() + "( DESCRIPTION, 
INT_COLUMN_NOTNULL, INT_COLUMN_NULL, LOCKING_TEST_ID, NAME) " +
                         "VALUES( ?, ?, ?, ?, ?)",
@@ -92,8 +78,7 @@ public class InsertBatchTranslatorIT {
             entity.getDataMap().setQuotingSQLIdentifiers(true);
 
             InsertBatchQuery insertQuery = new InsertBatchQuery(entity, 1);
-            InsertBatchTranslator builder = new 
InsertBatchTranslator(insertQuery, adapter);
-            String generatedSql = builder.getSql();
+            String generatedSql = new 
InsertBatchTranslator().translate(insertQuery, adapter).sql();
             String charStart = unitAdapter.getIdentifiersStartQuote();
             String charEnd = unitAdapter.getIdentifiersEndQuote();
             assertNotNull(generatedSql);
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslatorIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslatorIT.java
index c5cd3a691..b8ecf8423 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslatorIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslatorIT.java
@@ -56,13 +56,13 @@ public class SoftDeleteBatchTranslatorIT {
     private TestDbAdapter unitAdapter;
     private AdhocObjectFactory objectFactory;
 
-    private DeleteBatchTranslator createTranslator(DeleteBatchQuery query) {
+    private String createSql(DeleteBatchQuery query) {
         DbAdapter adapter = objectFactory.newInstance(JdbcAdapter.class, 
JdbcAdapter.class.getName());
-        return createTranslator(query, adapter);
+        return createSql(query, adapter);
     }
 
-    private DeleteBatchTranslator createTranslator(DeleteBatchQuery query, 
DbAdapter adapter) {
-        return (DeleteBatchTranslator) new 
SoftDeleteTranslatorFactory().translator(query, adapter, null);
+    private String createSql(DeleteBatchQuery query, DbAdapter adapter) {
+        return new SoftDeleteBatchTranslator().translate(query, adapter).sql();
     }
 
     @BeforeEach
@@ -79,8 +79,7 @@ public class SoftDeleteBatchTranslatorIT {
         List<DbAttribute> idAttributes = 
Collections.singletonList(entity.getAttribute("ID"));
 
         DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, 
idAttributes, Collections.emptySet(), 1);
-        DeleteBatchTranslator builder = createTranslator(deleteQuery);
-        String generatedSql = builder.getSql();
+        String generatedSql = createSql(deleteQuery);
         assertNotNull(generatedSql);
         assertEquals("UPDATE " + entity.getName() + " SET DELETED = ? WHERE ID 
= ?", generatedSql);
     }
@@ -94,8 +93,7 @@ public class SoftDeleteBatchTranslatorIT {
         Collection<String> nullAttributes = Collections.singleton("NAME");
 
         DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, 
idAttributes, nullAttributes, 1);
-        DeleteBatchTranslator builder = createTranslator(deleteQuery);
-        String generatedSql = builder.getSql();
+        String generatedSql = createSql(deleteQuery);
         assertNotNull(generatedSql);
         assertEquals("UPDATE " + entity.getName() + " SET DELETED = ? WHERE ( 
ID = ? ) AND ( NAME IS NULL )", generatedSql);
     }
@@ -111,8 +109,7 @@ public class SoftDeleteBatchTranslatorIT {
 
             DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, 
idAttributes, Collections.emptySet(), 1);
             DbAdapter adapter = node.getAdapter();
-            DeleteBatchTranslator builder = createTranslator(deleteQuery, 
adapter);
-            String generatedSql = builder.getSql();
+            String generatedSql = createSql(deleteQuery, adapter);
 
             String charStart = unitAdapter.getIdentifiersStartQuote();
             String charEnd = unitAdapter.getIdentifiersEndQuote();
@@ -131,9 +128,9 @@ public class SoftDeleteBatchTranslatorIT {
 
         DbEntity entity = 
env.context().getEntityResolver().getObjEntity(SoftDelete.class).getDbEntity();
 
-        BatchTranslatorFactory oldFactory = node.getBatchTranslatorFactory();
+        BatchTranslator<DeleteBatchQuery> oldTranslator = 
node.getDeleteBatchTranslator();
         try {
-            node.setBatchTranslatorFactory(new SoftDeleteTranslatorFactory());
+            node.setDeleteBatchTranslator(new SoftDeleteBatchTranslator());
 
             final SoftDelete test = env.context().newObject(SoftDelete.class);
             test.setName("SoftDeleteBatchQueryBuilderTest");
@@ -169,7 +166,7 @@ public class SoftDeleteBatchTranslatorIT {
             }.runTest(200);
         } finally {
             env.context().performQuery(new SQLTemplate(entity, "DELETE FROM 
SOFT_DELETE"));
-            node.setBatchTranslatorFactory(oldFactory);
+            node.setDeleteBatchTranslator(oldTranslator);
         }
     }
 
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslatorIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslatorIT.java
index 13150be93..053a03d22 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslatorIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslatorIT.java
@@ -40,7 +40,6 @@ import java.util.Collections;
 import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.mock;
 
 public class UpdateBatchTranslatorIT {
 
@@ -61,16 +60,6 @@ public class UpdateBatchTranslatorIT {
         objectFactory = env.adhocObjectFactory();
     }
 
-    @Test
-    public void constructor() {
-        DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, 
JdbcAdapter.class.getName());
-        UpdateBatchQuery query = mock(UpdateBatchQuery.class);
-        UpdateBatchTranslator builder = new UpdateBatchTranslator(query, 
adapter);
-
-        assertSame(adapter, builder.context.getAdapter());
-        assertSame(query, builder.context.getQuery());
-    }
-
     @Test
     public void createSqlString() {
         DbEntity entity = 
runtime.getDataDomain().getEntityResolver().getObjEntity(SimpleLockingTestEntity.class)
@@ -83,8 +72,7 @@ public class UpdateBatchTranslatorIT {
                 Collections.emptySet(), 1);
 
         DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, 
JdbcAdapter.class.getName());
-        UpdateBatchTranslator builder = new UpdateBatchTranslator(updateQuery, 
adapter);
-        String generatedSql = builder.getSql();
+        String generatedSql = new 
UpdateBatchTranslator().translate(updateQuery, adapter).sql();
         assertNotNull(generatedSql);
         assertEquals("UPDATE " + entity.getName() + " SET DESCRIPTION = ? 
WHERE LOCKING_TEST_ID = ?", generatedSql);
     }
@@ -103,8 +91,7 @@ public class UpdateBatchTranslatorIT {
         UpdateBatchQuery updateQuery = new UpdateBatchQuery(entity, 
idAttributes, updatedAttributes, nullAttributes, 1);
 
         DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, 
JdbcAdapter.class.getName());
-        UpdateBatchTranslator builder = new UpdateBatchTranslator(updateQuery, 
adapter);
-        String generatedSql = builder.getSql();
+        String generatedSql = new 
UpdateBatchTranslator().translate(updateQuery, adapter).sql();
         assertNotNull(generatedSql);
 
         assertEquals("UPDATE " + entity.getName() + " SET DESCRIPTION = ? 
WHERE ( LOCKING_TEST_ID = ? ) AND ( NAME IS NULL )",
@@ -123,8 +110,7 @@ public class UpdateBatchTranslatorIT {
 
             UpdateBatchQuery updateQuery = new UpdateBatchQuery(entity, 
idAttributes, updatedAttributes, Collections.emptySet(), 1);
 
-            UpdateBatchTranslator builder = new 
UpdateBatchTranslator(updateQuery, adapter);
-            String generatedSql = builder.getSql();
+            String generatedSql = new 
UpdateBatchTranslator().translate(updateQuery, adapter).sql();
 
             String charStart = unitAdapter.getIdentifiersStartQuote();
             String charEnd = unitAdapter.getIdentifiersEndQuote();
@@ -153,8 +139,7 @@ public class UpdateBatchTranslatorIT {
 
             UpdateBatchQuery updateQuery = new UpdateBatchQuery(entity, 
idAttributes, updatedAttributes, nullAttributes, 1);
 
-            UpdateBatchTranslator builder = new 
UpdateBatchTranslator(updateQuery, adapter);
-            String generatedSql = builder.getSql();
+            String generatedSql = new 
UpdateBatchTranslator().translate(updateQuery, adapter).sql();
             assertNotNull(generatedSql);
 
             String charStart = unitAdapter.getIdentifiersStartQuote();
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslatorIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslatorIT.java
deleted file mode 100644
index 3db0aa773..000000000
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslatorIT.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.translator.batch.legacy;
-
-import org.apache.cayenne.access.translator.ParameterBinding;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.JdbcAdapter;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.query.BatchQuery;
-import org.apache.cayenne.query.BatchQueryRow;
-import org.apache.cayenne.unit.CayenneProjects;
-import org.apache.cayenne.unit.CayenneTestsEnv;
-import org.junit.jupiter.api.extension.RegisterExtension;
-import org.junit.jupiter.api.Test;
-import java.sql.Types;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.mockito.Mockito.mock;
-
-@Deprecated
-public class DefaultBatchTranslatorIT {
-
-    @RegisterExtension
-    static final CayenneTestsEnv env = 
CayenneTestsEnv.forProject(CayenneProjects.TESTMAP_PROJECT);
-
-    @Test
-    public void constructor() throws Exception {
-        DbAdapter adapter = 
env.adhocObjectFactory().newInstance(DbAdapter.class, 
JdbcAdapter.class.getName());
-        DefaultBatchTranslator builder = new 
DefaultBatchTranslator(mock(BatchQuery.class), adapter, null) {
-            @Override
-            protected String createSql() {
-                return null;
-            }
-
-            @Override
-            protected ParameterBinding[] createBindings() {
-                return new ParameterBinding[0];
-            }
-
-            @Override
-            protected ParameterBinding[] doUpdateBindings(BatchQueryRow row) {
-                return new ParameterBinding[0];
-            }
-        };
-
-        assertSame(adapter, builder.adapter);
-    }
-
-    @Test
-    public void appendDbAttribute1() throws Exception {
-        DbAdapter adapter = 
env.adhocObjectFactory().newInstance(DbAdapter.class, 
JdbcAdapter.class.getName());
-        String trimFunction = "testTrim";
-
-        DefaultBatchTranslator builder = new 
DefaultBatchTranslator(mock(BatchQuery.class), adapter, trimFunction) {
-
-            @Override
-            protected String createSql() {
-                return null;
-            }
-
-            @Override
-            protected ParameterBinding[] createBindings() {
-                return new ParameterBinding[0];
-            }
-
-            @Override
-            protected ParameterBinding[] doUpdateBindings(BatchQueryRow row) {
-                return new ParameterBinding[0];
-            }
-        };
-
-        StringBuilder buf = new StringBuilder();
-        DbEntity entity = new DbEntity("Test");
-        DbAttribute attr = new DbAttribute("testAttr", Types.CHAR, null);
-        attr.setEntity(entity);
-        builder.appendDbAttribute(buf, attr);
-        assertEquals("testTrim(testAttr)", buf.toString());
-
-        buf = new StringBuilder();
-        attr = new DbAttribute("testAttr", Types.VARCHAR, null);
-        attr.setEntity(entity);
-        builder.appendDbAttribute(buf, attr);
-        assertEquals("testAttr", buf.toString());
-    }
-
-    @Test
-    public void appendDbAttribute2() throws Exception {
-        DbAdapter adapter = 
env.adhocObjectFactory().newInstance(DbAdapter.class, 
JdbcAdapter.class.getName());
-
-        DefaultBatchTranslator builder = new 
DefaultBatchTranslator(mock(BatchQuery.class), adapter, null) {
-            @Override
-            protected String createSql() {
-                return null;
-            }
-
-            @Override
-            protected ParameterBinding[] createBindings() {
-                return new ParameterBinding[0];
-            }
-
-            @Override
-            protected ParameterBinding[] doUpdateBindings(BatchQueryRow row) {
-                return new ParameterBinding[0];
-            }
-        };
-
-        StringBuilder buf = new StringBuilder();
-        DbEntity entity = new DbEntity("Test");
-
-        DbAttribute attr = new DbAttribute("testAttr", Types.CHAR, null);
-        attr.setEntity(entity);
-        builder.appendDbAttribute(buf, attr);
-        assertEquals("testAttr", buf.toString());
-
-        buf = new StringBuilder();
-        attr = new DbAttribute("testAttr", Types.VARCHAR, null);
-        attr.setEntity(entity);
-
-        builder.appendDbAttribute(buf, attr);
-        assertEquals("testAttr", buf.toString());
-    }
-}
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DataDomainProviderTest.java
 
b/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DataDomainProviderTest.java
index 81c9b61f2..cf8ea1cd6 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DataDomainProviderTest.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DataDomainProviderTest.java
@@ -32,8 +32,10 @@ import 
org.apache.cayenne.access.dbsync.SkipSchemaUpdateStrategy;
 import org.apache.cayenne.access.dbsync.ThrowOnPartialOrCreateSchemaStrategy;
 import org.apache.cayenne.access.translator.sqltemplate.SQLTemplateTranslator;
 import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
-import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
-import 
org.apache.cayenne.access.translator.batch.DefaultBatchTranslatorFactory;
+import org.apache.cayenne.access.translator.batch.BatchTranslator;
+import org.apache.cayenne.access.translator.batch.DeleteBatchTranslator;
+import org.apache.cayenne.access.translator.batch.InsertBatchTranslator;
+import org.apache.cayenne.access.translator.batch.UpdateBatchTranslator;
 import 
org.apache.cayenne.access.translator.select.DbAdapterDelegatedSelectTranslator;
 import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.DefaultValueObjectTypeRegistry;
@@ -222,7 +224,9 @@ public class DataDomainProviderTest {
             b.bind(DataChannelDescriptorLoader.class).toInstance(testLoader);
             b.bind(DbAdapterFactory.class).to(DefaultDbAdapterFactory.class);
             b.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class);
-            
b.bind(BatchTranslatorFactory.class).to(DefaultBatchTranslatorFactory.class);
+            b.bind(Key.get(BatchTranslator.class, 
BatchTranslator.INSERT)).to(InsertBatchTranslator.class);
+            b.bind(Key.get(BatchTranslator.class, 
BatchTranslator.UPDATE)).to(UpdateBatchTranslator.class);
+            b.bind(Key.get(BatchTranslator.class, 
BatchTranslator.DELETE)).to(DeleteBatchTranslator.class);
             
b.bind(SelectTranslator.class).to(DbAdapterDelegatedSelectTranslator.class);
 
             b.bind(DataSourceFactory.class).toInstance(new 
MockDataSourceFactory());
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DefaultDbAdapterFactoryTest.java
 
b/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DefaultDbAdapterFactoryTest.java
index 75cf74369..06b48b562 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DefaultDbAdapterFactoryTest.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DefaultDbAdapterFactoryTest.java
@@ -18,7 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.configuration.runtime;
 
-import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
+import org.apache.cayenne.access.translator.batch.BatchTranslator;
 import org.apache.cayenne.access.types.DefaultValueObjectTypeRegistry;
 import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
@@ -84,7 +84,9 @@ public class DefaultDbAdapterFactoryTest {
             
binder.bind(ClassLoaderManager.class).to(DefaultClassLoaderManager.class);
             
binder.bind(AdhocObjectFactory.class).to(DefaultAdhocObjectFactory.class);
             
binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class);
-            
binder.bind(BatchTranslatorFactory.class).toInstance(mock(BatchTranslatorFactory.class));
+            binder.bind(Key.get(BatchTranslator.class, 
BatchTranslator.INSERT)).toInstance(mock(BatchTranslator.class));
+            binder.bind(Key.get(BatchTranslator.class, 
BatchTranslator.UPDATE)).toInstance(mock(BatchTranslator.class));
+            binder.bind(Key.get(BatchTranslator.class, 
BatchTranslator.DELETE)).toInstance(mock(BatchTranslator.class));
         };
 
         Injector injector = DIBootstrap.createInjector(testModule);
@@ -114,7 +116,9 @@ public class DefaultDbAdapterFactoryTest {
             b.bind(ResourceLocator.class).to(ClassLoaderResourceLocator.class);
             b.bind(Key.get(ResourceLocator.class, 
Constants.RESOURCE_LOCATOR)).to(ClassLoaderResourceLocator.class);
             b.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class);
-            
b.bind(BatchTranslatorFactory.class).toInstance(mock(BatchTranslatorFactory.class));
+            b.bind(Key.get(BatchTranslator.class, 
BatchTranslator.INSERT)).toInstance(mock(BatchTranslator.class));
+            b.bind(Key.get(BatchTranslator.class, 
BatchTranslator.UPDATE)).toInstance(mock(BatchTranslator.class));
+            b.bind(Key.get(BatchTranslator.class, 
BatchTranslator.DELETE)).toInstance(mock(BatchTranslator.class));
 
             
b.bind(ValueObjectTypeRegistry.class).to(DefaultValueObjectTypeRegistry.class);
         };
@@ -149,7 +153,9 @@ public class DefaultDbAdapterFactoryTest {
             b.bind(ResourceLocator.class).to(ClassLoaderResourceLocator.class);
             b.bind(Key.get(ResourceLocator.class, 
Constants.RESOURCE_LOCATOR)).to(ClassLoaderResourceLocator.class);
             b.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class);
-            
b.bind(BatchTranslatorFactory.class).toInstance(mock(BatchTranslatorFactory.class));
+            b.bind(Key.get(BatchTranslator.class, 
BatchTranslator.INSERT)).toInstance(mock(BatchTranslator.class));
+            b.bind(Key.get(BatchTranslator.class, 
BatchTranslator.UPDATE)).toInstance(mock(BatchTranslator.class));
+            b.bind(Key.get(BatchTranslator.class, 
BatchTranslator.DELETE)).toInstance(mock(BatchTranslator.class));
 
             
b.bind(ValueObjectTypeRegistry.class).to(DefaultValueObjectTypeRegistry.class);
             
b.bind(ValueComparisonStrategyFactory.class).to(DefaultValueComparisonStrategyFactory.class);
@@ -189,7 +195,9 @@ public class DefaultDbAdapterFactoryTest {
             binder.bind(JdbcEventLogger.class).to(Slf4jJdbcEventLogger.class);
             
binder.bind(AdhocObjectFactory.class).to(DefaultAdhocObjectFactory.class);
             
binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class);
-            
binder.bind(BatchTranslatorFactory.class).toInstance(mock(BatchTranslatorFactory.class));
+            binder.bind(Key.get(BatchTranslator.class, 
BatchTranslator.INSERT)).toInstance(mock(BatchTranslator.class));
+            binder.bind(Key.get(BatchTranslator.class, 
BatchTranslator.UPDATE)).toInstance(mock(BatchTranslator.class));
+            binder.bind(Key.get(BatchTranslator.class, 
BatchTranslator.DELETE)).toInstance(mock(BatchTranslator.class));
         };
 
         Injector injector = DIBootstrap.createInjector(testModule);

Reply via email to