@Validated 기능 사용 시 Spring의 AOP Proxy 동과 관련되어 AService 클래스내에 @Autowired BService bService가 null 해결 방법

@Validated
@Service
class AService {
	@Autowired
    private BService bService;
    
    private Test getTest() {
    	// bService null인 상황일때
    }
}





@ActiveProfiles({ Application.ACTIVE_PROFILE_TEST_CASE })
@SpringBootTest
@ExtendWith(SpringExtension.class)
@TestMethodOrder(MethodOrderer.Alphanumeric.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Slf4j
class TestCase {
    @MockBean
    private BService bService;

    @Autowired
    private AService aService;

    @Test
    void test() {
        ...
        BService bService = new BService();
        Mockito.when(bService.get1("1")).thenReturn(new Test());
        ...

        ReflectionTestUtils.invokeMethod(AopTestUtils.getTargetObject(aService), AService.class, , "getTest");
    }
}

build.gradle

    implementation ('org.springframework.boot:spring-boot-starter-jdbc') {
        exclude group: 'com.zaxxer', module: 'HikariCP'
    }
    implementation group: 'org.apache.commons', name: 'commons-dbcp2', version: '2.9.0'

application.yaml

 

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3380/testdb
    username: root
    password: test
    dbcp2:
      initial-size: 5
      max-total: 10
      test-while-idle: true
      time-between-eviction-runs-millis: 1000
      num-tests-per-eviction-run: 1

 

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
    <http auto-config="false" use-expressions="true" disable-url-rewriting="true" entry-point-ref="loginUrlAuthenticationEntryPoint">
        ...
        <custom-filter position="BASIC_AUTH_FILTER" ref="basicAuthenticationFilter">
        ...
    </custom-filter></http>
 
    ...
 
    <beans:bean id="loginUrlAuthenticationEntryPoint" class="com.ezwel.core.security.access.EzLoginUrlAuthenticationEntryPoint">
        <beans:constructor-arg name="loginFormUrl" value="${page.login.form.url}">
    </beans:constructor-arg></beans:bean>
     
    <beans:bean id="basicAuthenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
        <beans:property name="authenticationManager" ref="ezAuthenticationManager">
        <beans:property name="authenticationEntryPoint" ref="loginUrlAuthenticationEntryPoint">
    </beans:property></beans:property></beans:bean>
 
    <beans:bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder">
 
    <authentication-manager alias="ezAuthenticationManager">
        <authentication-provider user-service-ref="ezUserDetailsService">
            <!-- <password-encoder hash="md5" base64="false" /> -->
            <password-encoder ref="passwordEncoder">
        </password-encoder></authentication-provider>
    </authentication-manager>
 
    ...
</beans:bean>


N대의 WAS에서 성능을 위한 Local cache와 Memory효율을 위한 Server Cache 를 사용중이다.

Spring에서 동시에 사용해보자.

Local Cache 는 Clustring 하지않은 Ehcache,
Server Cache 는 Infinispan Server HotRoad 방식을 사용한다.




[context-cache.xml]

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
<!--?xml version="1.0" encoding="UTF-8"?-->
 
    <cache:annotation-driven />
     
    <!-- EHCache Local 형 -->
    <bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
        <property name="cacheManager">
            <bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
                <property name="configLocation" value="classpath:config/cache/ehcache.xml"></property>
            </bean>
        </property>
    </bean>
     
    <!-- Infinispan Server 형 -->
    <util:properties id="hotrod_data" location="classpath:properties/hotrod_data.properties" />
    <bean id="ispnCacheManager" class="org.infinispan.spring.provider.SpringRemoteCacheManagerFactoryBean">
        <property name="configurationProperties" ref="hotrod_data" />
    </bean>
 
    <bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
        <property name="cacheManagers">
            <list>
                <ref bean="ehCacheManager" />
                <ref bean="ispnCacheManager" />
            </list>
        </property>
    </bean>
</beans>

[ehcache.xml]

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
<?xml version="1.0" encoding="UTF-8"?>
    xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false">
    <diskStore path="java.io.tmpdir" />
 
    <defaultCache
        maxElementsInMemory="50000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="false"
        diskPersistent="false"
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU">
    </defaultCache>
 
    <cache name="EHCACHE_MENU"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="0"
        timeToLiveSeconds="600"
        overflowToDisk="false"
        diskPersistent="false"
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU">
    </cache>
</ehcache>

[Service.java]

1
2
3
4
5
6
7
8
@Cacheable("ISPN_EVENT")
public Map getEvent(String eventKey) {
    return query...
}
@Cacheable("EHCACHE_MENU")
public Map getMenu(String menuKey) {
    return query...
}


Authentication Basic 로 인증되는 서버에서 제공하는 API를 이용 하는 중 Spring RestTemplate 활용 방법


[환경]

Spring-4.1.6

HttpClient-4.3.5


1. 호출 URL 에서 인증 하는 방법

1
Map<String, Object> resultMap = ezRestTemplate.get("http://admin:1234@localhost:12345/api/overview", HashMap.class);



2. 설정 부분에서 인증 하는 방법

[설정]

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
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
 
import javax.net.ssl.SSLContext;
 
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
 
import pe.kr.ddakker.framework.support.wrapper.http.EzRestTemplate;
 
@ImportResource("classpath:config/spring/context-*.xml")
@Configuration
public class ApplicationContext {
     
    @Bean(name = "ezRestTemplate")
    public EzRestTemplate getEzRestTemplate() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
        SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).useTLS().build();
        SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, new AllowAllHostnameVerifier());
        BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
 
 
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("admin", "1234"));
          
        HttpClient httpClient = HttpClientBuilder.create()
                                                .setSSLSocketFactory(connectionFactory)
                                                .setDefaultCredentialsProvider(credentialsProvider)
                                                .build();
          
        ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
                 
        return new EzRestTemplate(new RestTemplate(requestFactory));
    }
}


