- hibernate 버전: 5.0.12-FINAL


스프링 배치 잡 작업 중 뜬금없이 하이버네이트의 EntityEntryContext 클래스의 reentrantSafeEntityEntries() 메서드에서 ArrayIndexOutOfBoundsException이 발생했다.


java.lang.ArrayIndexOutOfBoundsException: 6673

at org.hibernate.engine.internal.EntityEntryContext.reentrantSafeEntityEntries(EntityEntryContext.java:319)

at org.hibernate.engine.internal.StatefulPersistenceContext.reentrantSafeEntityEntries(StatefulPersistenceContext.java:1128)

at org.hibernate.engine.internal.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:136)

...


검색하다가 hibernate 커뮤니티의 JIRA에서 동일한 이슈[각주:1]를 발견할 수 있었는데 해당 이슈 보고자도 멀티스레드 환경에서 엔티티를 저장하는 과정에서 가끔 발생했다고 한다.

커멘트의 마지막 부분에 관련 이슈[각주:2]와 함께 이슈를 클로즈한다고 하여 따라가보니 5.1 버전에 픽스되었다고 하여 일단은 버전업을 해보는 것으로 해결.

  1. https://hibernate.atlassian.net/browse/HHH-8880 [본문으로]
  2. https://hibernate.atlassian.net/browse/HHH-10795 [본문으로]

QueryDSL 설정 시 Maven APT Plugin을 이클립스에서 사용 시 이슈가 있어서 아래와 같은 오류 메시지를 확인할 수 있다.


You need to run build with JDK or have tools.jar on the classpath.If this occures during eclipse build make sure you run eclipse under JDK as well


첫 번째 해결 방법으로는 커맨드 라인에서 직접 메이븐 빌드를 수행하는 것

mvn generate-sources


두 번째 해결 방법으로는 이클립스 설정 파일에 vm을 직접 설정해주는 것

이클립스가 위치한 폴더의 eclipse.ini (Spring Tool Suite의 경우 sts.ini)에서 아래와 같이 -vmargs 옵션의 위 쪽에 -vm 옵션을 추가해준다.

-vm

C:\Program Files\Java\jdk1.8.0_45\bin\javaw.exe

...

-vmargs


※ 참고 : http://stackoverflow.com/questions/24482259/eclipse-issue-with-maven-build-and-jdk-when-generating-qclasses-in-querydsl

Jenkins에서 SSH 서버 추가 시 다음과 같은 오류가 발생하면서 [Test configuration]을 수행할 수 없을 때


Connected, but failed to setup SFTP - check the SSH server. Exec commands should work, but transferring files will fail

jenkins.plugins.publish_over_ssh.BapSshSftpSetupException: Failed to connect SFTP channel. Message [4: Received message is too long: 1027423515]


위 문제는 SSH를 non-interactive로 세션을 접속했을 때 쉘 초기화 파일에서 표준 출력이 있는 경우에 발생한다.

접속하는 계정의 ~/.profile, ~/.bashrc, ~/.bash_profile과 같은 쉘 세션 초기화 시에 실행되는 스크립트에서 echo 명령어나 다른 명령어로 인해 출력이 있을 때 발생할 수 있다. 이 출력 값으로 인해 SSH 클라이언트나 scp, sftp에서 정상적으로 출력되지 않는 경우가 있으므로, 위 초기화 파일로 인해 출력되는 내용이 없도록 수정하면 된다.


확인 방법은 대상 SSH 서버를 원격 호스트로 하여 아래와 같은 명령을 수행해서 아무런 출력이 없음을 확인하면 된다. (/usr/bin/true 파일의 존재는 상관없다, 파일이 없을 때는 bash: /usr/bin/true: No such file or directory라고만 출력된다.)


$ ssh localhost /usr/bin/true

bash: /usr/bin/true: No such file or directory


