반응형

Spring Boot 2.x -> 3.0 차이

  • 최소 요구사항 변경 (M4 기준)
    • Gradle 7.5
    • Groovy 4.0
    • Jakarta EE 9
    • Java 17
    • Kotlin 1.6
    • Hibernate 6.1
    • Spring Framework 6
  • AOT maven, gradle 플러그인 제공
  • native 지원 기능 확대

spring 3.0 지원 라이브러리

Spring의 AOT란? (Ahead Of Time)

Spring AOT 엔진은 빌드 시 스프링 애플리케이션을 분석하고 최적화하는 도구입니다. 또한 AOT 엔진은 GraalVM Native Configuration이 필요로 하는 reflection configuration을 생성해줍니다. 이것은 Spring native 실행 파일로 컴파일 하는데 사용되고 이후에 애플리케이션의 시작 시간과 메모리 사용량을 줄일 수 있게 됩니다.

 

Spring Boot 3.0 AOT

Spring Boot 3.0 AOT 부분 확대

위 그림에서 보면 AOT가 Spring Boot 환경에서 하는 일들과 순서를 알 수 있습니다. 간단하게 얘기하자면 Bytecode를 분석하고 최적화해서 좀 더 실행하기에 빠르고 메모리적으로 효율적인 코드를 만듭니다.

(+ spring의 native-image는 JVM에서 실행되는 파일에 비해 빌드 시간은 길고 시작시간이 짧고 메모리는 적게 사용하게 된다.)

AOT 적용 효과

  • 런타임시 Spring 인프라를 적게 사용
  • 런타임 시 검증할 조건 수 감소
  • 리플렉션을 줄이고 프로그래밍적 Bean 등록 방식 사용

 

제일 메이저한 변화는 java17 이다. 또한 R2DBC 지원도 눈에 띈다.

 

 

참고문헌

https://www.baeldung.com/spring-boot-3-spring-6-new

https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Release-Notes  - github release note (자세한 버젼 정보)

반응형
반응형

1. 자바 11 지원

2. 스프링 데이터 JPA, lazy 모드 지원

3. 의존성이 많이 변경됨.

  • 스프링 프레임워크 5.0 -> 스프링 프레임워크 버전 5.1

 

  • JUnit 4.12 -> JUnit 5.2
  •  

 

  • 톰캣 8.5.39 -> 톰캣 9
  • BIO 커넥터 사라지고 NIO 커넥터 기본으로 사용
  • HTTP/2 지원
  • 웹소켓 2.0 지원
  • 서블릿 4.0 / JSP 2.4 지원
  • 참고

 

  • 하이버네이트 5.2 -> 하이버네이트 5.3
  • JPA 2.2 지원
  • Java 8의 Date와 Time API 지원
  • 참고

 

4. 빈 오버라이딩을 기본으로 허용하지 않도록 변경

만약 허용해야 하면 

spring.main.allow-bean-definition-overriding to true.

과 같이 설정 변경.

 

5. Acutator에 “/info”와 “/health” 공개하도록 바뀜

 

6.프로퍼티 변경

7. 로깅 그룹

 

 

어떤버젼을 사용해야 하는가

부트 버젼은 GA 라고 명시된 버젼을 사용하는것 권장. 스냅샷 버젼은 권장 X

https://spring.io/projects/spring-boot#learn - > 링크에서 확인 가능.

 

 

https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.1-Release-Notes [공식문서]

 

반응형
반응형

@Configuration

@Configuration: 이 어노테이션을 단 클래스는 빈 설정을 담당하는 클래스가 된다. 이 클래스 안에서 @Bean 어노테이션이 동봉된 메소드를 선언하면, 그 메소드를 통해 스프링 빈을 정의하고 생명주기를 설정하게 된다. 자바 기반의 config가 싫다면 XML 쓰면 된다.

@Configuration을 클래스에 적용하고 @Bean을 해당 Class의 method에 적용하면 @Autowired로 Bean을 부를 수 있다.

 

@ComponentScan: @Configuration 어노테이션과 함께 쓰면, 이 클래스는 자바 빈 설정 클래스이며, 이 @ComponentScan 어노테이션에서 제공하는 package 속성을 통해 스프링 빈 범위를 정의할 수 있다. 복수개를 지정하고 싶다면 자바 8에서는 단순히 같은 어노테이션을 쓰면 되지만, 자바 7 이하에서는 @ComponentScans 어노테이션 안에 넣으면 된다.

 

@Import: @Configuration 어노테이션이 선언된 스프링 설정 클래스를 가져온다. 클래스명을 기입하면 된다. 예를 들면, DB 빈 설정과 DB를 활용하는 빈 (Mybatis 등)을 같이 써야 할 경우 사용하면 된다. 이게 있으면 빈 선언 시 실행 순서도 보장되기 때문에 가끔 튀어나오는 빈 선언 오류도 막을 수 있다.

 

 

@Component: 빈을 선언하는 클래스를 작성하기 위해 넣는 어노테이션이다. 개발자가 직접만든 클래스를 빈으로 만들기 위한 어노테이션이다. 이것도 당연히 @ComponentScan 의 주요 스캔 대상이다. Component에 대한 추가 정보가 없다면 Class의 이름을 camelCase로 변경한 것이 Bean id로 사용된다. 하지만 @Bean과 다르게 @Component는 name이 아닌 value를 이용해 Bean의 이름을 지정한다.

@Component
public class MyComponent {
}

@Component(value="myComponent")
public class MyComponent {
}

@Bean: @Configuration 선언한 빈 설정 클래스에 빈 선언을 담당하는 어노테이션으로, 메소드에만 넣을 수 있다. 보통은 메소드 이름이 곧 빈 이름으로 탄생한다.

 

@Configuration
public class ApplicationConfig {    
    @Bean
    public void configMethod(){
    }   
}​

@Bean에 아무런 값을 지정하지 않았으므로 Method 이름을 camelCase로 변경한 것이 Bean id로 등록된다.
method 이름이 configMethod()인 경우 configMethod가 Bean id 이다.

또는 @Bean(name="configMethod") 로 지정 가능

 

@Service

@Service: 사실 @Component 어노테이션과 별 차이가 없어 보인다. 하지만 이녀석과 @Component 어노테이션의 차이점을 알려면 캡슐화 개념에 대한 이해가 필요하다. 이 말은 즉슨, 보통 이 어노테이션을 달은 클래스에는 '비즈니스 레이어' 로직에 맞게 짜기 때문에 보통 별도의 데이터를 가지고 있지 않을 것이다. 이건 아주 올바른 사용 케이스이다.

이 어노테이션을 단 클래스는 업무 메소드만 정의하면 된다. 다른 빈에서 가져와 처리를 하고 결과를 제공하는 그저 비즈니스 로직만 정의하면 된다.

보통 스프링 MVC에 자주 쓰기 때문에 MVC 소속 어노테이션이라고 생각하지만 MVC 없어도 선언 가능하다.

Service Class에서 쓰인다.
비즈니스 로직을 수행하는 Class라는 것을 나타내는 용도이다.

@Repository

DAO class에서 쓰인다.
DataBase에 접근하는 method를 가지고 있는 Class에서 쓰인다.

@Autowired

@Autowired:  이 어노테이션은 필드, 메소드, 생성자에다가 넣을 수 있다.  스프링 빈을 가져오는 가장 기본적인 방법이다.

