MultipartFile file 의 file.getOriginalFilename() 함수 사용 시 

 

익스플로러로 업로드 하는 경우는 아래와 같이 파일의 전체 경로가 나온다.

C:\Users\kyoborealco\Downloads\no_image.jpg

 

IE에서만 발생하는 문제로

 

org.apache.commons.io.FilenameUtils.getName(file.getOriginalFilename())

를 사용하면 된다.

 

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "필드명"

 

에러 원인은 json 문자열에 있는 키값이 DTO등 객체에는 없어서 생기는 문제

 

해결법은 

DTO등 객체 클래스 위에

@JsonIgnoreProperties(ignoreUnknown = true)

어노테이션 설정

 

또는

ObjectMapper objectMapper = new ObjectMapper();

objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

ObjectMapper 설정에 없는 필드 무시 설정

 

 

스트링 형식을 DTO로 매핑하기

그리고 DTO를 json스트링으로 변환하기

 

UserDTO.class

public class UserDTO {

	String userID;
	String userName;
	public String getUserID() {
		return userID;
	}
	public void setUserID(String userID) {
		this.userID = userID;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}

}

 

변환소스

package test2.mapper;

import java.util.Arrays;
import java.util.List;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import com.fasterxml.jackson.databind.ObjectMapper;

import test2.entity.dto.UserDTO;

@SpringBootTest
public class StringToDto {
	
	@Test
	public void stringToJsonToDto() {
		String str = "{\"userID\":\"user01\", \"userName\":\"홍길동\"}";
		String strList = "[{\"userID\":\"user01\", \"userName\":\"홍길동\"},{\"userID\":\"user02\", \"userName\":\"홍길순\"}]";
		
		ObjectMapper mapper = new ObjectMapper();
		try {
			// 1. 스트링에서 DTO로 매핑하기
			UserDTO dto = mapper.readValue(str, UserDTO.class);
			System.out.println(dto);
			// UserDTO(userID=user01, userName=홍길동)
			
			// 2. 스트링에서 DTO LIST로 매핑하기
			List<UserDTO> dtos = Arrays.asList(mapper.readValue(strList, UserDTO[].class));
			System.out.println(dtos.size() + " : " +dtos);
			// 2 : [UserDTO(userID=user01, userName=홍길동), UserDTO(userID=user02, userName=홍길순)]
			
			// 객체를 JSON 스트링으로 변환하기
			String jsonStr = mapper.writeValueAsString(dtos);
			System.out.println(jsonStr);
			// [{"userID":"user01","userName":"홍길동"},{"userID":"user02","userName":"홍길순"}]
			
		} catch (Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
	}
	
}

 

 

메이븐에 spring-boot-starter-web을 포함하거나

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

또는 jackson을 포함한다.

