반응형

프로젝트 진행도중 특정 테이블에 특정 구분자값으로 구분된 문자열을 통으로 넣어줄테니 알아서 파싱하여 쓰세요같은 불친절한 일이 발생했습니다.

 

처음에 처리방법으로 생각한건 java에서 일정시간마다 스케줄러가 데이터를 수집하고 해당데이터들을 구문별로 나누고 테이블에 insert해주는 방법을 생각했지만, 데이터가 꼬일 우려가 있어 Flag처리를 통한 방법... 또는 스케줄러 동작이 끝나기전에 또 스케줄러가 돌아야 하는 경우가 발생하거나 등등... 여러 문제가 예상되었습니다....

 

아이디어 회의를 통해 해결 방법으로 제시된게 문자열을 넣어주면 바로 mysql trigger를 사용해보자였고, 예전에 pg에서 사용해본 기억이 있어서 mysql에서 진행해보았습니다.

 

총에서 방아쇠를 당기면 총알이 날라가듯 여기서 trigger란 특정 테이블에 어떤 행동 이벤트가 발생하면 발동하는 것을 말합니다.

 

여기서 이벤트로는 특정 테이블에 INSERT, UPDATE, DELETE가 일어나면 발동 시킬 수 있고, 실행되는 순서도 지정할 수 있습니다. INSERT행위가 일어나기 전에 처리하려면 BEFORE INSERT행위가 일어나고 처리하려면 AFTER 키워드를 사용합니다.

 

바로 생성방법 사용법을 알아보겠습니다.  급하신분들은 맨 아래로 내려가면 최종 트리거를 볼 수 있습니다.

 

Trigger 생성하기

DROP TRIGGER IF EXISTS parserTrigger;
delimiter $$
	CREATE TRIGGER parserTrigger
	AFTER INSERT ON input_table
	FOR EACH ROW
	BEGIN
		INSERT INTO output_table
	SET
		sender = NEW.data,
		timestamp = now();
	END
$$delimiter;

이미 같은 이름의 트리거가 존재할경우 지우기 위해 DROP TRIGGER를 사용하였고

CREATE TRIGGER를 통해 트리거의 이름을 parserTrigger라고 만들었습니다.

 

delimiter라는 키워드도 보이실텐데 생소한분들도 많을 것 같습니다. 제가 지금 생소하거든요.

delimiter는 트리거의 구문을 시작하고 끝내는 키워드라고 합니다. 즉 트리거들의 구분을 위한 키워드로 생각하시면 됩니다.

 

input_table 테이블에 INSERT동작이 발생하면 data컬럼의 값을 가져와  output_table에 넣어주는 INSERT -> INSERT 트리거를 생성했습니다.

 

트리거 동작 행동 정의

 

 

실제 동작 테스트

insert 구문을 이용한 input_table에 데이터 추가
trigger발동으로 output테이블에 값이 추가된 모습

 

정상적으로 데이터가 인입된 것을 볼 수 있습니다.

 

 

 


응용편 - 특정 문자열을 구분값으로 나누어 집어넣기

이번에는 기본 생성방식에서 응용하여 어떤 문자열이 들어오고 ';'구분값으로 나누어 들어온 데이터를 행으로 입력하는 트리거를 작성해보겠습니다.

 

작업 준비를 위해 테이블을 생성합니다.

 

input_table

먼저 insert가 들어올 테이블입니다

