728x90
반응형
728x90
반응형
728x90
반응형

이번에 내가 docker을 적용하면서 겪은 내용을 정리해 본다.
먼저 docker가 무엇이고 왜 사용하는지는 구글을 검색해보길 바란다.

ubuntu 서버 세팅은 아래 내 글을 참고하자.

[Oracle Cloud] ubuntu server setting

지난주 금요일 오후에 클라우드 서버에 수정된 내용을 반영하려고 보니 접속이 되지 않았다. 분명 전날까지 됐던것 같은데 안 되는 것이다. 무료 평가판이 종료되었다는 메일은 받았지만, 그래

deonggi.tistory.com


1. docker 설치 및 세팅은 이 글을 참고

Ubuntu 20.04 Docker 설치하기. - 달소씨의 하루

이번에는 Ubuntu 20.04 LTS Server 버전에서 Docker 설치를 한번 진행해보겠습니다. 설치방법은 매번 하던대로라서.. 크게 달라지지않지만 기록용으로 남겨봅니다. 설치하기전에 기본적으로 apt update & ap

blog.dalso.org

이 글을 따라면 docker를 설치할 수 있다.
그리고 해당글의 portainer는 docker의 이미지, 컨테이너, 네트워크 관리에서 유용하므로 설치하는 것을 추천한다.

2. 원하는 tomcat 이미지를 받자.
docker hub에 접속하여 원하는 이미지를 검색한다.
그리고 docker pull 이미지명 으로 이미지를 받는다.
이미지 명의 콜론(:) 버전을 구분하기 위한 태그다. 원하는 특정 버전을 설치한다면 태그를 입력해 줘야 한다.

docker images로 현재 가지고 있는 이미지를 확인할 수 있다.

3. 이미지로 컨테이너 생성 및 실행

docker run -d -it --name con_test -p 8080:8080 tomcat:8.5.78-jre8-temurin-focal
입력된 명령어를 정리하면
1) -d : 백그라운드로 실행, 터미널을 빠져나와도 여전히 컨테이너는 실행됨
2) -it : 컨테이너를 종료하지 않고 계속 터미널에 입력
3) --name : 컨테이너의 이름을 지정 (길어 잘렸는데 위 스샷에서 맨아래 붉은줄에서 오른쪽 끝에 해당 이름이 표시됨)
4) -p : 포트지정, docker host 포트:docker 컨테이너 내부 포트로 바인딩
외부에서 8080포트로 서버에 접근하면 컨테이너 내부의 톰캣 기본 포트인 8080으로 연결해 주겠다는 의미
5) 맨 뒤의 tomcat:8.5.78-jre8-temurin-focal 은 2번에서 받은 이미지로 컨테이너를 생성

4. 톰캣 실행 결과
위 4번에서 두번째 빨간줄 docker ps -a 명령어로 이미 실행중임이 확인되고 있다.
그런데 브라우저로 접속하면 아래와 같다.

404 에러가 나고 있는데 이는 톰캣의 응답으로 정상이다.
call에 응답할 있는 war 파일을 넣어주면 된다
그러려면 설치된 컨테이너에 war 파일을 어디에 위치하고, 어떻게 넣어줘야 할까?

명령어를 docker exec -it con_test bash 라고 입력해준다.
con_test 컨테이너로 접속하여 명령어를 입력하겠다는 의미다.
붉은색 박스를 보면 컨테이너로 접속된 것을 알 수 있다.
컨테이너에 접속된 위치가 tomcat이 설치된 것으로 보여지는데 ls -la로 확인하면 맞는것을 알 수 있다.
그러므로 con_test 컨테이너의 /usr/local/tomcat/webapps 폴더에 ROOT.war 라고 war 파일을 넣어주면 되는 것이다.

exit로 컨테이너에서 나오고, docker 컨테이너에 파일을 복사해 넣는다.
docker cp /opt/HelloWorld.war con_test:/usr/local/tomcat/webapps/ROOT.war
docker host의 /opt 폴더에 HelloWorld.war 파일이 있는데 이걸 con_test 컨테이너의 /usr/local/tomcat/webapps 폴더에 ROOT.war로 파일로 넣어준다는 의미다.
에러 없이 복사 되었는지 확인해 준다. 파일명이 잘못된거나 폴더가 잘못 되었다면 에러가 난다.
(반대로 하면 컨테이너 파일을 docker host로 복사할 수 있다.)