Type에 따라 알아서 Bean을 주입 해준다.

해당 클래스에 맞는 빈을 가져오는데, 만약 하나만 있으면 잘 되지만, 만약 2개 이상이면 시그니처 명칭에 맞는 빈을 가져오지만, 그 외의 경우는 NoUniqueBeanDefinitionException 예외가 발행한다.

@Inject

@Autowired 어노테이션과 비슷한 역할을 한다.

@Qualifier("id")

@Autowired와 같이 쓰이며, 같은 타입의 Bean 객체가 있을 때 해당 아이디를 적어 원하는 Bean이 주입될 수 있도록 하는 Annotation이다.
같은 타입이 존재하는 경우 ex) 동물 = 원숭이, 닭, 개, 돼지

같은 타입의 Bean이 두 개 이상이 존재하는 경우 Spring이 어떤 Bean을 주입해야 할지 알 수 없어서 Spring Container를 초기화하는 과정에서 예외를 발생시킨다.

이 경우 @Qualifier을 @Autowired와 함께 사용하여 정확히 어떤 bean을 사용할지 지정하여 특정 의존 객체를 주입할 수 있도록 한다.

 

@Resource

@Autowired와 마찬가지로 Bean 객체를 주입해주는데 차이점은 Autowired는 타입으로, Resource는 이름으로 연결해준다.

javax.annotation.Resource
표준 자바(JSR-250 표준) Annotation으로, Spring Framework 2.5.* 부터 지원 가능한 Annotation이다.

Annotation 사용으로 인해 특정 Framework에 종속적인 어플리케이션을 구성하지 않기 위해서는 @Resource를 사용할 것을 권장한다.

@Resource를 사용하기 위해서는 class path 내에 jsr250-api.jar 파일을 추가해야 한다.
필드, 입력 파라미터가 한 개인 bean property setter method에 적용 가능하다.

 

@PostConstruct, @PreConstruct

의존하는 객체를 생성한 이후 초기화 작업을 위해 객체 생성 전/후에(pre/post) 실행해야 할 method 앞에 붙인다.

@PreDestroy

객체를 제거하기 전(pre)에 해야할 작업을 수행하기 위해 사용한다.

@PropertySource

해당 프로퍼티 파일을 Environment로 로딩하게 해준다.

클래스에 @PropertySource("classpath:/settings.properties")라고 적고 클래스 내부에 @Resource를 Environment타입의 멤버 변수앞에 적으면 매핑된다.

@ConfigurationProperties

yaml파일 읽는다.

default로 classpath:application.properties 파일이 조회된다.

속성 클래스를 따로 만들어두고 그 위에 (prefix="mail")을 써서 프로퍼티의 접두사를 사용할 수 있다.

mail.host = mailserver@mail.com

mail.port = 9000

mail.defaultRecipients[0] = admin@mail.com

mail.defaultRecipients[1] = customer@mail.com

 

@Lookup

이 어노테이션이 선언된 메소드는 리턴 타입을 가진 스프링 빈을 뱉는다. 메소드 본문을 아무리 화려하게 작성해도 씹힌다. 그렇게 작용하도록 AOP 디자인되어 있기 때문이다. 주로 Provider<T> 클래스 감싼 빈 클래스처럼, 싱글톤 빈에 대한 새 인스턴스를 가져오려고 할 때 사용한다. 또다른 사용법도 있는데 이건 나중에 다루도록 하겠다.

 

@Primary

@Configuration 선언한 빈 설정 클래스에 빈 선언을 담당하면서, 기본 빈 요소를 정의할 때 사용한다. 만약 같은 클래스로 여러 개의 빈을 설정했을 때, 이 어노테이션을 추가로 달면, @Autowired 어노테이션을 통해 빈을 주입 시 이 어노테이션을 가진 빈을 우선적으로 가져온다고 보면 된다. 빈 관리에 편리한 기능이니 여러 빈을 다룰 때 참고하도록.

@Required

일반적인 빈 클래스를 초기화 시, 반드시 설정(setter)해야 하는 setter 메소드를 정의할 때 사용한다. @Bean 선언 및 XML 방식의 빈 선언 시, 만약 이 어노테이션을 통해 빈 속성을 정의하지 않으면 BeanInitializationException 예외가 발생한다. 선언적 어노테이션(@Component 등)에도 넣을 수 있는데, 이 때는 @Autowired(required = false) 어노테이션 주입을 통해서 씹을 수 있다.

setter method에 적용해주면 Bean 생성시 필수 프로퍼티 임을 알린다.

Required Annotation을 사용하여 optional 하지 않은, 꼭 필요한 속성들을 정의한다.

영향을 받는 bean property를 구성할 시에는 XML 설정 파일에 반드시 property를 채워야 한다.
엄격한 체크, 그렇지 않으면 BeanInitializationException 예외를 발생

 

@Value

생성자, setter 따위의 메소드, 필드 등에다가 스프링에서 설정한 값을 주입할 수 있다. 주로 application.properties 및 스프링이나 자바 property 값을 가져올 때 쓴다.

properties에서 값을 가져와 적용할 때 사용한다.

@Value("${value.from.file}")

private String valueFromFile; 이라고 구성되어 있으면 value.from.file의 값을 가져와서 해당 변수에 주입해준다.

spEL을 이용해서 조금 더 고급스럽게 쓸 수 있다.

