모드버스 프로토콜

RS485 위에 동작하도록 만들어진 것이 모드버스 프로토콜

RS485

  • 시리얼 통신 중 하나
  • 선 하나에 0101011 등 한번에 1비트만 보낸다

설정해야하는 것

  • 보통의 설정값 → bps: 9600, 데이터길이: 8. 패리티: N, 정지비트: 1
  • bps(= Baud Rate)
    • bit per second: 통신속도
    • 어떤 시간간격으로 읽는지에 따라 데이터가 달라질수있으니 속도가 중요하다
    • 보통 9600 또는 38400으로 설정: 1초에 9600개로 나눠서 보냄
      • 이미 읽을때 시간을 정해진 갯수로 나눠서 읽는다
      • 따라서 이 신호가 적절하게 설정되어있지않으면 데이터를 읽어올 수 없음
  • 데이터 길이
    • 대부분 8로 설정 → 8비트가 1바이트니까
    • 나는 한번 보낼때 8비트씩 보낸다 라는 뜻
    • 이걸 정해야 받는 쪽에서도 8비트가 전송될때까지 기다릴 수 있음
    • 이게 안맞으면 통신이 안된다~
  • 흐름제어
    • Flow Control, TRUE 또는 FALSE
    • 패킷을 서로 주고받을때 본인이 할당한 버퍼(패킷을 임시 저장하는 공간)가 존재.
    • 그 버퍼가 꽉차면 그만보내라는 신호를 보낼 필요가 있음.
    • 그걸 설정하는 것이 바로 흐름제어임
    • 보통은 설정하지 않음 → 이걸쓰려면 별도의 신호선 필요. 그래서 거의 안 씀
  • 패리티
    • 에러검출을 위한 존재
    • N, E, O 세가지 종류가 있으나, 보통 안써서 N으로 설정함 → 따라서 8N 이런식으로 씀
      • N(미사용), E(짝수), O(홀수)
      • 통신하고자 하는 장치와 동일하게 설정해놔야함
  • 정지비트
    • 1 또는 2로 설정 가능, 주로 1로 설정함
    • 정해진 데이터 길이만큼 보낸 뒤 정지비트를 1비트로 보낼지, 2비트로 보낼지 설정

  • 컴퓨터 사이에 통신을 위해 물리적인 경로가 필요(유무선 관계X)한 것이 통신
    • 선 2개로 연결하는 것: RS485
    • 8가닥으로 연결하는 것: 이더넷
    • Wifi모듈 주파수 5G 등 무선 주파수

물리적인 경로에 약속이 필요

  • 전선을 몇가닥 쓸거냐, 가닥수
  • 어케 포트를 만들어서 단자의 규격
  • 또는 주파수 약속
  • 전압 인식에 대한 약속

멀티드롭

  • 여러대의 단말을 하나의 통신회선에 연결하는 방식
  • 문제점: 제어용컴퓨터의 외침이 모든 단말기에 연결되어 특정 단말기와의 통신이 어려움
  • 위 문제점을 해결하기 위해 RS485 통신 사용이 가능함
  • 하지만 이런 문제점 해결은 코드로, 응용sw의 영역임

멀티드롭 이미지

 

위에서 언급한 RS485 통신규격 등을 맞추기 어려운 경우 USB 컨버팅 모듈을 사서 쓸 수 있음

  • RS485 통신선을 모듈에 연결하면 USB에서 컨버팅을 해주고 미니PC에 USB를 연결하면 된다.

이러한 문제점을 고려해서 프로토콜을 만들어주는 것이 바로 모드버스 프로토콜이다~!!!

모드버스 패킷

  • S-ID, Slave, 국번: 특정 단말기만 들을수있도록 지정하는 패킷

 

사담

업무상 필요해서 급하게 공부하게 된 모드버스 프로토콜과 RS485통신 개념인데, 확실히 개념을 알고 나니 그동안 다른 담당자가 정리해준 내용에 대해 정확하게 이해하게 됐다. 담당자의 퇴사로 인해 직접 RS485를 구현해야하는 상황이 돼서 걱정이 많았는데, 공부할 시간을 가지고 천천히 접근하니 할수있겠는데 하는 생각이 들기도 했다. 간단한 내용이지만, 업무에 도움이 되는 기본적인 내용이라 블로그에 정리해봤다. 화이팅..!

🙌잘못된 설명에 대한 지적이나 조언의 댓글을 환영합니다🙌

 

발생상황

웹뷰 기반의 Android 앱을 개발하여 제공중인 환경에서 아래와 같은 오류가 발생했다. 

특정 페이지에서 바코드리더기를 활용해 바코드 리딩을 해야하는데, 다음 현상때문에 정상적인 사용이 어려웠다.

사용하는 바코드 리더기는 Zebra사의 DS-8178, 태블릿은 삼성의 갤럭시탭 A7 제품이다.

(블루투스 기반으로 동작하는 무선 제품과 삼성 태블릿을 사용할때 발생하는 문제로 보인다.)

 

디바이스와 블루투스키보드를 페어링(연결) → 연결이 됨과 동시에 실행 중이던 작업(어플)이 새로고침 됨.

연결 해제 or 다른 기기로 페어링 전환 → 연결이 해제됨과 동시에 실행 중이던 작업(어플)이 새로고침 됨.

 

태블릿에서 바코드리더기랑 내장 키보드를 동시에 입력할 수 있도록 설정할 수 있으면 좋을텐데, 블루투스로 바코드리더기를 연결하면 태블릿 내장 키보드 사용이 안된다. 구글링해보니 예전부터 삼성측에 관련해서 문의를 남긴 글도 있었는데, 공식적인 답변은 찾을 수가 없었다. 

원인