[사용]

1
2
3
4
5
// EzRestTemplate 는 RestTemplate 감쌓놓은 Wrapper Class임.
@Resource EzRestTemplate    ezRestTemplate;
 
Map<String, Object> resultMap = ezRestTemplate.get("http://localhost:12345/api/overview", HashMap.class);
System.out.println("resultMap: " + resultMap);


BindResult 삽질기!!!


회사 동료가 Spring Validation 기능을 활용해보자고 해서 해보는데 엄청난 시간 소모가...

결론은 BindResult 파라미터 선언을 @Valid 바로 뒤에 해야 한다는...


[TestWeb.java]

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
import javax.validation.Valid;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
 
@Controller
@RequestMapping("test")
public class TestWeb {
    private static Logger log = LoggerFactory.getLogger(TestWeb.class);
 
    @RequestMapping(value="case1", method=RequestMethod.GET)
    public String test(@Valid TestVo testVo, BindingResult bindResult, Model model) {
        log.debug("bindResult.hasErrors(): {}", bindResult.hasErrors());
        if (bindResult.hasErrors()) {
            throw new RuntimeException("파라미터 설정이 잘 못 되었습니다..");
            //return "coupon/mainError";    // 공통 Exception 처리 Throws 하거나 에러 뷰 return
        }
        return "test/case1";
    }
     
     
     
}