그리고 잠시 후 다시 8080 포트로 접속해 보면 아래와 같이 정상 적용되는 것을 확인할 수 있다.


테스트를 위해 간단히 만든 war 파일로 Hello World라고 표시하고 있다.

5. 톰캣 컨테이너 삭제

docker stop con_test (또는 container id) : 컨테이너를 정지
docker rm con_test (또는 container id) : 컨테이너 삭제

6. docker 이미지 삭제

docker rmi tomcat:8.5.78-jre8-temurin-focal (또는 image id)


728x90
반응형
728x90
반응형

mysql에 데이터를 insert하는 과정에서 발생된 에러다.

java.sql.SQLException: Incorrect string value: '\xF0\x9F\x98\x99 "...' for column 'DSC' at row 1

 

원인은 '😙'와 같은 이모지를 기존에 사용하던 DB에 insert하려 했다는 문제다.

웹에서 검색한 대부분은 답변은 character set을 변경하라는 내용이다.

 

MySQL utf8에서 utf8mb4로 마이그레이션 하기

Emoji같은 글자들은 utf8 인코딩 되는경우 글자당 최대 4bytes까지 필요하다. 하지만 기존 MySQL의 utf8 필드의 경우 글자당 최대 3bytes 까지만 지원하는 한계점이 있었다. 때문에 MySql database에서 utf8mb4

www.letmecompile.com

 

물론 변경하는 것이 가장 베스트의 선택이다. 

그러나 나는 이미 운영중인 서버이기 때문에 함부로 선택할 수 없는 문제였고,

이런 이모지가 꼭 필요한 요소인가를 봤을때 아니라고 생각했다.

그래서 이모지를 제거 또는 변환하는 방법을 사용하기로 했다.

 

 

[Java] Emoji 문자 제거

현재 웹에서 제공하는 이모지 [ win + . ] 또는 모바일 단말기에서 제공하는 특수문자들이 유니코드이지만 DB에 저장 시 지원하지 않는 경우 에러를 유발하게 됩니다. 😊 정확하게는 uft8mb4를 지원

string.tistory.com

 

이렇게 문제가 해결되는 줄 알았다. (분명 로컬에서는 잘 돌았거든..)

그런데 tomcat 서버에서 실행될 때 문제가 발생 했다.

좀 큰 덩어리의 스케줄로 돌아가는 부분이기 때문에 자세한 검토는 하지 못했지만

짐작하기로는 해당 라이브러리가 내 운영서버 환경에서는 적용되지 않는 것이 아닌가 짐작하고 있다. 

아쉽지만 운영서버에서 발생되는 문제의 원인은 아직 확인하지 못했다.

다만, 항상 발생되는 문제가 아니고, 중요한 요소가 아닌지라 이것으로 인해 에러가 발생되면 패스하도록

예외처리를 통해 피하고 있는 중이다.

728x90
반응형
728x90
반응형

jpa repository 메소드 사용시 에러가 나는 경우입니다.

원인은 엔티티에 없는 delYn 파라미터를 findByNameAndDelYn(String name) 와 같이 사용하려는 경우 발생합니다.

 

아래는 에러 전문

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.

20220228 11:16:35.550 [main] ERROR o.s.b.SpringApplication - Application run failed

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bbsService': Unsatisfied dependency expressed through field 'repositoryClass'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'bbsRepository' defined in packagename.core.model.repository.URepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Failed to create query for method public abstract java.util.List packagename.core.model.repository.Bbs.findByUrlLikeAndDelYn(java.lang.String)! Method public abstract java.util.List packagename.core.model.repository.Bbs.findByUrlLikeAndDelYn(java.lang.String) expects at least 2 arguments but only found 1. This leaves an operator of type SIMPLE_PROPERTY for property delYn unbound.

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:660)

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)

at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1413)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)

at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)

