반응형

오늘은 프로젝트에서 적용한 일괄등록에서 사용한 @Async를 포스팅해볼게요.

월래는 Batch를 이용하여 새벽시간에 동작을 하도록 적용을 하였는데 @Async를 이용하여 빠르게 선처리 작업을 하고 이후 Async메서드를 호출하여 비동기 방식으로 작동하도록 구현을 하였습니다.

@Configuration
@EnableScheduling
@EnableAsync
public class AsyncThreadConfiguration {

	@Bean(name = "asyncExecutor")
	public Executor asyncThreadTaskExecutor() {
		ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
		threadPoolTaskExecutor.setCorePoolSize(2); //최초생성수
		threadPoolTaskExecutor.setMaxPoolSize(4); //max생성수
		threadPoolTaskExecutor.setThreadNamePrefix("fabs-batch-pool");

		return threadPoolTaskExecutor;
	}

	public class HandlingExecutor implements AsyncTaskExecutor {
		private final AsyncTaskExecutor executor;

		public HandlingExecutor(AsyncTaskExecutor executor) {
			this.executor = executor;
		}

		@Override
		public void execute(Runnable task) {
			executor.execute(createWrappedRunnable(task));
		}

		@Override
		public void execute(Runnable task, long startTimeout) {
			executor.execute(createWrappedRunnable(task), startTimeout);
		}

		@Override
		public Future<?> submit(Runnable task) {
			return executor.submit(createWrappedRunnable(task));
		}

		@Override
		public <T> Future<T> submit(final Callable<T> task) {
			return executor.submit(createCallable(task));
		}

		private <T> Callable<T> createCallable(final Callable<T> task) {
			return () -> {
				try {
					return task.call();
				} catch (Exception ex) {
					handle(ex);
					throw ex;
				}
			};
		}

		private Runnable createWrappedRunnable(final Runnable task) {
			return () -> {
				try {
					task.run();
				} catch (Exception ex) {
					handle(ex);
				}
			};
		}

		private void handle(Exception ex) {
			log.info("Failed to execute task. : {}", ex.getMessage());
			log.error("Failed to execute task. ", ex);
		}

		public void destroy() {
			if (executor instanceof ThreadPoolTaskExecutor) {
				((ThreadPoolTaskExecutor) executor).shutdown();
			}
		}
	}

먼저 ThreadPool설정입니다. 

setCorePoolSize 최초 생성 시 사용되는 Thread 수입니다.

setMaxPoolSize 생성할 수 있는 Thread Max값입니다. 

setThreadNamePrefix ThreadPool이 여러 곳에서 사용이 되면 알아볼 수 있도록 Name을 지정합니다.

@Bean(name = "asyncExecutor") <- "asyncExecutor"이름으로 비동기 호출을 사용할 수 있습니다.

//Controller Class
@RequestMapping(value = "/excelUpload", method = RequestMethod.POST)
public ResponseEntity<HttpResponse<?>> excelUpload(MultipartHttpServletRequest req, HttpServletResponse res,
    @AuthenticationPrincipal UserAuthentication userAuthentication) throws Exception {
    
    //비동기 Method호출
    Service.add(reqFundManagement);
    
    //비동기 Method 실행이 다 완료 안되더라도 이후 코드 실행
    
}

//Service Class

@Async("asyncExecutor")
public void add(ReqFundManagement reqFundManagement) {
    // 필요한 소스구현
}

기존 방식은

1) Excel업로드

2) 대상자 추출(LIST)

3) 대상자 등록

4) 대상자 API를 이용해 데이터 가져오기

대상자가 많을수록 3->4번이 대기 시간들이 길어져서 Batch로 만들려고 고민 중에 비동기 호출 방식 확인

비동기

1) Excel업로드

2) 대상자 추출(LIST)

3) 대상자 등록

4) 대상자 API를 이용해 데이터 가져오기

호출 순서는 같은데 3->4번 실행 후 4번 응답 대기 없이 바로 3->4가 반복을 하여 3번 대상자 먼저 등록이 되고 4번은  비동기로 작동이 돼서 여러 건을 동시에 가져와서 호출 시간이 절약이 되었어요.

 

