반응형

RestTemplate이란

스프링에서 제공하는 http 통신에 유용하게 쓸 수 있는 템플릿

Spring 3부터 지원 되었고 REST API 호출이후 응답을 받을 때까지 기다리는 동기방식이다

 

AsyncRestTemplate

Spring 4에 추가된 비동기 RestTemplate이다

Spring 5.0에서는 deprecated 되었다

 

메소드

메서드

HTTP

설명

getForObject

GET

주어진 URL 주소로 HTTP GET 메서드로 객체로 결과를 반환받는다

getForEntity

GET

주어진 URL 주소로 HTTP GET 메서드로 결과는 ResponseEntity로 반환받는다

postForLocation

POST

POST 요청을 보내고 결과로 헤더에 저장된 URI를 결과로 반환받는다

postForObject

POST

POST 요청을 보내고 객체로 결과를 반환받는다

postForEntity

POST

POST 요청을 보내고 결과로 ResponseEntity로 반환받는다

delete

DELETE

주어진 URL 주소로 HTTP DELETE 메서드를 실행한다

headForHeaders

HEADER

헤더의 모든 정보를 얻을 수 있으면 HTTP HEAD 메서드를 사용한다

put

PUT

주어진 URL 주소로 HTTP PUT 메서드를 실행한다

patchForObject

PATCH

주어진 URL 주소로 HTTP PATCH 메서드를 실행한다

optionsForAllow

OPTIONS

주어진 URL 주소에서 지원하는 HTTP 메서드를 조회한다

exchange

any

HTTP 헤더를 새로 만들 수 있고 어떤 HTTP 메서드도 사용가능하다

execute

any

Request/Response 콜백을 수정할 수 있다

 

GET 메소드

getForObject()

Employee employee = restTemplate.getForObject(BASE_URL + "/{id}", Employee.class);

Employee 로의 매핑은 jackson-databind 가 기본적으로 담당하고 있다.

 

getForEntity()

응답을 ResponseEntity 객체로 받는다. getForObject()와 달리 HTTP 응답에 대한 추가 정보를 담고 있어서 GET 요청에 대한 응답 코드, 실제 데이터를 확인할 수 있다. 또한 ResponseEntity<T> 제네릭 타입에 따라서 응답을 String이나 Object 객체로 받을 수 있다.

 

ResponseEntity<String> responseEntity = restTemplate.getForEntity(BASE_URL + "/{id}", String.class, 25);
log.info("statusCode: {}", responseEntity.getStatusCode());
log.info("getBody: {}", responseEntity.getBody());

getForEntity()에 여러 값을 담은 params을 같이 넘겨주기

LinkedMultiValueMap 객체에 담아서 params로 넘겨줄 수 있다.

MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("name", "Frank Oh");
params.add("country", "US");

ResponseEntity<Employee> responseEntity = restTemplate.getForEntity(BASE_URL + "/{name}/{country}", Employee.class, params);
log.info("statusCode: {}", responseEntity.getStatusCode());
log.info("getBody: {}", responseEntity.getBody());

 

get 요청에 header 값이 필요한 경우 

get 메소드에서는 header 를 추가 할 수 가 없다.

exchange 메소드를 사용해야 한다.

HttpHeaders headers = new HttpHeaders();
headers.set("header", header);
headers.set("header2", header2);

HttpEntity request = new HttpEntity(headers);

ResponseEntity<String> response = restTemplate.exchange(
  URL_PATH,
  HttpMethod.GET,
  request,
  String.class
);

 

get 요청에 header 값 과 쿼리스트링(query String, param)이 필요한 경우

post 처럼 HttpEntity 에 넣어서 요청할 수가 없다.

HttpHeaders headers = new HttpHeaders();
headers.set("header", header);
headers.set("header2", header2);

HttpEntity request = new HttpEntity(headers);
//adding the query params to the URL
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(URL_PATH)
	.queryParam("keywords", "11");
    .queryParam("name", "22");

