[개발일지10] SpringBatch insert하기

김희주·2023년 2월 8일
0

개발일지

목록 보기
9/10
post-thumbnail

1. 스프링 배치

SpringBatchScheduler

@Configuration
@EnableBatchProcessing
@EnableScheduling
public class SpringBatchScheduler {
	@Scheduled(cron = "0 0 0 1 1 ?")	
	public void updateIpFile() {
		ipTrackingService.updateIpFile();
	}
}

IpTrackingService

@Service
@Transactional
public class IpTrackingService {
	
	@Autowired
	private Ipv4Repository ipv4Repository;
	
	@Autowired
	private UtmRepository utmRepository;
	
	@Autowired
	private Ipv4BatchRepository ipv4BatchRepository;
	
	//서블릿 컨텍스트 객체
	@Autowired ServletContext context;
	
	SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");
	
	public String trackingCountryByIp(String stringIp) {
		
		//IP문자를 . 기준으로 문자배열로 만듬
		String[] arr = stringIp.split("\\.");    //.단위로 나눈다는 뜻. '.'는 정규식 예약어라서 \\.로 표현해야함
		//String 배열을 Int 배열로 변환
		int [] ipArr = new int[4];
			for(int i = 0; i<4; i++) {
				ipArr[i] = Integer.parseInt(arr[i]);
			}
		//int배열을 10진수로 변환   2의 0승, 2의 8승, 2의 16승, 2의 24승
		long ip10 = 0;
			for(int i=0; i<4; i++) {
				if( i == 3) {
					ip10 += ipArr[i];
				} else {
					ip10 += ipArr[i] * Math.pow(2, 8*(3-i));
				}
			}		
		System.out.println("10진수로 변환한 ip : "+ ip10);
		String countryCode = utmRepository.trackingCountryByIp(ip10);
		
		return countryCode;
		
		
	}
	
	/*
	 * KISA에서 IP 기준 정리한 csv파일 다운받아오기 
	 */
	public void downloadIpFile() {
		
		String address = "https://xn--3e0bx5euxnjje69i70af08bea817g.xn--3e0b707e/jsp/statboard/IPAS/ovrse/natal/IPaddrBandCurrentDownload.jsp\r\n"
				+ "";  // 다운 받을 파일 주소
        
        try {

          URL url = new URL(address);
          ReadableByteChannel rbc = Channels.newChannel(url.openStream());
          String storedPath = context.getRealPath("ipFile.csv");
          System.out.println(storedPath);
          FileOutputStream fos = new FileOutputStream(storedPath); //다운받을 경로 설정
          fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);   // 처음부터 끝까지 다운로드
          fos.close();
          
          System.out.println("파일 다운완료");
          
        } catch (Exception e) {
          e.printStackTrace();
        }	
	}

	/*
	 * 새 csv파일 다운받고, 이전 데이터 삭제하고, 새 데이터 가공한 후, DB에 저장하기
	 */
	@Async
	public void updateIpFile() {
		
		// 1. 새 csv파일 다운받아오기
		downloadIpFile();
		// 2. 이전 데이터 삭제하기                 truncate와 차이점 알고 이걸로 바꾸기
		ipv4Repository.delete();
		
	    //csv파일의 절대경로 구하기
	    String storedPath = context.getRealPath("ipFile.csv");
	    System.out.println("storedPath = " + storedPath);

	    List<Ipv4> ipv4List = new ArrayList<>();
	    String[] ipv4Info;
	
	    try {
	        //utf-8 형태로 csv 파일 파싱
	        CSVReader csvReader = new CSVReader(new InputStreamReader(new FileInputStream(storedPath), "EUC-KR"));	
	        csvReader.readNext(); // 컬럼명은 저장되지 않도록 한 줄 읽기	
	        do {   
	        	ipv4Info = csvReader.readNext();    //한 라인 읽기 (자동으로 콤마 분리해서 배열에 저장 됌)
	
	            if (ipv4Info != null) {
	                if (ipv4Info[0] == null || ipv4Info[1] == null || ipv4Info[2].isEmpty() || ipv4Info[3].isEmpty()  || ipv4Info[4].isEmpty() || ipv4Info[5].isEmpty())//읽어온 데이터의 위도, 경도 값이 없거나 null 이면 저장하지 않고 넘김
	                    continue;
	                else {  //위의 두 조건에 해당사항이 없으면 데이터를 객체에 저장 후 임시 저장 ArrayList에 삽입	                	
	                	  Ipv4 ipv4 = new Ipv4();   //객체 생성하기	
							try {
								ipv4.setRecordDate(formatter.parse(ipv4Info[0]));
							} catch (ParseException e) {
								e.printStackTrace();
							}	                	
	                	ipv4.setCountry(ipv4Info[1]);
	                	ipv4.setStartIp(ipv4Info[2]);
	                	ipv4.setEndIp(ipv4Info[3]);
	                	ipv4.setPrefix(ipv4Info[4]);	                	
	                	//----------------------------IP 10진수로 변환하기----------------------------
	                	String stringIp = ipv4Info[2];
	                	String[] arr = stringIp.split("\\.");
	                	for(int i=0; i<arr.length; i++) {
	            			System.out.print(arr[i] + ",");
	            		}
	                	int [] ipArr = new int[4];
	            		for(int i = 0; i<4; i++) {
	            			ipArr[i] = Integer.parseInt(arr[i]);
	            		}
	            		long ip10 = 0;
	            		for(int i=0; i<4; i++) {
	            			if( i == 3) {
	            				ip10 += ipArr[i];
	            			} else {
	            				ip10 += ipArr[i] * Math.pow(2, 8*(3-i));

	            			}
	            		}
	            		ipv4.setStartToTen(ip10);	            		
	                	//----------------------------IP 10진수로 변환하기 끝----------------------------
							try {
								ipv4.setAssignDate(formatter.parse(ipv4Info[5]));
							} catch (ParseException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
							ipv4List.add(ipv4);
	                }
	            }
	        } while (ipv4Info != null);
	    } catch (IOException e) {
	        System.out.println(e.getMessage());
	    } catch (EntityExistsException | CsvValidationException e) {
	        e.printStackTrace();
	    }
	    //ipv4Repository.saveAll(ipv4List);   saveAll 쓰지 말기. 속도가 너무 느림(약 2분 걸림)
	    ipv4BatchRepository.insert(ipv4List);   //saveAll 대신 batch insert 쓰기(약 10초 걸림)
	}
	
	
	@Async
	public void test(int i) {
		System.out.println("비동기 동작 중 : " + i);
	}
}