@Value(#{systemProperties['priority'] ?: 'some default'})

 

@DependsOn

@Bean  @Component 등에 정의 및 선언한 빈 생성 시 요구되는 다른 빈을 정의할 때 사용한다. 이 어노테이션을 달면, 이 어노테이션에서 설정한 빈이 등록되지 전까지 어노테이션이 달린 빈을 선언하지 않게 된다. @Import 어노테이션이 클래스 단위 종속성 관리라면, @DependsOn 어노테이션은 빈 단위의 종속성 관리라고 보면 된다.

 

@Lazy

이 어노테이션을 선언한 빈이나 빈 getter 및 필드는 다른 빈처럼 스프링 시작 시 빈 초기화를 하는 것이 아닌, 처음 가져올 때 빈 초기화를 하게 된다, 만약 같은 빈을 가져올 때, 하나라도 이 어노테이션이 달리지 않으면 평소처럼 스프링 시작시 빈을 초기화하게 되므로, @Bean  @Component 어노테이션과 같이 선언하거나, 가져올 때 @Lazy 어노테이션이 다 붙어있는지 확인해야 한다.

@Component나 @Bean Annotation과 같이 쓰는데 Class가 로드될 때 스프링에서 바로 bean등록을 마치는 것이 아니라 실제로 사용될 때 로딩이 이뤄지게 하는 방법이다.

 

@Scope: 빈의 생명주기를 설정한다. 기본값은 뭐 많이 쓰는 singleton 이고, 그다음 매 가져올 때마다 빈을 생성해서 보내주는 prototype 이 있으며, 스프링 MVC가 있을 경우 요청 단위인 request, 세션 단위인 session  globalSession 생명주기를 추가로 설정할 수 있다. 또한 아예 커스텀 생명주기 설정도 가능하다.

 

@Profile: 이 어노테이션이 달린 빈은 특정 프로필에서만 동작하도록 설정할 수 있다. 예를 들어 spring.profile=dev 설정 시 @Profile("dev") 일 때만 빈이 초기화된다. 다른 프로필로 하면 없는 빈이 되기 때문에 빈 관리 잘 하도록.

Spring boot 어노테이션

@SpringBootApplication

 스프링 부트는 기본적으로 이 어노테이션 기준으로 동작하도록 되어 있다. 스프링에서 필수적인 초기화를 담당하는 @EnableAutoConfiguration, @ComponentScan, @Configuration 어노테이션이 함축되어 있다.

이 클래스를 기준으로 빈 스캔을 하게 되며, 여기다가도 @Bean 어노테이션을 이용한 빈 정의도 가능하다.

@EnableAutoConfiguration

스프링 부트의 핵심 어노테이션으로, 여태까지 Spring boot 없이 XML이던 자바던 필수적으로 annotation-driven 이나 annotation-config 등... 필수적으로 스프링에 세팅하는 왠만한 것들을 자동 설정하여 일단 돌아가게 하도록 도와주는 어노테이션이다.

Spring Application Context를 만들 때 자동으로 설정하는 기능을 켠다.

classpath의 내용에 기반해서 자동으로 생성해준다.

만약 tomcat-embed-core.jar가 존재하면 톰캣 서버가 setting된다.

Spring MVC 어노테이션

@Controller

@Controller: 이 어노테이션을 통해 웹 요청의 기준을 담당한다. 이 어노테이션을 통해 빈 등록과 동시에 라우팅 테이블에 등록하는 중요한 어노테이션이다.

API와 view를 동시에 사용하는 경우에 사용한다.
대신 API 서비스로 사용하는 경우는 @ResponseBody를 사용하여 객체를 반환한다.
view(화면) return이 주목적

@ResponseBody

@ResponseBody: 컨트롤러 메소드 리턴값에 이 어노테이션을 선언함으로써 스프링은 해당 응답 객체를 클라이언트가 요구하는 요청 내용 유형(Content-Type)에 따라 응답하도록 도와주는 어노테이션이다. 보통은 Jackson 모듈에 의해 json 유형으로 응답해줄 것이며, 클라이언트가 xml 응답을 요청하면 xml 응답을 해주기도 하고, 경우에 따라 응답 유형을 mimeType 에 따라 설정할 수 있다.(예: MessagePack 등)

@RestController

 이 어노테이션을 달면, 모든 컨트롤러 메소드는 @ResponseBody 어노테이션이 달린 반환 값을 달고 다니게 된다. 

Spring에서 Controller 중 View로 응답하지 않는, Controller를 의미한다.

method의 반환 결과를 JSON 형태로 반환

view가 필요없는 API만 지원하는 서비스에서 사용한다.
Spring 4.0.1부터 제공

@RestController = @Controller + @ResponseBody

 

@RequestMapping(method = RequestMethod.GET, value = "/path"):

메소드에 달면, 해당 메소드는 이 어노테이션이 설정한 경로를 호출했을 때 메소드가 설정한 응답값을 받게 되며, 컨트롤러 클래스에 달면, 클래스에 설정한 경로를 기준삼아 각 메소드는 클래스 경로의 하위 경로로 추가되어 경로가 잡히게 된다.

 method 설정으로 GET만 받거나, POST로 받는 등 특정 HTTP 메소드에만 응답 가능하도록 설정할 수 있으며, GET 의 경우 @GetMapping 같이 메소드 별칭 어노테이션이 있으니 이걸 활용해도 된다.

 consumes 속성으로 요청 값에 대한 유형을 한정지을 수 있으며, produces 속성을 통해 응답 유형을 강제할 수 있다. 이들 둘이 설정 시 지정 타입 안맞으면 400 Bad Request 응답을 뱉도록 스프링에서 설정되어 있다.

 

@RequestParam(value="name", defaultValue="World")

컨트롤러 메소드 인자에서 요청값을 받을 때, 요청 주소(URL)에 ? 뒤로 시작하는 질의 문자열, POST 전송 시 요청받을 키/값, 등을 받을 수 있으며, required 속성으로 필수 여부를 지정할 수 있고, defaultValue 속성을 통해 요청이 없을 경우 대체 기본값을 설정할 수 있다. 만약 required = true 일 경우 해당 파라미터 없이 호출하면 400 Bad Request 응답을 뱉도록 스프링에서 설정되어 있다. 

Spring Webflux 사용시 주의!: Spring WebFlux 사용시 MVC 패턴을 사용할 수 있으나, @RequestParam 어노테이션은 요청 주소 뒤에 질의 문자열만 받게 된다. 만약 POST 를 통해 얻은 값을 얻고 싶으면, @ModelAttribute 어노테이션을 사용해야 한다.

 

@PathVariable("placeholderName"): 동적 라우팅 구성 시, (예: /path/to/{placeholderName}) 동적 라우팅에 대한 바인딩 값을 가져오는 인자 어노테이션이다. 길게 설명할 거 없이 동적 라우팅에서 동적 값을 가져올 때 없어서 안될 어노테이션이다.

@Controller
// 1) Class Level
//모든 메서드에 적용되는 경우 “/home”로 들어오는 모든 요청에 대한 처리를 해당 클래스에서 한다는 것을 의미
@RequestMapping("/home") 
public class HomeController {
    /* an HTTP GET for /home */ 
    @RequestMapping(method = RequestMethod.GET)
    public String getData(Model model) {
        ...
    }
    /*
    2) Handler Level
    요청 url에 대해 해당 메서드에서 처리해야 되는 경우
    “/home/data” POST 요청에 대한 처리를 addData()에서 한다는 것을 의미한다.
    value: 해당 url로 요청이 들어오면 이 메서드가 수행된다.
    method: 요청 method를 명시한다. 없으면 모든 http method 형식에 대해 수행된다.
    */
    /* an HTTP POST for /home/data */ 
    @RequestMapping(value = "/data", method = RequestMethod.POST) 
    public String addData(Employee employee) {
        ...
    }
}

@CookieValue

쿠키 값을 parameter로 전달 받을 수 있는 방법이다.

해당 쿠키가 존재하지 않으면 500 에러를 발생시킨다.

속성으로 required가 있는데 default는 true다.
false를 적용하면 해당 쿠키 값이 없을 때 null로 받고 에러를 발생시키지 않는다.

// 쿠키의 key가 auth에 해당하는 값을 가져옴 
public String view(@CookieValue(value="auth")String auth){...};

@CrossOrigin

CORS 보안상의 문제로 브라우저에서 리소스를 현재 origin에서 다른 곳으로의 AJAX요청을 방지하는 것이다.

@RequestMapping이 있는 곳에 사용하면 해당 요청은 타 도메인에서 온 ajax요청을 처리해준다.

//기본 도메인이 http://jeong-pro.tistory.com 인 곳에서 온 ajax요청만 받아주겠다.
@CrossOrigin(origins = "http://jeong-pro.tistory.com", maxAge = 3600)

@ModelAttribute

view에서 전달해주는 parameter를 Class(VO/DTO)의 멤버 변수로 binding 해주는 Annotation이다.

binding 기준은 <input name="id" /> 처럼 어떤 태그의 name값이 해당 Class의 멤버 변수명과 일치해야하고 setmethod명도 일치해야한다.

@Controller
@RequestMapping("/person/*")
public class PersonController{
	@RequestMapping(value = "/info", method=RequestMethod.GET)
    //view에서 myMEM으로 던져준 데이터에 담긴 id 변수를 Person타입의 person이라는 객체명으로 바인딩.
	public void show(@ModelAttribute("myMEM") Person person, Model model){ 
       model.addAttribute(service.read(person.getId())); 
    }
}

@SessionAttributes

Session에 data를 넣을 때 쓰는 Annotation이다.

@SessionAttributes("name")이라고 하면 Model에 key값이 "name"으로 있는 값은 자동으로 세션에도 저장되게 한다.

 

@Valid

유효성 검증이 필요한 객체임을 지정한다.

@InitBinder

@Valid 애노테이션으로 유효성 검증이 필요하다고 한 객체를 가져오기전에 수행해야할 method를 지정한다.

@RequestAttribute

Request에 설정되어 있는 속성 값을 가져올 수 있다.

@RequestBody

요청이 온 데이터(JSON이나 XML형식)를 바로 Class나 model로 매핑하기 위한 Annotation이다.

POST나 PUT, PATCH로 요청을 받을때에, 요청에서 넘어온 body 값들을 자바 타입으로 파싱해준다.

HTTP POST 요청에 대해 request body에 있는 request message에서 값을 얻어와 매핑한다.
RequestData를 바로 Model이나 클래스로 매핑
한다.

이를테면 JSON 이나 XML같은 데이터를 적절한 messageConverter로 읽을 때 사용하거나 POJO 형태의 데이터 전체로 받는 경우에 사용한다.

@RequestMapping(value = "/book", method = RequestMethod.POST)
public ResponseEntity<?> someMethod(@RequestBody Book book) {
// we can use the variable named book which has Book model type.
try {
   service.insertBook(book);
} catch(Exception e) {
    e.printStackTrace();
}

// return some response here
}

iiiiiii

@RequestHeader

Request의 header값을 가져올 수 있다. 메소드의 파라미터에 사용한다.

//ko-KR,ko;q=0.8,en-US;q=0.6 
@RequestHeader(value="Accept-Language")String acceptLanguage 로 사용

@RequestParam

@PathVariable과 비슷하다.
request의 parameter에서 가져오는 것이다. method의 파라미터에 사용된다.
?moviename=thepurge 와 같은 쿼리 파라미터를 파싱해준다.

HTTP GET 요청에 대해 매칭되는 request parameter 값이 자동으로 들어간다.
url 뒤에 붙는 parameter 값을 가져올 때 사용한다.

http://localhost:8080/home?index=1&page=2

@GetMapping("/home")
public String show(@RequestParam("page") int pageNum {
}

위의 경우 GET /home?index=1&page=2와 같이 uri가 전달될 때 page parameter를 받아온다.
@RequestParam 어노테이션의 괄호 안의 문자열이 전달 인자 이름(실제 값을 표시)이다.

@RequestMapping(value = "/search/movie", method = RequestMethod.GET)
public ResponseEntity<?> someMethod(@RequestParam String moviename) {
// request URI would be like '/search/movie?moviename=thepurge'
try {
   List<Movie> movies = service.searchByMoviename(moviename);
} catch(Exception e) {
   e.printStackTrace();
}
// return some response here
}

@RequestPart

Request로 온 MultipartFile을 바인딩해준다.

@RequestPart("file") MultipartFile file

@ResponseBody

HttpMessageConverter를 이용하여 JSON 혹은 xml 로 요청에 응답할수 있게 해주는 Annotation이다.
view가 아닌 JSON 형식의 값을 응답할 때 사용하는 Annotation으로 문자열을 리턴하면 그 값을 http response header가 아닌 response body에 들어간다.

이미 RestController Annotation이 붙어 있다면, 쓸 필요가 없다.
허나 그렇지 않은 단순 컨트롤러라면, HttpResponse로 응답 할 수 있게 해준다.

만약 객체를 return하는 경우 JACKSON 라이브러리에 의해 문자열로 변환되어 전송된다.

context에 설정된 viewResolver를 무시한다고 보면된다.

@PathVariable

method parameter 앞에 사용하면서 해당 URL에서 {특정값}을 변수로 받아 올 수 있다.

@RequestMapping(value = "/some/path/{id}", method = RequestMethod.GET)
public ResponseEntity<?> someMethod(@PathVariable int id) {
}

HTTP 요청에 대해 매핑되는 request parameter 값이 자동으로 Binding 된다.

uri에서 각 구분자에 들어오는 값을 처리해야 할 때 사용한다.

REST API에서 값을 호출할 때 주로 많이 사용한다.

http://localhost:8080/index/1

@PostMapping("/index/{idx}")
@ResponseBody
public boolean deletePost(@PathVariable("idx") int postNum) {
return postService.deletePost(postNum);
}

@RequestParam와 @PathVariable 동시 사용 예제

@GetMapping("/user/{userId}/invoices")
public List<Invoice> listUsersInvoices(@PathVariable("userId") int user,
	                                  @RequestParam(value = "date", required = false) Date dateOrNull) {
}

위의 경우 GET /user/{userId}/invoices?date=190101 와 같이 uri가 전달될 때
구분자 {userId}는 @PathVariable(“userId”)로,
뒤에 이어붙은 parameter는 @RequestParam(“date”)로 받아온다.

@ExceptionHandler(ExceptionClassName.class)

해당 클래스의 예외를 캐치하여 처리한다.

@ControllerAdvice

Class 위에 ControllerAdvice를 붙이고 어떤 예외를 잡아낼 것인지는 각 메소드 상단에 @ExceptionHandler(예외클래스명.class)를 붙여서 기술한다.

@RestControllerAdvice

@ControllerAdvice + @ResponseBody다.

@ResponseStatus

사용자에게 원하는 response code와 reason을 return해주는 Annotation이다.

@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "my page URL changed..") => 예외처리 함수 앞에 사용한다.

@EnableEurekaServer

Eureka 서버로 만들어준다.

@EnableDiscoveryClient

Eureka 서버에서 관리될 수 있는 클라이언트 임을 알려주기위한 Annotation이다.

@Transactional

데이터베이스 트랜잭션을 설정하고 싶은 method에 Annotation을 적용하면 method 내부에서 일어나는 데이터베이스 로직 전부 성공하게되거나 데이터베이스 접근중 하나라도 실패하면 다시 롤백할 수 있게 해주는 Annotation이다.

@Transaction(readOnly=true, rollbackFor=Exception.class)에서 readOnly는 읽기전용임을 알리고 rollbackFor는 해당 Exception이 생기면 롤백하라는 뜻이다.

@Transaction(noRollbackFor=Exception.class)는 해당 Exception이 나타나도 롤백하지 말라는 뜻이다.

@Transaction(timeout = 10)은 10초안에 해당 로직을 수행하지 못하면 롤백하라는 뜻이다.

메소드 내에서 Exception이 발생하면 해당 메소드에서 이루어진 모든 DB 작업을 초기화한다.
save 메소드를 통해서 10개를 등록해야 하는데 5번째에서 Exception이 발생하면 앞에 저장된 4개 까지 모두 롤백

정확히 얘기하면, 이미 넣은걸 롤백시키는건 아니며, 모든 처리가 정상적으로 됐을때만 DB에 커밋하며 그렇지 않은 경우엔 커밋하지 않는 것이다.

비지니스 로직과 트랜잭션 관리는 대부분 Service에서 관리한다.

따라서 일반적으로 DB 데이터를 등록/수정/삭제 하는 Service 메소드는 @Transactional를 필수적으로 가져간다.

@Cacheable

method 앞에 지정하면 해당 method를 최초에 호출하면 캐시에 적재하고 다음부터는 동일한 method 호출이 있을 때 캐시에서 결과를 가져와서 return하므로 method 호출 횟수를 줄여주는 Annotation이다.

주의할 점은 입력이 같으면 항상 출력이 같은 method(=순수 함수)에 적용해야한다.

그런데 또 항상 같은 값만 뱉어주는 메서드에 적용하려면 조금 아쉬울 수 있다.

따라서 메서드 호출에 사용되는 자원이 많고 자주 변경되지 않을 때 사용하고 나중에 수정되면 캐시를 없애는 방법을 선택할 수 있다.

@Cacheable(value="cacheKey"), @Cacheable(key="cacheKey")

@CachePut

캐시를 업데이트하기 위해서 method를 항상 실행하게 강제하는 Annotation 이다.

해당 Annotation이 있으면 method 호출을 항상한다. 그러므로 @Cacheable과 상충되어 같이 사용하면 안된다.

@CacheEvict

캐시에서 데이터를 제거하는 트리거로 동작하는 method에 붙이는 Annotation이다.

캐시된 데이터는 언제가는 지워져야한다. 그러지 않으면 결과값이 변경이 일어났는데도 기존의 데이터(캐시된 데이터)를 불러와서 오류가 발생할 수 있다.

물론 캐시 설정에서 캐시 만료시간을 줄 수도 있다.

@CacheEvict(value="cacheKey"), @CacheEvict(value="cacheKey", allEntries=true)
allEntries는 전체 캐시를 지울지 여부를 선택하는 것이다.

@CacheConfig

클래스 레벨에서 공통의 캐시설정을 공유하는 기능이다.

@Scheduled

Linux의 crontab처럼 정해진 시간에 실행해야하는 경우에 사용한다.

@Scheduled(cron = "0 0 07 * * ?")
"초 분 시 일 월 요일 년(선택)에 해당 메서드 호출


Lombok Annotation

@NoArgsConstructor

기본생성자를 자동으로 추가한다.

@NoArgsConstructor(access = AccessLevel.PROTECTED)

기본생성자의 접근 권한을 protected로 제한한다.
생성자로 protected Posts() {}와 같은 효과

Entity Class를 프로젝트 코드상에서 기본생성자로 생성하는 것은 금지하고, JPA에서 Entity 클래스를 생성하는것은 허용하기 위해 추가한다.

@AllArgsConstructor

모든 필드 값을 파라미터로 받는 생성자를 추가한다.

@RequiredArgsConstructor

final이나 @NonNull인 필드 값만 파라미터로 받는 생성자를 추가한다.
final: 값이 할당되면 더 이상 변경할 수 없다.

@Getter

Class 내 모든 필드의 Getter method를 자동 생성한다.

@Setter

Class 내 모든 필드의 Setter method를 자동 생성한다.

Controller에서 @RequestBody로 외부에서 데이터를 받는 경우엔 기본생성자 + set method를 통해서만 값이 할당된다.
그래서 이때만 setter를 허용한다.
Entity Class에는 Setter를 설정하면 안된다.
차라리 DTO 클래스를 생성해서 DTO 타입으로 받도록 하자

@ToString

Class 내 모든 필드의 toString method를 자동 생성한다.

@ToString(exclude = "password")
특정 필드를 toString() 결과에서 제외한다.
클래스명(필드1이름=필드1값, 필드2이름=필드2값, …) 식으로 출력된다.

@EqualsAndHashCode

equals와 hashCode method를 오버라이딩 해주는 Annotation이다.

@EqualsAndHashCode(callSuper = true)

callSuper 속성을 통해 equals와 hashCode 메소드 자동 생성 시 부모 클래스의 필드까지 감안할지 안 할지에 대해서 설정할 수 있다.

즉, callSuper = true로 설정하면 부모 클래스 필드 값들도 동일한지 체크하며, callSuper = false로 설정(기본값)하면 자신 클래스의 필드 값들만 고려한다.

@Builder

어느 필드에 어떤 값을 채워야 할지 명확하게 정하여 생성 시점에 값을 채워준다.

Constructor와 Builder의 차이

생성 시점에 값을 채워주는 역할은 똑같다.
하지만 Builder를 사용하면 어느 필드에 어떤 값을 채워야 할지 명확하게 인지할 수 있다.
해당 Class의 Builder 패턴 Class를 생성 후 생성자 상단에 선언 시 생성자에 포함된 필드만 빌더에 포함된다.

@Data

@Getter @Setter @EqualsAndHashCode @AllArgsConstructor을 포함한 Lombok에서 제공하는 필드와 관련된 모든 코드를 생성한다.

실제로 사용하지 않는것이 좋다.
전체적인 모든 기능 허용으로 위험 존재


JPA Annotation

JPA를 사용하면 DB 데이터에 작업할 경우 실제 쿼리를 사용하지 않고 Entity 클래스의 수정을 통해 작업한다.

@Entity

실제 DB의 테이블과 매칭될 Class임을 명시한다.
즉, 테이블과 링크될 클래스임을 나타낸다.

Entity Class

가장 Core한 클래스로 클래스 이름을 언더스코어 네이밍(_)으로 테이블 이름을 매칭한다.
SalesManage스.java -> sales_manager table

Controller에서 쓸 DTO 클래스란??

Request와 Response용 DTO는 View를 위한 클래스로, 자주 변경이 필요한 클래스이다.
Entity 클래스와 DTO 클래스를 분리하는 이유 View Layer와 DB Layer를 철저하게 역할 분리하기 위해서다.

테이블과 매핑되는 Entity 클래스가 변경되면 여러 클래스에 영향을 끼치게 되는 반면 View와 통신하는 DTO 클래스(Request/ Response 클래스)는 자주 변경되므로 분리해야 한다.

@Table

Entity Class에 매핑할 테이블 정보를 알려준다.
@Table(name = "USER")
Annotation을 생략하면 Class 이름을 테이블 이름 정보로 매핑한다.

@Id

해당 테이블의 PK 필드를 나타낸다.

@GeneratedValue

PK의 생성 규칙을 나타낸다.
가능한 Entity의 PK는 Long 타입의 Auto_increment를 추천
스프링 부트 2.0에선 옵션을 추가하셔야만 auto_increment가 된다.

기본값은 AUTO로, MySQL의 auto_increment와 같이 자동 증가하는 정수형 값이 된다.

@Column

테이블의 컬럼을 나타내며, 굳이 선언하지 않더라도 해당 Class의 필드는 모두 컬럼이 된다.
@Column을 생략하면 필드명을 사용해서 컬럼명과 매핑

@Column(name = "username")
@Column을 사용하는 이유는, 기본값 외에 추가로 변경이 필요한 옵션이 있을 경우 사용한다.

문자열의 경우 VARCHAR(255)가 기본값인데, 사이즈를 500으로 늘리고 싶거나(ex: title),
타입을 TEXT로 변경하고 싶거나(ex: content) 등의 경우에 사용

반응형
반응형

spring 에서 mybatis 로 bulk upsert 를 해줘야 할때 아래와 같이 작업을 했다.

 

jpa 를 사용하면 saveall 메소드로 bulk upsert 가 가능했는데, mybatis 에서는 메소드를 직접 만들다 보니 그런게 없어서, 직접 만들어줘야 한다.

일반적으로는 pk 로 데이터 조회 후 없으면 insert 있으면 Update 를 해줘야 하나 매번 select 를 하기 db 커넥션도 아깝고 코드가 번거롭다.

하지만 mysql 에는 ON DUPLICATE KEY UPDATE 기능 이 있어 이미 데이터가 있다면 update 를 할 수 있다. 

 

해당 방식은 INSERT를 하다가 PK값이 존재하면 UPDATE를 한다는 조건이 있는데 이 조건을 만족하기 위해 테이블에 무조건 PK값이 지정이 되어있어야 정상적으로 동작한다.

Mapper

@Repository
public interface TestMapper {

    int upsertData(List<Data> dataList);

}

mybatis 쿼리

<insert id="upsertData" parameterType="Data">
        INSERT INTO 테이블명 (id, data_name, data_category)
        VALUES
        <foreach collection="list" index="index" item="item" separator=",">
            (
            #{item.id},
            #{item.dataName},
            #{item.dataCategory}
            )
        </foreach>
        ON DUPLICATE KEY UPDATE building_name=VALUES(dataName), building_category=VALUES(dataCategory);
</insert>

parameterType 에는 List 객체 내부 클래스를 사용하고, collection 에 list 를 명시하고 다음과 같이 foreach 구문을 사용한다.

ON DUPLICATE KEY UPDATE 에는 pk를 제외한 update 쳐야 하는 컬럼명을 명시해준다.

ON DUPLICATE KEY UPDATE  도 for 문을 작성할 필요는 없다.

 

데이터 클래스

public class Data {
     private int id;
     private String dataName;
     private String dataCategory;
 }

사용예시

List<Data> list = new ArrayList<>();
list.add(data1);
list.add(data2);
list.add(data3);
~~

testMapper.upsertData(list);

 

위의 예시가 bulk upsert 이니깐, bulk insert 및 bulk update 는 ON DUPLICATE KEY UPDATE 키워드만 빼고, foreach 구문만 사용하면 된다.

 

 

 

참고문헌 > https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html [ON DUPLICATE KEY UPDATE 공식문서]

반응형
반응형

사용이유

datasource 커넥션을 로직이나 상황별로 나눠야 하는 상황이 올 수 도 있고, 어플리케이션단에서 샤딩을 구현해야 할 수 도 있고, read 커넥션 및 write 커넥션의 datasource 를 나눠야 하는 경우가 있다.

애초에 다른 datasoure 파일을 만들어도 되지만, 동적으로 쿼리 요청시마다 datasource 를 변경해야 할 때 AbstractRoutingDataSource 를 사용하면 된다.

 

AbstractRoutingDataSource란?

spring-jdbc 모듈에 포함되어 있는 클래스로, 여러 DataSource를 등록하고 특정 상황에 맞게 원하는 DataSource를 사용할 수 있도록 추상화한 클래스이다.

 

구현방식

AbstractRoutingDataSource는 이름 기반으로 다중 DataSource 정보를 등록하는 방법이다.

AbstractRoutingDataSource의

public void setTargetDataSources(Map<object, object=""> targetDataSource)를 호출하여

String:DataSource을 키:값으로하는 Datasource를 Map에 저장할 수 있다.

 

AbstractRoutingDataSourcedetermineCurrentLooupKey()를 오버라이드해서 상황에 맞게 Key를 반환하도록 구현해야 한다.

어떤 데이터 소스를 쓸지에 대한 결정은 determineCurrentLooupKey 메소드 내부에서 쿼리 호출시마다 동적으로 결정된다.

return 되는 String 결과값에 해당되는 datasource 를 실행하게 된다.

public class MyRoutingDataSource extends AbstractRoutingDataSource {
	@Override
	protected Object determineCurrentLookupKey() {
        boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
        if (isReadOnly) {
            return "slave";
        } else {
            return "master";
        }	
	}
}

참고로 boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); 를 사용하려면