바코드스캐너 구매처에 왜 안되는지에 대한 원인을 물어보니 다음과 같은 답변을 얻을 수 있었다.

  • 블투는 1:1 매칭을 기본으로 하고있다.
  • 바코드 리더기는 일종의 마우스라고 보면 된다.
  • 따라서 키보드랑 마우스는 다른신호이므로 태블릿이랑 리더기 둘 중에 하나만 연결이 가능하다.
  • 둘다 기본적으로 연결값이 같은데, 다른 신호를 가지고있어서 동시에 연결이 안된다.

해결방안

태블릿과 블루투스를 활용한 무선연결시 발생하는 오류이므로, 바코드리더기에서 제공하는 동글을 태블릿에 직접 연결하면 해당 문제가 발생하지 않는다. 하지만 우리가 구입한 바코드리더기의 경우, 동글을 제공하지 않는다. 따라서 위 방법으로도 해결이 불가능한 상황이었다.

 

동글연결이 안된다면, 안드로이드 설정파일 수정을 통해 해결이 가능하다.

안드로이드 설정파일 AndroidManifest.xml에서 메인 액티비티 재생성 방지코드를 추가하면 된다.

<activity
    android:name=".MyActivity"
    android:configChanges="orientation|screenSize|keybord|keyboardHidden">

 

이와 관련해서는 아래 공식문서의 자료를 읽어보면 된다. 

참고: 안드로이드 개발자 공식문서

 

느낀점

앱 자체를 현업에서 개발한것도 처음이고, 블투와 연결했을때 이런 오류가 발생한다는 사실도 처음이라 당황했었다. 구글링했을때도 관련 자료가 많이 없어서 문제해결에도 많은 시간이 소요됐지만, 해결해내서 넘 뿌듯했다 ^_^ 찾아보니 예전에 어떤 앱에서는 에어팟 연결하면 앱이 리로드 되는 이슈가 있었다고 하던데, 이런 오류가 지속해서 발생했다면 사용성이 정말 최악이었을것 같다. 웹뷰를 만들때의 설정이 잘못되어서 그럴수있다는 조언도 들었는데, 아직 내 수준으로는 어느 부분에서 설정이 잘못되었는지를 찾기가 어려웠다. 이럴때일수록 간절한건 시니어님의 존재지만,, 어떻게든 혼자서 해결해보는것도 참 좋은 경험인 것 같다.. 더이상 이 오류는 무섭지 않으니까!

 

참고자료

https://developer.android.com/guide/topics/resources/runtime-changes?hl=ko#restrict-activity 

 

구성 변경 처리  |  Android 개발자  |  Android Developers

Android 앱에서 구성 변경을 처리하세요.

developer.android.com

https://stackoverflow.com/questions/23311748/my-webview-reloads-when-bluetooth-disconnects-or-reconnects

 

My webview reloads when bluetooth disconnects or reconnects

I have an app that has an issue with bluetooth devices. When i disconnect or reconnect a bluetooth device the app seems to reload or i would say reload webview. It doesn't crash the app because I'm

stackoverflow.com

https://gjhhhh.tistory.com/93

 

블루투스 키보드 페어링 전환시 새로고침 현상 해결 방법(멀티페어링 의미)

필자는 블로그 포스팅을 할 때 스마트폰과 태블릿을 활용하는 경우가 많습니다. 그리고 해당 기기들은 K780 키보드에 페어링하여 사용하고 있습니다. 요즘 키보드들은 많은 제품이 멀티페어링

gjhhhh.tistory.com

 

🙌잘못된 설명에 대한 지적이나 조언의 댓글을 환영합니다🙌

 

 

오류문구

org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade

원인

삭제나 업데이트 시 수정된 데이터가 제대로 반영되지 않은 상태에서 또 변경을 시도할 떄 발생하는 것으로 보인다.

데이터가 연관관계를 가지는 상황에서 원본 데이터를 수정하고 이를 참조하는 다른 엔티티의 컬럼을 변경하려고 시도할때 위와같은 오류가 발생됐다. 데이터 수정 순서를 변경하거나 아래 해결방안의 옵션을 추가하는 것으로 문제를 해결 할 수 있다.

해결방안

1. 엔티티에 orphanRemoval=true 추가

이 옵션은 삭제 등의 작업을 통해 joinColumn한 PK가 NULL 상태로 변경된 경우 해당 객체를 DELETE 해준다.

 

2. Cascade 옵션 삭제

이미 삭제한 코드를 Cascade 옵션이 부여된 상태에서 다시 참조하는 경우 해당 오류가 발생할 수 있다.

따라서 Cascade 옵션이 불필요하다면 이를 삭제하는 것도 방법이 될 수 있다. 

또는 코드의 순서를 적절하게 변경하는 것도 도움이 된다.

 

참고자료

https://stackoverflow.com/questions/18358407/org-hibernate-objectdeletedexception-deleted-object-would-be-re-saved-by-cascad

 

org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations):

i am getting the above error "org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): ". could someone help me what might be the...

stackoverflow.com

https://dev-elop.tistory.com/entry/JPA-orphanRemoval-%EC%9A%A9%EB%8F%84

 

JPA orphanRemoval 용도

@OneToMany(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER, orphanRemoval = true) 보통 1:N 관계 테이블 설정할때 저렇게 옵션을 추가해준다. 자식 엔티티의 변경이 있다면 JPA 에서 자식엔티티의 수정은 insert upda

dev-elop.tistory.com

 

미니PC에 우분투 설치하기

OS가 설치되지 않은 미니pc에 리눅스를 설치할 일이 생겼다.

학생때 이후로 리눅스 설치는 너무 오랜만이라 어떻게 설치해야할지 전부 까먹었었는데,

이번기회로 다시 설치방법을 알게되어서 기록으로 남겨두려고 한다 ㅎㅅㅎ

(사내공유용 문서화 작업을 하면서, 내 블로그에도 같이 기록해야지😏)

무료로 사용할수있는 오픈소스인 리눅스 기반 우분투를 설치하는 방법은 아래에 기술하겠다.