반응형

'개발 > Java' 카테고리의 다른 글

Java Excel Upload  (0) 2021.02.25
이클립스 Formatter off  (0) 2021.02.16
Java 단방향,양방향 암호화  (1) 2021.01.25
Spring RestTemplate Converter오류  (0) 2021.01.14
List 중복 제거 및 List 정렬(Collections.sort)  (1) 2021.01.04
반응형

Java에서 excel 파일을 읽을 때 대부분 apache-poi lib를 사용을 할 거예요 저도 poi를 사용하여 구현을 했어요.

compile group: 'org.apache.poi', name: 'poi', version: '4.1.2'
compile group: 'org.apache.poi', name: 'poi-ooxml', version: '4.1.2'

4.1.2 버전을 사용을 했어요.

보통 2003,통합문서 2가지 버전들이 있는데 org.apache.poi.ss.usermodel.WorkbookFactory를 이용하면 포맷 구분 없이 읽을 수 있어요.

 

예를 들어 위 해당하는 문서를 읽을경우를 해볼게요.

//엑셀파일을 넣구 Wookbook를 생성합니다
Workbook workbook = WorkbookFactory.create(file1.getInputStream());

int rowindex = 0;
int cellindex = 0;
//시트 수 (첫번째에만 존재하므로 0을 준다)
//만약 각 시트를 읽기위해서는 FOR문을 한번더 돌려준다
Sheet sheet = workbook.getSheetAt(0);
//행의 수
int rows = sheet.getPhysicalNumberOfRows();
//3번째 row부터 읽기위해 rowindex는 2부터
for (rowindex = 2; rowindex < rows; rowindex++) {
	//행을읽는다
	Row row = sheet.getRow(rowindex);
	
    //row값이 있으면
	if (row != null) {
			//B3부터 데이터 추출
        	//엑셀에 데이터 형식에 따라 get방식이 달르게 가져와야해요 전 전부 일반으로 설정을하고 가져오고 있어요        
        	for (cellindex = 1; cellindex < row.getPhysicalNumberOfCells(); cellindex++) {

			System.out.println(getStringValue(row.getCell(cellindex)));

		}
        
	}
            
}


public static String getStringValue(Cell cell) {
	String rtnValue = "";
	try {
		rtnValue = cell.getStringCellValue();
	} catch (IllegalStateException e) {
		rtnValue = Integer.toString((int) cell.getNumericCellValue());
	} catch (NullPointerException e) {
		rtnValue = "";
	}

	return rtnValue;
}

cell이 늘어남에 따라 row.getCell(1) 2,3,4.... 이렇게 계속할 수 없어 for문을 사용했어요.

 

그럼 B3~F3까지 데이터를 와서 sysout으로 출력을 하고 있어요. 엑셀에 데이터 방식에 따라 cell처리 방식들이 다른데 저는 전부 텍스트 형식으로 지정하고 업로드를 했어요

 

반응형
반응형

