개요

JAVA 8 에서 17 로 넘어가려는 움직임이 보인다. 간략하게나마 이유를 알아보고 진짜 넘어갈 필요가 있는지 한번 생각해 보자.
자세한 내용이 궁금하면 다른 사람의 글을 참고하자.

  • 목차
    • 아직도 JAVA 8을 사용하는 이유?
    • java 9 ~ 17 까지의 변경점들

 

아직도 JAVA 8을 사용하는 이유?


  • 호환성
    JAVA8이 나왔을 당시만 해도 JAVA 1.6, 1.7에서 자연스럽게 8(공식적으로 JAVA 1.8 버전부터 JAVA8로 표기하기로 함)로 업그레이드 했다. 이전 버젼들과 호환성이 좋았고 업그레이드에 큰 문제가 거의 없어서 자연스럽게 1.8을 선택했던 것 같다.
  • LTS
    JAVA8 은 LTS(Long Term Support) 버전이다. 말 그대로 Oracle사에서 오랜기간 지원해주는 버젼이라는 뜻이다. JAVA17 이 나오기 전까지 LTS버젼은 JAVA11 이 있었지만, 8보다도 지원 기간이 짧았기 때문에 시스템 운영면에서 굳이 11로 넘어가는 모험을 할 필요가 없었을 것이다. 그래서 아직까지도 8 버젼을 쓰는 곳이 많을 수 밖에 없다.
  • LTS 버젼별 지원 기간
  •   JAVA 8 : 2030년 12월
      JAVA 11 : 2026년 9월
      JAVA 17 : 2029년 9월
    

그렇다면 17도 8보다 지원기간이 짧은데 왜 넘어가야 할까?

 

JAVA 9~17 까지의 변경점들


JAVA17은 이전 버젼들의 변화들을 포함하고 있다.
내가 주목하는 점은 개발시의 이점이 있을만한 편의성 개선에 있다.
어떤 것들이 쓸모가 있을지 알아보자.

