대용량 데이터(전국 병의원 정보)
no | 컬럼명 | Java | 타입 | 설명 |
---|---|---|---|---|
1 | id(pk) | id | Int | 번호 |
2 | open_service_name | openServiceName | VARCHAR(10) | 개방서비스명 |
3 | open_local_government_code | openLocalGovernmentCode | int | 개방자치단체코드 |
4 | management_number(unique) | managementNumber | varchar(40) | 관리번호 |
5 | license_date | license_date | datetime | 인허가일자 |
6 | business_status | businessStatus | tinyint(2) | 1: 영업/정상 2: 휴업 3: 폐업 4: 취소/말소영업상태구분 |
7 | business_status_code | businessStatusCode | tinyint(2) | 영업상태코드 2: 휴업 3: 폐업 13: 영업중 |
8 | phone | phone | varchar(20) | 소재지전화 |
9 | full_address | fullAddress | VARCHAR(200) | 소재지전체주소 |
10 | road_name_address | roadNameAddress | VARCHAR(200) | 도로명전체주소 |
11 | hospital_name | hospitalName | VARCHAR(20) | 사업장명(병원이름) |
12 | business_type_name | businessTypeName | VARCHAR(10) | 업태구분명 |
13 | healthcare_provider_count | healthcareProviderCount | tinyint(2) | 의료인수 |
14 | patient_room_count | patientRoomCount | tinyint(2) | 입원실수 |
15 | total_number_of_beds | totalNumberOfBeds | tinyint(2) | 병상수 |
16 | total_area_size | totalAreaSize | float | 총면적 |
CREATE TABLE `likelion-db`.`nation_wide_hospitals` (
`id` INT NOT NULL,
`open_service_name` VARCHAR(10) NULL COMMENT '개방서비스명',
`open_local_government_code` INT NOT NULL COMMENT '개방자치단체코드',
`management_number` VARCHAR(45) NULL COMMENT '관리번호',
`license_date` DATETIME NULL COMMENT '인허가일자',
`business_status` TINYINT(2) NULL COMMENT '영업상태구분(boolean)\n1: 영업/정상\n2: 휴업\n3: 폐업\n4: 취소/말소',
`business_status_code` TINYINT(2) NULL COMMENT '영업상태코드\n2: 휴업\n3: 폐업\n13: 영업중',
`phone` VARCHAR(20) NULL COMMENT '소재지전화',
`full_address` VARCHAR(200) NULL COMMENT '소재지전체주소',
`road_name_address` VARCHAR(200) NULL COMMENT '도로명전체주소',
`hospital_name` VARCHAR(20) NULL COMMENT '사업자명',
`business_type_name` VARCHAR(10) NULL COMMENT '업태구분명',
`healthcare_provider_count` TINYINT(2) NULL COMMENT '의료인수',
`patient_room_count` INT NULL COMMENT '입원실수',
`total_number_of_beds` INT NULL COMMENT '병상수',
`total_area_size` FLOAT NULL COMMENT '총면적',
PRIMARY KEY (`id`),
UNIQUE INDEX `management_number_UNIQUE` (`management_number` ASC) VISIBLE);
❓ 컬럼 타입을 세분화 하는 이유 ?
DB에 저장공간을 줄이고, 검색 속도를 최적화 하기 위함
Tinyint
Smallint
Int
Bigint
tinyint | smallint | int | bigint | |
---|---|---|---|---|
크기 | 0 ~ 2^8 | -2^15 ~ (2^15 - 1) | -2^31 ~ (2^31 - 1) | -2^63 ~ (2^63 - 1) |
8 = 2^3 | 15 = 2^4 -1 | 31 = 2^5 - 1 | 63 = 2^6 - 1 | |
용량 | 1 byte | 2 byte | 4 byte | 8 byte |
1 byte | tinyint * 2 byte | smallint * 2 byte | int * 2 byte |
DateTime
Float
❓ Lombok이란?
Java 라이브러리로 반복되는getter,setter,toString 등의 메서드 작성 코드를 줄여주는 코드 다이어트 라이브러리입니다.
즉, Lombok을 사용하면 생성자, getter/setter/toString 등의 메서드를 작성하지 않아도 실제로 컴파일된 결과물에서는 자동으로 생성이 되어 있다.
@NoArgsConstructor
- 비어있는 생성자를 자동으로 만들어줌
@AllArgsConstructor
- 모든 필드값을 파라미터로 받는 생성자를 자동으로 만들어줌
@Getter/Setter
- Getter/Setter를 자동으로 만들어줌
@ToString
- toString을 자동으로 만들어줌
@AllArgsConstructor // lombok이 알아서 생성자를 만들어줌 즉, 내가 만들지 않아도 됨
@Getter // Getter 생성
@NoArgsConstructor // 빈 생성자 생성
@Setter
public class Hospital {
private int id;
private String openServiceName;
private int openLocalGovernmentCode;
private String managementNumber;
private LocalDateTime licenseDate;
private int businessStatus;
private int businessStatusCode;
private String phone;
private String fullAddress;
private String roadNameAddress;
private String hospitalName;
private String businessTypeName;
private int healthcareProviderCount;
private int patientRoomCount;
private int totalNumberOfBeds;
private float totalAreaSize;
}
(1) Parser(Interface)
public interface Parser<T> {
T parse(String str);
}
(2) HospitalParser
public class HospitalParser implements Parser<Hospital> {
@Override
public Hospital parse(String str) {
String[] row = str.split("\",\""); // \,\ 기준으로 나눔
Hospital hospital = new Hospital();
hospital.setId(Integer.parseInt(row[0].replace("\"",""))); // 첫번째는 \으로 시작하기 때문에 replace해주고 시작
hospital.setOpenServiceName(row[1]);
hospital.setOpenLocalGovernmentCode(Integer.parseInt(row[3]));
hospital.setManagementNumber(row[4]);
int year = Integer.parseInt(row[5].substring(0, 4));
int month = Integer.parseInt(row[5].substring(4, 6));
int day = Integer.parseInt(row[5].substring(6, 8));
hospital.setLicenseDate(LocalDateTime.of(year,month,day,0,0,0));
hospital.setBusinessStatus(Integer.parseInt(row[7]));
hospital.setBusinessStatusCode(Integer.parseInt(row[9]));
hospital.setPhone(row[15]);
hospital.setFullAddress(row[18]);
hospital.setRoadNameAddress(row[19]);
hospital.setHospitalName(row[21]);
hospital.setBusinessTypeName(row[25]);
hospital.setHealthcareProviderCount(Integer.parseInt(row[29]));
hospital.setPatientRoomCount(Integer.parseInt(row[30]));
hospital.setTotalNumberOfBeds(Integer.parseInt(row[31]));
hospital.setTotalAreaSize(Float.parseFloat(row[32].replace("\"",""))); // 마지막은 \로 끝나기 때문에 replace로 지워준다
return hospital;
}
}
💡 LocalDateTime은 초기화 하는 방식이 다르다
각 년,월,일을 각자 받아서 나중에 한번에 초기화 해줘야한다.
@substring( )
를 통해 원하는 구역별로 나눌 수가 있다
위의 표처럼 0~3까지는 year, 4~5까지는 month, 6~7까지는 day의 값을 입력받아@LocalDateTime.of(year,month,day,hour,minute,second)
를 통해 값을 넣어 초기화 할 수 있다.
(3) HospitalParserTest
String line1 = "\"1\",\"의원\",\"01_01_02_P\",\"3620000\",\"PHMA119993620020041100004\",\"19990612\",\"\",\"01\",\"영업/정상\",\"13\",\"영업중\",\"\",\"\",\"\",\"\",\"062-515-2875\",\"\",\"500881\",\"광주광역시 북구 풍향동 565번지 4호 3층\",\"광주광역시 북구 동문대로 24, 3층 (풍향동)\",\"61205\",\"효치과의원\",\"20211115113642\",\"U\",\"2021-11-17 02:40:00.0\",\"치과의원\",\"192630.735112\",\"185314.617632\",\"치과의원\",\"1\",\"0\",\"0\",\"52.29\",\"401\",\"치과\",\"\",\"\",\"\",\"0\",\"0\",\"\",\"\",\"0\",\"\",";
@Test
@DisplayName("csv 1줄을 Hospital로 잘 만드는지 Test")
void converToHospital(){
HospitalParser hp = new HospitalParser();
Hospital hospital = hp.parse(line1);
assertEquals(1, hospital.getId()); // col:0
assertEquals("의원", hospital.getOpenServiceName());//col:1
assertEquals(3620000,hospital.getOpenLocalGovernmentCode()); // col: 3
assertEquals("PHMA119993620020041100004",hospital.getManagementNumber()); // col:4
assertEquals(LocalDateTime.of(1999, 6, 12, 0, 0, 0), hospital.getLicenseDate()); //19990612 //col:5
assertEquals(1, hospital.getBusinessStatus()); //col:7
assertEquals(13, hospital.getBusinessStatusCode());//col:9
assertEquals("062-515-2875", hospital.getPhone());//col:15
assertEquals("광주광역시 북구 풍향동 565번지 4호 3층", hospital.getFullAddress()); //col:18
assertEquals("광주광역시 북구 동문대로 24, 3층 (풍향동)", hospital.getRoadNameAddress());//col:19
assertEquals("효치과의원", hospital.getHospitalName());//col:21
assertEquals("치과의원", hospital.getBusinessTypeName());//col:25
assertEquals(1, hospital.getHealthcareProviderCount()); //col:29
assertEquals(0, hospital.getPatientRoomCount()); //col:30
assertEquals(0, hospital.getTotalNumberOfBeds()); //col:31
assertEquals(52.29f, hospital.getTotalAreaSize()); //col:32
}
assertEquals
를 통해 각 분야별로 비교해본다.String line1 = "";
틀을 만들고 난 후 String line1 = "Data";
데이터를 넣게되면 intellij에서 자동으로 "\"
백슬러쉬를 넣어 구분해준다.(1) ReadLineContext
public class ReadLineContext<T>{
private Parser<T> parser;
public ReadLineContext(Parser<T> parser){
this.parser = parser;
}
public List<T> readByLine(String filename) throws IOException {
List<T> result = new ArrayList<>();
BufferedReader reader = new BufferedReader(
new FileReader(filename)
);
String str;
while ((str = reader.readLine()) != null) {
try {
result.add(parser.parse(str));
}catch(Exception e){
System.out.printf("파싱중 문제가 생겨 이 라인은 넘어갑니다. 파일내용:%s",str.substring(0,20));
}
}
reader.close();
return result;
}
}
(2) ParserFactory(DI/IOC)
@Configuration
public class ParserFactory {
@Bean
public ReadLineContext<Hospital> hospitalReadLineContext(){
return new ReadLineContext<Hospital>(new HospitalParser());
}
}
@Bean
을 작성하여 Bean으로 설정할 수 있다.@Bean
을 사용하기 위해서는 클래스명 위에 @Configuration
을 사용해야 @Bean
을 사용할 수 있으며 Spring의 설정 파일이라는 것을 의미한다.💡 Bean ?
- Spring Ioc 컨테이너가 관리하는 자바 객체를 빈이라고 부른다.
- Spring은 싱글톤 객체만 사용하기 때문에 Bean을 통해 사용
- 의존성 주입
(3) ParserFactoryTest
@SpringBootTest(classes = ParserFactory.class)
class HospitalParserTest {
@Autowired // new를 통한 객체 생성 대신 spring에서 자동으로 autowired를 통해 DI를 해줌
ReadLineContext<Hospital> hospitalReadLineContext;
@Test
void name() throws IOException {
// 서버환경에서 build할 때 문제가 생길 수 있다.
// 어디에서든 실행할 수 있게 자는 것이 목표
String filename = "C:\\SpringBoot_DBFile\\fulldata_01_01_02_P_의원2.csv";
List<Hospital> hospitalList = hospitalReadLineContext.readByLine(filename);
assertTrue(hospitalList.size() > 1000);
assertTrue(hospitalList.size() > 10000);
}
@SpringBootTest(classes = ParserFactory.class)
를 통해 Test클래스에서 Bean을 사용할 수 있도록 설정해주며 어떤 Bean 파일을 사용할 것인지도 선언해 준다.@Autowired
를 통해 해당 변수 및 메서드에 스프링이 관리하는 Bean을 자동으로 매핑해주는 개념이다. 즉, Bean에 기능을 부여한다고 봐도 될것 같다