내 경우는 쉘 접속 시 초기화 스크립트의 수행 순서를 확인하려고 .bashrc 파일에 echo "=== START ~/.bashrc ==="를 넣어두었는데, 이것 때문에 젠킨스에서 SSH 서버 설정을 테스트할 수 없었다. 위 구문만 지워주니 깔끔하게 success로 확인이 되었다.



참조 : OpenSSH FAQ - 2.9 sftp/scp is fails at connection, but ssh is OK

저번 주까지만 해도 괜찮던 랩탑이 작업을 못할 정도로 너무 느려졌다.

그저 주말에 꺼두었다가 어제 출근해서 켰더니 느렸다. 달리 설치한 거라고는 Groovy와 JRuby를 설치했다 정도..?


작업 할 때 기본적으로 크롬과 슬랙을 켜놓았는데, 슬랙에서는 타이핑을 따라오지 못할 정도로 랙이 걸리고, 크롬은 새 탭을 띄워서 페이지 로딩을 시작하는 데까지 길게는 1분이 넘게 걸리고(아직 페이지 로딩을 한 상태가 아니라 탭만 만들어지고 그대로 프리징..), 페이지를 로딩 하는데도 한참 걸린다.


무슨 이유인지 짐작할 수가 없어서 시스템 리소스를 확인해봤더니 CPU도 20~30%대이고, 메모리도 50% 미만으로 사용하고 있었는데, 혹시나 싶어 쓸 데 없는(특히 Active X류.. IE 플러그인들.. 부들부들) 프로그램들을 삭제하고,

서비스들도 정리하고, 시작 프로그램도 정리하고, 조각 모음도 해보고 재부팅도 해보고, 리눅스 파티션도 날리고 다 해보았지만 소용이 없었다.


그러던 와중에 검색 결과 중에서 크롬에서 "가능한 경우 하드웨어 가속 사용" 옵션을 끄면 하드웨어 가속 기능으로 인한 충돌을 없애서 속도를 향상할 수 있다는 걸 찾았다.

(이 글을 읽으시는 분 중에 비슷한 증상인 경우 크롬을 모두 종료하고, 파이어폭스, 오페라, 인터넷 익스플로러와 같은 타 브라우저를 실행해서 다른 브라우저는 정상적으로 동작한다면 같은 문제일 수 있습니다.)


먼저 크롬 실행 후 주소 입력 창에 chrome://settings 를 입력하거나 오른쪽 위의 메뉴에서 설정(S) 메뉴로 이동한 후,

스크롤을 내려 하단의 고급 설정 보기를 클릭하고, 펼쳐진 고급 설정에서 다시 하단의 시스템 섹션에서 "가능한 경우 하드웨어 가속 사용"의 체크를 해제하고 크롬을 재시작한다.




다행히 내 케이스는 하드웨어 가속이 문제였는지 느려졌던 증상이 나아졌다.

헌데 잘되다가 갑자기 느려진 이유를 도무지 알 도리가 없다. 그래픽 카드가 나갔나..?



참고 : 씨넷 코리아 기사 "이유없이 느린 구글 크롬 속도 개선하는 법"


여담으로 PC 리소스 모니터링을 하다가 nProtect에서 스레드를 152개를 사용하고 있었다. 윈도우즈 시스템에서 사용하는게 144개였는데.. -_- 리소스를 많이 먹고 있지는 않았지만 저 스레드를 다 어디에 쓰고 있는지 궁금하다. 결제할 때라던가 관공서 사이트를 이용할 때 설치했을텐데, 대체 왜 백그라운드에서 돌고 있는지 어이가 없다.

  확인 사항

1. Project Properties > Project Facets > Dynamic Web Module > Version 3.0




2. 프로젝트 폴더의 .settings 폴더의 org.eclipse.wst.common.project.facet.core.xml 파일 확인

<?xml version="1.0" encoding="UTF-8"?>

