맨날 까먹는 JPA의 Repository 조회 메소드 규칙은 다음과 같다. 



https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods


Table 3. Supported keywords inside method names
KeywordSampleJPQL snippet

And

findByLastnameAndFirstname

… where x.lastname = ?1 and x.firstname = ?2

Or

findByLastnameOrFirstname

… where x.lastname = ?1 or x.firstname = ?2

IsEquals

findByFirstname,findByFirstnameIs,findByFirstnameEquals

… where x.firstname = ?1

Between

findByStartDateBetween

… where x.startDate between ?1 and ?2

LessThan

findByAgeLessThan

… where x.age < ?1

LessThanEqual

findByAgeLessThanEqual

… where x.age <= ?1

GreaterThan

findByAgeGreaterThan

… where x.age > ?1

GreaterThanEqual

findByAgeGreaterThanEqual

… where x.age >= ?1

After

findByStartDateAfter

… where x.startDate > ?1

Before

findByStartDateBefore

… where x.startDate < ?1

IsNullNull

findByAge(Is)Null

… where x.age is null

IsNotNullNotNull

findByAge(Is)NotNull

… where x.age not null

Like

findByFirstnameLike

… where x.firstname like ?1

NotLike

findByFirstnameNotLike

… where x.firstname not like ?1

StartingWith

findByFirstnameStartingWith

… where x.firstname like ?1 (parameter bound with appended %)

EndingWith

findByFirstnameEndingWith

… where x.firstname like ?1 (parameter bound with prepended %)

Containing

findByFirstnameContaining

… where x.firstname like ?1 (parameter bound wrapped in %)

OrderBy

findByAgeOrderByLastnameDesc

… where x.age = ?1 order by x.lastname desc

Not

findByLastnameNot

… where x.lastname <> ?1

In

findByAgeIn(Collection<Age> ages)

… where x.age in ?1

NotIn

findByAgeNotIn(Collection<Age> ages)

… where x.age not in ?1

True

findByActiveTrue()

… where x.active = true

False

findByActiveFalse()

… where x.active = false

IgnoreCase

findByFirstnameIgnoreCase

… where UPPER(x.firstame) = UPPER(?1)


Posted by '김용환'
,


jackson 2.9 버전부터  com.fasterxml.jackson.databind.util.ISO8601DateFormat 클래스는 deprecated되었다.


아래와 같은 코드는 더 이상 사용하지 않고.


 ObjectMapper mapper = new ObjectMapper();

objectMapper.setDateFormat(new ISO8601DateFormat());


StDateFormat이나, Joda 를 사용하는 것이 좋은 것 같다. 


 ObjectMapper mapper = new ObjectMapper();

 mapper.setDateFormat(new StdDateFormat());




개인적으로  Joda로 변경하니 괜찮았다.

Posted by '김용환'
,


보통 소켓을 다루는 간단한 자바 애플리케이션 예시의 경우, socket을 close하지 않아도 자연스럽게 정리된다.


공식 RabbitMQ 자바 Client을 사용할 때 

publish 코드에서 사용하는 connection을 종료하지 않으면 계속 hang된다.



val connection = connectionFactory.newConnection
val channel = connection.createChannel
channel.exchangeDeclare(exchange, builtinExchangeType, false)
messages.foreach { message =>
channel.basicPublish(exchange, "", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8"))
}


jstack을 통해  비동기 쓰레드 폴링을 확인할 수 있다.

"Process Proxy: RabbitPublisher" #473 prio=6 os_prio=31 tid=0x00007f854bd53000 nid=0x1572b runnable [0x000070000cde6000]

   java.lang.Thread.State: RUNNABLE

at sun.nio.ch.KQueue.keventPoll(Native Method)

at sun.nio.ch.KQueuePort$EventHandlerTask.poll(KQueuePort.java:196)

at sun.nio.ch.KQueuePort$EventHandlerTask.run(KQueuePort.java:276)

at sun.nio.ch.AsynchronousChannelGroupImpl$1.run(AsynchronousChannelGroupImpl.java:112)

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)

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



항상 자원을 close를 처리할 필요가 있다.

