'개발관련'에 해당되는 글 51건
- 2015.12.04 :: hibernate(3)
- 2015.12.04 :: hibernate(2)
- 2015.12.04 :: hibernate(1)
- 2015.11.30 :: aop(2)
- 2015.11.30 :: AOP(1)
- 2015.11.26 :: CI Jenkins 설치 및 github 연동(2)
- 2015.11.26 :: CI Jenkins 설치 및 github 연동(1)
- 2015.11.23 :: Lucene 활용 - search
- 2015.11.23 :: lucene 기초 -search
- 2015.11.20 :: mongodb mapreduce(2)
하나의 객체에 두개의 테이블 생성하기 를 해보자.
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 | package com.zest.hibernate.chapter2; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.SecondaryTable; import javax.persistence.Table; @Entity @Table(name="Customer") @SecondaryTable(name="CustomerDetail") public class Customer { @Id @GeneratedValue private int customerId; private String customerName; @Column(table="CustomerDetail") private String customerAddress; @Column(table="CustomerDetail") private int creditScore; @Column(table="CustomerDetail") private int rewardPoints; public int getCustomerId() { return customerId; } public void setCustomerId(int customerId) { this.customerId = customerId; } public String getCustomerName() { return customerName; } public void setCustomerName(String customerName) { this.customerName = customerName; } public String getCustomerAddress() { return customerAddress; } public void setCustomerAddress(String customerAddress) { this.customerAddress = customerAddress; } public int getCreditScore() { return creditScore; } public void setCreditScore(int creditScore) { this.creditScore = creditScore; } public int getRewardPoints() { return rewardPoints; } public void setRewardPoints(int rewardPoints) { this.rewardPoints = rewardPoints; } } | cs |
처음 보는 어노테이션이 등장한다. @SecondaryTable 이라는 넘이다. 기존 customer 말고 cumstomerDetail 이라는 테이블로 선언이 되었다. 이 객체의 재미난 점은 기본키는 둘다 같은 column을 사용한다는 것이다. 그리고 detail의 경우 cumstomerId를 기본키로 하면서 외래키를 가진다. 그리고 sencondaryTable에 추가할 컬럼은 @Column 어노테이션을 사용하여 추가한다.
아마도 1:1 대응 구조에 사용하면 좋을듯 보인다. 그럼 테스트 코드를 보자.
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 | package com.zest.hibernate.chapter2; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.AnnotationConfiguration; import org.hibernate.tool.hbm2ddl.SchemaExport; import org.junit.Test; public class CustomerTest { @Test public void customer1() { AnnotationConfiguration config = new AnnotationConfiguration(); config.addAnnotatedClass(Customer.class); config.configure("hibernate.cfg.xml"); new SchemaExport(config).create(true, true); // 아래 두개의 구문은 객체를 트랜잭션을 컨트롤. SessionFactory factory = config.buildSessionFactory(); Session session = factory.getCurrentSession(); session.beginTransaction(); Customer alex = new Customer(); alex.setCustomerName("Alex Rod"); alex.setCustomerAddress("101 washington"); alex.setCreditScore(780); alex.setRewardPoints(12000); session.save(alex); session.getTransaction().commit(); } } | cs |
사용법은 앞서 보여준 방법과 동일한데 결과 로그는 두개의 테이블에 넣는다.
1 2 3 | Hibernate: insert into TESTSCHEMA.Customer (customerName, customerId) values (?, ?) Hibernate: insert into TESTSCHEMA.CustomerDetail (creditScore, customerAddress, rewardPoints, customerId) values (?, ?, ?, ?) | cs |
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | package com.zest.hibernate.chapter1; import java.util.Calendar; import java.util.Date; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.TableGenerator; import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.Transient; @Entity @Table(name = "EmployeeInfo") public class Employee { @Id @Column(name = "EmployeeId", nullable = false) /** * sequence table을 생성. * name= sequence table의 alias 이름. * table은 sequence table의 이름. * pkColumnName 은 sequence table의 column이름. * pkColumnValue은 sequence table 값을 저장하는 이름.. * */ @TableGenerator(name = "empid", table = "emppktb", pkColumnName = "empkey" , pkColumnValue = "empvalue", allocationSize=1) @GeneratedValue(strategy = GenerationType.TABLE, generator = "empid") private int empId; private String empName; // db에 등록은 안되고 객체에서 db와 별도의 기능이 필요할 경우 사용되는 어노테이션. @Transient private String empPassword; @Column(nullable = false) private String empEmailAddress; @Basic private boolean isPermanent; @Temporal(TemporalType.DATE) private Calendar empJoinDate; // @Basic @Temporal(TemporalType.TIMESTAMP) private Date empLoginTime; public String getEmpPassword() { return empPassword; } public void setEmpPassword(String empPassword) { this.empPassword = empPassword; } public String getEmpEmailAddress() { return empEmailAddress; } public void setEmpEmailAddress(String empEmailAddress) { this.empEmailAddress = empEmailAddress; } // public boolean isPermanent() { return isPermanent; } public void setPermanent(boolean isPermanent) { this.isPermanent = isPermanent; } public Calendar getEmpJoinDate() { return empJoinDate; } public void setEmpJoinDate(Calendar empJoinDate) { this.empJoinDate = empJoinDate; } public Date getEmpLoginTime() { return empLoginTime; } public void setEmpLoginTime(Date empLoginTime) { this.empLoginTime = empLoginTime; } public int getEmpId() { return empId; } public void setEmpId(int empId) { this.empId = empId; } public String getEmpName() { return empName; } public void setEmpName(String empName) { this.empName = empName; } } | cs |
먼저 employee라는 클래스를 생성하자.
@Entity 라는 어노테이션이 등장한다. 이넘은 db의 table을 지징하는넘이다. nosql에선 collection정도?
@Table은 table의 이름을 바꾸고자 할 때 사용한다. 만약 이 어노테이션이 생략 되면 테이블 이름은 클래스 명과 동일하게 된다.
@TableGenerator는 Sequence Table을 정의할 때 사용한다. 옵션은 다음과 같다.
name= sequence table의 alias 이름.
table은 sequence table의 이름.
pkColumnName 은 sequence table의 column이름.
pkColumnValue은 sequence table 값을 저장하는 이름..
@GenerartedValue는 auto increment 의 옵션이다. 이때 위의 예제는 segence table의 값을 사용할 때의 예제 이다.
@Transient는 database의 table과 상관없이 employee 객체 안에서 다른 객체 또는 무언가 다른 작업을 하고 싶을 때 사용하는 옵션이다. 결국 db에 영향을 주지는 않는다.
@Basic은 lazy 관련 옵션이다.
@Temporal은 시간,날짜에 대한 옵션을 줄때 사용하는 어노테이션이다.
이제 실행해보자..
1 2 3 4 5 6 7 8 9 10 11 | @Test public void entityTest1(){ AnnotationConfiguration config = new AnnotationConfiguration(); config.addAnnotatedClass(Employee.class); config.configure("hibernate.cfg.xml"); new SchemaExport(config).create(true,true); } | cs |
아주 간단한 소스이다. 여기서 중요한 것은 create의 옵션이다. 첫번째 두번째 파라미터의 설명은
script print the DDL to the console, export export the script to the database
해석하자면 첫번째 것은 니가 실행하고자 하는거 콘솔에 찍을까? 두번째 것은 스크립트 실행할래? 이다. 두번째 것이 false로 되어있음 table이 생성되지 않는다. 그럼 로그를 살펴보자.
1 2 3 4 5 6 7 8 | INFO : org.hibernate.connection.DriverManagerConnectionProvider - connection properties: {user=user, password=****} drop table TESTSCHEMA.EmployeeInfo drop table TESTSCHEMA.emppktb create table TESTSCHEMA.EmployeeInfo (EmployeeId integer not null, empEmailAddress varchar(255) not null, empJoinDate date, empLoginTime timestamp, empName varchar(255), isPermanent smallint not null, primary key (EmployeeId)) create table TESTSCHEMA.emppktb ( empkey varchar(255), sequence_next_hi_value integer ) INFO : org.hibernate.tool.hbm2ddl.SchemaExport - schema export complete INFO : org.hibernate.connection.DriverManagerConnectionProvider - cleaning up connection pool: jdbc:derby://localhost:1527/hibernateDb | cs |
create 함수를 콜을 하면 기존의 테이블을 삭제하고 생성하는것이 보일것이다.
이제 데이터를 추가해보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @Test public void entityTest2(){ AnnotationConfiguration config = new AnnotationConfiguration(); config.addAnnotatedClass(Employee.class); config.configure("hibernate.cfg.xml"); // new SchemaExport(config).create(true,true); SessionFactory factory = config.buildSessionFactory(); Session session = factory.getCurrentSession(); session.beginTransaction(); Employee alex = new Employee(); alex.setEmpName("Alex Berry"); alex.setEmpEmailAddress("aaa@aa.com"); session.save(alex); session.getTransaction().commit(); } | cs |
세션을 한개 만들어서 트랜잭션을 시작하게 된다. 작업을 마치고 commit으로 종료를 하게 된다.
굉장히 심플하다. 아마도 jpa에서 이부분이 조금 변경 될 것이다. session 관련은 bean으로 뺄 것이며, 트랜잭션은 어노테이션 기반으로 처리 할것이고 repository 에서 save등등 sql문을 처리 할 것이다. 어쨋든 하이버네이트에선 위와 같이 처리하게 된다.
위의 junit테스트를 실행해보면 insert 구문이 나올 것이다.
empId의 경우는 sequence table에서 가져오므로 그냥 추가가 될것이다.
이번에는 create함수를 사용하고 모든 컬럼에 값을 넣어보자.
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 | @Test public void entityTest3(){ //hibernate의 persitst를 위한 클래스 AnnotationConfiguration config = new AnnotationConfiguration(); config.addAnnotatedClass(Employee.class); //db 접속 정보들을 저장. config.configure("hibernate.cfg.xml"); //db 접속후 여러개의 테이블을 자동으로 제너레이트 해주는 객체. //<property name="hibernate.default_schema">TESTSCHEMA</property> 이구문역시 마찬가지임. new SchemaExport(config).create(true,true); //아래 두개의 구문은 객체를 트랜잭션을 컨트롤. SessionFactory factory = config.buildSessionFactory(); Session session = factory.getCurrentSession(); session.beginTransaction(); Employee alex = new Employee(); // alex.setEmpId(2); alex.setEmpName("Alex Berry11"); alex.setEmpEmailAddress("alex@hibernate.com"); alex.setEmpPassword("alexpass"); alex.setEmpJoinDate(new GregorianCalendar(2009,05,26)); alex.setEmpLoginTime(Date.valueOf("2010-06-05")); session.save(alex); session.getTransaction().commit(); session.close(); } | cs |
기존 데이터가 삭제되고 한개만 추가 될것이다.
https://github.com/zest133/hibernateTest.git 소스는 다운받을 수 있다.
(출처:http://www.dil.univ-mrs.fr/~massat/docs/hibernate-3.1/reference/ko/html_single/)
하이버네이트의 기본 개념은 위와 같다.
orm(object relation mapping )기반 이며, spring data의 jpa 역시 내부적으로 hibernate를 감싸서 만들었다.
예제들은 아파치에서 무료로 제공하는 derby 를 사용한다. 사실 머 db는 상관없다.
먼저 pom파일을 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>3.4.0.GA</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>3.3.2.GA</version> </dependency> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derbyclient</artifactId> <version>10.12.1.1</version> </dependency> | cs |
hibernate를 추가하고 derby database client를 추가한다.
context.xml을 살펴보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="configLocation" > <value>hibernate.cfg.xml</value> </property> </bean> <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate"> <property name="sessionFactory"> <ref bean="sessionFactory" /> </property> </bean> </beans> | cs |
hibernate.cfg.xml이라는 설정파일이 보이고 hibernateTemplate이라는 객체가 보인다. 위와 같이 설정해주자.
다음은 hibernate.cfg.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 | <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="connection.driver_class">org.apache.derby.jdbc.ClientDriver</property> <property name="connection.url">jdbc:derby://localhost:1527/hibernateDb</property> <property name="connection.username">user</property> <property name="connection.password">password</property> <!-- SQL dialect --> <property name="dialect">org.hibernate.dialect.DerbyDialect</property> <property name="hibernate.default_schema">TESTSCHEMA</property> <!-- session을 사용하기 위한 정의 없으면 No CurrentSessionContext configured! 요에러 뜬다. --> <property name="current_session_context_class">thread</property> <!-- JDBC connection pool (use the built-in) --> <property name="connection.pool_size">2</property> <!-- Echo all executed SQL to stdout --> <property name="show_sql">true</property> <!-- Drop and re-create the database schema on startup <property name="hbm2ddl.auto">create</property>--> </session-factory> </hibernate-configuration> | cs |
여기서 중요한 건 dialect이다. 모든 db마다 dialect가 있고 아마도 저넘이 database마다 존재 하는 것으로 봐선 sql문으로 변환을 해주는 느낌이다. 그리고 마지막 부분의 hdm2ddl.auto 라는 넘이 있는데 저 구분을 활성화 하면 할때마다 기존 것들을 지우고 다시 만든다. derby는 설치하기 쉬우니 생략~
모든 설정이 끝났다. 다음 글에서 부터 예제를 만들어보자.
pom.xml은 바뀐게 없다. 저번에 설명을 못한 부분이 있는데 weaving이라는 것이다. 이부분은
http://najuung.tistory.com/65 이분의 사이트를 참조하라
바로 소스로 들어가자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd"> <context:annotation-config></context:annotation-config> <context:component-scan base-package="com.gno.sample"></context:component-scan> <aop:aspectj-autoproxy /> <bean id="simpleCalcService" class="com.gno.sample.service.SimpleCalcServiceImpl"></bean> <bean id="calcDao" class="com.gno.sample.dao.CalcDaoImpl"></bean> <bean id="loggingAspect" class="com.gno.sample.aop.SimpleAopAspect"></bean> </beans> | cs |
기존 aop:config에서 scan과 annotation-config, autoproxy 가 추가 되었다. 그리고 기존 pointcut과 expression등은 사라졌다. simpleAppAspect 클래스를 살펴보자.
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 | package com.gno.sample.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class SimpleAopAspect { @Before("execution(* com.gno.sample.dao.CalcDAO.*(..)) ") public void before(JoinPoint joinPoint) { System.out.println("before!"); Signature signature = joinPoint.getSignature(); Object target = joinPoint.getTarget(); Object[] args = joinPoint.getArgs(); System.out.println("Signature : " + signature.toString()); System.out.println("target : " + target.toString()); System.out.println("name : " + signature.getName()); System.out.println("longName : " + signature.toLongString()); System.out.println("shortName : " + signature.toShortString()); } @After("execution(* com.gno.sample.dao.CalcDAO.*(..))") public void after(JoinPoint joinPoint) { System.out.println("after!"); Signature signature = joinPoint.getSignature(); Object target = joinPoint.getTarget(); Object[] args = joinPoint.getArgs(); System.out.println("Signature : " + signature.toString()); System.out.println("target : " + target.toString()); System.out.println("name : " + signature.getName()); System.out.println("longName : " + signature.toLongString()); System.out.println("shortName : " + signature.toShortString()); } } | cs |
Aspect oriented Programming 관점 지향 프로그래밍... 이다.
(출처:http://ezquest.tistory.com/entry/Spring)
위 그림처럼 logging이라던가 security(login 관련) 또는 transaction 이라던가를 횡단으로 특정한 곳을 보는 관점 지향 하는 프로그래밍이다. 여기에 사족을 좀 덧 붙이자면, 관점 지향이라고 해서 무언가 autowired된 객체를 변환한다던가 또는 기존 객체를 변환한다던가는 하지 못한다. 단지. 내가 보고자 하는 부분에 대해 지정한 일을 한다는 것이다.
예를 들면, 이런 이런 객체에 로깅을 한다. 또는 exception에 대해 로그를 남긴다. 또는 전체 세션에 대해 로그인을 감지한다. 마지막으로 트랜잭션에 대해 처리를 한다. 이런 일들을 도맡아 한다. 멤캐쉬 redis캐쉬, ehcached 역시 aop로 처리를 한다.
그럼 바로 aop 관련 예제를 보자.
1 2 3 4 5 6 7 8 9 10 11 | <!-- Aspectj --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.11</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.11</version> </dependency> | cs |
pom.xml 설정은 위에 두가지를 선언한다.
다음은 context.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 29 30 31 32 33 34 35 36 37 38 39 | <?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd"> <bean id="simpleCalcService" class="com.gno.sample.service.SimpleCalcServiceImpl"></bean> <bean id="calcDao" class="com.gno.sample.dao.CalcDaoImpl"></bean> <bean id="loggingAspect" class="com.gno.sample.aop.SimpleAopAspectXML"></bean> <!-- aspect: 관점. 걍 aspect에 이용할 클래스로 라고 지정 advice: 어느 시점에 실행 될지 정하는 구분자 (before, after, throw, around) pointcut: 쳐다보고 있는 기준 함수 정의 expression: pointcut 에 입력하는 문법 (<리턴타입> <클래스>.<메소드><(매개변수)>) --> <aop:config> <aop:aspect id="aspect" ref="loggingAspect"> <aop:pointcut expression="execution(* com.gno.sample.dao.CalcDAO.*(..))" id="pointCut"/> <aop:before method="before" pointcut-ref="pointCut"/> <aop:after method="after" pointcut-ref="pointCut"/> </aop:aspect> </aop:config> </beans> | cs |
마지막 부분에 aop:config 라는 설정이 있다.
pointcut은 어느 객체를 횡단간 관심을 주냐는 거다. 이후 특정 메소드에 before,after, after-returning, after-throwing등 설정을 하여 pointcut의 id를 넘겨준다.
그리고 미리 설정해놓은 loggingAspect를 aop:config에 전달한다.
그럼 loggingAspect 소스를 보자.
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 | package com.gno.sample.aop; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import com.gno.sample.service.SimpleCalcService; //@ContextConfiguration(locations={"file:src/main/resources/aop-context.xml"}) @ContextConfiguration(locations={"file:src/main/resources/aop-context_xml.xml"}) @RunWith(SpringJUnit4ClassRunner.class) public class AopTest { @Autowired public SimpleCalcService simpleCalcService; @Test public void test(){ int returnVal = simpleCalcService.add(1, 1); assertThat(returnVal, is(2)); } } | cs |
프로젝트를 생성해보자.
새로운 item 을 클릭후 item 이름(프로젝트 이름)을 써주고 maven project를 체크 해주자.
그리고 ok 누르고 설정을 끝낸다.
이제 github에 프로젝트를 먼저 올려놔야 한다. 이부분은 생략 한다.
새로운 프로젝트(?)를 한개 생성했다. 이제 구성을 해보자. 만들어 놓은 item을 클릭 하고 왼쪽에 구성을 누르자.
github에 올려놓은 프로젝트의 url을 복사후 GitHub project에 url 주소를 설정한다. https://github.com/xxxx/xxxx 이런식으로 될것이다.
소스 코드 관리
우리는 github에서 소스를 가져오므로 git을 설정하고 소스의 url을 써준다. Repository URL에는 아래 그림과 같이 git에서 제공하는 git주소를 복사한 후 넣어준다.
만약 인증을 해야한다면 Credentials에 아이디와 패스워드를 입력하면 된다.
빌드유발 - Poll SCM 을 설정하고 나머지 설정(시간 설정)은 하지말자. 시간 설정 부분은 crontab 설정과 동일하니 찾아보길...
Build
Root POM 의 값은 pom.xml 있는 곳을 써야한다. 만약 현재 프로젝트가 test일 경우 하위 폴더에 pom.xml 있다면 pom.xml을 쓰면되지만 test/test1/pom.xml 이 있다면 test1/pom.xml을 써야한다.
Goals and options는 maven을 어떤 형태로 빌드 할 것인지 쓰는 부분이다. 보통은 clean install을 하겠지만, 여러명에서 개발을 하는 분이라면 아마도 resource 파일이 분리 되어있을 것이다. maven 옵션의 "-P프로필"을 써야한다. ex) clean install -P프로필
이번엔 프로젝트의 pom.xml을 설정 해야한다. -P프로필을 쓸려면 먼저 maven 설정 파일을 추가해야할 것이다.
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 | <!-- maven 빌드에 사용하는 java version 안쓰면 maven project가 compile 버전과 동일시 되게 설정 된다. 그러므로 다른 버전을 쓰고 싶으면 필히 넣어 놔야한다. --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.5.1</version> <configuration> <source>1.6</source> <target>1.6</target> <compilerArgument>-Xlint:all</compilerArgument> <showWarnings>true</showWarnings> <showDeprecation>true</showDeprecation> </configuration> </plugin> <!-- maven 자체에서 junit 테스트를 통해 결과를 xml로 변환해주는 빌드 plugin --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.18.1</version> <dependencies> <dependency> <groupId>org.apache.maven.surefire</groupId> <artifactId>surefire-junit47</artifactId> <version>2.18.1</version> </dependency> </dependencies> <configuration> </configuration> </plugin> <!-- maven내에서 war 를 묶기 위한 plugin --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.1.1</version> <configuration> <webResources> <resource> <!-- 어떤 resource 폴더 경로 --> <directory>src/main/resources-${environment}</directory> <!-- resource 파일내의 빌드 할 파일 타입 --> <includes> <include>*.properties</include> <include>*.xml</include> </includes> <filtering>true</filtering> </resource> </webResources> <!-- 빌드할 경로 --> <warSourceDirectory>src/main/webapp</warSourceDirectory> </configuration> </plugin> | cs |
위와 같이 maven 파일의 build tag안에 위에 문구를 넣어준다.
빌드 후 조치
우리는 war파일로 빌드 할 것이므로 먼저 tomcat의 tomcat-user.xml 열어보자.
1 2 | <role rolename="jenkins"/> <user username="admin" password="admin" roles="jenkins"/> | cs |
위와 같이 유저를 등록한다. 이후 다시 tomcat을 구동 후 jenkins 설정으로 돌아온다.
빌드 후 조치 추가 버튼을 누르면 Deploy war/ear to a container 라는 문구를 클릭한다.
tomcat 주소 및 아이디 패스워드를 등록하고 war/ear files 에 **/*war라고 써준다.
context path는 빌드 할 프로젝트의 context path를 등록해주면 된다. (webapp의 context path)
그리고 저장한다. 여기 까지 jenkins의 셋팅이 끝났다.
다시 정리 해보면
1. eclipse에서 github으로 commit/push를 한다.
2. github에서 hook를 통해 jenkins로 전달 한다.
3. jenkins에서 빌드되며, 빌드 후 조치로 미리 설치된 tomcat was에 빌드가 될것이다.
4. 설치된 tomcat was의 context path로 접근하면 된다.
위 작업 대로 되는지 보자..
커밋을 하면 아무것도 안될 것이다.....=_=;
위 2번째 항목을 안한 것이다. github에 hook을 설정하자.
github의 빌드할 프로젝트의 오른쪽에 settings를 클릭한다.
위와 같은 그림이 나올것이다. 왼쪽의 webhook & services를 클릭 후 위와 같은 화면이 나온다.
Available Services에 jenkins라고 입력하면 두가지가 나온다 이때 git plugin을 선택한다. (github plugin은 안되더라구여;;; )
여기에 jenkins url을 써주자. 내부 아이피는 불가하니 필히 외부 아이피로 등록해야한다.
필자의 경우 http://xxx.xxx.xxx.xxx/jenkins 이다. 앞서 설명한 jenkins 시스템 설정의 url과 동일하다. 그러니 둘다 외부 아이피로 등록해줘야한다.
이제 위에 순서대로 될것이다.
젠킨스는 아래 그림으로 설명이 된다.
(출처 : http://www.ranorex.com/blog/integrating-ranorex-automation-in-jenkins-continuous-integration-process)
머 대략 개발자는 svn또는 git에 commit을 하고 이를 캐치한 jenkins는 빌드를 진행한다. 이때 각종 코드며, 테스트며 jenkins가 알아서 해주며 이를 개발자 또는 테스터가 바로 볼수가 있다.
그럼 설치 gogogogo~
설치 자체는 간단하다. https://jenkins-ci.org/ 이 사이트 들어가서 오른쪽 상단의 war를 받자. war파일은 내부에 jetty로 구현 됬으므로 실행하면 되고 또는 tomcat 안에 webapp에 넣고 실행하면 된다.
스케쥴 설정에 따라 일정 주기마다 빌드 될수도 있고 소스에 업데이트가 일어났을때를 감지해서 자동으로 빌드 될수도 있다.
여러 빌드 도구를 지원하고 설치한 빌드 도구의 경로를 젠킨스 설정에 등록만 하면 사용 가능 하다.
빌드 결과를 기록하고 빌드 된 시점의 소스 버전 관리를 해준다.
위의 그림에서 필요한 것들은 tomcat, jdk, git 저장소 , Maven 이 필요하다. 이것들 설치는 생략한다. (현재 centos 6.x에 설치)
그럼 환경 구성 시작.
jenkins를 구동하면 왼쪽에 jenkins 관리 가 있다. 이후 플러그인 관리로 들어가자. 친절하게 한글이다.
디렉토리 설정
디렉토리는 알아서 설정이 되있다. 젠킨스 자체의 설정파일, 데이터 등이 저장되는경로를 등록한다
JDK 설정
name은 jenkins의 환경변수로 인식할 이름을 써주고, JAVA_HOME은 os상에 설치된 경로를 써주면 된다.
Git 설정
intall automatically를 설정하면 아마도 root 권한으로 jenkins를 깔아야 되는걸로 알고있다. 그러므로 필자는 root에 먼저 yum을 이용해 설치했다.
이후 whereis git 으로 경로를 찾은후 name과 git 경로를 설정한다. jdk 비슷하다.
Maven 설정
maven 도 마찬가지로 이름과 경로를 써주면 끝난다.
나머진 default로 둔다. (sonargube는 나중에 다시) 그리고, 저장.
이제 jenkins의 환경 설정이 끝났다.
개인적으로 Lucene을 사용하다보면 검색 쿼리를 객체로 컨트롤 하기가 귀찮아 진다. 여기서 객체란 BooleanQuery, TermQuery, FuzzyQuery, WildCardQuery등등 있다. 이런것들을 이용하여 쿼리를 만들 수 있으나 String 형태로 쿼리를 만들 수도 있다. 이번엔 그런 것들을 해보자.
먼저 Lucene도 3가지 옵션을 제공한다. Must, Should, MustNot 이 옵션 세가지를 제공한다.
1. Must - And와 같다.
2. Should - or와 같다.
3. Must Not - Not And와 같다.
그리고, 쿼리는 다음과 같은 형태로 변환이 된다.
(+text:cross) ((html:good) (+html:boat)) 이런식으로 쿼리가 변환이 된다. 이걸 다시 풀어보면..
text필드의 cross를 And 검색하라.
html필드를 good을 or로 검색하고 html에 boat는 and로 검색하라. 그리고 이전체를 or로 검색하라 . 라는 결론이 된다.
물론 단일 term의 경우 and나 or나 결과는 같다. 하지만 ((html:good) (+html:boat)) 이 구문의 경우 (+html:good +html:boat) 이런 형태로도 나올 것이다. 일종의 sql의 where과 흡사하다.
위에 예제와 같이 여러개의 쿼리를 필요 할때는 BooleanQuery를 루씬에서 제공한다. 그럼 만들어보자.
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 | //생략////////////////////////////////////////// public String tempAndAnalyze(String string[], String field, Analyzer analyzer) throws IOException { StringBuffer buffer = new StringBuffer(); buffer.append("("); for(String str : string){ buffer.append("+"); // buffer.append(field); // buffer.append(":"); buffer.append(str).append(" "); } buffer.append(")"); return buffer.toString(); } @Test public void testHtmlBasicSearchData2() { String andWordSearch = "cross sectional"; if (andWordSearch != null && andWordSearch != "") { try { // searchIndex(andWordSearch, textField); String queryStr = tempAndAnalyze(andWordSearch.split(" "), "text"); Query andQuery = new QueryParser(Version.LUCENE_36, "text", new StandardAnalyzer(Version.LUCENE_36)).parse(queryStr); // #B TopDocs hits = searcher.search(andQuery, searcher.maxDoc()); dumpHits(searcher, hits, "text"); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } | cs |
위에 설명한 대로 cross sectional 쿼리를 String으로 만들고 그것을 다시 stringbuffer에 저장해서 QueryParser로 전달했다. 갠적으론 이방법을 많이 쓴다.
여기서 tip 한개더. 루씬은 stanardAnalyzer를 space+stop word(내부에 저장) 를 기반으로 단어를 나눈다. 이때 중요한 점은 lowcase는 적용을 안한다는 사실이다. 이 때문에 standard를 쓸 시 필히 대소문자를 구분해야한다.
or문의 경우 tempAnalyze의 경우 +를 삭제 해주면 된다. 다음에는 다중 검색에 대해 알아보자.
루씬은 색인과 검색 두가지로 나뉜다. 기존 색인을 검색 할 때도 쓰이지만, 특정 문장을 검증 할때도 쓰인다.
루씬의 version은 3.6 기준으로 작성하였다.
먼저 검색은 두가지 객체가 중요하다. IndexReader와 IndexSearcher 여기서 reader는 특정 색인의 접근을 해주는 객체이며, searcher는 그 색인을 찾고자 하는 검색 룰을 전달해주는 넘이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | private IndexSearcher searcher; private Directory dir; private IndexReader reader; private String textField; @Before public void setup() throws IOException { textField = "text"; dir = FSDirectory.open(new File(dirPath)); reader = IndexReader.open(dir); searcher = new IndexSearcher(reader); } @After public void tearDown() throws IOException { searcher.close(); reader.close(); } | cs |
먼저 초기 셋팅을 해준다. 기존에 만들어 놓은 index경로는 dirPath가 될 것이다. 검색 필드는 "text"로 지정한다.
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 | private ArrayList<Document> dumpHits(IndexSearcher searcher, TopDocs hits, String fieldName) throws IOException { ArrayList<Document> docList = null; if (hits.totalHits == 0) { return null; } docList = new ArrayList<Document>(); for (ScoreDoc match : hits.scoreDocs) { Document doc = searcher.doc(match.doc); } return docList; } public void searchIndex(String searchString, String field) throws ParseException, IOException { System.out.println("\nSearching for '" + searchString + "' using QueryParser"); QueryParser queryParser = new QueryParser(Version.LUCENE_36, field, new StandardAnalyzer(Version.LUCENE_36)); Query query = queryParser.parse(searchString); System.out.println("Type of query: " + query.getClass().getSimpleName()); TopDocs hits = searcher.search(query, searcher.maxDoc()); dumpHits(searcher, hits, field); } @Test public void testHtmlBasicSearchData() { String andWordSearch = "cross"; if (andWordSearch != null && andWordSearch != "") { try { searchIndex(andWordSearch, textField); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } | cs |
가장 기본인 검색이다... (개인적으로 이방법은 안쓴다.. =_=; 찾는것도 힘드네 ㅋㅋ)
여기서 중요한 함수는 searchIndex라는 함수다.
중요한 객체는 총 3가지 , QueryParser와 Query, TopDocs 이 객체만 알면 Lucene을 가지고 분석을 자유롭게 할 수 있을 것이다. 먼저 한개씩 설명하자면,
1. QueryParser : 입력한 Query문을 Lucene이 알아먹을 수 있는 Query로 변환한다. 쉽게 말하자면 당신이 넣은 쿼리를 선택한 분석기에 맞게 쿼리 객체 형태로 반환한다.
2. Query : QueryParser로 변환된 쿼리 객체를 searcher 객체로 전달한다. (Query의 종류는 상당히 많으며, 최상위 객체가 Query이다. 종류는 TermQuery, BooleanQuery, Fuzzy, WildCard 등등 있다)
3. TocDocs : 검색된 결과를 보여준다.
위의 결과를 돌려보면
1 2 3 4 5 | Searching for 'cross' using QueryParser Type of query: TermQuery | cs |
이런 형태가 나오고 dubugging모드로 Query를 보면 text:cross 란 쿼리로 변환이 될것이다.
아마도 default Query는 TermQuery 이며, 객체를 통해 Lucene이 알아먹을 수 있는 형태의 쿼리로 변환 되는 듯 보인다.
이후 좀더 복잡한 쿼리에 대해 알아보겠다.
먼저 collections를 확인해보자.
1 2 3 4 5 6 7 | show collections //result testReduce users | cs |
show collections를 하면 결과가 나온다. 앞서 만든 mapreduce에 사용한 testReduce와 데이타를 저장한 users가 존재한다.
그럼 system.js를 만들어보자. 앞서 만들어놓은 함수들을 어딘가 복사해놓자.
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 | db.system.js.insert( [ { _id: "map", value : function () { var key = {}; key.eyeColor = this.eyeColor; key.gender = this.gender; var value = {}; value.age = this.age; value.count = 1; emit(key, value); } }, { _id: "reduce", value : function (key, values){ var returnVal = {}; var totalAge =0; var totalCount = 0; for( index in values){ totalAge += values[index].age; totalCount += values[index].count; } return {"age":totalAge , "count" : totalCount}; } }, { _id : "finalize", value : function(key, values){ return { "age" : values.age, "count" : values.count, "avg" : values.age/values.count }; } } ] ); | cs |
1 2 3 4 5 6 | system.indexes system.js testReduce users | cs |
system.js 라는 collections과 indexes라는넘이 추가되었다.
이제 mapReduce를 하기전에 마지막으로 위에 값들을 load 를 해야한다.
1 2 | db.loadServerScripts(); | cs |
위에 구문을 쓰면 몽고 디비의 서버에 맵리듀스 함수가 로드 된것이다.
그럼 map,reduce, finalize 라는 _id의 값은 어떻게 되있는지 확인하자
map; reduce; finalize; 라는 명령으로 확인하면 다음과 같은 결과를 확인 할 수 있다.
아래는 map; 의 함수를 보여주고있다.
1 2 3 4 5 6 7 8 9 10 | function __cf__13__f__anonymous_function() { var key = {}; key.eyeColor = this.eyeColor; key.gender = this.gender; var value = {}; value.age = this.age; value.count = 1; emit(key, value); } | cs |
이제 다시한번 mapreduce를 하는데 이번엔 out의 옵션을 좀 추가해보자.
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 | db.users.mapReduce( map,reduce, { out : { reduce : "testReduce" }, //map들어갈 collection 필터링 할때 query : { "index" : { $gte : 1000 } }, //가령 추천수가 가장 많은 사람을 쓸때 오름 차순으로 하면 제일 먼저 나오는 사람이 젤위이므로 이 사람을 빨리 가져올때 사용. //sort : {}, //reduce 를 한 결과를 가공을 할때 사용. finalize : finalize, //scope는 map, reduce, finalize 함수의 전역 변수로 사용된다. //사용시는 $변수명으로 사용하면된다. //scope: { "temp" : 1}, //몽고디비는 bson 타입으로 저장을한다. 이때 속도가 좀 걸리는데 json 형태로 할것인지를 선택하는 옵션이다. //그러나 json의 경우 속도는 빠르나 collection document의 제한이 걸린다. 그래서 안쓰는게 상책. //jsMode: <boolean>, //verbose: <boolean> } ); | cs |
out의 옵션에 reduce를 주고 "testReduce" collection명을 주었다. 실행하면 결과는 testReduce로 저장될 것이다. 이제
db.testReduce.find()로 값을 확인하자.
아까 값에 reduce 된 값이 보일것이다.
후 여기까지.....