ResponseEntity<String> response = restTemplate.exchange(
  uriBuilder.toUriString(),
  HttpMethod.GET,
  request,
  String.class
);

이렇게 UrlBuilder 를 사용해서 넣는 수 밖에 없다. post 방식과 달리 httpEntity 에 같이 넣거나 exchange 의 parameter 로 넘길 수가 없다.

사실 굳이 uriBuilder 를 써야 되나 싶기도 하다. 그냥 map 에 파라미터를 추가하고 map 을 parameter 로 변환해주는 메소드만 만들어서 사용하면 편할거 같다.


...
HttpHeaders headers = new HttpHeaders();
headers.set("header", header);
headers.set("header2", header2);

HttpEntity request = new HttpEntity(headers);

Map<String, String> params = new HashMap<String, String>();
params.put("query1", "test");
params.put("query2", "test2");

ResponseEntity<String> response = restTemplate.exchange(
  URL_PATH + "?" + this.mapToUrlParam(params),
  HttpMethod.GET,
  request,
  String.class
);
...

// 위에서 사용하려고 만든 util 메소드
private static String mapToUrlParam(Map<String, Object> params) {
  StringBuffer paramData = new StringBuffer();
  for (Map.Entry<String, Object> param : params.entrySet()) {
  	if (paramData.length() != 0) {
  		paramData.append('&');
  	}
    paramData.append(param.getKey());
    paramData.append('=');
    paramData.append(String.valueOf(param.getValue()));
  }
  return paramData.toString();
}

사실 굳이 uriBuilder 를 써야 되나 싶기도 하다.

단순하게 map 에 파라미터를 추가하고 map 을 parameter 문자열로 변환해주는 메소드만 만들어서 사용하면 편할거 같다.

 

 

postForObject() 메소드 header 값 없는 경우

Employee newEmployee = Employee.builder()
         .name("Frank")
         .address(Address.builder()
               .country("US")
               .build())
         .build();
 
   Employee employee = restTemplate.postForObject(BASE_URL + "/employee", newEmployee, Employee.class);

postForObject() 메소드 header 포함해서 보내기

Employee newEmployee = Employee.builder()
         .name("Frank")
         .address(Address.builder()
               .country("US")
               .build())
         .build();
 
   HttpHeaders headers = new HttpHeaders();
   headers.set("headerTest", "headerValue");
 
   HttpEntity<Employee> request = new HttpEntity<>(newEmployee, headers);
 
   Employee employee = restTemplate.postForObject(BASE_URL + "/employee", request, Employee.class);

postForEntity

getForEntity 와 동일해서 생략

 

post form data 사용

// 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

// 파라미터 세팅
MultiValueMap<String, String> map= new LinkedMultiValueMap<>();
map.add("id", "1");

// 요청 세팅 완료
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);

// 실제 요청부
ResponseEntity<String> response = restTemplate.postForEntity(fooResourceUrl+"/form", request , String.class);

 

Timeout 설정하기

timeout 을 설정하려면 ClientHttpRequestFactory 와 같은 팩토리 메소드를 만들고 RestTemplate 의 생성자에 추가해야 한다.

RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());

private ClientHttpRequestFactory getClientHttpRequestFactory() {
    int timeout = 5000;
    HttpComponentsClientHttpRequestFactory clientHttpRequestFactory
      = new HttpComponentsClientHttpRequestFactory();
    clientHttpRequestFactory.setConnectTimeout(timeout);
    return clientHttpRequestFactory;
}

 

참고사항으로 timeout 이 0 이면 무제한설정이다. (infinite timeout)

 

Execute()

 Execute()는 콜백을 통해 요청 준비와 응답 추출을 완벽하게 제어하여 요청을 수행하는 가장 일반적인 메서드를 RestTemplate에서 제공한다.

getForObject(), postForeObject() 등은 excute() 를 내부적으로 호출한다.

 

connection pool 적용

