지난 시간엔 'Servlet Controller' 'JPA' 'MySQL' 을 이용해 회원 정보 등록 기능을 구현했다.
(지난 게시글 보러가기)
이번에는 BufferedReader를 이용해 주차장 데이터가 들어있는 CSV 파일을 파싱해서 DB에 저장해주는 기능을 개발해볼 것이다.
파싱해온 데이터는 프론트에서 받은 위도,경도를 기반으로 주차장 정보를 제공해줄 때 사용될 예정이다.
이번 글에서는 데이터가 잘 파싱되어 DB에 저장되는지만 확인하기 위해 샘플데이터를 사용할 것이다.
원래는 컬럼이 훨씬 많지만 테스트 결과의 가시성을 위해 주차장명, 전화번호로 컬럼을 축약했다.
이제 DB에 저장하기 위한 코드를 작성하겠다. 우선 DTO를 작성해야한다.
DTO 생성
Park.java
@ToString
@Getter @Setter
@Table(name = "TEST_PARKING")
@Entity
public class Park {
@Id
@Column(name= "park_name")
private String park_name;
@Column(name = "park_tel")
private String park_tel;
@Table 어노테이션에 테이블명을 입력하여 DB의 테이블과 매핑시켜 주었고
주소와 전화번호를 저장하기 위해 추가한 park_name, park_tel 변수는 @Column 어노테이션을 통해 DB의 컬럼과 매핑했다.
다음으로 DB에서 테이블을 생성해보자.
DB 테이블 생성
@Table 어노테이션에 입력한 이름에 맞게 test_parking 이란 이름으로 테이블을 생성하고 @Column 어노테이션의 name에 맞게 컬럼을 추가했다.
CSV / DTO / DB 테이블 을 전부 준비했으니 이제 본격적으로 개발에 들어가겠다.
먼저 Park데이터 저장 수행을 위한 ParkRepository를 만들어야한다.
ParkingRepository 생성
ParkingRepository.java
public interface ParkingRepository {
void save();
List<Park> findByAdrs(String address);
List<Park> findByLocation(String lat, String lng);
List<Park> findAll();
구현하려하는 기능은 4가지이다.
save : 주차장 csv를 끌어와서 DB에 저장
findByAdrs : 입력받은 주소가 포함된 주차장 데이터를 DB에서 추출하여 리턴
Ex) "경기도" 입력 시 주소에 "경기도"가 포함 된 주차장 정보 리턴
findByLocation : 입력받은 위도(lat) 경도(lng)의 (+-n)값 사이에 있는 주차장 데이터를 DB에서 추출하여 리턴
findAll : DB에서 모든 주차장 데이터를 추출하여 리턴
어떤 기능을 개발할 지 인터페이스를 만들었으니 이를 기반으로 구현체를 만들어보자.
실제 데이터를 사용하기 전엔 save 함수만 개발할 것이다. 샘플 데이터는 '주차장 이름'과 '전화번호' 만 있기 때문이다.
TestParkingRepository.java
@Transactional
@Component
public class TestParkingRepository implements ParkingRepository{
private EntityManager em;
@Autowired
public TestParkingRepository(EntityManager em) {
this.em = em;
}
@Override
public void save() {
Query q = em.createQuery("DELETE FROM Park"); //PARK_DATA 초기화
q.executeUpdate();
String path = System.getProperty("user.dir"); //csv파일 path 저장
FileReader in = null;
BufferedReader bufIn = null;
try {
in = new FileReader(path + "\\src\\main\\java\\jh\\ParkingLot\\repository\\parking\\data.csv");
bufIn = new BufferedReader(in);
bufIn.readLine(); // 컬럼명은 저장되지 않도록 한 줄 읽기
String data;
do {//파일에서 데이터를 읽어 파싱하고 Park 객체로 만들어 ArrayList에 넣는다.
data = bufIn.readLine(); //한 라인 읽기
if(data != null) {
String[] parkInfo = data.split(","); //콤마로 분리하기
Park park = new Park(); //Park 객체 생성하기
if (data != null) {
park.setPrkplce_no(parkInfo[0].isEmpty() ? "" : parkInfo[0]); //객체에 값 저장하기
park.setPrkplce_nm(parkInfo[1].isEmpty() ? "" : parkInfo[1]);
em.persist(park); //DB에 컬럼별로 split하여 추출한 데이터 저장
}
}
}while (data != null);
//1번째 행 삭제 쿼리 추가해야함
} catch (IOException e) {
System.out.println(e.getMessage());
} finally {
try {
in.close();
} catch (Exception e) {
}
try {
bufIn.close();
} catch (Exception e) {
}
}
}
@Transactional 어노테이션은 데이터조작어(DML)인 DELETE나 UPDATE가 사용되는 클래스에 꼭 적어주어야 하는데 이 클래스 내의 save()함수에서 DB를 초기화 시키는 과정에 DELETE 가 사용되기 때문에 필수로 추가해 주어야한다.
0. 생성자 만들기
JPA 사용을 위해 스프링 컨테이너에서 EntityManager 를 가져오고, 생성자 부분에서 Autowired 처리 해준다.
생성자가 하나고 스프링 빈이라면 @Autowired 를 생략할 수 있지만 가시성을 위해 적어주었다.
(@Component 어노테이션으로 이 클래스를 스프링 컨테이너에 스프링 빈으로 등록함)
1. DELETE 쿼리문 작성
save 함수는 DB 의 테이블을 초기화 한 다음에 데이터 저장을 수행하도록 로직을 짯다.
( csv파일이 업데이트 되면 데이터를 통째로 다시 DB에 집어 넣을 것이기 때문! )
em.createQuery("DELETE FROM Park") 를 쿼리형 변수 q에 저장하고 excuteUpdate() 함수를 실행하면
엔티티 매니저가 Park DTO와 연결된 'TEST_PARKING' 테이블을 찾아 sql문을 수행할 것이다.
2. CSV Path 저장
이제 CSV 파일에서 데이터를 파싱하기 위해 세팅을 해야한다.
우선 CSV 파일의 경로를 담을 path 변수를 작성한다. 원래는 Repository 와 CSV 파일을 같은 패키지에 넣고
System.getProperty("user.dir") 함수를 사용하여 현재 Repository 의 경로를 path에 저장한 다음 path + "\\data.csv" 로
FileReader 의 경로를 지정해 줄 생각이었지만 프로젝트의 경로만 path에 저장되는 문제가 생겨 일단 직접 아래에서 경로를 입력해주었다.
3. FileReader, BufferedReader 설정
path가 준비되었으니 FileReader를 생성하여 안에 path + 상세주소 를 입력한 뒤 BufferedReader에 삽입한다.
이렇게 하면 BufferedReader의 readLine() 함수를 통해 csv파일을 한줄씩 읽어오는게 가능하다!
이 부분은 예외처리를 위해 try catch 구문을 사용했다.
4. 반복문을 통한 데이터 저장
do while 반복문으로 버퍼리더 에서 읽어온 한 줄의 데이터가 null값이 아니라면, split(",") 함수로 분리해 parkInfo 배열에 저장하게 했다.
여기서 왜 ',' 로 분리가 되는가 궁금하신 분들이 있으실텐데 CSV 파일이란 기본적으로 구분자를 콤마(,)로 분류한 텍스트 형태의 파일을 의미한다. 몰랐던 분들은 참고하길 바란다.
앞의 과정으로 분리되어 저장된 parkInfo[n]는 Park 객체에 입력할 것인데, 만약 읽어온 데이터가 빈 값이라
parkInfo에 저장되지 못했을 상황을 생각해서 set 로직에 삼항 연산자를 사용했다.
park.setPrkplce_no(parkInfo[0].isEmpty() ? "" : parkInfo[0]); //객체에 값 저장하기
park.setPrkplce_nm(parkInfo[1].isEmpty() ? "" : parkInfo[1]);
만약 parkInfo[n]의 값이 존재하지 않는다면 ""을 저장, 값이 존재한다면 parkInfo[n]를 저장시켜라 라는 뜻이다.
마지막으로 저장된 park객체를 em.persist(park)로 DB에 넘겨주면 끝이다.
Config에 스프링 빈 등록
UserConfig.class
@Bean
public ParkingRepository parkingRepository() { return new TestParkingRepository(em);}
UserRepository를 스프링 빈으로 등록하기 위해 만들었던 UserConfig 에 parkingRepository도 추가해주면 끝이다.
간단한 과정이다.
실행 테스트
이제 프로그램이 정상적으로 작동하는지 테스트 해 볼 것이다.
뭔가 이상한 글자가 출력되며 저장이 되질 않는다. 이렇게 문자가 깨지는 경우는 인코딩 문제이다.
csv파일의 인코딩을 utf-8로 변환해면 해결할 수 있다.
엑셀로 되어있는 csv파일을 메모장으로 실행시켜준다.
인코딩이 ANSI로 되어있는 상태이다. 이래서 글자가 깨지는 오류가 난 것이다. 인코딩을 utf-8로 바꿔주자
이제 문제가 해결됐다. 다시 실행시켜보면 결과는 성공이다.
이렇게 CSV 파일로부터 샘플 데이터를 파싱하여 DB에 저장하는 것에 성공했다. 다음 시간에는 오늘 작성한 코드들을
조금씩 수정하여 원본 데이터를 파싱하고 DB에 저장하는 과정을 보여줄 것이다.
이번 글은 조금 길었는데 읽어주셔서 감사합니다!
'Language > JAVA' 카테고리의 다른 글
[JAVA] ObjectMapper를 통한 손쉬운 객체 형식 변환 (2) | 2022.09.17 |
---|---|
[Java] 여러 데이터를 저장하는 법 - Array, List, Map (3) | 2022.08.15 |
[Java] [객체지향] SOLID 원칙 - 좋은 객체 지향 설계의 5가지 원칙 (9) | 2022.01.30 |
[JAVA] 자바 JVM, JRE, JDK 차이 (2) | 2022.01.20 |
[Java] 윈도우 설정으로 CSV 파일 ,(콤마) 말고 다른 구분자로 변경하기! (1) | 2021.12.13 |