728x90
반응형
728x90
반응형
728x90
반응형
SpringBoot 3.3.4
JDK 17
React 18.3.1 

 

스프링부트 RESTful 서비스를 이용해 리액트에서 데이터를 조회하려는데 개발자모드 Console창을 보니 다음과 같이 에러가 발생하였다.

Access to fetch at 'http://localhost:8080/api/items' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

 

번역하면

원본 'http://localhost:3000'에서 'http://localhost:8080/api/items'로의 가져오기 액세스가 CORS 정책에 의해 차단되었습니다: 요청된 리소스에 'Access-Control-Allow-Origin' 헤더가 없습니다. 불투명한 응답이 필요한 경우 요청의 모드를 'no-cors'로 설정하여 CORS를 비활성화한 상태에서 리소스를 가져오세요.

 

이와 같은 해석대로 프런트엔드에서 API를 호출할 때 mode: 'no-cors'로 설정 후 호출하면 요청이 성공하더라도 데이터를 정상적으로 가져오지 못하는 오류가 발생한다. 

 

 

 

그래서 CORS 정책을 풀려면 서버 측에서 처리해야 한다. Spring Boot 백엔드에서 관련 컨트롤러에 @CrossOrigin 어노테이션을 추가하여 CORS를 활성화하거나 Spring Boot 구성에서 전역적으로 구성하면 된다. 이렇게 하면 프런트엔드에서 응답에 올바르게 액세스 할 수 있게 된다.

 

React의 서버 주소가 http://localhost:3000 이므로 다음과 같이 @CrossOrigin(origins = "http://localhost:3000")를 추가하였다.

@CrossOrigin(origins = "http://localhost:3000")
@RestController
public class ItemController {
	
	@Autowired
	private ItemRepository itemRepository;
	
	@GetMapping("/items")
	public Iterable<Item> getItems(){
		return itemRepository.findAll();
	}
}

 

그런데 위와 같이 처리했음에도 동일하게 에러가 발생하였다. 

 

반응형
728x90

 

@CrossOrigin 어노테이션을 추가했음에도 에러가 발생했던 건 application.properties 파일에 base-path 설정한 것 때문이었다.

RESTful 서비스의 base path로 /api를 사용하기 위해 spring.data.rest.base-path=/api를 설정했는데 @CrossOrigin 어노테이션은 이를 해결해주지 못하는 것으로 보인다.

 

그래서 해당 속성값을 삭제하고 다음과 같이 Controller 파일에 @RequestMapping("/api")를 추가하는 방식으로 변경하였다.

 

@CrossOrigin(origins = "http://localhost:3000")
@RequestMapping("/api")
@RestController
public class ItemController {
	
	@Autowired
	private ItemRepository itemRepository;
	
	@GetMapping("/items")
	public Iterable<Item> getItems(){
		return itemRepository.findAll();
	}
}

 

더 이상 에러가 발생하지 않고 데이터가 정상적으로 화면에 표시된다.

 

그런데 spring.data.rest.base-path 설정한 값 그대로 사용하고 싶을 때는 어떻게 해야 할까? 이 때 Spring Boot 구성에서 전역적으로 구성하는 방식을 사용해야 한다.

 

import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;

@Configuration
public class RestConfig implements RepositoryRestConfigurer {
    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
        cors.addMapping("/**")
            .allowedOrigins("http://localhost:3000")
            .allowedMethods("GET", "POST", "PUT", "DELETE")
            .allowCredentials(true);
    }
}

 

참고로 application.properties 파일에 spring.data.rest.base-path를 설정했을 때와 @RequestMapping 어노테이션을 사용했을 경우 Response의 JSON 형태가 다르다.

 

spring.data.rest.base-path를 설정했을 때는 객체._ embedded.items 키에서 데이터 접근이 가능하다. React에서 data => setItems(data._embedded.items)

 

@RequestMapping 어노테이션을 사용했을 경우는 키 없이 리턴된 배열객체에서 데이터를 가져올 수 있다.

React에서 data => setItems(data)

 

결론

스프링부트의 RESTful 서비스 이용한다면 application.properties 파일에 spring.data.rest.base-path를 사용하고 RepositoryRestConfigurer를 이용한 두 번째 방법으로 처리하는 것이 맞다고 생각된다.

 

 

 

 