RestTemplate 은 기본적으로 connection pool 을 사용하지 않는다. 따라서 연결할 때 마다, 로컬 포트를 열고 tcp connection 을 맺는다. 이때 문제는 close() 이후에 사용된 소켓은 TIME_WAIT 상태가 되는데, 요청량이 많다면 이런 소켓들을 재사용하지 못하고 소켓이 오링나서 응답이 지연될 것이다.

 

 

참고문헌

www.baeldung.com/rest-template [샘플 문서]
stackoverflow.com/questions/31869193/using-spring-rest-template-either-creating-too-many-connections-or-slow/ [커넥션 많을 경우 pool 관리
반응형
반응형

스프링 부트 2.3 버젼부터 spring-data-elasticsearch 버젼도 4 버젼으로 올라가고, 여기서 부터 기존 설정 이 조금 달라졌다.

아래 내용은 버젼명시가 없다면 2.3 버젼부터 사용가능한 버젼이다.

 

소스코드 버젼

spring boot 2.4.3

spring-data-elasticsearch 4.1.3

 

pom.xml dependency 추가

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

 

 

config 설정소스

@Configuration
@EnableElasticsearchRepositories(basePackages = "com.baeldung.spring.data.es.repository")
@ComponentScan(basePackages = { "com.baeldung.spring.data.es.service" })
public class Config {

    @Bean
    public RestHighLevelClient client() {
        ClientConfiguration clientConfiguration 
            = ClientConfiguration.builder()
                .connectedTo("localhost:9200")
                .build();

        return RestClients.create(clientConfiguration).rest();
    }

    @Bean
    public ElasticsearchOperations elasticsearchTemplate() {
        return new ElasticsearchRestTemplate(client());
    }
}

spring-data-elasticsearch 4.0.0 이상부터는,
TransportClient 클래스가 Deprecated 되어 RestHighLevelClient 또는 ReactiveRestLevelClients 만 사용 가능하다.

 

예제 상에서는 대부분 

@EnableElasticsearchRepositories 어노테이션을 추가하지만 예제 소스를 직접 만들어본 결과 @EnableElasticsearchRepositories 를 따로 추가해 주지 않아도 됐다.

 

@ComponentScan 는 application 에 추가했다면 안해도 된다.

@EnableElasticsearchRepositories 도 추가 없이 동작확인했지만, 사용하려는 버젼에서 안된다면 추가하자.

 

Document 객체 설정하기

@Setter
@Document(indexName = "blog")
public class Blog {

    @Id
    private String id;
    private String title;
    private String content;

}

@Document 는 내가 넣을 index 테이블이다. @Entity 와 동일하다. 또한 몽고디비의 @Document 와 동일하다.

컬럼의 경우 @Field 를 사용한다.

@Field(type = FieldType.Date)
private Date log_date;

@Field(type = FieldType.Text)
private String log_text;

@Field(type = FieldType.Long)
private Long price;

필드별로 타입을 지정할 수 있다.

당연한 거지만 숫지데이터가 없을 때 null 을 넣어주고 싶은경우 Reference Type 을 넣어주면 된다. Long, Integer 등등

 

원래는 @Document 어노테이션에 매핑 Type 지정이 가능했지만, ES 7.0.0 부터는 Type 이 deprecated 되어 indexName만 설정가능하다.

어차피 6. 버젼에서도 하나의 index 에 하나의 type 만 가능했어서 상관없는 부분이다.

https://www.elastic.co/guide/en/elasticsearch/reference/7.x/removal-of-types.html [타입삭제 레퍼런스]

 

@ElasticsearchReposity 사용

JpaRepository 와 사용이 유사하다.

https://docs.spring.io/spring-data/elasticsearch/docs/4.0.0.M4/reference/html/#repositories [repository 문서]

@Repository
public interface BlogEsRepository extends ElasticsearchRepository<Blog, String> {
}

 

사용 예시

@Resource
BlogEsRepository blogEsRepository;

@Test
void test(){
    Blog blog = new Blog();
    blog.setId("1");
    blog.setContent("내용입니다.");
    blog.setTitle("제목입니다.");
    blogEsRepository.save(blog);
}

 

키바나 결과화면

 

엘라스틱 조회 결과

 

 

엘라스틱 구축 하는 방법은 

juntcom.tistory.com/121    [도커로 엘라스틱 구축]

를 참고하자.

 

 

참고문헌

https://docs.spring.io/spring-data/elasticsearch/docs/4.0.0.M4/reference/html/#preface [스프링 공식문서]
https://www.baeldung.com/spring-data-elasticsearch-tutorial [elastic 예제소스]

 

 

반응형
반응형

스프링에서 설정파일 값을 외부에 노출하고 싶지 않을떄, Jasypt 를 사용하면 된다.

 

라이브러리

spring boot starter 용

3.0.3 이 작성기준 2021년 1월 28일 기준 최신버젼이다.

3,0.3 이 출시된 날짜는 2020년 5월 31일 이다.

<dependency>
	<groupId>com.github.ulisesbocchio</groupId>
	<artifactId>jasypt-spring-boot-starter</artifactId>
	<version>3.0.3</version>
</dependency>

위와 같이 라이브러리를 추가하면

@SpringBootApplication or @EnableAutoConfiguration 어노테이션을 메인에 추가해 주어야 한다.

이렇게 해주면 환경설정 파일 command line argument, application.properties, yaml properties 들을 암호화 할 수 있다.

 

@SpringBootApplication or @EnableAutoConfiguration 을 추가해주지 않으면 pom.xml 과 다른 어노테이션을 추가해주어야 한다.

<dependency>
        <groupId>com.github.ulisesbocchio</groupId>
        <artifactId>jasypt-spring-boot</artifactId>
        <version>3.0.3</version>
</dependency>
@Configuration
@EncryptablePropertySources({@EncryptablePropertySource("classpath:encrypted.properties"),
							@EncryptablePropertySource("classpath:encrypted2.properties")})
public class MyApplication {
...
}

위 작업이 필요하다. 그래서 그냥 @SpringBootApplication or @EnableAutoConfiguration 어노테이션 을 추가하자.

 

 

속성값에 대한 정의

Key Required Default Value
jasypt.encryptor.password True -
jasypt.encryptor.algorithm False PBEWITHHMACSHA512ANDAES_256
jasypt.encryptor.key-obtention-iterations False 1000
jasypt.encryptor.pool-size False 1
jasypt.encryptor.provider-name False SunJCE
jasypt.encryptor.provider-class-name False null
jasypt.encryptor.salt-generator-classname False org.jasypt.salt.RandomSaltGenerator
jasypt.encryptor.iv-generator-classname False org.jasypt.iv.RandomIvGenerator
jasypt.encryptor.string-output-type False base64
jasypt.encryptor.proxy-property-sources False false
jasypt.encryptor.skip-property-sources False empty list

 

configuration 설정파일

@Configuration
public class JasyptConfig {

    @Bean("jasyptStringEncryptor")
    public StringEncryptor stringEncryptor() {
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
        config.setPassword("password");
        config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
        config.setKeyObtentionIterations("1000");
        config.setPoolSize("1");
        config.setProviderName("SunJCE");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
        config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
        config.setStringOutputType("base64");
        encryptor.setConfig(config);
        return encryptor;
    }
}

password 를 제외하고 기본 값들로 작성되어 있다.

 

 

application.yml 설정 추가

jasypt:
  encryptor:
    bean: jasyptStringEncrptor

설정으로 등록한 빈의 명을 명시해줘야 한다.

 

암호화 및 복호화 예시

위에서 암호화를 위한 모든 작업은 끝났다. 아래와 같이 설정파일의 값을 암호화 복호화 해가면서 사용하면 된다.

@SpringBootApplication
public class Application implements CommandLineRunner {

	public static void main(String [] args) {
        SpringApplication.run(Application.class, args);
        System.out.println("=========== Server Start ===========");
	}
	
	@Override
   public void run(String... args) throws Exception {
		StandardPBEStringEncryptor pbeEnc = new StandardPBEStringEncryptor();
		pbeEnc.setAlgorithm("PBEWithMD5AndDES");
		pbeEnc.setPassword("test"); //2번 설정의 암호화 키를 입력
		
		String enc = pbeEnc.encrypt("1234"); //암호화 할 내용
		System.out.println("enc = " + enc); //암호화 한 내용을 출력
		
		//테스트용 복호화
		String des = pbeEnc.decrypt(enc);
		System.out.println("des = " + des);
   }
}

 

application.yml 사용예시

datasource:
  url: ENC(인크립트된dburl)
  username: ENC(인크립트된유저명)
  password: ENC(인크립트된패스워드)

 

 

더 많은 사용방법 및 기능들이 아래 참고 주소에 있어서 제대로 사용할거면 아래 문서를 참고하자

https://github.com/ulisesbocchio/jasypt-spring-boot [깃허브 jasypt-spring-boot 업데이트 문서]
반응형
반응형

spring boot 2 와 이전 버젼의 차이점

 

1. Java 8 이 최소 버젼이다.

   java9 를 지원하는 최초의 버젼이다.

2. tomcat 8,5 가 최소버젼이다.

3. Hibernate 5.2 가 최소 버젼이다.

4. Gradle 3.4 가 최소 버젼이다.

5. Spring Security 구성이 더 쉬워지고 Spring Security Oauth2가 Spring Security에 합쳐졌다. 보안 자동 구성은 더 이상 옵션을 노출하지 않고 가능한 한 Spring Security 기본값을 사용한다.  -Spring Security5

사용자가 한 곳에서 명시적으로 환경설정을 할 수 있다. 이런 게 WebSecurityConfigurerAdapter 의 순서 문제를 막을 수 있다.

예를 들어 Actuator 와 앱의 보안 문제를 커스텀 할 수 있게 해준다.

http.authorizeRequests()
  .requestMatchers(EndpointRequest.to("health"))
    .permitAll() // Actuator rules per endpoint
  .requestMatchers(EndpointRequest.toAnyEndpoint())
    .hasRole("admin") // Actuator general rules
  .requestMatchers(PathRequest.toStaticResources().atCommonLocations()) 
    .permitAll() // Static resource security 
  .antMatchers("/**") 
    .hasRole("user") // Application security rules 

6. 리액티브를 지원한다.

여러 리액티브 모듈의 스타터를 제공한다. 예를 들어 WebFlux 와 MongoDB 와 Cassandra 또는 Redis.

7. 커넥션 풀이 HikariCP 로 설정된다.

 

8. Spring Boot 2.X에서 많은 구성 속성의 이름이 변경 / 제거되었으며 개발자는 그에 따라 application.properties/application.yml을 업데이트해야한다.

9. Mockito 1.x는 더 이상 지원되지 않는다. spring-boot-starter-test를 사용하여 종속성을 관리하지 않는 경우 Mockito 2.x로 업그레이드해야한다.

10. Spring-boot-starter-data-redis를 사용할 때 Redis 드라이버로 Jedis 대신 Letuce가 사용된다.

11. Elasticsearch가 5.4 이상으로 업그레이드되었다.

 

반응형
반응형

스프링 부트에서 카프카 클라이언트 라이브러리를 추가하면, 이런오류가 생긴다.

Error registering AppInfo mbean

해당오류는 카프카 컨슈머 측에서만 발생한다.

javax.management.InstanceAlreadyExistsException: kafka.consumer:type=app-info,id=clientid-0

    at com.sun.jmx.mbeanserver.Repository.addMBean(Repository.java:437)

    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerWithRepository(DefaultMBeanServerInterceptor.java:1898)

    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerDynamicMBean(DefaultMBeanServerInterceptor.java:966)

    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerObject(DefaultMBeanServerInterceptor.java:900)

    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerMBean(DefaultMBeanServerInterceptor.java:324)

    at com.sun.jmx.mbeanserver.JmxMBeanServer.registerMBean(JmxMBeanServer.java:522)

    at org.apache.kafka.common.utils.AppInfoParser.registerAppInfo(AppInfoParser.java:64)

    at org.apache.kafka.clients.consumer.KafkaConsumer.<init>(KafkaConsumer.java:816)

    at org.apache.kafka.clients.consumer.KafkaConsumer.<init>(KafkaConsumer.java:631)

    at org.springframework.kafka.core.DefaultKafkaConsumerFactory.createRawConsumer(DefaultKafkaConsumerFactory.java:340)

    at org.springframework.kafka.core.DefaultKafkaConsumerFactory.createKafkaConsumer(DefaultKafkaConsumerFactory.java:308)

    at org.springframework.kafka.core.DefaultKafkaConsumerFactory.createConsumerWithAdjustedProperties(DefaultKafkaConsumerFactory.java:293)

    at org.springframework.kafka.core.DefaultKafkaConsumerFactory.createKafkaConsumer(DefaultKafkaConsumerFactory.java:267)

    at org.springframework.kafka.core.DefaultKafkaConsumerFactory.createConsumer(DefaultKafkaConsumerFactory.java:241)

    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.<init>(KafkaMessageListenerContainer.java:606)

    at org.springframework.kafka.listener.KafkaMessageListenerContainer.doStart(KafkaMessageListenerContainer.java:302)

    at org.springframework.kafka.listener.AbstractMessageListenerContainer.start(AbstractMessageListenerContainer.java:338)

    at org.springframework.kafka.listener.ConcurrentMessageListenerContainer.doStart(ConcurrentMessageListenerContainer.java:204)

    at org.springframework.kafka.listener.AbstractMessageListenerContainer.start(AbstractMessageListenerContainer.java:338)

    at org.springframework.kafka.config.KafkaListenerEndpointRegistry.startIfNecessary(KafkaListenerEndpointRegistry.java:312)

    at org.springframework.kafka.config.KafkaListenerEndpointRegistry.start(KafkaListenerEndpointRegistry.java:257)

    at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:182)

    at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:53)

    at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:360)

    at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:158)

    at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:122)

    at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:895)

    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:554)

    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)

    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)

    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)

    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)

    at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)

    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)

    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)

    at kr.co.lunasoft.productdb.ProductDbBotApplication.main(ProductDbBotApplication.java:14)

