반응형

보통 이 ERROR는 LOGS 폴더에 대한 권한 문제이거나 메모리 사용량 문제일 경우도 있는데 나는 그 두 케이스에 해당되지 않았다.

 

테스트를 위해 짧은 간격으로 스케줄을 걸어 확인했을 때는 정상 작동하는 경우도 있고 작동하지 않고 LOGS 하위 폴더에 log 파일도 생성하지 않는 경우도 있다.

 

 

Airflow UI 내 log

*** Could not read served logs: Request URL is missing an 'http://' or 'https://' protocol.

 

Airflow_Webserver.log

[2024-03-27 09:33:32 +0900] [3098307] [WARNING] Invalid request from ip=192.168.21.145: [SSL: SSLV3_ALERT_CERTIFICATE_UNKNOWN] sslv3 alert certificate unknown (_ssl.c:2633)
[2024-03-27T09:33:32.142+0900] {file_task_handler.py:568} ERROR - Could not read served logs
Traceback (most recent call last):
  File "/app/airflow/httpx/_transports/default.py", line 60, in map_httpcore_exceptions
    yield
  File "/app/airflow/httpx/_transports/default.py", line 218, in handle_request
    resp = self._pool.handle_request(req)
  File "/app/airflow/httpcore/_sync/connection_pool.py", line 208, in handle_request
    raise UnsupportedProtocol(
httpcore.UnsupportedProtocol: Request URL is missing an 'http://' or 'https://' protocol.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/app/airflow/airflow/utils/log/file_task_handler.py", line 546, in _read_from_logs_server
    response = _fetch_logs_from_service(url, rel_path)
  File "/app/airflow/airflow/utils/log/file_task_handler.py", line 92, in _fetch_logs_from_service
    response = httpx.get(
  File "/app/airflow/httpx/_api.py", line 189, in get
    return request(
  File "/app/airflow/httpx/_api.py", line 100, in request
    return client.request(
  File "/app/airflow/httpx/_client.py", line 821, in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
  File "/app/airflow/httpx/_client.py", line 908, in send
    response = self._send_handling_auth(
  File "/app/airflow/httpx/_client.py", line 936, in _send_handling_auth
    response = self._send_handling_redirects(
  File "/app/airflow/httpx/_client.py", line 973, in _send_handling_redirects
    response = self._send_single_request(request)
  File "/app/airflow/httpx/_client.py", line 1009, in _send_single_request
    response = transport.handle_request(request)
  File "/app/airflow/httpx/_transports/default.py", line 218, in handle_request
    resp = self._pool.handle_request(req)
  File "/usr/lib64/python3.9/contextlib.py", line 137, in __exit__
    self.gen.throw(typ, value, traceback)
  File "/app/airflow/httpx/_transports/default.py", line 77, in map_httpcore_exceptions
    raise mapped_exc(message) from exc
httpx.UnsupportedProtocol: Request URL is missing an 'http://' or 'https://' protocol.

 

 


 

개발 환경은 서버 1개, DB 1개라 문제가 없는데

지금 문제는 프로덕션 환경에서 서버 2개, DB 1개일 때에 해당된다.

 

서버 1번 ) webaserver 및 scheduler 기동 중 DAG 생성 완료

서버 2번 ) 서버 1과 같은 세팅 완료. DAG만 없음 ( webaserver 및 scheduler 기동 중 )

 

AIRFLOW는 서버가 다르더라도 DB 메타 데이터 기준으로 WORKER들을 생성한다.

 

1번 서버에 5분 간격으로 실행하는 DAG를 업로드하였으나

2번에는 DAG는 없이 WORKER만 보내진 격이니 에러를 뱉어낼 수 밖에!

 

생각도 못했던 2번 서버 LOGS 하위 폴더에 log 파일이 생성되어 있었다.

[2024-03-27T13:02:00.691+0900] {scheduler_job_runner.py:781} ERROR - 
Executor reports task instance <TaskInstance: scheduled__2024-03-27T04:00:00+00:00 
[queued]> finished (failed) although the task says it's queued. (Info: None) Was the task killed externally?

 

 

2번 서버 스케줄러 중단하니 말끔하게 처리ㅠ

혹은 2번 서버에도 DAG를 넣어주면 된다.

 

요약) 서버는 달라도 DB 기준으로 실제 WORK가 작동하고 DAG는 각각의 서버에서 관리된다.

반응형
반응형

오랜만의 블로그 포스팅..나는 요즘 AIRFLOW로 잡 스케줄링하는 업무를 맡았다.

 

airflow.cfg

# Whether to load the DAG examples that ship with Airflow. It's good to
# get started, but you probably want to set this to ``False`` in a production
# environment
#
# Variable: AIRFLOW__CORE__LOAD_EXAMPLES
#
#load_examples = True
load_examples = False

 

처음 세팅할 때 load_examples = False로 세팅

 

예시 사용 후 삭제할 때는?

 

airflow.cfg에  load_examples = False로 세팅 후 데이터베이스에 남아있는 이력들을 지워야 한다.

(혹은 UI에서 삭제하라는데 너무 많아서 개발 중이니 이 방법 씀)

$ airflow db init

 

속 시원.. 내가 만든 DAG만 있다!

반응형
반응형

 

이 글은 확실한 자료가 아니고 그냥 검색 기록이다.

 

쉽게 말하자면 logback은 log4j의 업그레이드 버전이다.

스프링부트에는 기본적으로 라이브러리가 탑재되어서 간편하게 쓸 수 있다. 나는 생짜배기 jar로 넣어야하기 때문에 열심히 조사하

 

 

🔩logback 라이브러리

 (보안위험 log4j 버전 주의!!!!!!!!!)

 

👀 logback 사용

logback-classic (logback-core, SLF4J API 라이브러리 포함)

 

👀  log4j로 쿼리 정렬 및 파라미터 대입

log4jdbc-log4j2-jdbc4.1-1.16.jar

 

라이브러리 뭐가 이리 많은지 어질어질하다. 글마다 필수 라이브러리 목록도 다르고,,

더보기

commons-logging-1.2.jar

logback-classic-1.2.3.jar 

logback-core-1.2.3.jar

log4jdbc-log4j2-jdbc4.1-1.16.jar

log4j-1.2.17.jar

slf4j-api-1.6.6.jar

jcl-over-slf4j-1.7.6.jar

 

gradle, maven, jar 파일이든 아래 사이트에 들어가서 다운로드 가능하다.

https://mvnrepository.com/


내가 본 설정 파일들은 이렇다.

  • logback.xml
  • log4jdbc.log4j2.properties
  • context-datasource.xml
  • jdbcs.properties

 

🔩 logback.xml

경로 src\main\resources

  • logger ( TRACE, DEBUG, INFO, WARN, ERROR)
  • root

 

🔩 log4jdbc.log4j2.properties

logback.xml과 같은 경로에 있어야 한다는데 내가 보는 프로젝트는 하위 log 패키지에 있다.

하위 패키지까지 다 찾는건지,, 경로 지정하는 곳을 못찾아서 확신이 없다.

log4jdbc.drivers=org.postgresql.Driver
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
log4jdbc.dump.sql.maxlinelength=0

 

 

🔩 context-datasource.xml

<bean id="datasource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
        <!--      logging을 위해 수정
        <property name="driverClassName" value="org.postgresql.Driver" /> 
        <property name="url" value="${jdbc.postgres.url}" />
        -->
        <property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy" />
        <property name="url" value="jdbc:log4jdbc:postgresql://URL:5432/postgres" />
        
        <property name="username" value="${jdbc.postgres.username}"/>
        <property name="password" value="${jdbc.postgres.password}"/>
        <!--     
        <property name="defaultAutoCommit" value="false" />
        <property name="initialSize" value="20"/>
        <property name="maxActive" value="20"/>
        <property name="maxIdle" value="20"/>
        <property name="minIdle" value="20"/>
        <property name="validationQuery" value="select 1" />
        <property name="timeBetweenEvictionRunsMillis" value="150000" />
        <property name="numTestsPerEvictionRun" value="2"/> 
        <property name="testWhileIdle" value="true" /> 
        -->
</bean>

 

완벽히 해결되지 않은 것들

dbcp 2가지 종류를 쓰고 있는데 dbcp2를 쓰고 있는 쿼리만 정렬이 안되고 일렬로 나온다,..

처음엔 logback.xml이나 log4j2.properties에서 처음부터 끝까지 관장하고 있는줄 알았는데 그게 아닌듯 싶다. db 뽑은걸 가로채서 어쩌고하는 것 같은데,,더 잘봐야지

 

drivarClassName

postgresql이나 orcale 등 각 dbms 드라이버? net.sf.log4jdbc.sql.jdbcapi.DriverSpy?

 

 

* org.springframework.jdbc.datasource.DriverManagerDataSource

<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" destroy-method="close">
</bean>

 

 이걸로 바꿨다가 알아보니 이건 실제 커넥션 풀이 아니라고 한다..주의!

 

 

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/datasource/DriverManagerDataSource.html

 

DriverManagerDataSource (Spring Framework 6.1.2 API)

Set the JDBC driver class name. This driver will get initialized on startup, registering itself with the JDK's DriverManager. NOTE: DriverManagerDataSource is primarily intended for accessing pre-registered JDBC drivers. If you need to register a new drive

docs.spring.io

 

 

🔩 jdbcs.properties

jdbc.postgres.url=jdbc:postgresql://URL:5432/DEV
jdbc.postgres.username=test
jdbc.postgres.password=test!

 

 


 

지금 프로젝트는 logback.xml을 로컬, 개발, 운영 별 분기해서 사용하고 있는데

운영거 조작하고는 안바뀌네,,이럼서 오만 생쇼를 다했다.

 

생각보다 간단한 문제였다. 

이미지는 logger에 jdbc.resultsettable도 추가했고 로그가 너무 길어져서 나는 쿼리 정렬 + 파라미터 갈아끼우기로 만족.

근데 쿼리가 생각보다 덜 깔끔하게 정렬이 된다.. 왜일까? 파라미터 갈아끼우는거 없이 정렬만 되는 것도 찾아봐야지..

<logger name="jdbc.sqltiming" level="DEBUG"/>

 


👀 애플리케이션에서 직접 로깅하고 싶다면?

 

1. Lombok 라이브러리 추가 후 @Slf4j 어노테이션 사용

@Controller
@Slf4j
public class TestController {

    @GetMapping("/")
    public void test(String name){
    	log.info("test = {}", name);
    }
    
}

 

2. LoggerFactory.getLogger 사용

private static final Logger logger = LoggerFactory.getLogger(testController.class);

@RequestMapping("/test.do")
public void test(String name) {
    logger.info("test = {}", name);
}

 


더보기

net.sf.log4jdbc.sql.jdbcapi.DriverSpy와 org.postgresql.Driver는 서로 다른 JDBC 드라이버입니다. JDBC 드라이버는 특정 데이터베이스와의 연결을 지원하는 데 사용되며, 각각의 드라이버는 특정 데이터베이스 제품과의 상호 작용을 처리하는 역할을 합니다.

  1. net.sf.log4jdbc.sql.jdbcapi.DriverSpy:
    • log4jdbc는 JDBC 드라이버를 래핑하여 SQL 쿼리를 로깅하고 분석하는 데 사용되는 라이브러리입니다.
    • DriverSpy는 이 라이브러리에서 제공하는 JDBC 드라이버 중 하나로, 실제 데이터베이스 연결은 내부적으로 다른 JDBC 드라이버에 위임하면서 SQL 로깅을 추가로 수행합니다.
    • 이 드라이버를 사용하면 애플리케이션에서 실행되는 모든 SQL 쿼리를 로그에 남길 수 있습니다.
  2. org.postgresql.Driver:
    • PostgreSQL 데이터베이스에 연결하기 위한 JDBC 드라이버입니다.
    • PostgreSQL 데이터베이스와 상호 작용할 때는 주로 이 드라이버를 사용합니다.
    • org.postgresql.Driver를 사용하면 데이터베이스 연결 및 SQL 쿼리 수행이 기본적으로 이 드라이버에 의해 처리됩니다.

따라서 선택하는 드라이버에 따라 다음과 같은 차이가 있을 수 있습니다:

  • net.sf.log4jdbc.sql.jdbcapi.DriverSpy를 사용하면 SQL 쿼리가 추가적으로 로깅되어 개발 및 디버깅에 도움이 될 수 있습니다. 하지만 성능에는 약간의 오버헤드가 발생할 수 있습니다.
  • org.postgresql.Driver를 사용하면 PostgreSQL 데이터베이스와 직접 상호 작용할 수 있습니다. SQL 로깅은 추가로 수행되지 않습니다. 일반적으로 운영 환경에서는 이러한 SQL 로깅을 최소화하는 것이 권장됩니다.

어떤 드라이버를 선택할지는 프로젝트의 요구사항과 개발 및 디버깅 목적에 따라 다를 수 있습니다. 개발 환경에서는 net.sf.log4jdbc.sql.jdbcapi.DriverSpy를 사용하여 SQL 로깅을 편리하게 수행할 수 있으며, 운영 환경에서는 필요에 따라 선택할 수 있습니다.

 

반응형
반응형

스프링은 직접 Validatior를 만들지 않아도 간단히 검증을 할 수 있는 라이브러리를 제공한다.

* 만약 직접 등록할 시에는 Bean Validator를 글로벌 Validator로 등록하지 않아 애노테이션 기반의 빈 검증기가 동작하지 않는다.

 

 

🔩 bulid.gradle 라이브러리 추가

자동으로 글로벌 Validator로 등록한다.

implementation 'org.springframework.boot:spring-boot-starter-validation'

 

🔩 FieldError, ObjectError

정확한 설명아니지만 내가 이해한 바로는

 

FieldError는 하나의 필드에 대한

ObjectError는 필드 하나를 넘어서 필드 값을 곱한 값에 대한 검증을 필요한다거나 할 때 필요한 생성자이다.

 

public FieldError(String objectName, String field, String defaultMessage) {}

public ObjectError(String objectName, String defaultMessage) {}

 

* bindingResult.addError(new FieldError()); 이런 식으로 사용

if (!StringUtils.hasText(item.getName())) {
	bindingResult.addError(new FieldError("item", "name",
	item.getName(), false, null, null, "이름은 필수입니다."));
}

 

이런 식으로 필드별로 하나하나 다 작성해주어야 하는데 그렇게 되면 Controller의 코드도 너무 길어지고 복잡해진다. 또 이런 검증 기능은 기본적으로 사용하기 때문에 기본적으로 자바나 스프링에서 제공하는 검증 라이브러리를 사용한다.

 

🔩 @Valid, @Validated 라이브러리

 

1.  @Valid - Java 표준 

2. @Validated - 스프링 전용

 

 

 

🔩 애노테이션으로 검증 (@NotNull, @Range 등)

 

검증 애노테이션 모음: https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/#validator-defineconstraints-spec

 

Hibernate Validator 6.2.5.Final - Jakarta Bean Validation Reference Implementation: Reference Guide

Validating data is a common task that occurs throughout all application layers, from the presentation to the persistence layer. Often the same validation logic is implemented in each layer which is time consuming and error-prone. To avoid duplication of th

docs.jboss.org

 

* 대상 : @ModelAttribute에서 바인딩 성공한 필드들만!

예시) 예시) age 필드에 "Merry" 라는 String 값을 넣으면

해당 필드만 typeMismatchFieldError가 추가되면서 Bean Validation은 적용이 되지 않는다. (각각 필드 단위로 적용)

 

- Domain

@Data
public class Item {

    @NotNull
    private Long id;
    
    @NotBlank
    private String name
    
    @NotNull
    @Range(min = 1, max = 150)
    private Integer age;
    
	@NotNull
    @Max(value = 9999)
    private Integer etc;

    public Item() {
    }

    public Item(String name, Integer age, Integer etc) {
        this.name = name;
        this.age = age;
        this.etc = etc;
    }
}

 

* 애노테이션에서 message 속성 사용 가능 - @NotBlank(message = "공백 안돼!")

* FieldError가 아닌 ObjectError는 @ScriptAssert()을 사용할 수 있지만 권장하지 않음 (자바 코드로 직접 작성 '복합 룰 검증 ')

 

- Controller

@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult
bindingResult, RedirectAttributes redirectAttributes) {

    if (bindingResult.hasErrors()) {
    	log.info("errors={}", bindingResult);
    	return "test/addForm";
    }
    //성공 로직
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("name", savedItem.getName());
    redirectAttributes.addAttribute("status", true);
    return "redirect:/test/{id}";
}

 

