File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
package kr.itn.itnhub.seed;
import kr.itn.itnhub.contact.Contact;
import kr.itn.itnhub.contact.ContactMapper;
import kr.itn.itnhub.org.Organization;
import kr.itn.itnhub.org.OrganizationMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import java.io.InputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Service
public class SeedService {
private final SeedParser parser;
private final OrganizationMapper mapper;
private final ContactMapper contactMapper;
private final TransactionTemplate transactionTemplate;
public SeedService(SeedParser parser, OrganizationMapper mapper, ContactMapper contactMapper,
PlatformTransactionManager transactionManager) {
this.parser = parser;
this.mapper = mapper;
this.contactMapper = contactMapper;
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
/**
* 엑셀 파싱은 DB와 무관하므로 트랜잭션 밖에서 수행한다. 커넥션을 잡아두는 구간은
* 기존 행 조회 + upsert 루프뿐이며, {@link TransactionTemplate}으로 그 구간만 감싼다.
* (같은 빈의 @Transactional 메서드를 self-invocation으로 호출하면 프록시를 우회해
* 트랜잭션이 아예 적용되지 않으므로 이 방식을 쓴다.)
*/
public SeedReport seed(InputStream xlsx) {
List<SeedRow> rows = parser.parse(xlsx);
return transactionTemplate.execute(status -> seedRows(rows));
}
private SeedReport seedRows(List<SeedRow> rows) {
// DB에 이미 있는 키와, 이번 업로드에서 먼저 처리된 키를 모두 "이미 존재함"으로 취급한다.
// 그래야 한 시트에 같은 기관이 두 번 들어와도 두 번째는 created가 아니라 updated로 집계된다.
Set<String> seen = new HashSet<>();
for (Organization existing : mapper.findAll()) {
seen.add(key(existing.getOrgNo(), existing.getOrgName()));
}
int created = 0;
int updated = 0;
for (SeedRow row : rows) {
if (seen.add(key(row.orgNo(), row.orgName()))) {
created++;
} else {
updated++;
}
mapper.upsertBySeed(toOrganization(row));
linkApplicantContactIfMissing(row);
}
return new SeedReport(created, updated, rows.size());
}
/**
* 시트에 담당자 이름이 있고 그 기관이 아직 신청기관 담당자가 배정되지 않았을 때만
* 담당자를 연결한다. 이미 배정돼 있으면(담당자관리 화면에서 사람이 고쳤을 수도
* 있는 값이므로) 절대 건드리지 않는다 - 재시드가 배정을 되돌리거나 중복 생성하면 안 된다.
*
* <p>연결할 담당자는 새로 만들기 전에 (구분, 성명, 전화, 이메일)이 같은 기존 담당자가
* 있는지 먼저 찾는다 - 경찰청 계열 27개 하위기관이 같은 담당자를 공유하는 것처럼,
* 여러 기관 행이 동일 인물을 가리키는 경우 재시드 때마다 담당자가 중복 생성되면 안 된다.</p>
*/
private void linkApplicantContactIfMissing(SeedRow row) {
if (row.managerName() == null) {
return;
}
Organization saved = mapper.findByOrgNoAndOrgName(row.orgNo(), row.orgName());
if (saved.getApplicantContactId() != null) {
return;
}
Contact contact = contactMapper.findMatching(
"APPLICANT", row.managerName(), row.managerPhone(), row.managerEmail());
if (contact == null) {
contact = new Contact();
contact.setCategory("APPLICANT");
contact.setName(row.managerName());
contact.setAffiliation(row.orgName());
contact.setDeptName(row.deptName());
contact.setTitle(row.managerTitle());
contact.setPhone(row.managerPhone());
contact.setEmail(row.managerEmail());
contactMapper.insert(contact);
}
mapper.updateAssignments(saved.getId(), contact.getId(),
saved.getMjContactId(), saved.getItnContactId(),
saved.getLawyerContactId(), saved.getLawyerAssignedDate());
}
private String key(String orgNo, String orgName) {
return orgNo + " " + orgName;
}
private Organization toOrganization(SeedRow row) {
Organization org = new Organization();
org.setOrgNo(row.orgNo());
org.setOrgName(row.orgName());
org.setChannelSlug(row.channelSlug());
return org;
}
}