이런 경우는 clientId 등록의 문제다.

이대로 어플리케이션을 올려도 상관은 없다.

하지만 오류가 뜨는걸 별로 보고싶지는 않기 떄문에 안뜨게 해주려면 카프카 리슨을 하고 있는 각각의 컨슈머에 client id 를 명시해줘야 한다.

카프카 토픽이 여러개 있을텐데 모두 같은 clientid 를 사용할때 다음과 같은 오류로그가 발생한다.

 

아래 코드와 같이 clientIdPrefix 로 해결

카프카컨슈머 - KafkaListener

    @KafkaListener(topics = {"topic-test"}, containerFactory = "KafkaListenerContainerFactory", clientIdPrefix = "test-topic-client")
    public void consumer(ConsumerRecord<String, Object> consumerRecord) {
        log.info("카프카 컨슈머")
    }

카프카컨테이너 팩토리 - ConsumerFactory

@Slf4j
@Configuration
public class KafkaConsumerConfig {

    @Value("#{'${spring.kafka.bootstrap-servers}'.split(',')}")
    List<String> bootstrapAddress;
    @Value("${spring.kafka.consumer.group-id}")
    String groupId;
    @Value("${spring.kafka.consumer.auto-offset-reset}")
    String autoOffsetReset;

    @Bean
    public ConsumerFactory<String, String> consumerFactory() {

        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress);
        props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
        props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetReset);
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        return new DefaultKafkaConsumerFactory<>(props);
    }

    @Bean("kafkaListenerContainerFactory")
    public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
        return factory;
    }

}