transactionManager 에 LazyConnectionDataSourceProxy 를 설정해 주어야 한다.

    @Bean("RoutingDataSource")
    public DataSource routingDataSource() {
        HikariDataSource masterDataSource = createDatasource(~~);
        HikariDataSource slaveDataSource = createDatasource(~~);

        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("master", masterDataSource);
        dataSourceMap.put("slave", slaveDataSource);

        DynamicRoutingDataSource routingDataSource = new MyRoutingDataSource();
        routingDataSource.setTargetDataSources(dataSourceMap);
        routingDataSource.setDefaultTargetDataSource(masterDataSource);
        return routingDataSource;
    }
    
    @Bean("routingLazyDataSource")
    public DataSource routingLazyDataSource(@Qualifier("RoutingDataSource") DataSource dataSource) {
        return new LazyConnectionDataSourceProxy(dataSource);
    }
    
    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager(@Qualifier("routingLazyDataSource") DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
    
    public HikariDataSource createDatasource() {
    	
        // 데이터 connection 정보
        ~~
        return new HikariDataSource(config);
    }

 

LazyConnectionDataSourceProxy 는 데이터소스를 한번 더 감싸는 역할을 해주어, LazyConnectionDataSourceProxy 로 여러 데이터 소스를 묶어 PlatformTransactionManager 에 datasource 를 세팅해야, tranaction 이 정확히 동작한다.

다음과 같이 PlatformTransactionManager 에 LazyConnectionDataSourceProxy 로 datasource 여러개를 감싼 datasource 를 set 해줘야 

TransactionSynchronizationManager.isCurrentTransactionReadOnly() 를 할 경우 정확히 readOnly 를 사용하는 쿼리를 캐치해서 분리 할 수 있다.

@Transactional(readOnly = true)
public List<Test> findTestList() {
	return testMapper.selectTestList();
}

다음과 같이 readOnly 가 true 인 경우는 isCurrentTransactionReadOnly 가 true 이므로 determineCurrentLookupKey 메소드에서 "slave" datasource 를 반환하게 된다.

만약에 위에서 만든 여러 datasource 를 LazyConnectionDataSourceProxy 로 감싸지 않으면 readOnly 가 true 인지를 알 수 가 없다.

 

 

위처럼도 사용가능하고, determineCurrentLookupKey 를 어떻게 구현하느냐에 따라 db 정보를 다르게 사용할 수 있다. 

 

반응형
반응형

spring boot 에서 mybatis 를 연동하는 법을 해보자

pom.xml

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>

일단 pom.xml 에 mybatis 라이브러리부터 추가하자. dbms 에 맞는 driver 는 추가됐다고 가정하고 진행한다.

 

사용버젼 표

아래 표에 맞게 version 명시하면 된다.

MyBatis-Spring-Boot-Starter MyBatis-Spring Spring Boot Java 
2.2 2.0 (need 2.0.6+ for enable all features) 2.5 or higher 8 or higher
2.1 2.0 (need 2.0.6+ for enable all features) 2.1 - 2.4 8 or higher
2.0 (EOL) 2.0 2.0 or 2.1 8 or higher
1.3 1.3 1.5 6 or higher
1.2 (EOL) 1.3 1.4 6 or higher
1.1 (EOL) 1.3 1.3 6 or higher
1.0 (EOL) 1.2 1.3 6 or higher

spring boot 2.5 이상부터 mybatis-spring-boot-starter 2.2 버젼이 사용가능하다.

 

config 클래스

@Configuration
@MapperScan(value="com.juntcom.api.mapper.*", sqlSessionFactoryRef="dbMybatisSqlSessionFactory")
@Slf4j
public class DbMybatisConfig {
    @Autowired
    private Environment env;
 
    private static final String prefix = "spring.db.datasource.hikari.";
        
    @Bean(name = "dbMybatisSource", destroyMethod = "close")
    @Primary
    public HikariDataSource dbMybatisSource() {
    	HikariConfig config = new HikariConfig();
    	config.setUsername(env.getProperty(prefix+"username")); 
    	config.setPassword(env.getProperty(prefix+"password")); 
//    	config.setDriverClassName(env.getProperty(prefix+"driverClassName"));
    	config.setJdbcUrl( env.getProperty(prefix+"jdbc-url") );
    	config.setMaxLifetime( Long.parseLong(env.getProperty(prefix+"max-lifetime")) );
    	config.setConnectionTimeout(Long.parseLong( env.getProperty(prefix+"connection-timeout")));
    	config.setValidationTimeout(Long.parseLong( env.getProperty(prefix+"validation-timeout")));
    	
    	
    	config.addDataSourceProperty( "cachePrepStmts" ,  env.getProperty(prefix+"data-source-properties.cachePrepStmts"));
        config.addDataSourceProperty( "prepStmtCacheSize" , env.getProperty(prefix+"data-source-properties.prepStmtCacheSize"));
        config.addDataSourceProperty( "prepStmtCacheSqlLimit" , env.getProperty(prefix+"data-source-properties.prepStmtCacheSqlLimit") );	
        config.addDataSourceProperty( "useServerPrepStmts" , env.getProperty(prefix+"data-source-properties.useServerPrepStmts") );
        
    	
    	HikariDataSource dataSource = new HikariDataSource( config );
        
        return dataSource;
    }
    
    @Bean(name = "dbMybatisSqlSessionFactory")
    @Primary
    public SqlSessionFactory dbMybatisSqlSessionFactory(@Qualifier("dbMybatisSource") DataSource dbMybatisSource) throws Exception {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dbMybatisSource);
//        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:sql/read/*.xml"));
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:sql/**/*.xml"));
        return sqlSessionFactoryBean.getObject();
    } 

    @Bean(name = "dbMybatisSqlSessionTemplate")
    @Primary
    public SqlSessionTemplate dbMybatisSqlSessionTemplate(SqlSessionFactory dbMybatisSqlSessionTemplate) throws Exception {
        return new SqlSessionTemplate(dbMybatisSqlSessionTemplate);
    }


    
   
    @Bean(name="dbMybatisTransactionManager")
    @Primary
    public PlatformTransactionManager dbMybatisTransactionManager(@Qualifier("dbMybatisSource") DataSource dbMybatisSource) {
  
    	DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
    	transactionManager.setDataSource(dbMybatisSource);
        return transactionManager;
    }
}