[TestVo.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
 
import lombok.Getter;
import lombok.Setter;
 
@Getter @Setter
public class TestVo {
    @Size(min=0, max=5)
    private String test1;
     
    @NotNull
    private String test2;
}

[build.gradle]

1
2
3
4
dependencies {
    compile 'spring.... 4.1.x...'
    compile 'org.hibernate:hibernate-validator:5.1.3.Final'
}

최근 인터넷 곳곳에서 ORM 관련 내용들이 많이 쏟아져나오고 있다.


예전에도 Spring 과 Hibernate를 이용해서 내부 프로젝트를 진행 한적이 있었는데, 그때는 단순한것은 Criteria를 사용하고, 조금이라도 복잡한것은 HQL을 사용했던 기억인데 개념적으로 잘 이해되지 않아 초반에 힘들었던거 같은데...


이번에도 역시 많은 삽질을 해야 했다.. 개념 이해 뿐만 아니라 이래저래 많은 변화가 있었던것 같다.

Hibernate에서 JPA, Spring Data JPA, QueryDSL, Spring Data JPA & QueryDSL 등 표준이니 뭐니...


단순 셈플링이다.

아직 알지도 못하는 많은 방법들이 존재해서 뭐가 어떻게 되는건지 심도 있는 학습이 필요할 것 같다.

작은 내부 프로젝트를 진행하려고 하는데 이번 기회에 사용해봐야겠다.


[build.gradle]


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
apply plugin: 'java'
apply plugin: 'idea'
 
ext {
    javaVversion            = '1.7'
    servletVersion          = '3.0.1'
    springframeworkVersion  = '4.1.6.RELEASE'
    hibernateJpaVersion        = '1.0.1.Final'
    hibernateEntitymanagerVersion = '4.1.9.Final'
    hibernateCommonsAnnotationsVersion = '4.0.1.Final'
    hibernateValidatorVersion = '4.3.1.Final'
    aspectjVersion = '1.6.8'
    queryDslVersion = '3.2.0'
    springDataJpaVersion = '1.8.0.RELEASE'
}
 
List loggerSlf4jAndLogback = [
        "ch.qos.logback:logback-classic:1.0.13",
        "org.slf4j:jcl-over-slf4j:1.7.5"
]
 
sourceCompatibility = javaVversion
version = '1.0'
 
task wrapper(type: Wrapper) {
  gradleVersion = '2.3'
}
 
repositories {
    mavenCentral()
}
 
dependencies {
    compile loggerSlf4jAndLogback
 
    compile "org.springframework:spring-webmvc:" + springframeworkVersion
    compile "org.springframework:spring-orm:" + springframeworkVersion
    compile "org.springframework:spring-aspects:" + springframeworkVersion
 
    compile "org.springframework.data:spring-data-jpa:" + springDataJpaVersion
    compile 'com.h2database:h2:+'
    //compile 'mysql:mysql-connector-java:5.1.23'
 
    compile "org.hibernate.javax.persistence:hibernate-jpa-2.0-api:" + hibernateJpaVersion
    compile "org.hibernate:hibernate-entitymanager:" + hibernateEntitymanagerVersion
    compile "org.hibernate.common:hibernate-commons-annotations:" + hibernateCommonsAnnotationsVersion
    compile "org.hibernate:hibernate-validator:" + hibernateValidatorVersion
 
    compile "com.mysema.querydsl:querydsl-core:" + queryDslVersion
    compile "com.mysema.querydsl:querydsl-apt:" + queryDslVersion
    compile "com.mysema.querydsl:querydsl-jpa:" + queryDslVersion
    compile "com.mysema.querydsl:querydsl-sql:" + queryDslVersion
 
 
    compile "org.aspectj:aspectjrt:" + aspectjVersion
    compile "org.aspectj:aspectjweaver:" + aspectjVersion
 
 
    compile 'javax:javaee-api:7.0'
 
    //providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
 
    testCompile group: 'junit', name: 'junit', version: '4.11'
    testCompile "org.springframework:spring-test:" + springframeworkVersion
}
 
sourceSets {
    generated {
        java {
            srcDirs = ['src/main/generated']
        }
    }
}
 
task generateQueryDSL(type: JavaCompile, group: 'build') {
    source = sourceSets.main.java
    classpath = configurations.compile
    options.compilerArgs = [
            "-proc:only",
            "-processor", "com.mysema.query.apt.jpa.JPAAnnotationProcessor"
    ]
    destinationDir = sourceSets.generated.java.srcDirs.iterator().next()
}
 
compileJava {
    dependsOn generateQueryDSL
    source generateQueryDSL.destinationDir
}
compileGeneratedJava {
    dependsOn generateQueryDSL
    options.warnings = false
    classpath += sourceSets.main.runtimeClasspath
}
 
clean {
    delete sourceSets.generated.java.srcDirs
}
 
idea {
    module {
        sourceDirs += file('src/main/generated')
    }
}

[Config AppConfig.java]

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
package pe.kr.ddakker.jpa;
 
import org.h2.tools.Server;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.aspectj.EnableSpringConfigured;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
import java.sql.SQLException;
import java.util.Properties;
 
/**
 * Created by ddakker on 2015-04-09.
 */
@Configuration
@ComponentScan("pe.kr.ddakker.jpa")
@EnableTransactionManagement
@EnableSpringConfigured
@EnableJpaRepositories("pe.kr.ddakker.jpa")
public class AppConfig {
 
    @Autowired
    Environment env;
 
    @Bean
    public DataSource testDataSource() {
        return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
    }
 
    @Bean
    public HibernateJpaVendorAdapter jpaVendorAdapter() {
        return new HibernateJpaVendorAdapter();
    }
 
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws SQLException, PropertyVetoException{
        LocalContainerEntityManagerFactoryBean emf =
                new LocalContainerEntityManagerFactoryBean();
        emf.setDataSource(testDataSource());
        emf.setJpaVendorAdapter(jpaVendorAdapter());
        emf.setPersistenceUnitName("rss");
        emf.setPackagesToScan("pe.kr.ddakker.jpa.domain");
        Properties properties = new Properties();
        properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        properties.put("hibernate.hbm2ddl.auto", "create-drop");
//        properties.put("hibernate.show_sql","true");
//        properties.put("hibernate.format_sql", "true");
        emf.setJpaProperties(properties);
        return  emf;
    }
 
    @Bean
    public JpaTransactionManager transactionManager() throws PropertyVetoException, SQLException{
        JpaTransactionManager trans = new JpaTransactionManager();
        trans.setEntityManagerFactory(entityManagerFactory().getObject());
        return trans;
    }
 
}

[Domain User.java]

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
package pe.kr.ddakker.jpa.domain;
 
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
 
 
 
 
@Entity
public class User implements Serializable {
    @Id
    @Column(name = "USER_ID")
    private Long id;
 
    @Column(name = "USER_NM")
    private String name;
 
    @Column(name = "USER_AGE")
    private Integer age;
 
    @Temporal(TemporalType.DATE)
    @Column(name = "REG_DT")
    private Date regDt;
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
 
    public Date getRegDt() {
        return regDt;
    }
 
    public void setRegDt(Date regDt) {
        this.regDt = regDt;
    }
}

[Repository UserRepository.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package pe.kr.ddakker.jpa.repository;
 
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.repository.CrudRepository;
import pe.kr.ddakker.jpa.domain.User;
 
import java.util.List;
 
/**
 * Created by ddakker on 2015-04-09.
 */
public interface UserRepository extends CrudRepository<user, long="">, QueryDslPredicateExecutor<user> {
 
    public List<user> findByName(String name);
}
</user></user></user,>

[Test UserRepositoryTest.java]

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
122
package pe.kr.ddakker.jpa.repository;
 
import com.mysema.query.jpa.impl.JPAQuery;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import pe.kr.ddakker.jpa.AppConfig;
import pe.kr.ddakker.jpa.domain.QUser;
import pe.kr.ddakker.jpa.domain.User;
 
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.Date;
import java.util.List;
 
import static org.junit.Assert.assertEquals;
 
/**
 * Created by ddakker on 2015-04-09.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {AppConfig.class})
@Transactional
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class UserRepositoryTest {
    @Autowired private UserRepository userRepository;
 
    @PersistenceContext private EntityManager entityManager;
 
    /**
     * Spring JPA CRUD 기능을 이용한다면
     */
    @Before
    public void create() {
        User inputUser = new User();
        inputUser.setId(System.currentTimeMillis());
        inputUser.setName("ddakker");
        inputUser.setAge(33);
        inputUser.setRegDt(new Date());
        userRepository.save(inputUser);
 
        inputUser = new User();
        inputUser.setId(System.currentTimeMillis());
        inputUser.setName("petitjjang");
        inputUser.setAge(33);
        inputUser.setRegDt(new Date());
        userRepository.save(inputUser);
 
        inputUser = new User();
        inputUser.setId(System.currentTimeMillis());
        inputUser.setName("sisigi");
        inputUser.setAge(1);
        inputUser.setRegDt(new Date());
        userRepository.save(inputUser);
 
        assertEquals("전체 사이즈", 3, userRepository.count());
    }
 
    /**
     * Method Name Query를 사용한다면
     */
    @Test
    public void testRepository_methodNameQuery() {
        List<user> userList = userRepository.findByName("ddakker");
        assertEquals("갯수는", 1, userList.size());
    }
    /**
     * Spring Data JPA & QueryDSL Predicate 사용한다면
     */
    @Test
    public void testRepository_Predicate() {
        String name = "dda%";
        int age = 33;
 
        QUser user = QUser.user;
        Page<user> page =  userRepository.findAll(user.name.like(name).and(user.age.eq(age)), new PageRequest(0,10));
        assertEquals("검색 결과", 1, page.getNumberOfElements());
 
        Iterable<user> users = userRepository.findAll(user.age.eq(age));
        for (User u : users) {
            System.out.println("iterable user: " + u.getId() + ", " + u.getName() + ", " + u.getAge() + "," + u.getRegDt());
        }
    }
 
 
    /**
     * Spring Data JPA & QueryDSL 확장 기능을 이용한다면
     * @throws Exception
     */
    @Test
    public void testRepository_support() throws Exception {
        UserRepositorySupport userRepositorySupport = new UserRepositorySupport(User.class);
        userRepositorySupport.setEntityManager(entityManager);
        assertEquals("큰 나이", 33, userRepositorySupport.getMaxAge());
        assertEquals("작은 나이", 1, userRepositorySupport.getMinAge());
 
    }
 
    /**
     * 직접 QueryDSL을 써본다면..
     *      - 이것을 어느 영역에 둬야 할까...
     */
    @Test
    public void test_Dsl() {
        JPAQuery query = new JPAQuery(entityManager);
        QUser qUser = QUser.user;
        List<user> userList = query.from(qUser).where(qUser.name.eq("ddakker")).list(qUser);
 
        for (User u : userList) {
            System.out.println("list user: " + u.getId() + ", " + u.getName() + ", " + u.getAge() + "," + u.getRegDt());
        }
    }
}
</user></user></user></user>

[Repository UserRepositorySupport.java]

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
package pe.kr.ddakker.jpa.repository;
 
import org.springframework.data.jpa.repository.support.QueryDslRepositorySupport;
import pe.kr.ddakker.jpa.domain.QUser;
import pe.kr.ddakker.jpa.domain.User;
 
/**
 * Created by ddakker on 2015-04-09.
 */
public class UserRepositorySupport extends QueryDslRepositorySupport {
    public UserRepositorySupport(Class<user> user) {
        super(user);
    }
 
    public int getMinAge() {
        QUser qUser = QUser.user;
        return from(qUser).uniqueResult(qUser.age.min());
    }
 
    public int getMaxAge() {
        QUser qUser = QUser.user;
        return from(qUser).uniqueResult(qUser.age.max());
    }
}
 
</user>
1
<user><span style="font-family: 돋움; font-size: 9pt; line-height: 1.5; white-space: normal;">[참고] </span><span style="font-family: 돋움; white-space: normal;">https://www.youtube.com/watch?v=ho0fQt8v_HA</span></user>


Redis 를 활용해서 Cache를 적용해보자..


우선 Redis Client는 http://www.redis.io/clients 에서 확인되는바와 같이 종류가 여러가지다.

그런데 Spring Data Redis 를 사용하면 별도의 Client가 필요 없네...(Interface만 있고 구현체는 가져다 쓰는줄 알았더니 아니네요.)


Source Code에서 사용법과 Method Annotataion 사용법을 보자.


[build.gradle]

1
2
3
4
dependencies {
    compile springDependencies // Spring 3.2.x
    compile 'org.springframework.data:spring-data-redis:1.3.4.RELEASE'
}


[context-cache.xml]

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
<!--?xml version="1.0" encoding="UTF-8"?-->
  
    <cache:annotation-driven>
  
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="${redis.pool.maxTotal}">
        <property name="maxIdle" value="${redis.pool.maxIdle}">
        <property name="minIdle" value="${redis.pool.minIdle}">
    </property></property></property></bean>
  
    <bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.host}">
        <property name="port" value="${redis.port}">
        <property name="poolConfig" ref="jedisPoolConfig">
    </property></property></property></bean>
  
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnFactory">
    </property></bean>
     
    <!-- 접두어(실제 키는 prefix:cacheName:key 가된다. -->
    <bean id="prefix" class="pe.kr.ddakker.core.framework.cache.prefix.RedisCacheNamePrefix">
        <constructor-arg name="delimiter" value="SAMPLE">
    </constructor-arg></bean>
  
    <bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
        <constructor-arg name="template" ref="redisTemplate">
        <property name="usePrefix" value="true">
        <property name="cachePrefix" ref="prefix">
        <!-- 초 단위 -->
        <property name="expires">
            <map>
                <entry key="BANNER" value="60">
                <entry key="EVENT" value="5">
            </entry></entry></map>
        </property>
        <!-- 순서 중요!!! 맨 나중에!! -->
        <property name="cacheNames">
            <list>
                <value>BANNER</value>
                <value>EVENT</value>
            </list>
        </property>
    </property></property></constructor-arg></bean>
  
    <bean id="cacheEvent" class="pe.kr.ddakker.core.framework.cache.CacheRedis">
        <constructor-arg name="cacheManager" ref="cacheManager">
        <constructor-arg name="cacheName" value="EVENT">
    </constructor-arg></constructor-arg></bean>
  
    <bean id="cacheBanner" class="pe.kr.ddakker.core.framework.cachezCacheRedis">
        <constructor-arg name="cacheManager" ref="cacheManager">
        <constructor-arg name="cacheName" value="BANNER">
    </constructor-arg></constructor-arg></bean>
  
</cache:annotation-driven></beans>


[RedisTestCase.java]

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
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
        "classpath*:config/spring/context-common.xml",
        "classpath*:config/spring/context-cache.xml",
        "classpath*:config/spring/context-datasource.xml"
})
public class RedisTestCase {
    @Resource Cache<string> cacheEvent;
    @Resource Cache<user> cacheBanner;
  