<faceted-project>

  <runtime name="Apache Tomcat v8.0"/>

  <fixed facet="jst.web"/>

  <fixed facet="jst.java"/>

  <installed facet="jst.web" version="3.0"/>

  <installed facet="jst.java" version="1.7"/>

</faceted-project>




3. web.xml 파일 스키마 버전 확인

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

xmlns="http://java.sun.com/xml/ns/javaee" 

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 

http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"

version="3.0">




4. 프로젝트 탐색기에서 프로젝트 선택 후 오른쪽 버튼 클릭하여 컨텍스트 메뉴 호출 후 [Maven] > [Update Projects...] 클릭 또는 Alt+F5키를 눌러 프로젝트 업데이트




하이버네이트 튜토리얼을 보며 따라하고 있는데 SessionFactory 빈을 생성하는 과정에서 예외가 발생하면서 아래와 같은 스택을 뿌리며 생성에 실패한다.


Caused by: java.util.MissingFormatArgumentException: Format specifier '%s'

at java.util.Formatter.format(Formatter.java:2519) ~[na:1.8.0_25]

at java.util.Formatter.format(Formatter.java:2455) ~[na:1.8.0_25]

at java.lang.String.format(String.java:2927) ~[na:1.8.0_25]

at org.jboss.logging.Slf4jLocationAwareLogger.doLogf(Slf4jLocationAwareLogger.java:81) ~[jboss-logging-3.1.3.GA.jar:3.1.3.GA]

at org.jboss.logging.Logger.debugf(Logger.java:553) ~[jboss-logging-3.1.3.GA.jar:3.1.3.GA]

at org.hibernate.annotations.common.util.StandardClassLoaderDelegateImpl.classForName(StandardClassLoaderDelegateImpl.java:53) ~[hibernate-commons-annotations-4.0.4.Final.jar:4.0.4.Final]

at org.hibernate.cfg.annotations.SimpleValueBinder.fillSimpleValue(SimpleValueBinder.java:491) ~[hibernate-core-4.3.5.Final.jar:4.3.5.Final]

at org.hibernate.cfg.SetSimpleValueTypeSecondPass.doSecondPass(SetSimpleValueTypeSecondPass.java:42) ~[hibernate-core-4.3.5.Final.jar:4.3.5.Final]

at org.hibernate.cfg.Configuration.processSecondPassesOfType(Configuration.java:1470) ~[hibernate-core-4.3.5.Final.jar:4.3.5.Final]

at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1418) ~[hibernate-core-4.3.5.Final.jar:4.3.5.Final]

at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1844) ~[hibernate-core-4.3.5.Final.jar:4.3.5.Final]

at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1928) ~[hibernate-core-4.3.5.Final.jar:4.3.5.Final]

at org.springframework.orm.hibernate4.LocalSessionFactoryBuilder.buildSessionFactory(LocalSessionFactoryBuilder.java:372) ~[spring-orm-4.1.6.RELEASE.jar:4.1.6.RELEASE]

at org.springframework.orm.hibernate4.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:454) ~[spring-orm-4.1.6.RELEASE.jar:4.1.6.RELEASE]

at org.springframework.orm.hibernate4.LocalSessionFactoryBean.afterPropertiesSet(LocalSessionFactoryBean.java:439) ~[spring-orm-4.1.6.RELEASE.jar:4.1.6.RELEASE]

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1633) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE]

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1570) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE]

... 43 common frames omitted





구글링으로 예외와 관련한 것들을 찾아보다가, 비슷한 유형이 나타나지 않길래 라이브러리 내에서 어떤 부분이 문제인지를 확인하려고 org.hibernate.annotations.common.util.StandardClassLoaderDelegateImpl 클래스를 따라가보니 아래 부분에서 예외가 발생했다.


catch ( Throwable ignore ) {

log.debugf( "Unable to locate Class [%s] using TCCL, falling back to HCANN ClassLoader" );

}


