반응형

properties에서 개발환경과 운영환경을 나눈 후 배포 할 경우, IntelliJ에서는 설정에서 profile 설정이 가능하지만,

Tomcat에서 실행할 profile을 설정할 경우 아래와 같이 설정이 가능하다.

 

Tomcat 디렉토리 내의 bin 폴더에 아래와 같이 설정을 하고 재기동을 한다.

window

setenv.bat 파일 생성 후 아래 내용 추가(dev라는 profile로 실행할 경우)

set JAVA_OPTS=%JAVA_OPTS% -Dspring.profiles.active=dev

Linux

setenv.sh 파일 생성 후 아래 내용 추가(dev라는 profile로 실행할 경우)

export JAVA_OPTS="$JAVA_OPTS -Dspring.profiles.active=dev"

 

 

참고자료

https://steady-snail.tistory.com/151

 

톰캣 배포환경 운영/개발 나누기

Spring Framework와 Tomcat을 사용하는 환경에서 properties를 개발환경과 운영환경을 나눠놓았을 경우입니다. 그럴 때 이클립스에서는 톰캣을 더블클릭하여 open launch configure를 통해 argument("-Dspring.profiles

steady-snail.tistory.com

 

반응형
반응형

String으로 설정한 DB 컬럼에 JSON 형태로 된 값을 넣어야했다.

 

JSON.parse(), JSON.Stringify()의 존재는 알고 있었는데

막상 JSON으로 만들 객체를 만들려니 헷갈려서 찾아보게 되었고 참고자료가 매우x1000000 도움이 되었다.

 

내가 하려고 한 작업은 아래와 같다.

각각 firstInput, secondInput, thirdInput으로 ID를 설정한 input 값을 가져 온 후

JSON 형태로 객체를 만들고 allInput이라는 hidden 설정 되어있는 input값에 넣은 후 Controller에 넘긴다.

 

JSON.stringify(); (JSON -> String 변환)

function getInputVal(){
    var data = new Object();
    data.first_input_value = $('#firstInput').val();
    data.second_input_value = $('#secondInput').val();
    data.third_input_value = $('#thirdInput').val();

    var jsonData = JSON.stringify(data);
    $('#allInput').val(jsonData);
}

Controller에서 로그를 찍어보면 아래처럼 String 타입으로 넘어가는 것을 확인할 수 있다.

{"first_input_value":"firstValue","second_input_value":"secondValue","third_input_value":"thirdValue"}

 

 

JSON.parse(); String-> JSON 변환

let allScore = JSON.parse(data.allScore);

JSON.parse({String type으로 JavaScript에 넘어온 데이터값});

JavaScript 콘솔에서 로그를 찍어오면 아래와 같이 JSON 형태로 변환 된 것을 확인할 수 있다.

first_input_value: "firstValue"
second_input_value: "secondValue"
third_input_value: "thirdValue"

 

 

 

 

참고 자료

https://fruitdev.tistory.com/190

 

[Javascript] JSON 형태의 데이터 생성하기

업무를 진행하다 보면 Json 형태의 데이터를 생성하여 전송하거나, 받는 경우가 종종 발생 한다. json 형태의 데이터는 일일히 문자열로 쭉 나열하여 규칙대로 만들수도 있지만, 구조가 복잡해 지

fruitdev.tistory.com

 

반응형
반응형

사용한 부트스트랩 토글은 아래 사이트의 토글 버튼이다.

 

https://www.bootstraptoggle.com/

 

Bootstrap Toggle

offstyle string "default" Style of the off toggle.Possible values are:default,primary,success,info,warning,danger Refer to Bootstrap Button Options documentation for more information.

www.bootstraptoggle.com

 

구현하고 싶은 버튼

구현된 버튼

사이즈는 별도로 조정한것이고 옆에 흰색 부분이 구현이 안되어서 찾아본 결과 CSS를 추가하여 해결했다.

 

참고한 코드

.toggle > .toggle-group {
    background:white;
    box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
    color:#666;
}

.toggle.off {
    border-color: rgba(0, 0, 0, .25);
}

.toggle-handle {
    background-color: white;
    border: thin rgba(0, 0, 0, .25) solid;
}

 

실제로 사용한 코드

.toggle-off {
    border-color: rgba(0, 0, 0, .25);
}
.toggle-handle {
    background-color: white;
    border: thin rgba(0, 0, 0, .25) solid;
}

.toggle > .toggle-group CSS 부분은 크기를 조정해서 그런지 버튼 아랫부분에 흰줄이 생겨서 삭제했는데 구현하는 데에는 무방했다.

 

완성한 버튼 화면

 

 

참고 자료

https://github.com/minhur/bootstrap-toggle/issues/186

 

Bootstrap 4 fix without altering original js/css · Issue #186 · minhur/bootstrap-toggle

If you want to update the official version of bootstrap-toggle to work with Bootstrap 4, without altering the source js / css or forking the project, you can add the following CSS tweaks to your ow...

github.com

 

반응형
반응형

스크롤바를 설정할 영역에 overflow-y : scroll; 설정을 준다.

예를 들어, 페이지 내에 항상 스크롤바를 만들 경우는 아래와 같이 CSS를 설정해준다.

html { overflow-y:scroll; }

내가 실제로 적용한 것은 모달을 띄웠는데 모달창이 컴퓨터 화면 보다 크게 설정되었고, 스크롤바가 생성되지 않아서

JavaScript에서 모달이 띄워졌을때 이벤트를 주어 모달 기준으로 스크롤바를 추가했다.

 

$('#모달아이디값').css('overflow-y','scroll');

 

참고 자료

https://88240.tistory.com/41

 

[CSS] 페이지 내에 항상 스크롤바가 있게하기

홈페이지를 만들다 보면 미세한 1픽셀의 차이로 디자인을 수정해야할 때가 많다. 특히 페이지 이동할때 화면이 덜컥덜컥 거리는 듯한 느낌이 받을때, 제일 먼저 스크롤바를 의심하라. 어떤 페이

88240.tistory.com

 

반응형
반응형

유효성 검사는 당연히 JS 영역에서 하는거라고 생각했다.

이번에 유효성 검사를 해야하는 부분이 있었는데, 찾아보니 Controller, DTO 하기 나름이라더라..

특히 JS에서만 유효성 검사를 하는 것은 안전하지 않기 때문에 백단에서 해주는게 좋다고 하는것 같았다.

 

내가 이번에 맡은 부분 중 추가/수정 모달창에 입력한 값에 대한 유효성 검사를 해야했다.

추가/수정 모두 한개의 DTO를 사용하고 있는데,

추가할 경우는 필수값이지만 수정할 경우는 display:none으로 값을 받지 않는 컬럼값이 있어서 이부분에서 좀 오~~~래 해맸다.

 

결론으로는 @Validated와 DTO 유효성 설정시 groups를 나누는 것이 핵심!

 

 

pom.xml

(Spring Boot 2.3 버전 이후인 경우)

우선 @Validated 어노테이션을 사용하기 위해서 dependency를 추가해야한다.

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-validation</artifactId>
   <version>3.0.2</version>
</dependency>

 

DTO interface

생성, 수정할때의 유효성 검사가 다르기 때문에 interface 변수를 별도로 선언한다.

package com.beige0905.test.dto;

public class UserValidGroups {
    public interface createValid {};
    public interface modifyValid {};
}

 

DTO

유효성 검사 변수는 아래와 같이 설정했으며, address는 유효성 검사 제외이다.

생성시 : userType, userName, userCode

수정시 : userType, userName

변수에 선언한 유효성검사 어노테이션의 groups에 위에서 선언해둔 interface 클래스를 넣는다.

package com.beige0905.test.dto;

import lombok.*;

import javax.validation.constraints.*;

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {

   @NotNull(message = "학생/교사 중에서 선택하세요"
         ,groups = {UserValidGroups.createValid.class, UserValidGroups.modifyValid.class})
   String userType;

   @Pattern(regexp = "^([ㄱ-ㅎ가-힣A-Za-z])(\\s?[ㄱ-ㅎ가-힣A-Za-z])*$", message = "한글, 영문만 입력 가능합니다."
         ,groups = {UserValidGroups.createValid.class, UserValidGroups.modifyValid.class})
   @NotEmpty(message = "이름을 입력해 주세요."
         ,groups = {UserValidGroups.createValid.class, UserValidGroups.modifyValid.class})
   String userName;

   @Pattern(regexp = "^([0-9])(\\s?[0-9])*$", message = "숫자만 입력 가능합니다."
         ,groups = {UserValidGroups.createValid.class})
   @NotEmpty(message = "인증코드를 입력해주세요."
         ,groups = {UserValidGroups.createValid.class})
   String userCode;
   
   String address;
}

 

Controller

컨트롤러에 가져올 매개변수 앞에 @Validated(interface명) 어노테이션을 달아준다. 

유효성검사에 에러를 가져오기 위해 BindingResult 매개변수를 추가해주면 getFieldErrors() 메소드로 에러를 가져올수 있다.

@PostMapping(path = "/add-valid-check-catalog", consumes = {MediaType.APPLICATION_JSON_VALUE})
public List<FieldError> postAddValidCheckUser(@Validated(UserValidGroups.createValid.class) @RequestBody User userInfo, BindingResult bindingResult) {
   List<FieldError> allErrors = new ArrayList<>();
   if(bindingResult.hasErrors()){
      allErrors = bindingResult.getFieldErrors();
   }
   return allErrors;
}
@PostMapping(path = "/modify-valid-check-catalog", consumes = {MediaType.APPLICATION_JSON_VALUE})
public List<FieldError> postModifyValidCheckUser(@Validated(UserValidGroups.modifyValid.class) @RequestBody User userInfo, BindingResult bindingResult) {
   List<FieldError> allErrors = new ArrayList<>();
   if(bindingResult.hasErrors()){
      allErrors = bindingResult.getFieldErrors();
   }
   return allErrors;
}

 

 

참고 자료

https://wildeveloperetrain.tistory.com/25

 

Spring Boot Validation 적용하는 법, @Valid 작동 안되는 이유

* @Valid 어노테이션이 작동이 안 한다면? spring boot 2.3 version 이상부터는 spring-boot-starter-web 의존성 내부에 있던 validation이 사라졌습니다. 때문에 사용하시는 spring boot version이 2.3 이상이라면 validatio

wildeveloperetrain.tistory.com

https://me-analyzingdata.tistory.com/entry/Spring-Controller-request-%EC%9C%A0%ED%9A%A8%EC%84%B1-%EA%B2%80%EC%82%AC?category=1034382 

 

[Spring] Controller request 유효성 검사

Request 유효성 검사 서버는 client로 부터 값을 받아서 사용할 때 client로 받은 정보가 서버에서 원하는 형식 혹은 값의 조건에 맞는지 검사를 해야한다. 이를 유효성 검사라고 한다. 이러한 유효성

me-analyzingdata.tistory.com

 

반응형
반응형

아직 신입이지만 개인적으로 개발하고 싶은게 생겨서 시간이 나면 틈틈히 개발할 생각이다.

회사에서는 Window 환경에서 개발을 하기 때문에 기왕이면 OS 환경에서 개발하는 것도 익혀두고 싶다고 생각이 들어

오년전에 사고 묵혀두었던 맥북을 꺼내고 인텔리제이를 설치했다.

 

설정은 보통 한번하고 안건들이고 프로젝트는 회사에서는 import만 하니까 매번 헷갈려서 하는김에 정리하고자 한다.

 

jdk11 설치

회사에서 jdk1.8을 사용하기 때문에 집에서는 jdk11로 결정

아래 사이트에서 DMG 파일을 다운받으면 된다.

https://www.oracle.com/kr/java/technologies/downloads/#java11-mac

 

 

- 설치 후 터미널에서 java version 확인

java -version

설치된 모든 java 버전을 확인하려면 아래 명령어로 확인 가능하다.

/usr/libexec/java_home -V

 

환경변수 설정

- Java 설치 경로 확인

cd Library/Java/JavaVirtualMachines

 

- 편집기를 열어주고 i 를 입력하여 insert 모드로 전환

vi ~/.bash_profile

- JAVA_HOME과 PATH export

export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home
export PATH=${PATH}:/Library/Java/VirtualMachines/jdk-11.jdk/Contents/Home

jdk 버전은 경로에서 확인한 본인의 버전으로 변경!!!

입력 후 esc를 눌러 insert 모드를 종료하고 :wq를 입력한 후 엔터로 편집기를 종료한다

 

 

 

 


 

참고자료

https://ribo.dev/4

 

[macOS] 자바(JDK 1.8) 설치 하기 및 버전 확인

macOS 에서 자바를 설치하는 방법으로는 두 가지가 있다. 하나는 Home brew를 활용하여 쉽게 설치하는 법과 다른 하나는 오라클 홈페이지에서 직접 dmg파일을 받아서 설치하는 방법이 있다, 오늘은

ribo.dev

https://shanepark.tistory.com/88

 

Mac ) 설치되어있는 모든 자바 버전 확인하기