개발시 사용될 변경점들

  • Stream Class - iterate (JAVA 9)
    • 1.6 때 JAVA를 공부해서 그런가 개인적으로는 iterate 같은 함수 형태의 반복문을 비 선호하는 편이다. 물론 필요에 따라 사용하긴 했다.
      JAVA 9 에서는 iterate() 를 Override 하여 파라미터 3가지를 받는 형태의 메서드가 추가되었다.
      기존 for문 처럼 초기값, 반복될 범위, 증가값을 파라미터로 전달 할 수 있게 되었다. 그것도 반복 범위와 증가값을 람다 형태로 말이다.아니 이럴거면 기존 for문을 쓰라고 경우에 따라 JAVA 9 버젼의 iterate()를 쓸 일이 있을 것 같다.
  • interface private method (JAVA 9)
    • JAVA 8의 interface default Method에 이어 interface private Method 가 추가되었다. 인터페이스 상에서 정의해 둔 것을 상속한 클래스에서 구현할 수 없도록 하여 변경할 수 없도록 강제하는 것이 가능해졌다.
  • var (JAVA10) 타입추론 개념으로 var라는 형태를 사용할 수 있다.
    var는 몇가지 제약사항이 있다.
    • 지역변수로만 사용가능
    • 초기화 필수, null 불가
      • 타입 추론을 위해서는 초기화가 필수겠지? 그러니 null도 사용할 수 없다.

        C#을 안해봐서 그런지 JAVA에 꼭 필요한가 의문이 든다. 람다식이 나온 후로 적응하기 힘들어하는 내가 문제겠지..
        뭔가 편리할 것 같긴 한데 큰 변화라고 하긴 힘들어보인다. 오히려 사용하기 위해 공부를 해야하는 느낌이지만 일단 알아두고 나중에 써먹자.
  • switch~case문의 변화 (Java 12 ~ 17)
    기존에는 ‘case’마다 하나의 값만 지정이 가능했기 때문에 ‘break’문을 적절히 사용하면서 로직을 구성해야 해서 가독성이 떨어지는 경우가 많고 로직이 지저분해짐을 느껴 그냥 ‘if ~ else if’문으로 처리해버리는 경우가 많았다.
    switch~case문에 많은 변화가 생겼는데 JAVA17 이전까지는 ‘case’에 여러 값을 지정할 수 있게 되고, 화살표로 리턴값을 지정할 수 있게 되는 등 기존에 불편했던 case문의 기능상 편의성 개선이 많았고 JAVA17 에서는 패턴 매칭 기능, Gaurded Patterns, Null Cases 도 추가되어 기능을 확장시키려는 노력이 엿보인다. 이로써 더욱 깔끔한 코딩이 가능해지고 가독성이 향상되었지만 더 머리아파질 것 같다.
    • case 문에 여러 값 적용
      String java_lts = switch (version) {
          case 8, 11, 17 -> "LTS";
          default -> "NOT LTS";
      };
      
    • 패턴 매칭
      return switch (obj) {
          case Integer i -> "It is an integer";
          case String s -> "It is a string";
          case Employee s -> "It is a Employee";
          default -> "Unknown";
      };
      
    • Gaurded Patterns & Null Cases
      return switch (obj) {
          case Integer i -> "It is an integer";
          case String s -> "It is a string";
          case Employee employee && employee.getDept().equals("IT") -> "IT Employee";
              // case 문에 이런 로직도 사용 가능하리라고는 생각지도 못했다.
          case null -> "It is a null object";	// null case 가 가능해졌다.
          default -> "It is none of the known data types";
      };
      

 

  • Sealed Classes, Sealed Interface (JAVA 15, 17)
    • Sealed 는 ‘봉인된’ 이라는 뜻으로, 지정된 클래스만 상속 혹은 구현이 가능하고 다른 클래스에서는 상속이나 구현이 불가능하다.
    • sealed, final, non-sealed 세 가지 상태가 있다.
	/* 
	 * Game.java 파일
	 * permits 로 선언된 Nintendo, Sony class 만 Game class 를 상속할 수 있다.
	 */
	public sealed class Game permits Nintendo, Sony {}
	...
	
	/* 
	 * Nintendo.java 파일
	 * final 로 선언된 Nintendo Class. 다른 클래스에서 Nintendo 클래스를 상속할 수 없다.
	 */
	public final class Nintendo extends Game {} 	// Game Class 상속가능
	...


	/* 
	 * Sony.java 파일
	 * non-sealed 선언되었기 때문에 Sony class는 아무 클래스에서나 상속 가능하다.
	 */
	public non-sealed class Sony extends Game {}	// Game Class 상속가능
	...


	/* 
	 * Playstation.java 파일
	 * Nintendo 가 final 로 선언된 class 이기 때문에 상속할 수 없다.
	 */
	public class Playstation extends Nintendo {} // Nintendo Class 상속불가
	
	
	/* 
	 * Vita.java 파일
	 * non-sealed class 인 Sony.java 상속이 가능하다
	 */
	public class Vita extends Sony {} // 상속가능
	...
	

 

그 외 변경점들

  • JAVA17은 가장 최신의 LTS 버젼
  • Garbage Collector(GC) 병렬 처리 도입으로 인한 성능 향상 (Java 10)
  • JVM heap 영역을 시스템 메모리가 아닌 다른 종류의 메모리에도 할당 가능 (Java 10)
  • 유니코드 12.1 지원 (Java 13) …
    등등 기술하지 않은 많은 변화들이 있다.

내가 환경 구성을 선택할 수 있는 위치가 아니다보니 새로운 JAVA 버전이 나와도 특별히 관심을 기울이지 않았었다. 이번 기회로 JAVA 버젼별 특징을 정리하고 보니 몇몇 변경점에 관심이 생겼다. 특히 heap 영역을 시스템 메모리가 아닌 다른 종류의 메모리에도 할당 가능한 기능이 눈에 띄는데 이건 더 자세히 공부할 필요가 있을 것 같고, 변경된 switch~case 문 만으로도 JAVA 17 버전을 적용해보고 싶다는 생각이 든다.
회사에서는 우선 새로 구축될 시스템에서 JAVA17을 적용하고 그 이후에 레거시 시스템을 업그레이드 하는 게 작은 위험요소라도 줄일 수 있을 것 같다.