    @Resource AuthService authService;
  
    @Test
    public void test() {
        User user = new User();
        user.setUserNm("ddakker");
      
        cacheEvent.put("a", "zzz");
        String a = cacheEvent.get("a");
        System.out.println("a: " + a);
      
        cacheBanner.put("user", user);
        User resultUser = cacheBanner.get("user");
        System.out.println("user: " + resultUser);
    }
  
}
  
@Test
public void testAnnotation() {
    // DB 쿼리 호출
    List<auth> authList = authService.getList();
    System.out.println("authList: " + authList);
  
    // DB 쿼리 호출 않음
    authList = authService.getList();
    System.out.println("authList: " + authList);
  
    Auth auth = new Auth();
    auth.setSearchKey("AUTH_CD");
    auth.setSearchKwd("ROLE_USER");
  
    // DB 쿼리 호출
    authList = authService.getList(auth);
    System.out.println("authList user: " + authList);
  
    // DB 쿼리 호출 않음
    authList = authService.getList(auth);
    System.out.println("authList user: " + authList);
}
</auth></user></string>


[AuthService.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// @Cacheable 의 key나 조건의 디테일한 제어에 대한 설멍은 - http://dev.anyframejava.org/docs/anyframe/plugin/optional/cache/1.0.3/reference/html/ch01.html
@Service
public class AuthService {
    @javax.annotation.Resource AuthMapper authMapper;
  
    @Cacheable(value="BANNER")
    public List<auth> getList() {
        return authMapper.getList(new Auth());
    }
  
    @Cacheable(value="EVENT")
    public List<auth> getList(Auth authVo) {
        return authMapper.getList(authVo);
    }
}
</auth></auth>

Spring MyBatis 환경에서 아래와 같은 메시지가 나오면서 트랜잭션 정상적으로 작동 안 하는 상황이 있다.


was not registered for synchronization because synchronization is not active

will not be managed by Spring 


TestCase로 Service 계층에서부터 테스트 하면 정상적으로 처리 되는 모습을 보였고, Controller 부분 부터 테스트 하면 실패하는 모습을 보였다.


우선 정상 로그와 비교해보았을때 아래와 같았고, Servlet MVC 부분에 문제로 보여 살펴보았는데 눈에 잘 띄지 않아.. 삽질 하다.. 찾음;;


문제는 <context:component-scan base-package="pe.kr.ddakker">부분에서 하위에 Controller이 아닌 Service 계층이 포함되면 문제가 생기는것이였다.

그리하여 해상 설정에서 Controller 이 아닌 Annotation들을 exclude-filter 해주거나, base-package 부분을 명확하게 구분되게 해주면 되겠다.



[servlet-mvc.xml]

1
2
3
4
5
6
7
<context:component-scan base-package="pe.kr.ddakker">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller">
    <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice">
 
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository">
</context:exclude-filter></context:exclude-filter></context:include-filter></context:include-filter></context:component-scan>


[Log]

1
2
3
4
5
6
7
-- 비 정상
[DEBUG] org.mybatis.spring.SqlSessionUtils[getSqlSession:140] - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4c1c68d3] was not registered for synchronization because synchronization is not active
[DEBUG] org.mybatis.spring.transaction.SpringManagedTransaction[openConnection:86] - JDBC Connection [jdbc:oracle:thin:@ip:1521:sid, UserName=id, Oracle JDBC driver] will not be managed by Spring
 