728x90
반응형
728x90
반응형

기본 개념을 이해하는 것이 중요하므로 간단하게 다음과 같이 1부터 10까지 리스트에 추가합니다.

    List<Integer> list = new ArrayList<>();
    
    for(int i=1;i<=10;i++) {
        list.add(i);
    }
    System.out.println(list);

print하면 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]가 출력됩니다.

 

여기서 삭제하고 싶은 값이 10이라고 해보고 다음과 같이 입력해본다고 합시다.

    list.remove(10);

remove메소드의 파라미터는 index값을 받게 되어 있습니다.

index는 0부터 시작하므로 위에 있는 list객체에는 10번의 index가 존재하지 않아 IndexOutOfBoundsException가 발생하게 됩니다.

 

여기서 숫자 10을 삭제하고 싶을 때 어떻게 해야 할까요?

    list.remove(new Integer(10));
    
    System.out.println(list);

위와 같이 래퍼 클래스(Wrapper Class)로 감싸서 파라미터 값을 넘겨주면 됩니다.

 

이제 출력값은 다음과 같습니다.

[1, 2, 3, 4, 5, 6, 7, 8, 9]

 

우리가 원했던대로 10이 삭제되었습니다.

 

Java에서 수치나 논리값은 객체가 아니라 기본형으로 관리됩니다. 그런데 List나 Map과 같은 컬렉션 클래스 안에서는 객체만 저장할 수 있습니다. 그렇기 때문에 기본형을 객체로 감싸줘야 하는데 이러한 역할을 하는 것이 래퍼 클래스입니다. 

 

이러한 개념을 바탕으로 List에 숫자 10을 담기 위해서는 다음과 같이 해야 합니다.

    int ten = 10;
    
    Integer wrap_ten = new Integer(ten);
    
    List<Integer> list = new ArrayList<>();
    list.add(wrap_ten);
    
    System.out.println(list);

그런데 굳이 위처럼 하지 않고 다음과 같이 int값을 바로 넣어도 List에 추가가 되는 것을 알 수 있습니다.

    int ten = 10;

    List<Integer> list = new ArrayList<>();
    list.add(ten);
    
    System.out.println(list);

자바가 자동으로 int값을 래퍼 클래스로 변환해주기 때문입니다. 참고로 기본형에서 래퍼 클래스로 변환해주는 것을 '오토박싱'이라고 합니다. 

 

다시 이 글의 주제인 'List에서 특정 index가 아닌 원하는 값 삭제'로 돌아가 보겠습니다.

반응형
728x90

 

그러면 왜 remove 메소드에 10을 넘겼을 때 왜 래퍼 클래스로 변환해주지 않는 것이냐 라는 의문이 들 수 있습니다. 

 

List 객체의 특성을 다시 생각해보면 됩니다.

List에서 어떠한 작업을 하기 위한 키는 기본적으로 index입니다. 그러므로 remove 메소드 파라미터의 기본은 index인 것입니다. 몇 번째 index에 위치한 것을 우리가 호출한 메소드에 맞게 처리해라가 기본인 것입니다. 

 

그러므로 List에서 index가 아니라 우리가 생각하는 그 숫자를 삭제하려면 래퍼 클래스로 감싸서 파라미터로 넘겨야 합니다. 

 

그렇다면 Java가 List 안에서 우리가 생각하는 것처럼 숫자 10을 인식해서 삭제하는 것일까요?

 

객체의 기본 개념을 생각해봅시다. 이해하기 쉽게 객체는 주소와 값으로 구성된 map과 같은 것입니다. 파이썬에서는 Dictionary 같은 것입니다. 

 

이 글 처음에 for문을 이용해서 1부터 10까지 숫자를 List에 추가하였습니다. 이 때 내부적으로 1부터 10까지 각각 래퍼 클래스로 전부 감싼 후 List에 추가한 것입니다. 그러므로 1부터 10까지 숫자가 있는 것이 아니라 1부터 10까지 주소와 값으로 구성된 객체 10개가 만들어진 것입니다.

 

그러므로 list.remove(new Integer(10))을 실행했을 때 자바가 List 안에서 숫자 10을 인식한 게 아니라 10이 위치한 주소를 찾아 그 객체를 삭제한 게 되는 것입니다.

 

 

 

 