참고로 카프카 컨테이너 를 위와같이 구성해서 쓴다.

또한 스프링부트 2.3.# 버젼 코드이다.

반응형
반응형

스프링부트 2.3.3 을 쓰게되면서 부터 라이브러리 사용법들이 조금 많이 달라졌다.

예를 들어 몽고db 가 있다.

spring-data-mongodb:3.0.0 이후의 버젼이다.

 

몽고 db 쿼리 사용 예시이다.

Date dateLogStartDate = new Date(); // date 조건검색 시작
Date dateLogEndDate = new Date(); // date 조건검색 종료

Query query = new Query();
query.with(Sort.by(Sort.Direction.DESC, "log_date")); // date 내림차순
Criteria criteria = Criteria
        .where("log_date").gte(dateLogStartDate).lte(dateLogEndDate);

query.addCriteria(criteria);
Map<String, Object> result = new HashMap<String, Object>();
List<Test> mongo = null;
long count = mongoTemplate.count(query, Test.class);
log.info( "count : {}", count);
int perPage = 50;
int currentPage = 1;
if(count > 0) {
    query.with(Sort.by(Sort.Direction.DESC, "log_date")); // date 내림차순
    query.limit(perPage);
    query.skip(perPage * (currentPage - 1) );
    mongo = mongoTemplate.find(query, Test.class);
}

