class SqlReportEntitiesPersister implements EntityPersister {

  @Override
  @Transactional
  @Retryable(
      value = {LockAcquisitionException.class},
      maxAttemptsExpression = "#{ @systemProperties['retryBackoff'] ?: 20}",
      backoff =
          @Backoff(
              delayExpression = "#{ @systemProperties['retryDelay'] ?: 100}",
              maxDelayExpression = "#{ @systemProperties['retryMaxDelay'] ?: 50000 }",
              multiplierExpression = "#{ @systemProperties['retryMultiplier'] ?: 1.5}"))
  public void persistReportEntities(List<? extends Report> reportEntities) {
    int batchFlush = 0;
    Session session = sessionFactory.getCurrentSession();
    FlushMode previousFlushMode = session.getHibernateFlushMode();
    session.setHibernateFlushMode(FlushMode.MANUAL);

    try {
      for (Report report : reportEntities) {
        report.setRowId();

        session.saveOrUpdate(report);
        batchFlush++;

        if (batchFlush == config.getBatchSize()) {
          session.flush();
          session.clear();
          batchFlush = 0;
        }
      }

      if (batchFlush > 0) {
        session.flush();
        session.clear();
      }
    } catch (NonUniqueObjectException ex) {
      // Github issue 268 & 280
      //   https://github.com/googleads/aw-reporting/issues/268
      //   https://github.com/googleads/aw-reporting/issues/280
      //
      // Currently we allow specifying report definitions which do not include all primary key
      // fields. This leads to cryptic hibernate errors without providing a reasonable
      // resolution strategy.
      //
      // This fix explains where to find the list of primary key fields, but does not address
      // the underlying issue of allowing non-unique rows to be downloaded in the first place.
      //
      // Ideally we would guarantee uniqueness of rows without the user having to specify the
      // PK fields.
      // However, this would be a substantial migration for the AWReporting user base.
      // Instead, we just log a (hopefully) useful error message.
      // Also note that the error message assumes that reportEntities was not empty, because
      // otherwise the exception would not have been thrown.
      logger.error(
          "Duplicate row detected. This is most likely because your report definition does not "
              + "include the primary key fields defined in {}.setRowId(). "
              + "Please add the missing fields and try again.",
          reportEntities.get(0).getClass().getName());
      throw ex;
    } finally {
      session.setHibernateFlushMode(previousFlushMode);
    }
  }

  @Autowired public  SqlReportEntitiesPersister(SessionFactory sessionFactory, Config config);

}