최근 인터넷 곳곳에서 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


Slack 라는 업무용 메신저를 회사 동료가 알려줘서 우선 기존 모니터링 SMS 발송 부분을 Slack 으로 부분적으로 대체 해보기로 했다.


JAVA Client API로는 여러가지가 있고, 메시지 발송용으로는 아래 두가지 아무거나 사용하면 될 것 같다.

(https://api.slack.com/community)


SimpleSlackAPI - https://github.com/Ullink/simple-slack-api

SlackSDK - https://github.com/estebanwasinger/slack-sdk


SimpleSlackAPI 의 경우는 본인 계정 Token 기반으로만 글을 남길 수 있고, 내용 이외에 스타일 구조 메시지를 넣을 수 있다.(제목, 들여 쓰기 내용 등)

SlackSDK 의 경우는 본인 계정 Token 기반으로는 인증 하되 전송자명, 전송자 이미지를 수동으로 조정 가능하다.


각각 단순 셈플링시의 차이점이며 자세한 API는 아직 잘 모름!!

하지만 이정도만 해도 현재로서는 충분함!!


[SimpleSlackAPI]

package com.ullink.slack;


import org.junit.Test;

import com.ullink.slack.simpleslackapi.SlackAttachment;
import com.ullink.slack.simpleslackapi.SlackChannel;
import com.ullink.slack.simpleslackapi.SlackSession;
import com.ullink.slack.simpleslackapi.impl.SlackChannelImpl;
import com.ullink.slack.simpleslackapi.impl.SlackSessionFactory;

public class SlasckTest {
	@Test
	public void create() {
		final SlackSession session = SlackSessionFactory.createWebSocketSlackSession("개인 계정 Token 문자열");
		
		SlackChannel channel = new SlackChannelImpl("#cms-batch", null, null, null);
		session.sendMessage(channel, "내용이여", new SlackAttachment("title", "fallback", "text", "pretext"));
	}
}


[SlackSDK]

package msg.test;

import org.junit.Test;
import org.stevew.SlackClient;

public class SlackTest {
	@Test
	public void create1() {
		SlackClient sc = new SlackClient("개인 계정 Token 문자열");
		sc.sendMessage("내용이여", "#channel-name", "전송자명(쓰는대로 됨)", "http://../logo.png");
	}
}



로그성 DB INSERT로 인해서 전체적인 DB성능 이슈가 발생하면서 어플리케이션까지 서능에 영향이 미치는 상황이 발생하였다.


처음에는 Logging 라이브러리를 활용하여 Text형태로 쌓아놓고 한가한 시간에 배치로 DB에 INSERT하려고 하다가 공부 겸 JMS(java message service)를 적용해보기로 하였다.


Message Queue 관련 방법은 여러가지가 있던데 선택은 Apache ActiveMQ 를 선택 하였다.

관련 장단점에 대해서는 인터넷 참고!!





우선 Apache ActiveMQ 설치


context-jms.xml


	



	



	



	
    




	
	
	



	
	
	
		
	


MemberMessageConverter.java

package pe.kr.ddakker.jms;

import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.Session;

import org.springframework.jms.support.converter.MessageConversionException;
import org.springframework.jms.support.converter.MessageConverter;

public class MemberMessageConverter implements MessageConverter {

	public MemberMessageConverter(){}
	
	@Override
	public Object fromMessage(Message message) throws JMSException,
			MessageConversionException {
		if(!(message instanceof MapMessage)){
			throw new MessageConversionException("Messaeg isn't a MapMessage");
		}
		
		MapMessage mapMessage = (MapMessage) message;
		Member member = new Member();
		member.setName(mapMessage.getString("name"));
		member.setEmail(mapMessage.getString("email"));
		return member;
	}

	@Override
	public Message toMessage(Object object, Session session) throws JMSException,
			MessageConversionException {
		if(!(object instanceof Member)){
			throw new MessageConversionException("Messaeg isn't a Member");
		}
		
		Member member = (Member) object;
		MapMessage message = session.createMapMessage();
		message.setString("name", member.getName());
		message.setString("email", member.getEmail());
		return message;
	}

}


ReceiverMessageListenerImpl.java

package pe.kr.ddakker.jms;

import javax.annotation.Resource;
import javax.jms.Message;
import javax.jms.MessageListener;

import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.stereotype.Component;

@Component
public class ReceiverMessageListenerImpl implements MessageListener {


	@Resource
	private MessageConverter memberConverter;

	@Override
	public void onMessage(Message message) {
		Member member = null;
		try {
			member = (Member) memberConverter.fromMessage(message);

			if (member != null) {
				System.out.println("member.getEmail(): " + member.getEmail());
			}
		} catch (Exception e) {
			System.err.println(e);
		}

	}

}


JmsTest .java

package pe.kr.ddakker.jms;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath*:config/spring/context-jms.xml"})
public class JmsTest {
	@Resource
	private JmsTemplate jmsTemplate;
	
	@Test
	public void test() {
		
		for (int i = 0; i < 5; i++) {
			Member member = new Member();
			member.setName("ddakker" + i);
			member.setEmail("ddakker" + i + "@gmail.com");
			
			jmsTemplate.convertAndSend(member);
		}
	}
}


Redis Cluster 셈플링 좀 하다보니 Cluster 기능이 Ruby 기반으로 작동하고 있네..


Ruby 를 안 해본 입장에서 설치 좀 해보려니 좀 삽질..


[환경]

CentoOs 5.9 64Bit


해당 환경에서 yum으로 설치해보니 ruby 1.8.5가 설치되는 것 같고, Gem이 포함되지 않은 버전이여서 Gem도 수동으로 설치하고.. 안되서 버전 이것저것 삽질을 하다가 RVM 발견..


아주 간단하게 설치..


[RVM 설치] http://rvm.io/

설명대로 설치

$ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
$ \curl -sSL https://get.rvm.io | bash -s stable

[RUBY 설치]

$ rvm install 2.2.0

Gem 포함 설치


* Redis Cluster 구동해보려 하니 ruby 관련 redis가 필요하단다.


[RUBY Redis Lib 설치]

$ gem install redis


* /usr/bin/env: ruby: No such file or directory .. 없단다..

ruby 실행 파일을 /usr/bin/ruby 에 심볼릭 링크 걸어 해결

ln -s /usr/local/rvm/rubies/default/bin/ruby /usr/bin/ruby


Redis Cluster 실행 성공!!

VMWare 기반 Mint 해상도 설정 리스트에 존재 하지 않는 사이즈로 변경하기



$ cvt 1920 1080 엔터

--------------------------------------------------------------------------------------------------------
# 1920x1080 59.96 Hz (CVT 2.07M9) hsync: 67.16 kHz; pclk: 173.00 MHz
Modeline "1920x1080_60.00"  173.00  1920 2048 2248 2576  1080 1083 1088 1120 -hsync +vsync
--------------------------------------------------------------------------------------------------------

위에 나온 정보 입력
$ xrandr --newmode "1920x1080_60.00"  173.00  1920 2048 2248 2576  1080 1083 1088 1120 -hsync +vsync
$ xrandr
--------------------------------------------------------------------------------------------------------
Screen 0: minimum 1 x 1, current 800 x 600, maximum 8192 x 8192
Virtual1 connected 800x600+0+0 (normal left inverted right x axis y axis) 0mm x 0mm
   800x600        60.0*+   60.3 
생략
--------------------------------------------------------------------------------------------------------
$ xrandr --addmode Virtual1 "1920x1080_60.00"

# Virtuals1 부분은 xrandr 명령어 처보면 확인 가능 VGA, DVI, HDMI 등??

Gradle

[build.gradle]

dependencies {
	compile 'com.yahoo.platform.yui:yuicompressor:2.4.8'
}

apply plugin: 'js'
apply plugin: 'css'
buildscript {
    repositories {
    	jcenter()
    }
    dependencies {
       classpath 'com.eriwen:gradle-js-plugin:1.12.1'
       classpath 'com.eriwen:gradle-css-plugin:1.11.1'
    }
}

javascript.source {
	def targetDir = "$project.projectDir" + File.separatorChar + "webapp" + File.separatorChar + "resources" + File.separatorChar + "js"
    dev {
        js {
            srcDir targetDir
            include "**/*.js"
            exclude "**/*.min.js"
        }
    }
}
css.source {
	def targetDir = "$project.projectDir" + File.separatorChar + "webapp" + File.separatorChar + "resources" + File.separatorChar + "css"
    dev {
        css {
            srcDir targetDir
            include "**/*.css"
            exclude "**/*.min.css"
        }
    }
}

javascript.source.dev.js.files.eachWithIndex { jsFile, i ->
	tasks.create(name: "minifyJs${i}", type: com.eriwen.gradle.js.tasks.MinifyJsTask) {
		source 	= jsFile
		dest 	= jsFile
		//dest = jsFile.getAbsolutePath().replace('.js', '.min.js')
	}
}
task allMinifyJs(dependsOn: tasks.matching { Task task ->
		if( "$targetServer" != "local" ){
			task.name.startsWith("minifyJs")
		} else {
			println '로컬에서는 minify 하지 않음'
		}
	}
)

css.source.dev.css.files.eachWithIndex { cssFile, i ->
	tasks.create(name: "minifyCss${i}", type: com.eriwen.gradle.css.tasks.MinifyCssTask) {
		source 	= cssFile
		dest 	= cssFile
    	//dest = cssFile.getAbsolutePath().replace('.css','.min.css')
    	yuicompressor {
    		lineBreakPos = -1
    	}
    }
}
task allMinifyCss(dependsOn: tasks.matching { Task task ->
		if ( "$targetServer" != "local" ){
			task.name.startsWith("minifyCss")
		} else {
			println '로컬에서는 minify 하지 않음'
		}
	}
)



Ant

https://github.com/yui/yuicompressor

[build.xml]

	<path id="yui.classpath">
		<fileset dir="./lib">
			<include name="ant-yui-compressor-taskdef-1.0.jar" />
			<include name="yuicompressor-2.4.8.jar" />
		</fileset>
	</path>

	<taskdef resource="yuicompressor.tasks" classpathref="yui.classpath" />

	<target name="compress" description="Compress JS files using YUI Compressor">
		<!-- Compress JS files using YUI Compressor -->
		<ant-yui-compressor todir="./webapp/resources/js" deleteOriginal="false">
			<fileset dir="./webapp/resources/js">
				<include name="**/*.js" />
				<include name="**/*.min.js" />
				<exclude name="ez-common.js" />
				<exclude name="IBSheetPro3/IBSheetInfo_UTF8.js" />
			</fileset>
			<mapper type="glob" from="*.js" to="*.js" />
		</ant-yui-compressor>

		<!-- Compress CSS files using YUI Compressor -->
		<ant-yui-compressor todir="./webapp/resources/css" deleteOriginal="false">
			<fileset dir="./webapp/resources/css">
				<include name="**/*.css" />
				<include name="**/*.min.css" />
			</fileset>
			<mapper type="glob" from="*.css" to="*.css" />
		</ant-yui-compressor>
	</target>


web.xml 의 Filter 부분을 동적으로 처리해야 할 일이 생겼다.


Spring 3.1 이상을 사용하게 되면 org.springframework.web.WebApplicationInitializer 를 이용하게되면 더 심플하겠지만 현재 상황에서는 Spring 2.5 환경이여서 불가능!!


Servlet 3.x (javax.servlet.ServletContainerInitializer) 만으로 처리해보자.



유의할 점은 Tomcat 8.x 에서는 아래와 같이 그대로 해도 정상적으로 작동하지만 JBoss와 같이 Java EE 환경에서는 

jar cvf webxml.jar META-INF/services/javax.servlet.ServletContainerInitializer pe.kr.ddakker.WebXml.class

와 같이 jar 생성 후 WEB-INF/lib 하위에 위치 시켜야 정상 작동합니다.


build.gradle

dependencies {
	providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
}


WebXml.java

package pe.kr.ddakker;

import java.util.EnumSet;
import java.util.Set;

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

import pe.kr.ddakker.filter.TestFilter;

public class WebXml implements ServletContainerInitializer {

	@Override
	public void onStartup(Set> c, ServletContext servletContext) throws ServletException {
		System.out.println("ServletContainerInitializer");


		TestFilter testFilter = new TestFilter();
        FilterRegistration.Dynamic test = servletContext.addFilter("testFilter", testFilter);
        test.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");

	}
}


TestFilter.java

package pe.kr.ddakker.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class TestFilter implements Filter {

    public TestFilter() {
    }

	public void destroy() {
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		// pass the request along the filter chain
		System.out.println("TestFilter S");
		chain.doFilter(request, response);
		System.out.println("TestFilter E");
	}

	public void init(FilterConfig fConfig) throws ServletException {
	}

}


classpath 하위 META-INF/services/javax.servlet.ServletContainerInitializer

pe.kr.ddakker.WebXml


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 

Tomcat Multi Instance로 설정 하려면, 기동관련을 위해서 Shell도 만져야 하고, server.xml 도 만져야 한다.


그것을 Script에서만 하면 좋을것 같아서 고민 중, Tomcat에 적용해 보았다.


[env.sh]

#!/bin/sh

DATE=`date +%Y%m%d_%H%M%S`

export JAVA_HOME=/usr/local/java8
export PATH=$PATH:$JAVA_HOME/bin

# Apache Balance Workers 값(WAS별 유니크)
HTTPD_LB_WORKER_NM=testcluster
HTTPD_WORKER_NM=$HTTPD_LB_WORKER_NM'11'
PROJECT_NM_PREPIX=
PROJECT_NM=test-cluster
WAR_PATH=/usr/local/tomcat/webroot/$PROJECT_NM_PREPIX$PROJECT_NM
WEB_ROOT=$WAR_PATH/webapp

export HTTPD_WORKER_NM
export PROJECT_NM
export WAR_PATH
export WEB_ROOT

# 동일 머신에서 중복되지 않게(01~99: 최대 90개)
export PORT_OFFSET=1

let SHUTDOWN_PORT=9100+$PORT_OFFSET
let HTTP_PORT=9200+$PORT_OFFSET
let HTTPS_PORT=9300+$PORT_OFFSET
let AJP_PORT=9400+$PORT_OFFSET

export SHUTDOWN_PORT
export HTTP_PORT
export HTTPS_PORT
export AJP_PORT


export CATALINA_HOME=/usr/local/tomcat/apache-tomcat
export CATALINA_BASE=/usr/local/tomcat/domains/$HTTPD_LB_WORKER_NM

# JVM Options : Server
export JAVA_OPTS="-server $JAVA_OPTS"

# JVM Options : Memory
export JAVA_OPTS="$JAVA_OPTS -Xms2048m -Xmx2048m -Xss256k"
# jdk7 이하
#export JAVA_OPTS="$JAVA_OPTS -XX:PermSize=256m -XX:MaxPermSize=256m"

export JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCTimeStamps "
export JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCDetails "
export JAVA_OPTS="$JAVA_OPTS -Xloggc:$CATALINA_BASE/logs/gc_$DATE.log "
export JAVA_OPTS="$JAVA_OPTS -XX:+UseParallelGC "
export JAVA_OPTS="$JAVA_OPTS -XX:+UseParallelOldGC "

export JAVA_OPTS="$JAVA_OPTS -XX:+ExplicitGCInvokesConcurrent "
export JAVA_OPTS="$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError "
export JAVA_OPTS="$JAVA_OPTS -XX:HeapDumpPath=$CATALINA_BASE/logs/heapDump-$DATE.hprof "

export JAVA_OPTS="$JAVA_OPTS -Djava.net.preferIPv4Stack=true"
export JAVA_OPTS="$JAVA_OPTS -Dsun.rmi.dgc.client.gcInterval=0x7FFFFFFFFFFFFFFE"
export JAVA_OPTS="$JAVA_OPTS -Dsun.rmi.dgc.server.gcInterval=0x7FFFFFFFFFFFFFFE"
export JAVA_OPTS="$JAVA_OPTS -Djava.awt.headless=true"



export JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8"
export JAVA_OPTS="$JAVA_OPTS -DjvmRoute=$HTTPD_WORKER_NM"
export JAVA_OPTS="$JAVA_OPTS -Dtomcat.web.root=$WEB_ROOT"
export JAVA_OPTS="$JAVA_OPTS -Dtomcat.port.shutdown=$SHUTDOWN_PORT"
export JAVA_OPTS="$JAVA_OPTS -Dtomcat.port.http=$HTTP_PORT"
export JAVA_OPTS="$JAVA_OPTS -Dtomcat.port.https=$HTTPS_PORT"
export JAVA_OPTS="$JAVA_OPTS -Dtomcat.port.ajp=$AJP_PORT"

# WEB JK_MOD 정보
export HTTPD_HOSTS=( "192.0.0.100" )
export LB_WORKER=$HTTPD_LB_WORKER_NM
export WORKER=$HTTPD_WORKER_NM

#HTTPD_HOSTS_STR=""
for i in "${HTTPD_HOSTS[@]}"
do
        HTTPD_HOSTS_STR="${HTTPD_HOSTS_STR}${i},"
done

echo "============================================================="
echo "   Apache Tomcat 8.x                     ddakker@naver.com   "
echo "-------------------------------------------------------------"
$CATALINA_HOME/bin/./version.sh
echo "-------------------------------------------------------------"
echo "PROJECT_NM=$PROJECT_NM"
echo "WORKER_NM=$HTTPD_WORKER_NM"
echo "WEB_ROOT=$WEB_ROOT"
echo "SHUTDOWN_PORT=$SHUTDOWN_PORT"
echo "HTTP_PORT=$HTTP_PORT"
echo "HTTPS_PORT=$HTTPS_PORT"
echo "AJP_PORT=$AJP_PORT"
echo "HTTPD_HOSTS=$HTTPD_HOSTS_STR"
echo "============================================================="


[start.sh]

#!/bin/sh


. ./env.sh

# 임시 파일 삭제
rm -Rf $CATALINA_BASE/temp/*
# 로그 파일 관리
rm -Rf $CATALINA_BASE/logs/catalina.out


if [ "`ps -eaf | grep java | grep "$CATALINA_BASE "`" != "" ]; then
        echo "Tomcat SERVER - $PROJECT_NM is already RUNNING..."
        exit;
fi

if [ ! -d "$WEB_ROOT" ];
then
        mkdir -p $WAR_PATH
                mkdir -p $WEB_ROOT
                mkdir -p $WAR_PATH/../logs/$HTTPD_LB_WORKER_NM
fi


cd $CATALINA_HOME/bin

./startup.sh


if [ -z "`ps -eaf | grep java | grep "$CATALINA_BASE "`" ]; then
        echo "Error!!! Cannot start tomcat server."
fi


IS_WAIT=$1
IS_JK_CTL=$2

if [ "$IS_WAIT" == "wait" ]; then
        while [ `netstat -an | grep :$HTTP_PORT | grep LISTEN | wc | awk '{print $1}'` != 1 ]; do
                echo -ne "."
                sleep 1
        done

        echo "Check WAS Context Ready http://localhost:$HTTP_PORT ..."

        until [ "`curl --silent -o --show-error --connect-timeout 1 -w "%{http_code}\n" http://localhost:$HTTP_PORT | egrep '200|301|302|500'`" != "" ];
        do
                echo -ne "."
                sleep 1
        done
fi



if [ "$IS_JK_CTL" == "update" ]; then
        for HTTPD_HOST in "${HTTPD_HOSTS[@]}"
        do
                echo "<<< Turn-ON Worker $WORKER in $LB_WORKER [$HTTPD_HOST]"
                if [ "`curl --silent --show-error --connect-timeout 1 -I \"http://$HTTPD_HOST/jkstatus/?cmd=update&w=$LB_WORKER&sw=$WORKER&vwa=0\" | egrep '404|500|503'`" != "" ]
                then
                        echo "<<< Error"
                else
                        echo "<<< Done"
                fi
        done
fi


[shutdown.sh]

#!/bin/sh
 
 . ./env.sh

IS_WAIT=$1
IS_JK_CTL=$2
JK_CTL_AFTER_DELAY=$3
PID=`ps -ef | grep java | grep "$CATALINA_BASE " | awk '{print $2}'`

if [ e$PID == "e" ]
then
        echo "NOT RUNNING!!!"
        exit;
fi

if [ "$JK_CTL_AFTER_DELAY" == "" ]; then
        JK_CTL_AFTER_DELAY=0
fi

if [ "$IS_JK_CTL" == "update" ]; then
        for HTTPD_HOST in "${HTTPD_HOSTS[@]}"
        do
                echo "<<< Turn-OFF Worker $WORKER in $LB_WORKER [$HTTPD_HOST]"
                #if [ "`curl --silent --show-error --connect-timeout 1 -I \"http://$HTTPD_HOST/jkstatus/?cmd=update&w=$LB_WORKER&sw=$WORKER&vwa=1\" | egrep '403|404|500|503'`" != "" ]
                if [ "`curl --silent --show-error --connect-timeout 1 -I \"http://$HTTPD_HOST/jkstatus/?cmd=update&w=$LB_WORKER&sw=$WORKER&vwa=2\" | egrep '403|404|500|503'`" != "" ]
                then
                        echo "<<< Error"
                else
                        echo "<<< Done"
                fi
        done

        echo "+sleep $JK_CTL_AFTER_DELAY"
        sleep $JK_CTL_AFTER_DELAY
fi


$CATALINA_HOME/bin/./shutdown.sh
 
 
if [ "$IS_WAIT" == "wait" ]; then
        I=0
        until [ "`ps -eaf | grep java | grep "$CATALINA_BASE "`" == "" ];
        do
                if [ $I == 120 ]; then
                        ps -ef | grep java | grep "$CATALINA_BASE " | awk {'print "kill -9 " $2'} | bash -x
                        break;
                fi
 
                let I=$I+1
 
                echo -ne "."
                sleep 1
        done
fi


echo "*** Tomcat process ($PID) received SHUTDOWN signal ***" >> $CATALINA_BASE/logs/catalina.out


[setting.sh]

#!/bin/sh

. ./env.sh

if [ ! -d "$WEB_ROOT" ];
then
        mkdir -p $WAR_PATH
                mkdir -p $WEB_ROOT
                mkdir -p $WAR_PATH/../logs/$HTTPD_LB_WORKER_NM
fi


cd $CATALINA_BASE
find ./ -type d -exec chmod 750 {} \;
find ./ -type f -exec chmod 740 {} \;

chmod -Rf 640 $CATALINA_BASE/logs/*

cd $WEB_ROOT
find ./ -type d -exec chmod 755 {} \;
find ./ -type f -exec chmod 640 {} \;


if [ ! -e "$WEB_ROOT/index.html" ];
then
        echo "Hello World" >> $WEB_ROOT/index.html
fi
if [ ! -e "$WEB_ROOT/info.jsp" ];
then
        echo "<%@ page language=\"java\" contentType=\"text/html; charset=EUC-KR\" pageEncoding=\"EUC-KR\"%><%@page import=\"java.net.InetAddress\"%><%String serverIP = \"\";try{serverIP = InetAddress.getLocalHost().getHostAddress();serverIP = serverIP.substring(serverIP.lastIndexOf(\".\")+1);}catch(Exception e){}String info = request.getServerName() + \", \" + serverIP + \", \" + System.getProperty(\"jvmRoute\");out.print(info);// test%>" >> $WEB_ROOT/info.jsp
fi

if [ $LANG = "ko_KR.eucKR" ]; then
        cd $CATALINA_BASE
        FILE_ENC=`file -i env.sh `

        UTF8_IDX=`awk -v a="$FILE_ENC" -v b="utf-8" 'BEGIN{print index(a,b)}' `

        if [ $UTF8_IDX -gt 0 ]; then
                cd $CATALINA_BASE
                echo "----- encoding convertor -----"
                find . -name '*.sh' -exec iconv -f utf-8 -t euc-kr {} -o {} \;
        fi


[top.sh]

#!/bin/sh


. ./env.sh

PID=`ps -ef | grep java | grep "$CATALINA_BASE " | awk '{print $2}'`

if [ e$PID == "e" ]
then
        echo "NOT RUNNING!!!"
        exit;
fi

$CATALINA_BASE/../monitor_agent/./jvmtop.sh $PID


[dump.sh]

#!/bin/sh

. ./env.sh

for count in 1 2 3 4 5; do
    echo "Thread Dump : $count"
    for i in `ps -ef | grep java | grep "base=$CATALINA_BASE " | awk '{print $2}'`;do
        date
        #echo "+kill -3 $i"
        echo "+jstack -l $i >> $CATALINA_BASE/logs/thread_dump.$i"
        #kill -3 $i
        jstack -l $i >> $CATALINA_BASE/logs/thread_dump.$i
        #echo "sleep 1 sec"
        #sleep 1
    done
    echo "done"
    sleep 3
done


[dump_cpu.sh]

#!/bin/sh

. ./env.sh

for count in 1 2 3 4 5; do
    echo "Thread Dump : $count"
    for i in `ps -ef | grep java | grep "base=$CATALINA_BASE " | awk '{print $2}'`;do
        top -H -b -n1 >> $CATALINA_BASE/logs/dump_high_cpu_$i.txt
        date
        #kill -3 $i
        echo "+jstack -l $i >> $CATALINA_BASE/logs/dump_high_cpu_$i.txt"
        jstack -l $i >> $CATALINA_BASE/logs/dump_high_cpu_$i.txt
        echo "cpu snapshot and thread dump done. #"
    done
    echo "done"
    sleep 3
done


[httpd.sh]

#!/bin/sh


. ./env.sh



if [ "$1" == "stop" ]; then
        for HTTPD_HOST in "${HTTPD_HOSTS[@]}"
        do
                echo "<<< Turn-OFF Worker $WORKER in $LB_WORKER [$HTTPD_HOST]"
                if [ "`curl --silent --show-error --connect-timeout 1 -I \"http://$HTTPD_HOST/jkstatus/?cmd=update&w=$LB_WORKER&sw=$WORKER&vwa=2\" | egrep '403|404|500|503'`" != "" ]
                then
                        echo "<<< Error"
                else
                        echo "<<< Done"
                fi
        done
fi

if [ "$1" == "start" ]; then
        for HTTPD_HOST in "${HTTPD_HOSTS[@]}"
        do
                echo "<<< Turn-ON Worker $WORKER in $LB_WORKER [$HTTPD_HOST]"
                if [ "`curl --silent --show-error --connect-timeout 1 -I \"http://$HTTPD_HOST/jkstatus/?cmd=update&w=$LB_WORKER&sw=$WORKER&vwa=0\" | egrep '404|500|503'`" != "" ]
                then
                        echo "<<< Error"
                else
                        echo "<<< Done"
                fi
        done
fi


[server.xml]



  
  
  
  
  
  
  
  
    
    
    
    
 
      
        
      
      
                
        

      
    
  


+ Recent posts