이클립스로 개발 중에 save action에 지정된 formatter방식으로 저장이 되는데 간혹 소스가 길어져서 가독성이 안 좋게 저장이 되면 그 라인은 formatter가 적용 안되게 off 할 수가 있습니다.

	public static enum ProgressStatus_new {

		RECEPTION(238, "접수완료", "241"), VERIFICATION(239, "현장검증완료", "VERIFICATION"), PRIMARY(240, "읍면동 최종확인",
		    "PRIMARY"), COMPLETE(241, "시군 최종확인", "COMPLETE"), END(242, "지급완료", "END"), REJECT_RECEPTION(243, "접수완료 반려",
		        "REJECT_RECEPTION"), REJECT_VERIFICATION(244, "현장검증 완료 반려", "REJECT_VERIFICATION"), REJECT_PRIMARY(245,
		            "읍면동 최종확인 반려", "REJECT_PRIMARY"), REJECT_COMPLETE(246, "시군 최종확인 반려", "REJECT_COMPLETE"), DENY_RECEPTION(
		                247, "접수완료 부정수급", "DEMY_RECEPTION"), DENY_VERIFICATION(248, "현장검증 완료 부정수급",
		                    "DENY_VERIFICATION"), DENY_PRIMARY(249, "읍면동 최종확인 부정수급", "DENY_PRIMARY"), DENY_COMPLETE(250,
		                        "시군최종확인 부정수급", "DENY_COMPLETE"), UNDEFINED(-1 - 199, "정의되지않음", "UNDEFINED");

		@Getter
		private Integer id;

		@Getter
		private String nm;

		@Getter
		private String alias;

		private ProgressStatus_new(Integer id, String nm, String alias) {
			this.id = id;
			this.nm = nm;
			this.alias = alias;
		}

		public static ProgressStatus_new get(Integer id) {
			for (ProgressStatus_new o : ProgressStatus_new.values()) {
				if (o.id.intValue() == id.intValue()) {
					return o;
				}
			}

			return UNDEFINED;
		}

		public static ProgressStatus_new get(String alias) {
			for (ProgressStatus_new o : ProgressStatus_new.values()) {
				if (o.alias.equals(alias)) {
					return o;
				}
			}

			return UNDEFINED;
		}

	}

적용된 formatter가 적용된 부분인데 선언 부분이 가독성이 너무 떨어져서

//@formatter:off
	public static enum ProgressStatus_new {

		RECEPTION(238, "접수완료", "241"),
		VERIFICATION(239, "현장검증완료", "VERIFICATION"),
		PRIMARY(240, "읍면동 최종확인","PRIMARY"),
		COMPLETE(241, "시군 최종확인", "COMPLETE"),
		END(242, "지급완료", "END"),
		REJECT_RECEPTION(243, "접수완료 반려","REJECT_RECEPTION"),
		REJECT_VERIFICATION(244, "현장검증 완료 반려", "REJECT_VERIFICATION"),
		REJECT_PRIMARY(245,"읍면동 최종확인 반려", "REJECT_PRIMARY"),
		REJECT_COMPLETE(246, "시군 최종확인 반려", "REJECT_COMPLETE"),
		DENY_RECEPTION(247, "접수완료 부정수급", "DEMY_RECEPTION"),
		DENY_VERIFICATION(248, "현장검증 완료 부정수급","DENY_VERIFICATION"),
		DENY_PRIMARY(249, "읍면동 최종확인 부정수급", "DENY_PRIMARY"),
		DENY_COMPLETE(250,"시군최종확인 부정수급", "DENY_COMPLETE"), UNDEFINED(-1 - 199, "정의되지않음", "UNDEFINED");

		@Getter
		private Integer id;

		@Getter
		private String nm;

		@Getter
		private String alias;

		private ProgressStatus_new(Integer id, String nm, String alias) {
			this.id = id;
			this.nm = nm;
			this.alias = alias;
		}

		public static ProgressStatus_new get(Integer id) {
			for (ProgressStatus_new o : ProgressStatus_new.values()) {
				if (o.id.intValue() == id.intValue()) {
					return o;
				}
			}

			return UNDEFINED;
		}

		public static ProgressStatus_new get(String alias) {
			for (ProgressStatus_new o : ProgressStatus_new.values()) {
				if (o.alias.equals(alias)) {
					return o;
				}
			}

			return UNDEFINED;
		}

	}
	//@formatter:on

보기 좋게 적용을 했어요. 기본적으로 off가 되어있는데

Window -> Preferences -> Java -> Code Style -> Formatter

사용 중인 formatter에서 edit를 누르시면

enable off가 되어있을겅에요. 이걸 활성화시키고 off/on 태그를 소스에 입력을 하시면 돼요. 만약 여러 명이 같이 하는 프로젝트일 경우 모든 사용자가 동일하게 기능 활성화를 해야 해요.

반응형

'개발 > Java' 카테고리의 다른 글

Spring @Async(비동기 Method) 사용하기  (0) 2021.03.18
Java Excel Upload  (0) 2021.02.25
Java 단방향,양방향 암호화  (1) 2021.01.25
Spring RestTemplate Converter오류  (0) 2021.01.14
List 중복 제거 및 List 정렬(Collections.sort)  (1) 2021.01.04
반응형