728x90
반응형

'Programming > Java' 카테고리의 다른 글

[자바] Open API에서 xml 파싱(parsing)하기  (0) 2020.12.17
728x90
반응형

이번 포스팅에서는 controller 파일을 생성 후 was를 실행하지 않고 controller 테스트를 진행해보도록 하겠습니다.

 

먼저 스프링 프레임워크에서 어떤 설정이 필요한지 확인해보겠습니다.

 

스프링 프레임워크에서 controller 파일을 인식시키려면 controller class 위에 @Controller 어노테이션을 작성하면 됩니다.

 

다음으로 이 @Controller 어노테이션을 인식하게 하려면 src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml 파일을 살펴봐야 합니다.

 

아래 그림에 보면 component-scan context를 설정한 부분이 있는데 base-package 속성값에 @Controller 어노테이션을 자동으로 인식시키고자 하는 폴더명을 지정해야 합니다.

 


이제 controller 파일을 생성하고 JSON데이터를 리턴하는 코드를 작성해보도록 하겠습니다.

 

스프링에서 JSON 처리를 하기 위해서는 jackson-databind 라이브러리를 추가해야 합니다.

 

다음과 같이 pom.xml에 추가합니다.

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.12.0</version>
</dependency>

 

그 다음 VO 객체를 만듭니다.

 

다음과 같이 Stock 클래스 파일을 만들겠습니다.

import lombok.Data;

@Data
public class Stock {
	private String code;
	private String name;
}

 

Stock 객체에 종목코드와 종목명을 넣고 JSON으로 리턴하는 클래스 파일을 만들겠습니다.

 

StockController 클래스를 다음과 같이 작성합니다.

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.kenmiles.hellojarvis.web.entity.Stock;

@Controller
public class StockController {
	
	@RequestMapping("/setVO")
	public @ResponseBody Stock setVO(){
		Stock vo = new Stock();
		vo.setCode("005930");
		vo.setName("삼성전자");
		
		return vo;
	}
	  
}

 

여기서 주의할 점은 리턴타입에 @ResponseBody 어노테이션을 추가해야 한다는 점입니다.

 

위 controller가 정상적으로 실행되는지 확인하기 위해서는 보통 톰캣을 실행해 브라우저 창에 주소를 입력해서 확인합니다.

 

하지만 이번 포스팅에서는 톰캣 실행 없이 controller 파일이 정상 실행되는지 확인해보도록 하겠습니다.

 

톰캣 실행 없이 controller를 테스트하기 위해서 브라우저에서 요청과 응답하는 과정을 대신처리할 수 있는 MockMvc를 이용하겠습니다.

 

다음과 같이 JUnit Test 파일을 생성합니다.

import java.nio.charset.Charset;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import com.kenmiles.hellojarvis.web.controller.StockController;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations= {"file:src/main/webapp/WEB-INF/spring/**/*.xml"})
public class sampleControllerTest {

	private static final Logger logger = LoggerFactory.getLogger(sampleControllerTest.class);

	@Autowired
	StockController stockController;
	
	private MockMvc mockMvc;
	
    private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
            MediaType.APPLICATION_JSON.getSubtype(),
            Charset.forName("utf8"));

	@Before
	public void setup(){
		mockMvc = MockMvcBuilders.standaloneSetup(stockController).build();
		logger.info("setup......");
	}
	
	@Test
	public void testGetData()throws Exception{
		mockMvc.perform(MockMvcRequestBuilders.get("/setVO")
				.contentType(contentType))
		.andExpect(MockMvcResultMatchers.status().isOk())
		.andDo(MockMvcResultHandlers.print()); 

	}
}

이 코드에서 일반 자바를 테스트할 때와 다른 점은 @WebAppConfiguration 어노테이션을 추가한 것과 servlet-context.xml을 읽도록 하기 위해 WEB-INF/spring 폴더 아래 xml파일을 전부 인식하도록 했다는 점입니다.

 

테스트를 하고자 하는 controller 파일을 @Autowired로 주입하고 해당 객체를 setup메소드에서 mockMvc 객체로 초기화합니다.

 

