SpringSpring 4.0 연동 메모 모음 — JUnit · XML View · RabbitMQ · Redis · Batch · SiteMesh · Hadoop
2015 · 11 · 10
7 min read
페이퍼
2015년 한 해 동안 Spring 4.0 프로젝트에 이것저것 붙여보면서 여덟 번에 나눠 올렸던
설정 메모를 한 페이지로 합쳤다. 하나하나는 토막글이라 따로 두면 찾기가 더 힘들었다.
순서는 시간순이 아니라 쓸모순이다. 테스트 환경이 먼저 있어야 나머지를 확인할 수 있어서
JUnit 을 앞에 뒀다.
버전은 전부 2015년 기준이다. 지금 그대로 쓸 물건은 아니고, 그때 뭘 어떻게 붙였는지의 기록이다.
JUnit 테스트케이스
나머지 연동을 확인하려면 이게 먼저 있어야 한다.
1
2
3
4
5
6
| <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.0.5.RELEASE</version>
<scope>test</scope>
</dependency>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration( {
"classpath:servlet-context.xml",
"classpath:config/context-datasource.xml"
}
)
public class MemberServiceTest {
@Autowired
MemberService memberService;
@Test
public void testSr2002() throws Exception {
RequestData req = new RequestData(null, new DbMap());
ResponseData res = new ResponseData(new DbMap());
memberService.sr2002(req, res);
}
}
|
DB 를 jndi-lookup 으로 받는 경우가 문제다. 테스트에서는 JNDI 가 없다.
test/resources/config/context-datasource.xml 을 넣어서 기존 id 를 덮어버렸다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://server:3306/dbname"
p:username="sa"
p:password="" />
</beans>
|
같은 id 로 나중에 로드되는 쪽이 이긴다. 이 패턴은 아래 연동 테스트에서 계속 쓴다.
Map 을 XML 로 뱉는 ViewResolver
항상 JSON 으로만 뱉다가 XML 로 뱉어야 하는 상황이 생겨서 만든 view 클래스다.
결과가 Map 인 경우에만 해당된다.
applicationServlet.xml
1
2
3
4
| <beans:bean id="xmlView2" class="org.springframework.web.servlet.view.XmlViewResolver">
<beans:property name="order" value="1"/>
<beans:property name="location" value="classpath:xml-views.xml"/>
</beans:bean>
|
xml-views.xml
1
2
3
4
5
6
7
8
9
10
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="xmlView" class="com.xxxxx.view.AjaxResponseXMLView">
<property name="contentType">
<value>text/xml;charset=utf-8</value>
</property>
</bean>
</beans>
|
AjaxResponseXMLView.java
Map 과 List 를 재귀로 훑으면서 태그를 만든다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
| public class AjaxResponseXMLView extends AbstractView {
@Override
protected void renderMergedOutputModel(Map map, HttpServletRequest request,
HttpServletResponse response) throws Exception {
String xmlHeader = "\r\n";
StringBuffer xmlSb = new StringBuffer();
xmlSb.append(xmlHeader);
xmlSb.append("");
writeFromMap(xmlSb, map);
xmlSb.append("");
response.setContentType("application/xml");
response.setCharacterEncoding("utf-8");
response.setHeader("Cache-Control", "no-cache");
response.setContentLength(xmlSb.toString().getBytes("utf-8").length);
response.getWriter().print(xmlSb.toString());
}
private void writeFromMap(StringBuffer sb, Map map) {
for(Object str : map.keySet()) {
Object v = map.get(str);
sb.append("<" + str + ">");
if(v instanceof Map) {
writeFromMap(sb, (Map) v);
}
else if(v instanceof List) {
writeFromList(sb, (List) v);
}
else {
writeFromData(sb, v);
}
sb.append("</" + str + ">");
}
}
private void writeFromList(StringBuffer sb, List list) {
for(Object v : list) {
sb.append("");
if(v instanceof Map) {
writeFromMap(sb, (Map)v);
}
else if(v instanceof List) {
writeFromList(sb, (List) v);
}
else {
writeFromData(sb, v);
}
sb.append("");
}
}
private void writeFromData(StringBuffer sb, Object data) {
sb.append(escapeXml(data+""));
}
private String escapeXml(String src) {
src = src.replace("\"", """);
src = src.replace("<", "<");
src = src.replace(">", ">");
src = src.replace("&", "&");
return src;
}
}
|
RabbitMQ 연동
설치는 그냥 rpm 으로 했다.
1
2
3
4
5
| # 서버 시작.
sbin/rabbitmq-server start
# 서버 중지
sbin/rabbitmqctl stop
|
pom.xml
1
2
3
4
5
| <dependency>
<groupid>org.springframework.amqp</groupId>
<artifactid>spring-rabbit</artifactId>
<version>1.4.1.RELEASE</version>
</dependency>
|
context-rabbitmq.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <!-- A reference to the org.springframework.amqp.rabbit.connection.ConnectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="localhost" username="worker" password="workerpassword" />
<!-- Creates a org.springframework.amqp.rabbit.core.RabbitTemplate for access to the broker -->
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory" />
<!-- Creates a org.springframework.amqp.rabbit.core.RabbitAdmin to manage exchanges, queues and bindings -->
<rabbit:admin connection-factory="connectionFactory" />
<!-- Creates a queue for consumers to retrieve messages -->
<rabbit:queue name="simple_queue" />
<rabbit:listener-container connection-factory="connectionFactory">
<rabbit:listener queues="simple_queue" ref="mqService" />
</rabbit:listener-container>
|
MqService.java
보내는 쪽과 받는 쪽을 한 클래스에 넣었다. MessageListener 를 구현하면
위 listener-container 가 알아서 물어다 준다.
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
| @Service
public class MqService implements MessageListener {
private static final Logger logger = LoggerFactory.getLogger(MqService.class);
private static final String TASK_QUEUE_NAME = "simple_queue";
@Autowired
private RabbitTemplate rabbitTemplate;
public void send(String message) throws IOException {
rabbitTemplate.convertAndSend(TASK_QUEUE_NAME, message);
logger.info("send message={}", message);
}
@Override
public void onMessage(Message message) {
String msg = null;
try {
msg = new String(message.getBody(), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
logger.info("recv message=" + msg );
}
}
|
Redis 연동
pom.xml
1
2
3
4
5
6
7
8
9
10
| <dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.1.0</version>
</dependency>
|
버전을 잘 맞춰야 한다. 안 그러면 몇몇 class 가 없어서 오류가 난다.
context-redis.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<bean id="jedisConnFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:usePool="true"
p:hostName="172.xxx.xxx.xxx"
p:port="6379"
/>
<!-- redis template definition -->
<bean id="redisTemplate"
class="org.springframework.data.redis.core.RedisTemplate"
p:connectionFactory-ref="jedisConnFactory"
/>
</beans>
|
6379 가 redis 기본 포트다. 설치할 때 바꿀 수 있다.
RedisTest.java
ValueOperations 로 만료시간까지 같이 넣는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration( {
"classpath:spring/application-context.xml"
}
)
public class RedisTest {
private static final Logger logger = LoggerFactory.getLogger(RedisTest.class);
@Autowired
RedisTemplate<String, String> redisTemplate;
@Resource(name="redisTemplate")
private ValueOperations<String, ResultMap> valueOps;
@Test
public void testTp4110() throws Exception {
// redisTemplate.delete("1");
ResultMap res = valueOps.get("1");
if(res == null) {
logger.info("create.. cache..");
// create..
// 10분 캐시
valueOps.set("1", ResultMap.create(), 10, TimeUnit.MINUTES);
res = valueOps.get("1");
}
logger.info("redis-test={}", res);
res = valueOps.get("2");
logger.info("redis-test={}", res);
}
}
|
실행 결과다. 없는 키(“2”)는 null 로 온다.
1
2
3
4
5
| ...
[INFO ] 17:30:28.990 [main] - create.. cache..
[INFO ] 17:30:29.190 [main] - redis-test={result_code=0000, result_message=success}
[INFO ] 17:30:29.288 [main] - redis-test=null
...
|
Spring Batch
써봤는데 결과는 성공적이었다. 특히 트랜잭션 commit size 와 read size 를
따로 지정할 수 있다는게 좋은것 같다.
job 을 요약하면 이렇다.
reader 에서 데이터를 읽어서 process 에서 처리하고 writer 로 결과를 기록한다.
각 시작 구간마다 이벤트를 받는 listener 같은 것도 제공한다.
reader, writer 는 커스텀하지 않고 mybatis 가 기본으로 주는 걸 썼다.
(참고 https://mybatis.github.io/spring/ko/batch.html)
쿼리나 로직보다 아래 설정이 중요한 것 같아서 그것만 남긴다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| <bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean id="jobRepository"
class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
</bean>
<!-- sent type sent -->
<job:job id="rstJob" job-repository="jobRepository">
<job:step id="step1">
<tasklet>
<chunk reader="rstReader" processor="memberRstProcess"
writer="rstWriter" commit-interval="500">
</chunk>
</tasklet>
</job:step>
</job:job>
<bean id="memberRstProcess" class="com.xxxxx.MemberRstProcess" />
<bean id="rstReader"
class="org.mybatis.spring.batch.MyBatisPagingItemReader"
p:sqlSessionFactory-ref="sqlSessionFactory"
p:queryId="com.xxxxx.mapper.QueryMapper.selectMemberRstList"
p:pageSize="500"
scope="step" />
<bean id="rstWriter" class="org.mybatis.spring.batch.MyBatisBatchItemWriter">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
<property name="statementId" value="com.xxxxx.mapper.QueryMapper.updateMemberRst" />
</bean>
|
pageSize 가 읽는 단위, commit-interval 이 커밋 단위다. 둘이 따로 논다.
SiteMesh
pom.xml
1
2
3
4
5
| <dependency>
<groupId>opensymphony</groupId>
<artifactId>sitemesh</artifactId>
<version>2.4.2</version>
</dependency>
|
WEB-INF/web.xml
1
2
3
4
5
6
7
8
| <filter>
<filter-name>sitemesh</filter-name>
<filter-class>com.opensymphony.module.sitemesh.filter.PageFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sitemesh</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
|
WEB-INF/sitemesh.xml
이 파일은 수정할 부분이 거의 없다. decorators.xml 경로 정도.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| <?xml version="1.0" encoding="UTF-8"?>
<sitemesh>
<property name="decorators-file" value="/WEB-INF/decorators.xml" />
<excludes file="${decorators-file}" />
<page-parsers>
<parser content-type="text/html"
class="com.opensymphony.module.sitemesh.parser.HTMLPageParser" />
<parser content-type="text/html;charset=UTF-8"
class="com.opensymphony.module.sitemesh.parser.HTMLPageParser" />
</page-parsers>
<decorator-mappers>
<mapper class="com.opensymphony.module.sitemesh.mapper.PrintableDecoratorMapper">
<param name="decorator" value="printable" />
<param name="parameter.name" value="printable" />
<param name="parameter.value" value="true" />
</mapper>
<mapper class="com.opensymphony.module.sitemesh.mapper.PageDecoratorMapper" >
<param name="property" value="meta.decorator" />
</mapper>
<mapper class="com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper">
<param name="config" value="${decorators-file}" />
</mapper>
</decorator-mappers>
</sitemesh>
|
WEB-INF/decorators.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <?xml version="1.0" encoding="UTF-8"?>
<decorators defaultdir="/decorators">
<excludes>
<pattern>/*.json</pattern>
</excludes>
<decorator name="top" page="/views/layout/top.jsp" />
<decorator name="left" page="/views/layout/left.jsp" />
<decorator name="layout2" page="/views/layout/layout2.jsp">
<pattern>/login</pattern>
<pattern>/login_error</pattern>
</decorator>
<decorator name="layout" page="/views/layout/layout.jsp">
<pattern>/*</pattern>
</decorator>
</decorators>
|
설정은 XML 만 넣어주면 끝난다.
<excludes> 에는 decorator 를 적용하지 않을 URL 패턴을 넣는다<decorator> 는 실제 적용될 jsp 레이아웃이나 템플릿이다- name, page 로 구성되며 name 은
<page:applyDecorator name="top" /> 처럼
다른 decorator 에 적용될 수 있다 <pattern>/login</pattern> 은 decorator 를 적용할 URL 을 지정한다
레이아웃 만들기
기본 레이아웃인 layout.jsp 다. 실제로 쓰는 파일을 그대로 올린건 아니고 간단히 요약했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="decorator" uri="http://www.opensymphony.com/sitemesh/decorator"%>
<%@ taglib prefix="page" uri="http://www.opensymphony.com/sitemesh/page" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>sample</title>
<decorator:head/>
</head>
<body>
<div class="wrapper">
<header class="main-header">
<page:applyDecorator name="top" />
</header>
<!-- Left side column. contains the logo and sidebar -->
<aside class="main-sidebar">
<page:applyDecorator name="left" />
</aside>
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<decorator:body />
</div><!-- /.content-wrapper -->
<footer class="main-footer">
<div class="pull-right hidden-xs">
<b>Version</b> 0.0.1
</div>
<strong>Copyright © 2015 sample </strong> All rights reserved.
</footer>
</div>
</body>
</html>
|
<decorator:head /> 는 대상 페이지의 <head> 내용을 가져다 붙인다<page:applyDecorator name="top" /> 은 top decorator 를 가져와 붙인다.
include 라고 생각하면 이해가 빠르다<page:applyDecorator name="left" /> 는 left decorator<decorator:body /> 는 대상 페이지의 <body> 내용을 가져와 붙인다
실제 MVC 에서 쓰는 jsp 는 이렇게 생겼다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<head>
<!-- page script -->
<script>
....
</script>
</head>
<body>
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
데이타 관리
<small>버전관리</small>
</h1>
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> Home</a></li>
<li><a href="#">데이타관리</a></li>
<li class="active">버전관리</li>
</ol>
</section>
<!-- Main content -->
<section class="content">
<div class="row">
<div class="col-xs-12">
<div class="box">
<div class="box-header">
<h3 class="box-title">버전 목록</h3>
<a href="create"><button type="button" class="btn btn-primary btn-lg pull-right">신규 추가</button></a>
</div><!-- /.box-header -->
<div class="box-body">
<table id="list" class="table table-bordered table-hover">
</table>
</div><!-- /.box-body -->
</div><!-- /.box -->
</div><!-- /.col -->
</div><!-- /.row -->
</section><!-- /.content -->
</body>
|
이렇게 원하는 jsp 에 <head> 와 <body> 만 구성하면 layout.jsp 형식으로 출력된다.
Hadoop 연동
설치는 OSX 요세미티에 2.6.x 버전으로 했다.
pom.xml
1
2
3
4
5
| <dependency>
<groupid>org.springframework.data</groupId>
<artifactid>spring-data-hadoop</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
|
context-hadoop.xml
1
2
3
4
5
6
7
8
9
10
11
| <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:hdp="http://www.springframework.org/schema/hadoop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/hadoop http://www.springframework.org/schema/hadoop/spring-hadoop.xsd">
<hdp:configuration id="hdConf">
fs.default.name=hdfs://localhost:9000
</hdp:configuration>
</beans>
|
파일 읽고 쓰기 테스트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({
"classpath:servlet-context.xml",
"classpath:config/context-datasource.xml",
"classpath:config/context-hadoop.xml"
})
public class HdTestServiceTest {
private static final Logger logger = LoggerFactory.getLogger(HdTestService.class);
@Autowired
private org.apache.hadoop.conf.Configuration hdConf;
@Test
public void testDoTest() throws Exception {
FileSystem hdfs = null;
try {
Path filePath = new Path("/tmp/test.txt");
logger.info("filePath.uri={}", filePath.toUri());
hdfs = FileSystem.get(filePath.toUri(), hdConf);
if(hdfs.exists(filePath)) {
logger.info("read file path={}", filePath);
BufferedReader r = new BufferedReader(new InputStreamReader(hdfs.open(filePath), "utf-8"));
String line = null;
do {
line = r.readLine();
logger.info(" line={}", line);
}
while(line != null);
r.close();
// dfs.delete(filePath, true);
} else {
logger.info("create new file path={}", filePath);
FSDataOutputStream out = hdfs.create(filePath, false);
out.write("한글 생성 테스트".getBytes("utf-8"));
out.flush();
out.close();
}
}
finally {
IOUtils.closeQuietly(hdfs);
}
}
}
|
잘 된다. 다만 아직 로컬에서 못 벗어났다. 벗어날 서버가 없어..
이 코드면 파일 업로드 다운로드까지는 구현이 가능하다.
Hadoop MapReduce — WordCount
위 설정에 job 을 추가한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <hdp:configuration id="hdConf">
fs.default.name=hdfs://localhost:9000
</hdp:configuration>
<hdp:job id="wordCountJob"
input-path="/input/"
output-path="/output/"
configuration-ref="hdConf"
mapper="delim.app.service.WordCount$TokenizerMapper"
reducer="delim.app.service.WordCount$IntSumReducer"
>
</hdp:job>
<hdp:job-runner id="wordCountJobRunner" job-ref="wordCountJob" run-at-startup="false">
</hdp:job-runner>
|
WordCount.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.StringTokenizer;
public class WordCount {
private static final Logger logger = LoggerFactory.getLogger(WordCount.class);
public static class TokenizerMapper
extends Mapper<Object, Text, Text, IntWritable> {
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
@Override
public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
logger.info("map key={}, value={}", key, value);
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}
}
public static class IntSumReducer
extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
@Override
public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
logger.info("reduce key={}", key);
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
}
|
실행
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| @Autowired
private org.apache.hadoop.conf.Configuration hdConf;
@Autowired
private JobRunner wordCountJobRunner;
@Before
public void beforeCopyFile() throws IOException {
String file = "/Users/paper/Desktop/4/14/debug.2015-04-09.log";
Path srcFilePath = new Path(file);
Path dstFilePath = new Path("/input/debug.2015-04-09.log");
FileSystem hdfs = FileSystem.get(dstFilePath.toUri(), hdConf);
hdfs.copyFromLocalFile(false, true, srcFilePath, dstFilePath);
hdfs.delete(new Path("/output/"), true);
}
@Test
public void testRunJob() throws Exception {
wordCountJobRunner.call();
}
|
순서는 이렇다.
@Before 에서 로컬의 debug.log 를 hdfs 로 복사해둔다- job 을 실행한다
- 실행하면
debug.log 를 line 단위로 읽어들이는걸 확인할 수 있다 (WordCount$TokenizerMapper)
정리
- 연동 테스트는 JUnit 환경부터. JNDI 데이터소스는 테스트용 xml 로 같은 id 를 덮어쓴다
- RabbitMQ 는
listener-container 에 MessageListener 구현체를 물리면 끝 - Redis 는 jedis 와 spring-data-redis 버전을 맞춰야 한다. 안 맞으면 클래스가 없다고 터진다
- Spring Batch 는 read size(
pageSize)와 commit size(commit-interval)가 따로 논다 - SiteMesh 는 XML 세 개(web.xml, sitemesh.xml, decorators.xml)만 넣으면 된다
- Hadoop 은
hdp:configuration 하나로 FileSystem 을 주입받아 쓸 수 있다