@MapperScan(value="com.juntcom.api.mapper.*", sqlSessionFactoryRef="dbMybatisSqlSessionFactory")

@MapperScan -> mapper 클래스를 놓을 클래스 경로가 맞아야 한다. 

mapper 라는 패키지 하위에 mapper interface 를 위치 시켜야 mapper 를 스캔한다.

 

private static final String prefix = "spring.db.datasource.hikari."

라고 선언부가 application.yml 파일에서 가져오기 위한 prefix 이다. 따로 설정하지 않고, class 파일 내부에서 작성해도 되나, 설정값을 한눈에 보기 위해 application.yml 에 기입하는게 낫다.

 

("classpath:sql/**/*.xml"));

resources 하위에 sql 폴더 생성 후 그 밑에 xml 파일을 만들자.

경로는 sql/*.xml 로 sql 폴더 바로 밑에 xml 파일 두고 싶은경우는 다음과 같이 하자. 

나 같은 경우는 read 폴더 및 writer 폴더를 나누는게 좋아서 /**/ 를 추가했다.

 

참고

datasource 를 하나만 둘 거면 yml 에 설정값을 추가함으로써 db connection 을 할 수 있지만,

java 파일에서 한 이유는

여러개의 db 를 사용할 경우 multi datasource 를 사용하기 위함이다.

