아키텍처와 함께

블로그 이미지
by gregorio
  • Total hit
  • Today hit
  • Yesterday hit

'전체 글'에 해당되는 글 56건

  1. 2018.04.23
    Spring framework Future와 AsyncResult 를 이용한 Async 서비스 호출
  2. 2018.04.19
    Mybatis ResultHandler를 이용한 ExcelDownload 3
  3. 2018.04.13
    Spring framework Property Reload
  4. 2018.04.12
    Java Reflection 사용법
  5. 2018.03.23
    Mybatis include sql

Spring framework Controller에서는 일반적으로 동기 방식으로 서비스를 호출한다.

그러나 요청된 파라미터의 값에 따라 동시에 여러 서비스를 호출하여 결과를 받을 때 순차적으로 서비스를 호출하지 않고  비동기로 서비스를 호출하여 결과를 Return 받으면 서비스의 수행 성능을 향상 시킬 수 있다.


Controller에서 비동기적으로 서비스를 호출하기 위해서는 Java 1.5에서부터 지원하는 Future 기능을 사용하고, 응답을 받을 경우 호출되는 서비스에서  AsyncResult를 이용하여 결과를 Return할 수 있다.


Funcure 기능과 AsyncResult를 이용하기 위해서는 task관련 Bean을 다음과 같이 정의한다.


■ context-baen.xml

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

        xmlns:p="http://www.springframework.org/schema/p"

        xmlns:context="http://www.springframework.org/schema/context"

        xmlns:task="http://www.springframework.org/schema/task"

        xmlns:mvc="http://www.springframework.org/schema/mvc"

        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd

                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd

                http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd

                http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd">


<task:annotation-driven  executor="taskExecutor"/>

<!-- mvc:annotation-driven>

<mvc:async-support default-timeout="2500" task-executor="taskExecutor">

<mvc:callable-interceptors>

<bean class="org.springframework.web.servlet.config.MvcNamespaceTests.TestCallableProcessingInterceptor" />

</mvc:callable-interceptors>

<mvc:deferred-result-interceptors>

<bean class="org.springframework.web.servlet.config.MvcNamespaceTests.TestDeferredResultProcessingInterceptor" />

</mvc:deferred-result-interceptors>

</mvc:async-support>

</mvc:annotation-driven-->

<!-- Async Task -->

<!-- task:annotation-driven scheduler="scheduler" executor="taskExecutor"/>

<task:scheduler id="scheduler" pool-size="10"/-->

<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">

    <property name="corePoolSize" value="100"/>

    <property name="maxPoolSize" value="100"/>

    <property name="queueCapacity" value="2000000000"/>

    <property name="keepAliveSeconds" value="120"/>

</bean>  

</beans> 

비동기적으로 서비스를 호출하기 위해 <task:annotation-drive>을 설정하고, executor를 정의하여 Pool size와 Capacity를 정의한다.



다음은 비동기적으로 실행할 서비스를 생성한다.


■AsyncServiceImpl.java

import org.springframework.scheduling.annotation.Async;

import org.springframework.scheduling.annotation.AsyncResult;

import org.springframework.stereotype.Service;


@Service("asyncService")

public class AsyncServiceImpl extends BaseService implements AsyncService{


@Async

@Override

public Future<List<Map<String, Object>>> selectUserListAsync(NexacroMapDTO dto) throws Exception {

LOGGER.info("selectUserListAsync Start");

Map<String, Object> searchMap = getDataSet(dto, "searchMap");

Thread.sleep(2000);

List<Map<String, Object>> resultList = getMapper("sbMapper").selectList("user.selUser", searchMap);

return new AsyncResult<List<Map<String, Object>>>(resultList);

}


@Async

@Override

public Future<List<Map<String, Object>>> selectUserListAsync1(NexacroMapDTO dto) throws Exception {

LOGGER.info("selectUserListAsync1 Start");

Map<String, Object> searchMap = getDataSet(dto, "searchMap");

List<Map<String, Object>> resultList = getMapper("sbMapper").selectList("user.selUser", searchMap);

LOGGER.info("selectUserListAsync1 End");

return new AsyncResult<List<Map<String, Object>>>(resultList);

}


}


Service 메소드에 @Async Annotation을 설정하여 해당 메소드가 비동기적 수행을 정의한다.