* 복합 룰 검증 : a 값과 b값을 곱한 값에 대한 검증을 부여하는 등 복합 룰 검증이 필요하다면 자바 코드로 작성하며 method로 분리하는 것을 권장한다.

 

 

- errors.properties

#Bean Validation 추가
NotBlank={0} 공백 안됨
Range={0}, {2} ~ {1} 허용
Max={0}, 최대 {1}

 


 

실무를 할 때엔 회원가입 시, 회원수정 시 넘겨아 하는 데이터가 다르다.

하나의 모델 객체에서 검증 룰이 다르다면 어떻게 해야할지 알아보자.

 

결론은 사뭇 비슷하다고 하나의 객체를 사용할 수도 있지만 깔끔하게 다르게 넘기는 것을 추천한다.(2번)

 

1. BeanValidation groups (복잡도 상승 및 실무에서 사용 거의 안함)

 

생성용, 수정용 Interface를 각각 만들어서 (실습은 domain 폴더에 생성함)

스프링에서 제공하는 groups 기능을 사용한다.

 

Interface는 내용없이 파일만 생성했고

Domain 모든 필드에 적용할 Interface를 작성하고

Controller 메소드에 @Validated(UpdateCheck.class)에 작성한다.

 

* Javax 라이브러리 @Valid로는 해당 기능을 사용하지 못하므로 참고바람.

 