-- 정상
[DEBUG] org.mybatis.spring.SqlSessionUtils[getSqlSession:120] - Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@49ffa050]
[DEBUG] org.mybatis.spring.transaction.SpringManagedTransaction[openConnection:86] - JDBC Connection [jdbc:oracle:thin:@ip:1521:test, UserName=STAT, Oracle JDBC driver] will be managed by Spring

.properties 의 경우 ISO-8859-1 문자셋이기때문에 한글 사용에 문제가 있어, IDE Tool 을 사용하여 자동으로 변환하는 방법을 이용하곤 했다.

하지만 이 방법의 경우에도 서버상에 올라가있는 설정 파일을 확인 및 직접 수정할 경우 에로 사항이 발생한다.

또한 개개인별 .properties 를 ms949, euc-kr 등으로 변경 해 놓는 사태도 발생해버린다. OTL...


이러한 불편함을 해결 하기 위해 .xml 확장자를 이용하면 좋을것이다.



[applicationContext.xml]

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
<!--?xml version="1.0" encoding="UTF-8"?-->
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:/properties/globals-properties.xml</value>
            </list>
        </property>
    </bean>
 
    <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource ">
        <property name="basenames">
            <list>
                <value>classpath:properties/messages-properties</value>
            </list>
        </property>
    </bean>
 
    <util:properties id="global" location="classpath:/properties/globals-properties.xml">
    <bean id="globalsProperties" class="pe.kr.ddakker.core.framework.web.GlobalsProperties">
        <constructor-arg index="0">
            <ref bean="global">
        </ref></constructor-arg>
    </bean>
