Java8 의 날짜 API 사용하기.

|

Java 1.8 이전 버전의 SDK에서 날짜와 시간을 다루는 java.util.Date 클래스와 java.util.Calendar 클래스는 사용하기가 불편하고 직관적이지 않으며 또한 java.util.Date와 SimpleDateFormatter는 Thread-Safe 하지 않아서 잠재적인 동시성 문제를 가지고 있다. 많이 늦었지만 JDK 8에서는 개선된 날짜와 시간 API가 제공된다.

기존 API

  • java.util.Date - 날짜와 시간, 기본 시간대를 사용하여 출력.
  • java.util.Calendar - 날짜와 시간, 날짜를 조작하는데 더 많은 메소드 제공.
  • java.text.SimpleDateFormat - 날짜와 달력을위한 형식 (날짜 -> 텍스트), 변환 (텍스트 -> 날짜).

JAVA8 에서 추가된 API

  • java.time.LocalDate - 날짜(시간 포함하지 않음), 타임존 사용하지 않음.
  • java.time.LocalTime - 시간(날짜 포함하지 않음), 타임존 사용하지 않음.
  • java.time.LocalDateTime - 날짜 및 시간, 타임존 사용하지 않음.
  • java.time.ZonedDateTime - 날짜 및 시간, 타임존 사용.
  • java.time.DateTimeFormatter - java.time에 대한 형식 (날짜 -> 텍스트), 변환 (텍스트 -> 날짜)
  • java.time.Duration - 시간을 초 단위 및 나노초 단위로 측정한다.
  • java.time.Period - 시간을 년, 월, 일로 측정한다.
  • java.time.TemporalAdjuster - 날짜를 조정한다.

사용해보기

예제코드 1)

  • 컴퓨터의 현재 날짜 출력.
import java.time.*;

public class WorkingWithLocalDate {
  public static void main(String[] args) {
      LocalDate localDate = LocalDate.now();
      System.out.println(localDate); 
  }
}

예제코드 2)

  • 특정 일, 월 및 연도 지정하여 반환하고 싶을 때 LocalDate.of 메서드를 사용하거나 LocalDate.parse 메서드를 사용.

import java.time.*;
import java.time.format.DateTimeFormatter;

public class WorkingWithLocalDate {
    public static void main(String[] args) {
        //다음날짜로 지정 (2017-12-12)
        LocalDate localDate = LocalDate.of(2017, 12, 12);
        System.out.println(localDate);

        //다음날짜로 지정 (2017-12-05)
        localDate = LocalDate.parse("2018-12-05");
        System.out.println(localDate);
	}
}

출력 :

2017-12-12
2018-12-05

예제코드 3)

  • LocalDate parse 사용시에 포멧 형식 변경하여 사용할 경우.

import java.time.*;
import java.time.format.DateTimeFormatter;

public class WorkingWithLocalDate {
    public static void main(String[] args) {
		// LocalDate parse 사용 시 포멧 형식 변경해서 사용 시
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d/MM/yyyy");
        String date = "18/09/2018";
        //convert String to LocalDate
        LocalDate localDate = LocalDate.parse(date, formatter);
        System.out.println(localDate);

        // LocalDateTime 사용 시 pattern 지정하여 출력하기.
        LocalDateTime now = LocalDateTime.now();
        System.out.println("Before : " + now);
        DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String formatDateTime = now.format(formatter2);
        System.out.println("After : " + formatDateTime);
	}
}

출력 :

2018-09-18
Before : 2018-12-13T09:49:53.383
After : 2018-12-13 09:49:53

예제코드 4)

  • 날짜 시간 증감시키기 예제

import java.time.*;
import java.time.format.DateTimeFormatter;

public class WorkingWithLocalDate2 {
    public static void main(String[] args) {
        //날짜 시간 증감.
        LocalDateTime localDateTime = LocalDateTime.of(2018, 12, 12, 0, 0, 0);
        LocalDateTime changedLocalDateTime = localDateTime.plusDays(1);  // 일
        System.out.println(changedLocalDateTime); // 출력 : 2018-12-13T00:00

        changedLocalDateTime = localDateTime.plusMonths(1);  // 월
        System.out.println(changedLocalDateTime); //출력 : 2019-01-12T00:00

        changedLocalDateTime = localDateTime.plusHours(1); // 시간
        System.out.println(changedLocalDateTime); //출력 : 2018-12-12T01:00

        changedLocalDateTime = localDateTime.plusWeeks(1); // 주
        System.out.println(changedLocalDateTime); //출력 : 2018-12-19T00:00

        changedLocalDateTime = localDateTime.minusYears(1);  // 년
        System.out.println(changedLocalDateTime); //출력 : 2017-12-12T00:00

        changedLocalDateTime = localDateTime.minusMinutes(1);  // 분
        System.out.println(changedLocalDateTime); //출력 : 2018-12-11T23:59
  }
}