이번에 사용자 패스워드에 암호화 적용을 하여 포스팅하게 되었어요.

패스워드는 보통 단방향을 사용해서 복호화가 안되게 되어야 하고 암호화된 문자들끼리 비교를 하여 패스워드가 맞는지 판별을 하게 돼요. 그 외 암호화, 복호화가 필요한 부분도 있어 양방향 암호화도 만들었어요.

	public static String encryptSha256(String value) {

		MessageDigest md;
		StringBuffer sb = new StringBuffer("");

		try {

			md = MessageDigest.getInstance("SHA-256");
			md.update(value.getBytes());
			byte byteData[] = md.digest();

			for (byte tmpStrByte : byteData) {
				String tmpEncTxt = Integer.toString((tmpStrByte & 0xff) + 0x100, 16).substring(1);

				sb.append(tmpEncTxt);
			}
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return sb.toString();

	}

	public static String encryptAes(String str, String key) throws Exception {

		Cipher cipher = Cipher.getInstance("AES");

		SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");

		cipher.init(Cipher.ENCRYPT_MODE, secretKey);

		byte[] encPassword = cipher.doFinal(str.getBytes("UTF-8"));

		String result = Base64.getEncoder().encodeToString(encPassword);

		return result;
	}

	/*
	* password = AES 방식으로 암호화된 암호문
	* key = 암호화시 사용했던 키워드
	*/
	public static String decryptAes(String str, String key) throws Exception {

		Cipher cipher = Cipher.getInstance("AES");
		SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");

		cipher.init(Cipher.DECRYPT_MODE, secretKey);

		byte[] decPassword = cipher.doFinal(Base64.getDecoder().decode(str));
		String result = new String(decPassword, "UTF-8");

		return result;
	}

단방향 암호화는 SHA-256 방식으로 했어요. 양방향 암호화는 AES 방식으로 사용을 하였는 데 사용되는 key는 값이 16, 24, 32 Byte로 작성이 되어야 해요. 그 외 byte에 안 맞는 키값이 입력이 되면 Invalid AES key length에러가 발생을 하게되므로 반드시 byte길이를 맞춰야해요.

반응형
반응형

외부기관 API를 통해서 데이터를 주고받고 하는 부분이 있어 RestTemplate을 사용하는 중에 발생한 에러 포스팅해볼게요.

public ResponseEntity<T2> post(String endpointUrl, T1 body, Class<T2> responseType, HeadersStrategy strategy) {
	HttpEntity<T1> httpEntity = new HttpEntity<>(body, headers(HttpMethod.POST, strategy));

	ResponseEntity<T2> response = restTemplate.postForEntity(endpointUrl, httpEntity, responseType);

	return response;
}

body부분에 VO class를 전달하고 post로 발송하니 서버 쪽에서는 parameter값이 없다 하여 확인을 했어요.

private static boolean romePresent =
			ClassUtils.isPresent("com.rometools.rome.feed.WireFeed",
					RestTemplate.class.getClassLoader());

private static final boolean jaxb2Present =
			ClassUtils.isPresent("javax.xml.bind.Binder",
					RestTemplate.class.getClassLoader());

private static final boolean jackson2Present =
			ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
					RestTemplate.class.getClassLoader()) &&
			ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
					RestTemplate.class.getClassLoader());

private static final boolean jackson2XmlPresent =
			ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper",
					RestTemplate.class.getClassLoader());

private static final boolean gsonPresent =
			ClassUtils.isPresent("com.google.gson.Gson",
					RestTemplate.class.getClassLoader());

실제 RestTemplate 소스 부분이에요. 기본적인 Converter들이에요 일반적인 json방식은 jackson으로 변환이 돼요.