/usr/libexec/java_home -V 위의 명령어를 터미널에서 입력하면 됩니다. 위와 같이 설치되어 있는 모든 자바의 경로와 버전이 나옵니다. java -version 을 입력하면 지금 기본으로 사용중인 자바 버전을 확

shanepark.tistory.com

 

반응형
반응형

Pageable를 이용하여 Paging 처리를 하는 도중, Query문에서 SUM과 Group By를 이용하는 Native Query를 사용하게 되었다.

 

기존 방법대로 pageable을 이용한 JPA 코드는 굉장히 간단하다

만약 학생의 이름을 기준으로 학생 정보를 가져오고 싶은 경우 아래 코드와 같다.

package com.beige0905.test.repository;

import com.beige0905.test.repository.vo.StudentVo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StudentRepository extends JpaRepository<StudentVo, String> {
    Page<StudentVo> findByStudentName(String studentName, Pageable pageable);
}

 

하지만 집계함수와 Group By를 쓰고싶다면?

기본적으로 지원해주는 JPA 문법이 없기 때문에 Native Query를 사용해야 한다.

경로를 설정하여 xml 파일에 쿼리문을 모아두는 방법도 있지만 Query Annotation을 사용해서 작성하는게 개인적으로는 바로 쿼리문을 확인 할 수 있어서 좋은 것 같은데 뭐가 더 나은지는 아직 잘 모르겠다.