출력 :

2018-12-13T00:00
2019-01-12T00:00
2018-12-12T01:00
2018-12-19T00:00
2017-12-12T00:00
2018-12-11T23:59

예제코드 5)

  • 특정 지역의 시간에 의존하지 않고 날짜와 시간을 표현하고 싶다면 ZonedDateTime 클래스를 사용하면 된다.

import java.time.*;
import java.time.format.DateTimeFormatter;

public class WorkingWithLocalDate {
    public static void main(String[] args) {
      LocalDateTime dateTime = LocalDateTime.of(2018, Month.DECEMBER, 12, 9, 00, 00);
      // UTC+9는 서울시간
      ZonedDateTime seoulTime = dateTime.atZone(ZoneId.of("Asia/Seoul"));
      System.out.println("ZonedDateTime : " + seoulTime);
      
      //세계표준시.
      Instant instant = seoulTime.toInstant();
      System.out.println("Instant : " + instant);
	}
}

서울시간이 UTC (세계 표준시) 보다 9시간이 빠르다.
만약 서울시간이 2018-12-12 09:00:00 일경우 표준시는 2018-12-12 00:00:00 가 된다.

출력 :

ZonedDateTime : 2018-12-12T09:00+09:00[Asia/Seoul]
Instant : 2018-12-12T00:00:00Z

예제코드 6)

  • Period를 통해 두 “날짜” 사이의 간격을 알 수 있다.

import java.time.*;
import java.time.format.DateTimeFormatter;

public class WorkingWithLocalDate {
    public static void main(String[] args) {
      LocalDate startDate = LocalDate.of(2010, 10, 18);
      LocalDate endDate = LocalDate.of(2018, 12, 12);
      
      Period period = Period.between(startDate, endDate);
      
      System.out.println(period.getYears() + " 년" + period.getMonths() + " 월" + period.getDays() + " 일");
	}
}

출력:

8 년1 월24 일

참고:

  • http://starplatina.tistory.com/entry/Java-8-%EC%83%88%EB%A1%9C%EC%9A%B4-Date-Time-API
  • http://www.mkyong.com/tutorials/java-date-time-tutorials/

Intellij 에서 git 프로젝트 생성 후 remote 저장소(github)에 push 하기.

|

1. Git 실행설정

  • Intellij 실행 후 File -> Settings 메뉴로 접근하여 실행파일 경로를 지정한다.
  • git을 기본경로로 설정하였으면 따로 설정할 필요는 없다.

2. github 계정등록

  • github 연동을 위해 생성한 github계정을 Injtellij에 등록한다.

3. VCS - Import into Version Control - Share Project on Github 선택

4. 올라갈 프로젝트의 이름지정 후 Share 후에 프로젝트를 push 한다.

5. push후 등록한 계정으로 github 로그인 후 잘 올라갔는지 확인한다.


스프링 부트에서 크로스도메인 이슈 처리하기. (@CrossOrigin 어노테이션을 사용)

|

크로스도메인 이슈란?

Ajax 등을 통해 다른 도메인의 서버에 url(data)를 호출할 경우 XMLHttpRequest는 보안상의 이유로 자신과 동일한 도메인으로만 HTTP요청을 보내도록 제한하고 있어 에러가 발생한다.
내가 만든 웹서비스에서 사용하기 위한 rest api 서버를 무분별하게 다른 도메인에서 접근하여 사용하게 한다면 보안상 문제가 될 수 있기 때문에 제한하였지만 지속적으로 웹 애플리케이션을 개선하고 쉽게 개발하기 위해서는 이러한 request가 꼭 필요하였기에 그래서 XMLHttpRequest가 cross-domain을 요청할 수 있도록하는 방법이 고안되었다.
그것이 CORS 이다.