public RestTemplate() {
	this.messageConverters.add(new ByteArrayHttpMessageConverter());
	this.messageConverters.add(new StringHttpMessageConverter());
	this.messageConverters.add(new ResourceHttpMessageConverter());
	this.messageConverters.add(new SourceHttpMessageConverter<Source>());
	this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());

	if (romePresent) {
		this.messageConverters.add(new AtomFeedHttpMessageConverter());
		this.messageConverters.add(new RssChannelHttpMessageConverter());
	}

	if (jackson2XmlPresent) {
		this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
	}
	else if (jaxb2Present) {
		this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
	}

	if (jackson2Present) {
		this.messageConverters.add(new MappingJackson2HttpMessageConverter());
	}
	else if (gsonPresent) {
		this.messageConverters.add(new GsonHttpMessageConverter());
	}
}

생성자 소스 부분인데 여기에서 생성할 때 기본적인 Converter를 등록해요.

for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
	if (messageConverter instanceof GenericHttpMessageConverter) {
		GenericHttpMessageConverter<Object> genericMessageConverter = (GenericHttpMessageConverter<Object>) messageConverter;
			if (genericMessageConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) {
					if (!requestHeaders.isEmpty()) {
						for (Map.Entry<String, List<String>> entry : requestHeaders.entrySet()) {
								httpHeaders.put(entry.getKey(), new LinkedList<String>(entry.getValue()));
						}
					}
					if (logger.isDebugEnabled()) {
						if (requestContentType != null) {
							logger.debug("Writing [" + requestBody + "] as \"" + requestContentType +
										"\" using [" + messageConverter + "]");
							}
							else {
								logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]");
							}

						}
						genericMessageConverter.write(
								requestBody, requestBodyType, requestContentType, httpRequest);
						return;
						}
					}
					else if (messageConverter.canWrite(requestBodyClass, requestContentType)) {
						if (!requestHeaders.isEmpty()) {
							for (Map.Entry<String, List<String>> entry : requestHeaders.entrySet()) {
								httpHeaders.put(entry.getKey(), new LinkedList<String>(entry.getValue()));
							}
						}
						if (logger.isDebugEnabled()) {
							if (requestContentType != null) {
								logger.debug("Writing [" + requestBody + "] as \"" + requestContentType +
										"\" using [" + messageConverter + "]");
							}
							else {
								logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]");
							}

						}
						((HttpMessageConverter<Object>) messageConverter).write(
								requestBody, requestContentType, httpRequest);
						return;
					}
				}

RestTemplate에서 execute실행 전 소스인데 전달받은 body가 해당 converter에 해당되는지 판별하고 값을 write를 하고 있습니다. 저기에 해당이 안되면 body가 없이 전달이 되기 때문에 생성할 때 convert를 전달하거나 기본 내장된 convert를 사용해야 해요. 그래서 사용한 게 Spring에서 제공하는 MultiValueMap이에요. 해당 부분은 MAP처럼 parameter를 put을해서 사용하시면 돼요.

MultiValueMap<String, Object> mmap = new LinkedMultiValueMap<>();

 

 

사용 중에 값은 전달이 되는데 빨간 부분처럼 header에 "multipart/form-data"가 추가되어 API 서버에서 parameter를 못 받는 현상이 발견되어서 확인 중에VO변수 중에 Integer를 사용하는 변수들이 있는데 Integer -> String로 변경 후 실행하니 multipart가 add가 안되었습니다. 왜 Integer를 사용하면 붙는지는 소스를 더 확인을 안 하여 그냥 String을 사용하기로 하였습니다~~~ 

HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setConnectTimeout(HTTP_CONNECT_TIMEOUT);
requestFactory.setReadTimeout(HTTP_READ_TIMEOUT);
farmLandInfoReqRequestTemplatProvider.get().setRequestFactory(requestFactory);

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

MultiValueMap<String, Object> mmap = new LinkedMultiValueMap<>();
try {

  map = BeanConverter.toMap(request);
  mmap.setAll(map);

} catch (Exception e) {

}


return farmLandInfoReqRequestTemplatProvider.get().post(url, mmap, BaseReqResponse.class);

최종적으로 body부분을 MultivalueMap으로 변경하여 사용하였습니다.

 

반응형
반응형

List에서 특정값이 중복되는 Model을 제거하고 frlndSn을 값으로 정렬을 해야 하는 상황이 발생하여 나름 중복 제거 및 List 정렬한 소스 포스팅해볼게요.

 

