회사에서 변경이력관리시스템(VCS, Version Control System)으로 SVN에서 Git으로 변경하려는 시도를 하고 있다.

이 시도는 앞으로도 꾸준하게 진행예정이다. 현재는 BitBucket을 원격저장소Remote Repository로 사용하고 있는데, 사설 저장소private Repository를 위해서 사용하고 있지만, 몇가지 불편한 사항들이 있어서 Github으로 변경하고 유료서비스를 이용하려고 한다.


그런데, SVN에서 Git으로 넘어가려는 과정에서 다른 개발자와의 충돌이 발생하고 있다. 반대하는 의견으로 내놓는 의견 중에 하나가 

'Git은 개발자가 서로 멀리 떨어져 있는 분산된 개발환경에 적합하다'

라는 의견이다. Git은 지역저장소Local Repository를 기반으로 하는 변경이력관리시스템이다. 여기에 원격저장소를 연결하면 그때 분산 변경이력관리시스템(DVCS, Distributed Version Control System)으로서의 진가를 발휘하게 된다.


SVN은 중앙집중형 변경이력관리시스템이다. 개발서버에 SVN Server를 설치하고 이 SVN에 연결체Connector를 이용하여 연결하여 관리한다. SVN은 변경이력이 추가될 때마다 해당하는 변경이력의 데이터를 모두 함께 저장하기 때문에, 변경이력을 관리하는 데이터와 변경이력이 증가할수록 시스템이 무거워지는 단점이 있다. Git은 지역Local을 기반으로 파일시스템으로 처리되기 때문에 원격지에서 데이터를 주고받아야 하는 SVN과는 속도의 차이가 확연하게 난다. 지역에서 작업한 내용들의 변경이력을 쉬이 관리하고, 필요에 따라서 원격지에 밀어넣을 수 있다(푸쉬, push).

Git의 변경이력관리 중심활동지는 로컬이다. 그러나 원격저장소를 지정하고 함께 작업을 진행하게 되면 SVN과 동일한 기능을 모두 사용할 수 있다.

SVN을 사용하기 위해서는 서버를 개발자가 관리해야하는 부담이 있다. 설치 이후에 서버의 용량이나 상태를 꾸준하게 주시(모니터링Monitoring) 해야하는 귀찮음이 발생한다는 것이다. 이런 것을 해결하려는 용도로 Github에서 유료로 사설저장소를 사용하는 것을 요청하고 있다. 이 서비스를 이용하면 서버에 대한 관리를 개발자가 신경쓰지 않아도 된다는 이득을 얻을 수 있다(사용하면서 서비스가 중단된 경험은 1년에 1, 2번 정도였다).  이럴 경우에는 서비스가 정상화될 때까지 로컬에서 작업(커밋Commit)해두고 서비스가 정상화되었을 때 밀어넣으면 된다. 


깃이 분산변경이력관리시스템DVCS라는 타이틀을 가지고 있다고 해서, 반드시 분산된 개발환경에서 사용해야만 하는 것은 아니다.

SVN 사용을 지속하려는 개발자들을 어떻게 하면 Git으로 이동시킬 수 있을까?


요즘 한켠에서 가지고 있는 고민...

  1. EB 2013.05.07 09:27 신고

    저희 회사는 CVS였었는데 git으로 도입하기까지 얼마 걸리지 않았고 의견 조율이 쉬운 편이었습니다. 도입한 서버와 시스템은 ubuntu12.04LTS + gitlab이었습니다.

    개발자가 서버를 관리해야하는 부담에 있어서는 CVS나 SVN이나 gitlab이나 별반 어렵지 않다고 생각합니다. 백업이나 복원이 다들 크게 어렵지 않아서 주기적인 백업 시스템만 잘 찍어 놓으면 큰 문제가 없으니까요.

    도구라는 측면에서 똑같은 일을 할 수 있다면 그 장점을 어필하기 어렵습니다. svn과 git의 비교하면서 git을 도입해야하는 이유를 설명해봐야 그 장점이 먹혀들기 어렵죠. 더 빠르다. 오프라인에서도 쓸 수 있다. 등의 장점은 분명 그 이점이 있습니다만 설득하기 쉽지 않습니다.

    저의 경우는 커뮤니케이션과 이슈트래커를 같이 할 수 있는 github 또는 gitlab과 같은 도구의 장점을 설득하는 것이 훨씬 좋았었습니다. 기존에 이슈트래커에 여러 불만이 있었기 때문에 그 장점들을 보여주니 쉽게 설득할 수 있었습니다.

    저희 회사 특수성도 한몫했었겠지만... 이와 같은 접근법도 조심스럽게 추천드려봅니다.

    • 조언 감사합니다. ^^

      기존에 있던 SVN과의 연동만 어느정도 지원을 한다면,
      Git을 사용하는 것으로 자연스럽게 넘어갈 수 있지 않을까하고 긍정적인 희망을 가지고 있습니다. ^^

      이클립스에서 SVN을 사용하기 위해서는 subclipse같은 플러그인을 추가로 설치해야한다는 것도 꽤 불편함으로 느껴질테니까 별로 부담은 없습니다. ㅎㅎ

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException

