RecordService

강한친구·2022년 7월 18일
0

기능

정보 기록

유저가 앱에서 기록한 정보를 프론트가 가공해서 백으로 보내주고, 백에서 정보를 받아서 Records 테이블에 저장하는 기능이다.

API Spec

//Records DTO
@Getter @Setter
public class RecordsDto {

    List<Integer> concentratedTime = new ArrayList<Integer>();
    private String timeStamp;
}

프론트단에서는 리스트에 그날의 공부시간을 기록한다. 1회 방해를 받을때마다 기록이 초기화된다. 또한 언제 공부한 기록인지를 기록하기위해 IOS TimeStamp가 넘어온다.

Records Entity

@Getter @Setter
@Entity
@Table(name = "Records")
public class Records {

    @ManyToOne // 연관관계가 이게 맞나
    @JoinColumn(name = "MEMBER_ID")
    private Member member;

    @Id @GeneratedValue
    @Column(name = "RECORD_ID")
    private long recordId;

    @Column(nullable = false)
    private int brokenCounter;

    @Column(nullable = false)
    private int maxConcentrationTime;

    @Column(nullable = false)
    private int total_time;

    @Column(nullable = false)
    private String timeStamp;

    // 비즈니스 로직 //
    public static Records createRecords(Member member, RecordsDto recordsDto) {
        Records records = new Records();
        records.setMember(member);
        List<Integer> dataHolder = recordsDto.getConcentratedTime();
        records.setTimeStamp(recordsDto.getTimeStamp());

        if (dataHolder.isEmpty()) {
            // 작동할 일이 있을까? (validation 추가하면 없애도 될듯0
            records.setBrokenCounter(0);
            records.setMaxConcentrationTime(0);
        } else {
            int max = 0;
            int sum = 0;
            for (Integer integer : dataHolder) {
                sum += integer;
                if (integer > max) max = integer;
            }
            records.setBrokenCounter(dataHolder.size());
            records.setMaxConcentrationTime(max);
            records.setTotal_time(sum);
        }
        return records;
    }
}

Record는 Member와 다대일 관계이다. 하나의 맴버는 여러개의 레코드를 가질 수 있기 때문이다.

프론트단에서 List형태로 자료를 넘겨주기에, 이를 받아서 별도의 가공을 통해서 Entity에 맞게 넣어야한다. 이를 수행해주는 메소드가 createRecords 메소드이다.

List의 길이는 총 방해 횟수, 전체 합은 총 집중 시간, 최대 집중값은 리스트의 최대값이 된다. 이를 통해 새로운 records 객체를 만들어준다.

Controller

@RestController
@RequiredArgsConstructor
public class RecordController {

    private final MemberService memberService;
    private final RecordService recordService;


    @PostMapping("/send/data")
    public void receiveRecordV2(@RequestBody RecordsDto recordsDto, HttpServletRequest request) {
        String username = getUsername(request);
        recordService.save(username, recordsDto);
    }

    @GetMapping("/showRecords")
    public Records showTodayRecord(HttpServletRequest request) {
        String username = getUsername(request);
        Member member = memberService.findMemberByUserName(username);
        String timeStamp = LocalDate.now().toString();
        return recordService.findRecordByTimeStamp(member, timeStamp);
    }

    // 지정 날짜로 검색
    // 항상 YYYY-MM-DD 형식을 지킬것
    // 아직 구현 안함
//    @GetMapping("/showRecords/date")
//    public Records showRecordByDate(@PathVariable long memberId, @RequestParam("date") String date, HttpServletRequest request) {
//        long id = getIdByuserName(request);
//        String timeStamp = LocalDate.now().toString();
//        return recordService.findRecordByTimeStamp(id, timeStamp);
//    }

    private String getUsername (HttpServletRequest request) {
        HttpSession session = request.getSession(false);

        SecurityContextImpl context = (SecurityContextImpl)session.getAttribute("SPRING_SECURITY_CONTEXT");
        return context.getAuthentication().getName();
    }
}

컨트롤러 구현부분이다.
Json 데이터는 전부 @RequestBody로 받아서 처리하고있다. 로그인된 사용자만 정보를 보낼 수 있다. 따라서 사용자가 보낸 request에서 username을 꺼내서 사용자를 식별해야한다.

sendData

send/data는 자료를 받아서 service -> repository 순서로 처리한다.

findByTimeStamp

showRecords는 get 요청이 오면, 오늘날짜의 레코드를 반환하는 기능이다.

특별히 어려운 로직은 없으니 생략하도록 하겠다.

getUsername 메서드

    private String getUsername (HttpServletRequest request) {
        HttpSession session = request.getSession(false);

        SecurityContextImpl context = (SecurityContextImpl)session.getAttribute("SPRING_SECURITY_CONTEXT");
        return context.getAuthentication().getName();
    }