개요

Spring-data-jpa를 사용하면 Entity Manager 를 사용하지 않고 쉽게 구현 가능하지만 가끔 Entity Manager로 직접 영속성 컨텍스트를 컨트롤 해야할 필요가 생긴다. 어떤식으로 둘을 함께 사용할 수 있는지 헤딩한 내용을 기록해본다.

  • 목차
    • Spring-data-jpa 와 jpa의 차이
    • 함께 사용해보자
    • 정리

 

Spring-data-jpa 와 jpa의 차이


Spring-data-jpa  JPA를 추상화 시켜둔 개념으로, 개발자가 EntityManager로 직접 접근 없이도 쉽게 개발 할 수 있도록 도와주는 모듈이다. Spring-data-jpa를 구성하는 내부에 Entity Manager가 구현되어 있어 사용자는 Repository Interface를 정의하는 것 만으로 JPA를 손쉽게 사용할 수 있다.

spring-data-jpa Repository


흔히 사용하는 spring-data-jpa Repository 예시

@Repository
public interface workRepository {
	int insertNtisNotice(FeedMessage feedMessage) throws Exception;
	List<FeedMessage> findByMessageLikeOrderByIDDesc(String message) throws Exception;
	int insertNtisNoticeAuto(FeedMessage feedMessage) throws Exception;
	int deleteNtisNoticeAutoCheck() throws Exception;
	List<AccountInfoDTO> selectAccountInfo(AccountInfoDTO accountInfoDTO) throws Exception;
	AccountInfoDTO findByManagerByID(int id) throws Exception;
}

interface 만으로 쿼리에 접근하도록 돕는다. 이것만으로도 반복에 가까운 자바 코드가 줄어든다.

하지만 뭔가 부족하다. 어쩌다 한 두번씩 EntityManager를 사용해야 할 때가 온다.. 그럼 그냥 함께 사용해보자.

 

함께 사용해보자


SpringBoot 프로젝트로 간단하게 구현해보자.

1. 프로젝트 생성하기

스프링부트 프로젝트 만들기 에서 프로젝트를 만들거나, 또는 하단 예제처럼 이클립스에서 New Spring Starter Project 로 프로젝트를 만든다.

설정은 크게 상관없다. Gradle 이나 자바 버전을 8로 선택한 건 단지 내가 익숙해서다.

간단하게 구현 테스트만 해 볼 것이기 때문에 메모리타입 DB를 지원하는 H2 DB와 편의성이 엄청난 Lombok, 당연히 Spring Data Jpa 와 웹서비스를 구현하는 것이므로 Spring Web도 선택한다.

자주 사용하는 항목에 저게 없으면 하단 검색창에서 검색하여 선택하면 된다.

사실 제대로 선택 안해도 상관없다. 프로젝트가 생성된 후 gradle 파일을 직접 수정하고 Refresh gradle project 해주면 제대로 반영된다.

finish 하면 프로젝트가 생성된다.

2. build.gradle 파일