at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:917)

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

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

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

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

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

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

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

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

at packagename.BookKeepingApplication.main(BookKeepingApplication.java:12)

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'bbsRepository' defined in packagename.core.model.repository.URepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Failed to create query for method public abstract java.util.List packagename.core.model.repository.Bbs.findByUrlLikeAndDelYn(java.lang.String)! Method public abstract java.util.List packagename.core.model.repository.Bbs.findByUrlLikeAndDelYn(java.lang.String) expects at least 2 arguments but only found 1. This leaves an operator of type SIMPLE_PROPERTY for property delYn unbound.

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1786)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)

at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)

at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:657)

... 21 common frames omitted

Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract java.util.List packagename.core.model.repository.Bbs.findByUrlLikeAndDelYn(java.lang.String)! Method public abstract java.util.List packagename.core.model.repository.Bbs.findByUrlLikeAndDelYn(java.lang.String) expects at least 2 arguments but only found 1. This leaves an operator of type SIMPLE_PROPERTY for property delYn unbound.

at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.(PartTreeJpaQuery.java:96)

at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:107)

at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:218)

at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:81)

at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:100)

at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lambda$mapMethodsToQuery$1(QueryExecutorMethodInterceptor.java:93)

at java.util.stream.ReferencePipeline$3$1.accept(Unknown Source)

at java.util.Iterator.forEachRemaining(Unknown Source)

at java.util.Collections$UnmodifiableCollection$1.forEachRemaining(Unknown Source)

at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Unknown Source)

at java.util.stream.AbstractPipeline.copyInto(Unknown Source)

at java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)

at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(Unknown Source)

at java.util.stream.AbstractPipeline.evaluate(Unknown Source)

at java.util.stream.ReferencePipeline.collect(Unknown Source)

at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.mapMethodsToQuery(QueryExecutorMethodInterceptor.java:95)

at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lambda$new$0(QueryExecutorMethodInterceptor.java:85)

at java.util.Optional.map(Unknown Source)

at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.(QueryExecutorMethodInterceptor.java:85)

at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:303)

at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:323)

at org.springframework.data.util.Lazy.getNullable(Lazy.java:230)

at org.springframework.data.util.Lazy.get(Lazy.java:114)

at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:329)

at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:144)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1845)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1782)

... 31 common frames omitted

Caused by: java.lang.IllegalStateException: Method public abstract java.util.List packagename.core.model.repository.Bbs.findByUrlLikeAndDelYn(java.lang.String) expects at least 2 arguments but only found 1. This leaves an operator of type SIMPLE_PROPERTY for property delYn unbound.

at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.throwExceptionOnArgumentMismatch(PartTreeJpaQuery.java:161)

at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.validate(PartTreeJpaQuery.java:147)

at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.(PartTreeJpaQuery.java:90)

... 57 common frames omitted

 

 

 

728x90
반응형
728x90
반응형

예전에 써봤지만 기억나지 않아 살짝 삽질한...

내가 기억하기 위해 남기는 메모.

.....

1. java코드 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* #### ReqDTO.java (DTO class) ####*/
public class ReqDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer dmnSeq;
    private String expt;
    private String prscYn;
    private String delYn;
}
 
/* #### Service.java #### */
List<ReqDTO> insertList = new ArrayList<>();
for () {
    // insert할 DTO를 작성함
}
// mapper 호출
Integer cnt = mapperClass.insertTable(insertList);
 
/* #### Mapper.java (interface) #### */
public Integer insertTable(List<ReqDTO> reqDTO) throws Exception;
 
cs

 

2. mybatis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<insert id="insertTable" parameterType="com.packagename.dto.ReqDTO">
    INSERT INTO TABLE_NAME
        (
        DMN_SEQ
        , EXPT
        , PRSC_YN
        , DEL_YN
        )
    VALUES
        <foreach collection="list" item="item" index="index" open="" separator=", " close="">
           (
           #{item.dmnSeq}
           , #{item.expt}
           , #{item.prscYn}
           , #{item.delYn}
           )
        </foreach>
</insert>
cs

 

728x90
반응형
728x90
반응형