@Async는 public 메소드에만 정의되고, 동일한 Class에서는 @Async 메소드를 호출할 수 없다. 즉 외부의  Class에서 @Async 메소드를 호출한다.


@Async로 정의된 메소드는 Return Type이 Future<?>이고 결과는 AsyncResult를 생성하여 결과를 Return한다.


다음은 서비스를 호출하는 Controller를 생성한다.


■AsyncController


@RequestMapping(value = "/sample/selectUserAsync.do")

public ModelAndView selectUserAsync(NexacroMapDTO dto) throws Exception {

ModelAndView modelAndView = NexacroUtil.getModelAndView(dto);


Future<List<Map<String,Object>>> future1 = asyncService.selectUserListAsync(dto);


Future<List<Map<String,Object>>> future2 = asyncService.selectUserListAsync1(dto);


// List<Map<String,Object>> resultList1 = future1.get();

// List<Map<String,Object>> resultList2 = future2.get();


               modelAndView.addObject("list1", resultList1);

               modelAndView.addObject("list2", resultList2);

return modelAndView;

}

 


Controller에서 Future에 서비스의 Async Method를 호출한 후 결과를 받기 위해 생성된 future의 get Method를 호출하여 결과를 받는다.


■ 수행결과


  INFO | createCompositeTransaction ( 300000 ): created new ROOT transaction with id 192.168.0.59.tm0000100063

 INFO | createCompositeTransaction ( 300000 ): created new ROOT transaction with id 192.168.0.59.tm0000200063

 INFO | selectUserListAsync1 Start

 INFO | selectUserListAsync Start

 INFO | AtomikosDataSoureBean 'xaSBDataSource': getConnection ( null )...

 INFO | AtomikosDataSoureBean 'xaSBDataSource': init...

 INFO | atomikos connection proxy for oracle.jdbc.driver.LogicalConnection@1c182f16: calling getMetaData...

 INFO | 1. Connection opened

 INFO | 1. Connection.new Connection returned 

 INFO | atomikos connection proxy for oracle.jdbc.driver.LogicalConnection@1c182f16: calling getAutoCommit...

 INFO | 1. Connection.getAutoCommit() returned true

 INFO | addParticipant ( XAResourceTransaction: 3139322E3136382E302E35392E746D30303030313030303633:3139322E3136382E302E35392E746D31 ) for transaction 192.168.0.59.tm0000100063

 INFO | XAResource.start ( 3139322E3136382E302E35392E746D30303030313030303633:3139322E3136382E302E35392E746D31 , XAResource.TMNOFLAGS ) on resource xaSBDataSource represented by XAResource instance oracle.jdbc.driver.T4CXAResource@5c7c0881

 INFO | registerSynchronization ( com.atomikos.jdbc.AtomikosConnectionProxy$JdbcRequeueSynchronization@48d3cd82 ) for transaction 192.168.0.59.tm0000100063

 INFO | atomikos connection proxy for oracle.jdbc.driver.LogicalConnection@1c182f16: calling prepareStatement(SELECT

ID,

NAME,

PASSWORD,

DESCRIPTION,

USE_YN,

REG_USER

FROM TB_USER

WHERE 1 = 1)...


결과를 확인하면 로그에 selectUserListAsync1,과 selectUserListAsync 출력됨을 확인할 수 있으며, 각각의 서비스가 순차적으로 호출되지 않고 병렬로 호출되었음을 확인할 수 있다.

 

AND

대용량의 데이터를 데이터베이스에서 읽어 Excel로 다운로드 할 때 OOM(Out Of Memory)가 자주 발생한다.


OOM을 해결하기 위해서 Mybatis를 사용하는 경우에는 ResultHandler를 이용하여 각각의 Result에 대해 Excel의 Row를 생성하면 OOM 발생을 방지할 수 있다.


먼저 Mybatis의 ResultHandler를 구현한다.


■ ExcelResultHandler 예제

import java.util.List;

import java.util.Map;


import org.apache.ibatis.session.ResultContext;

import org.apache.ibatis.session.ResultHandler;

import org.apache.poi.ss.usermodel.Cell;

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.util.CellRangeAddress;

import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;


import stis.framework.exception.BizException;

import stis.framework.util.ExcelStyle;

import stis.framework.util.ExcelUtil;

import stis.framework.util.NullUtil;


