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


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


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

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


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


[build.gradle]

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


[context-cache.xml]



 
	
 
	
		
		
		
	
 
	
		
		
		
	
 
	
		
	
	
	
	
		
	
 
	
		
		
	    
	    
	    
		    
				
				
			
		
		
		
			
	            BANNER
	            EVENT
	        
		
	
 
	
	    
	    
	
 
	
	    
	    
	
 


[RedisTestCase.java]

@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 cacheEvent;
    @Resource Cache 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 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);
}


[AuthService.java]

// @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 getList() {
		return authMapper.getList(new Auth());
	}
 
	@Cacheable(value="EVENT")
	public List getList(Auth authVo) {
		return authMapper.getList(authVo);
	}
}

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 

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

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

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


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



[applicationContext.xml]


	
	
        
            
                classpath:/properties/globals-properties.xml
            
        
    

	
		
			
				classpath:properties/messages-properties
			
		
	

	
	
		
			
		
	


[Sample.java]

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

	
	
	
	
		로컬임
	

	
	

	
	


globals-properties.xml




	globals-properties local
	local								

	result					
	message				


[messages-properties_ko.xml]




	messages-properties_ko


	ko 한글 정보는...
	ko 파라미터1 = {0}, 파라미터2 = {1}...


지금까지 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
		/
	


+ Recent posts