- Domain

@Data
public class Item {
	@NotNull(groups = UpdateCheck.class) //수정시에만 적용
	private Long id;
}

 

- Controller

@PostMapping("/{id}/edit")
public String editV2(@PathVariable Long id, @Validated(UpdateCheck.class)
	@ModelAttribute Item item, BindingResult bindingResult) {
	//...
}

 

2. 폼 전송을 위한 별도 모델 객체 생성

 

각 생성, 수정 시 주고 받는 데이터들이 다음과 같다고 치자.

Field 생성 수정
id X O
name O O
age O O

 

 

Controller가 있는 경로에 form 패키지를 추가하여 생성, 수정 폼 모델 객체를 새로 만들었다.

 

- Domain

@Data
public class ItemSaveForm {

    @NotBlank
    private String name;

    @NotNull
    @Range(min = 1, max = 150)
    private Integer age;
}
@Data
public class ItemUpdateForm {

    @NotNull
    private long id;

    @NotBlank
    private String name;

    @NotNull
    private Integer age; // 수정 시에는 범위 검증X
}

 

- Controller

@PostMapping("/add")
public String addItem(@Validated @ModelAttribute("item") ItemSaveForm form, BindingResult bindingResult,
                      RedirectAttributes redirectAttributes, Model model) {

    log.info("bindingResult.getObjectName() => {}",bindingResult.getObjectName()); // item
    log.info("bindingResult.getTarget() => {}", bindingResult.getTarget()); // toString() override

    // domain에서 @ScriptAssert으로 검증할 수 있으나 자바 코드 권장
    // object 에러는 자바 코드로, field error는 Bean Validation으로 진행 권장
    // 특정 필드가 아닌 복합 룰 검증 (method로 뽑아 쓰는거 권장)
    // 여기가 복합 룰 검증 코드

    // 검증에 실패하면 다시 입력 폼으로
    if(bindingResult.hasErrors()){
        log.info("bindingResult = {}", bindingResult);
        // model에 자동으로 담아줌
        return "test/addForm";
    }

    // 성공 로직
    Item item = new Item();
    item.setName(form.getName());   // 원래는 GETTER, SETTER 없이 생성자로 하는 것이 좋다.
    item.setAge(form.setAge());

    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);
    return "redirect:/test/{id}";
}