		<dependency>
		    <groupId>com.fasterxml.jackson.core</groupId>
		    <artifactId>jackson-databind</artifactId>
		    <version>2.9.8</version>
		</dependency>

 

 

select count(*) as cnt 
from (
select REGEXP_REPLACE(REPLACE('.'||'192.168.100.55', '.', '.00'), '([^.]{3}(\.|$))|.', '\1') as ip 
, REGEXP_REPLACE(REPLACE('.'||'192.168.100.51', '.', '.00'), '([^.]{3}(\.|$))|.', '\1') as ip_start
, REGEXP_REPLACE(REPLACE('.'||'192.168.100.56', '.', '.00'), '([^.]{3}(\.|$))|.', '\1') as ip_end
from dual
) where ip between ip_start and ip_end

 

이런 정규식을 ibatis에서 사용시 아래와 같은 에러가 났다.

 

 

org.springframework.jdbc.UncategorizedSQLException: SqlMapClient operation; uncategorized SQLException for SQL []; SQL state [null]; error code [0];

또는

 

The error occurred while preparing the mapped statement for execution.  

 

토드나 쿼리창에서는 잘 돌아가는 쿼리인데..

곰곰히 생각해보니

문장을 맵핑해서 준비하는데 에러가 난다.... prepare statement 로 만드는데 에러가 난다는 말 같다.

 

이유는 간단하다.

ibatis에서는 #변수#, $변수$ 를 이용해서 변수를 맵핑한다.

정규식에 $가 들어가 있으니 에러가 난것이다.

 

해결법은.... 역시 간단하다.

$$를 두번 써주면 된다.

 

QueryDSL을 사용하면서 max 앤터티를 가져오는 방법은 2가지가 있다.

첫번째는 JPAExpressions를 이용하여 키값과 max값을 조회조건으로 하여 가져오는것.

두번째는 orderby를 이용하여 첫번째 앤터티를 가져오는것.

 

1. JPAExpressions를 이용한 max값과 비교하여 가져오기

JPAQueryFactory query = new JPAQueryFactory(em); // JPA 쿼리
		
QBuilding qBuilding = QBuilding.building;
QBuilding b = new QBuilding("b");

Building building
	= query.selectFrom(qBuilding)
	    .where(qBuilding.buildingID.eq(
	            JPAExpressions.select(b.buildingID.max()).from(b)))
	    .fetchFirst();

이런 경우 실제 실행되는 쿼리는 아래와 같다. (MS-SQL 기준)

select
    TOP(1) building0_.BuildingID as Building1_3_,
    building0_.Address as Address2_3_,
    building0_.InsertID as InsertID3_3_,
    building0_.InsertDT as InsertDT4_3_,
    building0_.UpdateID as UpdateID5_3_,
    building0_.UpdateDT as UpdateDT6_3_,
    building0_.BranchID as BranchI10_3_,
    building0_.BuildingNm as Building7_3_,
    building0_.OwnType as OwnType8_3_,
    building0_.Version as Version9_3_ 
from
    dbo.Building building0_ 
where
    building0_.BuildingID=(
        select
            max(building1_.BuildingID) 
        from
            dbo.Building building1_
    )

실행계획을 보면

 

 

2. orderby 를 이용한 첫번째 엔터티 가져오기

JPAQueryFactory query = new JPAQueryFactory(em); // JPA 쿼리
		
QBuilding qBuilding = QBuilding.building;

Building building
	= query.selectFrom(qBuilding)
		.orderBy(qBuilding.buildingID.desc())
	    .fetchFirst();

실제 실행되는 쿼리는 다음과 같다.

select
    TOP(1) building0_.BuildingID as Building1_3_,
    building0_.Address as Address2_3_,
    building0_.InsertID as InsertID3_3_,
    building0_.InsertDT as InsertDT4_3_,
    building0_.UpdateID as UpdateID5_3_,
    building0_.UpdateDT as UpdateDT6_3_,
    building0_.BranchID as BranchI10_3_,
    building0_.BuildingNm as Building7_3_,
    building0_.OwnType as OwnType8_3_,
    building0_.Version as Version9_3_ 
from
    dbo.Building building0_ 
order by
    building0_.BuildingID desc

실행계획은 다음과 같다.

둘다 큰차이는 없지만

코딩의 단순화와 실행계획이 한단계 준다는 사소한 문제로

orderby를 이용한 max entity 조회를 선호하고 있다.

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

JPA, Querydsl에서 exists 사용하기  (0) 2021.09.01

핸드폰으로 사진을 세로로 찍어서 업로드 하면

웹화면에서 해당 이미지를 출력하면 가로로 누워서 보이는 현상이 있다.

 

이는 웹 브라우저가 이미지의 orientation 정보대로 보여주지 못하기 때문이다.

모바일 앱으로 보면 세로 이미지가 정상으로 출력된다.

 

 

위 사진은 이미지 정보중 Exif의 Orientation 값을 나타낸다.

1. 정상

2. 좌우반전

3. 180도 회전

4. 180도 회전 + 좌우반전

5. 270도 회전 + 좌우반전

6. 270도 회전

7. 90도 회전 + 좌우반전

8. 90도 회전

 

이를 해결하는 방법은

화면단에서 자바스크립트를 이용한 방법도 있으나 해당 이미지 파일을 이용하여 여러 문서파일도 만드는 작업이 있어

모바일 앱에서 이미지 업로드 시 자바에서 orientaion 정보를 이용하여 1값으로 회전하여 저장하는 방식으로 

문제를 해결했다.

 

다음은 자바 소스이다.

 

이미지 Exit 정보를 읽기 위해서는 라이브러리 metadata-extractor 이 필요하다.

pom.xml에 아래와 같이 디펜던시를 설정한다.

 