yml 에 db1, db2 라고 추가해 java class 파일을 별도로 

class1 {

  private static final String prefix "spring.db1.datasource.hikari."

  ...

}

class2 {

  private static final String prefix "spring.db2.datasource.hikari."

  ...

}

와 같이 class 를 별도로 두면 된다.

application.yml

spring:
  db: #db1 이든 db2 이든 상관없다. java class 의 prefix 의 값과 맞춰주면 된다.
    datasource:
      hikari:
        jdbc-url: jdbc:mysql://localhost:3306?&useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
        username: root
        password: 비밀번호
        driverClassName: com.mysql.cj.jdbc.Driver
        maximum-pool-size: 10
        #minimum-idle: 100
        max-lifetime: 1800000 #1800000
        connection-timeout: 30000 #30000
        validation-timeout: 5000
        #connection-test-query: SELECT 1
        data-source-properties:
          cachePrepStmts: true
          prepStmtCacheSize: 250
          prepStmtCacheSqlLimit: 2048
          useServerPrepStmts: true

위까지만 하면 datasource 가 뜨기까지의 설정은 되었으나, mapper 클래스 및 resource 하위에 쿼리가 비어있기 때문에 샘플 하나는 만들어야 어플리케이션이 뜬다.

 

 

mapper 클래스