public class ExcelResultHandler<T> implements ResultHandler<T> {


private static final Logger LOGGER = LoggerFactory.getLogger(ExcelResultHandler.class);

private Workbook workbook =  null;

private Sheet sheet = null;

private int rowNum = 0;

public ExcelResultHandler(Workbook workbook) {

this.workbook = new XSSFWorkbook();

try {

createSheetTitle(this.workbook, null, null);

}

catch(Exception ex) {

}

}

public ExcelResultHandler(Workbook workbook, List<String> headerColumns) {

this.workbook = workbook;

try {

createSheetTitle(this.workbook, null, headerColumns);

}

catch(Exception ex) {

}

}

public ExcelResultHandler(Workbook workbook, String headerName, List<String> headerColumns) {

this.workbook = workbook;

try {

createSheetTitle(this.workbook, headerName, headerColumns);

}

catch(Exception ex) {

LOGGER.error("Exception :: {}", ex.getMessage());

}

}



/**

*<pre>

* 1.Description: Create excel header and header column

* 2.Biz Logic:

* 3.Author : LGCNS

*</pre>

* @param workbook excel workbook

* @param headerName excel header name

* @param headerColumns excel header column

* @throws Exception

*/

private void createSheetTitle(Workbook workbook, String headerName, List<String> headerColumns) throws Exception {


sheet = workbook.createSheet();

/** Create Excel Header Name **/

if (!NullUtil.isNull(headerName)) {

Row row = sheet.createRow(rowNum);

Cell cell = row.createCell(0);

cell.setCellValue(headerName);

cell.setCellStyle(ExcelStyle.getHeaderNameStyle(this.workbook));


/** Cell Merge **/

sheet.addMergedRegion(new CellRangeAddress(rowNum, rowNum, 0, headerColumns.size() - 1));

rowNum++;

}

/** Create excel header columns **/

if (headerColumns != null) {

Row row = sheet.createRow(rowNum);

int idx = 0;

for (String headerColumn : headerColumns) {

Cell cell = row.createCell(idx++);

cell.setCellStyle(ExcelStyle.getHeaderColumnStyle(this.workbook));

cell.setCellValue(headerColumn);

}

rowNum++;

}

}

@SuppressWarnings("unchecked")

@Override

public void handleResult(ResultContext<? extends T> resultContext) {

/** Map **/

if (LOGGER.isDebugEnabled()) {

LOGGER.debug("Result Count :: {}", resultContext.getResultCount());

LOGGER.debug("Result Class :: {}", resultContext.getClass());

}

if (resultContext.getResultObject() instanceof Map) {

try {

ExcelUtil.map2Excel(sheet, (Map<String, Object>)resultContext.getResultObject(), ExcelStyle.getDataStyle(workbook), rowNum);

}

catch (Exception ex) {

LOGGER.error("Excel file creation error :: {}", ex.getMessage());

throw new BizException("Excel file creation error");

}

rowNum++;

}

/** Value Object **/

else {

try {

ExcelUtil.object2Excel(sheet, resultContext.getResultObject(), ExcelStyle.getDataStyle(workbook), rowNum);

}

catch (Exception ex) {

LOGGER.error("Excel file creation error :: {}", ex.getMessage());

throw new BizException("Excel file creation error");

}

rowNum++;

}

}


ExcelResultHandler는 Mybatis의 ResultHandler의 handleResult 메소드를 구현한다.



ExcelResultHandler는 3개의 생성자를 가지고 있는데 Excel File 생성 시 Title과 Excel Column의 이름을 파라미터를 받아 Excel File에 생성한다.

 

또한 Workbook은 ExcelResultHandler를 호출하는 객체에서 생성하여 생성자 파라미터를 전달하여야 한다.


handleResult에서는 result의 Generic Type에 따라 Map과 Value Object를 Excel의 sheet에 Row를 생성한다.


다음은 SQL File을 생성한다.

■ SQL 예제


<select id="selUser" parameterType="java.util.HashMap" resultType="java.util.LinkedHashMap">

SELECT

ID,

NAME,

PASSWORD,

DESCRIPTION,

USE_YN,

REG_USER

FROM TB_USER

WHERE 1 = 1

<if test="ID != null and ID != ''">

AND ID LIKE '%' || #{ID} || '%'

</if>

</select> 


resultType은 LinkedHashMap을 사용하고 있다. Excel File을 생성하는 경우 Column의 수선가 있기 때문에 LinkedHashMap을 사용하여 SQL 컬럼 순서와 일치한다.


SQL을 호출하기 위한 서비스를 작성한다.


■ Service 예제


@Override

public void excelUser(NexacroMapDTO dto, Workbook workbook) throws Exception {

String headerNames = "사용자 정보";

List<String> headerColumns = new ArrayList<String>();

headerColumns.add("사용자 ID");

headerColumns.add("사용자 명");

headerColumns.add("패스워크");

headerColumns.add("설명");

headerColumns.add("사용영부");

headerColumns.add("등록자 ID");

ExcelResultHandler<Map<String, Object>> handler = new ExcelResultHandler<Map<String, Object>>(workbook, headerNames, headerColumns);

getMapper("sbMapper").listToOutUsingResultHandler("user.selUser", handler);

 


Service에서 ExcelResultHandler 객체를 생성하며, SQL의 resultType이Map으로 설정하여 ExcelResultHandler의 GenericType을 Map으로하여 객체를 생성한다.


그리고 Mapper에 해당 SQL ID와 Handler를 넘겨준다.



다음은 Controller 예제이다.


■ Controller 예제

@RequestMapping(value = "/sample/excelResult.do")

public ModelAndView excelResult(NexacroMapDTO dto, HttpServletRequest request, HttpServletResponse response) throws Exception {

ModelAndView modelAndView = new ModelAndView();

Workbook workbook = ExcelUtil.createWorkbook(ExcelUtil.ExcelType.XSSF);

userService.excelUser(dto, workbook);

String fileName = "user.xls";

FileOutputStream fos = null;


try {

fos = new FileOutputStream(new File( PropertiesUtil.getString("file.upload.root.dir") + File.separator + fileName));

workbook.write(fos);

}

catch(Exception ex) {

LOGGER.error(ex.getMessage());

}

finally {

if (fos != null) fos.close();

if (workbook != null) workbook.close();

}

Map<String, Object> fileMap = new HashMap<String, Object>();

fileMap.put("FILE_NM", fileName);

fileMap.put("deleteYn", "Y");

modelAndView.setViewName("fileDownloadView");

modelAndView.addObject("FILE_INFO", fileMap);

return modelAndView;


Controller에서 Excel Workbook을 생성하여 서비스 호출 시 해당 Workbook을 파라미터를 전달한다.


서비스가 완료되면 workbook을 파일로 저장한 후 fileDownloadView를 이용하여 임시로 생성된 Excel File을 다운로드 한다.



AND

Spring framework에서는 기본적으로 Properties를 Runtime시에 변경된 설정을 자동으로 반영하는 기능을 제공하고 있지 않다.


Runtime 시 Properites 파일이 변경된 경우 변경된 내용을 읽어서 반여이 필요한 경우 별도로 개발이 필요하다.


먼저 Properties를 읽어서 저장한 후 프로그램에서 사용하기 위해 static으로 ReloadPropertiesUtil을 생성한다.


■ ReloadPropertiesUtil.java

import java.io.InputStream;

import java.util.List;

import java.util.Properties;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;


public class ReloadPropertiesUtil {

private static final Logger LOGGER = LoggerFactory.getLogger(ReloadPropertiesUtil.class);

private static Properties propertyMap = new Properties();

/** Properties file **/

private List<String> propertyFiles;

public void setPropertyFiles(List<String> propertyFiles) {

this.propertyFiles = propertyFiles;

}


/**

*<pre>

* 1.Description: Load Property file when the context is loaded

* 2.Biz Logic:

* 3.Author : LGCNS

*</pre>

* @throws Exception

*/

public void init() throws Exception {

generateProperties();

}

/**

*<pre>

* 1.Description: Reload Properties file

* 2.Biz Logic:

* 3.Author : LGCNS

*</pre>

* @throws Exception

*/

public void reload() throws Exception {

synchronized(this) {

propertyMap.clear();

generateProperties();

}

}

private void generateProperties() throws Exception {

for (String fileName : propertyFiles) {

InputStream fis = BeanUtil.getResoure(fileName);

if (fis == null) {

LOGGER.error("{} file does not exist", fileName);

continue;

}

propertyMap.load(fis);

}

if (LOGGER.isDebugEnabled()) {

propertyMap.list(System.out);

}

}

public static Object get(String key) {

return propertyMap.get(key);

}

public static String getString(String key) {

return propertyMap.get(key) != null ? String.valueOf(propertyMap.get(key)) : "";

}

public static int getInt(String key) {

return propertyMap.get(key) != null ? Integer.parseInt(String.valueOf(propertyMap.get(key))) : 0;

}

public static long getLong(String key) {

return propertyMap.get(key) != null ? Long.parseLong(String.valueOf(propertyMap.get(key))) : 0L;

}

}


ReloadProperties에서는  init과 reload 메소드에서 generateProperties 메소드를 호출하여 Propeties File을 읽어 load하는 기능을 수행한다.


init method는 Spring Framework의 Bean으로 설정하여  IoC Container에 로드될 때 호출되는 메소드이며, reload는 Properties 파일이 변경될 때 호출되는 메소드이다.


다음으로 FileAlterationObserver를 이용하여 Properties  파일의 디렉토리 내의 파일이 변경을 인식하여 서비스를 호출하는 Class를 생성한다.


■BaseFileObserver.java

import java.io.File;

import java.util.ArrayList;

import java.util.List;


import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;

import org.apache.commons.io.monitor.FileAlterationMonitor;

import org.apache.commons.io.monitor.FileAlterationObserver;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Required;

import org.springframework.util.ResourceUtils;


import BizException;

import FileService;

import PropertieFileServiceImpl;


public class BaseFileObserver {


private static final Logger LOGGER = LoggerFactory.getLogger(BaseFileObserver.class);

private List<String> directories;

/** File Check interval (unit : milliseconds**/

private int interval = 500;

/** Service when file or directory is modified **/

private FileService fileService;

@Required

public void setDirectories(List<String> directories) {

this.directories = directories;

}


public void setInterval(int interval) {

this.interval = interval;

}


@Required

public void setFileService(FileService fileService) {

this.fileService = fileService;

}


public BaseFileObserver() {

}


public BaseFileObserver(FileService fileService, List<String> directories) {

this.fileService = fileService;

this.directories = directories;

}


public void init() throws Exception {

for (String dir : directories) {

if (dir.startsWith( ResourceUtils.CLASSPATH_URL_PREFIX)) {

dir = ResourceUtils.getFile(dir).getAbsolutePath();

}

if (LOGGER.isDebugEnabled()) {

LOGGER.debug("File Observe path :: {}", dir);

}

FileAlterationObserver observer = new FileAlterationObserver(dir);

observer.addListener(new FileAlterationListenerAdaptor() {

@Override

public void onDirectoryCreate(File file) {

LOGGER.info("Directory is created : {}", file.getAbsolutePath());

System.out.println("Directory is created : " + file.getAbsolutePath());

try {

fileService.onDirectoryCreate(file);

}

catch(Exception ex) {

throw new BizException(ex.getMessage());

}

}

@Override

public void onDirectoryChange(File file) {

LOGGER.info("Directory is changed : {}", file.getAbsolutePath());

System.out.println("Directory is changed : " +  file.getAbsolutePath());

try {

fileService.onDirectoryChange(file);

}

catch(Exception ex) {

throw new BizException(ex.getMessage());

}

}

@Override

public void onDirectoryDelete(File file) {

LOGGER.info("Directory is deleted : {}", file.getAbsolutePath());

System.out.println("Directory is deleted : " +  file.getAbsolutePath());

try {

fileService.onDirectoryDelete(file);

}

catch(Exception ex) {

throw new BizException(ex.getMessage());

}

}

@Override

public void onFileCreate(File file) {

LOGGER.info("File is created : {}", file.getAbsolutePath());

System.out.println("File is created : " + file.getAbsolutePath());

try {

fileService.onFileCreate(file);

}

catch(Exception ex) {

throw new BizException(ex.getMessage());

}

}

@Override

public void onFileChange(File file) {

LOGGER.info("File is modified : {}", file.getAbsolutePath());

System.out.println("File is modified : " + file.getAbsolutePath());

try {

fileService.onFileChange(file);

}

catch(Exception ex) {

throw new BizException(ex.getMessage());

}

}

@Override

public void onFileDelete(File file) {

LOGGER.info("File is deleted : {}", file.getAbsolutePath());

System.out.println("File is deleted : " +  file.getAbsolutePath());

try {

fileService.onFileDelete(file);

}

catch(Exception ex) {

throw new BizException(ex.getMessage());

}

}

});

/** Create monitor to check file is changed **/

FileAlterationMonitor monitor = new FileAlterationMonitor(interval, observer);

try {

monitor.start();

}

catch(InterruptedException ie) {

monitor.stop();

}

}


BaseFileObserver는 Properties 파일이 생성, 변경, 삭제되었을 때 FileService의 해당 메소드를 호출한다.


■ PropertieFileServiceImpl.java

import java.io.File;


import org.springframework.beans.factory.annotation.Required;


import ReloadPropertiesUtil;

import FileService;


public class PropertieFileServiceImpl implements FileService {


private ReloadPropertiesUtil propertiesUtil;

@Required

public void setPropertiesUtil(ReloadPropertiesUtil propertiesUtil) {

this.propertiesUtil = propertiesUtil;

}


@Override

public void onDirectoryCreate(File file) throws Exception {

}


@Override

public void onDirectoryChange(File file) throws Exception {

}


@Override

public void onDirectoryDelete(File file) throws Exception {

}


@Override

public void onFileCreate(File file) throws Exception {

propertiesUtil.reload();

}


@Override

public void onFileChange(File file) throws Exception {

propertiesUtil.reload();

}


@Override

public void onFileDelete(File file) throws Exception {

propertiesUtil.reload();

}


}

 


PropertieFileServiceImpl Service는 파일 생성, 삭제, 변경이 발생하는 경우에 ReloadPropertiesUtil의 reload 메소드를 호출한다.



■ Spring Bean 설정

   <bean id="propertiesUtil" class="stis.framework.spring.ReloadPropertiesUtil" init-method="init">

  <property name="propertyFiles">

  <list>

  <value>properties/app.properties</value>

  <value>properties/system.properties</value>

  </list>

  </property>

  </bean>

 

  <bean id="propertyFileObserver" class="stis.framework.file.BaseFileObserver">

  <property name="directories">

  <list>

  <value>classpath:properties</value>

  </list>

  </property>

  <property name="interval" value="500" />

  <property name="fileService" ref="propertyFileService" />

  </bean>

 

  <bean id="propertyFileService" class="stis.framework.spring.service.impl.PropertieFileServiceImpl">

  <property name="propertiesUtil" ref="propertiesUtil" />

   </bean> 


propertiesUtil Bean에서는 properties 파일을  propertyFiles에 주입하여 Context가 로드될 때  init method를 호출하여 해당 파일을 읽도록 설정한다.


propertyFileObserver는 Property 파일이 있는 디렉토리를 directories 속성에 정의하였으며, 파일의 변경 체크 주기를 0.5초 간격으로 수행하도록 설정한다.


또한 propertyFileService에 propertiesUtil을 정의하여 해당 bean에 있는 reload 기능을 수행하도록 한다.


AND

Java 프로그램 개발 시 Reflection을 이용하여 Field의 값을 Map 등 다른 Object에 생성하고자 하는 경우 Java에서 제공하는 Reflection API, Apache commons-lang3 FieldUtils 사용, Spring Framework에서 제공하는 ReflectionUtils을 사용할 수 있다.


먼저 Apache commons-lang3에서  제공하는 FiledUtils 예제는 다음과 같다.


■ TestVo.java


public class TestVo {

private String name;

private int age;

private String password;


public String getName() {

return name;

}


public void setName(String name) {

this.name = name;

}


public int getAge() {

return age;

}


public void setAge(int age) {

this.age = age;

}


public String getPassword() {

return password;

}


public void setPassword(String password) {

this.password = password;

}


}

 



■ FieldUtils 예제


import org.apache.commons.lang3.reflect.FieldUtils;


public static void main(String[] args) {


    TestVo vo = new TestVo();

    vo.setAge(10);

    vo.setName("kkimdoy");

   vo.setPassword("aaaaa");


   /** age를 20으로 변경 **/

   FieldUtils.writeField(vo, "age", 20, true);

   Field[] fields = FieldUtils.getAllFields(vo.getClass());

   for (Field field : fields) {

  String name =field.getName();

Object object = FieldUtils.readField(vo, name, true);

LOGGER.debug("{} :: {}", name, object);

   }

}  

먼저 TestVo에 값을 Set한 후 FieldUtils.getAlFiled() 메소드를 이용하여 객체의 모든 Filed를 조회한다.


getAllField의 Parameter는 Class이므로 vo 객체를 class로 변환하여 파라미터로 사용한다.


FiledUtils.readField 메소를 이용하여 Field의 값을 추출할 수 있다. 파라미터 중 마지막 파라미터는 private 필드를 Access할 때 true 값을 파라미터로 전달한다.




■ ReflectionUtils 예제

import org.springframework.util.ReflectionUtils;

import org.springframework.util.ReflectionUtils.FieldCallback;


public static void main(String[] args) {

final TestVo vo = new TestVo();

vo.setAge(10);

vo.setName("kkimdoy");

vo.setPassword("aaaaa");


       /** Age를 30으로 변경 **/

        Field field = ReflectionUtils.findField(vo.getClass(), "age");

ReflectionUtil.makeAccessible(field);

ReflectionUtils.setField(field, vo, 30);


ReflectionUtils.doWithFields(vo.getClass(), new FieldCallback() {


@Override

public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {

ReflectionUtil.makeAccessible(field);

Object value = ReflectionUtils.getField(field, vo);

LOGGER.debug("{} :: {}", field.getName(), value);

}

});

}

 


Spring framework에서 제공하는 ReflectionUtils를 사용하는 경우에는 doWithFields 메소드의 FileldCallback 함수를 재정의하여 사용한다.


private 필드에 대해 access를 허용하기 위해서 makeAccessilbe 메소드에 Filed를 파라미터로 전달한다.


Field의 값을 읽어오기 위해 getFiled 함수를 사용하며, 첫번째 파라미터는 Filed, 두번째 파라미터에 Value Object 객체를 넘겨준다.


'Java' 카테고리의 다른 글

Java에서 Shell호출 방법  (0) 2018.07.05
JVM GC 설정  (0) 2018.06.07
JSP의 img, script에 jsessionid가 추가되는 경우  (0) 2018.02.22
Html a tag function call  (0) 2018.02.20
CDI를 이용한 Java 개발  (0) 2018.02.13
AND

Mybatis에서 Paging 처리 등 공통적으로 사용되는 SQL을 작성한 후 SQL File에서 Include하여 사용하면 개발의 생산성을 향상할 수 있다.


시스템에서 공통적으로 사용하는 SQL을 /resources/sqlmap 폴더에 common 폴더를 생성한 후 commonSQL.xml 파일을 생성한다.


■ commonSQL.xml

<?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="common.sql">


<sql id="pageHeader">

SELECT *

  FROM (

    SELECT ROWNUM AS RN

   ,INNER_SQL.*

  FROM (

</sql>

<sql id="pageFooter">

<![CDATA[

                 ) INNER_SQL

           WHERE ROWNUM <= ( #{curPage} * #{pageSize} )

      )

    WHERE RN > ( ( #{curPage} -1 ) * #{pageSize} )

]]>

</sql>


<sql id="batchpageOffset">

<![CDATA[

    OFFSET ( #{_page} * #{_pagesize} ) ROWS FETCH NEXT #{_pagesize} ROWS ONLY

]]>

</sql>


       <!-- Paging Oracle 12C 이상 -->

<sql id="onlinepageOffset">

<![CDATA[

    OFFSET ( ( #{curPage} -1 ) * #{pageSize} ) ROWS FETCH NEXT #{pageSize} ROWS ONLY

]]>

</sql>


</mapper>


SQL 파일 작성이 완료되면 실제 업무 SQL에서 include를 이용하여 해당 sql을 사용할 수 있다.



■ 적용 예

<select id="selAPUserListPopup" parameterType="java.util.HashMap" resultType="egovMap">

SELECT /* selAPUserListPopup */ 

       A.USER_ID

      ,A.ORG_CD

FROM   AP_USER A

    WHERE  1 = 1

    <if test = 'userId != null and userId != ""'>

AND    A.USER_ID = #{userId, jdbcType=VARCHAR}

</if>

    ORDER BY UP_DT DESC

<include refid="common.sql.pageOffset"/>

</select> 


해당 SQL에 include refid(Reference Id)를 commonSQL의 namespace이름과 sql id를 사용하여 Include하면 SQL 실행 시 해당 파일이 Include되어 실행된다.


AND

ARTICLE CATEGORY

분류 전체보기 (56)
Spring Framrwork (33)
Linux (1)
APM (1)
Java (8)
python (0)
ant (1)
chart (1)
OS (1)
tomcat (1)
apache (1)
database (0)

RECENT ARTICLE

RECENT COMMENT

CALENDAR

«   2024/10   »
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

ARCHIVE

LINK