생각보다 우분투 공식문서에 설치방법이 잘 나와있어서 많은 도움을 받았다👍

https://ubuntu.com/tutorials/install-ubuntu-desktop#1-overview

 

Install Ubuntu desktop | Ubuntu

Ubuntu is an open source software operating system that runs from the desktop, to the cloud, to all your internet connected things.

ubuntu.com

 

 

설치 전 준비물

설치를 위해 필요한 준비물은 우분투가 설치된 USB, 미니pc 2개가 끝이다.

우분투가 설치된 USB는 Bootable USB 이라고도 부르며, 공식문서에서는 12GB 이상을 권장하고 있다.

설치할 위치인 미니pc도 최소 25GB 이상의 여유공간이 있어야 한다.

또한 공식문서에서는 이전에 사용한 적이 있는 pc(또는 노트북)에 우분투를 설치하기 전 백업을 권장하고 있다.

Bootable USB가 없어도 걱정마시라. 아래에서 직접 경험한 설치방법을 서술할 예정이다.

우분투 공식문서 1.Overview 내용 발췌

본격적으로 설치하기

우분투를 설치하기 위해서는 크게 두가지 과정을 거치면 된다.

가장 먼저 우분투 설치전용으로 만들어진 USB를 만들고, 미니pc에 이를 연결하여 우분투를 설치하면 된다.

너무 기본적인 내용이지만, 차근차근히 아래 방법을 따라온다면 누구나 우분투 설치마스터가 될 수 있다 ^_^

 

1. Bootable USB 만들기

우분투 설치전용 USB를 만들기 위해서는 우분투 이미지를 먼저 다운받아야한다.

우분투 이미지는 아래에서 다운이 가능하다. 본 예제에서는 우분투 데스크탑 22.04.2 LTS 버전을 사용할 예정이다.

버전이 달라도 진행하는데는 큰 어려움이 없을테니 걱정말고 적절한 버전으로 다운로드 받으면 된다.

https://ubuntu.com/download/desktop

 

Download Ubuntu Desktop | Download | Ubuntu

Ubuntu is an open source software operating system that runs from the desktop, to the cloud, to all your internet connected things.

ubuntu.com

 

.iso 확장자를 가진 우분투 이미지 다운로드를 마쳤다면, 이를 USB 스틱에 기록해 설치전용 미디어를 생성해야한다.

단순히 USB에 이미지를 넣어놓는다고 끝나는 것이 아니므로, 맞춤형 SW를 사용해야한다.

여기서는 윈도우용 오픈소스인 Refus를 사용한다. (다운로드 링크는 아래에서 확인)

https://rufus.ie/ko/

 

Rufus - 간편하게 부팅 가능한 USB 드라이브 만들기

Rufus는 USB 메모리 및 플래시 드라이브를 포맷하고 부팅할 수 있도록 만드는 도구입니다. 이 페이지 아래에 나열된 ISO 이미지 이외에도 Rufus는 여러 종류의 ISO 이미지를 지원합니다. (1) Windows 8 이

rufus.ie

 

혹시나 Windows에서 Rufus로 부팅 가능한 USB 스틱 만들기에 관한 공식문서를 참고하고싶다면 아래 링크를 확인하시라.

https://ubuntu.com/tutorials/create-a-usb-stick-on-windows#1-overview

 

Create a bootable USB stick with Rufus on Windows | Ubuntu

Ubuntu is an open source software operating system that runs from the desktop, to the cloud, to all your internet connected things.

ubuntu.com

refus 홈페이지에서 다운로드 부분을 보면 설치 가능한 프로그램이 보인다. 

그 중 나는 4.0 버전으로 선택하여 다운받았다.

출처: refus 다운로드 페이지

 

다운받고 해당 프로그램을 열면 아래와 같은 이미지가 표시된다. 

[장치]의 입력은 Bootable USB로 사용할 USB를 작업중인 PC/노트북에 연결하고 refus 프로그램을 켜면 알아서 인식된다.

 

이후 [부팅선택] 영역의 선택 버튼을 눌러 다운받은 우분투 iso 이미지를 선택하고 [시작] 버튼을 누른다.

 

[시작]버튼을 누르면 아래와 같은 이미지가 표시된다.

여기서 프로그램의 권장대로 ISO 이미지 모드로 쓰기를 선택 후 작업을 이어가면 된다.

 OK를 누르면 아래와 같은 경고창이 뜨고, USB에 다른 데이터가 있다면 전부 삭제후 설치하면 된다.

위 경고창에서 [확인]버튼을 누르면 알아서 설치가 이뤄지고, 완료여부는 아래 progress bar에서 확인할 수 있다.

여기까지 잘 따라왔다면, 거의 다 설치한 셈이다 ㅎㅎ

그럼 이제 본격적으로 미니PC에 우분투를 설치해보겠다.

 

2. 미니PC에 우분투 설치하기

위에서도 언급했듯이 내가 이번 예제에서 사용한 미니pc는 ASRock DeskMini B660 제품이다. 

설치할 미니pc에 따라 부팅방법으로 들어가는 방법은 다를 수 있으니 참고바란다.

가장 먼저 미니pc에 우분투 ISO 이미지를 설치한 Bootable USB를 꽂는다.

그 다음 pc를 켜고 로고가 나왔을때 부팅모드로 진입한다.

아래 이미지는 미니pc 부팅화면으로, 우측 하단에 F11을 누르면 Boot Menu에 진입가능하다고 적혀있다.

 

위 화면에서 F11을 눌러 부팅모드에 진입하면 아래 화면이 나온다.

USB를 꽂아놔서 pc에서 boot device를 인식하고있는 모습을 확인할 수 있다. 

아래 화면에서 엔터를 클릭하여 부팅을 시작하면 된다.

부팅모드에 진입한 모습
부팅모드에서 엔터 클릭시 확인할 수 있는 화면, Try or Install Ubuntu 클릭

 