plugins {
	id 'java'
	id 'org.springframework.boot' version '2.4.5'
	id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
	maven { url 'https://repo.spring.io/milestone' }
	maven { url 'https://repo.spring.io/snapshot' }
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

예제를 따라 했다면 위 내용은 버젼만 좀 다르고 거의 같을 것이다.

3. H2 DB 설정하기

spring:
  datasource: 
    url: jdbc:h2:mem:demo #메모리 타입DB로 demo라는 DB에 접근한다는 뜻.
    username: sa          #사용자는 필수 입력사항.
    password:             #패스워드는 선택사항.
    driver-class-name: org.h2.Driver
  h2:
    console:
      enabled: true # H2 DB의 웹 콘솔 사용여부 설정. (http://localhost:8080/h2-console 로 접근)
  jpa:
    database: H2
    show-sql: true # jpa 에서 쿼리 출력여부 설정
    properties:
      hibernate:
        format_sql: true # 쿼리 출력시 정렬 여부 설정 

src/main/resource 경로에 application.yml 파일을 생성하고 위 내용을 입력한다. DB 콘솔 접근법은 하단에서 설명하겠다.

4. 패키지 생성, 소스 작성

위와 같이 패키지 혹은 폴더를 생성하고 다음 파일들을 작성한다. 핵심 내용만 보면

  • Member.java
    public class Member {
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      @Column(name="ID")
      private long id;
    
      private String name;
    
      @Column(nullable=true)
      private Integer age;
    }
    
    간단하게 id와 이름, 나이 컬럼을 사용하려 한다. 실행시 테이블 자동생성을 위해 id에 GeneratedValue 어노테이션과 strategy를 설정해 준다.

 

  • MemberController.java
public class MemberController {
	@Autowired
    private MemberService memberService;

    // 멤버 등록 (spring-data-jpa)
    @PostMapping(value = "/member")
    public String createMember(@RequestBody Member member){
        return memberService.createMember(member);
    }

    // 멤버 등록 (jpa EntityManager)
    @PostMapping(value = "/memberjpa")
    public String createMemberJpa(@RequestBody Member member){
        return memberService.createMemberJpa(member);
    }
	
	// 멤버 조회
    @GetMapping(value = "/member")
    public List<Member> getMember(@RequestParam(required = false, defaultValue = "") String name){
        return memberService.getMemberList(name);
    }
}

두 가지 방식의 멤버 등록을 위한 메써드를 준비하고, 등록이 잘 되었는지 확인할 조회 메서드를 하나 만든다.

 

  • MemberService.java
public class MemberService {

	@Autowired
    private MemberRepository memberRepository;

    // spring-data-jpa
    public String createMember(Member member){
    	memberRepository.save(member); // 사용자 등록 처리
        return "success";
    }

    // jpa
    public String createMemberJpa(Member member){
    	log.info("member name : " + member.getName());
    	memberRepository.createUserAuto(member);
    	return "jpa success";
	}

    public List<Member> getMemberList(String name){
        if(name.isEmpty()) {
            return memberRepository.findAll();
        } else {
        	return memberRepository.findByNameLikeOrderByName(name);
        }
    }
}

컨트롤러와 같은 목적으로 생성한다.

 

  • MemberRepository.java

Repository는 우선 spring-data-jpa를 위해 기본적인 JpaRepository를 상속하는 interface를 만들어준다.

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
	List<Member> findByNameLikeOrderByName(String name);
}

이제 JPA를 만들어본다. JpaRepository는 EntityManager에 직접 접근할 수 없으므로 우회로를 만들어주자.

추후 MemberRepository에 상속하기 위한 interface repository를 만드는데 service에서 jpa용으로 만들어 둔 함수를 사용한다.

public interface MemberCustomReposity {
	String createUserAuto(Member member);
}

그리고 해당 인터페이스를 구현할 MemberCustomReposityImpl 파일도 만든다.

@Transactional(readOnly = true)
public class MemberCustomReposityImpl implements MemberCustomReposity {
	@PersistenceContext	// 영속성 컨택스트를 spring-data-jpa 에서 사용하기 위한 어노테이션
	EntityManager em;

	@Override
	@Transactional
	public String createUserAuto(Member member){
		log.info(member.getName());
		em.persist(member);

		Member member2 = em.find(Member.class, member.getId());
		log.info(member2.getName() + " / " + member2.getId());

		return "실행 완료";
	}
}

아주 단순하게 넘어온 멤버객체 이름을 출력하고, 등록된 값의 id로 데이터를 찾아 정보를 출력해 보았다.

@PersistenceContext를 통해 EntityManager를 가져다 사용하고, 클래스 단의 트랜잭션은 readonly 상태로, 트랜잭선 처리가 필요한 메서드는 @Transactional 어노테이션을 추가해야 EntityManager가 1차캐쉬를 플러쉬 하면서 처리할 수 있다.

