Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- Runnable
- JCE
- ACAP
- DAMO
- 자바 암호화
- StringUtils
- 한글조사
- 암호학
- AES
- Postman
- mac
- Instrumentation
- String
- 한글조사처리
- Freemaker
- Java
- sha1
- RSA
- ORM
- Log4J
- Executors
- xlet
- 이클립스 플러그인 개발
- PKCS#8
- Callable
- IPTV
- PKCS
- Executor
- 자바
- date
Archives
- Today
- Total
오늘은 어디로 갈까...
스케줄러를 이용한 메일 발송 본문
봄을 재촉하는 비가 내리고 있다. 이런날을 커다란 창문이 있는 곳에 앉아, 하염없이 내리는 비를 바라보는게 즐거움일텐데, 어쩔 수 없이 모니터만 뚫어지게 바라보고 있다.
봄하면 생각나는게 spring~ 그렇다. PostMan을 spring화(?) 하자. 스프링을 사용하는김에 Persistant layer도 제대로 구현해보도록 하겠다. 여기서는 iBatis를 사용하겠다. (Spring Batch를 사용할까도 생각해봤지만, 구조를 설명하는게 좌절인거 같아서 간단한(?) spring만 사용하겠다.)
1. 관련 라이브러리
- 갑자기 라이브러리가 많이 필요해졌다. 대충 정리하면 아래와 같은데, 알아서 구해보시길. ^^;
- spring 2.5.6 (http://www.springsource.org/download)
- iBatis 2.3.4 (http://ibatis.apache.org/javadownloads.cgi)
- commons-dbcp 1.2.2 (http://commons.apache.org/dbcp/downloads.html)
- commons-pool 1.4 (http://commons.apache.org/pool/downloads.html)
- commons-collections(http://commons.apache.org/downloads/download_collections.cgi)
- JDBC Driver (http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/index.html)
2. Quartz Scheduler(http://www.opensymphony.com/quartz/)
- 원래 의도는 Timer를 사용해서 간단한 스케줄러 기능을 구현하려고 했지만, 멋(?)을 위해 Quartz Scheduler를 사용하도록 하겠다.
- Quartz는 다양한 시간간격에 의한 수행과 사용자 정의에 의한 수행을 모두 구현할 수 있다.
- spring에서는 quartz 연동 클래스를 제공해 준다.
- 발동(?) 클래스
+ org.springframework.scheduling.quartz.SimpleTriggerBean
: 반복 시간을 지정할수 있다.
+ org.springframework.scheduling.quartz.CronTriggerBean
: 다양한 시간간격을 지정할수 있다. (매일, 매주, 심시어 요일별로 지정할 수 있다)
표현식 -> http://quartz.sourceforge.net/javadoc/org/quartz/CronTrigger.html
- 실행시킬 작업(Job)을 정의하는 클래스
+ org.springframework.scheduling.quartz.JobDetailBean
: org.springframework.scheduling.quartz.QuartzJobBean을 상속받아 executeInternal(JobExecutionContext) 메소드를 구현하면 된다.
+ org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean
: 지정한 Bean의 대상 메소드를 실행시켜준다.
- 스케줄러를 지정해 주면, spring이 초기화되면서 자동으로 작동을 한다. triggers란 이름의 속성에 작동시킬 Trigger들을 넣어주면 된다.
3. DAO 수정하기
- 기존 SimpleMailDao랑, SimpleMailTemplateDao는 과감히 날려버리고 iBatis를 이용해서 새로이 만들겠다.
- MailDao의 getMailList()메소드와 MailTemplateDao의 getMailTemplateList()가 변경되었으니 유의바란다.
- 여기 샘플은 insert, update, delete, select 모두 구현되어 있는데, 코드 생성기로 구현한거라 그런것일뿐, 실제적으로는 아주 조금만(?) 필요하다.
- 지금에 와서야 고백(?)을 하지만 오라클의 자릿수를 지정하지 않은 Number형은 자바의 Long형의 범위를 벗어나서 BigDecimal이 맞는것이지만, 귀찮은 관계로 Long형을 이용했다. ^^;
4. SqlMap 만들기
- iBatis에서 사용할 sql을 만들어보자.
- 실제적으로 사용할 부분은 update 부분과 getList 부분이다. 상태코드에 맞는 데이터를 가져오고, 생태코드를 갱신하면 되는것이다. iBatis와 별로 안친하신분들을 JDBC를 직접 이용해서 구현하시면 되겠다.
- 원래 의도는 select for update를 이용해서 동일한 데몬이 동시에 여러개 실행될때 데이터를 어떻게 가지고 오는지도 설명해보려고 했으나, 오라클 종속(?)적이고, postman의 성격과는 별로 안 맞는거 같아서 넘어가도록 한다.
- getList만 사용한다. USE_FLAG가 "Y"인 놈만 가져오게 사용할것이다.
5. Service 수정하기
- DAO가 수정된 관계로 Manager 클래스들도 변경을 하도록 하겠다.
- DAO에서 데이터를 조회할때, 파라메터를 넘겨주는 부분이 추가되었고, update 하는 부분이 변경되었다.
- DAO에서 데이터를 조회할때, 파라메터를 넘겨주는 부분이 추가되었다.
6. 작업 클래스 만들기
- 기존에서는 PostMan 클래스에서 주기마다 반복하면서 작업을 처리했는데, 스케줄러를 도입했으므로, 단순히 작업만 하는 클래스를 만들어보겠다.
- 간단한 PostMan 클래스에서 주기 처리 부분이 빠졌다고 보면 되겠다.
- PostMan 클래스는 단순히 스프링 프레임워크의 ApplicationContext 를 생성하는 역할만을 한다.
7. XMl 만들하기
- 자, 이제 공포의 XML을 만들어보자.
- data-source-context.xml (데이터베이스 연결 관리, 트랜잭션관리등을 선언해놓았다.)
- ibatis-config.xml (iBatis 설정 정보이다.)
- post-man-context.xml
- post-man-scheduler.xml (quartz를 이용한 스케줄 설정 파일이다.)
- MethodInvokingJobDetailFactoryBean 를 이용해서 POJO인 PostManJob의 run()메소드를 실행시킨다.
- CronTriggerBean을 이용해서, 30초다(사실은 0초, 30초인 시점이지만 ^^;)마다 메일을 조회해서 발송한다.
- postman.properties (설정 정보)
8. 넋두리
- Spring, iBatis를 전혀 모르면, 이해하기 어려우실테지만, 뭐 그냥 Quartz란 스케줄러가 있다는 정도로 만족 하시면 되겠다.
- PostMan을 상당히 많이 우려먹고 있는데, 봄이니까~~~ 이해바란다. ^^; (사실 몇번 더 우려 먹어도 될거 같다.)
봄하면 생각나는게 spring~ 그렇다. PostMan을 spring화(?) 하자. 스프링을 사용하는김에 Persistant layer도 제대로 구현해보도록 하겠다. 여기서는 iBatis를 사용하겠다. (Spring Batch를 사용할까도 생각해봤지만, 구조를 설명하는게 좌절인거 같아서 간단한(?) spring만 사용하겠다.)
1. 관련 라이브러리
- 갑자기 라이브러리가 많이 필요해졌다. 대충 정리하면 아래와 같은데, 알아서 구해보시길. ^^;
- spring 2.5.6 (http://www.springsource.org/download)
- iBatis 2.3.4 (http://ibatis.apache.org/javadownloads.cgi)
- commons-dbcp 1.2.2 (http://commons.apache.org/dbcp/downloads.html)
- commons-pool 1.4 (http://commons.apache.org/pool/downloads.html)
- commons-collections(http://commons.apache.org/downloads/download_collections.cgi)
- JDBC Driver (http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/index.html)
2. Quartz Scheduler(http://www.opensymphony.com/quartz/)
- 원래 의도는 Timer를 사용해서 간단한 스케줄러 기능을 구현하려고 했지만, 멋(?)을 위해 Quartz Scheduler를 사용하도록 하겠다.
- Quartz는 다양한 시간간격에 의한 수행과 사용자 정의에 의한 수행을 모두 구현할 수 있다.
- spring에서는 quartz 연동 클래스를 제공해 준다.
- 발동(?) 클래스
+ org.springframework.scheduling.quartz.SimpleTriggerBean
: 반복 시간을 지정할수 있다.
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <property name="jobDetail" ref="jobDetail" /> <property name="startDelay" value="30000" /> <property name="repeatInterval" value="30000" /> </bean>
+ org.springframework.scheduling.quartz.CronTriggerBean
: 다양한 시간간격을 지정할수 있다. (매일, 매주, 심시어 요일별로 지정할 수 있다)
표현식 -> http://quartz.sourceforge.net/javadoc/org/quartz/CronTrigger.html
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="jobDetail" /> <property name="cronExpression" value="0/30 * * * * ?" /> </bean>
- 실행시킬 작업(Job)을 정의하는 클래스
+ org.springframework.scheduling.quartz.JobDetailBean
: org.springframework.scheduling.quartz.QuartzJobBean을 상속받아 executeInternal(JobExecutionContext) 메소드를 구현하면 된다.
<bean name="jobDetail" class="org.springframework.scheduling.quartz.JobDetailBean"> <property name="jobClass" value="kr.kangwoo.postman.XxxJob" /> <property name="jobDataAsMap"> <map> <entry key="timeout" value="30000" /> </map> </property> </bean>
+ org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean
: 지정한 Bean의 대상 메소드를 실행시켜준다.
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="postManJob" /> <property name="targetMethod" value="run" /> <property name="concurrent" value="false" /> <!-- job이 동시에 여러개 실행될지 여부를 지정하는것이다. --> </bean>
- 스케줄러를 지정해 주면, spring이 초기화되면서 자동으로 작동을 한다. triggers란 이름의 속성에 작동시킬 Trigger들을 넣어주면 된다.
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref local="simpleTrigger"/> <ref local="cronTrigger"/> </list> </property> </bean>
3. DAO 수정하기
- 기존 SimpleMailDao랑, SimpleMailTemplateDao는 과감히 날려버리고 iBatis를 이용해서 새로이 만들겠다.
- MailDao의 getMailList()메소드와 MailTemplateDao의 getMailTemplateList()가 변경되었으니 유의바란다.
- 여기 샘플은 insert, update, delete, select 모두 구현되어 있는데, 코드 생성기로 구현한거라 그런것일뿐, 실제적으로는 아주 조금만(?) 필요하다.
- 지금에 와서야 고백(?)을 하지만 오라클의 자릿수를 지정하지 않은 Number형은 자바의 Long형의 범위를 벗어나서 BigDecimal이 맞는것이지만, 귀찮은 관계로 Long형을 이용했다. ^^;
package kr.kangwoo.postman.repository; import java.util.List; import java.util.Map; import kr.kangwoo.postman.domain.Mail; public interface MailDao { void insert(Mail mail); int update(Mail mail); int updateSelective(Mail mail); int delete(Long mailNo); Mail getMail(Long mailNo); ListgetList(Map parameterMap); int count(Map parameterMap); }
package kr.kangwoo.postman.repository; import java.util.List; import java.util.Map; import kr.kangwoo.postman.domain.MailTemplate; public interface MailTemplateDao { void insert(MailTemplate mailTemplate); int update(MailTemplate mailTemplate); int updateSelective(MailTemplate mailTemplate); int delete(String templateId); MailTemplate getMailTemplate(String templateId); ListgetList(Map parameterMap); int count(Map parameterMap); }
package kr.kangwoo.postman.repository.ibatis; import java.util.List; import java.util.Map; import kr.kangwoo.postman.domain.Mail; import kr.kangwoo.postman.repository.MailDao; import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport; public class MailDaoSqlMap extends SqlMapClientDaoSupport implements MailDao { public void insert(Mail mail) { getSqlMapClientTemplate().insert("kr.kangwoo.postman.repository.MailDao.insert", mail); } public int update(Mail mail) { int rows = getSqlMapClientTemplate().update("kr.kangwoo.postman.repository.MailDao.update", mail); return rows; } public int updateSelective(Mail mail) { int rows = getSqlMapClientTemplate().update("kr.kangwoo.postman.repository.MailDao.updateSelective", mail); return rows; } public int delete(Long mailNo) { Mail key = new Mail(); key.setMailNo(mailNo); int rows = getSqlMapClientTemplate().delete("kr.kangwoo.postman.repository.MailDao.delete", key); return rows; } public Mail getMail(Long mailNo) { Mail key = new Mail(); key.setMailNo(mailNo); Mail record = (Mail) getSqlMapClientTemplate().queryForObject("kr.kangwoo.postman.repository.MailDao.getMail", key); return record; } @SuppressWarnings("unchecked") public List<Mail> getList(Map<String, Object> parameterMap) { List<Mail> records = getSqlMapClientTemplate().queryForList("kr.kangwoo.postman.repository.MailDao.getList", parameterMap); return records; } public int count(Map<String, Object> parameterMap) { Integer count = (Integer) getSqlMapClientTemplate().queryForObject("kr.kangwoo.postman.repository.MailDao.count", parameterMap); return count; } }
package kr.kangwoo.postman.repository.ibatis; import java.util.List; import java.util.Map; import kr.kangwoo.postman.domain.MailTemplate; import kr.kangwoo.postman.repository.MailTemplateDao; import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport; public class MailTemplateDaoSqlMap extends SqlMapClientDaoSupport implements MailTemplateDao { public void insert(MailTemplate mailTemplate) { getSqlMapClientTemplate().insert("kr.kangwoo.postman.repository.MailTemplateDao.insert", mailTemplate); } public int update(MailTemplate mailTemplate) { int rows = getSqlMapClientTemplate().update("kr.kangwoo.postman.repository.MailTemplateDao.update", mailTemplate); return rows; } public int updateSelective(MailTemplate mailTemplate) { int rows = getSqlMapClientTemplate().update("kr.kangwoo.postman.repository.MailTemplateDao.updateSelective", mailTemplate); return rows; } public int delete(String templateId) { MailTemplate key = new MailTemplate(); key.setTemplateId(templateId); int rows = getSqlMapClientTemplate().delete("kr.kangwoo.postman.repository.MailTemplateDao.delete", key); return rows; } public MailTemplate getMailTemplate(String templateId) { MailTemplate key = new MailTemplate(); key.setTemplateId(templateId); MailTemplate record = (MailTemplate) getSqlMapClientTemplate().queryForObject("kr.kangwoo.postman.repository.MailTemplateDao.getMailTemplate", key); return record; } @SuppressWarnings("unchecked") public List<MailTemplate> getList(Map<String, Object> parameterMap) { List<MailTemplate> records = getSqlMapClientTemplate().queryForList("kr.kangwoo.postman.repository.MailTemplateDao.getList", parameterMap); return records; } public int count(Map<String, Object> parameterMap) { Integer count = (Integer) getSqlMapClientTemplate().queryForObject("kr.kangwoo.postman.repository.MailTemplateDao.count", parameterMap); return count; } }
4. SqlMap 만들기
- iBatis에서 사용할 sql을 만들어보자.
- 실제적으로 사용할 부분은 update 부분과 getList 부분이다. 상태코드에 맞는 데이터를 가져오고, 생태코드를 갱신하면 되는것이다. iBatis와 별로 안친하신분들을 JDBC를 직접 이용해서 구현하시면 되겠다.
- 원래 의도는 select for update를 이용해서 동일한 데몬이 동시에 여러개 실행될때 데이터를 어떻게 가지고 오는지도 설명해보려고 했으나, 오라클 종속(?)적이고, postman의 성격과는 별로 안 맞는거 같아서 넘어가도록 한다.
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd" > <sqlMap namespace="kr.kangwoo.postman.repository.MailDao" > <resultMap id="Mail" class="kr.kangwoo.postman.domain.Mail" > <result column="MAIL_NO" property="mailNo" jdbcType="NUMBER" /> <result column="TEMPLATE_ID" property="templateId" jdbcType="CHAR" /> <result column="STATUS_CODE" property="statusCode" jdbcType="CHAR" /> <result column="TO_ADDRESS" property="toAddress" jdbcType="VARCHAR2" /> <result column="TO_NAME" property="toName" jdbcType="VARCHAR2" /> <result column="FROM_ADDRESS" property="fromAddress" jdbcType="VARCHAR2" /> <result column="FROM_NAME" property="fromName" jdbcType="VARCHAR2" /> <result column="SUBJECT_DATA" property="subjectData" jdbcType="VARCHAR2" /> <result column="COTENT_DATA" property="cotentData" jdbcType="VARCHAR2" /> <result column="SENT_BY" property="sentBy" jdbcType="VARCHAR2" /> <result column="SENT_DATE" property="sentDate" jdbcType="DATETIME" /> <result column="CREATED_BY" property="createdBy" jdbcType="VARCHAR2" /> <result column="CREATION_DATE" property="creationDate" jdbcType="DATETIME" /> <result column="UPDATED_BY" property="updatedBy" jdbcType="VARCHAR2" /> <result column="UPDATED_DATE" property="updatedDate" jdbcType="DATETIME" /> </resultMap> <insert id="insert" parameterClass="kr.kangwoo.postman.domain.Mail" > insert into MAIL (MAIL_NO, TEMPLATE_ID, STATUS_CODE, TO_ADDRESS, TO_NAME, FROM_ADDRESS, FROM_NAME, SUBJECT_DATA, COTENT_DATA, SENT_BY, SENT_DATE, CREATED_BY, CREATION_DATE, UPDATED_BY, UPDATED_DATE) values (#mailNo:NUMBER#, #templateId:CHAR#, #statusCode:CHAR#, #toAddress:VARCHAR2#, #toName:VARCHAR2#, #fromAddress:VARCHAR2#, #fromName:VARCHAR2#, #subjectData:VARCHAR2#, #cotentData:VARCHAR2#, #sentBy:VARCHAR2#, #sentDate:DATETIME#, #createdBy:VARCHAR2#, #creationDate:DATETIME#, #updatedBy:VARCHAR2#, #updatedDate:DATETIME#) </insert> <update id="update" parameterClass="kr.kangwoo.postman.domain.Mail" > update MAIL set TEMPLATE_ID = #templateId:CHAR#, STATUS_CODE = #statusCode:CHAR#, TO_ADDRESS = #toAddress:VARCHAR2#, TO_NAME = #toName:VARCHAR2#, FROM_ADDRESS = #fromAddress:VARCHAR2#, FROM_NAME = #fromName:VARCHAR2#, SUBJECT_DATA = #subjectData:VARCHAR2#, COTENT_DATA = #cotentData:VARCHAR2#, SENT_BY = #sentBy:VARCHAR2#, SENT_DATE = #sentDate:DATETIME#, CREATED_BY = #createdBy:VARCHAR2#, CREATION_DATE = #creationDate:DATETIME#, UPDATED_BY = #updatedBy:VARCHAR2#, UPDATED_DATE = #updatedDate:DATETIME# where MAIL_NO = #mailNo:NUMBER# </update> <update id="updateSelective" parameterClass="kr.kangwoo.postman.domain.Mail" > update MAIL <dynamic prepend="set" > <isNotNull prepend="," property="templateId" > TEMPLATE_ID = #templateId:CHAR# </isNotNull> <isNotNull prepend="," property="statusCode" > STATUS_CODE = #statusCode:CHAR# </isNotNull> <isNotNull prepend="," property="toAddress" > TO_ADDRESS = #toAddress:VARCHAR2# </isNotNull> <isNotNull prepend="," property="toName" > TO_NAME = #toName:VARCHAR2# </isNotNull> <isNotNull prepend="," property="fromAddress" > FROM_ADDRESS = #fromAddress:VARCHAR2# </isNotNull> <isNotNull prepend="," property="fromName" > FROM_NAME = #fromName:VARCHAR2# </isNotNull> <isNotNull prepend="," property="subjectData" > SUBJECT_DATA = #subjectData:VARCHAR2# </isNotNull> <isNotNull prepend="," property="cotentData" > COTENT_DATA = #cotentData:VARCHAR2# </isNotNull> <isNotNull prepend="," property="sentBy" > SENT_BY = #sentBy:VARCHAR2# </isNotNull> <isNotNull prepend="," property="sentDate" > SENT_DATE = #sentDate:DATETIME# </isNotNull> <isNotNull prepend="," property="createdBy" > CREATED_BY = #createdBy:VARCHAR2# </isNotNull> <isNotNull prepend="," property="creationDate" > CREATION_DATE = #creationDate:DATETIME# </isNotNull> <isNotNull prepend="," property="updatedBy" > UPDATED_BY = #updatedBy:VARCHAR2# </isNotNull> <isNotNull prepend="," property="updatedDate" > UPDATED_DATE = #updatedDate:DATETIME# </isNotNull> </dynamic> where MAIL_NO = #mailNo:NUMBER# </update> <delete id="delete" parameterClass="kr.kangwoo.postman.domain.Mail" > delete from MAIL where MAIL_NO = #mailNo:NUMBER# </delete> <select id="getMail" parameterClass="kr.kangwoo.postman.domain.Mail" resultMap="Mail" > select MAIL_NO, TEMPLATE_ID, STATUS_CODE, TO_ADDRESS, TO_NAME, FROM_ADDRESS, FROM_NAME, SUBJECT_DATA, COTENT_DATA, SENT_BY, SENT_DATE, CREATED_BY, CREATION_DATE, UPDATED_BY, UPDATED_DATE from MAIL where MAIL_NO = #mailNo:NUMBER# </select> <select id="getList" parameterClass="java.util.Map" resultMap="Mail" > select MAIL_NO, TEMPLATE_ID, STATUS_CODE, TO_ADDRESS, TO_NAME, FROM_ADDRESS, FROM_NAME, SUBJECT_DATA, COTENT_DATA, SENT_BY, SENT_DATE, CREATED_BY, CREATION_DATE, UPDATED_BY, UPDATED_DATE from MAIL <dynamic prepend="where" > <isNotNull prepend="and" property="statusCode" > STATUS_CODE = #statusCode:CHAR# </isNotNull> <isNotNull prepend="and" property="listSize" > ROWNUM <= #listSize:NUMBER# </isNotNull> </dynamic> <dynamic prepend="order by" > <isNotNull property="orderBy" > $orderBy$ </isNotNull> </dynamic> </select> <select id="count" parameterClass="java.util.Map" resultClass="java.lang.Integer" > select count(*) cnt from MAIL </select> </sqlMap>
- getList만 사용한다. USE_FLAG가 "Y"인 놈만 가져오게 사용할것이다.
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd" > <sqlMap namespace="kr.kangwoo.postman.repository.MailTemplateDao" > <resultMap id="MailTemplate" class="kr.kangwoo.postman.domain.MailTemplate" > <result column="TEMPLATE_ID" property="templateId" jdbcType="CHAR" /> <result column="TEMPLATE_DIR" property="templateDir" jdbcType="VARCHAR2" /> <result column="TEMPLATE_FILENAME" property="templateFilename" jdbcType="VARCHAR2" /> <result column="MAIL_SUBJECT" property="mailSubject" jdbcType="VARCHAR2" /> <result column="DATA_MODEL_TYPE" property="dataModelType" jdbcType="CHAR" /> <result column="USE_FLAG" property="useFlag" jdbcType="CHAR" /> <result column="CREATED_BY" property="createdBy" jdbcType="VARCHAR2" /> <result column="CREATION_DATE" property="creationDate" jdbcType="DATETIME" /> <result column="UPDATED_BY" property="updatedBy" jdbcType="VARCHAR2" /> <result column="UPDATED_DATE" property="updatedDate" jdbcType="DATETIME" /> </resultMap> <insert id="insert" parameterClass="kr.kangwoo.postman.domain.MailTemplate" > insert into MAIL_TEMPLATE (TEMPLATE_ID, TEMPLATE_DIR, TEMPLATE_FILENAME, MAIL_SUBJECT, DATA_MODEL_TYPE, USE_FLAG, CREATED_BY, CREATION_DATE, UPDATED_BY, UPDATED_DATE) values (#templateId:CHAR#, #templateDir:VARCHAR2#, #templateFilename:VARCHAR2#, #mailSubject:VARCHAR2#, #dataModelType:CHAR#, #useFlag:CHAR#, #createdBy:VARCHAR2#, #creationDate:DATETIME#, #updatedBy:VARCHAR2#, #updatedDate:DATETIME#) </insert> <update id="update" parameterClass="kr.kangwoo.postman.domain.MailTemplate" > update MAIL_TEMPLATE set TEMPLATE_DIR = #templateDir:VARCHAR2#, TEMPLATE_FILENAME = #templateFilename:VARCHAR2#, MAIL_SUBJECT = #mailSubject:VARCHAR2#, DATA_MODEL_TYPE = #dataModelType:CHAR#, USE_FLAG = #useFlag:CHAR#, CREATED_BY = #createdBy:VARCHAR2#, CREATION_DATE = #creationDate:DATETIME#, UPDATED_BY = #updatedBy:VARCHAR2#, UPDATED_DATE = #updatedDate:DATETIME# where TEMPLATE_ID = #templateId:CHAR# </update> <update id="updateSelective" parameterClass="kr.kangwoo.postman.domain.MailTemplate" > update MAIL_TEMPLATE <dynamic prepend="set" > <isNotNull prepend="," property="templateDir" > TEMPLATE_DIR = #templateDir:VARCHAR2# </isNotNull> <isNotNull prepend="," property="templateFilename" > TEMPLATE_FILENAME = #templateFilename:VARCHAR2# </isNotNull> <isNotNull prepend="," property="mailSubject" > MAIL_SUBJECT = #mailSubject:VARCHAR2# </isNotNull> <isNotNull prepend="," property="dataModelType" > DATA_MODEL_TYPE = #dataModelType:CHAR# </isNotNull> <isNotNull prepend="," property="useFlag" > USE_FLAG = #useFlag:CHAR# </isNotNull> <isNotNull prepend="," property="createdBy" > CREATED_BY = #createdBy:VARCHAR2# </isNotNull> <isNotNull prepend="," property="creationDate" > CREATION_DATE = #creationDate:DATETIME# </isNotNull> <isNotNull prepend="," property="updatedBy" > UPDATED_BY = #updatedBy:VARCHAR2# </isNotNull> <isNotNull prepend="," property="updatedDate" > UPDATED_DATE = #updatedDate:DATETIME# </isNotNull> </dynamic> where TEMPLATE_ID = #templateId:CHAR# </update> <delete id="delete" parameterClass="kr.kangwoo.postman.domain.MailTemplate" > delete from MAIL_TEMPLATE where TEMPLATE_ID = #templateId:CHAR# </delete> <select id="getMailTemplate" parameterClass="kr.kangwoo.postman.domain.MailTemplate" resultMap="MailTemplate" > select TEMPLATE_ID, TEMPLATE_DIR, TEMPLATE_FILENAME, MAIL_SUBJECT, DATA_MODEL_TYPE, USE_FLAG, CREATED_BY, CREATION_DATE, UPDATED_BY, UPDATED_DATE from MAIL_TEMPLATE where TEMPLATE_ID = #templateId:CHAR# </select> <select id="getList" parameterClass="java.util.Map" resultMap="MailTemplate" > select TEMPLATE_ID, TEMPLATE_DIR, TEMPLATE_FILENAME, MAIL_SUBJECT, DATA_MODEL_TYPE, USE_FLAG, CREATED_BY, CREATION_DATE, UPDATED_BY, UPDATED_DATE from MAIL_TEMPLATE <dynamic prepend="where" > <isNotNull prepend="," property="useFlag" > USE_FLAG = #useFlag:CHAR# </isNotNull> </dynamic> </select> <select id="count" parameterClass="java.util.Map" resultClass="java.lang.Integer" > select count(*) cnt from MAIL_TEMPLATE </select> </sqlMap>
5. Service 수정하기
- DAO가 수정된 관계로 Manager 클래스들도 변경을 하도록 하겠다.
- DAO에서 데이터를 조회할때, 파라메터를 넘겨주는 부분이 추가되었고, update 하는 부분이 변경되었다.
package kr.kangwoo.postman.service; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import kr.kangwoo.postman.core.MailStatusCode; import kr.kangwoo.postman.domain.Mail; import kr.kangwoo.postman.repository.MailDao; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SimpleMailManager implements MailManager { private Logger logger = LoggerFactory.getLogger(getClass()); private MailDao mailDao; public void setMailDao(MailDao mailDao) { this.mailDao = mailDao; } public List<Mail> getSendList(String daemonName) { Map<String, Object> parameterMap = new HashMap<String, Object>(); parameterMap.put("statusCode", MailStatusCode.CREATED); parameterMap.put("listSize", 2); parameterMap.put("orderBy", "MAIL_NO"); List<Mail> list = mailDao.getList(parameterMap); if (list != null) { // 상태값 변경 Date updatedDate = new Date(); for (Mail mail : list) { mail.setStatusCode(MailStatusCode.ACCEPTED); mail.setSentBy(daemonName); mail.setUpdatedBy(daemonName); mail.setUpdatedDate(updatedDate); Mail uMail = new Mail(); uMail.setMailNo(mail.getMailNo()); uMail.setStatusCode(mail.getStatusCode()); uMail.setSentBy(mail.getSentBy()); uMail.setUpdatedBy(mail.getUpdatedBy()); uMail.setUpdatedDate(mail.getUpdatedDate()); mailDao.updateSelective(uMail); uMail = null; } } return list; } public int updateMail(Mail mail) { return mailDao.updateSelective(mail); } }
- DAO에서 데이터를 조회할때, 파라메터를 넘겨주는 부분이 추가되었다.
package kr.kangwoo.postman.service; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import kr.kangwoo.postman.core.MailException; import kr.kangwoo.postman.core.MailStatusCode; import kr.kangwoo.postman.core.MessageException; import kr.kangwoo.postman.domain.Mail; import kr.kangwoo.postman.domain.MailTemplate; import kr.kangwoo.postman.repository.MailTemplateDao; import kr.kangwoo.util.MessageUtils; import kr.kangwoo.util.StrTokenizer; import kr.kangwoo.util.StringUtils; import org.xml.sax.InputSource; import freemarker.cache.FileTemplateLoader; import freemarker.cache.MultiTemplateLoader; import freemarker.cache.TemplateLoader; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; public class FreemarkerMailTemplateManager implements MailTemplateManager { private MailTemplateDao mailTemplateDao; private Map<String, MailTemplate> mailTemplateMap; private Configuration configuration; public FreemarkerMailTemplateManager() { } public void setMailTemplateDao(MailTemplateDao mailTemplateDao) { this.mailTemplateDao = mailTemplateDao; } public List<MailTemplate> getMailTemplateList() { Map<String, Object> paramMap = new HashMap<String, Object>(2); paramMap.put("useFlag", "Y"); return mailTemplateDao.getList(paramMap); } public void reload() { try { mailTemplateMap = new HashMap<String, MailTemplate>(); configuration = new Configuration(); List<MailTemplate> templateList = getMailTemplateList(); if (templateList != null) { List<TemplateLoader> loaderList = new ArrayList<TemplateLoader>(); Set<String> templateDirs = new HashSet<String>(); String dir = null; for (MailTemplate mailTemplate : templateList) { dir = mailTemplate.getTemplateDir(); if (StringUtils.isNotBlank(dir)) { mailTemplateMap.put(mailTemplate.getTemplateId(), mailTemplate); if (!templateDirs.contains(dir)) { // 중복 디렉토리 제거 loaderList.add(new FileTemplateLoader(new File(dir))); templateDirs.add(dir); } } else { throw new MessageException(MessageUtils.format("{0} 템플릿의 경로가 지정되어 있지 않습니다.", mailTemplate.getTemplateId())); } } TemplateLoader[] loaders = new TemplateLoader[loaderList.size()]; MultiTemplateLoader multiTemplateLoader = new MultiTemplateLoader(loaderList.toArray(loaders)); configuration.setTemplateLoader(multiTemplateLoader); } } catch (MessageException e) { throw e; } catch (Exception e) { throw new MessageException("메일 템플릿 정보를 가져오는 중 문제가 발생했습니다.", e); } } public String getSubject(Mail mail) throws MailException { // TODO 제목도 파라메터를 가져올 수 있게 수정할것. MailTemplate mailTemplate = mailTemplateMap.get(mail.getTemplateId()); if (mailTemplate == null) { throw new MailException(MailStatusCode.TEMPLATE_INFO_NOT_FOUND, MessageUtils.format("메일템플릿 정보가 존재하지 않습니다. (TEMPLATE_ID={0})", mail.getTemplateId())); } return mailTemplate.getMailSubject(); } public String getContent(Mail mail) throws MailException { MailTemplate mailTemplate = mailTemplateMap.get(mail.getTemplateId()); if (mailTemplate == null) { throw new MailException(MailStatusCode.TEMPLATE_INFO_NOT_FOUND, MessageUtils.format("메일템플릿 정보가 존재하지 않습니다. (TEMPLATE_ID={0})", mail.getTemplateId())); } Template template; try { template = configuration.getTemplate(mailTemplate.getTemplateFilename()); } catch (FileNotFoundException e) { throw new MailException(MailStatusCode.TEMPLATE_FILE_NOT_FOUND, MessageUtils.format("템플릿 파일을 가져올 수 없습니다.(TEMPLATE_ID={0}, TEMPLATE_DIR={1}, TEMPLATE_FILE={2})", mailTemplate.getTemplateId(), mailTemplate.getTemplateDir(), mailTemplate.getTemplateFilename())); } catch (IOException e) { throw new MailException(MailStatusCode.TEMPLATE_ERROR, MessageUtils.format("템플릿을 가져오는 중 에러가 발생했습니다.(TEMPLATE_ID={0}, TEMPLATE_DIR={1}, TEMPLATE_FILE={2})", mailTemplate.getTemplateId(), mailTemplate.getTemplateDir(), mailTemplate.getTemplateFilename())); } Map<String, Object> root = new HashMap<String, Object>(); String dataModelType = mailTemplate.getDataModelType(); if ("X".equals(dataModelType)) { // XML InputSource input = new InputSource(new StringReader(mail.getCotentData())); try { root.put("doc", freemarker.ext.dom.NodeModel.parse(input)); } catch (Exception e) { throw new MailException(MailStatusCode.DATA_MODEL_ERROR, MessageUtils.format("데이터 모델을 분석하는중 에러가 발생했습니다. (MAIL_NO={0}, TEMPLATE_ID={1})", mail.getMailNo(), mail.getTemplateId()), e); } } else if ("J".equals(dataModelType)) { // TODO JSON 구현 } else if ("P".equals(dataModelType)) { // Parameter StrTokenizer tokenizer = new StrTokenizer(mail.getCotentData(), "&"); String token = null; while (tokenizer.hasMoreTokens()) { token = tokenizer.nextToken(); root.put(StringUtils.substringBefore(token, "="), StringUtils.substringAfter(token, "=")); } } else { throw new MailException(MailStatusCode.UNKOWN_DATA_MODEL_TYPE, MessageUtils.format("{0}은 사용할 수 없는 데이터 모델 타입(DATA_MODE_TYPE)입니다. ", dataModelType)); } Writer out = new StringWriter(); try { template.process(root, out); } catch (IOException e) { throw new MailException(MailStatusCode.BAD_CONTENT, MessageUtils.format("메일 본문 내용을 작성하는 중 에러가 발생했습니다. (MAIL_NO={0}, TEMPLATE_ID={1})", mail.getMailNo(), mail.getTemplateId()), e); } catch (TemplateException e) { throw new MailException(MailStatusCode.BAD_CONTENT, MessageUtils.format("메일 본문 내용을 작성하는 중 에러가 발생했습니다. (MAIL_NO={0}, TEMPLATE_ID={1})", mail.getMailNo(), mail.getTemplateId()), e); } return out.toString(); } }
6. 작업 클래스 만들기
- 기존에서는 PostMan 클래스에서 주기마다 반복하면서 작업을 처리했는데, 스케줄러를 도입했으므로, 단순히 작업만 하는 클래스를 만들어보겠다.
- 간단한 PostMan 클래스에서 주기 처리 부분이 빠졌다고 보면 되겠다.
package kr.kangwoo.postman.core; import java.util.Date; import java.util.List; import kr.kangwoo.postman.domain.Mail; import kr.kangwoo.postman.service.MailManager; import kr.kangwoo.postman.service.MailSendManager; import kr.kangwoo.postman.service.MailTemplateManager; import kr.kangwoo.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PostManJob { private Logger logger = LoggerFactory.getLogger(PostManJob.class); private String daemonName = getClass().getName(); private MailManager mailManager; private MailTemplateManager mailTemplateManager; private MailSendManager mailSendManager; public void setDaemonName(String daemonName) { this.daemonName = daemonName; } public void setMailManager(MailManager mailManager) { this.mailManager = mailManager; } public void setMailTemplateManager(MailTemplateManager mailTemplateManager) { this.mailTemplateManager = mailTemplateManager; } public void setMailSendManager(MailSendManager mailSendManager) { this.mailSendManager = mailSendManager; } public void run() { try { logger.info("메일 템플릿 정보 적재 시작"); mailTemplateManager.reload(); logger.info("메일 템플릿 정보 적재 완료"); List<Mail> sendList = mailManager.getSendList(daemonName); if (logger.isDebugEnabled()) { logger.debug("{}개의 메일을 가져왔습니다.", sendList != null ? sendList.size() : 0); } if (sendList != null) { String subject = null; String content = null; for (Mail mail : sendList) { if (StringUtils.equals(mail.getStatusCode(), MailStatusCode.ACCEPTED)) { try { subject = mailTemplateManager.getSubject(mail); content = mailTemplateManager.getContent(mail); mail.setStatusCode(MailStatusCode.SEND_READY); mailSendManager.send(mail.getToAddress(), mail.getToName(), mail.getFromAddress(), mail.getFromName(), subject, content); mail.setStatusCode(MailStatusCode.SEND_OK); mail.setSentDate(new Date()); } catch (MailException e) { mail.setStatusCode(e.getStatusCode()); logger.warn("메일 발송 중 에러가 발생했습니다.", e); } catch (MessageException e) { mail.setStatusCode(MailStatusCode.UNKOWN_ERROR); logger.warn("메일 발송 중 에러가 발생했습니다.", e); } catch (Exception e) { mail.setStatusCode(MailStatusCode.UNKOWN_ERROR); logger.warn("메일 발송 중 에러가 발생했습니다.", e); } finally { mail.setUpdatedBy(daemonName); mail.setUpdatedDate(new Date()); mailManager.updateMail(mail); } } else { logger.warn("메일 상태 코드가 잘못되었습니다. (STATUS_CODE={})", mail.getStatusCode()); } } } } catch (Exception e) { logger.error(e.getMessage(), e); } } }
- PostMan 클래스는 단순히 스프링 프레임워크의 ApplicationContext 를 생성하는 역할만을 한다.
package kr.kangwoo.postman.core; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class PostMan { private Logger logger = LoggerFactory.getLogger(PostMan.class); public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[] {"post-man-scheduler.xml"}); } }
7. XMl 만들하기
- 자, 이제 공포의 XML을 만들어보자.
- data-source-context.xml (데이터베이스 연결 관리, 트랜잭션관리등을 선언해놓았다.)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="placeholderProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:postman.properties" /> </bean> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${batch.jdbc.driver}" /> <property name="url" value="${batch.jdbc.url}" /> <property name="username" value="${batch.jdbc.user}" /> <property name="password" value="${batch.jdbc.password}" /> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:ibatis-config.xml" /> </bean> <bean id="baseTransactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" lazy-init="true"> <property name="transactionManager"> <ref local="transactionManager" /> </property> <property name="transactionAttributes"> <props> <prop key="add*">PROPAGATION_REQUIRED</prop> <prop key="insert*">PROPAGATION_REQUIRED</prop> <prop key="update*">PROPAGATION_REQUIRED</prop> <prop key="delete*">PROPAGATION_REQUIRED</prop> <prop key="save*">PROPAGATION_REQUIRED</prop> <prop key="get*">PROPAGATION_SUPPORTS</prop> <prop key="getList*">PROPAGATION_SUPPORTS</prop> <prop key="count*">PROPAGATION_SUPPORTS</prop> <prop key="*">PROPAGATION_SUPPORTS</prop> </props> </property> </bean> </beans>
- ibatis-config.xml (iBatis 설정 정보이다.)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN" "http://www.ibatis.com/dtd/sql-map-config-2.dtd"> <sqlMapConfig> <settings useStatementNamespaces="true"/> <sqlMap resource="kr/kangwoo/postman/repository/ibatis/MailDaoSqlMap.xml" /> <sqlMap resource="kr/kangwoo/postman/repository/ibatis/MailTemplateDaoSqlMap.xml" /> </sqlMapConfig>
- post-man-context.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <!-- B:Data Access Object --> <bean id="mailDao" class="kr.kangwoo.postman.repository.ibatis.MailDaoSqlMap"> <property name="sqlMapClient"> <ref bean="sqlMapClient"/> </property> </bean> <bean id="mailTemplateDao" class="kr.kangwoo.postman.repository.ibatis.MailTemplateDaoSqlMap"> <property name="sqlMapClient"> <ref bean="sqlMapClient"/> </property> </bean> <!-- E:Data Access Object --> <!-- B:Service --> <bean id="mailManager" parent="baseTransactionProxy"> <property name="proxyInterfaces" value="kr.kangwoo.postman.service.MailManager" /> <property name="target"> <bean class="kr.kangwoo.postman.service.SimpleMailManager"> <property name="mailDao"> <ref bean="mailDao"/> </property> </bean> </property> </bean> <bean id="mailSendManager" parent="baseTransactionProxy"> <property name="proxyInterfaces" value="kr.kangwoo.postman.service.MailSendManager" /> <property name="target"> <bean class="kr.kangwoo.postman.service.SMTPMailSendManager"> <property name="host" value="${smtp.host}" /> <property name="port" value="${smtp.port}" /> <property name="starttlsEnable" value="${smtp.starttlsEnable}" /> <property name="userName" value="${smtp.userName}" /> <property name="password" value="${smtp.password}" /> </bean> </property> </bean> <bean id="mailTemplateManager" parent="baseTransactionProxy"> <property name="proxyInterfaces" value="kr.kangwoo.postman.service.MailTemplateManager" /> <property name="target"> <bean class="kr.kangwoo.postman.service.FreemarkerMailTemplateManager"> <property name="mailTemplateDao"> <ref bean="mailTemplateDao"/> </property> </bean> </property> </bean> <!-- E:Service --> </beans>
- post-man-scheduler.xml (quartz를 이용한 스케줄 설정 파일이다.)
- MethodInvokingJobDetailFactoryBean 를 이용해서 POJO인 PostManJob의 run()메소드를 실행시킨다.
- CronTriggerBean을 이용해서, 30초다(사실은 0초, 30초인 시점이지만 ^^;)마다 메일을 조회해서 발송한다.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <import resource="data-source-context.xml" /> <import resource="post-man-context.xml" /> <bean id="postManJob" class="kr.kangwoo.postman.core.PostManJob"> <property name="mailManager" ref="mailManager" /> <property name="mailTemplateManager" ref="mailTemplateManager" /> <property name="mailSendManager" ref="mailSendManager" /> </bean> <bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="postManJob" /> <property name="targetMethod" value="run" /> <property name="concurrent" value="false" /> </bean> <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="jobDetail" /> <property name="cronExpression" value="0/30 * * * * ?" /> </bean> </property> </bean> </beans>
- postman.properties (설정 정보)
#Database batch.jdbc.driver=oracle.jdbc.driver.OracleDriver batch.jdbc.url=jdbc:oracle:thin:@xxx.kangwoo.kr:1521:XE batch.jdbc.user=USER batch.jdbc.password=PWD #SMTP smtp.host=smtp.gmail.com smtp.port=587 smtp.starttlsEnable=true smtp.userName=xxx@mail.com smtp.password=pwd
8. 넋두리
- Spring, iBatis를 전혀 모르면, 이해하기 어려우실테지만, 뭐 그냥 Quartz란 스케줄러가 있다는 정도로 만족 하시면 되겠다.
- PostMan을 상당히 많이 우려먹고 있는데, 봄이니까~~~ 이해바란다. ^^; (사실 몇번 더 우려 먹어도 될거 같다.)