	<!-- image EXIF read -->
	<dependency>
		<groupId>com.drewnoakes</groupId>
		<artifactId>metadata-extractor</artifactId>
		<version>2.9.1</version>
	</dependency>

 

 

// 1. 원본 파일을 읽는다.
// 2. 원본 파일의 Orientation 정보를 읽는다.
// 3. 변경할 값들을 설정한다.
// 4. 회전하여 생성할 파일을 만든다.
// 5. 원본파일을 회전하여 파일을 저장한다.

// 1. 원본 파일을 읽는다.
File imageFile = new File("D:/o6.jpg");

// 2. 원본 파일의 Orientation 정보를 읽는다.
int orientation = 1; // 회전정보, 1. 0도, 3. 180도, 6. 270도, 8. 90도 회전한 정보
int width = 0; // 이미지의 가로폭
int height = 0; // 이미지의 세로높이
int tempWidth = 0; // 이미지 가로, 세로 교차를 위한 임의 변수

Metadata metadata; // 이미지 메타 데이터 객체
Directory directory; // 이미지의 Exif 데이터를 읽기 위한 객체
JpegDirectory jpegDirectory; // JPG 이미지 정보를 읽기 위한 객체

try {
	metadata = ImageMetadataReader.readMetadata(imageFile);
	directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
	jpegDirectory = metadata.getFirstDirectoryOfType(JpegDirectory.class);
	if(directory != null){
		orientation = directory.getInt(ExifIFD0Directory.TAG_ORIENTATION); // 회전정보
		width = jpegDirectory.getImageWidth(); // 가로
		height = jpegDirectory.getImageHeight(); // 세로
	}
	
	// 3. 변경할 값들을 설정한다.
    AffineTransform atf = new AffineTransform();
    switch (orientation) {
    case 1:
        break;
    case 2: // Flip X
    	atf.scale(-1.0, 1.0);
    	atf.translate(-width, 0);
        break;
    case 3: // PI rotation 
    	atf.translate(width, height);
    	atf.rotate(Math.PI);
        break;
    case 4: // Flip Y
    	atf.scale(1.0, -1.0);
    	atf.translate(0, -height);
        break;
    case 5: // - PI/2 and Flip X
    	atf.rotate(-Math.PI / 2);
    	atf.scale(-1.0, 1.0);
        break;
    case 6: // -PI/2 and -width
    	atf.translate(height, 0);
    	atf.rotate(Math.PI / 2);
        break;
    case 7: // PI/2 and Flip
    	atf.scale(-1.0, 1.0);
    	atf.translate(-height, 0);
    	atf.translate(0, width);
    	atf.rotate(  3 * Math.PI / 2);
        break;
    case 8: // PI / 2
    	atf.translate(0, width);
    	atf.rotate(  3 * Math.PI / 2);
        break;
    }
    
    switch (orientation) {
	case 5:
	case 6:
	case 7:
	case 8:
        tempWidth = width;
        width = height;
        height = tempWidth;
		break;
	}
    
	BufferedImage image = ImageIO.read(imageFile);
	final BufferedImage afterImage = new BufferedImage(width, height, image.getType());
	final AffineTransformOp rotateOp = new AffineTransformOp(atf, AffineTransformOp.TYPE_BILINEAR);
	final BufferedImage rotatedImage = rotateOp.filter(image, afterImage);
	Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpg");
    ImageWriter writer = iter.next();
    ImageWriteParam iwp = writer.getDefaultWriteParam();
    iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    iwp.setCompressionQuality(1.0f);

    // 4. 회전하여 생성할 파일을 만든다.
    File outFile = new File("D:/o6_rotated.jpg");
    FileImageOutputStream fios = new FileImageOutputStream(outFile);
    
    // 5. 원본파일을 회전하여 파일을 저장한다.
    writer.setOutput(fios);
    writer.write(null, new IIOImage(rotatedImage ,null,null),iwp);
    fios.close();
    writer.dispose();
	
} catch (ImageProcessingException e) {
	e.printStackTrace();
} catch (MetadataException e) {
	e.printStackTrace();
} catch (IOException e) {
	e.printStackTrace();
}

 

참고로

css 를 이용하는 방법도 있으나

image-orientation: from-image;

이는 아직 파이어폭스에서만 지원하는 방식이다.

 

1. jdk 1.8 설치

openjdk를 다운로드 받아서 설치한다.

oralce java를 다운로드 받아서 설치해도 상관없지만 zip버전을 제공하지 않으므로 

openjdk를 다운로드 받아 설치한다.

 

https://github.com/ojdkbuild/ojdkbuild

중간쯤에 Downloads for Windows x86_64 항목이 있다. 여기서 zip파일을 다운로드 한다.

openjdk 다운로드

d:\에 springboot라는 폴더를 생성해서 이곳에 다운받은 openjdk를 압축해제 합니다.

폴더명이 너무 길어서 버전만 알수 있도록 정리했습니다.

이후 이클립스 설치 이후 이클립스 설정에서 해당 폴더로 연결을 할겁니다.

 

2. 이클립스 설치

이클립스를 다운받아 설치합니다. 아래 링크로 이동하여 파일을 다운받습니다.

https://www.eclipse.org/downloads/

최신버전보다는 어느정도  지난 버전을 받는걸 추천합니다.

다운로드 버튼 아래의 Download Packages 링크를 클릭하고 들어가서

다른 버전을 받을 수 있습니다.

 

자신의 운영체제에 맞는 이클립스를 선택해서 다운로드 하면 됩니다.

 

다운로드시 미러 사이트를 선택할 수 있는데 한국의 카카오를 선택하면 빠른 속도로 다운로드가 가능합니다.

 

다운받은 zip파일을 D:\springboot 로 복사하여 압축을 해제 합니다.

 

D:\springboot\eclipse 폴더로 이동하여 eclipse.ini 파일을 문서편집기로 열어서 -vmargs 위에 -vm을 추가합니다.

다운받은 openjdk를 이클립스에서 기본으로 사용하도록 하는 설정입니다.

-vm
D:\springboot\openjdk-1.8.0.222-2.b10\bin\javaw.exe

eclipse.ini 파일

-startup
plugins/org.eclipse.equinox.launcher_1.5.200.v20180922-1751.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.1.900.v20180922-1751
-product
org.eclipse.epp.package.jee.product
-showsplash
org.eclipse.epp.package.common
--launcher.defaultAction
openFile
--launcher.defaultAction
openFile
--launcher.appendVmargs
-vm
D:\springboot\openjdk-1.8.0.222-2.b10\bin\javaw.exe
-vmargs
-Dosgi.requiredJavaVersion=1.8
-Dosgi.instance.area.default=@user.home/eclipse-workspace
-XX:+UseG1GC
-XX:+UseStringDeduplication
--add-modules=ALL-SYSTEM
-Dosgi.requiredJavaVersion=1.8
-Dosgi.dataAreaRequiresExplicitInit=true
-Xms1024m
-Xmx1024m
--add-modules=ALL-SYSTEM

 

이클립스를 실행하면 workspace를 선택하는 창이 나옵니다. springboot에 workspace라는 폴더를 만들고 해당 폴더를 선택합니다.

 

 

 

3. 이클립스에 스프링(STS) 설치

이클립스가 실행되면 메뉴 Help > Eclipse Marketplace 메뉴를 클릭하여 

marketplace로 들어갑니다.

검색창에 sts를 입력하고 검색합니다.

Spring Tools 4 를 선택하여 Install 합니다.

 

 

4. 프로젝트 생성 및 테스트 페이지 접속

메뉴 : File > New > Other > Spring Boot > Spring Starter Project 를 선택하고 Next 클릭

 

 

 

 

 

<?xml version="1.0" encoding="UTF-8"?>

앞에 에러표시가 나는 경우 프로젝트 진행에는 문제가 없습니다. 하지만 x박이 계속뜨므로

properties에

<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> 

를 추가합니다.