때문에 Query 어노테이션을 사용하여 아래와 같이 작성했다. 

Service

@Service
public class StudentService {

    @Autowired
    StudentRepository studentRepository;

    public Page<StudentVo> studentTotalScore(List<ColumnSearch> search, Pageable pageable) {
        log.info("search ==>" + search);
        //search ==>[StudentService.ColumnSearch(targetColumn=start, searchValue=2023-02-28), StudentService.ColumnSearch(targetColumn=end, searchValue=2023-03-06)]
        log.info("pageable ==>" + pageable);
        //pageable ==>Page request [number: 0, size 10, sort: studentId: ASC]

        String startDate = null;
        String endDate = null;

        for(ColumnSearch searchVo : search) {
            if(searchVo.getTargetColumn().equalsIgnoreCase("start")) {
                startDate = searchVo.getSearchValue();
            } else if(searchVo.getTargetColumn().equalsIgnoreCase("end")) {
                endDate = searchVo.getSearchValue();
            } else {
                log.info("check start date or end date");
            }
        }

        return studentRepository.getStudentTotalScore(startDate, endDate, pageable);
    }
}

Repository

package com.beige0905.test.repository;

import com.beige0905.test.repository.vo.StudentVo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

@Repository
public interface StudentRepository extends JpaRepository<StudentVo, String> {
@Query("SELECT v.studentId AS studentId, v.studentName AS studentName, SUM(v.mathScore) AS totalMathScore, "
        +"SUM(v.engScore) AS totalEngScore, v.className AS className, v.grade AS grade "
        +"FROM StudentVo v WHERE v.insertDate BETWEEN ?1 AND ?2 GROUP BY v.studentId, v.studentName, v.className, v.grade")
        Page<StudentVo> getStudentTotalScore(String startDate, String endDate, Pageable pageable);
        
}