CORS란?

CORS(Cross-origin resource sharing)이란, 웹 페이지의 제한된 자원을 외부 도메인에서 접근을 허용해주는 메커니즘이다.

스프링에서 CORS 설정하기.

스프링 RESTful Service에서 CORS를 설정은 @CrossOrigin 어노테이션을 사용하여 간단히 해결 할 수 있다. RestController를 사용한 클래스 자체에 적용할 수 도 있고, 특정 REST API method에도 설정 가능하다.
또한, 특정 도메인만 접속을 허용할 수도 있다.

사용예 )

@CrossOrigin(origins = “허용주소:포트”)

예제코드 1)

  • WebMvcConfigurer를 통해 적용하는 방식.

package io.jmlim.corssample.corssample;
 
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
      // 모든 uri에 대해 http://localhost:18080, http://localhost:8180 도메인은 접근을 허용한다.
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:18080","http://localhost:8180");

    }
}

예제코드 2)

  • @CrossOrigin 어노테이션을 통해 적용하는 방식.
package io.jmlim.corssample.corssample;

 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
//해당 컨트롤러의 모든 요청에 대한 접근 허용(아래 도메인 두개에 대해서만..)
@CrossOrigin(origins = {"http://localhost:18080", "http://localhost:8180" }) 
@RestController
public class CorssampleApplication {
 
	//아래와 같이 특정 메소드에만 적용할수도 있다.
    //@CrossOrigin(origins = {"http://localhost:18080", "http://localhost:8180" })
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
	
	@GetMapping("/my")
    public String my() {
        return "my";
    }
	
 
    public static void main(String[] args) {
        SpringApplication.run(CorssampleApplication.class, args);
    }
}

예제코드 호출하기

만약 이 코드가 실행되는 웹서버의 도메인이 http://localhost:18080과 http://localhost:8080이 아닐경우 fail이 발생한다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    <script
           src="https://code.jquery.com/jquery-3.3.1.js"
            integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60="
            crossorigin="anonymous"></script>
    <script>
       // alert(1);
        $(function() {
			  // 만약 이 코드가 실행되는 웹서버의 도메인이 http://localhost:18080과 http://localhost:8080이 아닐경우 fail이 발생한다.
            $.ajax("http://localhost:8180/hello")
                .done(function(msg) {
                    alert(msg);
                }).fail(function() {
                    alert("fail");
                });
        });
    </script>
</head>
<body>

</body>
</html>

참고:

  • http://zamezzz.tistory.com/137 [배워가는블로거]

Hex Color 코드 관련 자바스크립트 정규식 및 함수 정리.

|

먼저 Hex코드란?

헥스 코드는 색깔을 #과 뒤에 붙는 여섯 개의 알파벳 + 숫자로 나타낸 것이다. 숫자는 두 자리씩 끊어서 각각 ‘R’, ‘G’, ‘B’를 나타내며 16진수로 표현되어 00일 때 어둡고 FF일 때 밝다. 즉 엄밀히 말하면 색이 아니고 빛이다. #FF0000이면 R값이 최고값이고 나머지는 0이므로 순수한 빨간색이 나타난다. #FFFF00이면 R값과 G값이 최고값이고 B값이 0이므로 R과 G의 합성 빛인 노란색이 나타난다. 주로 디지털에서 RGB보다 더 많이 쓰인다. 색상코드 앞엔 반드시 #을 붙여서 하지만 대체로 붙이지 않으면 자동으로 붙여 주는 경우가 많다. 프로그래머(특히 웹 서버 프로그래머)들은 워낙 이 작업을 매우 많이 해서 어지간한 색들의 색상코드는 거의 다 알고 있다고 해도 과언이 아니다. 실제로 작업하다보면 자의 반 타의반으로 외우게 된다.

RGB코드는 hex코드를 두자리씩 나눠서 10진수로 변환한 수이다.

  • ex : rgb(255,255,255)

자세한 내용은 나무위키 참조

  • https://namu.wiki/w/%ED%97%A5%EC%8A%A4%20%EC%BD%94%EB%93%9C

hex 코드값 판별하는 정규식.

/^#[0-9a-f]{3,6}$/i
#abc, #abcd, #abcde, #abcdef 형식과 매칭.

/^#([0-9a-f]{3}|[0-9a-f]{6})$/i
#abc and #abcdef 형식과 매칭. #abcd는 매칭 안됨.

