반응형

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

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!

 

반응형
반응형

Gson 라이브러리 및 Jackson 사용하여 Json String 을 만들때 Date 클래스 및 LocalDateTime 항목이 있는 class 으로 json string 으로 변환하게 되면 변환된 값이 yyyy-MM-dd'T'HH:mm:ss 이러한 포맷으로 변환이 되지 않는다.

 

이런 경우 모두 커스텀을 해줘야 한다.

 

Jackson 

Date with Jackson

public class ItemDate {

  private Integer id; 
  private String name; 
  private String createBy; 
  @JsonFormat(shape= JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone="Asia/Seoul") 
  private Date createAt; 
    
}

 

이렇게 하면 "2019-05-15T11:23:10.108+0900" 와 같은 문자열로 변환이된다.

LocalDateTime with Jackson

public class ItemLocalDateTime { 
  private Integer id; 
  private String name; 
  private String createBy; 
  JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS") 
  @JsonDeserialize(using = LocalDateTimeDeserializer.class) 
  @JsonSerialize(using = LocalDateTimeSerializer.class) 
  private LocalDateTime createAt; 
}

이렇게 하면 JSON에서는 "2019-05-15T11:24:46.223" 와 같은 문자열로 변환이된다.

참고로, TimeZone을 생략했는데, Z를 붙이면 Unsupported field: OffsetSeconds 라는 예외가 발생한다.

지역 시간은 시간대 필드를 가지고 있지 않기 때문이다. 이것은 ZoneZonedDateTime에 대응하기 때문이다.

 

Gson

Gson의 경우 어노테이션이 아니라 GsonBuilder 로 타입을 지정해야한다.

registerTypeAdapter 을 통해 커스텀이 가능하므로 class 를 따로 만들어준다.

Date with Gson

class GsonDateConverter implements JsonSerializer<Date>, JsonDeserializer<Date> { 
private static final String FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; 
	@Override 
    public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) { 
    	SimpleDateFormat simpleDateFormat = new SimpleDateFormat(FORMAT); return src == null ? null : new JsonPrimitive(simpleDateFormat.format(src)); 
  	} 

	@Override 
    public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 
    	SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); 
        try { return json == null ? null : simpleDateFormat.parse(json.getAsString()); } 
        catch (ParseException e) { throw new JsonParseException(e); } 
    } 
}

LocalDateTime with Gson

public class GsonLocalDateTimeAdapter implements JsonSerializer<LocalDateTime>, JsonDeserializer<LocalDateTime> {
    @Override public JsonElement serialize(LocalDateTime localDateTime, Type srcType, JsonSerializationContext context) {
        return new JsonPrimitive(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(localDateTime));
    }

    @Override public LocalDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        return LocalDateTime.parse(json.getAsString(), DateTimeFormatter.ISO_LOCAL_DATE_TIME);
    }
}

사용

Gson gson = new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new GsonLocalDateTimeAdapter())
			.registerTypeAdapter(LocalDate.class, new GsonLocalDateAdapter()).create();

다음처럼  변환하고자 하는 데이터타입 클래스를 어댑터와 같이 registerTypeAdpater 메소드에 추가하면 된다.

 

반응형
반응형

java 에서 파싱을 해주는 라이브러리가 있는데 대표적인 라이브러리고 gson 과 jackson 있다.

gson 을 사용시 기본적으로 new Gson().toJson() 및 fromJson() 으로 기본적인 파싱이 되지만, 종종 커스텀이 필요한 경우가 있다,

이런 경우 Desrializer 및 Serializer 기능이 필요하다.

1. Custom Serialization

Serialization 은 toJson 할 경우 필요하다. 자바 객체를 Json 으로 변환 시 필요하다.

예시

Serialization 생성

public class BooleanSerializer implements JsonSerializer<Boolean> {

    public JsonElement serialize(Boolean aBoolean, Type type,
        JsonSerializationContext jsonSerializationContext) 
    {
        if(aBoolean){
           return new JsonPrimitive(1);
        }
        return new JsonPrimitive(0);
    }
}

사용

    public static void main(String[] args) throws Exception 
    {
        Employee emp = new Employee(1, "Lokesh", "Gupta", "howtodoinjava@gmail.com", true);

        Gson gson = new GsonBuilder()
                .registerTypeAdapter(Boolean.class, new BooleanSerializer())
                .setPrettyPrinting()
                .create();

        String json = gson.toJson(emp);

        System.out.println(json);
    }

결과물

{
  "id": 1,
  "firstName": "Lokesh",
  "lastName": "Gupta",
  "email": "howtodoinjava@gmail.com",
  "active": 1
}

다음과 같이 boolean 이 1또는 0 으로 Json 변환시 사용하려면 다음과 같이 사용하면 된다.

반대로 1또는 0을 true 또는 false 로 변환하려면 JsonDeserializer 을 반대로 implements 를 하면 된다.

이 떄 registerTypeAdapter 메소드는 빌더 타입이라 추가로 클래스를 붙이고 싶은 경우가 있으면 추가하면 된다.

날짜를 gson으로 Serialize 하기

또 사용해야 하는 이유는 날짜 Date 클래스를 gson 으로 Json String 을 만들시

Aug 31, 2020 10:26:17 처럼 날짜가 표시된다. 하지만 2020-08-31 10:26:17 이렇게 변환되기를 바랄것이다.

JsonSerializer<Date> ser = new JsonSerializer<Date>() {
    @Override
    public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext
                context) {
        return src == null ? null : new JsonPrimitive(src.getTime());
    }
};

