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 질주하는구
,