@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @Validated @ModelAttribute("item") ItemUpdateForm form, BindingResult bindingResult) {

    // 특정 필드가 아닌 복합 룰 검증 (method로 뽑아 쓰는거 권장)
    // 여기가 복합 룰 검증 코드

    // 검증에 실패하면 다시 입력 폼으로
    if(bindingResult.hasErrors()){
        log.info("bindingResult = {}", bindingResult);
        // model에 자동으로 담아줌
        return "test/editForm";
    }

    // 성공 로직
    Item itemParam = new Item();
    item.setName(form.getName());   // 원래는 GETTER, SETTER 없이 생성자로 하는 것이 좋다.
    item.setAge(form.setAge());

    itemRepository.update(itemId, itemParam);
    return "redirect:/test/{id}";
}

 

* 복합 룰 검증 코드는 설계에 따라 다르므로 주석 처리함.

 

>> 생성 시 코드만 따로 보자

 

1) @ModelAttribute("item") ItemSaveForm form

Item 객체가 아닌 ItemSaveForm 객체에 담아야한다.

@PostMapping("/add")
public String addItem(@Validated @ModelAttribute("item") ItemSaveForm form, BindingResult bindingResult,
                   		RedirectAttributes redirectAttributes, Model model) {
//...
}

 

이럴 경우 Item 객체로 변환하는 과정이 필요하다. (이런 과정이 복잡하게 느껴서 폼을 하나로 하면 그러다 더 망할 확률이 높다.)

 

 