다음과 같이 위치시켜주자. 패키지명을 달리하려면 config 클래스에서 MapperScan 의 값을 변경하자.

@Mapper
public interface ReadMapper {
    List<Map<String, Object>> selectCode() throws Exception;
}

. 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="com.juntcom.api.mapper.read.ReadMapper">

    <select id="selectCode" resultType="map">
		SELECT
		    *
		FROM
            TEST
    </select>


</mapper>

 

 

 

참고문헌

> https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/ [mybatis spring boot starter 공식문서]

반응형
반응형

 

firebase 가 클라이언트 사이드에서 구현하기 위한 severless db 로 많이 쓰이는데, 

나 같은 경우는 db 를 구축하기 일단 비용 및 인프라가 없어서 간단히 만들려고 firebase 를 서버에서 사용하기 위한 db 로 일단은 선택했다.

 

일정량 이하는 free 로 사용할 수 있다.

 

firebase 프로젝트 생성

대쉬보드에서 프로젝트 추가를 통해 프로젝트를 생성

console.firebase.google.com/

 

firestore Database 를 생성하자.

주의 할 것은 realtime database 와 firestore database 는 다르다.

다른 점으로는 과금 정책도 다르고, realtime 이 더 비싸다고 한다.

둘의 차이점은 아래 링크 문서에 있다.

firebase.google.com/docs/database/rtdb-vs-firestore?hl=ko

 

프로젝트 설정 에서 account 관련 키 파일을 다운받아야 한다.

프로젝트 개요 > 프로젝트 설정 > 서비스 계정 > Firebase Admin SDK 

에서 자바 를 클릭 후 

새 비공개 키 생성 으로 파일을 다운받자. 

다운 받으면 ~~~~.json 파일로 다운받아진다.

 

스프링 spring boot 코드 

pom.xml 라이브러리 다운

<dependency>
	<groupId>com.google.firebase</groupId>
	<artifactId>firebase-admin</artifactId>
	<version>6.11.0</version>