VO

package com.beige0905.test.repository.vo;

import lombok.*;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "student_info")
public class StudentVo {
    @Id
    @Column(name = "student_id")
    private int studentId;

    @Column(name = "student_name")
    private String studentName;

    @Column(name = "math_score")
    private int totalMathScore;

    @Column(name = "eng_score")
    private int totalEngScore;

    @Column(name = "class_name")
    private String className;

    @Column(name = "grade")
    private int grade;

    @Column(name = "insert_date")
    private String insertDate;
}

 

이렇게 돌린다면?

 

아래와 같은 오류가 발생한다.

java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to {VO path}

로그를 찍어보니 Service단에 return type이 Page<StudentVo> 인데 repository의 결과를 로그로 찍어보면

[[value, value, value], [value, value, value]] 로 Object Array로 나와서 발생하는 오류이다.

구글링을 해보니 기존 JPA 쿼리는 Entity 객체를 결과로 만들지만 집계 함수가 있는 쿼리의 경우 Object 배열을 반환하기 때문에 내가 선언한 Page<VO>가 아닌 Page<Object>로 반환 된 것이다.

 

처음엔 JPA문으로 날짜를 필터한 후 List로 반환한 후, 반환한 List를 stream으로 Group By를 적용하는 방법을 시도하였는다.