/^#([0-9a-f]{3}){1,2}$/i
#abc and #abcdef 형식과 매칭 #abcd는 매칭안됨.

/^#(?:[0-9a-f]{3}){1,2}$/i
#abc and #abcdef 형식과 매칭 #abcd는 매칭안됨.

자바 스크립트에서 정규 표현식에 대해 자세한 학습을 원하면 RegExp - MDN을 참고하면 많은 도움이 된다.

  • https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/%EC%A0%95%EA%B7%9C%EC%8B%9D

javascript 에서 정규식 사용하여 유효성 체크를 해보자.

var regex = /^#(?:[0-9a-f]{3}){1,2}$/i;
var hex = "qweqwe"; 
//올바른 컬러코드값이 아니므로 아래 메세지 창 실행됨. 
//만약 올바른 컬러코드값 (ex: #ffffff)이 hex 변수에 들어있을 경우 regex.test(hex) => true 이므로 메세지 창 뜨지 않음.
if(!regex.test(hex)) {
  alert("올바른 헥사코드 값이 아닙니다.");
  return false;
}

hex코드를 rgb로 변환하는 javascript 함수 만들기.

function hexToRgb(hex) {
    // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
    var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
    hex = hex.replace(shorthandRegex, function(m, r, g, b) {
        return r + r + g + g + b + b;
    });

    var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ? {
        r: parseInt(result[1], 16),
        g: parseInt(result[2], 16),
        b: parseInt(result[3], 16)
    } : null;
}

실행결과

