[SpringBoot] 전국 병원 정보 API 만들기 2~3일차

배지원·2022년 11월 7일
0

실습

목록 보기
10/24

📄 대용량 파일 읽고 파싱해서 저장

💡 Parser, Factory, DAO, DTO 클래스를 통해 파싱한 데이터 DB에 저장

1. 진행 순서

  1. DAO를 통해 CRUD문 작성
  2. DB에 파싱한 데이터 연동(삽입,삭제,수정,찾기)
  3. Controller를 통해 API 통신

2. DAO

HospitalDao

// 쿼리문 저장하는 DAO
@Component
public class HospitalDao {
    private final JdbcTemplate jdbcTemplate;

    public HospitalDao(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    // select 구문으로 받은 객체를 자신이 원하는 형태로 받을수 있도록 rowMapper함
    RowMapper<Hospital> rowMapper = (rs, rowNum) -> {
        Hospital hospital = new Hospital();
        hospital.setId(rs.getInt("id"));
        hospital.setOpenServiceName(rs.getString("open_service_name"));
        hospital.setOpenLocalGovernmentCode(rs.getInt("open_local_government_code"));
        hospital.setManagementNumber(rs.getString("management_number"));
        hospital.setLicenseDate(rs.getTimestamp("license_date").toLocalDateTime());
        hospital.setBusinessStatus(rs.getInt("business_status"));
        hospital.setBusinessStatusCode(rs.getInt("business_status_code"));
        hospital.setPhone(rs.getString("phone"));
        hospital.setFullAddress(rs.getString("full_address"));
        hospital.setRoadNameAddress(rs.getString("road_name_address"));
        hospital.setHospitalName(rs.getString("hospital_name"));
        hospital.setHealthcareProviderCount(rs.getInt("healthcare_provider_count"));
        hospital.setPatientRoomCount(rs.getInt("patient_room_count"));
        hospital.setTotalNumberOfBeds(rs.getInt("total_number_of_beds"));
        hospital.setTotalAreaSize(rs.getFloat("total_area_size"));
        return hospital;
    };

    public void add(Hospital hospital){
        this.jdbcTemplate.update("insert into nation_wide_hospitals(id, open_service_name, open_local_government_code, management_number, license_date, business_status, business_status_code, phone, full_address, road_name_address, hospital_name, business_type_name, healthcare_provider_count, patient_room_count, total_number_of_beds, total_area_size) " +
                        "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);",
                hospital.getId(),
                hospital.getOpenServiceName(),
                hospital.getOpenLocalGovernmentCode(),
                hospital.getManagementNumber(),
                hospital.getLicenseDate(),
                hospital.getBusinessStatus(),
                hospital.getBusinessStatusCode(),
                hospital.getPhone(),
                hospital.getFullAddress(),
                hospital.getRoadNameAddress(),
                hospital.getHospitalName(),
                hospital.getBusinessTypeName(),
                hospital.getHealthcareProviderCount(),
                hospital.getPatientRoomCount(),
                hospital.getTotalNumberOfBeds(),
                hospital.getTotalAreaSize());
    }

    public Hospital findById(int id){
        String sql = "SELECT * FROM nation_wide_hospitals WHERE id = ?";
        return this.jdbcTemplate.queryForObject(sql,rowMapper,id);
    }

    public int getcount(){
        return this.jdbcTemplate.queryForObject("select count(id) from nation_wide_hospitals", Integer.class); // integer로 반환
    }
    // this.jdbcTemplate.queryForObject(sql,어떤 자료형으로 내보낼지 정함)
    // queryForObject(sql,String.class)를 사용했을 때 String으로 return되는 것을 확인

    public void deleteAll(){
        this.jdbcTemplate.update("delete from nation_wide_hospitals");
    }
}

queryForObject란?

  • 쿼리문 수행결과가 한개이다
  • 객체 그대로 반환해준다.
query( )queryForObject( )
쿼리문 1개 이상 수행쿼리문 1개 수행
리스트형식 반환객체 그대로 반환

형식

Object jdbcTemplate.queryForObject(SQL구문, 반환 타입, 인자);
  1. SQL 구문
    : SQL 구문(insert,delete,select...)을 말한다.
  2. 반환 타입
    : 어떤 타입으로 반환할 건지를 선택. 단, 데이터 형만 가능하다.
    (Long, Integer, String 등)
  3. 인자
    : SQL 구문에 필요한 인자를 넣는다.

    예시
String name = jdbcTemplate.queryForObject(
        "SELECT name FROM USER WHERE id=?",
        String.class,
        1000L);
  • SELECT 쿼리문 입력
  • String.class로 String형으로 반환
  • 1000L을 통해 id가 1000번째인 값을 찾음

RowMapper란?

  • queryForObject는 데이터형만 입력이 가능하다고 했다. 하지만 자신이 필요한 형태로 받고 싶을때는 어떻게 해야할까? 이때 사용하는 것이 RowMapper이다.
  • RowMapper를 사용하면, 원하는 형태로 바꿔서 반환값을 받을 수 있다.
    SELECT로 나온 여러개의 값과 사용자가 원하는 형태로 반환을 받을 수 있다.
    다음과 같이 사용이 가능하다.
    RowMapper<Hospital> rowMapper = new RowMapper<Hospital>() {
        @Override
        public Hospital mapRow(ResultSet rs, int rowNum) throws SQLException {
            return null;
        }
    };

람다 사용