<beans>
</beans></util:properties></beans>


[Sample.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Controller
public class Sample {
     
    @Value("#{global['server.type']}") private String serverType;
    @Resource GlobalsProperties globalsProperties;
 
    @RequestMapping(value="/sample")
    public void test(ModelMap model) {
        // Properties Case 1
        System.out.println("serverType: " + serverType);
 
        // Properties Case 2
        System.out.println("server.type: " + globalsProperties.getProperty("server.type"));
             
        //MessageSource Case 1
        System.out.println("msg.test1: " + messageSource.getMessage("msg.test1", null, Locale.getDefault()));
 
        // MessageSource Case 2
        System.out.println("msg.test2: " + messageSource.getMessage("msg.test2", new String[]{"첫번째", "두번째"}, Locale.getDefault()));
    }
}


[sample.jsp]

1
2
3
4
5
6
7
8
9
10
11
12
13
    <!-- Properties Case 1 -->
    <spring:eval expression="@global['server.type']">
    <spring:eval var="serverType" expression="@global['server.type']">
    <c:if test="${ serverType == 'local' }">
        로컬임
    </c:if>
 
    <!-- MessageSource Case 1 -->
    <spring:message code="msg.test1">
 
    <!-- MessageSource Case 2 -->
    <spring:message code="msg.test2" arguments="첫번째, 두번째">
</spring:message></spring:message></spring:eval></spring:eval>


globals-properties.xml

1
2
3
4
5
6
7
8
9
<!--?xml version="1.0" encoding="UTF-8" ?-->
 
<properties>
    <comment>globals-properties local</comment>
    <entry key="server.type">local</entry>                              <!-- 서버정보[local,dev,stage,real] -->
 
    <entry key="content.key.result.code">result</entry>                 <!-- 결과 코드 Key -->
    <entry key="content.key.result.message">message</entry>             <!-- 결과 메시지 Key -->
</properties>


[messages-properties_ko.xml]

1
2
3
4
5
6
7
8
9
<!--?xml version="1.0" encoding="UTF-8" ?-->
 
<properties>
    <comment>messages-properties_ko</comment>
 
 
    <entry key="msg.test1">ko 한글 정보는...</entry>
    <entry key="msg.test2">ko 파라미터1 = {0}, 파라미터2 = {1}...</entry>
</properties>


+ Recent posts