중복 제거가 여러 가지 방법이 있겠지만 전 MAP을 이용했어요. key가 중복되면 마지막 값으로 계속 덮어버리니 마지막 남은 값만 유지가 되어 중복되는 key가 날아가도록 했어요.

List를 담을 때 frlndSn(일련번호)를 주어 순서대로 담았는데 Map을 거치면서 이 순서가 섞이는 거 같아요 List를 출력하니 add 한 순서대로 안 나오네요. 그래서 정렬도 했어요.

  for (ReqFundLivestockInfo reqFundLivestockInfo : reqFundBzobInfo.getReqFundLivestockInfoList()) {

      //같은 Value일경우 덮어버리기
      if (reqFundLivestockInfoMap.get(reqFundLivestockInfo.getValue()) != null) {

          ReqFundLivestockInfo tmpReqFundLivestockInfo = reqFundLivestockInfoMap.get(reqFundLivestockInfo.getValue());

          reqFundLivestockInfoMap.put(reqFundLivestockInfo.getValue(),reqFundLivestockInfo);
          
      } else {
      
          reqFundLivestockInfoMap.put(reqFundLivestockInfo.getValue(),reqFundLivestockInfo);         

      }

  }
	
    //map을 list로 변환
	ArrayList<ReqFundLivestockInfo> sortList = (ArrayList<ReqFundLivestockInfo>) reqFundLivestockInfoMap
							    .values().stream().collect(Collectors.toList());

	//일련번호 asc정렬
	Collections.sort(sortList, (arg0, arg1) -> {
    
		return arg0.getFrlndSn().compareTo(arg1.getFrlndSn());

	});

위 코드 실행결과 Value값이 중복된 Model은 Map에서 가장 마지막 Value만 남아있고 Map을 List로 변환 시 기존 List정렬순서가 섞이므로(왜 index순번이 바뀌는지는 모르겠습니다) sort를 이용하여 정렬을 다시 하였습니다.

예전 배울 때는 Bubble Sort방식으로 했었는데 간단하게 구현이 되네요.

Collections.sort말고 Arrays.sort도 있는데 이거는 배열 정렬에 사용되는거라 Collections.sort을 사용했어요

반응형
반응형

진행 중인 프로젝트에서 Model객체 안에 값들을 비교하여 다른 값을 보여줘야 하는 상황이 나와서 검색 중에 Javers라는 패키지를 사용하여 쉽게 적용을 하여 포스팅을 해보려고 해요

javers.org/

 

JaVers — Object auditing and diff framework for Java

With JaVers you can forget about troublesome data versioning. Let the changes in your data be managed by JaVers.

javers.org

여기를 들어가서 찾았어요.

mvnrepository.com/artifact/org.javers/javers-core

 

Maven Repository: org.javers » javers-core

JaVers - object auditing and diff framework for Java VersionRepositoryUsagesDate5.14.x5.14.0Central4Nov, 20205.13.x5.13.2Central4Oct, 20205.13.1Central3Oct, 20205.13.0Central3Oct, 20205.12.x5.12.0Central3Sep, 20205.11.x5.11.2Central3Aug, 20205.11.1Central3

mvnrepository.com

maven을 통해서 jar를 받을 수가 있어요.

 

Javers javers = JaversBuilder.javers().build();

Diff diff = javers.compare(Model1, Model2);
System.out.println(diff);
HashMap<String, Object> reqFundMapperHistMap = objectMapper.readValue(javers.getJsonConverter().toJson(reqFundHisttDiff), HashMap.class);

위 diff를 찍어보면

- 'areaCode' value changed from '' to '222'
- 'corporateName' value changed from '20201123113139' to '20201123111107'
- 'farmingWorkPeriodYy' value changed from '9' to '11'
- 'frmerSn' value changed from '' to '111'
- 'histId' value changed from '1015' to '1014'
- 'juminNo' value changed from '' to '31312312312'
- 'lagHistId' value changed from '1014' to ''
- 'modDtm' value changed from '' to '12312312313'
- 'modReason' value changed from '128' to ''
- 'regDtm' value changed from '20201123113139' to '20201123111107'
- 'status' value changed from '' to '149'
- 'userName' value changed from 'admin' to ''