val connection = connectionFactory.newConnection
val channel = connection.createChannel
channel.exchangeDeclare(exchange, builtinExchangeType, false)
messages.foreach { message =>
channel.basicPublish(exchange, "", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8"))
}
channel.close()
connection.close()


Posted by '김용환'
,


spring cloud config 사용시 사용자 이름, 패스워드를 입력한다


"java -jar config-server.jar --spring.cloud.config.server.git.username=knight76 --spring.cloud.config.server.git.password=1234"


아니면 프로퍼티(property) 설정을 추가한다.


spring.cloud.config.server.git.username=knight76

spring.cloud.config.server.git.password=1234




패스워드가 나타나는 게 꺼림직하다.

jasypt(com.github.ulisesbocchio:jasypt-spring-boot-starter) 라이브러리를 사용하면 암호화를 할수 있다.


gradle 설정에  jasypt(com.github.ulisesbocchio:jasypt-spring-boot-starter) 라이브러리를  추가한다.

dependencies {
implementation 'org.springframework.cloud:spring-cloud-config-server'
implementation 'org.springframework.cloud:spring-cloud-starter-config'
compile 'com.github.ulisesbocchio:jasypt-spring-boot-starter:2.1.2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}


application.properties에 


server.port=8080
jasypt.encryptor.bean=jasyptStringEncryptor

management.endpoint.env.enabled=true
management.endpoints.web.exposure.include=*

spring.cloud.config.server.bootstrap=true
spring.cloud.config.server.git.uri=https://github.com/knight76/spring-cloud-config-example
spring.cloud.config.server.git.timeout=5
spring.cloud.config.server.git.username=knight76
spring.cloud.config.server.git.password=123214



암호화에 사용되는 JasyptConfig 클래스를 추가한다. 


package com.github.knight76.config.configserver;

import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JasyptConfig {

final static String KEY = "knight76";

final static String ALGORITHM = "PBEWithMD5AndDES";

@Bean("jasyptStringEncryptor")
public StringEncryptor stringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(KEY);
config.setAlgorithm(ALGORITHM);
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
return encryptor;
}

}


 테스트 코드는 다음과 같다. 테스트 코드의 비밀번호를 사용한다. 

package com.github.knight76.config.configserver;

import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ConfigServerApplicationTests {

@Test
public void test() {
StandardPBEStringEncryptor standardPBEStringEncryptor = new StandardPBEStringEncryptor();
standardPBEStringEncryptor.setAlgorithm(JasyptConfig.ALGORITHM);
standardPBEStringEncryptor.setPassword(JasyptConfig.KEY);

String enc = standardPBEStringEncryptor.encrypt("비밀번호");
System.out.println("enc = " + enc);

String des = standardPBEStringEncryptor.decrypt(enc);
System.out.println("des = " + des);
}
}



테스트 코드를 통해 얻은 비밀번호를 application.properties의 비밀번호로 설정한다. ENC(..비밀번호..)로 적용한다.


server.port=8080
jasypt.encryptor.bean=jasyptStringEncryptor

management.endpoint.env.enabled=true
management.endpoints.web.exposure.include=*

spring.cloud.config.server.bootstrap=true
spring.cloud.config.server.git.uri=https://github.com/knight76/spring-cloud-config-example
spring.cloud.config.server.git.timeout=5
spring.cloud.config.server.git.username=knight76
spring.cloud.config.server.git.password=ENC(암호문)


풀 예제 코드는 다음과 같다.

https://github.com/knight76/spring-cloud-config-example



이 방식 외에 RSA 비밀 키를 사용하는 방식이 있다. 


https://cloud.spring.io/spring-cloud-config/reference/html/


spring: cloud: config: server: git: uri: git@gitserver.com:team/repo1.git ignoreLocalSshSettings: true hostKey: someHostKey hostKeyAlgorithm: ssh-rsa privateKey: | -----BEGIN RSA PRIVATE KEY----- MIIEpgIBAAKCAQEAx4UbaDzY5xjW6hc9jwN0mX33XpTDVW9WqHp5AKaRbtAC3DqX IXFMPgw3K45jxRb93f8tv9vL3rD9CUG1Gv4FM+o7ds7FRES5RTjv2RT/JVNJCoqF ol8+ngLqRZCyBtQN7zYByWMRirPGoDUqdPYrj2yq+ObBBNhg5N+hOwKjjpzdj2Ud 1l7R+wxIqmJo1IYyy16xS8WsjyQuyC0lL456qkd5BDZ0Ag8j2X9H9D5220Ln7s9i oezTipXipS7p7Jekf3Ywx6abJwOmB0rX79dV4qiNcGgzATnG1PkXxqt76VhcGa0W DDVHEEYGbSQ6hIGSh0I7BQun0aLRZojfE3gqHQIDAQABAoIBAQCZmGrk8BK6tXCd fY6yTiKxFzwb38IQP0ojIUWNrq0+9Xt+NsypviLHkXfXXCKKU4zUHeIGVRq5MN9b BO56/RrcQHHOoJdUWuOV2qMqJvPUtC0CpGkD+valhfD75MxoXU7s3FK7yjxy3rsG EmfA6tHV8/4a5umo5TqSd2YTm5B19AhRqiuUVI1wTB41DjULUGiMYrnYrhzQlVvj 5MjnKTlYu3V8PoYDfv1GmxPPh6vlpafXEeEYN8VB97e5x3DGHjZ5UrurAmTLTdO8 +AahyoKsIY612TkkQthJlt7FJAwnCGMgY6podzzvzICLFmmTXYiZ/28I4BX/mOSe pZVnfRixAoGBAO6Uiwt40/PKs53mCEWngslSCsh9oGAaLTf/XdvMns5VmuyyAyKG ti8Ol5wqBMi4GIUzjbgUvSUt+IowIrG3f5tN85wpjQ1UGVcpTnl5Qo9xaS1PFScQ xrtWZ9eNj2TsIAMp/svJsyGG3OibxfnuAIpSXNQiJPwRlW3irzpGgVx/AoGBANYW dnhshUcEHMJi3aXwR12OTDnaLoanVGLwLnkqLSYUZA7ZegpKq90UAuBdcEfgdpyi PhKpeaeIiAaNnFo8m9aoTKr+7I6/uMTlwrVnfrsVTZv3orxjwQV20YIBCVRKD1uX VhE0ozPZxwwKSPAFocpyWpGHGreGF1AIYBE9UBtjAoGBAI8bfPgJpyFyMiGBjO6z FwlJc/xlFqDusrcHL7abW5qq0L4v3R+FrJw3ZYufzLTVcKfdj6GelwJJO+8wBm+R gTKYJItEhT48duLIfTDyIpHGVm9+I1MGhh5zKuCqIhxIYr9jHloBB7kRm0rPvYY4 VAykcNgyDvtAVODP+4m6JvhjAoGBALbtTqErKN47V0+JJpapLnF0KxGrqeGIjIRV cYA6V4WYGr7NeIfesecfOC356PyhgPfpcVyEztwlvwTKb3RzIT1TZN8fH4YBr6Ee KTbTjefRFhVUjQqnucAvfGi29f+9oE3Ei9f7wA+H35ocF6JvTYUsHNMIO/3gZ38N CPjyCMa9AoGBAMhsITNe3QcbsXAbdUR00dDsIFVROzyFJ2m40i4KCRM35bC/BIBs q0TY3we+ERB40U8Z2BvU61QuwaunJ2+uGadHo58VSVdggqAo0BSkH58innKKt96J 69pcVH/4rmLbXdcmNYGm6iu+MlPQk4BUZknHSmVHIFdJ0EPupVaQ8RHT 

-----END RSA PRIVATE KEY-----


Posted by '김용환'
,



springboot2-thymeleaf 사용 예제를 다룬 코드를 참고한다.


https://www.mkyong.com/spring-boot/spring-boot-hello-world-example-thymeleaf/



실수하기 좋은 부분은 다음과 같다. 


1. html 파일에 다음을 추가해야 한다.

<html lang="ko" xmlns:th="http://www.thymeleaf.org">


2. th를 잘 사용한다.

velocity에서 아래와 같이 사용했다면..

  <span class="item">${link.itemName}</span>


아래와 같이 th:text를 사용한다.

<span class="item" th:text="${link.itemName}"></span>



3, 

velocity에서 아래와 같이 사용했다면..


    <span class="image" style="background-image:url(${link.thumbnail})"></span>


아래와 같이 th:style과 콜럼을 잘 사용해야 한다. 실수하기 가장 쉬운 곳이 아래 빨간색 색칠한 부분이다.

<span class="image" th:style="'background-image:url(' + ${link. thumbnail} + ');'"></span>


Posted by '김용환'
,


jenkins에서 아래와 같은 Exception이 발생했다.


sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

PKIX path building failed 




원인은 http 사이트에서 https 로 접속하려 할 때 자바에서 발생하는 에러이다. 이를 간단히 해결하려면


젠킨스 관리 -> Configure Global Security -> Plugin Manager -> Use browser for metadata download 를 체크 on한다.


앤서블을 사용해 자동화하는 경우라면 다음 코드를 실행한다. https가 아닌 http로 변경하는 것이다.


- name: Change Update-Center protocol - "{{ jenkins_home }}/hudson.model.UpdateCenter.xml"

  replace:

    path: "{{ jenkins_home }}/hudson.model.UpdateCenter.xml"

    regexp: 'https://updates.jenkins.io/update-center.json'

    replace: 'http://updates.jenkins.io/update-center.json'


Posted by '김용환'
,

jenkins 장비의 플러그인 목록을 형상관리 안하는 경우가 많다.. 플러그인 설정이 있어도 플러그인이 없으면 복구 할 수 없다. 


jenkins 장비의 플러그인 목록을 보려면 http://장비명:8080/script에 다음 커맨드를 실행한다.


Jenkins.instance.pluginManager.plugins.each{

  plugin -> 

    println ("${plugin.getShortName()}: ${plugin.getVersion()}")

}


결과는 다음과 같다.


Git plugin (git): 3.9.1
JavaScript GUI Lib: ACE Editor bundle plugin (ace-editor): 1.1
SCM Sync Configuration Plugin (scm-sync-configuration): 0.0.10
Pipeline: Job (workflow-job): 2.24

..


jenkins master를 제대로 복구할 때, 

플러그인 이름과 버전, jenkins 버전.. 이 매우 중요하다.

Posted by '김용환'
,

gradle에서 proxy 설정을 다음과 같이 진행하니 제대로 proxy

 

$ ./gradlew -Dhttp.proxyHost=proxy.igoogle.com -Dhttp.proxyPort=8082 - Dhttps.proxyHost=proxy.igoogle.com -Dhttps.proxyPort=8082 clean build

 

실행 안된다.

 

애플리케이션의 gradle.properties로 변경해야 동작한다.

 

 

systemProp.http.proxyHost=...
systemProp.http.proxyPort=..
systemProp.http.nonProxyHosts=...
systemProp.https.proxyHost=...
systemProp.https.proxyPort=..
systemProp.https.nonProxyHosts=....

Posted by '김용환'
,

spring boot2에 jpa에서 native query 를 사용하다가.

 

Validation failed for query for method... 에러를 만났다.

 

@Modifying

@Query("update user u set u.vin = ?2 where u.username = ?1")

void update(String username, String vin);

 

다음과 같이 nativeQuery인지를 알려줘야 더 이상 에러가 발생하지 않는다.

 

@Modifying
@Query(value="update user u set u.vin = ?2 where u.username = ?1", nativeQuery = true)
void update(String username, String vin);

Posted by '김용환'
,

 

spring boot 2 책을 보면 WebMvcTest DataJpaTest, JsonTest, RestClientTest  등을 설명하는데..

 

이것만 있는 것이 아니다. 

 

여러 개가 더 있다.

 

https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html

 

Appendix D. Test auto-configuration annotations

Appendix D. Test auto-configuration annotations The following table lists the various @…Test annotations that can be used to test slices of your application and the auto-configuration that they import by default: Test sliceImported auto-configuration@DataJ

docs.spring.io

 

@DataJdbcTest

@DataLdapTest

@DataMongoTest

@DataNeo4jTest

@WebFluxTest

@DataRedisTest

@JdbcTest

@JooqTest 등을 포함한다.

 

 

자세한 내용은 다음을 확인한다. 

 

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html

 

46. Testing

A Spring Boot application is a Spring ApplicationContext, so nothing very special has to be done to test it beyond what you would normally do with a vanilla Spring context. By default, @SpringBootTest will not start a server. You can use the webEnvironment

docs.spring.io

 

Posted by '김용환'
,