하지만 pageable을 적용하는 부분에서 코드가 너무 복잡해져서 맞지 않다고 판단했다.

stream으로 적용했던 방법은 더보기에 있다. 왜 사용하지 않기로 판단했는지 알수 있다 ㅎㅎㅎ.... 

더보기
public Page<StudentVo> seachList(List<Search> search, Pageable pageable) {
		String startDate = null;
        String endDate = null;

        String[] orderBy = String.valueOf(pageable.getSort()).replaceAll(" ","").split(":");
        String aa = orderBy[0] + " " + orderBy[1];

        for(ColumnSearch searchVo : search) {
            if(searchVo.getTargetColumn().equalsIgnoreCase("start")) {
                startDate = searchVo.getSearchValue();
            } else if(searchVo.getTargetColumn().equalsIgnoreCase("end")) {
                endDate = searchVo.getSearchValue();
            } else {
                log.info("check start date or end date");
            }
        }
        
        List<StudentVo> result = studentRepository.getStudentTotalScore(startDate, endDate);

        Collections.sort(result);

        List<StudentVo> sortResult = new ArrayList<>();
        if(orderBy[1].equals("ASC")) {
            switch(orderBy[0]) {
                case "sudentId":
                    sortResult = result.stream().sorted(Comparator.comparing(StudentVo::sudentId)).collect(Collectors.toList());
                    break;
                case "studentName":
                    sortResult = result.stream().sorted(Comparator.comparing(StudentVo::studentName)).collect(Collectors.toList());
                    break;
                case "totalMathScore":
                    sortResult = result.stream().sorted(Comparator.comparing(StudentVo::totalMathScore)).collect(Collectors.toList());
                    break;
                case "totalEngScore":
                    sortResult = result.stream().sorted(Comparator.comparing(StudentVo::totalEngScore)).collect(Collectors.toList());
                    break;
                default:
                    log.info("ORDER BY ASC 컬럼명 오류");
                    break;
            }
        } else if(orderBy[1].equals("DESC")) {
            switch (orderBy[0]) {
                case "sudentId":
                    sortResult = result.stream().sorted(Comparator.comparing(StudentVo::sudentId).reversed()).collect(Collectors.toList());
                    break;
                case "studentName":
                    sortResult = result.stream().sorted(Comparator.comparing(StudentVo::studentName).reversed()).collect(Collectors.toList());
                    break;
                case "totalMathScore":
                    sortResult = result.stream().sorted(Comparator.comparing(StudentVo::totalMathScore).reversed()).collect(Collectors.toList());
                    break;
                case "totalEngScore":
                    sortResult = result.stream().sorted(Comparator.comparing(StudentVo::totalEngScore).reversed()).collect(Collectors.toList());
                    break;
                default:
                    log.info("ORDER BY DESC 컬럼명 오류");
                    break;
            }
        }

        PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize());
        int start = (int) pageRequest.getOffset();
        int end = Math.min((start + pageRequest.getPageSize()), sortResult.size());

        Page<StudentVo> page = new PageImpl<>(sortResult.subList(start, end), pageRequest, sortResult.size());
        log.info("Page : {} ", page.getContent());
    }
        return page;
}

 