<Ctrl> + <F11>로 실행하면 다음과 같이 status가 200이 나타나고 Body에 controller 파일에서 리턴한 JSON 값이 표시되어 있는 것을 확인할 수 있습니다.

 

 

728x90
반응형
728x90
반응형

이번 포스팅에서는 API를 호출했을 때 xml로 출력되는 자료를 파싱(parsing)하는 방법에 대해 알아보도록 하겠습니다.

 

예제를 위해 opendart 사이트에 접속해 회원가입하고 API 이용하기 위한 인증키를 신청합니다.

 

opendart.fss.or.kr/

 

전자공시 OPENDART 시스템

--> 시스템 점검으로 모든 서비스 이용이 일시적으로 중단되어니 양해 부탁드립니다. 시스템 점검 일정 2019년12월30일 23:00 ~ 12월31일 24:00 (1시간) *상기 작업 시간은 사정에 의해 변경 될 수 있습니

opendart.fss.or.kr

개발가이드 -> 사업보고서 주요정보에서 증자(감자) 현황 정보를 가져와 보도록 하겠습니다.

 

맨 오른쪽에 바로가기를 클릭합니다.

개발가이드에 보면 기본 정보, 요청 인자, 응답 결과 정보를 확인할 수 있고 화면 아래로 더 내려보면 다음과 같이 OpenAPI 테스트도 해볼 수 있습니다.

 

XML로 파싱할 것이기 때문에 XML을 선택하고 검색을 누릅니다.

API 결과에 요청인자에 따른 url 주소와 요청한 결과값이 XML값으로 표시되는 것을 볼 수 있습니다.

 

이제 url을 호출해서 XML값을 파싱하는 작업을 자바로 구현해보겠습니다.

 

자바파일로 만들어도 되고 JUnit 테스트 코드로 작성해도 됩니다.

 

다음과 같이 자바 파일을 작성합니다.

import java.net.URLDecoder;

import javax.xml.parsers.DocumentBuilderFactory;

import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class xmlParsingTest {

	// tag값의 정보를 가져오는 메소드
	private static String getTagValue(String tag, Element eElement) {
	    NodeList nlList = eElement.getElementsByTagName(tag).item(0).getChildNodes();
	    Node nValue = (Node) nlList.item(0);
	    if(nValue == null) 
	        return null;
	    return nValue.getNodeValue();
	}

	@Test
	public void test() {
		try{
			// parsing할 url 지정(API 키 포함해서)
			String url = "https://opendart.fss.or.kr/api/irdsSttus.xml";
			String serviceKey = "인증키";
			String decodeServiceKey = URLDecoder.decode(serviceKey, "UTF-8");
			String corpCode = "00293886"; //공시대상회사의 고유번호(8자리)
			String bsnsYear = "2018"; //사업연도(4자리)
			String reprtCode = "11011"; //보고서 코드
					
			Document documentInfo = DocumentBuilderFactory
					.newInstance()
					.newDocumentBuilder()
					.parse(url+"?crtfc_key="+decodeServiceKey+"&corp_code="+corpCode+"&bsns_year="+bsnsYear+"&reprt_code="+reprtCode);
			
			// root tag 
			documentInfo.getDocumentElement().normalize();
			//				System.out.println("Root element :" + documentInfo.getDocumentElement().getNodeName());
			
			// 파싱할 tag
			NodeList nList = documentInfo.getElementsByTagName("list");
			//				System.out.println("파싱할 리스트 수 : "+ nList.getLength());
			
			for(int temp = 0; temp < nList.getLength(); temp++){
				Node nNode = nList.item(temp);
				if(nNode.getNodeType() == Node.ELEMENT_NODE){
					
					Element eElement = (Element) nNode;
					System.out.println("######################");
					System.out.println("고유번호  : " + getTagValue("corp_code", eElement));
					System.out.println("법인명  : " + getTagValue("corp_name", eElement));
					System.out.println("주식발행 감소일자 : " + getTagValue("isu_dcrs_de", eElement));
					System.out.println("발행 감소 형태  : " + getTagValue("isu_dcrs_stle", eElement));
					System.out.println("발행 감소 주식 종류  : " + getTagValue("isu_dcrs_stock_knd", eElement));
					System.out.println("발행 감소 수량  : " + getTagValue("isu_dcrs_qy", eElement));
					System.out.println("발행 감소 주당 액면 가액 : " + getTagValue("isu_dcrs_mstvdv_fval_amount", eElement));
					System.out.println("발행 감소 주당 가액  : " + getTagValue("isu_dcrs_mstvdv_amount", eElement));
				}	
			}	
		} catch (Exception e){	
			e.printStackTrace();
		}
	}

}