document 클래스

@Document(collection="test")
public class Test {

	private Date log_date;

	private String data;

	private String text;

}

spring-data-mongodb:3.0.0 이전 버젼에서는 어노테이션을 붙이지 않아도 동작을 했다.

 

하지만 스프링부트 2.3.0 이후버젼에서 사용하는 spring-data-mongodb:3.0.0 버젼이상은 

@Field 어노테이션을 꼭 붙여줘야 한다.

@Document(collection="test")
public class Test {

	@Field("log_date")
	private Date log_date;

	@Field("data")
	private String data;

	@Field("text")
	private String text;

}

 

@Field 을 붙여주지 않고 쿼리를 조회시 다음과 같은 오류가 생긴다.

org.springframework.data.mapping.PropertyReferenceException: No property log found for type Test!

 

반응형
반응형

어플리케이션 내에서 스케쥴링 해야하는 일이 생긴다.

 

스프링부트에서 사용하려명 Main 클래스에 @EnableScheduling 을 추가해주면 된다.

 

@Component
public class Scheduler {

	@Scheduled(fixedDelay = 3000)
	public void excute() {
    	System.out.println("스케쥴러");
	}
}    

위의 코드는 3초마다 실행이 된다.

fixedDelay 는 스케쥴러가 끝나고 3초이고, fixedRate 는 3초마다 주기적으로 실행을 한다는 의미이다.