위 화면에Try or Install Ubuntu를 클릭하면 알아서 설치가 시작된다.

한번 재부팅이 이뤄지고 아래와 같은 화면이 나오면 본격적인 우분투 설치를 진행하면 된다.

언어 > 한국어 선택 후 Ubuntu 설치 클릭
키보드 레이아웃 선택된 상태 그대로 계속하기
업데이트 > 일반설치(최소설치도 가능), 기타설정 > 서드파티 소프트웨어 설치
하드에 아무것도 없는 상태여서 위 옵션으로 적용

만일 다른 용도로 우분투 설치 pc를 사용할 경우 기타옵션을 통해  파티션을 적용해야한다.

파티션 적용은 아래 블로그에 잘 설명되어있으니 참고하시길!

https://jsitclub.tistory.com/83

 

리눅스 설치 - 부팅 USB 만들기 부터 설치완료 까지

먼저 리눅스를 사용하려면 리눅스를 설치 해야겠죠. 리눅스를 가상머신에 설치해 윈도로 부팅하고, 리눅스를 사용해보거나, 부팅시 윈도와 리눅스를 선택하는 방법도 있는데, 여기에서는 소개

jsitclub.tistory.com

나와 같은 옵션을 선택했다면, 우분투에서 한번 확인하라고 경고창을 띄워준다.

계속하려면 계속하기 클릭
위치 적절하게 설정
사용자 정보 입력, 자동로그인 선책하려면 클릭 후 계속하기
다음과 같이 나오면 설치가 정상적으로 시작한 것임
설치 완료 후 재부팅 안내창

설치가 모두 완료되면, pc를 한번 재시작 해줘야한다.

이 과정에서 시간이 꽤나 많이 소요되기때문에 그동안은 알아서 다른 일을 하고있으면 된다.

재부팅이 끝나면 아까 입력한 사용자 계정과 비밀번호를 눌러 로그인하는 창이 표시된다.

로그인 하면 설치 끝
로그인 후 표시되는 화면

위 해파리 배경화면이 보인다면 설치 끝이다!

 

설치후기

초반에 USB를 생성하는 과정과, 우분투 설치 후 재부팅 되는 시간이 생각보다 길었다. 또 크게 어려운 것 없이 화면이 시키는대로 작업하면 큰 문제없이 설치할 수 있었다. 개인적으로는 매번 이런저런 설정을 기억했다가 똑같이 해야하는게 번거로워서 모든 선택지를 이미지로 남겨놓는 것을 선호하는데, 그 이미지를 활용해 기록해두면 추후 동일한 작업을 할때 뭔가 기계적으로? 따라할 수 있어서 좋은 것 같다ㅎㅎ 그리고 미니pc의 경우 F2, Esc 등 부팅모드로 진입하는 키가 서로 다르기때문에 본인이 설치할 pc의 부팅모드 진입키는 사전에 알아두는 것이 좋은 것 같다. 제품에 따라서 특정 키를 연타해야 접근이 가능하다고 하던데, ASRock DeskMini B660 제품의 경우 F11 버튼을 한번만 눌러도 바로 접근이 가능해서 편리했다! 

 

이 글이 같은 상황에 놓여있는 누군가에게 도움이 되길 바라면서 글을 마친다🙏

 

참고자료

https://ubuntu.com/tutorials/install-ubuntu-desktop#1-overview

 

Install Ubuntu desktop | Ubuntu

Ubuntu is an open source software operating system that runs from the desktop, to the cloud, to all your internet connected things.

ubuntu.com

https://ubuntu.com/tutorials/create-a-usb-stick-on-windows#1-overview

 

Create a bootable USB stick with Rufus on Windows | Ubuntu

Ubuntu is an open source software operating system that runs from the desktop, to the cloud, to all your internet connected things.

ubuntu.com

https://jsitclub.tistory.com/83

 

리눅스 설치 - 부팅 USB 만들기 부터 설치완료 까지

먼저 리눅스를 사용하려면 리눅스를 설치 해야겠죠. 리눅스를 가상머신에 설치해 윈도로 부팅하고, 리눅스를 사용해보거나, 부팅시 윈도와 리눅스를 선택하는 방법도 있는데, 여기에서는 소개

jsitclub.tistory.com

 

DB data 추출하는 방법

현 회사에서 DB를 postgreSQL로 사용중인데, 데이터 제출을 위해 특정 테이블을 excel 파일로 추출해야했었다.

대부분 query tool을 통한 추출방법만 나와있길래, pgAdmin을 사용해 간단하게 추출하는 방법을 소개하고자 한다.

(윈도우, pgadmin4 사용 기준)

 

1. 데이터를 추출할 테이블을 조회한다.

아래 예시와 같이 조건없이 조회한 테이블도 추출이 가능하며, 특정한 조건을 붙여 조회한 결과 데이터도 추출이 가능하다.

 

2. pgAdmin 내 다운로드 버튼을 클릭한다. 

클릭하면 바로 내 컴퓨터로 조회한 데이터의 csv 파일이 저장된다.

 

3. csv로 저장된 파일을 연결프로그램 > excel로 열어서 확인한다. 

단, 이때 한글이 깨지거나 하는 문제가 발생한다면 csv 파일의 인코딩 타입을 변경하면 된다.

pgAdmin을 통해 저장된 csv파일을 메모장으로 열면 최조 인코딩 타입은 `UTF-8`로 지정되어있다.

메모장에서 다른 이름으로 저장 기능을 통해 인코딩 타입을 `ANSI`로 변경한 뒤 저장하고, excel로 열면 깨지지 않는다👍

+) Query tool을 통한 csv 파일 추출방법

COPY (
    select * from [테이블명] where [조건]
) TO '/home/postgres/[저장할 파일명].csv'
with csv header DELIMITER ',';