CREATE TABLE IF NOT EXISTS `input_table` (
  `idx` int(11) NOT NULL AUTO_INCREMENT,
  `data` varchar(6500) NOT NULL DEFAULT '0',
  PRIMARY KEY (`idx`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

 

 

 

output_table

input_table에 insert가 일어나면 trigger를 통해 받을 테이블입니다.

CREATE TABLE IF NOT EXISTS `output_table` (
  `idx` int(11) NOT NULL AUTO_INCREMENT,
  `type` varchar(50) NOT NULL,
  `value` varchar(50) NOT NULL,
  `timestamp` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`idx`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

idx: pk용 sequence값 입니다.

type : key값이 들어갈 컬럼입니다. ex) type1, type2, type3...

value : key에 해당하는 값이 들어갈 컬럼입니다. ex) 5, 7, 29

timestamp: trigger 동작시간인 now()를 넣을 컬럼입니다.

 

 

이제 트리거를 만듭니다.

문자열를 잘라서 넣어주는 Trigger

DROP TRIGGER IF EXISTS parserTrigger;
delimiter $$
	CREATE TRIGGER parserTrigger
	AFTER INSERT ON input_table
	FOR EACH ROW		
	BEGIN
		DECLARE type1 varchar(100);
		DECLARE type2 varchar(100);
		DECLARE type3 varchar(100);

		SET type1 = substring_index(substring_index(NEW.data, ';', 1), ':', -1);
		SET type2 = substring_index(substring_index(NEW.data, ';', 2), ':', -1);
		SET type3 = substring_index(substring_index(NEW.data, ';', 3), ':', -1);
			
		INSERT INTO output_table (type, value, timestamp) values ('type1', type1, now());
		INSERT INTO output_table (type, value, timestamp) values ('type2', type2, now());
		INSERT INTO output_table (type, value, timestamp) values ('type3', type3, now());
	END;
$$delimiter;

 

이번엔 DECLARE라는 키워드가 추가되었습니다.

DECLARE는 트리거의 변수를 생성할때 사용하는 키워드로 변수명 자료형(사이즈) 형태로 입력해주면 됩니다.

예시로 들어올 데이터는 'type1:20;type2:5;type3:15' 형태라고 가정하겠습니다.

;으로 구분되어 있고 ':'의 앞글자는 key, 뒷글자는 value값입니다.

 

input_tabledata컬럼에 예시데이터('type1:20;type2:5;type3:15' )가 들어오면 substring_indxe를 통해 생성한 변수 type1, 2, 3에 각각 잘라서 값을 넣어주고 값이 비어있지 않다면 각각 INSERT를 처리하는 트리거입니다.

 

 

 

동작을 확인해보겠습니다.

type1에 20

type2에 5

type3에 15

한줄로 들어가 있던 데이터가 정상적으로 들어간 모습을 볼 수 있습니다.

 

 

 

위에서 한 트리거도 동작은 하지만 문제가 있습니다 3개의 데이터가 무조건 있어야 정상동작을 한다는 점입니다.

'type2:5;type3:15' 이것처럼 type1이 안들어오면 버그가 발생합니다.

 

key값도 받고 길이도 체크할 필요가 있습니다.

 

 


최종 완성 - 값이 없으면 넣지 않도록 조건문 추가하기

입력값을 확인하여 동적으로 INSERT하는 TRIGGER

DROP TRIGGER IF EXISTS parserTrigger;
delimiter $$
	CREATE TRIGGER parserTrigger
	AFTER INSERT ON input_table
	FOR EACH ROW		
	BEGIN
		DECLARE dataStr varchar(1000);
		DECLARE splitLen INT default 0;
		DECLARE type1 varchar(100);
		DECLARE type2 varchar(100);
		DECLARE type3 varchar(100);
		DECLARE typeValue1 varchar(100);
		DECLARE typeValue2 varchar(100);
		DECLARE typeValue3 varchar(100);
		
		SET dataStr = NEW.data;
		SET splitLen = ROUND((CHAR_LENGTH(dataStr) - CHAR_LENGTH(REPLACE(dataStr, ';', '')) / CHAR_LENGTH(';')));
		SET type1 = substring_index(substring_index(substring_index(dataStr, ';', 1), ';', -1), ':', 1);
		SET type2 = substring_index(substring_index(substring_index(dataStr, ';', 2), ';', -1), ':', 1);
		SET type3 = substring_index(substring_index(substring_index(dataStr, ';', 3), ';', -1), ':', 1);
		SET typeValue1 = substring_index(substring_index(dataStr, ';', 1), ':', -1);
		SET typeValue2 = substring_index(substring_index(dataStr, ';', 2), ':', -1);
		SET typeValue3 = substring_index(substring_index(dataStr, ';', 3), ':', -1);
		
			
		IF type1 is NOT NULL
			THEN BEGIN
				INSERT INTO output_table (type, value, timestamp) values (type1, typeValue1, now());
			END; END IF;
			
		IF splitLen > 0 and type2 is NOT NULL
			THEN BEGIN
				INSERT INTO output_table (type, value, timestamp) values (type2, typeValue2, now());
			END; END IF;
			
		IF splitLen > 1 and type3 is NOT NULL
			THEN BEGIN
				INSERT INTO output_table (type, value, timestamp) values (type3, typeValue3, now());
			END; END IF;
	END;
$$delimiter;

추가된 것으로는 아래와 같습니다.

 

dataStr은 입력이 들어온 data컬럼의 값

splitLen은 ';' 특수문자의 개수

type의 키

typeValue는 값

 

별개로 받는 변수를 처리하여 IF문에 따라 NULL체크와 길이를 체크하여 INSERT를 제어합니다.

 

 

동작 확인!

type1이 없지만 2개만 정상적으로 넣은 모습

 

반응형
반응형

Spring tiles에 대해 다뤄보기 위해 새로운 프로젝트인 Legacy 프로젝트를 생성하고 파일들을 만들고

위치에 따라 설정을 하고 있었는데 'org.springframework.web.servlet.view.tiles3.TilesConfigurer' not found 와 같은 에러가 발생하였다.

 

관련 검색을 해보니 스프링 프레임워크의 버전이 낮아서 발생한 문제였다.

해결 방법으로 버전을 올린 뒤 Maven update - project clean하여 해결하였다.

 

 

기존에 문제가 되던 springframework 버전

3.1.1.RELEASE에서는 tiles3를 사용할 수 없었다.

 

확인을 해보니 3.2이상으로 버전을 올려야 한다고 해서 3.2.18로 변경하였다.

3.2.18.RELEASE로 변경해주었다.

project 우클릭 후 MAVEN - Update project... 처리 후 Proejct - clean... 이후에 에러가 사라진걸 볼 수 있었다.

 

사용한 tiles 버전은 3.0.3이였다.

반응형
반응형

스프링 타일즈란 뷰페이지의 jsp들을 상단, 사이드, 메인, 하단을 설정 상태로 include 처리해주는 구조의 템플릿을 말합니다.

 

페이지들을 일괄관리 할 수 있고, 공통사용하는 부분들을 매번 등록을 따로 해주지 않아도 되기 때문에 편리합니다.

 

적용하는법에 대해 알아보겠습니다.

먼저, 타일즈 적용을 위해 pom.xml에 메이븐 추가부터 해보겠습니다.

 


 

pom.xml - 라이브러리 추가

<properties>
	<org.apache.tiles-version>3.0.3</org.apache.tiles-version>
</properties>

<!-- Tiles -->
<dependency>
	<groupId>org.apache.tiles</groupId>
	<artifactId>tiles-core</artifactId>
	<version>${org.apache.tiles-version}</version>
</dependency>

<dependency>
	<groupId>org.apache.tiles</groupId>
	<artifactId>tiles-servlet</artifactId>
	<version>${org.apache.tiles-version}</version>
</dependency>

<dependency>
          <groupId>org.apache.tiles</groupId>
          <artifactId>tiles-jsp</artifactId>
          <version>${org.apache.tiles-version}</version>
      </dependency>

<dependency>
	<groupId>org.apache.tiles</groupId>
	<artifactId>tiles-extras</artifactId>
	<version>${org.apache.tiles-version}</version>
</dependency>

Maven에 추가된 모습

pom.xml을 열어서 원하는 버전으로 입력하시면 다운로드가 될 겁니다.

저는 3.0.3버전으로 진행했습니다.

 

버전 정보들은 아래 URL에서 확인하시면 좋을 것 같습니다.

https://mvnrepository.com/artifact/org.apache.tiles/tiles-jsp

 

Maven Repository: org.apache.tiles » tiles-jsp

Tiles JSP support: Classes and tag libraries to use Tiles in a JSP environment. VersionRepositoryUsagesDate3.0.x3.0.8Central8Sep, 20173.0.7Central12Aug, 20163.0.5Central18Sep, 20143.0.4Central5May, 20143.0.3Central4Nov, 20133.0.1Central7Jul, 20123.0.0Centr

mvnrepository.com

 

다음으로 사용할 상단, 사이드, 메인, 하단으로 사용할 jsp들을 만들어 놓겠습니다.

나중에 타일즈 설정파일에서 연결을 할겁니다.

 

WEB-INF/views/tiles라는 디렉토리를 만들고 안에 각각 JSP들을 구성합니다.

 

 

사용할 JSP구성

JSP 4개 생성

안에 사용할 내용은 비워두셔도 됩니다.

 

다음은 servlet-context.xmlInternalResourceViewResolver를 변경 및 tiles설정을 추가하겠습니다.

 

 

servlet-context.xml

<beans:bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <beans:property name="prefix" value="/WEB-INF/views/" />
    <beans:property name="suffix" value=".jsp" />
    <beans:property name="order" value="2" />
</beans:bean>

<!-- tiles 설정 -->
<beans:bean id="tilesViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <beans:property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView" />
    <beans:property name="order" value="1" />
</beans:bean>
 
<beans:bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
    <beans:property name="definitions">
        <beans:list>
            <beans:value>/WEB-INF/spring/tiles-define.xml</beans:value>
        </beans:list>
    </beans:property>
</beans:bean>

order 부분을 유의해서 설정해주셔야 합니다. tiles가 우선순위가 되도록 해주어야 합니다.

 

 

tiles 설정을 추가 후 이클립스에서 에러가 발생한다면 아래 포스팅을 참고해주세요

https://myhappyman.tistory.com/82

 

Spring - tiles 적용 에러 발생 'org.springframework.web.servlet.view.tiles3.TilesConfigurer' not found

Spring tiles에 대해 다뤄보기 위해 새로운 프로젝트인 Legacy 프로젝트를 생성하고 파일들을 만들고 위치에 따라 설정을 하고 있었는데 'org.springframework.web.servlet.view.tiles3.TilesConfigurer' not foun..

myhappyman.tistory.com

 

servlet-context.xml을 추가했으면 beans:value에 처리한 tiles-define.xml을 생성하고 설정하겠습니다.

tiles-define.xml

xml을 생성하고 설정을 하겠습니다.

 

 

tiles-define.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC
       "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
       "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
 
<tiles-definitions>
	<!-- main layout -->
	<definition name="layout-tiles" template="/WEB-INF/views/tiles/tiles-layout.jsp">
		<put-attribute name="header" value="/WEB-INF/views/tiles/headerTemplate.jsp" />
		<put-attribute name="left" value="/WEB-INF/views/tiles/sidebarTemplate.jsp" />
		<put-attribute name="body" value="" />
		<put-attribute name="foot" value="/WEB-INF/views/tiles/footerTemplate.jsp" />
	</definition>
	
	<definition name="*/*" extends="layout-tiles">
		<put-attribute name="body" value="/WEB-INF/views/{1}/{2}.jsp" />
		<put-attribute name="title" value="게시판" />
	</definition>
</tiles-definitions>

 

이 부분에서는 상단, 사이드, 바닥 메인으로 사용할 템플릿 등을 설정합니다.

definition에서 name은 변수처럼 사용할 이름을 지정하고 template은 사용할 jsp를 지정합니다.

그 내부에는 따라오는 헤더, 사이드, 바닥 jsp들을 처리하고 마찬가지로 name은 사용할 이름 value에는 실제로 들어갈 jsp를 입력합니다.

 

먼저 생성해놓은 JSP 중 tiles-layout.jsp를 메인으로 사용하고 각각 header sidebar, footer부분을 영역별로 사용할 예정입니다.

 

아래서 extends부분은 위에서 선언한 jsp를 상속받아 실제로 사용하는 페이지에 이식하고 body영역은 실제 페이지를 사용하기 위해 설정하는 부분으로 메인 body로 쓸 jsp영역을 설정할 수 있으며, {1}/{2}.jsp을 통해 board/board.jsp로 갈때 자연스럽게 이식되어 사용할 수 있게 합니다.

title과 같은 설정으로 브라우저의 title태그도 변경이 가능합니다.

 

이로써 간단하게 tiles설정이 끝났습니다.

 


 

JSP들 소스 수정과 CSS들을 입력하여 동작하는지 확인해보겠습니다.

 

headerTemplate.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<div class="Header">Header</div>

 

sidebarTemplate.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<div class="SideBar">Side</div>

 

footerTemplate.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<div class="Footer">Footer</div>

 

tiles-layout.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles"  %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<!-- 공통변수 처리 -->
<c:set var="CONTEXT_PATH" value="${pageContext.request.contextPath}" scope="application"/>
<c:set var="RESOURCES_PATH" value="${CONTEXT_PATH}/resources" scope="application"/>
<!DOCTYPE html>
<html>
  <head>
	<meta charset="UTF-8">
	<meta name="description" content="">
	<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
	<script type="text/javascript">
		var CONTEXT_PATH = "${CONTEXT_PATH}";
		var RESOURCES_PATH = "${RESOURCES_PATH}";
	</script>
	<link rel="stylesheet" href="${RESOURCES_PATH}/css/common.css">
    <title><tiles:insertAttribute name="title" /></title>
  </head>
  
  <body>
  	<div class='wrap'>
  		<tiles:insertAttribute name="header" />
		  <div class='content'>  	
  			<tiles:insertAttribute name="left"/>
	  		<div class="page_content">
	  			<tiles:insertAttribute name="body"/>
	  		</div>
  		</div>
  		<tiles:insertAttribute name="foot" />
  	</div>
  </body>
  
</html>

공통변수 처리부분이나 script부분은 무시하셔도 됩니다.

tiles-define.xml에서 각각 JSP들을 불러왔으니 사용해야겠죠? 해당페이지에서 원하는 영역에 삽입을 할 수 있습니다.

<tiles:insertAttribute name="사용할 이름" />을 통해 적용하시면 됩니다.

 

resources/css/common.css를 만들었습니다.

 

 

common.css

@charset "UTF-8";

*{
	margin: 0;
	padding: 0;
}
.wrap{
	width: 100%;
}

.Header, .content, .Footer{
	width: 100%;
	float: left;
}

.Header, .Footer{
	height: 8em;
}

.SideBar{
	width: 10%;
	height: 600px;
	background-color: #FFBB00;
	float: left;
}

.page_content{
	width: 90%;
	height: 600px;
	background-color: #EAEAEA;
	float: left;
}

.Header{
	background-color: #ABF200;
}

.Footer{
	background-color: #FF00DD;
}

 

 

예시로 board/board.jsp를 이동하게 된 경우 발생한 결과 화면입니다.

tiles 적용

반응형
반응형

https://myhappyman.tistory.com/77

 

JAVA 8 - Stream 사용하기 - 1(filter, 중복제거)

자바 8에서는 Stream이 추가되었는데, 이 기술은 컬렉션이나 배열등을 람다식으로 표현하여 간결하고 가독성 좋은 코드를 만들어주고 복잡하고 많은 양의 코드를 원하는 인스턴스끼리 조합하여 필터링을 해주는등..

myhappyman.tistory.com

https://myhappyman.tistory.com/78

 

JAVA 8 - Stream 사용하기 - 2(sorted 데이터 정렬)

Stream 사용하기 1에 이어서 이번엔 Stream의 sorted를 사용해보겠습니다. 배열, 컬렉션에 담긴 데이터를 정렬하는 예제를 보겠습니다. sorted String배열 정렬하기. String[] animals = {"rabbit", "fox", "cat",..

myhappyman.tistory.com

 


 

1, 2에서 Stream을 통해 필터링, 중복제거, 정렬등을 봤는데, 사실 사용하면서 최종연산까지는 하지 않고 Stream객체에 담아두고 해당 데이터를 확인할때 최종연산인 forEcah를 사용해서 데이터를 확인했다.

 

Stream에는 최종연산이 존재하는데 이를 통해 Stream을 완전히 닫아버리고 종료하게 된다.

즉, Stream은 재사용이 불가하다.

 

Stream의 최종연산자들의 사용법에 대해 알아보겠습니다.

 


count

public static void main(String[] args) {
	//count
	List<String> words = Arrays.asList("book", "desk", "keyboard", "mouse", "cup");
	int count = (int) words.stream().filter(w->w.contains("o")).count();
	System.out.println("count >" + count);
}

"o"가 포함된 Stream의 개수를 찾습니다.

 

min

public static void main(String[] args) {
	List<Integer> cal = Arrays.asList(49, 123, 22, 55, 21);
	//min
	int min = cal.stream().min(Comparator.comparing(x-> x)).get();
	System.out.println("min >" + min);
}

Integer 컬렉션의 최소값을 가져옵니다.

 

max

public static void main(String[] args) {
	List<Integer> cal = Arrays.asList(49, 123, 22, 55, 21);
	//max
	int max = cal.stream().max(Comparator.comparing(x-> x)).get();
	System.out.println("max >" + max);
}

Integer 컬렉션의 최대값을 가져옵니다.

 

reduce

public static void main(String[] args) {
	List<Integer> cal = Arrays.asList(49, 123, 22, 55, 21);
	//reduce
	Integer reduce = cal.stream().reduce((x, y) -> x+y).get();
	System.out.println("reduce >" + reduce);
}

누적된 값을 가져옵니다.

 

collect

public static void main(String[] args) {
	List<Integer> cal = Arrays.asList(49, 123, 22, 55, 21);
	//collect
	List<Integer> newList = cal.stream().filter(x-> x > 80).collect(Collectors.toList());
	newList.forEach(System.out::println);
}

스트림 객체를 원하는 컬렉션형태로 파싱해줍니다.

 

forEach

public static void main(String[] args) {
	List<Integer> cal = Arrays.asList(49, 123, 22, 55, 21);
	//forEach
	cal.stream().forEach(x-> System.out.print(x + "  "));
}

반복문입니다. 컬렉션의 데이터를 각각 호출해줍니다.

반응형
반응형

요즘 svn이 아닌 git을 사용하고 있는데 egov에서 정상적으로 연동이 되질 않아 git bash를 통해 파일들을 받고 올리고 하면서 사용중에 있는데 스프링 구조의 target파일과 .springBeans, maven파일등이 올라가면서 꼬이는 현상이 발생하였습니다.

git으로 add ~ commit ~ push까지 진행하면서 이러한 일때문에 제외하고 올리고 싶은 파일들이 있을텐데 제외하는 방법을 알아보겠습니다.

 

.git디렉토리가 존재하는 같은 위치 최상단에 .gitignore라는 파일을 생성하고 데이터를 입력해시면 간단하게 해결됩니다.

 

파일을 생성하면 아무 에디터든 열어서 올리지 않을 확장자 또는 디렉토리등을 입력합니다.

 

저는 target, META-INF 디렉토리와 .springBeans 확장자의 파일은 올라가지 않도록 설정하였습니다.

target/*
META-INF/*
.springBeans

* 까지 처리해줘야 하위 생성하는 모든 것들을 무시합니다.

 

 

위 와같이 입력 후 저장하고 에디터를 종료합니다.

 

이후 git bash를 열고 push를 진행해주셔야 적용이 됩니다.

 

그전에 캐시를 키우고 올리도록 하겠습니다.

 

git rm -r --cached . //캐시 삭제
git add . //모든 파일을 적용
git commit -m "적용하는 메시지" //commit 처리

 

이후에는 프로젝트의 설정한 파일들이나 디렉토리 하위의 파일들이 바뀌어도 git status를 입력시 제외처리되어 인식하지 않는 것을 확인 할 수 있습니다.

 


*.extension   // 확장자로 시작하는 파일 제외

dirName/      //디렉토리 하위의 모든 파일은 제외
반응형
반응형

Stream 사용하기 1에 이어서 이번엔 Stream의 sorted를 사용해보겠습니다.

 

배열, 컬렉션에 담긴 데이터를 정렬하는 예제를 보겠습니다.

 

sorted

String배열 정렬하기.

String[] animals = {"rabbit", "fox", "cat", "tiger", "dog"};
Stream.of(animals).sorted().forEach(System.out::println);

정렬된 문자열 스트림

배열데이터에는 stream()을 바로 사용할 수 없으니 Stream.of(T[]) 를통해 스트림을 생성하고 그것을 sorted()를 연결하여 정렬한 데이터를 보여줍니다.

 

Stream.of() 가 싫다면 아래처럼 Arrays.asList()를 통해 배열을 LIST화 시키고 해당 데이터를 정렬합니다.

String[] animals = {"rabbit", "fox", "cat", "tiger", "dog"};
Arrays.asList(animals).stream().sorted().forEach(System.out::println);

동일한 결과를 얻을 수 있습니다.

 

원본 데이터는 영향을 받지 않는다.

여기서 주의할 것은 "원본데이터인 animals라는 배열이 정렬되는게 아니다." 라는 점입니다.

배열데이터가 정렬된게 아닌 새로운 스트림데이터에 정렬된 데이터가 들어있습니다.

String[] animals = {"rabbit", "fox", "cat", "tiger", "dog"};
Arrays.asList(animals).stream().sorted().forEach(System.out::println);

System.out.println();System.out.println();
System.out.println("당연히 원본 배열의 정렬은 그대로이다.");
System.out.println();System.out.println();

Arrays.asList(animals).forEach(System.out::println);

animals 배열은 영향받지 않는다.

 

 

VO 객체 배열을 정렬하기

vo형태로 들어가있는 배열 데이터의 특정값을 기준으로 정렬을 해보겠습니다.

Student Class를 정의합니다.

Student.java

public class Student {
	private String name;
	private int jum;
	
	public Student(String name, int jum) {
		this.name = name;
		this.jum = jum;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getJum() {
		return jum;
	}
	public void setJum(int jum) {
		this.jum = jum;
	}
	
	@Override
	public String toString() {
		return "Student [name=" + name + ", jum=" + jum + "]";
	}
}

 

VO Array Sorted

Student[] sArr = {
		new Student("철수", 79)
		, new Student("영희", 95)
		, new Student("영수", 80)
		, new Student("민희", 85)
		, new Student("유리", 40)
		, new Student("민수", 75)};

Stream<Student> filter_Arr = Stream.of(sArr).sorted(Comparator.comparing((Student s) -> s.getName()));
filter_Arr.forEach(System.out::println);

name을 기준으로 정렬

sArr 배열에 new 생성자를 통해서 학생 6명을 넣고 정렬한 예제입니다.

Comparator 인터페이스의 comparing 메소드를 활용하여 VO객체의 특정 값을 정렬하였습니다.

특정 값은 생성자로 사용한 첫번째 파라미터 name값이 되었습니다.

 

 

여기서 궁금증이 생길수 있습니다. 반대로 정렬은 어떻게 할까요?

reversed() 메소드를 활용하면 됩니다.

Stream<Student> filter_Arr = Stream.of(sArr).sorted(Comparator.comparing((Student s) -> s.getName()).reversed());
filter_Arr.forEach(System.out::println);

반대로 정렬 reversed()

 

LIST<VO> 데이터 정렬하기

List<Student> list = new ArrayList<Student>();
list.add(new Student("철수", 79));
list.add(new Student("영희", 95));
list.add(new Student("영수", 80));
list.add(new Student("민희", 85));
list.add(new Student("유리", 40));
list.add(new Student("민수", 75));

list.stream()
	.sorted(Comparator.comparing((Student s) -> s.getJum()).reversed())
	.forEach(System.out::println);

List<Student> 정렬

네.. 배열안에 있는 VO 데이터나 List안에 있으나 큰 차이는 없습니다.

이번엔 점수가 높은 데이터부터 정렬이 되도록 출력해봤습니다.

 

마지막으로 List<HashMap<Obejct, Object> 형태 정렬하는 방법을 알아보겠습니다.

List<Map>정렬하기

먼저 값이 정수형인 데이터부터 정렬을 해보겠습니다.

List<HashMap<Object, Object>> list = new ArrayList<HashMap<Object, Object>>();
list.add(new HashMap<Object, Object>(){{put("name", "영희");put("jum", 95);}});
list.add(new HashMap<Object, Object>(){{put("name", "영수");put("jum", 80);}});
list.add(new HashMap<Object, Object>(){{put("name", "민수");put("jum", 75);}});
list.add(new HashMap<Object, Object>(){{put("name", "철수");put("jum", 79);}});
list.add(new HashMap<Object, Object>(){{put("name", "민희");put("jum", 85);}});
list.add(new HashMap<Object, Object>(){{put("name", "유리");put("jum", 40);}});
list.add(new HashMap<Object, Object>(){{put("name", "유리");put("jum", 55);}});
list.stream()
	.sorted((s1, s2) -> Integer.compare((int)s1.get("jum"), (int)s2.get("jum")))
	.forEach(System.out::println);

작은 값부터 정렬

Wrapper.class.compare를 통해 정렬을 하였습니다.

첫번째 파라미터가 앞에 있으면 작은값부터 정렬되며, 두번째 파라미터가 앞에 있으면 큰값부터 정렬이 됩니다.

 

파라미터 위치에 따른 정렬 변경

list.stream()
	.sorted((s1, s2) -> Integer.compare((int)s2.get("jum"), (int)s1.get("jum")) )
	.forEach(System.out::println);

큰 값부터 정렬

 

 

다음으로 문자열데이터가 들어간 key값이 "name"인 데이터를 정렬해보겠습니다.

compareTo()

Stream<HashMap<Object, Object>> string_list = list.stream()
			.sorted((s1, s2) -> 
				s1.get("name").toString().compareTo( s2.get("name").toString() )
			);
string_list.forEach(System.out::println);

문자열 정렬

 

반대로 정렬할때는 역시 순서를 바꿔주면 됩니다. 람다식 표현의 s1, s2의 위치를 바꿔주면 됩니다.

Stream<HashMap<Object, Object>> string_list = list.stream()
			.sorted((s1, s2) -> 
				s2.get("name").toString().compareTo( s1.get("name").toString())
			);
string_list.forEach(System.out::println);

문자열 정렬 역순

 

반응형
반응형

자바 8에서는 Stream이 추가되었는데, 이 기술은 컬렉션이나 배열등을 람다식으로 표현하여 간결하고 가독성 좋은 코드를 만들어주고 복잡하고 많은 양의 코드를 원하는 인스턴스끼리 조합하여 필터링을 해주는등 편리함을 제공합니다.

보통 컬렉션의 첫번째 값을 가져오거나, 특정 조건을 만족하는 데이터만 필터링을 하거나 중복데이터를 제거하거나 이러한 작업을 한결 더 편하게 가공해줍니다.

 

바로 예시부터 보겠습니다.

아래의 컬렉션을 기준으로 예제들을 진행하겠습니다.

 

예시데이터

name, jum이라는 문자열이 키값으로 해당 키값에 해당하는 데이터를 넣어놓은 LIST입니다.

List<HashMap<Object, Object>> list = new ArrayList<HashMap<Object, Object>>();
list.add(new HashMap<Object, Object>(){{put("name", "영희");put("jum", 95);}});
list.add(new HashMap<Object, Object>(){{put("name", "영수");put("jum", 80);}});
list.add(new HashMap<Object, Object>(){{put("name", "민수");put("jum", 75);}});
list.add(new HashMap<Object, Object>(){{put("name", "철수");put("jum", 79);}});
list.add(new HashMap<Object, Object>(){{put("name", "민희");put("jum", 85);}});
list.add(new HashMap<Object, Object>(){{put("name", "유리");put("jum", 40);}});
System.out.println("---- Original ----");
list.forEach(System.out::println);

6명의 학생 컬렉션

 

해당 list데이터를 점수가 80점 이상인 데이터만 처리하라고 하면 어떻게 해야할까요?

각각 HashMap데이터의 "jum"인 키를 조회하여 값이 80점 이상인 데이터만 새로운 컬렉션에 넣어주면 될겁니다.

 

반복문을 통한 필터링

아래 소스를 보시죠. forEach 람다식으로 표현하였습니다.

List<HashMap<Object, Object>> fliter_list = new ArrayList<HashMap<Object, Object>>();
list.forEach(x->{
  int jum = (int) x.get("jum");
  if(jum > 80) {
    fliter_list.add(x);
  }
});
System.out.println("---- Filtering ----");
fliter_list.forEach(System.out::println);

필터링한 데이터

80점 이상인 영희와 민희데이터만 가져온 걸 볼 수 있습니다.

 

 

stream filter

80점 이상인 데이터만 필터링하기

Stream<HashMap<Object, Object>> fliter_list = list.stream().filter(xx-> (int) xx.get("jum") > 80);
System.out.println("---- Filtering ----");
fliter_list.forEach(System.out::println);

stream filter 처리

위에서 복잡하게 80점 이상인 데이터만 호출하여 표출하던 부분이 filter를 통해 손 쉽게 가공이 되었습니다.

 

이번에는 80점 이상이면서 이름에 "영"이라는 String이 들어간 데이터를 필터링 해보겠습니다.

Stream<HashMap<Object, Object>> fliter_list = list.stream().filter(xx-> 
	(int) xx.get("jum") > 80 && xx.get("name").toString().contains("영")
);
System.out.println("---- Filtering ----");
fliter_list.forEach(System.out::println);

 

&&연산자를 통해 80점이상과 영이라는 String이 둘 조건이 만족되는 데이터만 필터링 된 것을 볼 수 있습니다.

둘중 하나만 만족해도 나오게 하려면 or 연산자인 || 으로 처리하면 됩니다.

 

 

stream을 사용하면서 유의해야 할 점은 최종연산자까지 사용을 해야 동작을 한다는 것입니다.

중간연산자(filter, peek, distinct, ...)까지만 정의하면 의미없는 코드가 되며, 최종연산(collect, forEach, count, ...)을 처리해야 그때부터 중간연산자들도 동작을 시작합니다.

 


중복제거하기 distinct

List<String> animals = Arrays.asList("dog", "cat", "dog", "cat", "monkey", "rabbit");
Stream<String> filter = animals.stream().distinct();
filter.forEach(System.out::println);

중복된 dog, cat의 문자열배열이 사라진 것을 볼 수 있다.

 

list내 map객체의 중복된 key값 제거는 단순하게 distinct를 활용하여 제거는 힘듭니다.

JAVA Functional 처리를 통한 필터링처리가 필요한데, 이부분은 추후 포스팅하도록 하겠습니다.

 

 

반응형
반응형

최근 특정 코드별로 쌓이는 테이블을 일정시간마다 호출해서 마지막값을 가져와야 할 일이 생겼다.

 

여러가지 찾아본 결과는 다음과 같다.

1. order by desc를 통해 select를 해온다.

2. select된 데이터를 한번 더 group by를 한다.

막상 적용해보면 내가 쌓고 있던 테이블의 데이터에서는 group by 후 마지막 날짜의 데이터가 나오지 않았다.

 


아래와 같은 데이터가 있다고 가정하겠다.

예시 데이터

항상 code컬럼의 AAA, BBB, CCC, DDD의 데이터들의 마지막 날짜 데이터들을 가져오는 쿼리를 작성해보겠다.

code컬럼에는 n개가 존재하며 언제나 증가할 수 있다고 가정하고 쿼리를 작성하였다.

 

아래는 조회에 성공한 쿼리이다.

select
	*
from(
	select
		*
	from tb_test
	where (code, date_time) in (
		select code, max(date_time) as date_time
		from tb_test group by code
	)
	order by date_time desc
) t
group by t.code

결과 데이터

 

먼저 뽑아낼 테이블을 max함수를 통해 마지막 날짜 조회 후 group by를 하고 해당하는 데이터를 조건문에 처리(where 조건문 in절부분)하여 최신 데이터 순으로 order by를 통해 다시 한번 정렬을 한다.

 

사실 여기까지만 처리해도 정상적으로 볼 수 있지만, 예시데이터처럼 중복된 시:분:초로 들어온 경우 마지막 한개의 데이터가 보장이 되질 않아 마지막에 group by를 한번 더 처리하였다.

 

아래는 마지막 group by를 안할 경우 발생하는 중복 데이터 노출이다.

시분초가 같은 데이터의 마지막 group by가 없는 경우

 

 

처음에 사실 쉽게 처리가 되질 않아 n개의 code값을 group by하여 가져온 후 그 코드값을 where절에 넣고 select를 n번 요청하였는데, 그 방법보단 위 방법이 깔끔한 것 같다.

반응형