ArrayList.remove()를 통해 List 특정항목을 제거해야 했다.

 

작성한 코드는 아래와 같았다.

1
2
3
4
5
6
7
8
9
10
11
List<Integer> delIndexList = new ArrayList<>();
/** 조건에 의해 삭제할 index를 delIndexList에 추가 **/
 
if (delIndexList != null && delIndexList.size() > 0) {
    // List에서 remove시 남은 요소들의 index가 변경되므로 2개 이상의 삭제시 문제 방지를 위해 
    // 삭제할 index를 가진 delIndexList를 역정렬하여 진행
    delIndexList = delIndexList.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
    for (Integer index : delIndexList) {
        list.remove(index);
    }
}
cs

 

그러나 이상하게도 9번 라인에서 삭제가 되지 않는 것이다.

임의로 1을 넣어 진행해보니 문제가 없었다.

즉, ArrayList.remove(index)에서 index는 int 형으로 해야한다.

 

아쉽게도 integer를 사용해도 별 다른 에러는 나오지 않고 넘어가고 삭제되지 않아 처음에는 쉽게 원인을 인지하기 어렵다.

 

 

 

* 관련글

[java] ArrayList를 다룰때 흔한 실수 : https://deonggi.tistory.com/65

 

728x90
반응형
728x90
반응형

에디터에서 흔히 많이 사용되는 input 영역에 2개 이상의 태그를 작성하기 위한 기능이 필요했다.

jQuery에서 사용할 수 있는 plugin을 찾아보니 Suggestags 라는 것이 있어 사용하기로 했다.

 

* plugin file download : https://github.com/amsify42/jquery.amsify.suggestags

* 환경 : jQuery v3.5.1, Bootstrap v4.5.3 사용, Suggestags 1.27.0 적용

 

1. plugin 적용

1
2
<link rel="stylesheet" href="/css/amsify.suggestags.css">
<script src="/js/jquery.amsify.suggestags.js"></script>
cs

 

2. 적용할 태그

1
<input type="text" class="form-control" id="tagsTxt">
cs

 

3. 내 소스에 적용

3-1. 기본 가이드

plugin  가이드 : https://openbase.com/js/suggestags/documentation

javascript 객체 상수 (constant) : https://webclub.tistory.com/527

기본적인 사용방법은 해당 가이드를 사용하면 된다.

3-2. 내가 필요했던 기능

1) javascript로 태그를 전부 다른 값으로 변경해야 한다.

2) javascript로 태그를 하나 추가하고 싶다.

3) 태그가 변경될 시 callback 이벤트를 사용하고 싶다.

3-3. 기존 소스의 문제점

다만 3-2의 내용들을 사용하기 위해서 refresh를 사용했는데

이 경우 기존에 선언시 설정했던 정보가 모두 날아가는 문제가 있었다.

 

3-4. 그래서 아래와 같은 함수를 만들어 적용했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// plugin 사용을 위한 값 세팅
definedValues();
// plugin 적용
usedSuggesTagsPlugin($('#tagsTxt'), 'init');
 
// 태그 전체 변경 변경시 
// tagsList : 'tag1,tag2' 형태의 스트링
usedSuggesTagsPlugin($('#tagsTxt'), 'setTags', {tags:tagsList});
 
// 태그 하나 추가 시
usedSuggesTagsPlugin($('#tagsTxt'), 'addTag', {tags:tag});
 
// javascript 객체 상수 (constant), 출처 : https://webclub.tistory.com/527
function definedValues(){
    constant.set('suggestagsSetting', {
        tagLimit : 10,                                      // 태그 최대 갯수 제한
        tags : '',                                          // 입력되는 태그 값
    });
}
 
/**
 * Suggestags의 사용을 위한 보조 함수
 * 출처 : https://openbase.com/js/suggestags/documentation
 * @param selector  : jquery형 selector
 * @param action    : 어떤 형태의 작업인지
 * => init : 사용을 위한 선언
 * => setTags : js로 태그 전체 변경시
 * => addTag : js로 태가 하나 추가시
 * => getClass : plugin에서 사용되는 클레스 값 전체를 구할때
 * @param value     : object형, action이 getClass인 경우 사용함
 * @returns
 */