여기까지 작성한 내용을 MemberRepository에 추가해 주자.

@Repository
public interface MemberRepository extends JpaRepository<Member, Long>, MemberCustomReposity {
	List<Member> findByNameLikeOrderByName(String name);
}

말은 길었지만 정작 코드는 참 쉽다. 이제 서버를 구동하고 테스트를 해보자.

 

5. 테스트

컨트롤러에 작성한 접근 방식을 이용하여 하나씩 테스트해보자.
먼저 spring-data-jpa 로 등록.

success 응답이 왔다.
다음 JPA EntityManager로 등록.


콘솔 로그도 정상 확인.


제대로 등록 되었는지 조회해보자.

제대로 등록되어 있다.

DB에 직접 붙어서 확인하고 싶다면,

http://localhost:8080/h2-console 로 접속한다.

이와같은 화면을 볼 수 있는데 이것이 h2 DB console 이다. 다른건 그대로 두고, JDBC URL 만 본인 설정에 맞게 변경한다.

연결 테스트 해보고 접속해보자.

그럼 SQL을 직접 작성해 볼 수 있는 창이 보인다. 마음껏 쿼리를 날려보자.

 

정리


  • spring-data-jpa를 기반으로 EntityManager를 위한 repository 를 따로 구현한다.
  • EntityManager를 사용하기 위해 @PersistenceContext 어노테이션을 이용한다.
  • JPA는 트랜잭션 기반으로 동작하기 때문에 EntityManager 설정 시 트랜잭션 설정을 해준다. 정도가 되겠다.

해당 내용의 소스 파일은 Github 에 올려두었다.

하나하나 헤딩하는 과정이 힘들지만 모레알처럼 작은 지식들 하나하나가 쌓여가고 있다고 행복회로를 돌려본다.

■ JPA (JAVA PERSISTENCE API) 란?

자바 ORM (oriented-releational Mapping) 기술에 대한 API 표쥰 명세.

쉽게 말해 인터페이스의 집합체.

JPA를 이용하기 위해서는 이를 구현 해 줄 수 있는 프레임워크를 사용해야 한다. 

Hibernate, EclipseLink, DataNucleus 가 주로 쓰이는 프레임워크이고

가장 대중적인 프레임워크는 Hibernate (하이버네이트).

 

JPA를 왜 사용해야 하는가?

1. 생산성 향상

  반복적인 코드와 기본 CRUD 용 쿼리를 작성하는 수고를 덜어준다. 

2. 유지보수 편의성

  필드 추가, 삭제 등의 수정사항을 개발자가 직접 유지보수를 위해 코딩해야 했던 노력이 줄어든다. (JPA가 대신 처리함)

3. 페러다임의 불일치 해결

  JAVA 의 객체기반 - DB의 관계형 데이터 기반 

 코드가 디테일 해 질수록 두 페러다임의 불일치가  발생하는데 JPA가 중간에서 해결 가능.

4. 성능 향상

  JPA는 어플리케이션과 DB 사이에서 동작하므로 이렇게 계층이 하나 더 있으면 최적화를 시도해 볼 수 있는 것이 많다. 

5. 데이터 접근 추상화와 벤더 독립성

  JPA는 어플리케이션과 DB 사이에 추상화된 데이터 접근 계층을 제공하여 어플리케이션이 특정 DB 기술에 종속되지 않도록 한다. (페이징 처리의 경우 각 DB마다 사용법이 다르지만, JPA를 사용하면 어떤 DB를 사용하는지 알려주기만 하면 된다)

6. 표준 기술

 JAVA의 ORM 표준 기술이기 떄문에 다른 구현기술로 손쉽게 변경 가능하다.

 

* 개발자는 엔티티 객체를 중심으로 개발하고 데이터베이스에 대한 처리는 JPA 에게 맡겨야 한다.

만남의 인연 10개가 모일때마다 상시를 돌리는데

이번달에 10개가 모였다.

바로 돌렸는데

그냥 타이나리나 나왔으면 좋겠다~ 하면서 돌린걸 미호요가 들었나? 

