package fr.openwide.core.jpa.migration.service;
import java.util.List;
import java.util.Map;
import org.hibernate.PropertyValueException;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import com.google.common.collect.ImmutableList;
import fr.openwide.core.jpa.batch.executor.BatchExecutorCreator;
import fr.openwide.core.jpa.batch.executor.MultithreadedBatchExecutor;
import fr.openwide.core.jpa.batch.monitor.ProcessorMonitorContext;
import fr.openwide.core.jpa.batch.runnable.ReadWriteBatchRunnable;
import fr.openwide.core.jpa.business.generic.model.GenericEntity;
import fr.openwide.core.jpa.business.generic.service.IGenericEntityService;
import fr.openwide.core.jpa.exception.SecurityServiceException;
import fr.openwide.core.jpa.exception.ServiceException;
import fr.openwide.core.jpa.migration.rowmapper.AbstractResultRowMapper;
import fr.openwide.core.jpa.migration.util.IBatchAssociationMigrationInformation;
/**
* An abstract base for migration services that import associations from an entity to other elements
* (@OneToMany, @OneToOne, @ManyToOne, @ManyToMany, but also @ElementCollection)
*
* @param Owning The type of the entity on the owning (non "mappedBy") side of the association
* @param Owned The type of the owning entity association member (e.g. Collection<AnotherType>)
*/
public abstract class AbstractBatchAssociationMigrationService<Owning extends GenericEntity<Long, Owning>, Owned>
extends AbstractMigrationService {
private static final int DEFAULT_VALUES_PER_KEY = 3;
@Autowired
private BatchExecutorCreator batchCreator;
public void importAllEntities() {
List<Long> entityIds = ImmutableList.copyOf(getJdbcTemplate().queryForList(getMigrationInformation().getSqlAllIds(), Long.class));
MultithreadedBatchExecutor executor = batchCreator.newMultithreadedBatchExecutor();
executor.threads(4).batchSize(100);
executor.run(getMigrationInformation().getAssociationName(), entityIds, new ReadWriteBatchRunnable<Long>() {
@Override
public void executePartition(List<Long> partition) {
importBatch(partition);
}
});
}
private void importBatch(List<Long> entityIds) throws PropertyValueException {
preload(entityIds, getMigrationInformation());
try {
MapSqlParameterSource entityIdsParameterSource = new MapSqlParameterSource();
entityIdsParameterSource.addValue(getMigrationInformation().getParameterIds(), entityIds);
AutowireCapableBeanFactory autowire = applicationContext.getAutowireCapableBeanFactory();
AbstractResultRowMapper<? extends Map<Owning, Owned>> rowMapper = getMigrationInformation().newRowMapper(entityIds.size(), DEFAULT_VALUES_PER_KEY);
autowire.autowireBean(rowMapper);
autowire.initializeBean(rowMapper, rowMapper.getClass().getSimpleName());
prepareRowMapper(rowMapper, entityIds);
getNamedParameterJdbcTemplate().query(getMigrationInformation().getSqlRequest(), entityIdsParameterSource, rowMapper);
for (Map.Entry<Owning, Owned> entry : rowMapper.getResults().entrySet()) {
Owning entity = entry.getKey();
getMigrationInformation().addToAssociation(entity, entry.getValue());
if (getEntityService() != null) {
getEntityService().update(entity);
} else {
entityManagerUtils.getEntityManager().persist(entity);
}
}
} catch (RuntimeException | ServiceException | SecurityServiceException e) {
getLogger().error("Error during the persistence of {} items. {} cancelled creations.",
getMigrationInformation().getAssociationName(), entityIds.size(), e);
ProcessorMonitorContext.get().getDoneItems().addAndGet(-1 * entityIds.size());
}
}
protected void prepareRowMapper(RowMapper<?> rowMapper, List<Long> entityIds) {
}
protected abstract IBatchAssociationMigrationInformation<Owning, Owned> getMigrationInformation();
protected abstract Logger getLogger();
/**
* Override this if you want to use a GenericEntityService when creating entities.
* <p><strong>Note:</strong>This is not recommended, since these services generally assume that they are really
* creating a brand new row, not importing it. They may, for instance, set the "creationdate" attribute of an
* entity to the current time, which is probably wrong when migrating.
*/
protected IGenericEntityService<Long, Owning> getEntityService() {
return null;
}
protected Integer getPartitionSize() {
return 100;
}
}