몇년만에 밑바닥부터 SpringBoot를 설정해서 개발하는지 모르겠다. 이하는 추후 활용을 위한 개인 기록용.

 

1. pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(중략)
 
<!-- Spring Boot 2.7.8 버전으로 활용 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.8</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
 
<!-- Mybatis Maven -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.2</version>
</dependency>
 
<!-- Maria DB -->
<dependency>
    <groupId>org.mariadb.jdbc</groupId>
    <artifactId>mariadb-java-client</artifactId>
    <version>2.7.11</version>
</dependency>
cs

 

maven dependency는 대략적으로 위와 같이 잡아주었다.

 

2. TestMapper.java / TestService.java / TestServiceImpl.java / TestController.java / TestSql.xml

1
2
3
4
5
6
7
8
9
10
package com.test.common.mapper;
 
import org.apache.ibatis.annotations.Mapper;
 
 
@Mapper
public interface TestMapper {
    public String getSysDate();
}
 
cs

 

 

1
2
3
4
5
6
package com.test.comm.service;
 
public interface TestService {
    String getSysdate();
}
 
cs

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.test.comm.service.impl;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.test.comm.service.TestService;
import com.test.common.mapper.TestMapper;
 
@Service
public class TestServiceImpl implements TestService {
    @Autowired
    TestMapper testMapper;
    
    @Override
    public String getSysdate() {
        String result = testMapper.getSysDate();
        return result;
    }
 
}
 
cs

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.test.comm;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
 
import com.test.comm.service.TestService;
 
@RestController
public class testController {
    @Autowired
    TestService testService;
    
    @PostMapping("/test")
    public String test(@RequestBody String a) {
        String result = "";
        result = testService.getSysdate();
        System.out.println("============= :::: " +result);
        return null;
    }
    
}
 
cs
 
 
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="com.test.common.mapper.TestMapper">
    <select id="getSysDate" resultType="java.lang.String">
        select sysdate() from dual
    </select>
</mapper>
cs

 

 

1
mybatis.mapper-locations=mapper/**/*.xml
cs

 

패키지 구조는 다음과 같다.

com.test.comm.TestController.java

com.test.comm.service.TestService.java

com.test.comm.service.impl.TestServiceImpl.java

com.test.common.mapper.TestMapper.java

src.main.resources.mapper.TestSql.xml

 

application.properties 에는 mybatis.mapper-locations 을 설정해준다.

 

오류가 난 상황으로는 SpringBoot 2.7.8 버전에서 mybatis-spring-boot-starter를 3.x 버전으로 설정해주었더니, TestServiceImpl 에서 @Autowired로 설정한 TestMapper.java를 인식할 수 없다는 오류가 발생했었다.

여러 시행착오 끝에 SpringBoot 2.7.8과 mybatis-spring-boot-starter 3.x과 호환이 안되는것을 인지하고, 현재는 2.x 버전대로 맞추어주었다.

 

에혀 힘들었다.

블로그 이미지

김생선

세상의 모든것을 어장관리

,

개발하는데 java 배치로 Windows Powershell을 파라미터 방식으로 호출할 일이 생겼다. 일단 powershell Script 코드는 다음과 같다.

 

1
2
echo 'TEST-1' $test
echo 'TEST-2' $userid
cs

대충 파라미터 두 개( test/userid)를 받아 echo로 찍어주는건데 기타 하위에는 물론 Azure Portal과의 통신이 있긴 하다. 근데 그게 중요한 것은 아니니까.

아무튼, java에서 파워쉘을 호출하는 소스코드는 다음과 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//중략
 
final Runtime rt = Runtime.getRuntime();
 
//powershell.exe 명령어를 통해서, 해당 경로의 ps1 파일을 실행함
String commands = String.format("powershell.exe \"F:\\powershell.ps1  ");
 
Process proc = null;
String s = null;
 
// PowerShell 명령 시도 및 메시지 출력
try {
    proc = rt.exec(commands);
 
    BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream());
    while (( s = stdInput.readLine()) != null ) {
        System.out.println(s);
    }
 
    BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream());
    while (( s = stdError.readLine()) != null ) {
        System.out.println(s);
    }
 
catch (IOException e) {
    e.printStacTrace();
 
}
 
//생략
cs

요기까지는 잘 했는데, 위의 powershell 코드와 같이 parameter를 넘기는 방법을 구글링해보아도 죄다 제각기 방법이 달라서, 츄라이를 해보니 다음과 같은 소스코드로 성공했다.

 

위의 java 소스에서 commands를 다음과 같은 방법으로 변경해주면 된다.

1
2
3
4
5
6
7
//중략
 