	<properties>
		<java.version>1.8</java.version>
		<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> <!-- prevent pom config error-->
	</properties>

 

 

com.example.demo 패키지안에 DemoApiController class를 새로 만듭니다.

package com.example.demo;

import java.util.HashMap;
import java.util.Map;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoApiController {

	@GetMapping("/demoapistring")
	public String demoapistring() {

		return "데모 스트링 타입 리턴";
	}
	
	@GetMapping("/demoapi")
	public Map<String, Object> demoapi() {
		Map<String, Object> map = new HashMap<>();
		map.put("name", "홍길동");
		map.put("birthday", 15920505);
		return map;
	}
}

 

DemoApplication.java 파일을 열고 Run As > Spring Boot App 을 클릭하여 실행합니다.

Demo(프로젝트명)Application.java 는 스프링 부트의 시작점 입니다.

 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.7.RELEASE)

2019-08-21 15:53:06.169  INFO 13044 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication on WIN-F2I020EETH2 with PID 13044 (D:\springboot\workspace\demo\target\classes started by kyoborealco in D:\springboot\workspace\demo)
2019-08-21 15:53:06.171  INFO 13044 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
2019-08-21 15:53:06.807  INFO 13044 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-08-21 15:53:06.827  INFO 13044 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-08-21 15:53:06.827  INFO 13044 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.22]
2019-08-21 15:53:06.897  INFO 13044 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-08-21 15:53:06.897  INFO 13044 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 697 ms
2019-08-21 15:53:07.042  INFO 13044 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-08-21 15:53:07.168  INFO 13044 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-08-21 15:53:07.171  INFO 13044 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 1.216 seconds (JVM running for 1.737)
2019-08-21 15:53:15.125  INFO 13044 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-08-21 15:53:15.125  INFO 13044 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-08-21 15:53:15.129  INFO 13044 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 4 ms
2019-08-21 15:56:16.330  INFO 13044 --- [n(12)-127.0.0.1] inMXBeanRegistrar$SpringApplicationAdmin : Application shutdown requested.
2019-08-21 15:56:16.332  INFO 13044 --- [n(12)-127.0.0.1] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

