@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");
    }
}

Class에 선언되지 않은 필드 정보가 넘어옴(columns 필드 선언되지 않음)

org.jboss.resteasy.spi.ReaderException: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "columns"

...
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "columns"

무시하려면

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class .....

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

 


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

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

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




[context-cache.xml]


<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:util="http://www.springframework.org/schema/util"
	xmlns:cache="http://www.springframework.org/schema/cache"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    					http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    					http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
    					http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">

	<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]

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    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]

@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 에서 인증 하는 방법

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



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

[설정]

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));
	}
}


[사용]

// 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]

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]

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]

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]


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'
  distributionUrl = 'http://services.gradle.org/distributions/gradle-2.3-bin.zip'
}

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]

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]

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]

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, QueryDslPredicateExecutor {

    public List findByName(String name);
}

[Test UserRepositoryTest.java]

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 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 page =  userRepository.findAll(user.name.like(name).and(user.age.eq(age)), new PageRequest(0,10));
        assertEquals("검색 결과", 1, page.getNumberOfElements());

        Iterable 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 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());
        }
    }
}

[Repository UserRepositorySupport.java]

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) {
        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());
    }
}

[참고] https://www.youtube.com/watch?v=ho0fQt8v_HA


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]


	
	

	
	


[Log]

-- 비 정상
[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 

지금까지 Spring 설정파일이나 Web.xml 파일의 JavaConfig형태로 사용하는것에 대해서 큰 필요성을 느끼지 못해 굳이 사용하고 있지 않았었다.


그런데 최근 Filter를 운영환경에서만 추가로 설정해야 할 필요가 생겨났다.

Filter를 상속받아서 처리해도 되겠지만 생각난김에 web.xml을 Java Code로 변경해 보았다.


[WebXml.java]

package config;

import java.util.EnumSet;

import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.multipart.support.MultipartFilter;
import org.springframework.web.servlet.DispatcherServlet;

import pe.kr.ddakker.core.framework.web.GlobalsProperties;
import pe.kr.ddakker.core.support.util.StringUtils;

/**
 * /WEB-INF/Web.xml 설정
 * @author ddakker 2014. 10. 13.
 */
public class WebXml implements WebApplicationInitializer {
	private static Logger log = LoggerFactory.getLogger(WebXml.class);

	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		// 스프링 설정
		XmlWebApplicationContext rootContext = new XmlWebApplicationContext();
        rootContext.setConfigLocations(new String[] { "classpath:config/spring/context-*.xml" });
        rootContext.refresh();
        rootContext.start();


        GlobalsProperties globalsProperties = rootContext.getBean("globalsProperties", GlobalsProperties.class);
        String serverType 	= StringUtils.defaultString(globalsProperties.getProperty("server.type"), "local")
        String domain		= GlobalsProperties.REAL.equals(serverType)?"board.ddakker.pe.kr":serverType + "-board.ddakker.pe.kr";
        log.debug("========== domain: {} ========== ", domain);
        log.debug("========== serverType: {} ========== ", serverType);


        // SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS Filter 영역 SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
        if (GlobalsProperties.STAGE.equals(serverType) || GlobalsProperties.REAL.equals(serverType)) {
			// 상황에 따른 필터 추가
        }

        // 인코딩 설정
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);
        FilterRegistration.Dynamic characterEncoding = servletContext.addFilter("characterEncodingFilter", characterEncodingFilter);
        characterEncoding.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");

        // 스프링 RestFull 설정
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        FilterRegistration.Dynamic hiddenHttpMethod = servletContext.addFilter("hiddenHttpMethodFilter", hiddenHttpMethodFilter);
        hiddenHttpMethod.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");

        // 파일업로드 설정
        MultipartFilter multipartFilter = new MultipartFilter();
        FilterRegistration.Dynamic multipart = servletContext.addFilter("multipartFilter", multipartFilter);
        multipart.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");

        // String Security 설정
        DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy();
        FilterRegistration.Dynamic springSecurity = servletContext.addFilter("springSecurityFilterChain", springSecurityFilterChain);
        springSecurity.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");



        // 사이트메쉬 설정
        SiteMeshFilter siteMeshFilter = new SiteMeshFilter();
        FilterRegistration.Dynamic siteMeshEncoding = servletContext.addFilter("siteMeshFilter", siteMeshFilter);
        siteMeshEncoding.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD), true, "/*");
        // EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE Filter 영역 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE




        // SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS Listener 영역 SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
        // String Core 설정
        servletContext.addListener(new ContextLoaderListener(rootContext));

        // Spring Security 증복로그인 관련 처리 여부(resources/config/spring/context-security.xml session-management->concurrency-control 부분)
        //servletContext.addListener(new HttpSessionEventPublisher());
        // EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE Listener 영역 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE





        // SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS Servlet 영역 SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
        // 스프링 MVC 설정
        XmlWebApplicationContext xmlWebApplicationContext = new XmlWebApplicationContext();
        xmlWebApplicationContext.setConfigLocation("classpath:config/spring/servlet-mvc.xml");
        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(xmlWebApplicationContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
        /* EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE Servlet 영역 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE */


	}
}