IpTrackingService > trackingCountryByIp

  • IP문자를 . 기준으로 문자배열로 만드는 코드는
		String[] arr = stringIp.split("\\.");

split(".") 으로 했다가 계속 오류가 나서 찍어보니 .단위로 slipt 되지 않아 찾아본 결과 .는 예약어라서 그대로 작성하면 안되고 \\.라고 작성해야 의도대로 먹힌다.

  • Math.pow로 2의 0승, 2의 8승, 2의 16승, 2의 24승을 계산하는 반복문은
			for(int i=0; i<4; i++) {
				if( i == 3) {
					ip10 += ipArr[i];
				} else {
					ip10 += ipArr[i] * Math.pow(2, 8*(3-i));
				}
			}

Math.pow가 (n,0)이라고 작성 시 n의 0승이 아닌, n의 1승을 뱉어내서 결과적으로 엄청난 오차가 생겨 저렇게 수동으로 바꿨다. 수동을 바꾸지 않는 방법도 있을까? 일단 찾아봤는데 내가 잘 못 검색했는지 아직 못 찾았다. 찾아볼 것🙌

IpTrackingService > downloadIpFile

url로 접속해 원하는 파일을 다운받아오는 메서드.
url을 받아오는 방법은, 해당 인터넷 페이지에서 '다운받기' 버튼을 클릭한 후 ctrl + shift + i를 누른 후 network 창을 열어 get이나 port 매핑 url을 추출하면 된다

IpTrackingService > updateIpFile

saveAll 보다 batchUpdate가 더 빠르다!

Ipv4BatchRepository

1. repository지만, class로 작성한다.

이걸 못 깨달아서 한 시간 날렸다.
batch를 사용하려면 private final JdbcTemplate jdbcTemplate;을 선언해야하는데, 인터페이스에서는 지역변수를 선언할 수 없다😟
그래서 class로 정의한 다음

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

생성자 선언에 필드로 넣어주는 선언까지 하면 빨간 줄 에러가 발생하지 않는다.

2. batchUpdate를 사용해 batch insert하기

	 public void insert(List<Ipv4> list) {
		 jdbcTemplate.batchUpdate(
				"INSERT INTO IPV4 (record_date, country, start_ip, end_ip, prefix, start_to_ten, assign_date ) VALUES ("
				    		+ "?, ?, ?, ?, ?, ?, ?)",
			                new BatchPreparedStatementSetter() {
			                    @Override
			                    public void setValues(PreparedStatement ps, int i) throws SQLException {
			                    	String recordDate = formatter.format(list.get(i).getRecordDate());
			                        ps.setString(2, list.get(i).getCountry());
			                        ps.setString(1, recordDate);
			                        ps.setString(3, list.get(i).getStartIp());
			                        ps.setString(4, list.get(i).getEndIp());
			                        ps.setString(5, list.get(i).getPrefix());
			                        ps.setLong(6, list.get(i).getStartToTen());
			                        String assignDate = formatter.format(list.get(i).getAssignDate());
			                        ps.setString(7, assignDate);
			                    }

			                    @Override
			                    public int getBatchSize() {
			                    	System.out.println("list.size : " + list.size());
			                        return list.size();
			                    }
			                }); 
	 }
  • Date 지역변수여도 setString으로 넣으면 된다!
ps.setDate(2, list.get(i).getCountry())

로 계속 시도하니 java.util.Date cannot be cast to java.sql.Date 에러가 계속 났다. 한 시간 붙잡은 끝에 setString으로 해보니 되었다. 애초에 지역변수를 Date로 선언했지만 String으로도 넣어진다!
그래서

String recordDate = formatter.format(list.get(i).getRecordDate());
ps.setString(2, list.get(i).getCountry());

로 코드를 변경하니 통과! 기나긴 고단한 하루였다...

profile
백엔드 개발자입니다 ☘

0개의 댓글