pgAdmin에서 query tool을 열고 위 쿼리문을 입력하면 /home/postgres 하위에 저장된다.

하지만 내 환경에서는 copy 쿼리 실행을 위한 exe 파일이 없다는 오류와 함께 동작하지 않아 GUI 방법을 사용했다.

아마 처음 postgresql을 설치할때 `Windows x86-32` 버전으로 잘못 설치해서 그런게 아닐까..

그것 때문인지 pgAdmin도 브라우저로 열리던데,, 이건 나중에 새로 설치하거나 해서 해결해봐야겠다

🙌잘못된 설명에 대한 지적이나 조언의 댓글을 환영합니다🙌

 

tiles란

View page로 제공하는 jsp 파일의 중복되는 요소를 페이지 조각으로 나누어 관리하도록 돕는 템플릿 구성 프레임워크

https://tiles.apache.org/framework/index.html

 

Apache Tiles - Framework - Home

 

tiles.apache.org

 

tiles의 간단 사용법

프로젝트에서 tiles를 사용하기 위해서는 pom.xml 파일에서 maven dependency를 가장 먼저 추가한다.

	<!-- tiles framework -->
	<dependency>
       <groupId>org.apache.tiles</groupId>
       <artifactId>tiles-extras</artifactId>
       <version>3.0.8</version>
     </dependency>
     <dependency>
         <groupId>org.apache.tiles</groupId>
         <artifactId>tiles-servlet</artifactId>
         <version>3.0.8</version>
     </dependency>
     <dependency>
         <groupId>org.apache.tiles</groupId>
         <artifactId>tiles-jsp</artifactId>
         <version>3.0.8</version>
     </dependency>

그 다음 Servlet-Context.xml 파일에서 tiles bean 생성이 가능하도록 viewResolver와 Configurer를 설정한다.

- viewResolver: tiles template으로 설정된 파일을 찾아서 렌더링 해주는 역할

<!-- Tiles 뷰 리졸버 -->
	<beans:bean id="tielsViewResolver"
		class="org.springframework.web.servlet.view.UrlBasedViewResolver">
		<beans:property name="viewClass"
			value="org.springframework.web.servlet.view.tiles3.TilesView" />
		<beans:property name="order" value="1" />
	</beans:bean>
	<!-- Tiles 설정 파일 -->
	<beans:bean id="tilesConfigurer"
		class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
		<beans:property name="definitions">
			<beans:list>
				<beans:value>/WEB-INF/tiles/tiles.xml</beans:value>
			</beans:list>
		</beans:property>
	</beans:bean>

 

세번째로는 tiles.xml가 필요하다. 위에서 선언한대로 /WEB-INF/tiles/tiles.xml 위치에 xml파일을 추가한다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC 
    "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN" 
    "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
    <definition name="classic" template="/WEB-INF/tiles/layouts/classic.jsp">
    	<put-attribute name="TopMenu" value="/WEB-INF/tiles/components/TopMenu.jsp" />
        <put-attribute name="SideBar" value="/WEB-INF/tiles/components/NewSideBar.jsp" />
    </definition>
    <definition name="*/*" extends="classic">
        <put-attribute name="content" value="/WEB-INF/views/{1}/{2}.jsp" />
    </definition>
</tiles-definitions>

 

위 예제의 classic.jsp처럼 layout용 jsp 파일을 생성한다.

tiles.xml 에서 put-attribute한 name으로 template용 jsp 파일 안에 tiles:insertAttribute하면 템플릿의 코드와 insert된 코드가 함께 렌더링 되어 화면에 표시된다.

<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>
<html>
<head>
  <link href="css/common.css" rel="stylesheet">
  <title>project titles</title>
</head>
<body>
  <tiles:insertAttribute name="SideBar" />
  <tiles:insertAttribute name="TopMenu" />
  <tiles:insertAttribute name="content" />
</body>
</html>

 

Reference

 

🙌잘못된 설명에 대한 지적이나 조언의 댓글을 환영합니다🙌

 

favicon이란

웹 브라우저의 탭 영역에 표시되는 홈페이지의 대표 아이콘

favicon.ico 라는 파일명과 확장자로 이미지를 변환시켜 사용한다.

 

favicon 설정을 하게 된 이유

항상 시간에 쫓기는 개발을 할수밖에 없었던 업무 특성상 기능을 붙이는 게 더 중요했기 때문에 홈페이지의 favicon 설정은 뒷전이었다. 하지만 미팅때 실제 유저들을 만나면 해당 웹페이지에 쉽게 접근하고, 구분하기 위해 아이콘을 넣어달라는 요청이 많았다. 간단한 작업이었지만 우선순위가 긴급한 안건들을 처리하고 이제야 시간이 나 프로젝트에 favicon 설정을 시도했고, 간단한 설정이었지만 프로젝트 구조에 따라 해당 파일을 넣어야하는 위치가 달라지기 때문에 시행착오를 겪었다. 구글링을 해도 spring 프로젝트에서 어디에 favicon 파일을 넣고 설정해야하는지 알려주는 글이 없어서 아쉬워서 남겨보는 글이니 누군가에게는 도움이 되길 바란다.

 

프로젝트 폴더구조

어떤 환경설정을 하는지에 따라 달라질 수 있지만, 우리는 src/main/webapp 하위에 resources 폴더가 있고 css, js, font, img 등 웹페이지에서 사용하는 코드들을 폴더별로 분리하여 관리한다. tiles도 사용중이기 때문에 웹페이지의 기본 틀이 되는 classic.jsp 파일의 head 태그 안에 link 태그를 넣어 favicon 설정을 한 뒤 src/main/webapp/resources 폴더 바로 아래에 favicon.ico 파일을 추가하여 설정을 완료했다. 

폴더구조

<link rel="shortcut icon" href="favicon.ico">

 

🙌잘못된 설명에 대한 지적이나 조언의 댓글을 환영합니다🙌

서론