끝나는 시점이 중요하면 fixedDelay 를 사용해야 한다.

 

그리고 크론탭과 같은 문법 사용이 가능하다. 

ex)

@Scheduled(cron="*/30 * * * * *")

시간 설정 @scheduled(cron=" ") * 리눅스 crontab 과 같은 설정방법

ex> @Scheduled(cron="0 0 02 * * ?") = 매일 새벽2시에 실행

ex> @Scheduled(cron="0 0 02 2,20 * ?") = 매월 2일,20일 새벽2시에 실행


시간 설정 @scheduled(cron=" ")  * 리눅스 crontab 과 같은 설정방법

ex> @Scheduled(cron="0 0 02 * * ?") = 매일 새벽2시에 실행

ex> @Scheduled(cron="0 0 02 2,20 * ?") = 매월 2일,20일 새벽2시에 실행

 

스케쥴러 cron 양식

초 0-59 , - * / 

분 0-59 , - * / 

시 0-23 , - * / 

일 1-31 , - * ? / L W

월 1-12 or JAN-DEC , - * / 

요일 1-7 or SUN-SAT , - * ? / L # 

년(옵션) 1970-2099 , - * /

* : 모든 값

? : 특정 값 없음

- : 범위 지정에 사용

, : 여러 값 지정 구분에 사용

/ : 초기값과 증가치 설정에 사용

L : 지정할 수 있는 범위의 마지막 값

W : 월~금요일 또는 가장 가까운 월/금요일

# : 몇 번째 무슨 요일 2#1 => 첫 번째 월요일

 

예제) Expression Meaning 