빨간색으로 표기한 %s에 전달되어야 할 문자열을 전달하지 않아서 발생하는 예외였다. 실수는 할 수 있지만.. 유틸리티성 클래스라 테스트 커버리지에 포함되지 않아서인지 릴리즈에 버젓이 나 버그요 어디 한번 실행해 보시지 하는 태도라니.


이는 하이버네이트 ORM 4.3.5.Final 버전에서 발생하며, 이 후 버전에서는 고쳐진 듯 하다. (4.3.10.Final 버전으로 확인하니 예외가 발생하지 않는다.)


사실 상용 프레임워크나 소프트웨어는 거의 사용하지 않고 오픈 소스를 주로 이용하다보니, 검증없이 신뢰하기 마련인데, 이번 계기로 모든 것에 의심을 가지자는 태도를 다시 갖춰야겠다.

Getter/Setter를 이클립스 코드 생성에서 지원해서 안쓸까하다가 가독성도 떨어지고 해서 lombok을 설정하려고 했는데 @Data 애너테이션을 적용해도 Getter/Setter가 생성되지 않는다. 몇 가지 사항을 체크해보았다.


VM 인자 설정이 잘못되었나?

-Dbootclasspath 추가 -> X


기존에 컴파일된 바이트코드가 남아있나?

메이븐 Update projects, Project Clean -> X


full path 설정이 없어서 그런가?

java -Dlombok.installer.fullpath -jar lombok.jar


config 파일을 설정해주어야 하나?

lombok.config 파일을 프로젝트 폴더에 추가 -> X


이클립스의 애너테이션 프로세싱 설정인가?

애너테이션 프로세싱을 활성화 해보았다 -> X


이클립스 버전인가?

아직 Eclipse Luna를 지원하지 않아서 그런건지 Kepler로 설치해 보았다. -> X


자바 버전인가?

JDK 8을 설치해서 쓰고 있는데, CSB.IO는 JDK 7로 되어있어 컴파일 설정에서 Java 1.7로 설정해보았다 -> 자바 버전 때문인 것 같다.

Java APT가 Java 8로 올라오면서 없어지고, JSR-269 PAPA(Pluggable Annotation Processing API)가 추가되면서 lombok에서 아직 해당 부분에 대한 대응이 되지 않은 듯 하다. (확인해볼 필요가 있음)


Lombok 이클립스 설정 exe 파일을 선택해서 그런가?

lombok.jar를 실행하고 설치된 이클립스를 선택하는데 계속 sts.ini 파일을 선택했는데 sts.exe로 선택해서 설정해보았다. (사실 바뀌는 건 sts.ini 파일 뿐이니 관련이 없을 듯하다.)


참고 링크

- http://stackoverflow.com/questions/3418865/cannot-make-project-lombok-work-on-eclipse-helios

- https://projectlombok.org/features/configuration.html

- https://groups.google.com/forum/#!searchin/project-lombok/sts$20@data/project-lombok/DXY9IKW08A0/PdNQV9y2_5AJ

아마 Moneycomb 프로젝트를 할 때였던 것 같다. 나는 그때도 JSON으로 메시지를 변환하기 위해, 책과 튜토리얼에서 빈번하게 등장하고, 다들 문제없이 쓰는 것으로 보이던 MappingJacksonHttpMessageConverter를 '나도 당연히 되겠지?' 하며 튜토리얼들을 따라 서블릿 컨텍스트에 빈을 설정했다.


지옥의 시작

왜인가? 어째서인가? 남들 다 된다고 댓글에도 "Thank you!"가 넘치고, 누구하나 되지 않는다는 이 없는데, 나는 왜 안되는 것인가.

지금도 그렇지만 그 때에도 Google의 JSON 라이브러리인 Gson을 즐겨썼다. API가 복잡하지 않고 설정도 간단했기 때문이다.

하지만 스프링에서 기본적으로 제공하는 JSON 메시지 컨버터는 Jackson에 의존성이 있었는데, 일단 Jackson이라는 라이브러리 자체가 낯설었고, 스프링 MVC의 동작을 깊이 파악하고 있지 않았던 탓에 하라는 대로 하는 것 외에는 손대볼 수 있는게 없었다.