사수님이 퇴사하고, 주로 비즈니스 로직을 다루는 백엔드 작업을 할 일이 많아졌다. 기존에는 코드를 마치 레시피처럼 외워서 개발해왔기 때문에 문제가 생겼을 때 스스로 디버깅하기가 어려웠다. 그래서 주 1회 퇴근 후 스터디를 통해 스프링 입문부터 공부하기로 결심했다. 오늘은 그 첫 번째 챕터인 웹개발 기초에 대해 알아보고자 한다.

 

스프링을 통한 웹 개발 방법

스프링으로 웹개발을 할 때 아래 세 가지 방법을 사용할 수 있다.

 

1. 정적컨텐츠 : HTML 파일 그대로 웹 브라우저에 전달

2. MVC와 템플릿 엔진 : 서버에서 HTML 파일을 동적으로 변환한 뒤 웹 브라우저에 전달

3. API : JSON으로 데이터를 클라이언트에 직접적으로 전달

 

각 방법의 특징과 스프링에서의 동작방식에 대해서 알아보자.

정적컨텐츠의 동작과 특징

정적컨텐츠는 클래스 경로나 ServletContext 루트에 있는 /static(or /public, /resources, META-INF/resources) 디렉터리에서 제공한다. 해당 폴더 내에 HTML 파일을 추가하는 경우 이를 그대로 반환한다는 특징이 있다. 별도의 설정 없이 스프링부트에서 기본적으로 제공하는 기능이지만 Spring MVC의 ResourceHttpRequestHandler를 사용하여 동작하기 때문에 자체 WebMvcConfigurer를 추가하고 addResourceHandlers 메소드를 재정의하여 해당 동작을 수정할 수도 있다.(하지만 이렇게 수정해서 사용할 이유가 없음..) 또한, 기본적으로 리소스는 /**에 mapping되며, 이 경로를 수정하고 싶은 경우 spring.mvc.static-path-pattern 속성을 변경하여 mapping 경로를 수정하는 것도 가능하다. 

 

스프링부트로 실행한 웹 브라우저에서 정적컨텐츠인 HTML파일을 표시하기 위해 톰캣 서버로 요청을 보내면, 스프링부트의 스프링컨테이너에서는 해당 url과 mapping 된 controller를 찾는다. 하지만 controller가 없는 정적컨텐츠이기 때문에, static 폴더 등을 살펴보고 동일한 이름의 HTML파일을 브라우저로 전달하는 방식으로 동작한다. 

 

출처: 김영한님의 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의

 

MVC와 템플릿 엔진의 동작과 특징

이 방법의 특징은 한번 렌더링 한 HTML을 브라우저에 전달한다는 점이다. 이해를 위해 MVC란 무엇인지에 대해 먼저 알아보자. MVC는 Model, View, Controller의 구성을 뜻하며, 역할에 따라 기능을 분리하여 제공하기 위해 사용한다. Model은 controller가 화면에 표시해야 하는 것들을 정리해서 담는 곳이며, 이를 view에 전달하는 역할을 한다. View는 오로지 화면을 그리는 일만을 담당하며, Controller는 비즈니스 로직을 담당하며, 요청을 받았을 때 화면 뒷단에서 처리해야 하는 일들을 담는 역할이다. 여기서 View가 php, jsp 등 템플릿엔진을 통해 Model, Controller의 정보를 받아 HTML을 다시 렌더링 하고 그 결과를 브라우저에 전달하도록 개발하면 된다. 이처럼 템플릿엔진을 MVC방식으로 쪼개서 동작하도록 만들었다면 이 방법을 사용한 것이라고 이야기할 수 있다. 

 

웹 브라우저의 요청을 톰캣서버로 보냈을 때 스프링컨테이너에서 가장 먼저 확인하는 Controller단에서 mapping된 정보가 존재한다면, 컨트롤러의 동작을 먼저 수행하고 이를 바탕으로 viewResolver가 HTML파일을 렌더링해 변환을 마친 HTML을 웹 브라우저로 전달하는 방식으로 동작한다.

 

출처: 김영한님의 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의

 

API의 동작과 특징

API 방식은 데이터를 바로 내려준다는 특징을 가진다. 이 방식을 사용하기 위해서는 Controller에서 @ResponseBody라는 어노테이션을 사용해야 하는데, 이는 해당 데이터를 HTML의 <Body> 태그 내부로 전달한다는 뜻이 아닌, HTTP 내 응답 Body부분에 해당 데이터를 직접 넘겨주겠다고 정하는 것을 뜻한다. 따라서 return 값이 view 템플릿을 거치는 것이 아니라 데이터 형식 그대로 전달된다는 특징이 있다. 

 

웹 브라우저를 통해 톰캣서버로 전달된 요청은 MVC 때와 동일하게 스프링컨테이너 내 mapping된 Controller로 전달되고, 해당 Controller에 적용된 @ResponseBody가 파라미터를 통해 생성한 객체를 그대로 return으로 반환한다. 이때 HttpMessageConverter가 JsonConverter를 통해 객체를 JSON 형태로 변환하여 이를 웹 브라우저에 전달하는 방식으로 동작한다.

 

출처: 김영한님의 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의

@Controller
public class HelloController {
 
    @GetMapping("hello-api")
    @ResponseBody
    public Hello helloApi(@RequestParam("name") String name) {
        Hello hello = new Hello();
        hello.setName(name);
        return hello;
    }

    static class Hello {
        private String name;

        public String getName() {
        	return name;
        }

        public void setName(String name) {
        	this.name = name;
        }
    }
}

 

정리하며,

이렇게 오늘은 스프링을 통해 웹개발하기 위한 방법 세 가지에 대해 공부했다. 부끄럽지만 평소 내가 사용하고 있는 방식이 API인지도 정확히 모르고 사용해 왔었고, view 구성을 위해 model에 데이터를 넘겨주는 방법도 이제야 제대로 알게 된 것 같다. 아직 기본적인 내용 밖에 담지 못했지만, 점차 학습하며 각 방법을 적절하게 활용하는 개발자가 되어야겠다💪 

 

Reference

 

Spring Boot Features

Graceful shutdown is supported with all four embedded web servers (Jetty, Reactor Netty, Tomcat, and Undertow) and with both reactive and Servlet-based web applications. It occurs as part of closing the application context and is performed in the earliest

docs.spring.io

 

[무료] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

 

본 글은 초보개발자의 입장에서 작성된 글이며, 잘못된 설명이 포함될 가능성이 있습니다.

🙌잘못된 설명에 대한 지적이나 조언의 댓글을 환영합니다🙌

 

 

https://inf.run/dzXn

매일 아침에 출근해 업무를 시작하기 전, 5-10분 동안 깃헙의 잔디를 채울 겸 JS공부를 할 겸 제주코딩베이스캠프의 JS 100제 강의에서 제공되는 문제를 푸는 중이다. 사실 아침에 해설 영상까지 볼 시간은 없어서 문제를 푼 뒤에 답안을 보고 내가 맞게 풀었는지 정도만 확인하고 있는데, 오늘 풀었던 13번 문제를 푸는 과정에서 문득 이런 의문점을 갖게 됐다.

 

제주코딩베이스캠프 Code Festival: JavaScript 100제 - 인프런 | 강의

이 강좌를 통해 문법을 보다 명확하게 이해하시고, 문제 풀이에 대한 자신감을 얻으시길 바랍니다., - 강의 소개 | 인프런...

www.inflearn.com

 


 

const a = ['수성', '금성', '지구', '화성', '목성', '토성', '천왕성', '해왕성'];

let num = prompt('숫자를 입력하세요');

console.log(a[num-1]);

사용자로부터 입력값을 받아 해당 순서의 행성 이름을 출력하는 문제였는데, 위 코드가 이번 문제의 답안이었다. 

간단한 문제였고, 무난하게 풀 수 있었는데 내가 생각한 답안은 책에서 제공하는 답안과는 조금 달랐다.

 

const a = ['수성', '금성', '지구', '화성', '목성', '토성', '천왕성', '해왕성'];

let num = prompt('숫자를 입력하세요');
num -= 1;

console.log(a[num]);

위 답안이 내가 생각한 방법인데, 책에서 제공하는 답안보다 코드 한 줄이 더 많은 점이 제법 거슬렸다. 

이런 코드를 작성하게 된 이유는 콘솔로 값을 찍다가 console.log(a[num+1]) 을 우연히 실행시켜봤는데 값이 undefined가 나오는 게 아닌가. 그래서 당연히 console.log(a[num-1]) 도 undefined가 나오지 않을까 해서 저렇게 num 계산을 별도로 해주는 답안을 작성하게 됐었다. 사실 책에서 제공하는 답안을 보고 의문이 들었는데, 왜 num+1했을 때는 출력이 안되고 num-1을 했을때는 값 출력이 정상적으로 되는지를 도저히 이해할 수가 없었다. 그래서 구글링도 해봤는데 도저히 어떤 키워드로 검색해야 할지 감이 잡히지 않아 같이 공부하는 동료들에게 질문을 던졌다.

 

 

9시 5분쯤부터 문제를 풀기 시작했는데, 질문한 시간은 24분ㅋㅋㅋㅋ

이렇게 질문을 하고 업무를 보고 있었는데 동료들이 다양한 답을 던져주기 시작했다.

그중 타입 때문에 이런 현상이 발생하는 게 아니냐는 이야기가 있었는데, 정말 유레카스러웠던 순간이었다. 

 

 

콘솔로 타입을 찍어보니 요상한 타입을 뱉어내는 콘솔을 확인할 수 있었다.

동료들이 해당 값의 타입을 찍어 봤을 때 동작이 이상한 것 같다고 의견을 주셔서 확인해보니 정말 이상한 타입이 나왔고, 스스로 문제를 살필 때는 도저히 생각하지 못했던 부분이었던지라 해결의 실마리를 찾은 기분이었다. 사실 JS에서는 타입을 별도로 선언해주지 않고 사용하기 때문에 암묵적 형변환에 의존해서 사용하게 되는데,  포스팅을 하려고 찾아보니 이러한 기능은 가급적이면 사용하지 않는게 좋다고 한다. 내가 겪은 의문점도 형변환 때문에 생겼다고 생각하니 사람들이 왜 가급적 쓰지 말라고 했었는지 체감할 수 있었다.  

 

더 정확하게 이야기하자면, prompt를 통해 받아온 값은 String 형태로 저장되고 있었다.

만약 prompt로 값:1을 입력받아왔다면 a[num]은 숫자 1로 암묵적 형변환이 되어 a[1]의 값인 '금성'이 출력되고,

a[num-1]도 -1을 처리하기 위해 동일한 암묵적 형변환이 발생해 a[0]에 해당하는 값인 '수성'을 출력했던 것이다.

 

그렇다면 왜 a[num+1]만 undefined가 나온 것일까? 

num+1에는 암묵적 형변환이 일어나지 않아서 해당 출력 값이 표시된 것일까?

그건 아니다. a[num+1]인 경우 JS엔진이 이를 a['1'+1] 로 인식해 a[2]가 아닌 a['11']로 처리했고 이후에 형변환이 일어나 a[11]을 출력하려고 보니 배열에 인덱스가 11인 값이 없기 때문에 undefined가 출력된 것이다. 이렇게 보니 간단한 이유인데 이 생각을 못해서 한참 문제와 씨름했던걸 떠올리니 다시 한번 사람들이 왜 타입스크립트를 쓰는지 알 것 같기도 했다🤦‍♀️

 

 


 

 

이러한 문제를 해결하기 위해서는 명시적인 형변환을 사용해주면 된다. prompt를 통해 받아온 입력 값이 String 타입이지만, 나는 해당 값을 Number로 사용할 예정이기 때문에  prompt('숫자를 입력하세요')라는 function을 Number()로 감싸 명시적인 형변환을 해주면 된다. 이렇게 명시적으로 형변환을 시켜준 뒤에는 console.log(a[num+1])을 찍어도 내가 의도한 '지구'라는 출력 값을 표시하게 된다. 

 

오늘도 이렇게 타입을 잊으면 안 되겠다는 엄청난 교훈을 얻었다..🙏

 

 

 

올 1월 말까지 모든 에너지를 쏟았던 멋사 FE SCHOOL 1기 과정에 대한 피드백을 어제 전달받았다. 

같이 팀프로젝트를 진행한 팀원들 각각에 대한 피드백을 설문을 통해 수집하고, 이를 취합해 전달해주신다고 했는데

한 사람당 설문을 작성해야하는 인원이 많아서 그런지 생각보다 참여율이 저조하다는 공지를 몇 번 받았던 기억이 있다.

(물론 나는 우리 팀원들+a의 소중한 동료들에게 정성껏 피드백을 드렸던 기억이 있따..! 피드백 너무 재밌어💕)

그래서인지 전달받은 내용에 대한 큰 기대를 하지 않았던 것이 사실이다.

 

하지만 어제 전달받은 자료를 보고 눈물을 좔좔 흘릴뻔 했다 뿌엥 .·´¯இ௰இ´¯`·. 

늘 이야기하던 말이긴 하지만 학부생으로 지내면서 팀프로젝트 보다는 개인 프로젝트 위주로 학습하기도 했고,

대학을 다니면서 과제를 해내기에 급급해 몰입해서 프로젝트를 진행한 경험이 없었기 때문에 멋사 과정에서 

팀을 구성해 감귤마켓을 만들어 내야한다는 사실에 굉장히 큰 부담을 느껴 도망가고 싶었지만

그래도 내가 할 수 있는 작은 일이라도 찾아서 기여하는 것이 더 낫지않을까라는 결론에 도달했고

피하는 것이 아니라 어떻게 팀프로젝트를 해야할지 스스로의 최선을 찾아나갔던 것 같다. 

 


 

처음 팀원들을 만나 서로 소개하고 각자의 역할을 나누는 과정에서 먼저 나의 부족함에 대해 이야기하고,

당시 나의 지식 수준에서 할 수 있는 역할은 이런것들이다하고 공유해서 비교적 쉬운 파트를 가져갔었다.

이제와 생각해보면 당장 개발 경험과 실력이 부족하다는 마음이 무의식 한 켠에 자리잡고 있었던 것 같다.

약간의 자격지심인 것 같기도 하다. 사실 뭐,, 경험한다고 다 잘하게 되는 것도 아닌 것 같긴 하지만? ㄟ( ~0~ )ㄏ

 

그 당시 팀프로젝트의 업무 분담이나 어떻게 일했는지가 궁금하다면, 아래 링크를 구경해보시는걸 추천한다.

https://github.com/jiseung-kang/ggul-market

 

GitHub - jiseung-kang/ggul-market: 감귤마켓

감귤마켓. Contribute to jiseung-kang/ggul-market development by creating an account on GitHub.

github.com

 


 

그래서 감동받은 점이 어떤 부분이길래 이렇게 서론을 길게 설명하느냐 하면, 위에서 보인 내 태도와 관한 피드백이었다.
나는 평소에 내 주제, 다르게 말하자면 객관화가 잘 되는 편이라고 생각했다. 그래서 내가 할 수 있는 일과 하지 못하는 일을 명확하게 구분하는 편이었고, 이렇게 일하는 방식이 개선점이 되리라고 생각해본 적이 없었던 것 같다. 하지만 단순히 일이 아닌 배움이나 성장의 시각에서 본다면 이러한 나의 모습이 소극적인 자세로 비춰질 수도 있겠다는 생각이 들었다.

사실 예전의 나는 불가능해보이는 일에 도전하는 것을 꺼려했었다. 어짜피 안될거 해봤자 뭐해 라고 생각했을지도 모른다. 그런 내가 조금 더 나이를 먹어가면서, 도전을 피해서 얻지 못한 결과들에 대해 후회하는 경험을 하게 됐다. 항상 과거에 놓친 기회들만 후회하다보니 그 시간이 너무 아깝다고 느껴졌고, 그 때 도전했다면, 그 때 시도했다면 뭔가 다른 결과를 만들 수 있지 않았을까하는 생각에 다다르게 됐다. 이를 통해 도전은 소중하고 적어도 후회를 남기지는 않는다는 배움을 얻었고 지금은 나름 도전을 즐기는 사람이 되었다고 생각했는데, 아직도 내게 그런 습관이 남아있었나보다.

참.. 의식하면서 살아가지 않으면 쉽게 놓칠 수 있는 부분인 것 같다. 그래도 이번 피드백을 통해 새로운 시각으로 나에 대해 되돌아 볼 수 있었던 것 같아서 너무 좋은 기회였다. 익명으로 전달받은 내용이라 직접 감사를 표할 수는 없지만, 그래도 이번 과정을 통해 만나게 된 모든 동료들과 인연에게 감사하는 마음을 전하고 싶다 (´▽`ʃ♡ƪ) 


아래는 해당 피드백의 내용 전문이다. 넘 따듯하고 진심으로 조언해주시는 모습에 감동의 눈물바다..🌊😭

 

 

'TIL 記 + 회고' 카테고리의 다른 글

2021을 돌아보며..  (0) 2022.01.08
211222_TIL:Today I Learned  (3) 2021.12.22
211221_TIL:Today I Learned  (0) 2021.12.21
211220_TIL:Today I Learned  (0) 2021.12.20
211219_TIL:Today I Learned  (4) 2021.12.19

+ Recent posts