타이라니가 똭!!! 그것도 5성 스택이 얼마 안된 상태로 나왔다.!!

진을 뽑고 대략 30뽑? 쯤에 타이라니가 나왔다!!

이게 바로 비틱이로구나~!!!!

ㅋㅋㅋ

수메르 캐릭 하나씩 모으는 재미가 쏠쏠 ㅎㅎ

이젠 타이라니를 키워봐야겠다 ㅋㅋㅋ

이번 3.4 패치에 야란이 복각했다.

1년넘게 전무는 절대 안뽑는 공기유져(공월, 기행만 결제하는) 였지만

애정캐라 전무까지 주고싶은 마음에 갓성능 1돌에 전무까지 뽑았다.

절연을 그렇게나 많이 돌았지만.. 치확뚝이 워낙 없다보니 치피뚝으로 세팅.

원충이 좀 낮지만 그래도 1돌이라 딜사이클 돌릴만 하다.

1돌이 된 야란은 정말 갓갓갓.. 

최고다.!!

 

----------------------------------------------------- 230212 이전 -----------------------------------------------------

물원소 활 캐릭 야란.

너무 이쁘다. 나오자마자 최애가 되어버렸다.

나올 당시 물의신이라 불리는 행추의 상위 호환이라고 하는 말이 나왔었는데

그 말이 딱 맞는 것 같다. 명함만으로도 성능이 좋다.

 

스킬 / 특성

난 메인딜로 쓰지 않기 때문에 평타는 안찍고,

원소스킬 원소폭발은 국룰의 8랩.

원소스킬은 잠깐동안 홀드하고 있으면 질주모드에 들어가면서 스피드가 빨라지고, 적에게 다가가면 야란이 가진 실로 적들을 묶을 수 있다. 질주 모드가 끝나면 적들이 넉백을 당하며 야란의 HP의 최대치에 따라 물원소 피해를 준다.

스킬 지속시간 동안 스피드가 상승하여 이동시에도 많이 쓰인다. 

원소폭발은 시전시 물원소 범위 피해를 주며, 주사위가 생겨나 캐릭을 따라다니는데 이 주사위는 평타공격시에 1초에 한번씩 물원소 피해를 준다. 이게 행추의 우렴과 같은 매커니즘이라고 보면 될 것 같다. 이것도 야란 HP최대치의 영향을 받기 때문에 야란의 HP가 높을수록 유리하다.

그러니까 야란이 궁을 쓰고 메인딜러가 나와서 평타로 적들을 패면 야란의 주사위가 같이 때려줌. 이게 원소 효과가 발동하면서 어마어마한 딜을 가져온다. 

무기

원충을 돕기 위해 페보활을 들었다. 메인딜로 쓴다면 이벤트 보상으로 주는 노을도 좋겠지만

난 서포터로 쓰기 때문에 페보활을 줬다. 사실 이것 말고는 야란에게 줄 활을 가지고 있지 않다.

전무가 젤 좋겠지만.. 무기 뽑은 안하니까 패스.

성유물

야란도 절연이 젤 좋다더라. 무지성으로 껴줬다.

서포터 권장 스팩은 치확 50% 치피 200% 원충 150% 정도라고 하더라.

무기에서 원충을 챙겼기 때문에 시계에서 HP를 챙길 수 있었다. 

성배는 물원소피해.

왕관은 치확이나 치피를 가면 되는데 난 치확뚝을 쓰고 다른 성유물에서 치피를 챙기고 싶었지만

절대 마음처럼 되지 않는다. 이게 나름 치피를 포기하면서 치확을 챙긴거다..

성유물은 절대 마음대로 되지 않는다는 것. 주는대로 써야지 어쩌겟나..

스코어

내 캐릭터들은 대부분 18~19 스코어인 듯..

모험랩 56랩~57랩 기준으로 이게 무소과금러의 평균 스팩인지, 내가 낮은건지는 잘 모르겠다.

아무튼 20이 가까우니 일단 이정도만 키우고 다른 캐릭 키우러간다.

+ Recent posts