과거 직접 만든 Session과 Interceptor를 사용해서 로그인 처리를 했을때는, 직접 session에다가 member 객체정보를 집어넣었기에 꺼내는데 큰 불편함이 없었다.

springSecurity를 사용하기위해 바꾸고나서는 session이 자동생성으로 변경되어서, 이를 내 맘대로 수정할 수도 있지만, 기본으로 주는걸 사용하면 SPRING_SECURITY_CONTEXT라는 이름의 Session이 생긴다.

이 세션은 SecurityContextImpl 타입인데, 이 타입 안에는 getAuthentication이 있고 그 안에는 Principal을 가져오는 기능이 이싸디. 따라서 이를 통해 우리는 username을 가지고 오고, 이를 식별자로 사용하기로 하였다.

이게 최선의 방법인지는 잘 모르겠다.
자세한 메서드 탐색은 추후 따로 다루도록 하겠다.

RecordsService

@Repository
@RequiredArgsConstructor
@Transactional
public class RecordService {

    private final MemberRepository memberRepository;
    private final MemberService memberService;
    private final RecordRepository recordRepository;

    public void save(String username, RecordsDto recordsDto) {
        Member member = memberRepository.findByUsername(username);
        Records records = Records.createRecords(member, recordsDto);
        recordRepository.save(records);
    }

    public Records findRecordByTimeStamp(String username, String timeStamp) {
        Member member = memberService.findMemberByUserName(username);
        return recordRepository.findRecordByTimeStamp(member, timeStamp);
    }
}

save

서비스에서는 username을 기반으로 member를 찾고, Records의 createRecords를 통해서 새로운 records 객체를 만들어낸다. 이를 기반으로 repository에 넘기고 save 처리한다.

findByTimeStamp

마찬가지로 member를 찾고, 이를 기반으로 repository로 넘긴다.

RecordsRepository

@Repository
@RequiredArgsConstructor
public class RecordRepository {

    private final EntityManager em;

    //레코드 Dto를 받아서, 이를 Records로 변환하고 db에 저장
    public Records save(Member member, Records records) {
        em.persist(records);
        return records;
    }

    public Records findRecordByMemberId(long memberId) {
//        Records records = em.find(Records.class, memberId); // 이거는 pk만 가능
        List<Records> resultList = em.createQuery("select r from Records r where r.member = :memberId", Records.class)
                .setParameter("memberId", memberId)
                .getResultList();
        for (Records records : resultList) {
            long recordId = records.getRecordId();
            System.out.println("recordId = " + recordId);
        }
        Records records = resultList.get(0);
        return records;
    }

    public Records findRecordByTimeStamp(Member member, String timeStamp) {
        List<Records> resultList = em.createQuery("select r from Records r where r.member = :member", Records.class)
                .setParameter("member", member)
                .getResultList();
        for (Records value : resultList) {
            String valueTimeStamp = value.getTimeStamp();
            String cut = valueTimeStamp.substring(0, 10);
            if ((value.getMember().getId() == member.getId()) && (cut.equals(timeStamp))) {
                String timeStamp1 = value.getTimeStamp();
                return value;
            }
        }
        // 아무것도 안돌아오면 exception 처리 후 안내메시지를 올린다?
        return null;
    }
}

save

단순히 em.persist를 통해서 영속성 컨텍스트를 만들고 등록하면 된다.

findRecordByTimeStamp

jpql기능을 이용해서 timeStamp로 레코드를 찾는쿼리를 날린다.

값을 찾았으면, 검증로직을 1회 돌린 후 반환한다.
다만 검증로직은 추후 필요없다고 판단되면 제거할 생각이다.

정리

추후 Records에 수정이 일어날 수 있기때문에 records 검색하면 이를 반환할 때 사용되는 RecordsEntity가 작성되어야한다.

또한, 나중에 데이터가 많이 쌓이면, 효율적으로 쿼리를 날리는 방법을 연구해봐야한다.

PostManTest

테스트용 로컬맴버인 Jameson으로 테스트해보겠다.

로그인

우선 로그인을 해서 Session을 만들어야한다. 다만, 이는 테스트용 formLogin이고, 추후 로그인은 Json 로그인 혹은 OAuth로그인으로 구현된다.

로그인하고나면 Security에 의해서 session이 생긴것을 알 수 있다.

정보 보내기

작성된 로직에 의해 이렇게 정보가 들어가는것을 확인할 수 있다.

정보 검색

보이는것처럼, Records Entity에 있는 member 객체가 통으로 넘어와서, 불필요한 정보까지 같이 넘어가고 있는것을 볼 수 있다.

따라서 이 부분은 추후 RecordsReturnerDto를 통해서 처리를 해야 한다.

0개의 댓글