MapStruct 라이브러리를 이용해서 Dto와 Entity간 변환하는 테스트가 성공했기 때문에 큰 이상을 느끼지 못하고 다음작업을 하고 있었는데 갑자기 잘되던 회원가입 로직이 동작하지 않았다.

 

 

 

 

에러내용 :

detached entity passed to persist: koo.project.matcheasy.domain.member.Member; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist:

"준영속 상태인 엔티티가 영속되기위해 전달되었다"

 

 

아직 복잡한 단계의 로직을 짜지 않았고 엔티티 준영속 상태가 될 이유가 없다고 생각했었다.. 뭔가 트랜잭션 설정에 문제가 있다고 판단했다.

 

 

 

 

의심1. 

MemberRepository는 클래스 레벨에서 @Transactional 애노테이션을 readOnly = true 옵션으로 지정하여 안의 메소드를 읽기전용으로 지정을 해주었다. save는 직접 DB에 접근하기 때문에 readOnly옵션을 풀어준 @Transactional 애노테이션만 붙여준 상황이었다. 

 

여기서 발견한 잘못된 점은 Service계층에서는 @Transactional 애노테이션을 붙여주지 않았다는 것이었다. 한개의 Request가 있고 난 후, 같은 트랜잭션 단위로 동작하기 위해서는 Service에서도 @Transactional을 붙여주어야 할 것 같았다. 

 

하지만, 변경 후에도 위와 똑같은 에러가 발생했다.

 

 

 

 

의심2.

 

참고 : 

http://credemol.blogspot.com/2011/01/spring-junit-transaction.html

 

Spring에서 JUnit 테스트 시 Transaction 처리

http://viralpatel.net/blogs/2010/11/spring3-mvc-hibernate-maven-tutorial-eclipse-example.html 위의 블로그에 방문하면 spring3 기반에서 hibernate를 이용해서 da...

credemol.blogspot.com

@Test annotation과 함께 설정된 @Transactional은 항상 rollback 된다고 한다. 테스트 환경에서 save로직이 실행되지 않았기에 테스트클래스에 @Rollback(value = false) 애노테이션을 붙여주어 롤백되지 않고 직접 데이터베이스에 저장되는 것을 확인하고자 하였다.

 

하지만 결과는 마찬가지였다.

 

 

 

 

save메서드에 직접 로그를 찍어서 잘 넘어오나 확인했는데.. 이상한점이 발견되었다.

save: member=Member(id=0, loginId=test, password=test!, name=테스트, age=20, email=aa@aa.com)

id값이 0...? 아차..

 

 

 

MapStruct의 Mapper 파일이다. toEntity메서드는 Dto를 Entity로 바꾸어주는 메서드이다. @Mapping을 이용하여 매핑속성을 지정할 수 있다.

target은 Entity의 field를 지정하고, constant는 그 field를 상수 0L로 지정하겠다는 의미이다.

Member엔티티는 DB와 매핑되는 필드를 가지고 있기 때문에 id값이 있지만, Dto는 id필드가 없다. 그러므로 toEntity를 할때 id필드의 값이 없어서 상수로 지정해준 것이다. (id값이 없어서 Error가 날 수도 있을 것 같아서 테스트를 진행할 때 constant로 박아놓고 잘 돌아가는군. 이라고 생각했다......)

 

그런데, Member엔티티의 id값은 @GeneratedValue의 default전략인 AUTO로 지정했기 때문에 기본키를 전략에 맞게 자동으로 증가시킨다. (H2데이터베이스로 로컬테스트를 하고 있기 때문에, 기본전략은 SEQUENCE전략이다)

처음 시퀀스값이 0인 상태에서 DB에 접근할 때마다 sequence값을 증가시켜서 id값을 지정해야하는데 0L 상수로 고정이 되어 있었기 때문에 에러가 발생한 것이었다.

 

매핑 애노테이션을 없앤 후 오류를 해결하였다.

Spring STOMP를 이용하여 실시간 채팅이 가능한 페이지를 만드는중...

 

 

 

요놈이 나를 괴롭혔다...

SockJS로만 하면 연결이 되는데 Stomp를 얹어서 연결을 시도하는 순간 에러가 났다.

spring명세서랑 스택오버플로우랑 깃헙 예제 싹다 뒤져도 저 오류에 대한 해결법을 찾을 수 없었다..

 

 

 

 

<해결을 위한 여러가지 생각의 과정>

 