String[] arr = {"test_msg" , "test_user"}
//powershell.exe 명령어를 통해서, 해당 경로의 ps1 파일을 실행함
String commands = String.format("powershell.exe \"F:\\powershell.ps1  " + arr[0+ " " + arr[1]);
 
//생략
cs

PowerShell 코드는 다음과 같이 수정해주면 된다.

1
2
3
4
5
$test = $args[0]
$userid = $args[1]
 
echo 'TEST-1' $test
echo 'TEST-2' $userid
cs

java 에서 공백문자열로 구분된 string 값들이 args로 잘 매핑이 된다.

 

테스트를 해보니 해당 ps1(powershell)을 호출할 때 각 String 형태의 parameter(arguments)들을 넘길 때, 그냥 공백문자열로 구분하는 것으로 보인다. 실제로 여러방법으로 테스트 해보니 잘 되기도 하고. 이로써 java로 powershell script를 parameter 포함하여 execute 하는것에 성공했다. 앞으로 근데, 이러한 플젝을 몇번이나 할 지는 모르겠지만 말이다 =_=;;

 

 

java - how pass string array as a parameters to a powershell 

블로그 이미지

김생선

세상의 모든것을 어장관리

,

한컴오피스의 한글파일(hwp)에서 텍스트 추출할 일이 생겼다.

대충 뒤져보니 대단하신 분 께서 한글문서 파서 라이브러리를 만드셨는데, 아직까지도 일부기능에 대해 개선작업을 진행중이신 것 같다.

뭐 표 라거나 그림파일 등에 대해서는 정상동작하지 않는 듯 하지만 나는 텍스트만 추출할 것이기 때문에 당장은 문제없이 사용가능할것으로 보인다.

 

자세한 지원범위는 이 분의 깃으로 들어가보면 될 듯.

https://github.com/neolord0/hwplib

 

GitHub - neolord0/hwplib: hwp library for java

hwp library for java. Contribute to neolord0/hwplib development by creating an account on GitHub.

github.com

아무튼, 대충 임포트하고 대충 써보기로 한다. 생각보다 텍스트 추출이 아주 잘 되어서 다행이다.

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/kr.dogfoot/hwplib -->
<dependency>
  <groupId>kr.dogfoot</groupId>
  <artifactId>hwplib</artifactId>
  <version>1.0.1</version>
</dependency>
cs

이렇게 잡아주고,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* hwplib hwp document text extraction example
*/
 
import kr.dogfoot.hwplib.object.HWPFile;
import kr.dogfoot.hwplib.reader.HWPReader;
import kr.dogfoot.hwplib.tool.textextractor.TextExtractMethod;
import kr.dogfoot.hwplib.tool.textextractor.TextExtractor;
 
// 중략
 
HWPFile hwpFile;
String hwpText;
try {
    hwpFile = HWPReader.fromFile("/Users/kimfish/DEV/java_workspace/"+"test.hwp");
    hwpText = TextExtractor.extract(hwpFile, TextExtractMethod.InsertControlTextBetweenParagraphText);
 
    System.out.println("===== hwp text extractor =====");
    System.out.println("hwpText = " + hwpText);
catch (Exception e) {
    e.printStackTrace();
cs

이렇게 쓰면 된다. 개꿀

블로그 이미지

김생선

세상의 모든것을 어장관리

,

일전의 포스트와 마찬가지로 pdf 에서도 텍스트를 추출할 일이 생겼다.

당연하겠지만 해당 pdf는 ocr이 된 pdf를 기준으로만 추출이 가능하다.

 

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox -->
<dependency>
  <groupId>org.apache.pdfbox</groupId>
  <artifactId>pdfbox</artifactId>
  <version>2.0.24</version>
</dependency>
cs

이야 1년쯤 전에는 2.0.18 이었는데 그새 버전업했네. 아무튼 maven repository는 이렇게 잡아주고

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* PDFBox library PDF text Extraction Example
*/
 
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
 
// 중략
 
try {
    File file = new File("/Users/kimfish/DEV/java_workspace/"+"/test.pdf");
    PDDocument document;
    document = PDDocument.load(file);
 
    PDFTextStripper s = new PDFTextStripper();
    String content = s.getText(document);
 
    System.out.println("===== docx text extractor =====");
    System.out.println(content); 
catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
cs

이렇게 하면 정상적으로 OCR 처리된 text가 추출된다.

 

 

 

블로그 이미지

김생선

세상의 모든것을 어장관리

,

아주 심플하게, 회사에서 해당 문서규격(xls/xlsx/doc/docx)에 대해 텍스트를 추출하는것이 필요했다. 일전에는 잠깐 해보았는데, 정리된 적이 없었고 이번을 계기로 좀 알게된 몇가지 사실들이 있기에 간단하게 기록해본다.

 

일단 메이븐에서 다음과 같이 라이브러리를 잡아준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi-scratchpad</artifactId>
  <version>5.2.2</version>
</dependency>
 
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi</artifactId>
  <version>5.2.2</version>
</dependency>
 
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi-ooxml</artifactId>
  <version>5.2.2</version>
</dependency>
 
cs

 

1. 워드 (doc/docx) 텍스트 추출

워드 2003 이전버전인 doc 와 이후 버전인 docx 를 추출하기 위해서는 두 가지만 기억하면 된다.

HWPFDocument 클래스는 Horrible Word Process Format 의 약자라는 점이며, poi 라이브러리만 상속받는것이 아닌 poi-scratchpad 라이브러리를 같이 상속받아야 한다. 즉, poi dependency만 잡아놓으면 HWPFDocument가 import 되지 않는다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
* Word 2003 doc File Text Extraction Example
*/
 
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
 
// 중략
 
try {
    POIFSFileSystem poiFS = new POIFSFileSystem(
        new FileInputStream("/Users/kimfish/DEV/java_workspace/"+"/test.doc"));
    HWPFDocument hwp = new HWPFDocument(poiFS);
    WordExtractor we = new WordExtractor(hwp);
 
    String[] paragraphs = we.getParagraphText();
    System.out.println("===== doc text extractor ======");
    for (int i = 0; i < paragraphs.length; i++) {
        System.out.println(paragraphs[i]);
    }
 
catch (Exception e) {
    System.out.println(e); 
}
cs

 

docx 파일은 XWPFDocument를 import 받으며, docx의 text추출을 위해서는 poi 및 poi-ooxml 라이브러리에 대한 dependency가 필요하다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* Word 2007 docx File Text Extraction Example
*/
 
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
 
// 중략
 
try(
    FileInputStream fis = new FileInputStream
            ("/Users/kimfish/DEV/py_workspace/"+"/test.docx")) {  
    XWPFDocument file   = new XWPFDocument(OPCPackage.open(fis));  
    XWPFWordExtractor ext = new XWPFWordExtractor(file);  
 
    System.out.println("===== docx text extractor ======");
    System.out.println(ext.getText());  
}catch(Exception e) {  
    System.out.println(e);  
}  
cs

 

2. 엑셀 (xls/xlsx) 텍스트 추출

엑셀은 고려할 점이 상당히 많은데 각 셀/로우 별 텍스트 및 쉬트도 있는데다가 수식까지 가지고 있다. 현재는 샘플 토이 프로젝트라 수식이나 셀/로우 구분없이 텍스트만 가져오기를 수행해보았는데 일단 첫 쉬트에서는 잘 가져오고 있는듯. 좀 더 정교한 작업이 필요할 경우엔 공식 document 가 필요할것으로 보인다.

 

엑셀 2003 이전버전인 xls 같은 경우에는 poi Dependency 하나로도 잘 작동한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* Excel 2003 xls File Text Extraction Example
*/
 
import org.apache.poi.hssf.extractor.ExcelExtractor;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 
// 중략
 
try (
    FileInputStream fis = new FileInputStream("/Users/kimfish/DEV/java_workspace/"+"/test.xls")) {  
    
    String result = "";
 
    HSSFWorkbook wb = new HSSFWorkbook(fis);
    ExcelExtractor ee = new ExcelExtractor(wb);
    result = ee.getText();    
 
    System.out.println("===== xls text extractor =====");
    System.out.println(result);    
catch (Exception e) {
    //TODO: handle exception
}
cs

 

하지만 엑셀 2007 버전인 xlsx 같은 경우에는 워드2007과 마찬가지로 poi / poi-ooxml dependency 둘 다 필요하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
* Excel 2007 xlsx File Text Extraction Example
*/
 
import org.apache.poi.xssf.extractor.XSSFExcelExtractor;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 
// 중략
 
try (
    
    FileInputStream fis = new FileInputStream("/Users/kimfish/DEV/py_workspace/"+"/test.xlsx")) {  
                
    // Workbook
    XSSFWorkbook wb = new XSSFWorkbook(fis);
            
    // Text Extraction
    XSSFExcelExtractor extractor = new XSSFExcelExtractor(wb);
    extractor.setFormulasNotResults(true);
    extractor.setIncludeSheetNames(false);
    
    System.out.println("===== xlsx text extractor =====");
    System.out.println( extractor.getText() );
catch (Exception e) {
    //TODO: handle exception
}
cs

 

일단은 이렇게 해서 정상적으로 출력되는것을 확인함. 어휴 poi 하나로 다 해놓든가 의존성을 걸어두던가. 왜 poi / poi-ooxml / poi-scratchpad 이렇게 세 개가 죄다 쪼개져있는지원;;

 

정리

xlsx - poi / poi-ooxml

xls - poi

docx - poi / poi-ooxml

doc - poi / poi-scratchpad

'어장 Develop > 어장 JAVA' 카테고리의 다른 글

[hwplib] hwplib을 이용한 한글파일 텍스트 추출  (1) 2022.07.06
[PDFBox] pdf 텍스트 추출  (0) 2022.07.05
[Tomcat 9.x] JNDI설정  (0) 2022.02.21
[PDFBox] java Image to PDF  (0) 2021.08.24
[xml] RestAPI XML Return (2)  (0) 2021.07.06
블로그 이미지

김생선

세상의 모든것을 어장관리

,

뭔놈의 플젝이 SVN Update를 받으면 계속 에러가 나는건지. 이번에는 누가 JNDI 설정을 갈아치운건지 에러가 난다. 그래서 정리할 겸 작성함.

해당 프로젝트는 Tomcat 9.0버전대를 기준으로 JNDI설정이 되어있음.

 

1. Project - application.yml

1
2
3
spring:
  datasource:
  jndi-name: TESTDB_Pool
cs

별거는 없다. SpringBoot의 Application.yml 에서 JNDI를 다음과 같은 이름으로 사용하겠다고 명시적으로 알려주면 끝.

 

2. Tomcat - Context.xml

1
2
3
4
5
6
//중략
 
<WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>
 
//application.yml에 작성한 spring.datasource.jndi-name과 동일하게 작성한
<ResourceLink global="TESTDB_Pool" name="TESTDB_Pool" type="javax.sql.Datasource"/>
cs

여기 또한 별거 없다. application.yml의 jndi-name과 3.Tomcat-Server.xml과 연결시켜주는 부분이다. 

 

3. Tomcat - Server.xml

1
2
3
4
5
6
7
8
9
10
11
//중략
<Resource auth="Container" description="User database that can be updated and saved"
  factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
  name="UserDatabase" pathname="conf/tomcat-users.xml"
  type="org.apache.catalina.UserDatabase" />
 
//하단에 작성
<Resource auth="Container" driverClassName="com.tmax.tibero.jdbc.TbDriver"
  maxActive="5" maxIdle="5" name="TESTDB_Pool" type="javax.sql.DataSource"
  url="jdbc:tibero:thin:@000.000.000.000:65535" username="DB_ID" password="DB_PASSWORD"
  validationQuery="select 1 from dual" />
cs

가장 중요한 부분이다. DB커넥션에 관련된 모든 정보를 작성하는 곳으로, 기존의 application.yml에서 작성된 DB정보를 여기에 작성한다고 생각하면 된다.

 

 

이렇게 설정하고 재부팅 하면 된다.

'어장 Develop > 어장 JAVA' 카테고리의 다른 글

[PDFBox] pdf 텍스트 추출  (0) 2022.07.05
[poi] java poi 를 활용한 엑셀/워드 텍스트 추출  (0) 2022.07.04
[PDFBox] java Image to PDF  (0) 2021.08.24
[xml] RestAPI XML Return (2)  (0) 2021.07.06
[xml] RestAPI XML Return  (0) 2021.07.02
블로그 이미지

김생선

세상의 모든것을 어장관리

,

플젝을 하면서 PDF 문서만들기를 하게 되었다. 주어진 이미지들을 하나의 PDF 문서로 만드는 것인데 구글링 하니 잔뜩 나오고해서 내 입맛대로 소스 변경 후 기록용으로 작성한다.

 

우선 사용하는 라이브러리는 PDFBox라는 놈이다. Maven Dependency는 다음과 같다.

1
2
3
4
5
6
<!-- Apache PDF Box -->
<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.18</version>
</dependency>
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public static String makeImgPdf(File[] imgDir , String pdfDir) throws Exception {
    String result = "";
    //PDF 문서를 생성함
    PDDocument doc = new PDDocument();
    try {
        //Post를 통해 생성할 이미지를 가져옴
        File[] imgFiles = imgDir;
        
        //첨부된 이미지 파일 갯수만큼 반복문 실행
        for ( int i = 0; i<imgFiles.length; i++) {
        //이미지 사이즈 확인
        Image img = ImageIO.read(imgFiles[i]);
        
      //PDF 페이지를 생성함
       PDPage page = new PDPage(PDRectangle.A4);   
       // 그래서 PDF 문서내에 삽입함
        doc.addPage(page);
         
        //PDF에 삽입할 이미지를 가져온다. 인자값 - 이미지경로, PDF 페이지
        PDImageXObject pdImage = PDImageXObject.createFromFile(imgFiles[i].toString(), doc);
        //현재 설정된 PDF 페이지의 가로/세로 구하기
        int pageWidth = Math.round(page.getCropBox().getWidth());
        int pageHeight = Math.round(page.getCropBox().getHeight());
        
        //이미지 가로사이즈가 PDF 가로사이즈보다 클 경우를 대비해서 이미지 리사이징 실행
        //현재 설정된 PDF 페이지 가로 , 이미지 가로 사이즈로 비율 측정 
        float imgRatio = 1;
        if ( pageWidth < img.getWidth(null)) {
            imgRatio = (float)img.getWidth(null/ (float)pageWidth;
            System.out.println(">>> 이미지 비율 : " + ratio);
        }
        
        //설정된 비율로 이미지 리사이징
        int imgWidth = Math.round(img.getWidth(null/ imgRatio);
        int imgHeight = Math.round(img.getHeight(null/ imgRatio);
        
        //이미지를 가운데 정렬하기 위해 좌표 설정
        int pageWidthPosition = (pageWidth - imgWidth) / 2;
        int pageHeightPosition = (pageWidth - imgWidth) / 2;
        
        PDPageContentStream contents = new PDPageContentStream(doc, page);
        //그 콘텐츠에 이미지를 그린다. 인자값 - X/Y/가로사이즈/세로사이즈
        contents.drawImage(pdImage, pageWidthPosition, pageHeightPosition, imgWidth, imgHeight);
        //콘텐츠에 이미지를 다 그렸으면 콘텐츠를 종료
        contents.close();
        //그린것이 끝났으니 해당 문서를 저장.
        doc.save(pdfDir);
        
 
        }
    } catch (Exception e) {
        System.out.println("Exception! : " + e.getMessage());
    }
 
    try {
        doc.close();
        result = "success";
    } catch (IOException e) {
        result = "error";
        e.printStackTrace();
    }
 
    return result;
}
cs

최대한 주석을 많이 달아놨다.

 

PDF를 만들면서 가장 신경쓴 부분이 이미지의 리사이징 및 가운데정렬 부분이었다.

27번 라인부터 시작하는데, PDF의 가로 길이와 이미지의 가로길이를 나눠준 비율만큼 이미지 세로 비율도 축소시켜버리는 것.

그리고 그렇게 축소된 이미지를 PDF의 가운데정렬을 해야했기 때문에 38번 라인에서 PDF의 가로만큼 이미지 가로 차이를 계산해준다. 그리고 반으로 나눠주면 PDF 내 이미지가 양쪽 여백이 계산될 것이다. 마찬가지로 PDF의 세로만큼 이미지 세로 차이를 계산한 후, 반으로 나눠주면 PDF 내 이미지의 상하 여백이 계산되는 방식.

 

일단은 전체적으로 잘 나온다.

추후에 고려할 부분은 옵션에 따라 PDF 용지의 가로방향/세로방향을 설정할 수 있게 하는 부분인데, 이 또한 크게 어렵지 않으니 금방할 수 있을 것으로 보인다.

 

+결과

PDF의 배경이미지 사이즈는 가로 595, 세로 842이다

이미지는 가로 3000, 세로 3600으로, 계산시 이미지 비율이 약 5.042017이 나온다.

즉, 이미지 축소가 없었으면 PDF 전체를 덮고도 남았을것이나, PDF 가로 비율에 맞춰 축소가 성공적으로 진행되었으며,

위의 로직과 같이 상하 여백이 동일하게 되어 가운데 정렬이 성공적으로 되었음을 알 수 있다.

 

1
2
3
4
5
>>> pageWidth : 595
>>> pageHeight : 842
>>> 이미지 비율 : 5.042017
>>> imgWidth : 3000
>>> imgHeight : 3900
cs

'어장 Develop > 어장 JAVA' 카테고리의 다른 글

[poi] java poi 를 활용한 엑셀/워드 텍스트 추출  (0) 2022.07.04
[Tomcat 9.x] JNDI설정  (0) 2022.02.21
[xml] RestAPI XML Return (2)  (0) 2021.07.06
[xml] RestAPI XML Return  (0) 2021.07.02
[SpringBoot] Async 사용하기  (0) 2021.06.25
블로그 이미지

김생선

세상의 모든것을 어장관리

,

지난번에 XML RestAPI Return에 대해 포스팅을 하자마자, 실제 URL 호출 테스트를 해보니 웬걸 내가 전달받은 형태와는 판이하게 달랐다.

 

지난번에 작성한 부분은 아래와 같고, 그것에 대한 소스코드는 여기로 들어가면 된다.

https://kimfish.co.kr/328

1
2
3
4
5
6
7
<MAIN>
  <AGE>16</AGE>
  <SEX>Male</SEX>
  <JOB>Student</JOB>
  <PHONE>01022223333</PHONE>
  <STATUS>Normal</STATUS>
</MAIN>
cs

여튼, 이번에 서버와 통신을 해보니 다음과 같은 형식으로 이루어져야만 했다.

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="EUC-KR" standalone="no"?>
<MAIN>
  <AGE value="16"/>
  <SEX value="Male"/>
  <JOB value="Student"/>
  <PHONE value="01011112222"/>
  <STATUS value="Normal"/>
</MAIN>
cs

허미 ==;

여튼 그래서 다음과 같이 작성했다. 지난번의 경우에는 jackson-dataformat-xml 라이브러리를 활용했으나, 이번에는 기본적인 DocumentBuilderFactory를 활용하는걸로..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import java.io.StringWriter;
 
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
 
import org.w3c.dom.Element
import org.w3c.dom.Document;
import org.w3c.dom.Attr;
 
 
@GetMapping(path="/Main", produces=MediaType.APPLICATION_XML_VALUE)
public String xmlTestMain(HttpServletRequest request) throws Exception{
        Document doc = null;
        DocumentBuilderFactory factory = null;
        factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        doc = builder.newDocument();
        
        //최상위 Root를 구성하는 Element 생성
        Element rootElement = doc.createElement("MAIN");
        doc.appendChild(rootElement); 
 
        //하위 Element를 생성. 
        Element ageElement = doc.createElement("AGE");
        //Attribute 를 신규로 선언해주고, Document를 이용하여 value를 생성함
        Attr attr = doc.createAttribute("value");
        //생성된 Attr에 value를 세팅
        attr.setValue("1");
        //생성한 ageElement에 위에서 신규선언된 Attr을 세팅
        ageElement.setAttributeNode(attr);
        // ageElement를 rootElement의 하위요소로 지정
        rootElement.appendChild(ageElement);
 
        //위의 과정을 아래와 같이 단순화 시킬 수 있음
        Element sexElement= doc.createElement("SEX");
        sexElement.setAttribute("value""Male");
        rootElement.appendChild(sexElement);
 
        Element jobElement = doc.createElement("JOB");
        jobElement .setAttribute("value""Student");
        rootElement.appendChild(jobElement );
 
        Element phoneElement = doc.createElement("PHONE");
        phoneElement .setAttribute("value""01011112222");
        rootElement.appendChild(phoneElement );
 
        Element statusElement = doc.createElement("STATUS");
        statusElement .setAttribute("value""Normal");
        rootElement.appendChild(statusElement );
 
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        transformer.setOutputProperty(OutputKeys.ENCODING, "euc-kr");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        doc.setXmlStandalone(false);
 
        StringWriter sw = new StringWriter();
        transformer.transform(new DOMSource(doc), new StreamResult(sw));
 
        String aaa = sw.getBuffer().toString();
        return aaa;
}
 
cs

에휴 일단 됐다... 좀 쉬자

'어장 Develop > 어장 JAVA' 카테고리의 다른 글

[Tomcat 9.x] JNDI설정  (0) 2022.02.21
[PDFBox] java Image to PDF  (0) 2021.08.24
[xml] RestAPI XML Return  (0) 2021.07.02
[SpringBoot] Async 사용하기  (0) 2021.06.25
[SpringBoot] fixed delay, fixed rate의 차이  (0) 2021.06.23
블로그 이미지

김생선

세상의 모든것을 어장관리

,

RestAPI 통신을 활용해서 개발하다보면 접속대상지 서버가 구성되는데까지 시간이 다소 소요되는 경우가 많다. 그래서 나의 경우에는 자체적으로 dummy rest api 서버를 만들어두고 더미데이터를 return 해주는 것으로 임시방편 해결을 하는 편.

이번엔 XML 형태의 RestAPI 통신이 이루어지다보니, 잘 다루지 않던 XML 파싱 및 XML Return하는 것을 간단히 정리한다.

 

필요한 라이브러리는 다음과 같으며, maven에서는 바로 가져올 수 있다.

1. jackson-dataformat-xml-2.11.4.jar
2. (의존성) jackson-core-2.11.4.jar
3. (의존성) woodstox-core.6.2.3.jar
4. (의존성) jackson-databind-2.12.3.jar
5. (의존성) stax-2-api-4.2.1.jar
6. (의존성) jakarta.activation-api-1.2.1.jar
7. (의존성) jakarta.xml.bin-api-2.3.2.jar
8. (의존성) jackson-module-jaxb-annotations-2.11.4.jar

 

만들고자 하는 XML 형태는 아래와 같다.

1
2
3
4
5
6
7
<MAIN>
  <AGE>16</AGE>
  <SEX>Male</SEX>
  <JOB>Student</JOB>
  <PHONE>01022223333</PHONE>
  <STATUS>Normal</STATUS>
</MAIN>
cs

 

소스는 다음과 같다.

 

1.  testXml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
 
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
 
@Data
@NoArgsConstructor
@AllArgsConstructor
@JacksonXmlRootElement(localName="MAIN"//루트태그명
public class TESTXml {
  @JacksonXmlProperty private String AGE;
  @JacksonXmlProperty private String SEX;
  @JacksonXmlProperty private String JOB;
  @JacksonXmlProperty private String PHONE;
  @JacksonXmlProperty private String STATUS;
}
cs

xml로 생성할 항목들에 대해 TESTXml이라는 이름으로 데이터 클래스를 생성해준다.

@JacksonXmlProperty Annotation은 루트태그의 하위에 들어가는 그룹 요소로 생성이 된다.

 

2. DummyController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.http.MediaType;
 
 
@GetMapping(path="/xmlTest", produces=MediaType.APPLICATION_XML_VALUE)
public TESTXml xmlDummy(HttpServletRequest request) throws Exception {
  TESTXml testxml = new TESTXml();
  testxml.setAGE("16");
  testxml.setSEX("Male");
  testxml.setJOB("Student");
  testxml.setPHONE("01011112222");
  testxml.setSTATUS("Normal");
  
  return testxml;
}
cs

lombok 라이브러리를 이용했기에 getter/setter 지정이 아주 깔끔하다. 이렇게 해주면 /xmlTest라는 URL을 호출했을 때 예제와 같은 형식으로 사용이 가능하다.

 

아래의 블로그를 통해 정리했다. 도움 많이 받음.

https://binit.tistory.com/28

블로그 이미지

김생선

세상의 모든것을 어장관리

,

스프링부트에서 Async를 사용할 일이 생겼다. 대략적인 비즈니스 로직은 다음과 같다.

 

1. Scheduler를 통해 DB를 Select 함
2. 대략 1500~2000건의 데이터를 A 서버에 HttpConnection을 이용하여 Request/Response를 받아야 함
3. 최대한 빨리

일단 가장 심플하게 생각하자면 @Scheduled를 이용, DB를 쿼리한 후 해당 List를 for문으로 돌리면서 A서버로 httpConnection을 이용하면 될 것 같고 실제로 그렇게 구현했다. 하지만 Request/Response의 응답시간이 건당 약 3초가 소요되며, 전체적으로 건수가 많기에 1500 * 3 = 4500초가 걸리는 초유의 사태가 발생했다. 그래서 결국엔 Async를 이용해 전체로직을 다 뜯어고치게 되었다. 내게 이렇게 오래걸리면 안된다고 왜 미리 말을 안해준거니. 하루에 두 번만 수행하면 된다매!

 

기본적으로 다음과 같이 소스를 구성했다.

1. Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import org.springframework.scheduling.annotation.Scheduled;
 
// 중략
@Autowired
HttpConnectorUtil httpConnUtil;
 
@Scheduled(fixedDelay = 300000)
public void dbSelectScheduler() throws InterruptedException {
  logger.info("dbSelectScheduler Start!");
 
  JSONObject queryObj = new JSONObject();
  try {
    // DB 쿼리부. 대충 1000개의 사이즈를 가진 List라고 치자
    List<HashMap<String, Object>> targetList = dbService.selectTargetQuery(queryObj);
 
    for ( int a = 0 ; a < targetList.size(); a++){
      Thread.sleep(300); //너무 빨리 요청하면 서버가 못버팀
      httpConnUtil.HttpConnection("http://localhost:8080/localTest", targetList.get(a).toString());
    }
 
  } catch {
    e.printStackTrace();
    logger.info("ReqService ERROR! {}" , e.getMessage());
  }
 
  logger.info("dbSelectScheduler End!");
}
cs

여기에서는 별다른 로직이 없다. DB에서 쿼리한 갯수만큼 for문을 돌리고, for 내부에서는 지정된 URL에 대해 httpConnector 요청을 날린다.

 

2. AsyncConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.concurrent.Executor;
 
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
//Configuration 선언 필수
@Configuration
// EnableAsync 선언 필수. 여기서 해당 플젝의 AsyncConfigurer를 공통선언/사용
@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport {
 
  @Override
  public Executor getAsyncExecutor(){
    ThreadPoolTaskExecutor asyncExecutor = new ThreadPoolTaskExecutor();
    asyncExecutor.setCorePoolSize(10); //최소 동작하는 Async Thread 수
    asyncExecutor.setMaxPoolSize(20);  //최대 동작하는 Async Thread 수
    asyncExecutor.setQueueCapacity(100); // 최대 동작 Thread를 초과할 시, 보관되는 Queue의 수
    asyncExecutor.setThreadNamePrefix("TESTAsync-");  // Async Thread가 동작할 때 표시되는 Thread Name
    asyncExecutor.initialize();  //위 설정 적용
    return asyncExecutor;
  }
}
cs

현재 프로젝트에서 공통적으로 사용하는 AsyncConfig이다. 보다 자세한 내용은 아래의 블로그에서 확인이 가능하다. 해당 블로그를 통해 많이 배웠다. https://www.hanumoka.net/2020/07/02/springBoot-20200702-sringboot-async-service/

 

springboot 비동기 서비스 만들기(Async)

들어가기springboot rest 서버에서 어떤 요청을 받으면, Shell command를 호출하는 기능을 구현해야 한다. 문제는 Shell command로 호출하는 호출하는 것이 Python 스크립트이고, 이 스크립트 동작이 몇분은

www.hanumoka.net

여튼, 기본적으로 숙지해야 할 사항은 이번 플젝은 언제든 Thread의 수가 변할 수 있음을 감안하고 setCorePoolSize, setMaxPoolSize는 application.yml에서 가져올 수 있도록 수정했다. 현재는 위 설정대로 1500건에 대해 300ms의 thread.sleep을 준 상태로 요청을 하며, 아직까지는 문제없이 2500건까지 수행하였다.

 

@EnableAsync를 통해 프로젝트에서 공통적으로 사용할 AsyncExecutor를 설정한다. 이는 다른 AsyncService에서도 사용하므로 자신의 프로젝트 상황을 고려하여 설정값을 변경하면 된다.

 

3. AsyncService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import org.springframework.scheduling.annotation.Async;
//다른 import들은 생략
 
// Service 에서만 Async 어노테이션이 정상동
@Service
public class AsyncService {
 
  //Async를 사용할 때에는 아래의 @Async Annotation을 선언
  @Async
  public JSONObject HttpConnection(String url , String strJson) throws Exception{
    //대충 httpconnection에 필요한것들 선언, 생략
 
    try {
       URL url = new URL(url);
       HttpURLConnection conn = (HttpURLConnection) url.openConnection();
       //conn에 해당하는 내용 생략
       
       OutputStream = os = conn.getOutputStream();
       os.write(strJSON.getBytes("UTF-8"));
       os.flush();
 
       //리턴내용 생략
    } catch (Exception e) {
      e.getMessage();
    }
 
    return jsonResult;
  }
 
}
cs

 

실질적으로 Async요청을 하는 부분이다. controller에서는 AsyncService(@Service)를 호출하여, @Async 로 선언된 HttpConnection을 사용하게 된다. 이로써 Http를 호출하는 부분은 Async로 동작하게 된다.

 

실행 로그는 다음과 같다.

1
2
3
4
5
6
2021-06-21 16:39:41.300 INFO 2068 --- [  TEST-Async-3] com.example.AsyncService: [HttpConnectorResult] 1
2021-06-21 16:39:41.400 INFO 2068 --- [  TEST-Async-1] com.example.AsyncService : [HttpConnectorResult] 22
2021-06-21 16:39:41.510 INFO 2068 --- [  TEST-Async-2] com.example.AsyncService : [HttpConnectorResult] 4
2021-06-21 16:39:41.997 INFO 2068 --- [ TEST-Async-10] com.example.AsyncService : [HttpConnectorResult] 11
2021-06-21 16:39:42.010 INFO 2068 --- [  TEST-Async-7] com.example.AsyncService : [HttpConnectorResult] 3331
2021-06-21 16:39:42.314 INFO 2068 --- [  TEST-Async-5] com.example.AsyncService : [HttpConnectorResult] 51
cs

처음에는 AsyncConfig의 설정 방법 등을 몰라 @Async를 선언해준 class에서 위의 executor를 죄다 세팅해주었는데, 이 포스트를 작성하면서 샘플소스를 만들어보니 공통으로 사용가능하단 점을 깨닫게 되었다.

블로그 이미지

김생선

세상의 모든것을 어장관리

,