JsonDeserializer<Date> deser = new JsonDeserializer<Date>() {
    @Override
    public Date deserialize(JsonElement json, Type typeOfT,
                JsonDeserializationContext context) throws JsonParseException {
        return json == null ? null : new Date(json.getAsLong());
    }
};

Gson gson = new GsonBuilder()
                .registerTypeAdapter(Date.class, ser)
                .registerTypeAdapter(Date.class, deser)
                .create();

다음과 같이 익명클래스 로 객체 선언 후 사용 가능 하다.

 

2. Custom Deserialization

반대로 json String 을 객체로 변환시 아래와 같이 사용하면 된다.

json 문자열을 객체로 파싱할때 빈 문자열을 null 로 치환

public class EmptyToNullStringDeserializerGson implements JsonDeserializer<String> {

	@Override
	public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
		String nullCheckString = json.getAsJsonPrimitive().getAsString();
		return "".equals(nullCheckString) ? null : nullCheckString;  
	}

}

사용법

GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapter(String.class, new EmptyToNullStringDeserializerGson());
Gson gson = gb.create();

클래스 response =  gson.fromJson(jsonStr, 클래스명.class);

 

 

참고문헌

https://riptutorial.com/android/example/15339/custom-json-deserializer-using-gson
https://futurestud.io/tutorials/gson-advanced-custom-deserialization-basics
https://howtodoinjava.com/gson/custom-serialization-deserialization/

반응형
반응형

프론트에서 post 로 데이터 객체를 보내게 되면 

서버에서 RequestBody 로 객체를 받아야 하는 경우가 있다.

 

이때 받게 되는 데이터를 vo로 만들어서 받게 된다.

필드명을 프론트에서 보내는 객체명과 다르게 쓰고 싶은 경우 필드명을 어노테이션으로 받아야 한다.

 

같게 하려면 프론트필드명과 스프링객체 필드명을 같게만 하면 된다.

안되는 경우는 예를 들어 프론트 json 필드명에 . dot 가 들어간 경우는 아래 @JsonProperty 를 사용해야 한다.

 

 

프론트에서는 다음과 같이 post 로 데이터를 전송시

{
  "relationship.name": "someting"
}

서버에서는 프론트 필드명과 서버받는 객체의 필드명을 달리 하려면 아래와 같이

@JsonProperty 를 추가하면 된다.

public class Request {

    @JsonProperty("relationship.name")
    private String relationshipName;

    ...
}

물론 이때 컨트롤러는 @RequestBody 로 받을 경우이다.

public ResponseEntity<String> test(@RequestBody Request request) throws Exception {
반응형
반응형

mysql Table 정보검색 및 column정보 검색

 

1 테이블 정보검색.

select * 

from INFORMATION_SCHEMA.tables 

where table_schema='데이터베이스명';

=> show tables;

 

2. 컬럼명 검색.

select * 

 from INFORMATION_SCHEMA.columns 

where table_schema='데이터베이스명

    and table_name='테이블명' 

 order by ordinal_position;

=> show full columns from 테이블명 ;

 

반응형
반응형

JPA 를 사용하면서 DB 에 어떤 특정값이 들어오면 변환해줘야 할 떄가 있다.

예를 들어 empty string 을 null 로 넣어줘야 할떄다. (mysql)

 

빈스트링이 "" 가 들어올때 null 로 바꿔서 db 에 넣고 싶을때 @Convert 가 필요하다, 

이 외에도 어떤 고정된 값들은 특정 값으로 변환해서 db 에 넣어줄떄 이 Convert 가 필요하다.

 

 

구현 방법은 변경하고자 하는 컬럼명 위에 @Convert 적고, 커스텀한 Class 명을 기입한다.

@Table(name = "db1.test")
public class Test {   

    ...

    @Convert(converter = EmptyStringToNullConverter.class)
    @Column(name = "test_data")
    private String test_data;
    
    ...
}

커스텀할 Classs 에는 AttributeConverter 를 구현한다.

이름 그대로 디비 넣기 전에 변환해주는 메소드명은  convertToDatabaseColumn 에서 구현한다.

반대로 db 에서 꺼내서 vo,entity 에서 사용할떄는 convertToEntityAttribute 를 사용해서 다시 변환해 준다.

@Converter(autoApply = true)
public class EmptyStringToNullConverter implements AttributeConverter<String, String> {

    @Override
    public String convertToDatabaseColumn(String string) {
        // Use defaultIfEmpty to preserve Strings consisting only of whitespaces
        return "".equals(string) ? null : string;
    }

    @Override
    public String convertToEntityAttribute(String dbData) {
        //If you want to keep it null otherwise transform to empty String
        return dbData;
    }
}

 

 

참고문헌

https://www.baeldung.com/jpa-attribute-converters [jpa 컨버터]

 

반응형
반응형

파이썬 실행시 SyntaxError: Non-ASCII charcer 이런오류가 나면 인코딩 문제이다.

 

SyntaxError: Non-ASCII character  '\xea' in file test.py on line 1, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

이 경우에 해결방법은 파일 내부에 인코딩 방식변경을 해주면 된다,

 

utf-8 사용시

# -*- coding: utf-8 -*- 

euc-kr 사용시

# -*- coding: euc-kr -*-

 

반응형

'Languague > Python' 카테고리의 다른 글

[Python] python 으로 네이버 검색어 조회  (2) 2020.05.19
[Python] pip 설치  (0) 2020.05.19

+ Recent posts