hexToRgb(“#ffffff”); //실행.
{r: 255, g: 255, b: 255} //리턴값.

rgb를 hex코드로 변환하는 javascript 함수만들기.

//Function to convert rgb color to hex format
function rgbToHex(rgb) {
	rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
	return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}

실행결과

rgbToHex(“rgb(255,255,255)”); //실행.
#ffffff //리턴값.

참고:

  • https://stackoverflow.com/questions/9682709/regexp-matching-hex-color-syntax-and-shorten-form

Spring 에서의 트랜잭션 처리.

|

먼저 트랜잭션이란?
데이터베이스 연산들의 논리적 단위이며 트랜잭션 내 모든 연산들이 정상적으로 완료되지 않으면 아무 것도 수행되지 않은 원래 상태로 복원되어야 한다.

예를들어 친구에게 인터넷 뱅킹으로 10,000원을 송금할경우 나의 계좌에서 10,000원을 줄이고 친구의 계좌에 10,000원을 증가시켜야 한다. 하지만 알 수 없는 오류로 인해 나의 계좌에서는 10,000원이 줄었지만 친구 계좌에는 10,000원이 증가되지 않는다면? 나의 10,000원은 그냥 공중으로 증발해버리는 사고가 발생한다.

이러한 경우가 생기지 않도록 중간에 오류가 발생하면 다시 처음부터 송금을 하도록 하는 것이 rollback이다.

위 송금 과정을 하나의 트랜잭션이라 볼 수 있으며 데이터베이스 연산들의 논리적 단위라 할 수 있다. 즉 한 번 질의가 실행되면 질의가 모두 수행되거나 모두 수행되지 않는 작업수행의 논리적 단위인 것이다.

스프링 프레임워크를 사용하는 주목할 만한 이유중 하나가 광범위한 트랜잭션 지원이다.
대표적으로 선언적인 트랜잭션 관리 지원을 많이 사용하며,
어노테이션 @Transactional을 활용하여 트랜잭션 처리를 한다. 단 몇줄의 코드만으로 다이나믹 프록시와 AOP라는 기술을 통해 크게는 인터페이스 단위에서 작게는 메서드까지 세분화하여 트랜잭션을 컨트롤 할 수 있다.

설정하기

@Transactional 어노테이션 사용을 위해, Spring bean 설정은 아래와 같이 하면 된다. (스프링 부트에서는 그냥 쓰면 된다.)

....
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
	destroy-method="close">
	<property name="driverClassName" value="org.apache.derby.jdbc.ClientDriver" />
	<property name="url" value="jdbc:derby://localhost:1527/sample" />
	<property name="username" value="user" />
	<property name="password" value="jmlim123" />
</bean>

<!-- transaction manager, use JtaTransactionManager for global tx -->
<bean id="transactionManager"
	class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource" />
</bean>

<!-- enable transaction demarcation with annotations -->
<tx:annotation-driven />
...

@Transactional이 적용되어 있을 경우, 이 클래스에 트랜잭션 기능이 적용된 프록시 객체가 생성된다. 이 프록시 객체는 @Transactional이 포함된 메소드가 호출 될 경우, PlatformTransactionManager를 사용하여 트랜잭션을 시작하고, 정상 여부에 따라 Commit 또는 Rollback 한다.

정상 여부는 RuntimeException이 발생했는지 기준으로 결정되며, RuntimeException 외 다른 Exception(대표적으로 SQLException이 있다.)에도 트랜잭션 롤백처리를 적용하고 싶으면 @Transactional의 rollbackFor 속성을 활용하면 된다.

  • 아래는 사용 예제 이다.
	/**
	 * <pre>
	 *  
	 *  설정시간이 지난 미결제 예약 레코드 삭제 및 이력 레코드 삽입 
	 * 
	 * </pre>
	 * 
	 * @throws Exception 오류가 발생할 경우 예외를 발생시킵니다.
	 */
	@Transactional(readOnly = true, propagation = Propagation.REQUIRED, rollbackFor={Exception.class})
	public int deleteExpiredReserve() throws Exception {
		
		int count = 0;
		
		Date nowTime = new Date();
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");		
		
		List<HashMap<String, Object>> m_reservCdList = reserveDAO.selectExpireReserveCdList(format.format(nowTime));
		
		List<String> reservCdList = new ArrayList<String>();
		for(HashMap<String, Object> obj : m_reservCdList){
			reservCdList.add(obj.get("RESERV_CD").toString());
		}
		
		if(reservCdList.size() > 0){
			reserveDAO.insertExpiredReserveMaster(format.format(nowTime));
			count += reserveDAO.deleteExpiredReserveMaster(reservCdList);
			count += reserveDAO.deleteExpiredReserve(reservCdList);				
		}		
		
		return count;
	}

트랜잭션을 처리하다 보면 자주 발생하게 되는 상황이 있다.
바로 트랜잭션 동작 도중 다른 트랜잭션을 호출(실행)하는 상황이다.
피호출 트랜잭션의 입장에서는 호출한 쪽의 트랜잭션을 그대로 사용할 수도 있고, 새롭게 트랜잭션을 생성할 수도 있다.

전자의 경우 중간에 오류가 발생하면 모든 트랜잭션이 롤백될 것이고, 후자의 경우 오류가 발생한 트랜잭션이 롤백 될 것이다.
이러한 트랜잭션 관련 설정은 @Transactional의 propagation 속성을 통해 지정할 수 있다.

propagation에 지정할 수 있는 값은 다양하다. 그것들을 간단히 정리하면 아래와 같다.

  1. REQUIRED: 현재 진행중인 트랜잭션이 있으면 그것을 사용하고, 없으면 생성한다. [DEFAULT 값]
  2. MANDATORY: 현재 진행중인 트랜잭션이 없으면 Exception 발생. 없으면 생성한다.
  3. REQUIRES_NEW: 항상 새로운 트랜잭션을 만듦 (트랜잭션을 분리)
  4. SUPPORTS: 현재 진행중인 트랜잭션이 있으면 그것을 사용. 없으면 그냥 진행.
  5. NOT_SUPPORTED: 현재 진행중인 트랜잭션이 있으면 그것을 미사용. 없으면 그냥 진행.
  6. NEVER: 현재 진행중인 트랜잭션이 있으면 Exception. 없으면 그냥 진행.
  • 트랜잭션 주의 사항

    트랜잭션은 꼭 필요한 최소한의 범위로 수행해야 한다. 왜냐하면 일반적으로 데이터베이스 커넥션은 갯수가 제한적이기 때문에 각 트랜잭션에서 커넥션을 소유하는 시간이 길어진다면, 그 이후에 사용 가능한 커넥션의 갯수가 줄어든다. 그러다 어느 순간 다른 트랜잭션이 수행될 때, 커넥션이 부족하여 커넥션을 받기 위해 기다리는 상황이 발생할 수 있다.

참고:

  • http://virusworld.tistory.com/120
  • https://preamtree.tistory.com/97
  • http://victorydntmd.tistory.com/129 [victolee]