react에서 주고 받는 데이터를 변경 하기 위해서는 state를 사용해야 합니다.

class형의 경우 setState 등을 이용해서 갱신이 가능 합니다. 저는 함수형으로 작업하기 때문에 hook을 이용해서

state에 저장 및 접근 하는 방식으로 작업을 진행 했습니다.

 

부모<->자식 간 공유가 필요한 정보들을 app.js에서 정의 해줍니다.

const [dataList, setDataList] = useState([]);  
const [tabViewCss1, setTabViewCss1] = useState("active");
const [tabViewCss2, setTabViewCss2] = useState("");
const [divViewCss1, setDivViewCss1] = useState("d-block");
const [divViewCss2, setDivViewCss2] = useState("d-none");
const [{ mapLat, mapLng }, setGeometricData] = useState({
      mapLat: 37.486708581137,
      mapLng: 127.037893303934,
});

위의 내용을 간단하게 설명하면 

1) dataList는 목록 출력에 사용할 배열을 담는 객체로 비어있는 배열로 초기화 했습니다. useState([])

   해당 정보를 변경 하려는 경우 setDataList함수를 호출 해서 값을 변경 할 수 있습니다. setDataList(배열정보)

2) tabViewCss1~divViewCss2 탭 화면 구성을 위한 class정보를 담은 정보로 문자열 입니다.

   setTabViewCss1~setDivViewCss2 로 정보를 초기화 할 수 있습니다. setTabViewCss1('active')

3) mapLat, mapLng는 지도상의 위치(경도,위도) 정보로 배열 형식으로 선언 하였기 때문에 초기화 할때도

    useState({mapLat..., mapLng...})형식으로 초기화를 진행 했습니다.

    값을 변경 할때도 setGeometricData({mapLat:값, mapLng:값}) 와 같이 해당하는 값을 정의 해줘야 합니다.

 

useState를 이용해서 정의된 값은 변경시 해당 정보를 사용하는 모든 곳에서 영향을 받게 됩니다.

 

부모->자식 으로 전달된 함수의 경우 

<li className={"nav-item nav-link "+props.tabViewCss1} onClick={(e) => props.tabClick(1, e)}>
목록
</li>
<li className={"nav-item nav-link "+props.tabViewCss2} onClick={(e) => props.tabClick(2, e)}>
지도
</li>

와 같이 props를 통해서 접근 가능 합니다.

반응형
Posted by 질주하는구
,

react에서는 부모<->자식관의 데이터 전달을 부모 페이지에서 import 한 객체에 attribute로 정의해서 전달 할 수 있습니다. 전달 하는 방식은 아래와 같습니다.

<Top searchApi={searchApi}/>
<Tab tabViewCss1={tabViewCss1} tabViewCss2={tabViewCss2} tabClick={tabClick}/>
<Main dataList={dataList} divViewCss1={divViewCss1} setGeometricData={setGeometricData} tabClick={tabClick}/>
<Map divViewCss2={divViewCss2} mapLat={mapLat} mapLng={mapLng}/>
<Bottom tempVal="KOOREMO"/>

전달 되는 데이터는 배열, 문자열, 함수 등 다양하게 전달 가능 합니다.

전달한 정보는 자식 화면에서 아래와 같이 전달 받 을 수 있습니다.

 

const Bottom =(props)=>{
    return(
        <footer className="container mt-2">
			<div className="row"><div className="col-12 text-center">©2022 Copyright: {props.tempVal}</div></div>
		</footer>
    )
}
    
export default Bottom;

props로 전달 받은 객체를 props.tempVal 로 전달 받은 key를 이용해서 사용 할 수 있습니다.

props는 변경이 불가능한 객체 입니다.(전달 받은 정보를 수정하는 방식은 함수형에서 hook을 사용해야 합니다.)

https://ko.reactjs.org/docs/components-and-props.html

 

Components와 Props – React

A JavaScript library for building user interfaces

ko.reactjs.org