변경된 값만 나오고 있어요 변수명 - value1 to value2 이런 식으로 요 이걸 다시 java에서 바로 사용을 하려고 JSON으로 변환하여 MAP으로 담았어요. List를 넘겨서 비교를 해봤는데 List는 안되는 거 같네요. 가끔 프로젝트하면서 비교할 일이 생기는데 그때 편하게 사용할 수 있는 패키지인 거 같아요.

반응형
반응형

요즘 전자정부프레임워크를 사용하는데... 몇 번 사용은 해보지만 적응이 잘 안되네요.

이번에 3.9버전을 사용하게 되었어요.

전자정부프레임워크 버전확인

 

표준프레임워크 버전별 구성 - eGovFrame Portal 온라인 지원 포털

> 표준프레임워크 소개 > 구성 > 버전별 구성 버전별 구성 버전별 구성 표준프레임워크 3.9 구성 표준프레임워크 3.9은 다음과 같은 변경을 통해 '20년 2월 공개되었습니다. 실행환경 적용 오픈소스

www.egovframe.go.kr

링크 보시면 버전별로 각 jar버전이 표시가 되었어요.

 

<resultMap type="user" id="getUserResultMap" autoMapping="true">
        <association property="attachments" column="PROFILE_FILE_ID" select="com.cms.mapper.com.AttachmentsMapper.findById"></association>
</resultMap>

 

예전 소스를 참고하면서 개발 중에 Mybatis association사용 중에 org.apache.ibatis.javassist.util.proxy.SecurityActions.setAccessible 에러가 발생하여 이것저것 찾아보는중에 mybatis버전을 올려보라는 글을 찾게되어서 버전을 올렸더니 에러없이 동작이 되었어요. 전자정부에서는 3.4.6이 최신인데 3.5.1로 올려서 사용하니 에러가 해결이 되었어요.

혹시 association 사용중에 위처럼 에러가 발생하면 버전업을 한번 해보세요.

반응형
반응형
SELECT * FROM DUAL
WHERE GUBUN = 'A'
AND CATEGORY IN (1,2,3)

현재 프로젝트에서 JPA를 사용하고 있어서 몇몇 기능을 추가하면서 JAP를 사용해봤습니다.

기존에는 ExampleMatcher를 사용하여 단순한 where을 만들고 있었는데 IN을 추가하려고 하니 방법을 못 찾아서 Specifications를 사용하게 되었어요 혹시라도 ExampleMatcher를 사용해 구현하신 분.. 댓글로 알려주시면 감사하겠습니다.

ExampleMatcher.matching().withIgnoreNullValues().withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);

기존에는 단순히 title,name등 일치하는 거만 사용을 하여 ExampleMatcher 적용을 한거같아요.

SELECT * FROM DUAL
WHERE CATEGORY IN (1,2)
AND TITLE = 'A'

이런 결과를 을 얻으려고 Specifications를 사용하게 되었어요.

public static Specification<Document> category(String... categorys) {
		
	return new Specification<Document>() {

		@Override
		public Predicate toPredicate(Root<Document> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            	
			return root.get("categoryCd").in(categorys);

		}

	};
        
}
	
public static Specification<Document> fieldEquals(String searchField,String searchData) {
		
	return new Specification<Document>() {

		@Override
		public Predicate toPredicate(Root<Document> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            	
			return cb.equal(root.get(searchField), searchData);

		}

	};
        
}
	
public static Specification<Document> fieldLike(String searchField,String searchData) {
		
	return new Specification<Document>() {

		@Override
		public Predicate toPredicate(Root<Document> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            	
			return cb.like(root.get(searchField), "%" + searchData + "%");

		}

	};
        
}

category는 배열로 받아서 in을 사용하게 하였어요.

fieldEquals는 단순히 = 비교반 하기 위해 만들었어요 Column = Value로 실행하게 하였어요.