1. copyToString메소드의 매개변수가 배열로서(Ljava/io/ByteArrayOutputStream;Ljava/nio/charset/CharSet; -> Ljava가 배열이라는것도 이번에 알았다..) 받아져서 그런가?

 

2. json 형이 아니라 string으로 메세지를 넘기려고해서 파싱을 못하는건가?

 

3. 개발도구 Network에서 websocket요청값의 header가 upgrade로 정상 요청됨은 확인을 하였는데 Error메세지에 보니까 content-length : "0"이고 body도 비어 있으니까 헤더에 추가 정보가 필요한가?

 

 

 

 

1번은 Spring.io 문서에서 org.springframework.util.StreamUtils를 찾아보다가 발생한 의문이었는데, 애초에 ByteArray니까 어떤 배열형태라서 문제가 있는것 같지는 않았다. 다만 에러메세지가 이 문제를 계속 가리키고 있었기 때문에 가장 중요문제가 이곳인것 같았다...

 

2번은 아닌것 같았다. 여러 예제를 찾아보고 구글링해보아도, json형으로 id와 msg를 넘기는것이 당연히 더 좋은 형태이겠지만 테스트로 단순하게 메세지를 string형으로 넘기는 예제도 많이 있었다.

 

3번은... content-length에 대해서 찾아보다가

 

 

https://stomp.github.io/stomp-specification-1.2.html#Header_content-length

 

 

 

https://stomp.github.io 에서 우연히 찾게되어서 읽어보았는데, utf-8로 인코딩이 권장된다는 내용이다.

그런데 인코딩이 utf8으로 되어 있지 않았다!!! 약간의 희망을 품고 수정해보았지만

아쉽게도 해결되지 않았다..

 

 

 

 

 

자포자기의 심정으로 처음부터 다시 해보자는 생각을 가지고 메이븐 설정부터 다시 보았는데...

 

 

spring-messaging버전이 5.2.7이다. springframework 버전은 5.2.5인데.. 설마

 

 

 

 

 

.............. 그래도 이틀간의 삽질이 끝나서 좋았다..

 

 

오늘의 결론

버전관리를 잘하자..

 

 

 

 

버전을 따로 빼서 관리하자.. 윤재성 강사님(인프런 강좌)이 괜히 저렇게 하라고 한게 아니었다....ㅠ

자유게시판을 만들고 나니까 이미지를 올릴 수 있는 이미지 게시판도 만들고 싶어졌다.

이미지 게시판을 만들려면 메뉴를 선택할 수 있는 navigation bar 나 slide bar가 필요했다.

부트스트랩을 활용하고 있어서 부트스트랩에서 제공하는 네비게이션 바를 봤는데 마음에 들지 않았다... 내가 css나 클릭이벤트 같은 거를 좀 커스텀하고 싶었다.

 

 

이리저리 검색해가면서 css랑 클릭이벤트에 쓰일 클래스를 만들었다.

 

 

 

 

 

이제 적용을.. 해...

 

 

 

?

 

 

 

 

 

 

 

 

 

아, 경로를 잘못넣었나보다

 

 

 

 

contextPath를 가져와서 경로를 지정했다.

 

다시 실행..

 

 

 

 

 

 

?

 

 

 

 

 net::ERR_ABORTED 404

에러 발생.

 

뭘까.. 구글의 도움을 빌려보았다.

DispathcerServlet의 정적파일 매핑오류인것으로 보였다.

 

 

 

 

 

 

해결방법

 

 

 

Java 파일을 이용해서 Spring 설정을 하였다.

addResourceHandlers라는 메소드는 정적 파일의 경로를 매핑해주는 핸들러이다.

루트가 아닌 다른 위치에서 정적리소스를 제공하기 위한 것이라고 생각하면 될 것 같다.

registry.addResourceHandler("/resources/**").addResourceLocations("/resources/"); 를 이용해서 resources파일 아래의 정적메소드를 Handler로 등록해주고 경로도 추가해준다.

 

 

 

 

 

 

 

css파일을 이상없이 불러와서 적용됨을 확인하였다.

 

회원가입시 유효성 검사 기능을 추가하다가 생긴 오류로 인해서 한참을 묶여 있었다......

 

 

 

MemberController.java

 

JoinForm.jsp

 

joinMember라는 이름으로 jsp로 넘겨주었다. 넘겨줄 때, ModelAttribute를 이용하여 이름을 지정해준것이다. 그 이름으로 joinForm에서 form커스텀태그의 속성에 modelAttribute를 이용해서 받아주었다. 