개발환경

  • hibernate : 4.2.0.Final
    • hibernate dialect : org.hibernate.dialect.MySQLDialect
  • MariaDB : 5.5.29-MariaDB

문제 로그

java.lang.reflect.InvocationTargetException

    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)

    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

    at java.lang.reflect.Method.invoke(Method.java:601)

    at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:297)

    at java.lang.Thread.run(Thread.java:722)

Caused by: org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [insert into department(id, name, description, parent_id, createdDate) values(?, ?, ?, ?, ?)]; nested exception is java.sql.BatchUpdateException: Table 'database.department' doesn't exist

    at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:237)

    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)

    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:605)

    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:617)

    at org.springframework.jdbc.core.JdbcTemplate.batchUpdate(JdbcTemplate.java:890)

    at org.springframework.jdbc.core.namedparam.NamedParameterBatchUpdateUtils.executeBatchUpdateWithNamedParameters(NamedParameterBatchUpdateUtils.java:40)

    at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.batchUpdate(NamedParameterJdbcTemplate.java:324)

    ... 6 more

Caused by: java.sql.BatchUpdateException: Table 'database.department' doesn't exist

    at com.mysql.jdbc.PreparedStatement.executeBatchSerially(PreparedStatement.java:2054)

    at com.mysql.jdbc.PreparedStatement.executeBatch(PreparedStatement.java:1467)

    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeBatch(NewProxyPreparedStatement.java:1723)

    at org.springframework.jdbc.core.JdbcTemplate$4.doInPreparedStatement(JdbcTemplate.java:905)

    at org.springframework.jdbc.core.JdbcTemplate$4.doInPreparedStatement(JdbcTemplate.java:890)

    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:589)

    ... 12 more

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'database.department' doesn't exist

    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)

    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)

    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)

    at java.lang.reflect.Constructor.newInstance(Constructor.java:525)

    at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)

    at com.mysql.jdbc.Util.getInstance(Util.java:386)

    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1054)

    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4120)

    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4052)

    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2503)

    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2664)

    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2815)

    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2155)

    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2458)

    at com.mysql.jdbc.PreparedStatement.executeBatchSerially(PreparedStatement.java:2006)

    ... 17 more

[INFO] ------------------------------------------------------------------------

[INFO] BUILD FAILURE

[INFO] ------------------------------------------------------------------------

[INFO] Total time: 10.762s

[INFO] Finished at: Wed Apr 24 22:06:17 KST 2013

[INFO] Final Memory: 43M/365M

[INFO] ------------------------------------------------------------------------

[ERROR] Failed to execute goal org.codehaus.mojo:exec-maven-plugin:1.2.1:java (default-cli) on project docs-flow-data-manager: An exception occured while executing the Java class. null: InvocationTargetException: PreparedStatementCallback; bad SQL grammar [insert into department(id, name, description, parent_id, createdDate) values(?, ?, ?, ?, ?)]; nested exception is java.sql.BatchUpdateException: Table 'database.department' doesn't exist -> [Help 1]

[ERROR]