초 분 시 일 월 주(년)

 "0 0 12 * * ?" : 아무 요일, 매월, 매일 12:00:00

 "0 15 10 ? * *" : 모든 요일, 매월, 아무 날이나 10:15:00 

 "0 15 10 * * ?" : 아무 요일, 매월, 매일 10:15:00 

 "0 15 10 * * ? *" : 모든 연도, 아무 요일, 매월, 매일 10:15 

 "0 15 10 * * ? : 2005" 2005년 아무 요일이나 매월, 매일 10:15 

 "0 * 14 * * ?" : 아무 요일, 매월, 매일, 14시 매분 0초 

 "0 0/5 14 * * ?" : 아무 요일, 매월, 매일, 14시 매 5분마다 0초 

 "0 0/5 14,18 * * ?" : 아무 요일, 매월, 매일, 14시, 18시 매 5분마다 0초 

 "0 0-5 14 * * ?" : 아무 요일, 매월, 매일, 14:00 부터 매 14:05까지 매 분 0초 

 "0 10,44 14 ? 3 WED" : 3월의 매 주 수요일, 아무 날짜나 14:10:00, 14:44:00 

 "0 15 10 ? * MON-FRI" : 월~금, 매월, 아무 날이나 10:15:00 

 "0 15 10 15 * ?" : 아무 요일, 매월 15일 10:15:00 

 "0 15 10 L * ?" : 아무 요일, 매월 마지막 날 10:15:00 

 "0 15 10 ? * 6L" : 매월 마지막 금요일 아무 날이나 10:15:00 

 "0 15 10 ? * 6L 2002-2005" : 2002년부터 2005년까지 매월 마지막 금요일 아무 날이나 10:15:00 

 "0 15 10 ? * 6#3" : 매월 3번째 금요일 아무 날이나 10:15:00

 

반응형
반응형

vscode 확장툴들이 너무나도 많이 생겨서 이전에는 인텔리제이 및 이클립스에서만 가능했던 자바 IDE 기능이 이제는 vscode 에서도 가능해졌습니다.

 

여러패키지가 있지만 스프링부트를 vscode 에서 사용하기 위해서는 2가지가 필요합니다.

1. Java Extension Pack(Micosoft)

  - java언어 지원 기능, 디버거, 테스트 실행, maven 프로젝트 관리 등의 확장을 패키징 한 패키지

2. Spring Boot Extension Pack(Pivotal)

  - spring 프레임워크에 적용할 수 있는 유용한 기능이 들어있는 패키지, 말그대로 팩이여서 여러가지 스프링부트 관련 확장 패키지가 포함되어 있습니다.

 

 

이클립스 기반 sts 를 만든 회사에서 Spring boot 패키지를 만든것 같습니다, (Pivotal)

 

여기서 주의 할 설정은 스프링부트가 구동되려면 설치한 자바 경로를 잡아줘야 합니다.

이거만 잘 진행된다면 바로 스프링구동이 됩니다.

 

java 확장도구 까지 설치했다면, 위와같이 settings 에서 

jdk 로 검색 후 setting.json 에서 자바 경로를 설정해 줍니다.

java home 만 설정해 주면 됩니다.

mac 의 경우 jdk 설치시

/Library/Java/JavaVirtualMachines/ 에 설치 되고 jdk 버젼 폴더 하위에

/Contents/Home 경로로 세팅해주면 모든 준비가 끝납니다.

 

 

그러면 vscode 패키지 명령어를 실행하면 됩니다.

spring initializr 를 실행하면 되는데, 이클립스 sts 로 스프링부트를 해보신분들이라면 이부분부터는 거의 UI 만 다르지 내용은 똑같습니다.

 

프로젝트가 생성되면 오른쪽 상단에 실행버튼이 생깁니다.

그 외에도 프로젝트 화면에 스프링부트 대쉬보드 및 메이븐 플로그인도 같이 사용가능하도록 UI 가 구성되어 있습니다.

다음은 스프링부트를 실행한 화면입니다.

반응형

+ Recent posts