Постановка проблемы
Моя цель:
- Считать строку файла CSV -> Преобразовать в объект DTO ( Этот шаг не связанный с моим вопросом, включенный для полноты )
- Сбор 'n' объектов DTO -> Вызовите методы обслуживания save () / update (). ( В том же контексте транзакции )
Ожидается: При сбое одного вызова save () / update () -> Откатить весь пакет.
Факт: При сбое одного вызова save () / update () -> Spring фиксирует запись для предыдущего вызова (-ов).
Реализация
Ниже моя реализация кода. ( Пропущенные вспомогательные методы и нерелевантные атрибуты для краткости )
Класс импортера:
Список групп (пациент) и вызовы save () / update () в зависимости от типа операции.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.github.swapnil.hims.bo.importer.BulkImporter;
import com.github.swapnil.hims.bo.importer.Importer;
import com.github.swapnil.hims.dto.PatientDetail;
import com.github.swapnil.hims.service.PatientService;
@Component
public class PatientImporter extends Importer implements BulkImporter {
@Autowired
private PatientService patientSvc;
private final TransactionTemplate txTmpl;
public PatientImporter(PlatformTransactionManager transactionManager) {
this.txTmpl = new TransactionTemplate(transactionManager);
}
@Override
public PatientDetail rowToObject(CSVRecord record) {
// Convert row to DTO object.
return patientDetail;
}
@Override
public void saveToDb(List<Object> patients) {
List<PatientDetail> patientList = patients.stream().map(patient -> (PatientDetail) patient)
.collect(Collectors.toList());
saveAll(patientList, this.importType, BATCH_SIZE);
}
private void saveAll(List<PatientDetail> patients, Importer.Type action, Integer batchSize) {
txTmpl.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
createPatients(patients, action, batchSize);
}
});
}
private void createPatients(List<PatientDetail> patients, Importer.Type action, Integer batchSize) {
int startAt = 0;
while (true) {
List<PatientDetail> patientBatch = patients.stream().skip(startAt).limit(batchSize)
.collect(Collectors.toList());
for (PatientDetail patient : patientBatch) {
if (action == Importer.Type.CREATE) {
patientSvc.save(patient);
} else {
patientSvc.update(patient);
}
}
if (patientBatch.size() != batchSize) {
break;
}
startAt += batchSize;
}
}
}
Класс обслуживания пациента:
Класс репозитория / обслуживания для пациента.
import javax.transaction.Transactional;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.github.swapnil.hims.dao.PatientDao;
import com.github.swapnil.hims.dto.PatientDetail;
import com.github.swapnil.hims.entities.Patient;
import com.github.swapnil.hims.exception.PatientException;
import com.github.swapnil.hims.service.PatientService;
@Service
public class PatientServiceImpl implements PatientService {
private final static Log logger = LogFactory.getLog(PatientServiceImpl.class);
@Autowired
private PatientDao patientDao;
@Override
@Transactional
public void save(PatientDetail patientDetail) {
patientDao.save(getPatient(patientDetail));
}
@Override
@Transactional
public void update(Long id, PatientDetail patientDetail) {
patientDao.save(getPatient(id, patientDetail));
}
@Override
public void update(PatientDetail patientDetail) {
update(patientDetail.getIdentifier(), patientDetail);
}
private Patient getPatient(PatientDetail patientDetail) {
if (StringUtils.isEmpty(patientDetail.getPatientId())) {
throw new PatientException("Patient ID is required!");
}
if (StringUtils.isEmpty(patientDetail.getName())) {
throw new PatientException("Patient name is required!");
}
if (StringUtils.isEmpty(patientDetail.getEmail())) {
throw new PatientException("Patient e-mail is required!");
}
Patient dbPatient = patientDao.findByPatientId(patientDetail.getPatientId());
if (dbPatient != null) {
throw new PatientException(String.format("Patient with the same pid '%s' already exists!", dbPatient.getPatientId()));
}
return patientDetail.toPatient();
}
private Patient getPatient(Long id, PatientDetail patientDetail) {
Patient patient = patientDao.findById(id);
if (patient == null) {
throw new PatientException("Patient with id " + id + " does not exists.");
}
patient.setName(StringUtils.isEmpty(patientDetail.getName()) ? patient.getName() : patientDetail.getName());
patient.setCity(StringUtils.isEmpty(patientDetail.getCity()) ? patient.getCity() : patientDetail.getCity());
patient.setAddress(StringUtils.isEmpty(patientDetail.getAddress()) ? patient.getAddress() : patientDetail.getAddress());
patient.setEmail(StringUtils.isEmpty(patientDetail.getEmail()) ? patient.getEmail() : patientDetail.getEmail());
patient.setContactNumber(patientDetail.getContactNumber() != null ? patient.getContactNumber() : patientDetail.getContactNumber());
patient.setPatientId(StringUtils.isEmpty(patientDetail.getPatientId()) ? patient.getPatientId() : patientDetail.getPatientId());
return patient;
}
}
PatientDao. java
import org.springframework.data.jpa.repository.JpaRepository;
import com.github.swapnil.hims.entities.Patient;
public interface PatientDao extends JpaRepository<Patient, String> {
public Patient findById(Long id);
public void deleteById(Long id);
public Patient findByPatientId(String patientId);
}
Я пытался:
try {createPatients(patients, action, batchSize);} catch() {status.setRollbackOnly()}
- Установка PropagationBehaviour на PROPAGATION_REQUIRED.
Я не могу понять, чего мне здесь не хватает, я потратил кучу времени на решение этой проблемы и очень, очень признателен за любую помощь , Я использую MySQL v8.0.16.
(GitHub repo URL )
EDIT
Во время отладки, Я обнаружил, что Patient dbPatient = patientDao.findByPatientId(patientDetail.getPatientId());
вызов вызывает гибернацию к сеансу sh и переводит предыдущий объект в БД. Я не уверен, как предотвратить это, хотя. Я попробовал кое-что, но ничего не вышло, буду искать и обновлять.