2) 폼 객체를 Item 객체로 변환

@PostMapping("/add")
public String addItem(@Validated @ModelAttribute("item") ItemSaveForm form, BindingResult bindingResult,
                      RedirectAttributes redirectAttributes, Model model) {
                      
    // 검증에 실패하면 다시 입력 폼으로
    if(bindingResult.hasErrors()){
        log.info("bindingResult = {}", bindingResult);
        return "test/addForm";
    }

    // 성공 로직
    Item item = new Item();
    item.setName(form.getName());   // 원래는 GETTER, SETTER 없이 생성자로 하는 것이 좋다.
    item.setAge(form.setAge());

    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);
    return "redirect:/test/{id}";
}

 

Item 객체 생성해서

Form객체로 부터 생성자 OR getter, setter로 넣어준 Item 객체 값을 Repositroy에 보내야한다!

 

 


 

🔩 HTTP 메시지 컨버터

 

@Valid, @Validated 적용 가능

 

- Controller  (Domain은 동일)

@PostMapping("/add")
public Object addItem(@RequestBody @Validated ItemSaveForm form, BindingResult bindingResult){
    // ItemSaveForm 객체가 만들어지지 않으면 controller 호출 x
    log.info("api controller 호출");

    if(bindingResult.hasErrors()){
        log.info("검증 오류 발생 errors={}", bindingResult);
            return bindingResult.getAllErrors(); // 실무에서는 필요한 것만 뽑아서 객체 만들어서 반환 필요
    }

    log.info("성공 로직 실행");
    return form;
}

 

