Batch

We can explicitly turn on the use of JDBC batch via transaction.setBatchMode(true)

try (Transaction transaction = database.beginTransaction()) {

  // use JDBC batch
  transaction.setBatchMode(true);

  // these go to batch buffer
  aBean.save();
  bBean.save();
  cBean.save();

  // batched
  database.saveAll(someBeans);

  // flush batch and commit
  transaction.commit();
}

@Transactional(batchSize)

When using @Transactional setting the batchSize attribute will turn on JDBC batch.

@Transactional(batchSize = 50)
public void doGoodStuff() {

  ...
}

Batch size

The batch size controls the number of times Ebean will bind a JDBC prepared statement before executing it. When persisting beans (e.g. customer.save()) it is effectively the number of beans persisted.

When the number of beans (or SqlUpdate or CallableSql) hits the batch size then the batch is flushed and all the statements in the buffer are executed.

When persisting different types of beans, for example "order" and "order details" then when any of the buffers hits the batch size all buffers are flushed. This allows Ebean to preserve the execution order, for example persisting all the "orders" before persisting all the "order details".

The batch buffer size default value is set via databaseConfig.persistBatchSize which defaults to 20. We can set the buffer size on the transaction via transaction.setBatchSize() or @Transactional(batchSize).

try (Transaction transaction = database.beginTransaction()) {

  // use JDBC batch
  transaction.setBatchMode(true);
  transaction.setBatchSize(50);

  // these go to batch buffer
  aBean.save();
  ...

  // flush batch and commit
  transaction.commit();
}

GetGeneratedKeys

By default Ebean will get the generated keys after beans are inserted. When we have processing that is inserting a large number of rows/beans and we don't actually need the keys after the insert we can turn off getting the generated keys for a performance benefit.

// don't bother getting back the generated keys
transaction.setGetGeneratedKeys(false);

When we are performing a large bulk insert it is common to turn off getGeneratedKeys.

try (Transaction transaction = database.beginTransaction()) {
  transaction.setBatchMode(true);
  transaction.setBatchSize(100);

  // turn off GetGeneratedKeys as we don't need the keys
  transaction.setGetGeneratedKeys(false);

  // maybe even turn off persist cascade ...
  transaction.setPersistCascade(false)

  // perform lots of inserts ...
  ...


  transaction.commit();
}

Flush

The batch is flushed when:

  • A buffer hits the batch size
  • We execute a query - depending on setFlushOnQuery()
  • We mix SqlUpdate, CallableSql with bean persisting - depending on setFlushOnMixed()
  • We call a getter or setter on a bean property that is already batched e.g. bean.getId()
  • We explicitly call flush()

setFlushOnQuery()

Set setFlushOnQuery(false) on a transaction when we want to execute a query but don't want that to trigger a flush.

try (Transaction transaction = database.beginTransaction()) {
  transaction.setBatchMode(true);
  transaction.setFlushOnQuery(false);

  // these go to batch buffer
  aBean.save();
  ...

  // execute this query but we don't
  // want that to trigger flush
  SomeOtherBean.find.byId(42);

  ...

  transaction.commit();
}

setFlushOnMixed()

We set setFlushOnMixed(false) on a transaction when we want to execute a SqlUpdate or CallableSql mixed with bean save(), delete() etc and don't want that to trigger a flush.

try (Transaction transaction = database.beginTransaction()) {
  transaction.setBatchMode(true);
  transaction.setFlushOnMixed(false);

  // these go to batch buffer
  aBean.save();
  ...

  // execute SqlUpdate but we don't
  // want that to trigger flush
  SqlUpdate update = ...
  update.execute();

  ...

  transaction.commit();
}

Flush on getter/setter

For beans that are in the batch buffer, if we call getters/setters on a generated property or "unloaded" Id property this will trigger a flush.

try (Transaction transaction = database.beginTransaction()) {
  transaction.setBatchMode(true);

  // bean goes to batch buffer
  myBean.save();
  ...

  // will trigger flush if id is an "unloaded" property
  // and myBean is in the buffer (not flushed)
  long id = myBean.getId();

  // will trigger flush if myBean is in the buffer (not flushed)
  Instant whenCreated = myBean.getWhenCreated();

  ...

  transaction.commit();
}

Explicit flush()

We may wish to perform an explicit transaction.flush() when we want to ensure that any batched statements have been executed. This typically means that we know that all SQL statements have been executed and that all the DB constraints are tested at that point in the application logic.

try (Transaction transaction = database.beginTransaction()) {
  transaction.setBatchMode(true);

  // bean goes to batch buffer
  myBean.save();
  ...


  // ensure all SQL statements are executed which means that
  // all DB constraints (unique, foreign key etc) are tested
  transaction.flush();


  // carry on with stuff ...
  ...

  transaction.commit();
}

Configuration via DatabaseConfig

// use JDBC batch by default
databaseConfig.setPersistBatch(PersistBatch.ALL);

We can set databaseConfig.setPersistBatch(PersistBatch.ALL) so that JDBC batch mode is the default being used for all transactions.

// default batch size
databaseConfig.setPersistBatchSize(50);

We can change the global default batch size via databaseConfig.setPersistBatchSize(). The default is otherwise set at 20.

// update all loaded properties or just dirty properties
databaseConfig.setUpdateAllPropertiesInBatch(true);

When using batch update we have the option to include the dirty properties or all loaded properties in the update. If we choose dirty properties we will include less properties in the update statement but we may get more distinct update statements being executed (in the case where the application logic doesn't update the same properties on all the beans being updated).