<Ctrl> + <F11>을 눌러서 실행해보면 다음과 같이 콘솔창에 XML데이터가 파싱되어 표시되는 것을 볼 수 있습니다.

다시 Opendart 사이트로 가서 인증키 신청/관리 -> 오픈API 이용현황에 보면 다음과 같이 일일허용건수와 호출건수를 확인할 수 있습니다.

 

728x90
반응형
728x90
반응형

이번 포스팅에서는 MyBatis를 설정하고 Spring과 MyBatis, DB 서버가 제대로 연결이 되었는지까지 확인해보도록 하겠습니다.

 

MyBatis를 사용하기 위해서는 mybatis-spring과 mybatis 라이브러리가 필요합니다.

 

Maven Repository 사이트에 접속해서 mybatis-spring과 mybatis를 검색합니다.

 

Maven Dependency 코드를 복사해서 pom.xml에 붙여넣기 합니다.

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>

 

Mybatis와 스프링 연동 작업에서 가장 중요한 객체인 sqlSessionFactory를 사용할 수 있게 설정하도록 하겠습니다.

 

src/main/webapp/WEB-INF/spring/root-context.xml에 들어가 다음과 같이 설정합니다.

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
</bean>

 

dataSource 설정은 다음을 참조하세요.

2020/12/11 - [프로그래밍/Spring Framework] - [JDBC] Spring Maven 프로젝트에 MS-SQL Server 2019 JDBC Driver 설정 방법

 

[JDBC] Spring Maven 프로젝트에 MS-SQL Server 2019 JDBC Driver 설정 방법

이번 포스팅에서는 Spring Maven 프로젝트를 생성 후 MS-SQL과 연결하는 과정을 설명드리겠습니다. MS SQL Server 2019 JDBC Driver 정보가 있는 다음 사이트에 접속합니다. docs.microsoft.com/en-us/sql/connec..

grand-unified-engine.tistory.com

 

MyBatis는 SQL Mapping Framework로 Spring과 별도의 설정 파일을 가질 수 있습니다.

 

src/main/resources 아래 config 폴더를 만들고 mybatis-config.xml 파일을 생성합니다.

 

mybatis-config.xml파일에 다음을 입력합니다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration></configuration>

 

mybatis-config.xml에 추가로 설정할 것들이 있는데 다음 포스팅 때 하기로 하고 이번 포스팅에서는 기본설정만 하도록 하겠습니다.

 

이렇게 만든 mybatis-config.xml파일을 Spring Framework에서 사용할 수 있게 root-context.xml 파일을 다음과 같이 수정합니다.

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
    <property name="configLocation" value="classpath:/config/mybatis-config.xml"></property>
</bean>

 

지금까지 Mybatis 기본 설정을 마쳤습니다.

 

마지막으로 정상적으로 스프링에서 Mybatis와 DB 서버가 잘 연결되는지 확인해보도록 하겠습니다.

 

JUnit Test Case로 테스트 자바파일을 생성합니다.

 

테스트 자바 파일에 다음과 같이 입력합니다.

 

import javax.inject.Inject;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/spring/root-context.xml"})
public class MyBatisTest {

	//SqlSessionFactory 객체를 자동으로 생성
	@Inject
	private SqlSessionFactory sqlFactory;
		
	//SqlSessionFactory 객체가 제대로 만들어졌는지 Test
	@Test
	public void test() {
		System.out.println(sqlFactory);
	}
	
	//MyBatis와 DB 서버가 제대로 연결되었는지 Test
	@Test
	public void testSession() throws Exception{
		try(SqlSession session = sqlFactory.openSession()){
			System.out.println(session);
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

 

<Ctrl> + <F11>을 눌러서 결과를 확인하도록 하겠습니다.

 

콘솔창에 다음과 같이 출력되는 것을 볼 수 있습니다.

 

잘 연결된 것을 확인할 수 있습니다.

 

728x90
반응형

+ Recent posts