</dependency>

 

스프링 init 코드

@Service
public class FirebaseInitialize {

    @PostConstruct //has to be Run during the Start of
    public void initialize() {
        try {
            FileInputStream serviceAccount =
                    new FileInputStream("src/main/resources/serviceAccountKey.json");

            FirebaseOptions options = new FirebaseOptions.Builder()
                    .setCredentials(GoogleCredentials.fromStream(serviceAccount))
                    .setDatabaseUrl("https://{{데이터베이스명}}.firebaseio.com")
                    .build();

            FirebaseApp.initializeApp(options);
        } catch (Exception e){
            e.printStackTrace();
        }
    }

}

 

위에서 다운받은 json 파일을 이름을 변경해주고 resources 하위 경로에 넣자.

databaseUrl 은 따로 나오는 곳은 없어 보였는데, 프로젝트 명 에 뒤에 .firebaseio.com 을 붙이면 된다.

데이터베이스명은 firestore 조회 화면에 바로 나온다.

 

데이터 샘플로 넣고 조회하기

@Slf4j
@Service
public class FirebaseService {

    public static final String COLLECTION_NAME = "user";

    public void insertUser() throws Exception {

        Firestore db = FirestoreClient.getFirestore();
        User user = new User();
        user.setId("4444");
        user.setName("4444");
        ApiFuture<WriteResult> apiFuture = db.collection(COLLECTION_NAME).document("user_4").set(user);

        log.info(apiFuture.get().getUpdateTime().toString());
    }


    public void selectUser() throws Exception {

        Firestore db = FirestoreClient.getFirestore();
        User user = null;
        ApiFuture<DocumentSnapshot> apiFuture = db.collection(COLLECTION_NAME).document("user_4").get();
        DocumentSnapshot documentSnapshot = apiFuture.get();
        if(documentSnapshot.exists()) {
            user = documentSnapshot.toObject(User.class);
            log.info(user.toString());
        }
    }
}

 

User 라는 pojo 클래스를 만들고 넣어줘도 되고 HashMap 으로 만들어서 넣어줘도 된다.

collection 과 document 라는 개념이 몽고db 와 흡사하다.

collection 이라는 개념은 db와 같고, 하위의 document 가 테이블 과 개념이 같다. 조금 다른 점이 있다면 document 가 하위로 계속 들어갈 수 있다.

사용 

    @Resource
    FirebaseService firebaseService;

    @Test
    public void 테스트() throws Exception {
        firebaseService.insertUser();
        firebaseService.selectUser();
    }

이 정도 코드는 사실 안남겨도 되는데, 혹시나.

 

결과 

 

나 같은 경우는 저 코드를 작성했는데 한참 동작하지 않아서 이것저것 삽질을 조금 했다.

나중에 시간이 지나고 되는걸 보면 database 를 생성하고 일정시간이 지나야 되는거 같긴 한데, 이건 확실치 않다.

혹시 안되는 사람은 2시간 정도 경과하고 한번 다시 해보도록

 

혹시나 권한 때문에 안되는 사람은 규칙(rule)을 확인하자.

production 모드로 한 사람은 규칙에서 test 모드로 변경하면 된다.

2021 년 6월 3일 까지는 read write 권한이 된다는 코드이다.

반응형
반응형

jpa 관련 어노테이션

  • @Column : 컬럼을 매핑한다.
  • @Enumerated : enum 타입을 매핑한다.
  • @Temporal : 날짜 타입 매핑한다.
  • @Lob : BLOB, CLOB 타입을 매핑한다.
  • @Transient : 해당 필드를 데이터 베이스에 매핑 시키지 않는다.
  • @Access : JPA가 엔티티 접근하는 방식을 지정한다.

@Column

name : 필드와 매핑할 테이블의 컬럼 이름
insertable : 엔티티 저장시 이 필드도 같이 저장한다. false로 설정하면 데이터베이스에 저장하지 않는다. 읽기 전용일때 사용한다.
updatable : 위와 동일한 하지만 수정일때 해당 된다.

@Column(insertable=false, updatable=false)
private String defaultField;

위와같이 하면 update 나 insert 가 안된다. update 만 안할 경우 updatable 만 false 로 하면 된다.

 

table : 지정한 필드를 다른 테이블에 맵핑할 수 있도록 합니다.

nullable(DDL) : null 값 허용 여부를 설정한다. false일 경우 DDL생성시 not null 제약조건이 된다.
unique(DDL) : 한 컬럼에 간단히 유니크 제약 조건을 걸 때 사용한다. 만약 두개 이상 걸고 싶다면 클래스 레벨에서 @Table.uniqueConstraints를 사용해야 한다.
columnDefinition(DDL) : 데이터베이스 컬럼 정보를 직접 줄 수 있다.
length : 문자 길이 제약 조건이다. String만 해당된다.
precision, scale(DDL) : BigDecimal 타입에서 사용한다.(BigInteger 가능) precision은 소수점을 포함한 전체 자리수이고, scale은 소수점 자릿수이다. double랑 float타입에는 적용 되지 않는다.

 

@Enumerated

value : EnumType.ORDINAL값은 enum 순서를 데이터 베이스에 저장한다. EnumType.STRING값은 enum 이름을 저장한다.

enum RoleType {
  ADMIN, USER
}

@Enumerated(EnumType.STRING)
private RoleType roleType;

m.setRoleType(RoleType.ADMIN) //ADMIN으로 저장된다.

보통은 STRING을 쓰는것을 권장한다.

 

@Temporal

날짜 타입(java.util.Date, java.util.Calendar)을 맵핑할 때 사용한다.

value :

TemporalType.DATE  날짜, 데이터베이스 date 타입과 매핑 (2016-04-19),

TemporalType.TIME 시간, 데이터베이스 time 타입과 매핑 (21:22:11)

TemporalType.TIMESTAMP : 날짜와 시간, 데이터베이스 timestamp 타입과 매핑(2016-04-19 21:22:11)

 

@Lob

속성이 없다. 대신 매핑하는 필드 타입이 문자라면 CLOB으로 매핑하고 나머지는 다 BLOB으로 매핑하면 된다.
CLOB : String, char[], java.sql.CLOB
BLOB : byte[], java.sql.BLOB

 

@Transient

위와 같이 선언된 필드는 데이터베이스에 저장, 조회도 하지않는다. 임시로 값을 보관할 때 사용한다.

 

@Access

필드 접근 : AccessType.FIELD로 지정한다.필드에 직접 접근한다. private 권한이여도 접근할 수 있다.
프로퍼티 접근 : AccessType.PROPERTY로 지정한다. 접근자 getter로 접근한다.

@Entity
public class Member {

  @Id
  private String id;

  @Transient
  private String firstName;

  @Transient
  private String lastName;

  @Access(AccessType.PROPERTY)
  public String getFullName(){
    return this.firstName + this.lastName;
  }

  ...
}

@Id가 필드에 있으면 기본적으로 필드접근 방식을 사용한다.
만약 프로퍼티에 있으면 기본적으로 프로퍼티접근 방식을 사용한다.

위의 코드는 @Id가 필드에 있으므로 필드 접근 방식을 사용하고 getFullName만 프로퍼티를 사용한다. 그러면 회원이라는 엔티티의 FULLNAME 컬럼이 생성되고 firstName + lastName의 결과가 저장된다.

 

반응형

+ Recent posts