function usedSuggesTagsPlugin(selector, action, value){
    if (isNull(selector))
        throw ': we need selector info for Suggestags plugin!';
    
    var objValue = !isNull(value) ?
            Object.assign(constant.get('suggestagsSetting'), value) 
            : constant.get('suggestagsSetting');
    
    // 선언시 세팅값 정의
    var objInit = {
            // 태그 반경시 callback 이벤트 설정
            afterAdd : function(e) {
                checkTagValuse();
            },
            afterRemove : function(e) {
                checkTagValuse();
            },
    };    
    if (!isNull(objValue) && !isNull(objValue.tagLimit) && Number(objValue.tagLimit) > 0) {
        Object.assign(objInit, {
            tagLimit: objValue.tagLimit
        });
    }
    
    // plugin 적용
    if (isNull(action) || action == 'init') {
        init();
        return;
    }
    
    if (!isNull(action) && (action == 'setTags' || action == 'addTag'
            && !isNull(objValue) && !isNull(objValue.tags)) {
        // refresh 사용시 tagLimit와 같은 기존 세팅 값이 초기화 되므로 재생성하도록 처리
        init();
        if (action == 'setTags') {
            var arrValue = objValue.tags.split(',');
            $.each(arrValue, function(i, e){
                amsifySuggestags.addTag(e.trim());
            });
            return;
        } else if (action == 'addTag') {
            amsifySuggestags.addTag(objValue.tags.trim());
            return;
        }
    } else if (!isNull(action) && action == 'setTags' 
        && !isNull(objValue) && isNull(objValue.tags)) {
        init();
        return;
    }
    
    /**
     * 사용예
     * 1. 아직 반영되지 않은 입력중인 태그 값을 구할때
     *  : $('input[class="'+usedSuggesTagsPlugin(selector, 'getClass').sTagsInput.substring(1)+'"]').val()
     */ 
    if(!isNull(action) && action == 'getClass') {
        amsifySuggestags = new AmsifySuggestags(selector);
        return amsifySuggestags.classes;
    }
    
    
    /* 내부함수 - 시작 */
    function init(){
        if (action != 'addTag') selector.val('');
        amsifySuggestags = new AmsifySuggestags(selector);
        amsifySuggestags.destroy();
        amsifySuggestags = new AmsifySuggestags(selector);
        amsifySuggestags._settings(objInit);
        amsifySuggestags._init();
    }
    
    // 태그 변경 시 이벤트 
    function checkTagValuse(){    
        // 입력된 태그 값을 비교하여 같거다 다른 경우 분기
    }
    /* 내부함수 - 끝 */
}
 
/**
 * param이 null, undefined, 공백 포함 빈 스트링인 경우 true, 값이 있는 경우 false
 * @param param
 */
function isNull(param){
    if(typeof param == "undefined" || param == null || param.length == 0
            || (typeof param == 'string' && (param == "" || param.trim().length == 0)))
        return true;
    else
        return false ;
}
cs

 

 

 

분명히 더 아름다운 방법이 있겠지만 우선 이렇게 마무리 했다.

(어후 삽질이야...)

 

728x90
반응형
728x90
반응형

모든 테이블에 session_id라는 컬럼(not null)을 추가하고, jpa entity에서 @PrePersist를 이용해 insert시 자동 작성하도록 설정 했다.

그리고 modelmapper에서 null인 값은 맵핑하지 않을꺼야 라고 생각하고 수정 시는 테스트 하지 않았다.

근데 왠걸... 내 소스는 맵핑 설정에 그런건 없다.

그래서 ...... 이렇게 삽질이 시작됐다.

 

내가 생각한 순서는 이러했다.

 

1. 맵핑에서 sessionId 파라미터는 맵핑하지 않는다.

insert시에는 @PrePersist에서 처리하니 문제가 없을 것이고, 수정 시에는 find한 값에 modelmapper로 맵핑하니 Bean에 설정을 추가하면 된다고 생각했다.

소스는 아래와 같다.

1
2
3
4
5
// GrpCodeReqDTO -> GroupCode 맵핑 시에만 사용됨
TypeMap<GrpCodeReqDTO, GroupCode> typeMap = modelMapper.createTypeMap(GrpCodeReqDTO.class, GroupCode.class);
typeMap.addMappings(mapping -> {
   mapping.skip(GroupCode::setSessionId);
});
cs

근데 이 소스의 문제점은 GrpCodeReqDTO -> GroupCode 맵핑 시에만 사용된다는 것이다.

공통으로 상속받은 BaseDTO(dto 공통상속), Base(entity 공통상속)를 사용하고 싶었지만 안 된다.

단순하게 간단하게 하고 싶은 나의 욕심과 달리 하나하나 클래스 별로해야 한다는 거다.

 

2. 차선책으로 값이 null인 경우 맵핑에서 스킵한다.

1
2
// 값이 널인 경우 스킵
modelMapper.getConfiguration().setSkipNullEnabled(true);
cs

1번이 안 되니 2번으로 만족해야 한다고 생각하고 수정을 마무리 하려고 했다.

 

 

그러다 번득든 생각!  jpa save에 대한 예외처리 방법이 있지 않을까?

3. jpa entity에서 updatable=false 추가

1
2
3
@Column(name = "SESSION_ID", nullable = false, updatable=false)
@ColumnDefault("NONE")
private String sessionId;
cs

insert를 위한 save시 제외를 원한다면 insertable=false,

update를 위한 save시 제외를 원한다면 updatable=false를 넣어주면 된다.

내 경우는 update시에만 해당되므로 updatable=false를 추가해서 해결했다.

 

 

 

이렇게 삽질을 마무리 했다.

그런데 ......

여기서 반전은 생성일, 생성자 컬럼에서 이미 아래와 같이 사용하고 있었다.

1
2
3
4
5
@Column(name = "CRT_DT", updatable = false)
protected Date crtDt;
 
@Column(name = "CRT_BY", updatable = false)
protected Integer crtBy;
cs

역시 모르고 막 사용하면 이런 삽질을 하는 거다!! ㅠ_ㅠ

 

 

 

* 참고한 글

 

ModelMapper 사용하기 – Deliwind

이번에는 java에서 내가 애용하는 modelMapper에 관해 글을 써보고자 한다. 엄청 유용한 라이브러리임에도 불구하고, 그래서 java 사용자는 모두들 사용하고 있을거라고 생각했는데, 생각보다 주위에

blog.deliwind.com

 

 

[JPA] 특정 칼럼을 제외하고 INSERT, UPDATE하는 방법

보통 JPA는 SAVE시에 모든 칼럼을 INSERT한다. 그럴 경우, NOT NULL로 설정된 칼럼은 기본값으로 삽입되는것이 아닌 NULL로 삽입을 시도한다. 이로 인해 에러가 발생하는데, 이럴 경우에 아예 쿼리에서

jobc.tistory.com

 

 

 

728x90
반응형
728x90
반응형

나는 개인 싱글서버로 웹서버를 개발중이다.

그런데 어제 스케줄 실행에서 로그를 기록하는 것을 서버에 적용한 후 스케줄이 중복 실행되는 걸 발견했다.

큰 이상을 유발할 수 있는 상황은 아니었으나 그래도 깔끔하지 못하니 수정하고 싶었다.

 

검색을 해보니 Tomcat, spring 설정의 문제를 확인해 보라는 내용이 있었지만, 내 경우는 적용할 수 가 없었다.

(이건 내 지식이 얕기 때문에 이해를 제대로 하지 못해 적용하지 못했을 것이다.)

 

추가로 검색을 해보니 멀티 서버의 경우에서 스케줄 중복을 방지하는 방법을 발견했다.

넓게 본다면 비슷한 의도로 중복실행 방지를 가능하게 처리하는 방법이 될 것으로 생각하고 적용 했으며, 잘 반영 되었다.

 

* 참고한 좋은글

 

ShedLock 사용하기

www.baeldung.com/shedlock-spring 같은 잡을 수행하는 각각 다른 서버에서의 인스턴스 A,B가 있을 때, A,B 둘 중 하나가 수행하도록, 2개 이상의 서버에서 중복 수행을 방지하도록 Lock 을 걸게 하는 라이브

eunbc-2020.tistory.com

 

 

* 내 서버 환경 : spring boot 2.4.3, 스케줄 적용 중

 

자세한건 위 글을 참고하시고, 내 기준에서 추가로 보강한 내용만 적는다.

 

1. 테이블 생성 (mysql)

1
2
3
4
5
6
7
8
CREATE TABLE IF NOT EXISTS `bookkeeping`.`SHEDLOCK` (
  `NAME` VARCHAR(64NOT NULL COMMENT '스케줄잠금이름',
  `LOCK_UNTIL` TIMESTAMP(3NULL COMMENT '잠금기간',
  `LOCKED_AT` TIMESTAMP(3NULL COMMENT '잠금일시',
  `LOCKED_BY` VARCHAR(255NULL COMMENT '잠금신청자',
  PRIMARY KEY (`NAME`))
ENGINE = InnoDB
COMMENT = '스케줄 잠금'
cs

 

2. Bean 주입

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
 
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
 
@Configuration
public class SchedulerConfig {
 
    @Bean
    public LockProvider lockProvider(JdbcTemplate dataSource) {
        return new JdbcTemplateLockProvider(dataSource);
    }
}
cs

 

이상.

오늘도 잘 해결해서 보람차다. ㅠㅠ 

 

 

3. 21.11.24 추가 이슈.

스케줄 실행시 언제나 에러가 난다는 것이다.

스케줄의 name 값으로 select를 먼저하고, 확인해서 insert, update로 분기해서 진행해야 하는데 무조건 insert 후 에러가 나면 update를 하는거다.(어쨌든 에러가 나도 실행은 잘 되긴 한다.)

하지만 이 excption이 눈에 거슬려서 고쳐보려 했지만 해결 방법을 아직 찾지는 못했다.

* 에러 전문

1. INSERT INTO shedlock(name, lock_until, locked_at, locked_by) VALUES('job_10min', '11/22/2021 12:28:00.003', '11/22/2021 12:20:00.050', 'DESKTOP-VMFC4B1')

{FAILED after 16 msec}

java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'job_10min' for key 'PRIMARY'

at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117)

at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)

at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953)

at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1092)

at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1040)