열심히 설정하고 코드를 따라서 친 후 실행, 두근거리는 마음으로 Postman으로 메시지를 날려보았다.

@RequestBody 애너테이션이 붙어있는 파라미터에서도 변환이 되질 않고, @ResponseBody 애너테이션이 붙어있는 파라미터에서도 변환이 되질 않는다.

이때는 log4j 설정도 잘 몰라서, 콘솔에서는 어떠한 로그도 뜨지 않길래, 스프링이 아무말 없이 응답으로만 "안돼"라고 하는 줄로만 알았다.


Content-Type, Accept

스프링 컨트롤러에서 @RequestMapping 애노테이션에는 Content-Type과 Accept를 설정할 수 있는 consumes와 produces 속성이 있다. JSON의 미디어 타입이 application/json 인 것은 웹 하는 사람이라면 누구라도 알고 있을 것이다.

튜토리얼에서는 명시적으로 지정해주지 않았지만, 안되길래 consumes와 produces에 "application/json"이라고 적어주었다. 안된다. 이게 문제가 아닌가보다.


오늘도 삽질

그 때와 똑같은 행태를 반복해서였는지는 모르겠지만 오늘도 똑같은 현상이 나타났다. JSON 직렬화도, 역직렬화도 안된다.

그 때는 해결하지 못했던 문제였지만 오늘은 나름 해결을 하긴 했다.

여러가지 오해와 착각과 습관과 편견과 게으름의 결과였던 듯 하다.


1. 반환 타입

다른 테스트 클래스를 만들거나 모델 클래스를 반환하려니 값을 넣기도 귀찮아서

@RequestMapping(....)

@ResponseBody

public Object test(@RequestBody ....) { ... }

위 처럼 Object를 반환했다. JSON 자체에서도 { } 라고 쓰기도 하거니와, Gson에서는 Object 객체도 잘 변환한다. 빈 객체를 표현할 수도 있으니 당연하다고 생각했는데, Jackson에서는 Object 객체를 JSON으로 직렬화할 수 없다.

"Can not create bean serializer for Object.class" 란다. 어떤 정책으로 인해서 이렇게 정해졌는지는 알 수 없지만, 이것 때문에 한방.


2. 테스트 Ajax 요청

테스트로 JSON 객체를 보낼 때도, 필드 몇 개를 대충 보내거나 했는데, 이 전 버전의 Jackson(codehaus)은 JSON에는 있지만 변환될 자바 타입에서 없는 속성, 빈 문자열은 변환할 수 없고 예외를 발생시킨다.

애초에 테스트 JSON 문자열을 잘 썼으면 문제가 없었을지도 모르지만... 이 역시 Gson을 쓰면서 Gson이 동작하는 대로 생각해서 였던 것 같다. Gson에서는 없는 프로퍼티는 무시하고, 빈 JSON 문자열의 경우 null을 반환한다.


어쨌든 이번 일로 인해 좀 더 MessageConverter의 동작을 명확히 알게 되었고, 더불어 아래와 같은 것 잡지식(?)이 늘었다.

- com.fasterxml.jackson은 org.codehaus.jackson를 승계한 프로젝트

- com.fasterxml.jackson은 MappingJackson2HttpMessageConverter와 바인딩

- org.codehaus.jackson은 MappingJacksonHttpMessageConverter와 바인딩

- Spring 4부터 GsonHttpMessageConveter가 내장

- Spring 4부터 MappingJacksonHttpMessage는 없어짐





Gson 기반의 메시지 컨버터 설정 (Maven, Spring 4.1.6.RELEASE)


Maven Dependency 추가

<dependency>

<groupId>com.google.code.gson</groupId>

<artifactId>gson</artifactId>

<version>2.3.1</version>

</dependency>


