23 Jan 2018
|
redmine
issuetracking
git
기본적으로 Redmine과 Git을 연동하려면 Git저장소가 Redmine서버와 같은 서버에 있어야 연동이 됩니다.
SVN은 지원해주는거 같은데 Git만 안되는거 같네요.
아래 그림을 보면 로컬 bare 저장소를 지정하라고 나옵니다.
이슈관리때문에 저장소 위치를 바꿀수 없으니 구글링하면 나오는 꼼수를 이용하여 적용하는데, 요약하면 아래와 같습니다.
- 외부 저장소를
--mirror 옵션을 사용하여 Redmine서버에 clone 받음.
- crontab을 이용하여 분 단위로 Redmine서버의 소스를 동기화 시킴.
git remote update
- Redmine에서 Git 저장소를 사용할 수 있도록 설정 변
Redmine 저장소 만들기
Redmine에서 사용할 로컬 bare 저장소를 생성합니다. 원 저장소의 변경사항을 계속 받아와야 하기 때문에 --mirror 옵션을 사용하도록 할 예정인데 crontab을 이용하여 주기적으로 받아와야 하기 때문에 매번 ID/PW를 입력 할 수 없으므로 git credentials 설정을 바꾸도록 합니다.
$ git config --global credential.helper 'store --file ~/.credentials'
$ git clone --mirror https://....../xxxx.git
위와 같이 설정후 clone을 받으면 최초 인증과정을 거치면서 ID/PW를 저장하게 되고 그 다음 clontab 실행시 인증없이 소스 갱신이 가능해집니다.
Crontab 설정
crontab으로 실행할 sh파일을 만들고 주기적으로 실행하도록 설정하겠습니다.
update.sh 작성
#!/bin/bash
cd ~/your-git-src-path
git remote update
crontab -e 명령어를 통해 crontab 편집이 가능합니다.
* * * * * ~/update.sh >> ~/cron.log 2>&1
# crontab script end
crontab이 잘 동작하는지 확인차 로그를 남겼는데 잘 동작한다면 로그 빼주도록 합시다.
Redmine 설정
[프로젝트 설정 > 저장소] 에서 ‘저장소 추가’ 버튼을 클릭합니다.

형상관리시스템으로 Git을 선택하고 Redmine 서버에 만든 bare저장소 경로를 지정해줍니다.

저장버튼을 누르고 ‘저장소’탭으로 이동하면 아래와 같이 내역을 볼 수 있습니다.