at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1347)

at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1025)

at net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy.executeUpdate(PreparedStatementSpy.java:1080)

at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)

at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java)

at org.springframework.jdbc.core.JdbcTemplate.lambda$update$2(JdbcTemplate.java:965)

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

at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:960)

at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:981)

at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.update(NamedParameterJdbcTemplate.java:328)

at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.update(NamedParameterJdbcTemplate.java:333)

at net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateStorageAccessor.lambda$insertRecord$0(JdbcTemplateStorageAccessor.java:65)

at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140)

at net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateStorageAccessor.insertRecord(JdbcTemplateStorageAccessor.java:63)

at net.javacrumbs.shedlock.support.StorageBasedLockProvider.doLock(StorageBasedLockProvider.java:81)

at net.javacrumbs.shedlock.support.StorageBasedLockProvider.lock(StorageBasedLockProvider.java:65)

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

at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

at java.lang.reflect.Method.invoke(Unknown Source)

at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)

at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)

at com.sun.proxy.$Proxy61.lock(Unknown Source)

at net.javacrumbs.shedlock.core.DefaultLockingTaskExecutor.executeWithLock(DefaultLockingTaskExecutor.java:63)

at net.javacrumbs.shedlock.spring.aop.MethodProxyScheduledLockAdvisor$LockingInterceptor.invoke(MethodProxyScheduledLockAdvisor.java:85)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)

at packageName.core.schedule.cron.ScheduleCron$$EnhancerBySpringCGLIB$$5253d941.job_10min()

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

at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

at java.lang.reflect.Method.invoke(Unknown Source)

at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84)

at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)

at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:95)

at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)

at java.util.concurrent.FutureTask.run(Unknown Source)

at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(Unknown Source)

at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)

at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)

at java.lang.Thread.run(Unknown Source)

 

 

 

728x90
반응형

+ Recent posts