Moved phone jdbc calls to repository; Added RepositoryTimingAspect

This commit is contained in:
Mikhail Trofimov 2025-10-02 17:35:38 +03:00
parent f95b547a81
commit c31d515c84
9 changed files with 158 additions and 170 deletions

View file

@ -0,0 +1,28 @@
package ru.nbch.credit_tracker.component;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Slf4j
@Component
public class RepositoryTimingAspect {
@Around("within(@org.springframework.stereotype.Repository *)")
public Object timeMapper(ProceedingJoinPoint pjp) throws Throwable {
long start = System.nanoTime();
try {
return pjp.proceed();
} finally {
long tookMs = (System.nanoTime() - start) / 1_000_000;
String method = pjp.getSignature().toShortString();
if (tookMs > 1000) {
log.warn("Slow jdbc template call {} took {} ms", method, tookMs);
} else {
log.debug("Jdbc template call {} took {} ms", method, tookMs);
}
}
}
}

View file

@ -1,12 +0,0 @@
package ru.nbch.credit_tracker.mapper.indic;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import ru.nbch.credit_tracker.service.indic.PhoneService;
import java.util.List;
@Mapper
public interface PhoneMapper {
List<PhoneService.FidsData> findFids(@Param("phones") List<String> phones);
}

View file

@ -0,0 +1,78 @@
package ru.nbch.credit_tracker.repository;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.util.List;
@Repository
public class PhoneRepository {
private static final String SELCT_FIDS = """
SELECT
pn.number,
pn.fid,
pn.file_since_dt
FROM PHONE_NORM pn
JOIN (SELECT DISTINCT fid FROM NAME) nf
ON nf.fid = pn.fid
WHERE number in (:phones)
ORDER BY
pn.number ASC,
pn.file_since_dt DESC,
pn.fid DESC
WITH UR
""";
private final NamedParameterJdbcTemplate jdbcTemplate;
public PhoneRepository(@Qualifier("jdbcTemplateIndic") NamedParameterJdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public List<FidsData> findFids(List<String> phones) {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("phones", phones);
return jdbcTemplate.query(SELCT_FIDS, params, (rs, rowNum) ->
new PhoneRepository.FidsData(rs.getInt("fid"), rs.getString("number"), rs.getObject("file_since_dt", LocalDate.class))
);
}
public List<FidsDataLong> findFidsLong(List<String> phones) {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("phones", phones);
return jdbcTemplate.query(SELCT_FIDS, params, (rs, rowNum) ->
new PhoneRepository.FidsDataLong(rs.getInt("fid"), Long.parseLong(rs.getString("number")), rs.getObject("file_since_dt", LocalDate.class))
);
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class FidsData {
private Integer fid;
private String phone;
private LocalDate fileSinceDate;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class FidsDataLong {
private Integer fid;
private Long phone;
private LocalDate fileSinceDate;
}
}

View file

@ -1,17 +1,10 @@
package ru.nbch.credit_tracker.service.indic; package ru.nbch.credit_tracker.service.indic;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.entities.dto.PhoneFidMap; import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
import ru.nbch.credit_tracker.repository.PhoneRepository;
import java.time.LocalDate;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -21,26 +14,10 @@ import java.util.Map;
@Service @Service
public class PhoneService { public class PhoneService {
private static final String SELCT_FIDS = """ private final PhoneRepository phoneRepository;
SELECT
pn.number,
pn.fid,
pn.file_since_dt
FROM PHONE_NORM pn
JOIN (SELECT DISTINCT fid FROM NAME) nf
ON nf.fid = pn.fid
WHERE number in (:phones)
ORDER BY
pn.number ASC,
pn.file_since_dt DESC,
pn.fid DESC
WITH UR
""";
private final NamedParameterJdbcTemplate jdbcTemplate; public PhoneService(PhoneRepository phoneRepository) {
this.phoneRepository = phoneRepository;
public PhoneService(@Qualifier("jdbcTemplateIndic") NamedParameterJdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
} }
/** /**
@ -60,12 +37,7 @@ public class PhoneService {
) )
); );
MapSqlParameterSource params = new MapSqlParameterSource(); List<PhoneRepository.FidsDataLong> phonesByFid = phoneRepository.findFidsLong(phones);
params.addValue("phones", phones);
List<FidsData> phonesByFid = jdbcTemplate.query(SELCT_FIDS, params, (rs, rowNum) ->
new FidsData(rs.getInt("fid"), Long.parseLong(rs.getString("number")), rs.getObject("file_since_dt", LocalDate.class))
);
log.debug("Start merging fids"); log.debug("Start merging fids");
Map<Integer, PhoneFidMap> phoneFidMap = merger(subjects, phonesByFid); Map<Integer, PhoneFidMap> phoneFidMap = merger(subjects, phonesByFid);
@ -74,7 +46,7 @@ public class PhoneService {
return phoneFidMap; return phoneFidMap;
} }
private Map<Integer, PhoneFidMap> merger(List<PhoneFidMap> subjects, List<FidsData> phonesByFid) { private Map<Integer, PhoneFidMap> merger(List<PhoneFidMap> subjects, List<PhoneRepository.FidsDataLong> phonesByFid) {
Map<Integer, PhoneFidMap> phoneFidMap = new HashMap<>(); Map<Integer, PhoneFidMap> phoneFidMap = new HashMap<>();
if (phonesByFid.isEmpty()) { if (phonesByFid.isEmpty()) {
@ -86,7 +58,7 @@ public class PhoneService {
Long normalizedPhone = Long.parseLong("9" + subject.getPhone()); Long normalizedPhone = Long.parseLong("9" + subject.getPhone());
while (index < phonesByFid.size()) { while (index < phonesByFid.size()) {
FidsData currentFidData = phonesByFid.get(index); PhoneRepository.FidsDataLong currentFidData = phonesByFid.get(index);
if (currentFidData.getPhone() < normalizedPhone) { if (currentFidData.getPhone() < normalizedPhone) {
index++; index++;
@ -109,13 +81,4 @@ public class PhoneService {
return phoneFidMap; return phoneFidMap;
} }
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class FidsData {
private Integer fid;
private Long phone;
private LocalDate fileSinceDate;
}
} }

View file

@ -1,44 +1,21 @@
package ru.nbch.credit_tracker.service.indic; package ru.nbch.credit_tracker.service.indic;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.nbch.credit_tracker.entities.dto.PhoneFidMap; import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
import ru.nbch.credit_tracker.repository.PhoneRepository;
import java.time.LocalDate;
import java.util.*; import java.util.*;
@Slf4j @Slf4j
@Service @Service
public class PhoneService2 { public class PhoneService2 {
private static final String SELCT_FIDS = """ private final PhoneRepository phoneRepository;
SELECT
pn.number,
pn.fid,
pn.file_since_dt
FROM PHONE_NORM pn
JOIN (SELECT DISTINCT fid FROM NAME) nf
ON nf.fid = pn.fid
WHERE number in (:phones)
ORDER BY
pn.number ASC,
pn.file_since_dt DESC,
pn.fid DESC
WITH UR
""";
private final NamedParameterJdbcTemplate jdbcTemplate; public PhoneService2(PhoneRepository phoneRepository) {
this.phoneRepository = phoneRepository;
public PhoneService2(@Qualifier("jdbcTemplateIndic") NamedParameterJdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
} }
/** /**
@ -60,15 +37,10 @@ public class PhoneService2 {
) )
); );
MapSqlParameterSource params = new MapSqlParameterSource(); List<PhoneRepository.FidsData> phonesByFid = phoneRepository.findFids(phones);
params.addValue("phones", phones);
List<FidsData> phonesByFid = jdbcTemplate.query(SELCT_FIDS, params, (rs, rowNum) ->
new FidsData(rs.getInt("fid"), rs.getString("number"), rs.getObject("file_since_dt", LocalDate.class))
);
Map<String, Integer> phoneToFidsMap = new HashMap<>(); Map<String, Integer> phoneToFidsMap = new HashMap<>();
for (FidsData fidsData : phonesByFid) { for (PhoneRepository.FidsData fidsData : phonesByFid) {
if (!phoneToFidsMap.containsKey(fidsData.getPhone())) { if (!phoneToFidsMap.containsKey(fidsData.getPhone())) {
phoneToFidsMap.put(fidsData.getPhone(), fidsData.getFid()); phoneToFidsMap.put(fidsData.getPhone(), fidsData.getFid());
} }
@ -88,11 +60,11 @@ public class PhoneService2 {
return phoneFidMap; return phoneFidMap;
} }
private Map<Integer, PhoneFidMap> merger(List<PhoneFidMap> subjects, List<FidsData> phonesByFid) { private Map<Integer, PhoneFidMap> merger(List<PhoneFidMap> subjects, List<PhoneRepository.FidsData> phonesByFid) {
Map<Integer, PhoneFidMap> phoneFidMap = new HashMap<>(); Map<Integer, PhoneFidMap> phoneFidMap = new HashMap<>();
Iterator<FidsData> iterator = phonesByFid.iterator(); Iterator<PhoneRepository.FidsData> iterator = phonesByFid.iterator();
String lastUniquePhone = null; String lastUniquePhone = null;
FidsData fidsData = null; PhoneRepository.FidsData fidsData = null;
for (PhoneFidMap phoneFid : subjects) { for (PhoneFidMap phoneFid : subjects) {
String normalizedPhone = 9 + phoneFid.getPhone(); String normalizedPhone = 9 + phoneFid.getPhone();
while (iterator.hasNext()) { while (iterator.hasNext()) {
@ -116,13 +88,4 @@ public class PhoneService2 {
return phoneFidMap; return phoneFidMap;
} }
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class FidsData {
private Integer fid;
private String phone;
private LocalDate fileSinceDate;
}
} }

View file

@ -68,6 +68,12 @@
<appender-ref ref="FILE-JSON"/> <appender-ref ref="FILE-JSON"/>
</logger> </logger>
<logger name="ru.nbch.credit_tracker.component" level="DEBUG" additivity="false">
<appender-ref ref="FILE"/>
<appender-ref ref="FILE-JSON"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="ru.nbch.credit_tracker.service" level="TRACE" additivity="false"> <logger name="ru.nbch.credit_tracker.service" level="TRACE" additivity="false">
<appender-ref ref="FILE"/> <appender-ref ref="FILE"/>
<appender-ref ref="FILE-JSON"/> <appender-ref ref="FILE-JSON"/>

View file

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="ru.nbch.credit_tracker.mapper.indic.PhoneMapper">
<resultMap id="fidsDataResultMap" type="ru.nbch.credit_tracker.service.indic.PhoneService$FidsData">
<result property="phone" column="number" />
<result property="fid" column="fid" />
<result property="fileSinceDate" column="fileSinceDate" />
</resultMap>
<select id="findFids" resultMap="fidsDataResultMap">
SELECT
pn.number,
pn.fid,
pn.file_since_dt
FROM indic.PHONE_NORM pn
JOIN (SELECT DISTINCT fid FROM indic.NAME) nf
ON nf.fid = pn.fid
WHERE number in
<foreach item="phone" collection="phones" open="(" separator="," close=")">
#{phone}
</foreach>
ORDER BY
pn.number ASC,
pn.file_since_dt DESC,
pn.fid DESC
WITH UR
</select>
</mapper>

View file

@ -5,10 +5,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import ru.nbch.credit_tracker.entities.dto.PhoneFidMap; import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
import ru.nbch.credit_tracker.repository.PhoneRepository;
import ru.nbch.credit_tracker.service.indic.PhoneService2; import ru.nbch.credit_tracker.service.indic.PhoneService2;
import java.time.LocalDate; import java.time.LocalDate;
@ -18,21 +16,20 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
public class PhoneService2Test { public class PhoneService2Test {
@Mock @Mock
NamedParameterJdbcTemplate jdbcTemplate; PhoneRepository phoneRepository;
@Test @Test
public void defineFidByPhoneTest() { public void defineFidByPhoneTest() {
PhoneService2 phoneService = new PhoneService2(jdbcTemplate); PhoneService2 phoneService = new PhoneService2(phoneRepository);
fidsDataWithEqualSinceDt.sort(byDbOrder); fidsDataWithEqualSinceDt.sort(byDbOrder);
when(jdbcTemplate.query(anyString(), any(MapSqlParameterSource.class), any(RowMapper.class))).thenReturn(fidsDataWithEqualSinceDt); when(phoneRepository.findFids(any())).thenReturn(fidsDataWithEqualSinceDt);
List<PhoneFidMap> phoneFidMap = new ArrayList<>(); List<PhoneFidMap> phoneFidMap = new ArrayList<>();
phoneFidMap.add(PhoneFidMap.builder().phone("1111").build()); phoneFidMap.add(PhoneFidMap.builder().phone("1111").build());
@ -49,19 +46,19 @@ public class PhoneService2Test {
Assertions.assertEquals("1115", res.get(127).getPhone()); Assertions.assertEquals("1115", res.get(127).getPhone());
} }
private final List<PhoneService2.FidsData> fidsDataWithEqualSinceDt = new ArrayList<>( private final List<PhoneRepository.FidsData> fidsDataWithEqualSinceDt = new ArrayList<>(
List.of( List.of(
new PhoneService2.FidsData(123, "91111", LocalDate.of(2025, 9, 10)), new PhoneRepository.FidsData(123, "91111", LocalDate.of(2025, 9, 10)),
new PhoneService2.FidsData(124, "91111", LocalDate.of(2025, 9, 10)), new PhoneRepository.FidsData(124, "91111", LocalDate.of(2025, 9, 10)),
new PhoneService2.FidsData(125, "91112", LocalDate.of(2024, 10, 10)), new PhoneRepository.FidsData(125, "91112", LocalDate.of(2024, 10, 10)),
new PhoneService2.FidsData(126, "91114", LocalDate.of(2024, 10, 11)), new PhoneRepository.FidsData(126, "91114", LocalDate.of(2024, 10, 11)),
new PhoneService2.FidsData(127, "91115", LocalDate.of(2025, 9, 11)), new PhoneRepository.FidsData(127, "91115", LocalDate.of(2025, 9, 11)),
new PhoneService2.FidsData(128, "91115", LocalDate.of(2025, 9, 10)) new PhoneRepository.FidsData(128, "91115", LocalDate.of(2025, 9, 10))
) )
); );
Comparator<PhoneService2.FidsData> byDbOrder = Comparator<PhoneRepository.FidsData> byDbOrder =
Comparator.comparing(PhoneService2.FidsData::getPhone, Comparator.nullsFirst(Comparator.naturalOrder())) // pn.number ASC Comparator.comparing(PhoneRepository.FidsData::getPhone, Comparator.nullsFirst(Comparator.naturalOrder())) // pn.number ASC
.thenComparing(PhoneService2.FidsData::getFileSinceDate, Comparator.nullsLast(Comparator.reverseOrder())) // file_since_dt DESC .thenComparing(PhoneRepository.FidsData::getFileSinceDate, Comparator.nullsLast(Comparator.reverseOrder())) // file_since_dt DESC
.thenComparing(PhoneService2.FidsData::getFid, Comparator.reverseOrder()); .thenComparing(PhoneRepository.FidsData::getFid, Comparator.reverseOrder());
} }

View file

@ -5,10 +5,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import ru.nbch.credit_tracker.entities.dto.PhoneFidMap; import ru.nbch.credit_tracker.entities.dto.PhoneFidMap;
import ru.nbch.credit_tracker.repository.PhoneRepository;
import ru.nbch.credit_tracker.service.indic.PhoneService; import ru.nbch.credit_tracker.service.indic.PhoneService;
import java.time.LocalDate; import java.time.LocalDate;
@ -18,21 +16,20 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
public class PhoneServiceTest { public class PhoneServiceTest {
@Mock @Mock
NamedParameterJdbcTemplate jdbcTemplate; PhoneRepository phoneRepository;
@Test @Test
public void defineFidByPhoneTest() { public void defineFidByPhoneTest() {
PhoneService phoneService = new PhoneService(jdbcTemplate); PhoneService phoneService = new PhoneService(phoneRepository);
fidsDataWithEqualSinceDt.sort(byDbOrder); fidsDataWithEqualSinceDt.sort(byDbOrder);
when(jdbcTemplate.query(anyString(), any(MapSqlParameterSource.class), any(RowMapper.class))).thenReturn(fidsDataWithEqualSinceDt); when(phoneRepository.findFidsLong(any())).thenReturn(fidsDataWithEqualSinceDt);
List<PhoneFidMap> phoneFidMap = new ArrayList<>(); List<PhoneFidMap> phoneFidMap = new ArrayList<>();
phoneFidMap.add(PhoneFidMap.builder().phone("1111").build()); phoneFidMap.add(PhoneFidMap.builder().phone("1111").build());
@ -49,19 +46,19 @@ public class PhoneServiceTest {
Assertions.assertEquals("1115", res.get(127).getPhone()); Assertions.assertEquals("1115", res.get(127).getPhone());
} }
private final List<PhoneService.FidsData> fidsDataWithEqualSinceDt = new ArrayList<>( private final List<PhoneRepository.FidsDataLong> fidsDataWithEqualSinceDt = new ArrayList<>(
List.of( List.of(
new PhoneService.FidsData(123, 91111L, LocalDate.of(2025, 9, 10)), new PhoneRepository.FidsDataLong(123, 91111L, LocalDate.of(2025, 9, 10)),
new PhoneService.FidsData(124, 91111L, LocalDate.of(2025, 9, 10)), new PhoneRepository.FidsDataLong(124, 91111L, LocalDate.of(2025, 9, 10)),
new PhoneService.FidsData(125, 91112L, LocalDate.of(2024, 10, 10)), new PhoneRepository.FidsDataLong(125, 91112L, LocalDate.of(2024, 10, 10)),
new PhoneService.FidsData(126, 91114L, LocalDate.of(2024, 10, 11)), new PhoneRepository.FidsDataLong(126, 91114L, LocalDate.of(2024, 10, 11)),
new PhoneService.FidsData(127, 91115L, LocalDate.of(2025, 9, 11)), new PhoneRepository.FidsDataLong(127, 91115L, LocalDate.of(2025, 9, 11)),
new PhoneService.FidsData(128, 91115L, LocalDate.of(2025, 9, 10)) new PhoneRepository.FidsDataLong(128, 91115L, LocalDate.of(2025, 9, 10))
) )
); );
Comparator<PhoneService.FidsData> byDbOrder = Comparator<PhoneRepository.FidsDataLong> byDbOrder =
Comparator.comparing(PhoneService.FidsData::getPhone, Comparator.nullsFirst(Comparator.naturalOrder())) // pn.number ASC Comparator.comparing(PhoneRepository.FidsDataLong::getPhone, Comparator.nullsFirst(Comparator.naturalOrder())) // pn.number ASC
.thenComparing(PhoneService.FidsData::getFileSinceDate, Comparator.nullsLast(Comparator.reverseOrder())) // file_since_dt DESC .thenComparing(PhoneRepository.FidsDataLong::getFileSinceDate, Comparator.nullsLast(Comparator.reverseOrder())) // file_since_dt DESC
.thenComparing(PhoneService.FidsData::getFid, Comparator.reverseOrder()); .thenComparing(PhoneRepository.FidsDataLong::getFid, Comparator.reverseOrder());
} }