1. 타입 오류 : 400 에러

예시) age 필드에 "Merry" 라는 String 값을 넣으면

ItemSaveForm객체 자체가 만들어지지 않아 Bean Validation은 적용이 되지 않는다. Json 객체가 만들어지지 않았으니 당연히 Controller 호출도 되지 않는다. (전체 객체 단위로 적용)

 

2. 검증 오류

모든 값을 다 뽑아내서 Body에 출력한 화면이다.

rejectedValue 등 필요한 데이터만 뽑아서 API 개발 필요하다.

 

+) 타임리프 사용 시 화면에 오류 표출 부분

<div>
    <label for="name" th:text="#{label.item.name}">이름</label>
    <input type="text" id="name" th:field="*{name}"
           th:errorclass="field-error" class="form-control" placeholder="이름을 입력하세요">
    <div class="field-error" th:errors="*{name}">
        이름 오류
    </div>
</div>

 


처음 공부를 하고 게시판을 만들었을 때 입력 폼과, 수정 폼 비슷한데 한번에 쓰면 되지 않나? 라는 생각을 했던 적이 있었다. 그런 단계에서 이 강의를 봤으면 말끔하게 생각 정리를 할 수 있었을 것 같다. 

 

Validaion 챕터는 세부 단계로 나눠서 따라가다 보니 긴가민가했었는데 Bean Validation에서는 확실히 이해가 쉬웠다. ModelAttribute는 각각 필드 단위로 이루어지고 RequestBody에서는 Json 객체가 만들어지지 않으면 Controller를 태우지 않고 검증 단계로 이어가지 않는다는 정리까지도 좋았다. 생각보다 많은 어노테이션으로 검증을 할 수 있었다. 여기 실무 프로젝트에서는 직접 Validator를 작성해서 검증하는 것으로 보이는데 한번 조사를 해봐야겠다!

>> 내가 담당하고 있는 프로젝트에는 없어서 동료 프로젝트 validator를 간략하게 확인해봤는데

로직에 RequestValidator.validateSample(SampleRQmsg); 메소드를 날려서

RequestValidator class에서는 if문으로 다 분기처리해서 각 검증 룰에 맞게 contant로 관리되는 에러코드를 넣어주고 rs하고 있었다.

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술

반응형
반응형

 

>> 호스트 "111.11.11.11", 사용자 "test", 데이터베이스 "postgres", SSL 중지 연결에 대한 설정이 pg_hba.conf 파일에 없습니다.

 

상황