[web.xml]





	...
	
	
		contextConfigLocation
		classpath:config/spring/context-*.xml
	


	
		monitoring
		net.bull.javamelody.MonitoringFilter
	
	
		monitoring
		/*
	
	
		net.bull.javamelody.SessionListener
	
	
	
		encodingFilter
		org.springframework.web.filter.CharacterEncodingFilter
		
			encoding
			UTF-8
		
		
			forceEncoding
			true
		
	
	
		encodingFilter
		/*
	
	
		org.springframework.security.web.session.HttpSessionEventPublisher
	
	
		httpMethodFilter
		org.springframework.web.filter.HiddenHttpMethodFilter
	
	
		httpMethodFilter
		/*
	
	
		multipartFilter
		org.springframework.web.multipart.support.MultipartFilter
	
	
		multipartFilter
		/*
	
	
		springSecurityFilterChain
		org.springframework.web.filter.DelegatingFilterProxy
	
	
		springSecurityFilterChain
		/*
	
	
		sitemesh
		com.opensymphony.sitemesh.webapp.SiteMeshFilter
	
	
		sitemesh
		/*
		REQUEST
		FORWARD
	
	
		org.springframework.web.context.ContextLoaderListener
	
	
		action
		org.springframework.web.servlet.DispatcherServlet
		
			contextConfigLocation
			classpath:config/spring/servlet-mvc.xml
		
		1
	
	
		action
		/
	


Struts2 + Spring + iBatis 환경에서 MVC 테스트 방법을 찾아봤다.

POJO(s/getter) 방식과 Request s/getAttribute 방식 두가지로 결과값을 받아 데이터를 검증해보자.


[GoodsActionCase.java]

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;

import java.util.HashMap;
import java.util.Map;

import org.apache.struts2.ServletActionContext;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;

import com.opensymphony.xwork2.ActionProxy;;

public class GoodsActionCase extends StrutsTestCaseSupport {

	@Test
	public void 상품상세() throws Exception {
		Map sessionMap = new HashMap();

		// /view/goodsDetail.action
		ActionProxy proxy = StrutsTestCaseSupport.getInstance().createActionProxy ("detail", "/goods", sessionMap);
		MockHttpServletRequest request = new MockHttpServletRequest();
		request.setParameter("코드", "123456");
		ServletActionContext.setRequest(request);


		GoodsAction goodsAction = (GoodsAction) proxy.getAction();
		goodsAction.detail();

		GoodsInfoBean memberFieldData 	= goodsAction.getDataBean();
		GoodsInfoBean setAttributeData 	= (GoodsInfoBean) ServletActionContext.getRequest().getAttribute("goodsInfo");

		assertThat("널이 아니겠지?", memberFieldData, is(notNullValue()));
		assertThat("널이 아니겠지?", setAttributeData, is(notNullValue()));
		assertThat("앞뒤가 같겠지?", memberFieldData.getGoodsNm(), equalTo(setAttributeData.getGoodsNm()));
	}
}

[환경]

Struts2 2.0.x

Spring 2.5.x

StrutsTestCaseSupport.java - http://fassisrosa.blogspot.kr/2007/09/unit-testing-struts-20-part-3.html


+ Recent posts