열심히 삽질하다가 찾은 것이 Projection을 이용하는 방법이다.

 

먼저 필요한 기존VO에서 데이터들(DTO로 변환할 데이터)만 모은 인터페이스 VO를 생성한다.

Interface VO

package com.beige0905.test.repository.vo;

public interface IStudentVo {
    int getStudentId();
    String getStudentName();
    int getTotalMathScore();
    int getTotalEngScore();
}

기존 VO에 .map 함수를 이용하여 IStudentVo -> StudentVo로 Convert 해주는 method를 추가한다.

VO

package com.beige0905.test.repository.vo;

import lombok.*;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "student_info")
public class StudentVo {
	...
    
    public static StudentVo convertToPage(IStudentVo vo) {
        return StudentVo.builder()
                .studentId(vo.getStudentId())
                .studentName(vo.getStudentName())
                .totalMathScore(vo.getTotalMathScore())
                .totalEngScore(vo.getTotalEngScore())
                .build();
    }
}

Service단에 repository에서 return 받은 값을 map 함수에 convert method를 담아 변환한다.

Service

@Service
public class StudentService {

    @Autowired
    StudentRepository studentRepository;

    public Page<StudentVo> studentTotalScore(List<ColumnSearch> search, Pageable pageable) {
        log.info("search ==>" + search);
        //search ==>[StudentService.ColumnSearch(targetColumn=start, searchValue=2023-02-28), StudentService.ColumnSearch(targetColumn=end, searchValue=2023-03-06)]
        log.info("pageable ==>" + pageable);
        //pageable ==>Page request [number: 0, size 10, sort: studentId: ASC]

        String startDate = null;
        String endDate = null;

        for(ColumnSearch searchVo : search) {
            if(searchVo.getTargetColumn().equalsIgnoreCase("start")) {
                startDate = searchVo.getSearchValue();
            } else if(searchVo.getTargetColumn().equalsIgnoreCase("end")) {
                endDate = searchVo.getSearchValue();
            } else {
                log.info("check start date or end date");
            }
        }

        return studentRepository.getStudentTotalScore(startDate, endDate, pageable).map(StudentVo::convertToPage);
    }
}

 

 

참고자료

https://medium.com/@odysseymoon/spring-data-jpa%EC%97%90%EC%84%9C-groupby-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0-82cddc6e5d4a

 

Spring Data JPA에서 GroupBy 처리하기

Spring Data JPA에서는 GroupBy 를 사용하려면 어떻게 해야 할까요? Reference Doc 에서도 Group By 관련 키워드는 찾아보기 힘듭니다. Group By를 이용하려면 다른 방식을 적용해야 하는데요, Spring…

medium.com

https://beppy.hatenablog.com/entry/2022/06/11/162211

 

[Java]StreamのgroupingByで、カテゴリごとの合計(Sum)を求める方法 - 技術と日常。

前提 結果がMap int, long, doubleの場合 それ以外の型の場合 結果が元の型のCollection 参考 前提 例えば以下のような、カテゴリとその量が定義されているクラスがあったとします。 public record Obj( S

beppy.hatenablog.com

 

반응형

+ Recent posts