콘솔 로그를 보면 톰캣이 8080으로 기동되었다고 표시됩니다.

http://localhost:8080/demoapistring

 

http://localhost:8080/demoapi

@RestController 을 추가한 경우

return 타입이 Object 인 경우 해당 object 에 맞게 json 형식으로 표시됩니다.

 

 

5. Boot Dashboard 설정 및 서버 포트 변경

 

파일들이 많이 지면 Application.java 에서 실행하는게 귀찮아 질 수 있습니다.

그래서 Boot Dashboard에서 바로 실행을 할 수 있도록 설정을 합니다.

 

window > show view > Other > Other > Boot Dashboard 선택

 

포트 변경은 src/main/resources/application.properties 파일에서

server.port=8888

로 설정하면 톰캣이 8888번 포트로 기동됩니다.

1. 엑셀 파일 형식

 

 번호

이름 

생년월일 

 1

홍길동 

 2001-01-31

 2

홍길순

 2010-02-28

 3

홍길자 

 2019-05-05

 

 

위와 같이 학생 목록의 엑셀을 읽어서

학생 List를 만들것이므로 우선 학생 DTO를 만든다.

 

2. DTO 생성

import java.util.Date;

public class StudentDTO {
	Integer id;    	//번호
	String name;	//이름
	Date birthDate;	//생년월일

	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Date getBirthDate() {
		return birthDate;
	}
	public void setBirthDate(Date birthDate) {
		this.birthDate = birthDate;
	}
}

 

3. pom.xml 에 dependency 추가

메이븐 프로젝트인 경우 디펜던시를 추가한다.

다른 형식의 프로젝트인 경우 해당 프로젝트에 맞추어 라이브러리를 임포트한다.

	<dependency>
	    <groupId>org.apache.poi</groupId>
	    <artifactId>poi</artifactId>
	    <version>4.0.1</version>
	</dependency>
	<dependency>
	    <groupId>org.apache.poi</groupId>
	    <artifactId>poi-ooxml</artifactId>
	    <version>4.0.1</version>
	</dependency>

 

4. 엑셀 파일 읽기

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

public class ExcelImport {
	