A 시스템(111.11.11.11)에서 B DB서버 접근이 되지 않으며 해당 로그를 뱉음(A 서버 > B DB 서버 PORT 오픈한 상태)

*B 서버는 윈도우 서버

 

해결 - B DB 서버의 pg_hba.conf 파일 수정 (IP 추가하여 접근 권한 부여)

* 수정 시 DB 재시작 필요

 

 

https://www.postgresql.kr/docs/9.6/auth-pg-hba-conf.html

 

The pg_hba.conf 파일

이 레코드와 일치하는 데이터베이스 사용자 이름을 지정한다. all 값은 모든 사용자와 일치하도록 지정한다. 이 외에는, 특정한 데이터베이스 사용자의 이름이거나, 앞에 +를 붙인 그룹 이름이다

www.postgresql.kr

 

반응형

'DATABASE' 카테고리의 다른 글

[SQL] SELECT * INTO문은 무엇인가  (0) 2021.09.17
[표준 SQL] JOIN 정리 (INNER JOIN/OUTER JOIN)  (0) 2021.09.17
반응형
java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: HourOfDay
Exception evaluating SpringEL expression: "#temporals.hour(localDateTime)"

원인 : 날짜 포맷이 맞지 않아서 오류 발생

 

<li>${#temporals.hour(localDateTime)} = <span th:text="${#temporals.hour(localDateTime)}"></span></li>
<li>${#temporals.minute(localDateTime)} = <span th:text="${#temporals.minute(localDateTime)}"></span></li>
<li>${#temporals.second(localDateTime)} = <span th:text="${#temporals.second(localDateTime)}"></span></li>
<li>${#temporals.nanosecond(localDateTime)} = <span th:text="${#temporals.nanosecond(localDateTime)}"></span></li>

시간 데이터가 필요한데 컨트롤러에서 날짜 포맷팅이 잘못된 변수를 가져오니 오류를 뱉어냈다.

 


 

1. LocalDate.now(); 

날짜 출력 ex) 2023-09-26

 

2. LocalDateTime.now();

날짜와 시간 출력 ex)  2023-09-26T16:28:36.877742300

반응형
반응형

- 스프링에서 @ResoponseBody를 사용할 때

viewResolver 대신 HttpMessageConverter가 동작

 

HttpMessageConverter 인터페이스

- HTTP 요청, 응답 둘 다 사용

- byteArray, String, JSON 객체

- canRead(), canWrite() : 메시지 컨버터가 해당 클래스, 미디어타입(content-type, accept 미디어 타입)을 지원하는지 체크

- read(), write()  : 메시지 읽고 쓰기

- 우선 순위에 따라 쓰이는 convert가 다르다.

 


 

Dispatcher Servlet - RequestMapping 핸들러 어댑터

 

🚩HttpMessageConverter 있는 곳

- HandlerMerthodArgumentResolver (파라미터 처리)

- HandlerMerthodReturnValueHandler (응답 값을 반환, 처리)

 

*  인터페이스로 제공하니 WebMvcConfigurer로 확장할 수 있으나 실제로 할 일은 많지는 않다.

 


생각보다 깊은 곳까지 설명을 해주셨다. 완벽하게 이해하지는 않았지만

포괄적으로 HttpMessageConverter가 우선순위에 의해서 맞는 converter가 쓰이고 

Dispatcher Servlet - RequestMapping 핸들러 어댑터에서 request, response할 때 HttpMessageConverter가 쓰인다는 점. 

 

1년 전까지만 해도 Dispatcher Servlet을 알고나서 머리를 탁 쳤었는데 마감이 급한 일만 처리하다보니..기억에서 많이 사라졌다. 공부의 필요성을 새삼 느꼈다.

 

인프런 스프링 MVC 1편

반응형
반응형
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.NoClassDefFoundError: org/json/JSONException

 

<!-- https://mvnrepository.com/artifact/org.json/json -->
		<dependency>
		    <groupId>org.json</groupId>
		    <artifactId>json</artifactId>
		    <version>20230227</version>
		</dependency>

 

maven으로 json 라이브러리를 사용하고 있는데 서버에 올리니 저런 오류가 뜸.

 

해결 방법 )

 

간단하게 https://mvnrepository.com/artifact/org.json/json/20230227 에서 jar파일을 다운받아 lib 폴더에 넣어주기만 했다.

반응형