참고
git clone 의 두가지 옵션 –bare / –mirror 의 차이점 : http://pinocc.tistory.com/138
git credential 저장소 : https://git-scm.com/book/ko/v2/Git-%EB%8F%84%EA%B5%AC-Credential-%EC%A0%80%EC%9E%A5%EC%86%8C
Redmine - git repository 연결하기 : http://www.whatwant.com/450
09 Nov 2017
|
scss
sass
css
Bootstrap 으로 자주 썼던 class중 하나가 margin/padding 사이즈를 조절해주는 class였습니다.
하지만 정확한 픽셀 단위로 조절 해주는게 아니라서 정확한 사이즈 조절이 힘들었는데 SASS를 이용해서 간단하게 만들어봤습니다.
<div style="margin-bottom:10px;margin-top:10px;"/>
<div class="px-mb-10 px-mt-10"/>
/**
px값 List를 만들어주는 함수입니다.
*/
@function size($start, $end) {
$size : ();
@for $i from $start through $end {
$value : $i + 0;
$size : append($size, $value);
}
@return $size;
}
/**
값이 0일때는 0으로, 그 외에는 'px'을 붙여줍니다.
ex) 0 => 0, 10 => 10px
*/
@function getPx($value) {
@if $value == 0 {
@return $value;
} @else {
@return $value + 0px;
}
}
/**
px 값입니다.
0 ~ 100px 까지 조정하게 만들었습니다.
*/
$size : size(0, 100);
/**
margin과 padding의 각 위치를 지정해주는 map 입니다.
key는 class이름 생성시 쓰이며 value는 상세 속성 정의시 쓰입니다.
*/
$position : ('l':'left', 'r':'right', 't':'top', 'b':'bottom');
/**
margin과 padding 생성을 위한 map입니다.
key는 class이름 생성시 쓰이며 value는 상세 속성 정의시 쓰입니다.
*/
$nameMap : ('px-m':'margin', 'px-p':'padding');
/**
실제 css class를 만들어주는 mixin입니다.
nameMap, position, size를 혼합하여 아래와 같은 형식으로 만들어줍니다.
ex>
px-m-0 : { margin : 0; }
px-mt-1 : { margin-top : 1px; }
*/
@mixin generate($nameMap : (), $position : (), $size : ()) {
@each $preKey, $preValue in $nameMap {
@each $px in $size {
.#{$preKey}-#{$px} {
#{$preValue} : getPx($px);
}
@each $sufKey, $sufValue in $position {
.#{$preKey}#{$sufKey}-#{$px} {
#{$preValue}-#{$sufValue} : getPx($px);
}
}
}
}
}
@include generate($nameMap, $position, $size);
node-sass를 이용하여 scss파일을 css로 빌드해보면 아래와 같이 만들어집니다.
.px-m-0 {
margin: 0; }
.px-ml-0 {
margin-left: 0; }
.px-mr-0 {
margin-right: 0; }
.px-mt-0 {
margin-top: 0; }
.px-mb-0 {
margin-bottom: 0; }
.px-m-1 {
margin: 1px; }
.px-ml-1 {
margin-left: 1px; }
...
.px-pb-99 {
padding-bottom: 99px; }
.px-p-100 {
padding: 100px; }
.px-pl-100 {
padding-left: 100px; }
.px-pr-100 {
padding-right: 100px; }
.px-pt-100 {
padding-top: 100px; }
.px-pb-100 {
padding-bottom: 100px; }
06 Nov 2017
|
jpa
spring
springboot
springdata
JPA를 사용하다보면 쿼리메소드만으로는 감당이 안되는 부분이 많아 @Query를 이용하여 아래와 같이 늘어놓기 시작합니다.
@Query("select p from Post p where p.id > :id")
Post findPostByPk(@Param("id") Long id);
쿼리문이 짧을때는 상관없는데 쿼리문이 길어지고, 또 많아지면 그때부터는 관리가 안되기 시작하는데
가급적 JPA의 장점을 살리면서 Native를 쓰지 않고 버티기 위해 아래와 같이 설정할 수 있습니다.
쿼리문 xml로 빼기 (export query string to orm.xml)
Post라는 Entity를 조회하기 위한 쿼리를 만들어보겠습니다.
일단 여러개의 xml resource를 사용하기 위해 아래와 같이 설정했습니다.
# application.yml
spring.jpa.orm:
path: queries
queries:
- ${spring.jpa.orm.path}/post.xml
- ${spring.jpa.orm.path}/user.xml
...
@Configuration
public class JpaConfig extends HibernateJpaAutoConfiguration {
@Data
@Component
@ConfigurationProperties("spring.jpa.orm")
public class OrmProps {
private String[] queries;
}
@Autowired private OrmProps ormProps;
public JpaConfig(DataSource dataSource, JpaProperties jpaProperties, ObjectProvider<JtaTransactionManager> jtaTransactionManager, ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
super(dataSource, jpaProperties, jtaTransactionManager, transactionManagerCustomizers);
}
@Bean
@Override
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder factoryBuilder)
{
final LocalContainerEntityManagerFactoryBean ret = super.entityManagerFactory(factoryBuilder);
ret.setMappingResources(ormProps.getQueries());
return ret;
}
}
쿼리문만 따로 모으고 싶어서 아래와 같이 배치하였습니다.