    public static void main( String[] args ) throws EncryptedDocumentException, IOException
    {
    	List<StudentDTO> studnetList = getStudentList();
    	for (StudentDTO studentDTO : studnetList) {
			System.out.println(studentDTO.getId() + ", " + studentDTO.getName() + ", " + studentDTO.getBirthDate());
		}
    }
	
	public static List<StudentDTO> getStudentList() throws EncryptedDocumentException, IOException {
	    List<StudentDTO> studentList = new ArrayList<StudentDTO>();
		
	    // 웹상에서 업로드 되어 MultipartFile인 경우 바로 InputStream으로 변경하여 사용.
	    // InputStream inputStream =  new ByteArrayInputStream(file.getBytes());
	    
	    String filePath = "D:\\student.xlsx"; // xlsx 형식
	    // String filePath = "D:\\student.xls"; // xls 형식
	    InputStream inputStream =  new FileInputStream(filePath);
		
	    // 엑셀 로드
	    Workbook workbook = WorkbookFactory.create(inputStream);
	    // 시트 로드 0, 첫번째 시트 로드
	    Sheet sheet = workbook.getSheetAt(0);
	    Iterator<Row> rowItr = sheet.iterator();
	    // 행만큼 반복
	    while(rowItr.hasNext()) {
	    	StudentDTO student = new StudentDTO();
	        Row row = rowItr.next();
	        // 첫번재 행이 해더인 경우 스킵, 2번째 행부터 data 로드
	        if(row.getRowNum() == 0) {
	            continue;
	        }
	        Iterator<Cell> cellItr = row.cellIterator();
	        // 한행이 한열씩 읽기 (셀 읽기)
	        while(cellItr.hasNext()) {
	            Cell cell = cellItr.next();
	            int index = cell.getColumnIndex();
	            switch(index) {
	            case 0: // 번호
	            	student.setId(((Double)getValueFromCell(cell)).intValue());
	            	// 셀이 숫자형인 경우 Double형으로 변환 후 int형으로 변환
	                break;
	            case 1: // 성명
	            	student.setName((String)getValueFromCell(cell));
	                break;
	            case 2: // 생년월일
	            	student.setBirthDate((Date)getValueFromCell(cell));
	                break;
	            }
	        }
	        studentList.add(student);
	    } 
	    return studentList;
	}

    // 셀서식에 맞게 값 읽기
    private static Object getValueFromCell(Cell cell) {
        switch(cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue();
            case BOOLEAN:
                return cell.getBooleanCellValue();
            case NUMERIC:
                if(DateUtil.isCellDateFormatted(cell)) {
                    return cell.getDateCellValue();
                }
                return cell.getNumericCellValue();
            case FORMULA:
                return cell.getCellFormula();
            case BLANK:
                return "";
            default:
                return "";                                
        }
    }

}

 

기존에 xls, xlsx 파일을 구분하지 않고 읽는다.

웹시스템에서는 주로 Multipart 형식으로 파일을 업로드 하여 엑셀을 읽으므로

서버에 저장없이 메모리에서 처리가 가능하다.

InputStream을 바로 생성하여 엑셀 파일을 읽으면 된다.

-- 가장 빨리 만나는 스프링부트

1) 포트 사용 에러

 

오류: 에이전트에 예외사항이 발생했습니다. : java.rmi.server.ExportException: Port already in use: 35199; nested exception is: 
java.net.BindException: Address already in use: JVM_Bind

 

실행 중지를 하지 않고 다시 실행하는 경우 위와 같은 에러가 종종 발생한다.

 

사용하려는 포트가 이미 사용중이라는 말로 사용중인 포트의 프로세스아이디를 찾아서 해당 프로세스를 죽여줘야 한다.

 

윈도우키+r -> cmd -> netstat 명령어로 사용중인 포트의 프로세스아이디를 찾아 킬하면 됩니다.

 

8080 사용 프로세스 검색 및 프로세스 종료시키기

 

cmd > netstat -ano | findstr 8080

실행을 하면 8080포트를 사용하는 프로세스 목록이 나옵니다.

가장 오른쪽의 프로세스 아이디를 킬해주면 됩니다.

cmd > taskkill /F /PID 8964

 

리소스 모니터에서 해당 프로세스 종료하기