지금은 사용하지는 않지만 like절도 만들었어요 Column like '% value%'로 실행하게 하였어요.

사용하는 Service에서는

Specifications<Document> spec = Specifications.where(DocumentWhere.category(documentCategoryCds));
spec.and(DocumentWhere.fieldLike("title", document.getTitle()));

이런 식으로 where 첫 문장을 만들고 and로 title = value를 만들어서 findAll로 실행을 하였어요.

실행 로그로 위 SQL처럼 원하는 방식으로 실행이 되었어요. 위 Method처럼 원하는 게 있으면 구현을 하여 직접 적용을 해야 할 거 같아요. 사용하면서 아직 적응이 안되어서 Mybatis보다는 어렵네요.... Insert, Update에서는 간단히 사용이 가능한 거 같은데 복잡한 Query일 경우 @Query를 사용하여 결과를 가져오는 것도 나쁘지 않은 거 같아요

반응형
반응형

현재 프로젝트에서 외부 연결을 SOAP을 이용하여 가져와야 해서 프로젝트에 적용한 ksoap2 적용 모듈을 작성해볼게요.

 

	Map<String,String> map = new HashMap<String,String>();
		
	String SOAP_ACTION_WS_USP_MB_GET_TERM_MISU_SAB  = ""; //SOAP 액션
	String METHOD_NAME_WS_USP_MB_GET_TERM_MISU_SAB  = "";  //메소드 네임
	        
	String NAMESPACE = "http://www.unierp.com/";    
	        
	try {
		SoapObject soapReq = new SoapObject(NAMESPACE, METHOD_NAME_WS_USP_MB_GET_TERM_MISU_SAB);
			 
		soapReq.addProperty("INDEX","1");	//필요한 파라미터
		soapReq.addProperty("HP_TEL", hpTel);
			
		//Soap 버전및 .donet 지원여부 
		SoapSerializationEnvelope soapEnvelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);        
		soapEnvelope.dotNet = true;
		soapEnvelope.setOutputSoapObject(soapReq);
		        
		HttpTransportSE aht = new HttpTransportSE(erpURL);	//soap URL
		        
		aht.call(SOAP_ACTION_WS_USP_MB_GET_TERM_MISU_SAB, soapEnvelope); 

		SoapObject result  = (SoapObject) soapEnvelope.bodyIn;		//각 넘어온 xml구조에 맞게 property를 가져오시면 되요
		SoapObject nameResult = (SoapObject) result.getProperty(0);  
		SoapObject nameResult2 = (SoapObject) nameResult.getProperty(1);  
		SoapObject nameResult3 = (SoapObject) nameResult2.getProperty(0);          
		int count = nameResult3.getPropertyCount();
	        	
		for (int i = 0; i <= count - 1; i++) { 

			SoapObject simpleSuggestion = (SoapObject) nameResult3.getProperty(i);		    	
			map.put("C_YN", simpleSuggestion.getProperty("C_YN").toString().trim());
			map.put("C_MSG", simpleSuggestion.getProperty("C_MSG").toString().trim());
		}
		        
	} catch (Exception e) {
		e.printStackTrace();
	}

적용을 하면서 한 가지 의문이 생겼어요. obj.getProperty("C_MSG") 보통 없는 값이면 NULL 또는 ""빈 값을 리턴을 하는데 RuntimeException에러가 발생하여 SoapObject.java 소스를 봤어요.

public String getPropertyAsString(String name) {
	Integer index = propertyIndex(name);
	if (index != null) {
		return getProperty(index.intValue()).toString();
	} else {
		throw new RuntimeException("illegal property: " + name);
	}
}

이렇게 throw를 발생을 하고 있네요. 이 부분을 수정하여 프로젝트에 적용을 하면 되는데 그냥

//이렇게 get하기전에 count를 체크하여 가져오도록 했어요
if ( changeReason.getPropertyCount() > 0) {

}

원본 소스 링크

 

simpligility/ksoap2-android

ksoap2-android - SOAP support for Android. Contribute to simpligility/ksoap2-android development by creating an account on GitHub.

github.com

416라인을 보시면 돼요.

반응형

+ Recent posts