@Query어노테이션에 있던 쿼리문은 xml하위에 아래와 같이 정의합니다.
<named-query name="Post.findPostByPk">
<query><![CDATA[ select p from Post p where p.id > :id ]]></query>
</named-query>
그리고 dao 소스에서 @Query어노테이션을 제거해주면 끝.
Post findPostByPk(@Param("id") Long id);
결과를 Map으로 받기
Entity의 전체 결과를 받아올수도 있지만 일부만 필요할 수 도 있습니다.
<named-query name="Post.findPostByPk">
<query><![CDATA[ select p.id, p.message, p.user from Post p where p.id > :id ]]></query>
</named-query>
위 결과를 Post객체로 받을 경우 리턴값이 Object[]이기 때문에 파싱 오류가 발생합니다.
(아래 경우 p.id가 Long타입인데 저걸 Post객체로 변환하려 했기때문에 생기는 오류입니다.)
[11-06 14:44:48.269] ERROR [http-nio-8080-exec-9] [o.a.j.l.DirectJDKLog.log] 181 - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.Object[]] to type [io.github.jistol.geosns.jpa.entry.Post] for value '{65, 1234555666}'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.Long] to type [io.github.jistol.geosns.jpa.entry.Post]] with root cause
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.Long] to type [io.github.jistol.geosns.jpa.entry.Post]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:324)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:206)
at org.springframework.core.convert.support.ArrayToObjectConverter.convert(ArrayToObjectConverter.java:66)
at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:37)
...
named-native-query는 result-class, result-map-class등을 설정 할 수 있지만 JPA를 쓰면서 native쿼리 쓰려면 MyBatis를 쓰는게 더 낫다고 생각하기 때문에 다른 방법으로 해결하겠습니다.
- Object[] 를 원하는 객체로 Application단에서 직접 바꾸기.(설명 생략)
- Map으로 변환하여 결과 받기
Hibernate Select clause문서를 참고하면 HQL에서 어떻게 select한 값을 반환해주는지 잘 설명이 되어있습니다.
(다행히도 JPQL에서도 동일하게 동작하는것 같습니다.)
그 중 Map으로 반환받기 위해서는 아래와 같이 설정 가능합니다.
<named-query name="Post.findPostByPk">
<query><![CDATA[ select new map(p.id, p.message, p.user) from Post p where p.id > :id ]]></query>
</named-query>
그리고 dao 소스에서도 Return객체를 Map으로 바꿔주면 정상동작합니다.
Map<String, Object> findPostByPk(@Param("id") Long id);
참고
Hibernate Select clause
Spring Data JPA - 4.3.3 Using JPA NamedQueries
13 Oct 2017
|
springboot
placeholder
troubleshooting
application.yml 파일을 아래와 같이 만들고 실행했습니다.
app.url: http://localhost:${server.port}/
app.domain1: ${app.url}/domain/1
@Value("${app.domain1}") private String domain1;
public void print() {
System.out.println(domain1);
}
소스에서 위와 같이 참조하면 http://localhost:8080/domain/1로 출력 되기를 기대했으나 ${app.url}/domain/1 로 출력됩니다.
문제는 app.url에 설정한 server.port값을 application.yml 파일에 명시하지 않아 생긴 문제로
SpringBoot의 Placeholder가 파싱할때 참조 값이 없어 app.url값을 파싱하지 못하면서 다른 placeholder 설정들도 모두 일반 text로 인식해버리는 문제입니다.
아래와 같이 명시해주면 정상동작합니다.
server.port: 8080
app.url: http://localhost:${server.port}/
app.domain1: ${app.url}/domain/1
참고
Part IV. Spring Boot features - Placeholders in properties
13 Oct 2017
|
springboot
bootrun
gradle
gradle bootRun을 통해 SpringBoot를 실행하던 도중 아래와 같이 로그가 찍히고 멈춰서 더이상 동작하지 않는 현상이 발생하였습니다.
오전 11:27:32: Executing external task 'bootRun -Dspring.profiles.active=local'...
:compileJava UP-TO-DATE
:processResources
:classes
:findMainClass
Connected to the target VM, address: '127.0.0.1:61333', transport: 'socket'
:bootRun
11:27:33.296 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Included patterns for restart : []
11:27:33.300 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Excluded patterns for restart : [/spring-boot-starter/target/classes/, /spring-boot-autoconfigure/target/classes/, /spring-boot-starter-[\w-]+/, /spring-boot/target/classes/, /spring-boot-actuator/target/classes/, /spring-boot-devtools/target/classes/]
11:27:33.301 [main] DEBUG org.springframework.boot.devtools.restart.ChangeableUrls - Matching URLs for reloading : [file:/Users/jistol/IdeaProjects/github/geo-sns/src/main/resources/, file:/Users/jistol/IdeaProjects/github/geo-sns/build/classes/java/main/, file:/Users/jistol/IdeaProjects/github/geo-sns/build/resources/main/]
기다려도 오류도 안나고 아무런 메시지 없이 멈춰 있어서 디버깅 해본 결과 application.yml 파일을 잘못 설정했을때 위와 같이 멈춰버립니다.
# 예시
base.url : localhost
# ERROR : base.url을 사용하기 위해서는 ${base.url}로 표기해야합니다.
call-url : {base.url}/call
application.yml파일을 파싱하지 못하여 내부적으로 오류가 나지만 따로 찍어주진 않고 SpringBoot를 deploy하지 못한채 끝나버립니다.