작업관리자의 리소스모니터에서 해당 프로세스를 종료할 수 도 있습니다.

 

2) Unable to start embedded container

톰캣과 언더토우의 충돌

org.apache.tomcat.websocket.WsWebSocketContainer cannot be cast to io.undertow.websockets.jsr.ServerWebSocketContainer

 

org.springframework.context.ApplicationContextException: Unable to start embedded container; nested exception is java.lang.ClassCastException: org.apache.tomcat.websocket.WsWebSocketContainer cannot be cast to io.undertow.websockets.jsr.ServerWebSocketContainer
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.onRefresh(EmbeddedWebApplicationContext.java:133)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:474)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:686)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:320)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:957)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:946)
at com.example.App.main(App.java:13)
Caused by: java.lang.ClassCastException: org.apache.tomcat.websocket.WsWebSocketContainer cannot be cast to io.undertow.websockets.jsr.ServerWebSocketContainer
at io.undertow.websockets.jsr.Bootstrap.handleDeployment(Bootstrap.java:62)
at io.undertow.servlet.core.DeploymentManagerImpl.handleExtensions(DeploymentManagerImpl.java:244)
at io.undertow.servlet.core.DeploymentManagerImpl.deploy(DeploymentManagerImpl.java:149)
at org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory.createDeploymentManager(UndertowEmbeddedServletContainerFactory.java:340)
at org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory.getEmbeddedServletContainer(UndertowEmbeddedServletContainerFactory.java:203)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.createEmbeddedServletContainer(EmbeddedWebApplicationContext.java:157)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.onRefresh(EmbeddedWebApplicationContext.java:130)
... 7 common frames omitted

 

 

pom.xml 에서 스프링 부트의 기본 서버인 톰캣과 추가된 서버 언더토우의 충돌문제

 

	<dependency>
  		<groupId>org.springframework.boot</groupId>
  		<artifactId>spring-boot-starter-web</artifactId>
  	</dependency>
 
  	<dependency>
  		<groupId>org.springframework.boot</groupId>
  		<artifactId>spring-boot-starter-undertow</artifactId>
  	</dependency>

디팬던시에 undertow가 추가된 경우

spring-boot-starter-web 부분에서 톰캣을 사용하지 않는다는 설정을 추가해 주어야 한다.

  	<dependency>
  		<groupId>org.springframework.boot</groupId>
  		<artifactId>spring-boot-starter-web</artifactId>
 		<exclusions>
  			<exclusion>
  				<groupId>org.apache.tomcat.embed</groupId>
  				<artifactId>tomcat-embed-websocket</artifactId>
  			</exclusion>
  		</exclusions>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework.boot</groupId>
  		<artifactId>spring-boot-starter-undertow</artifactId>
  	</dependency>

또는 undertow를 사용하지 않는 방법이 있다.

 

jstl c:url 사용시 

url 뒤에 jsessionid가 붙는 현상이 발생했다.


jsp 소스

//상세조회

function fn_view(tblId, row) {

var param = {};

P_link("<c:url value = "/usr/agid/UsrAgidBasc0202.do"/>","",param);

}


결과 html

//상세조회

function fn_view(tblId, row) {

var param = {};

P_link("/usr/agid/UsrAgidBasc0202.do;jsessionid=897DDB88642E8EC0C014788F819FF3BA","",param);

}




세션이 생성되기 첫번째 접근 페이지에서 c:url을 사용한 경우

위와같은 ;jsessionid= 이 붙는 현상이다.

이는 서버에서 클라이언트에 세션을 유지하기 위해 쿠키를 생성해야 하는데 클라이언트가 쿠키생성을 허용했는지 허용하지 않았는지

불확실 하므로 jsessionid를 붙여서 다니는 것이다.


브라우저에서 쿠키 허용을 차단하면 더 쉽게 테스트 해볼 수 있다.





해결 방법은

1. jstl 기본 설정 방식이므로 그냥 사용한다.


2. web.xml 을 아래와 같이 변경한다.

    <session-config>

        <tracking-mode>COOKIE</tracking-mode>

    </session-config>


3. c:url 대신 ${pageContext.request.contextPath} 을 사용한다.



+ Recent posts