    RowMapper<Hospital> rowMapper = (rs, rowNum) -> {
        Hospital hospital = new Hospital();
        hospital.setId(rs.getInt("id"));
        hospital.setOpenServiceName(rs.getString("open_service_name"));
        hospital.setOpenLocalGovernmentCode(rs.getInt("open_local_government_code"));
        hospital.setManagementNumber(rs.getString("management_number"));
        hospital.setLicenseDate(rs.getTimestamp("license_date").toLocalDateTime());
        hospital.setBusinessStatus(rs.getInt("business_status"));
        hospital.setBusinessStatusCode(rs.getInt("business_status_code"));
        hospital.setPhone(rs.getString("phone"));
        hospital.setFullAddress(rs.getString("full_address"));
        hospital.setRoadNameAddress(rs.getString("road_name_address"));
        hospital.setHospitalName(rs.getString("hospital_name"));
        hospital.setHealthcareProviderCount(rs.getInt("healthcare_provider_count"));
        hospital.setPatientRoomCount(rs.getInt("patient_room_count"));
        hospital.setTotalNumberOfBeds(rs.getInt("total_number_of_beds"));
        hospital.setTotalAreaSize(rs.getFloat("total_area_size"));
        return hospital;
    };

3. Controller

HospitalController

@RestController
@RequestMapping("/hospital")
public class HospitalController {

    private final HospitalDao hospitalDao;


    public HospitalController(HospitalDao hospitalDao) {
        this.hospitalDao = hospitalDao;
    }

// ex) localhost:8080/hospital/1		id=1
    @GetMapping("/{id}")
    public ResponseEntity<Hospital> get(@PathVariable("id") Integer id){
        Hospital hospital = hospitalDao.findById(id);
        Optional<Hospital> opt = Optional.of(hospital);
        // 요즘은 null을 줄이는 추세이다. 따라서 null 대신 Optional을 사용함
        // Optional.of(hospital) => hospital에 대한 Optional(null)검사를 함
        
        if(!opt.isEmpty()){     // 비어있지 않다면 
            return ResponseEntity.ok().body(hospital);      // ok -> 정상 , body에 hospital 출력
        }else{                  // 비어있다면
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new Hospital());  // BAE_REQUEST 출력, body에 새로운 객체 출력
        }
    }
}
  • GetMapping을 통해 값을 입력받는데 PathVariable을 통해 입력한 값을 지정하여 변수에 저장함
  • null 검사를 줄이기 위해 Optional< T >을 사용함

    Optional이란?

    • null이 올 수 있는 값을 감싸는 Wrapper 클래스로, 참조하더라도 NPE가 발생하지 않도록 도와준다. 위와 같이 null이 발생해도 다른 길을 제공해준다.
  • ResponseEntity를 통해 내가 원하는대로 구조(Head,Body)를 설정한다.
    예) 오류가 없다면 ResponEntiy().ok.body(hospital)을 통해 head에는 정상신호를 body에는 객체를 반환함, 만약 오류가 있다면 status의 값을 BAD_REQUEST를 반환함

4. Service

Controller, Service, Repository에 관한 내용은 이전에 정리해 놓은 연결 구조정리 블로그에서 확인이 가능하다.

@Component : Spring에서 관리되는 객체임을 표시하기 위해 사용하는 가장 기본적인 annotation이다. DI를 위한 기본 annotation이다. Spring application context에서 Bean으로서 자동으로 등록되도록 하는 annotation이라 생각하면 될 듯 하다.

@Controller, @Service, @Repository의 기능은 @Component와 동일하지만, 보다 class를 특정화하여 나타내기 위해 @Controller, @Service, @Repository를 구분하여 사용한다. business layer를 구분하기 사용한다.

@Transactional : 데이터 추가, 갱신, 삭제 등 과정 중 오류가 발생했을 때 모든 작업들을 원상태로 되돌릴 수 있다. 모든 작업들이 성공해야만 최종적으로 데이터베이스에 반영하도록 한다.

CODE

@Service
public class HospitalService {

    private final ReadLineContext<Hospital> hospitalReadLineContext;        // ReadLineContext 클래스 사용하기 위해
    
    private final HospitalDao hospitalDao;          // HospitalDao 사용하기 위해

    public HospitalService(ReadLineContext<Hospital> hospitalReadLineContext, HospitalDao hospitalDao) {
        this.hospitalReadLineContext = hospitalReadLineContext;
        this.hospitalDao = hospitalDao;
    }

    /*  위의 코드는 아래와 같은 의미
    @Autowired
    private final ReadLineContext<Hospital> hospitalReadLineContext;

    @Autowired
    private final HospitalDao hospitalDao;   
     */
    

    // 반영된 개수를 반환함
    @Transactional          // 성능을 좀더 높여줌
    public int insertLargeVolumeHospitalData(String filename){
        int cnt = 0;
        try {
            List<Hospital> hospitalList = hospitalReadLineContext.readByLine(filename);
            System.out.println("파싱이 끝났습니다.");
            for(Hospital hospital: hospitalList){   // loop 구간
                try {
                    this.hospitalDao.add(hospital);     // db에 insert하는 구간
                    cnt++;
                } catch (Exception e) {
                    System.out.printf("id:%d 레코드에 문제가 있습니다.",hospital.getId());
                    throw new RuntimeException(e);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return cnt;
    }
}


참고자료 : queryForObject/RowMapper 참고

profile
Web Developer

0개의 댓글