빈의 형식은 MemberVO이기 때문에 joinForm에서 MemberVO의 프로퍼티와 똑같이 action으로 넘겨주면 @Valid어노테이션이 붙어있는 빈을 유효성검사 하게 된다.

 

그런데...

 

이대로 실행하면 자꾸 에러가 났다.

 

 

 

 

db.classname을 못찾는다?

 

 

ServletAppContext.java

 

db.properties에 디비접속정보를 담아서 위와 같이 @Value를 사용하여 변수에 주입받아서 사용했는데 이 부분이 에러가 난다. 분명 유효성검사 기능을 추가하기 전에는 나지 않았던 에러.

 

 

구글링해보니, @ProperySource로 등록하여 @Value를 하고, 또다른 properties파일을 메세지로 등록을 하게 되면 두 파일이 충돌이 나서 모두 인식하지 못하는 오류가 발생한다고 한다.

 

 

 

ServletAppContext.java파일에 PropertySourcePlaceholderConfigurer 라는 빈을 추가해주면 PropertySource에 등록한 properties를 따로 관리하기 때문에 충돌이 발생하지 않는다!!

 

 

PropertySourcePlaceholderConfigurer를 추가해주는 과정에서도 계속 오류가 나서 여기서만 한 3시간 고생했다.......

원인은........... 메소드 이름 자동완성..... 오타였다..

자동완성으로 PropertySourcePlaceholderConfigurer 클래스가 메소드 이름으로 완성되어서 만들어져 버림... 이걸 계속 찾지 못하였다.. 

 

 

 

 

 

암튼 오류 해결이긴한데, 

 

같은 빈으로 다른 valid후 error message를 출력하려고 하면 출력이 안된다.

그러니까 회원가입은 되는데 로그인은 <form:errors>로 출력이 안된다........ 추후에 해결해 보아야겠다.

에러 로그

### Cause: java.sql.SQLIntegrityConstraintViolationException: (conn=4502) Cannot delete or update a parent row: a foreign key constraint fails (`kooboard`.`board`, CONSTRAINT `reply` FOREIGN KEY (`b_index`) REFERENCES `board` (`b_index`)) 

 

board테이블에 있는 글을 삭제할 때, reply테이블의 b_index가 board테이블의 b_index에 외래키로 묶여 있기 때문에 다음과 같은 에러가 발생하였다.

 

 

BoardService.java

 

서비스레벨에서 참조되어있는 테이블의 row를 먼저 삭제 해 준 후, 실행함으로써 오류를 해결할 수 있었다.

db.properties

 

데이터베이스 접속정보를 db.properties파일에 정리를 해놓고 properties파일을 가져다가 datasource를 만들었다.

 

 

@PropertySource("/WEB-INF/properties/db.properties")

db.properties를 PropertySource로 등록해주고 @Value를 이용해서 정의해준 변수들을 가져와서 dataSource를 만들어준다.

 

 

접속, 쿼리 관리를 하는 SqlSessionFactory객체와 쿼리문 실행을 위한 MapperFactoryBean객체도 정의해주었다.

 

 

 

 

MapperInterface에 데이터를 집어넣을 수 있는 Insert문을 넣었다.

 

MapperInterface를 자동주입받은 후 선언한 insert메소드를 출력해보았는데...

 

 

 

 

심각: 경로 [/MVCStudy_JAVA]의 컨텍스트 내의 서블릿 [dispatcher]을(를) 위한 Servlet.service() 호출이, 근본 원인(root cause)과 함께, 예외 [Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLException: Cannot create PoolableConnectionFactory 'root'@'localhost' (using password: YES))
### The error may exist in com/study/spring/mapper/MapperInterface.java (best guess)
### The error may involve com.study.spring.mapper.MapperInterface.insert
### The error occurred while executing an update

 

 

 

JDBC Connection에러가 발생하였다.

dataSource에 이상이 있는것 같아서 다시 지웠다가 써도 안된다...

MapperInterface.java에 오류가 존재한다고 해서 쿼리문도 다시 적었다.. 그런데도 에러가 난다.

 

 

가정 1. 빈의 프로퍼티명 불일치

가정 2. mapper클래스 스펠링 문제

가정 3. dbcp 버전 문제

 

이 3가지정도가 떠올라서 한개한개 다시 해보기로 했다.

 

 

 

결국.. 찾아낸 문제..

 

properties파일을 가져올때 스펠링이 오류가 있었나보다.

변수명을 복붙으로 다시 선언해주고 url주소도 다시 쳐서 dataSource에 넣어주니까 오류가 안난다...

 

+ Recent posts