const Main =({dataList, divViewCss1, setGeometricData, tabClick})=>{

혹은 위의 내용과 같이 전달 받는 key를 맞춰서 정보를 전달하는 '비구조화할당' 방식을 사용 할 수 있습니다.

비구조화 할당 방식은 key가 맞다면 배열, 문자열, 함수 모두 전달 가능 합니다.

 

주고 받는 정보를 수정 하는건 다음에 작성할 hook에서 정리 하도록 하겠습니다.

반응형
Posted by 질주하는구
,

bootstrap디자인에 맞춰서 폴더 및 파일을 아래와 같이 나누어서 작업 할 예정 입니다.

/inc/...

/layout/...

2개의 폴더에 각각의 폴더를 생성 합니다. 폴더 및 파일은 자신이 생각하는 이름으로 작업 해주시면 됩니다.

(패키지 및 파일명, 변수명 생각하는데 정말 많은 시간을 사용하는데 많은 페이지가 아니고 작업 해보는것에 의미가 있어 막지었습니다.)

기존에 작업한 내용을 각 파일에 맞게 잘라서 붙여 주시면 됩니다.

다른 분들 글에는 class형으로 사용하는 경우가 있는데 저는 함수형 그중에서 익명함수형으로 소스 작업을 진행 했습니다.

Top.js

const Top =()=>{
    return(
        <div className="row mt-2">
            <div className="card w-100">
                <div className="card-header bg-primary text-white">DATA SEARCH</div>
                <div className="card-body">
                    <div className="row">
                        <div className="col-md-11">
                            <form className="form-inline">
                                <div className="form-group mb-2 mr-1">
                                    <label htmlFor="searchYear">년도</label>
                                    <select className="form-control" name="searchYear" id="searchYear"> 
                                        <option value="">선택</option>                               
                                        <option value="1">1</option>
										<option value="2">2</option>
										<option value="3">3</option>
										<option value="4">4</option>
                                    </select>
                                </div>
                                <div className="form-group mb-2 mr-1">
                                    <label htmlFor="searchSiDo">시군</label>
                                    <select className="form-control" name="searchSiDo" id="searchSiDo">
                                        <option value="">선택</option>                               
                                        <option value="1">1</option>
										<option value="2">2</option>
										<option value="3">3</option>
										<option value="4">4</option>                           
                                    </select>
                                </div>
                                <div className="form-group mb-2 mr-1">
                                    <label htmlFor="searchGuGun">구군</label>                           
                                    <select className="form-control" name="searchGuGun" id="searchGuGun">
                                        <option value="">선택</option>
                                        <option value="1">1</option>
										<option value="2">2</option>
										<option value="3">3</option>
										<option value="4">4</option>
                                    </select>
                                </div>
                                <ReactBootstrap.Button type="button" className="btn btn-primary mb-2">search</ReactBootstrap.Button>
                            </form>
                        </div>
                    </div>                                      						
                </div>
            </div>
        </div>
    )
}
    
export default Top;

tab.js

const Tab =()=>{
    return(
        <div className="row mt-2">
            <ul className="nav nav-tabs w-100">
                <li className={"nav-item nav-link"}>
                목록
                </li>
                <li className={"nav-item nav-link"}>
                지도
                </li>
            </ul>
        </div>
    )
}
    
export default Tab;

main.js

const Main =()=>{  
    return(
		<div className={"row mt-1">
			<div className="card w-100">
				<div className="card-body">NO DATA.</div>
			</div>				
		</div>       
	);
}
    
export default Main;

bottom.js

const Bottom =(props)=>{
    return(
        <footer className="container mt-2">
			<div className="row"><div className="col-12 text-center">©2022 Copyright: {props.tempVal}</div></div>
		</footer>
    )
}
    
export default Bottom;

App.js

import Top from './layout/top'
import Tab from './inc/tab'
import Main from './layout/main'
import Map from './layout/map'
import Bottom from './layout/bottom'

return (
	<div className="container">
	  <Top/>
	  <Tab/>
	  <Main/>
	  <Map/>
	  <Bottom tempVal="KOOREMO"/>
	</div>
);

App.js 는 상단 import 부분과 return 영역의 내용만 작성 했습니다.

최초 생성시 index.html 내용에 보면 <body> 태그 하위에 <div id="root"></div> 영역이 있는데 이 부분을

<body id="root">
    
</body>

body 기준으로 변경 해줍니다. <div> 하나를 허용하는 경우 출력되는 화면이 의도와 다르게 출력 될 수 있어 삭제 

처리 해줬습니다.

(class container 아래에 구성요소를 위치 하는게 화면 구성을 잡기 좋아서 그렇게 했는데 필요 없는경우 그냥 기본 index.html로 작업 진행 하셔도 됩니다.)

 

위의 내용 들중 설명이 필요한 부분은 class->className으로 처리 해야 하는 부분과, Bottom을 호출할때 tempVal이라는 key에 KOOREMO라는 값을 담아 보내 줬고 해당 값을 bottom.js에서 props로 전달 받아 사용 했다는 겁니다.

 

react는 부모가 자식 구성요소에 데이터 및 함수를 전달 할 수 있는데 이 때 props를 사용 하게 됩니다.

또한 데이터는 state에 담아 변경 사항을 관리 합니다. state부분은 다음 데이터를 불러오는 부분에서 설명 하겠습니다.

 

위의 내용까지 작업 하고 나면 확인이 가능 합니다. 해당 프로젝트로 이동 후

yarn start

명령어를 실행 하면 작업한 디자인을 확인 할 수 있습니다.

 

반응형
Posted by 질주하는구
,

front-end 관련 작업은 react를 이용 하려고 합니다.

https://kooremo.tistory.com/entry/window-react-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95

 

window react 개발 환경 설정

window10 환경에서 react 개발환경을 셋팅하는 방법을 간단하게 작성 합니다. 1. node js 2. yarn 3. vscode 3가지를 설치하고 실행하는 상황까지를 정리 합니다. node js 설치를 위해서 https://nodejs.org/ko/do..

kooremo.tistory.com

의 내용을 참고 하여 PC설정 및 초기 페이지 생성을 해주시면 됩니다.

 

저는 디자인 및 CSS에 대한 지식이 많지 않기 때문에 화면 구성을 위해 bootstrap을 사용할 예정 입니다.

https://kooremo.tistory.com/entry/react-%EC%97%90%EC%84%9C-bootstrap-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

 

react 에서 bootstrap 사용하기

react에서 bootstrap을 사용하기 위해 우선 bootstrap 패키지를 설치 해줘야 합니다. -npm을 사용하는 경우 아래와 같이 npm install react-bootstrap bootstrap -yarn을 사용하는 경우 아래와..

kooremo.tistory.com

내용을 참고해서 bootstrap관련 모듈을 추가 해주시고 작업을 진행 해주시면 됩니다.

우선 작업할 페이지의 기본 화면을 bootstrap을 이용해서 만들어 주고 해당 화면을 잘라 붙이는 작업을 진행 하겠습니다.

작업한 샘플 페이지는 아래와 같습니다.

<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8">
		<meta name="Generator" content="EditPlus®">
		<meta name="Author" content="">
		<meta name="Keywords" content="">
		<meta name="Description" content="">
		<link
		  rel="stylesheet"
		  href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
		  integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l"
		  crossorigin="anonymous"
		/>
		<title>bootstrap test</title>
	</head>
	<body>
		<div class="container">
			<div class="row mt-2">
				<div class="card w-100">
					<div class="card-header bg-primary text-white">DATA SEARCH</div>
					<div class="card-body">
						<form class="form-inline">
							<div class="form-group mb-2 mr-1">
								<label for="exampleInputName1">년도</label>
								<select class="form-control" id="exampleInputName1">
								  <option>1</option>
								  <option>2</option>
								  <option>3</option>
								  <option>4</option>
								  <option>5</option>
								</select>
							</div>
							<div class="form-group mb-2 mr-1">
								<label for="exampleInputName2">시군</label>
								<select class="form-control" id="exampleInputName2">
								  <option>1</option>
								  <option>2</option>
								  <option>3</option>
								  <option>4</option>
								  <option>5</option>
								</select>
							</div>
							<div class="form-group mb-2 mr-1">
								<label for="exampleInputName3">구군</label>
								<select class="form-control" id="exampleInputName3">
								  <option>1</option>
								  <option>2</option>
								  <option>3</option>
								  <option>4</option>
								  <option>5</option>
								</select>
							</div>
							<button type="submit" class="btn btn-primary mb-2">search</button>
						</form>						
					</div>
				</div>
			</div>
			<div class="row mt-2">
				<ul class="nav nav-tabs w-100">
				  <li class="nav-item">
					<a class="nav-link active" data-toggle="tab" href="#main_area_1">main1</a>
				  </li>
				  <li class="nav-item">
					<a class="nav-link" data-toggle="tab" href="#main_area_2">main2</a>
				  </li>
				</ul>
			</div>
			<div class="row mt-2">
				<ul class="nav nav-tabs w-100">
				  <li class="nav-item nav-link active">
					main1
				  </li>
				  <li class="nav-item nav-link">
					main2
				  </li>
				</ul>
			</div>
			<div class="row mt-1" id="main_area_1">
				<div class="card w-100">
				  <div class="card-header">SEARCH RESULT</div>
				  <ul class="list-group list-group-flush">
					<li class="list-group-item">test1</li>
					<li class="list-group-item">test2</li>
					<li class="list-group-item">test3</li>
				  </ul>
				</div>				
			</div>
			<div class="row mt-1 d-none" id="main_area_2">				
				<div class="card w-100">
				  <div class="card-body">NO DATA.</div>				  
				</div>
			</div>
		</div>
		<footer class="container mt-2">
			<div class="row"><div class="col-12 text-center">©2022 Copyright: KOOREMO</div></div>
		</footer>
	</body>
</html>

bootstrap5가 나온지 한참 지났는데 익숙하지 않아 bootstrap4로 진행 하겠습니다.

해당 소스는 cdn으로 작업해서 브라우저에서 확인이 가능 합니다.

상단 검색 영역, 목록 지도를 구분하는 탭, 그리고 데이터가 출력되는 메인, 마지막 바닥글로 되어 있습니다.

지금 설명한 저 영역에 맞게 화면을 자르고 자른 영역별로 폴더 및 파일들을 구성할 예정 입니다.

반응형
Posted by 질주하는구
,

데이터를 front-end에 전달하기 위한 controller 소스를 작업 해줍니다.

@RestController("areaCodeController")
@RequestMapping("/areacode")
@CrossOrigin(origins = "*")
public class AreaCodeController {
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	@Qualifier("apiCallComp")
	private ApiCallComp apiCallComp;
	
	@Autowired
	@Qualifier("koreaAreaCodeService")
	private KoreaAreaCodeService koreaAreaCodeService;
	
	@RequestMapping(value="/list", method = RequestMethod.GET)
	public String dataList(@ModelAttribute SearchParamVO searchParam) throws Exception{
		return apiCallComp.callAccidentAreaInfo(searchParam);
	}
	
	@RequestMapping(value="/list/page/{searchPageNo}", method = RequestMethod.GET)
	public String dataPageList(@ModelAttribute SearchParamVO searchParam) throws Exception{
		return apiCallComp.callAccidentAreaInfo(searchParam);
	}
	
	@RequestMapping(value="/year/list", method = RequestMethod.GET)
	public ArrayList<String> yearList() throws Exception{
		ArrayList<String> yearList = new ArrayList<String>();
		yearList.add("2015");
		yearList.add("2016");
		yearList.add("2017");
		yearList.add("2018");
		yearList.add("2019");
		yearList.add("2020");
		yearList.add("2021");
		yearList.add("2022");
		return yearList;
	}
	
	@RequestMapping(value="/sido/list", method = RequestMethod.GET)
	public List<AreaCodeDTO> siDoList() throws Exception{
		return koreaAreaCodeService.koreaAreaCodeList("1");
	}
	
	@RequestMapping(value="/{searchSiDo}/gugun/list", method = RequestMethod.GET)
	public List<AreaCodeDTO> gugunList(@ModelAttribute SearchParamVO searchParam) throws Exception{
		return koreaAreaCodeService.koreaAreaCodeChildList(searchParam.getSearchSiDo());
	}
}

데이터 결과를 body에 담아서 주기 때문에 @RestController 어노테이션을 선언 했습니다.

조회만 있기 때문에 모든 요청은 GET 메소드만 허용을 했고

다른 도메인 요청에 대한 응답을 해줘야 하기 때문에 @CrossOrigin(origins = "*") 을 추가 했습니다.

지금은 연습 이여서 *으로 모든 연결을 허용 하지만 해당 설정을 서비스나 프로젝트에서 사용하시는 경우 허용 영역을

지정 해주는게 좋습니다.

(origins = "http://test1.test.com, http://test2.test.com" 등으로 정의 해줄수 있습니다.)

@Autowired 시 @Qualifier("apiCallComp") 를 명시한 이유는 동일한 인터페이스를 구현한 구현체가 여럿 있을경우 생기는 충돌을 미리 예방하기 위해 습관적으로 코딩 하는 부분 이여서 @Autowired만 사용하여도 무방 합니다.

파리미터를 전달 받을때 @PathVariable 을 포함한 모든 정보를 SearchParamVO에 정의 했기 때문에 @ModelAttribute SearchParamVO 으로 받았지만 restfull url방식에는 맞지 않을거 같아 연습시 참고 하지 않는게 좋을거 같습니다.

SearchParamVO의 내용은 아래와 같습니다.

@Getter
@Setter
@ToString
public class SearchParamVO {
	private String searchColumn;
	private String searchWord;
	private String searchYear="2017";//2017년 이후 데이터는 공공데이터포털에 거의 없어서 2017년으로 기본설정
	private String searchSiDo="11680";//서울시
	private String searchGuGun="11680";//강남구
	private String searchPageNo="1";//1페이지
}

사용하는 api에 존재하는 데이터가 있는 검색 조건을 미리 설정 해서 무조건 데이터가 검색 되게 진행 했습니다.

이 부분 validation 어노테이션 이용해서 검증을 추가 해주셔야 합니다.

front-end부분에도 관련 영역만 만들고 작업은 진행 하지 않았습니다.

 

back-end는 위의 내용까지 간단하게 정리가 되었습니다. 다음에는 react front-end 작업 내용을 정리 하겠습니다.

반응형
Posted by 질주하는구
,

오픈api에서 데이터 가지고 오는 작업을 완료 했으니 서비스 페이지에서 사용할 법정동 코드 정보를

DB에 구축하고 해당 정보를 조회하는 data-jpa관련 소스 작업을 진행 해야 합니다.

 

실제 서비스가 아닌 연습용이고 1개의 테이블에 몇천건의 데이터만 있으면 되기 때문에 database는 h2 데이터베이스를 사용 할것이고 조회는 data-jpa을 이용해서 간단하게 작성 하려고 합니다.

 

우선 h2 database관련 설정을 추가 하기 위해 application.properties에 아래의 내용을 추가 해줍니다.

spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

spring.datasource.url=jdbc:h2:mem:test;
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

url을 보면 h2메모리 DB를 사용할것이기 때문에 h2:mem:test로 명시되어 있습니다. 이렇게 작성 하면 메모리에 test라는

database가 생성되고 서비스 운영중에만 유효한 데이터 변동이 발생됩니다.

(서비스 종료시에는 작업 내용이 모두 삭제 되기 때문에 해당 내용을 유지하고자 하면 h2:~/test 로 설정을 해주면 됩니다. 이 경우 내문서 하위에 test db파일이 생성되고 작업 내용을 서비스 여부와 상관없이 유지 해줍니다.)

비밀번호 없이 로그인 가능하기 때문에 비밀번호 설정은 하지 않아도 됩니다.

 

상단의 console관련 설정이 필요 없다면 하지 않아도 되지만 운영중 테이블 정보를 확인 하기 위해서는 해당 설정을 해주는게 좋습니다.

운영중인 서비스에 /h2-console path로 접속을 해주면 로그인 화면이 나오고 간단한 관리자 화면이 노출 됩니다.

접속시 jdbc url 부분을 mem에 맞춰 주셔야 합니다.

접속 하면 운영중인 메모리 DB의 내용을 확인 할 수 있습니다.

 

해당 설정 까지 해주시면 h2 데이터베이스에 대한 기본적인 설정은 마무리가 되었습니다.

이제 추가 적인 설정 부분을 작성 하고 설정 부분에 대한 설명은 마무리 하겠습니다.

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.sql.init.mode=always
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.hibernate.ddl-auto=none

위의 내용중 구현체로 하이버네티를 사용하기 때문에 하이버네이트 관련 설정이 들어가 있습니다.

특별한 내용이 없는데 특이한 부분은 있습니다. sql.init.mod / hibernate.ddl-auto 부분 입니다. 저는 schema.sql, data.sql

파일을 이용해서 데이터 베이스 초기화를 해줄것이기 때문에 sql.init.mode 부분을 활성화 했습니다. 또한 hibernate의

auto ddl관련 부분이 중복으로 실행되어 문제를 일의 킬수 있기 때문에 해당 부분은 동작하기 않게 none로 설정 했습니다.

(의도하지 않은 DB변화가 생기는 경우가 있어 하이버네이트 단독으로 사용할때는 저 부분은 none로 설정하고 사용 했었습니다.)

해당 설정에 맞춰서 resources 폴더 하위에 schema.sql 파일과 data.sql파일을 생성해서 넣어 주시면 서비스 시작시 해당 테이블과 정보가 생성 됩니다.

 

이제 h2 데이터베이스와 data-jpa관련 설정이 끝났으니 data-jpa관련 소스를 생성 해주면 됩니다.

repository 폴더 하위에 데이터 조회를 위한 class를 생성 해줍니다.

@Repository("koreaAreaCodeRepository")
public interface KoreaAreaCodeRepository extends JpaRepository<AreaCode, String>{
	
	//depth에 해당하는 지역코드 목록
	List<AreaCode> findByAreaDepth(String areaDepth, Sort sort);
	
	//parentCode에 해당하는 지역코드 목록
	List<AreaCode> findByParentCode(String parentCode, Sort sort);
}

queryDsl까지는 필요 하지 않고 간단하게 메소드로 쿼리문을 작성 할 수 있습니다.

해당 메소드를 간단하게 설명 해주면

findByAreaDepth(String areaDepth, Sort sort) select쿼리 실행 where 조건 area_depth order by 조건으로 Sort객체 사용

입니다.

해당 repository를 호출하는 service 부분 소스를 작성 해야 합니다.

@Service("koreaAreaCodeService")
public class KoreaAreaCodeServiceImpl implements KoreaAreaCodeService {
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Resource(name="koreaAreaCodeRepository")
	private KoreaAreaCodeRepository koreaAreaCodeRepository;
	
	@Override
	public List<AreaCodeDTO> koreaAreaCodeList(String depth) throws Exception {
		List<AreaCode> dataList 					= koreaAreaCodeRepository.findByAreaDepth(depth, Sort.by(Sort.Direction.ASC, "viewOrder"));
		List<AreaCodeDTO> areaCodeList 	= dataList.stream().map(AreaCode::entityToDTO).collect(Collectors.toList());
		
		return areaCodeList;
	}

	@Override
	public List<AreaCodeDTO> koreaAreaCodeChildList(String parentCode) throws Exception {
		List<AreaCode> dataList 					= koreaAreaCodeRepository.findByParentCode(parentCode, Sort.by(Sort.Direction.ASC, "viewOrder"));
		List<AreaCodeDTO> areaCodeList 	= dataList.stream().map(AreaCode::entityToDTO).collect(Collectors.toList());
		
		return areaCodeList;
	}

}

설명할 만한 부분이 거의 없습니다. 정렬 조건을 추가 해주는 부분과 domain->DTO로 변경 하는 부분만 보시면 됩니다.

해당 class에서 사용한 domain 및 DTO 클래스는 아래와 같습니다.

@Entity
@Getter
@ToString
@NoArgsConstructor
@Table(name = "KOREA_AREA_CODE")
public class AreaCode implements Serializable{
	private static final long serialVersionUID = 1L;
	@Id
    @Column(name = "AREA_CODE", nullable=false, insertable=true, updatable=false)
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private String areaCode;
	
	@Column(name = "AREA_NAME", length=100, nullable=true, insertable=true, updatable=true)
	private String areaName;
	
	@Column(name = "PARENT_CODE", length=10, nullable=true, insertable=true, updatable=true)
	private String parentCode;
	
	@Column(name = "VIEW_ORDER", nullable=true, insertable=true, updatable=true)
	private Integer viewOrder;
	
	@Column(name = "AREA_DEPTH", length=2, nullable=true, insertable=true, updatable=true)
	private String areaDepth;
	
	@JsonIgnore
	@ManyToOne(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY)//LAZY 지연호출, EAGER 즉시호출
    @JoinColumn(name = "PARENT_CODE", insertable=false, updatable=false)
    private AreaCode parent;
	
	@JsonProperty("nodes")
	@OneToMany(fetch = FetchType.LAZY)
	@JoinColumn(name = "PARENT_CODE")
	@OrderBy("viewOrder asc")
	private List<AreaCode> children = new ArrayList<AreaCode>();
	
	@Builder(builderClassName = "ByAreaCodeBuilder", builderMethodName = "ByAllBuilder")
	public AreaCode(String areaCode, String areaName, String parentCode, Integer viewOrder) {
		this.areaCode = areaCode;
		this.areaName = areaName;
		this.parentCode = parentCode;
		this.viewOrder = viewOrder;
	}
	
	public AreaCodeDTO entityToDTO() {
		AreaCodeDTO areaCodeDTO = new AreaCodeDTO();
		ModelMapper mMapper = new ModelMapper();
		mMapper.map(this, areaCodeDTO); 
		
		return areaCodeDTO;
    }
}
@Getter
@Setter
@ToString
@NoArgsConstructor
public class AreaCodeDTO {
    private String areaCode;
	private String areaName;
	private String parentCode;
	private Integer viewOrder;
	
	public AreaCode dtoToEntity() {
		AreaCode areaCode = AreaCode.ByAllBuilder().areaCode(this.areaCode).areaName(this.areaName).parentCode(this.parentCode).viewOrder(this.viewOrder).build();
		return areaCode;
    }
}

특이한 내용은 없고 소스 간소화를 위해 lombok을 사용했다 정보만 인지 하시면 될거 같습니다.

이제 해당 서비스를 호출하는 Controller 부분만 간략하게 다음에 정리하면 backend부분은 모두 마무리가 됩니다.

반응형
Posted by 질주하는구
,

기본 셋팅이 마무리 되었고 어떤 구성으로 소스를 생성 할지도 결정 했으니 OpenApi를 호출하는 componet를 작성 해보겠습니다.

ApiCallComp 는 검색 조건을 전달 받아 api를 호출 하고 그 결과를 return 해주는 아주 간단한 소스 입니다.

URL 호출은 HttpClient 패키지를 통해서 작업 하겠습니다.

저는 편한 okhttp를 주로 사용 했는데 HttpClient를 사용하는 이유는 해당 패키지가

https://docs.upbit.com/reference/%EC%A0%84%EC%B2%B4-%EA%B3%84%EC%A2%8C-%EC%A1%B0%ED%9A%8C

 

업비트 개발자 센터

업비트 Open API 사용을 위한 개발 문서를 제공 합니다.업비트 Open API 사용하여 다양한 앱과 프로그램을 제작해보세요.

docs.upbit.com

업비트 api의 예제 소스로 사용되는 패키지 이기 때문 입니다.

http연결 부터 뭔가 강력한 신뢰를 느낄수 있습니다.(http연결 관련한 라이브러리는 워낙에 잘 되어 있어서 무엇을 사용하던 상관 없습니다. 개인적으로는 okhttp를 추천하고 싶습니다.)

 

@Component("apiCallComp")
public class ApiCallComp {
	private String serviceUrl = "http://apis.data.go.kr/B552061/frequentzoneChild/getRestFrequentzoneChild";
	
	//application.properties의 설정 정보를 가지고 올때 @Value("${설정key}") 형식으로 호출
	@Value("${openApi.serviceKey}")
	private String serviceKey;
	
	public String callAccidentAreaInfo(SearchParamVO paramVO) throws ClientProtocolException, IOException {
		HttpClient client 				= HttpClientBuilder.create().build();
        HttpGet request 				= new HttpGet(serviceUrl+"?"+URLEncodedUtils.format(makeParameterList(paramVO.getSearchYear(), paramVO.getSearchSiDo(), paramVO.getSearchGuGun(), paramVO.getSearchPageNo()), "utf-8"));
        HttpResponse response 			= client.execute(request);
        HttpEntity entity 				= response.getEntity();
        
        String returnJson				= EntityUtils.toString(entity, "UTF-8");
		return returnJson;
	}
	
	public List<BasicNameValuePair> makeParameterList(String searchYearCd, String siDo, String guGun, String searchPageNo){
		List<BasicNameValuePair> params = new LinkedList<BasicNameValuePair>();
		
        params.add(new BasicNameValuePair("ServiceKey", serviceKey));
        params.add(new BasicNameValuePair("type", "json"));
        params.add(new BasicNameValuePair("searchYearCd", searchYearCd));
        params.add(new BasicNameValuePair("siDo", makeSiDoCode(siDo)));
        params.add(new BasicNameValuePair("guGun", makeGuGunCode(guGun)));
        params.add(new BasicNameValuePair("numOfRows", "10"));
        params.add(new BasicNameValuePair("pageNo", searchPageNo));        
        
        return params;
	}
	
	//법정동 코드값 앞 2자리만 사용 (ex>4111100000 -> 41)
	public String makeSiDoCode(String siDo) {
		return StringUtils.substring(siDo, 0, 2);
	}
	//법정동 코드값 2~5자리만 사용  (ex>4111100000 -> 111)
	public String makeGuGunCode(String guGun) {
		return StringUtils.substring(guGun, 2, 5);
	}
	
}

특별한 내용은 없고 service키를 가지고 오기 위해 application.properties 파일에 openApi.serviceKey 프로퍼티를 추가 했습니다.

추가로 get방식으로 호출시 현재 소스는 BasicNameValuePair 를 이용하고 있지만 다른 방법으로 아래와 같은 방법도

존재 합니다.

URIBuilder builder = new URIBuilder();
builder.setScheme("http")
		.setHost(host)
		.setPort(port)
		.setPath(yourpath)
		.setParameter("column1", "data1")
		.setParameter("column2", "data2");

파라미터 전달 시 편한 방법으로 선택 해서 소스 구성을 해주시면 됩니다.

현재 소스에서는 응답 실패에 대한 처리가 되어 있지 않은데 서비스 하시려는 소스 에서는 관련 소스가 추가 되어야

합니다.

반응형
Posted by 질주하는구
,

사용자 화면에서 호출할 backend 작업 부터 진행을 합니다.
작업할 내용이 많지 않기 때문에 gradle 구성을 간단하게
(data-jdbc, data-jpa, web, web-service, lombok, h2database, querydsl-jpa)
기준으로 작성 해줍니다.

패키지 구성은 아래의 이미지와 같습니다. 패키지 구성은 큰 의미가 있는건 아니고 제가 개인적으로 작업 시에 많이 사용하는 구성 입니다.

(패키지 구성, 함수명, 클래스명 변수명 언제나 고민이 많으실텐데 상황에 맞게 구성하는게 좋다고 생각 합니다. 가장 중요한건 룰을 만들 없으면 그 룰을 지키는 거라고 생각 합니다. 제가 만든것도 잘 안지키니...)

DB에 저장된 내용은 법정동 코드를 조회하기 위한 테이블 하나 이기 때문에 그에 상응 하는 domain이 하나만(아... 오타 있네...) 존재 하고 역시 관련 repository도 하나만 존재 합니다. custom, impl 패키지가 비어 있는데 이건 queryDsl을 

사용하는 경우 채워지는 영역이라 현재는 비어 있습니다.(앞으로도 비어 있을거라 삭제 하게 될거 같습니다.)

answer패키지 하위로 실제 작업을 진행할 controller, service, dto 관련 패키지를 생성 했습니다.

resources 하위에는 application.properties이외에 h2 database초기화에 사용할 schema.sql과 data.sql이 자리 잡고 있습니다.

 

다음 페이지 부터는 설정 및 개별 소스에 대한 간단한 설명을 작성 하겠습니다.

spring-boot패키지 생성시 선택한 옵션은 아래 사진과 같습니다.

반응형
Posted by 질주하는구
,

우선 react와 spring-boot관련 개발 환경을 구성 합니다.
React는 아래의 글을
https://kooremo.tistory.com/entry/window-react-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95
spring-boot는 아래의 글을
(정리한게 없어서 정리해서 링크 작성)

 

참고해서 설정 해주시면 됩니다.

반응형
Posted by 질주하는구
,

개인 프로젝트로 진행하는 CMS가 너무 규모가 크고 진행이 더뎌... 손에 잡히지 않아 몇달째 1~2주 단위로 진행 하는 토이 프로젝트를
계속 하고 있습니다.
지금 진행하는 내용 전국어린이사고다발지역 조회 현재 위치 기준으로 혹은 검색지 기준으로 어린이 사고 다발지역을 검색하고 인지 해서
우리 아이의 안전을 미리 미리 준비하는 그런 사이트 인데... 공공데이터 포털의 데이터가 충실하지 못해...
써먹을 만한 무언가가 되질 못했습니다.
(데이터 구성을 안하는건지 아니면 조건에 부합하는 데이터가 그게 다인건지 정말 검색 데이터가 조촐 합니다.)

우선 참고한 api는 
https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15058925
페이지 입니다.

서비스 구성은 아래와 같습니다.
aws(apache)<->gcp(docker)
react<->spring-boot jar
h2 memory db

화면은 bootstrap으로 간단하게 구성 했습니다.
현재까지 진행 상태는 react<->spring-boot 데이터 연동 및 노출 까지이고 앞으로 docker에 jar이미지 포팅 및 react 네이버 지도 연동이 남았습니다.

다음 글 부터는 기본 셋팅 및 작업 진행 내역을 정리해서 올리겠습니다.

반응형
Posted by 질주하는구
,