[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.

[ERROR] Re-run Maven using the -X switch to enable full debug logging.

[ERROR]

[ERROR] For more information about the errors and possible solutions, please read the following articles:

[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException

해결책

  • 하이버네이트가 자동으로 @Entity를 이용하여 테이블을 생성할 때 기본은 클래스명을 따른다.
    • 대소문자를 구분하는 Database 에서는 이 부분이 문제를 일으키는 요인으로 추측된다.
  • @Entity 아래에 @Table(name=“TABLE_NAME”) 으로 테이블명을 대문자로 하여 강제적으로 선언했다.
  • 참조관계가 생성되는 부분들에 대해서도 @JoinTable(name=“JOIN_TABLE”)으로 지정해주는 것이 적절해보인다.

### 개발환경

* Spring MVC 3.2.0

* Hibernate 4.2.0.Final


### 문제발생

MVC 패턴에서 Controller 단에서 @PathVariable로 객체를 받아서 처리하려는 시도를 하면서 문제가 발생했다.


### 로그


HTTP Status 500 - No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: java.util.HashMap["result"]->com.sil.docsflow.web.common.Result["data"]->com.sil.docsflow.domain.company.User_$$_javassist_15["handler"])


type Exception report


message No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: java.util.HashMap["result"]->com.sil.docsflow.web.common.Result["data"]->com.sil.docsflow.domain.company.User_$$_javassist_15["handler"])


description The server encountered an internal error that prevented it from fulfilling this request.


exception


org.codehaus.jackson.map.JsonMappingException: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: java.util.HashMap["result"]->com.sil.docsflow.web.common.Result["data"]->com.sil.docsflow.domain.company.User_$$_javassist_15["handler"])

org.codehaus.jackson.map.ser.StdSerializerProvider$1.failForEmpty(StdSerializerProvider.java:89)

org.codehaus.jackson.map.ser.StdSerializerProvider$1.serialize(StdSerializerProvider.java:62)

org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:430)

org.codehaus.jackson.map.ser.BeanSerializer.serializeFields(BeanSerializer.java:175)

org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:142)

org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:430)

org.codehaus.jackson.map.ser.BeanSerializer.serializeFields(BeanSerializer.java:175)

org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:142)

org.codehaus.jackson.map.ser.MapSerializer.serializeFields(MapSerializer.java:287)

org.codehaus.jackson.map.ser.MapSerializer.serialize(MapSerializer.java:212)

org.codehaus.jackson.map.ser.MapSerializer.serialize(MapSerializer.java:23)

org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:600)

org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:280)

org.codehaus.jackson.map.ObjectMapper.writeValue(ObjectMapper.java:1345)

org.springframework.web.servlet.view.json.MappingJacksonJsonView.writeContent(MappingJacksonJsonView.java:292)

org.springframework.web.servlet.view.json.MappingJacksonJsonView.renderMergedOutputModel(MappingJacksonJsonView.java:247)

org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:264)

org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1208)

org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:992)

org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:939)

org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)

org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936)

org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:827)

javax.servlet.http.HttpServlet.service(HttpServlet.java:621)

org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812)

javax.servlet.http.HttpServlet.service(HttpServlet.java:728)

org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)

org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)

org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)

org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)

note The full stack trace of the root cause is available in the Apache Tomcat/7.0.34 logs.


Apache Tomcat/7.0.34


### 해결방법

* 참조페이지 : [Strange Jackson exception being thrown when serializing Hibernate object](http://stackoverflow.com/questions/4362104/strange-jackson-exception-being-thrown-when-serializing-hibernate-object)


### 나름 분석

하이버네이트에 의해 영속화된 객체에 hadler 와 hibernateLazyInitializer 의 필드가 생성되는 것으로 보인다. 이렇게 영속화된 객체를 JSON으로 직렬화 하는 과정에서 hadler 가 lazy로 처리되어 있어서 발생하는 문제가 아닌가 추측해본다.


Hibernate 3.6.0.Final 을 사용하다가 4.2.Final 으로 변경하면서

MultipleBagFetchException: cannot simultaneously fetch multiple bags

이 발생하면서 entityManager가 정상적으로 밀드되지 않는 문제가 생겨남


* 관련 문제 해결 확인 :  MultipleBagFetchException: cannot simultaneously fetch multiple bags is there a workaround


@OneToMany(fetch=FetchType.EAGER)

private List<Department> departments;


형태로 선언된 부분들과 관련된 문제였다.

List 클래스와 관련된 문제가 아닐까 추측이 된다.


* 해결방법

    * 첫번째 방법

    @OneToMany(fetch=FetchType.EAGER)

    @LazyCollection(LazyCollectionOption.FALSE)  



    * 두번째 방법 

    @OneToMany(fetch=FetchType.LAZY)



* 개인적 의견 

    * 두번째 방법이 문제가 없다면, 걍 두번째 방법을 사용

    * @OneToOne의 경우에는 FetchType.EAGER 선언해도 오류없이 동작한다.

+ Recent posts