servlet-context.xml 설정

<beans:bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">

<beans:property name="messageConverters">

<beans:list>

<beans:ref bean="jsonMessageConverter"/>

</beans:list>

</beans:property>

</beans:bean>


<beans:bean id="jsonMessageConverter" class="org.springframework.http.converter.json.GsonHttpMessageConverter">

   </beans:bean>



※ 참고 자료

- http://www.leveluplunch.com/java/tutorials/023-configure-integrate-gson-spring-boot/

- http://www.studytrails.com/java/json/jackson-create-json.jsp

- http://docs.spring.io/spring/docs/current/javadoc-api//org/springframework/http/converter/json/GsonHttpMessageConverter.html

- http://erictus.tistory.com/entry/Spring-JSON-View-구현하기2-ResponseBody


Rabbit MQ의 ConnectionFactory를 아래처럼 연결의 각 프로퍼티를 지정해주고,


 ConnectionFactory factory = new ConnectionFactory();

 factory.setUsername("test");

 factory.setPassword("password");

 factory.setVirtualHost("vhost");

 factory.setHost("localhost");

 factory.setPort(5672);


새로운 커넥션을 생성하면 아래 메시지와 함께 예외가 발생한다.

ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile.


검색해보니 3.3.1 버전부터 guest 계정으로 루프백 주소 이외에는 연결할 수 없도록 바뀌었다고 하는데, guest 계정을 사용한 것도 아니고, 다른 호스트도 아닌데 인증에 실패했다.


조금 방법을 바꿔서 각 프로퍼티를 세터로 설정하는 대신 factory.setUri()메서드를 이용해 연결 설정을 하고 새로운 커넥션을 생성하면 정상적으로 연결된다. 아래와 같은 모습이다.

String urlEncodedPassword = URLEncoder.encode("test", "UTF-8");

factory.setUri("amqp://test:" + urlEncodedPassword + "@localhost:5672/vhost");


빠진 연결 정보가 없는데도(굳이 따지자면 amqp 스키마 정도..?) URI를 통해서는 연결이 되고, 프로퍼티를 설정해주었을 때는 연결이 되지 않는다.


이유를 알 수 없어서 ConnectionFactory의 setUri() 메서드의 소스코드를 봐도, 명확한 이유는 알 수 없었다.


Uri 객체로부터 host, port, username, password, virtualhost를 가져와서 동일하게 세터를 호출하여 값을 설정한다.


문제가 고쳐지긴 했지만 찝찝하다. 뭘 잘못하고 있는걸까



----- 추가

연결이 되지 않은 이유는... 패스워드에 오타가 있었습니다. 하하;;

3.3.1 버전부터 guest 계정으로는 localhost만 접속할 수 있다고 합니다. 링크를 참고하세요. http://www.rabbitmq.com/access-control.html#loopback-users



속성으로 하다보니 기본적인 설정에서부터 애로사항이 많다..

WEB-INF 폴더 내의 web.xml에 서블릿으로 Spring의 DispatcherServlet을 사용하는 경우에


<servlet>

<servlet-name>servletname</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<servlet>


위와 같이 설정하는데, 이 때 서버에서는 해당 서블릿의 컨텍스트 파일을 읽게 되는데, 파일 명은

<servlet-name>에 들어간 값에 -servlet 접미사가 붙은 xml파일을 WEB-INF 폴더로부터 찾게 된다.

따라서 서블릿 이름과 서블릿 컨텍스트 설정 파일의 이름(접미사를 제외한)은 같아야 한다.



만약 서블릿 이름을 foo로 했다면 web.xml의 설정과 컨텍스트 설정 파일 이름은 아래와 같다.



WEB-INF/web.xml

... 생략 ...

<servlet>

<servlet-name>foo</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

</servlet>


... 생략 ...



위 설정에 따른 서블릿 컨텍스트 설정 파일 : WEB-INF아래 foo-servlet.xml




+ Recent posts