Web/JPA 2018. 10. 14. 19:24

JPA는 쿼리가 너무 정적이라는 단점이 존재합니다. 이러한 단점을 극복하기 위하여 JAP+QueryDSL을 통해 type-safe하고, 자바의 메소드를 통한 동적인(유연한) 쿼리 작성이 가능해집니다.


이전 글에서 JPA사용법에 대하여 간단히 작성했지만 이번 글에서도 JPA부터 설명하겠습니다.



pom.xml 설정


JPA 및 querydsl 등 각종 dependency 설정입니다.



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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.spring</groupId>
    <artifactId>jpa</artifactId>
    <name>springjpa</name>
    <packaging>war</packaging>
    <version>1.0.0-BUILD-SNAPSHOT</version>
    <properties>
        <java-version>1.8</java-version>
        <org.springframework-version>5.0.2.RELEASE</org.springframework-version>
        <org.aspectj-version>1.6.10</org.aspectj-version>
        <org.slf4j-version>1.6.6</org.slf4j-version>
<!--querydsl version 명시-->
        <querydsl.version>4.1.4</querydsl.version>
    </properties>
    <!-- 오라클 저장소 -->
    <repositories>
        <repository>
            <id>codelds</id>
            <url>https://code.lds.org/nexus/content/groups/main-repo</url>
        </repository>
    </repositories>
    <dependencies>
<!--querydsl dependency-->
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <version>${querydsl.version}</version>
            <scope>provided</scope>
        </dependency>
 
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <version>${querydsl.version}</version>
        </dependency>
 
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.9.0</version>
        </dependency>
<!--JPA dependency-->
        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.1.11.Final</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>2.0.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
        <!-- <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> 
            <version>${org.springframework-version}</version> </dependency> -->
 
        <!-- oracle -->
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <version>11.2.0.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ucp</artifactId>
            <version>11.2.0.3</version>
            <scope>compile</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.0</version>
        </dependency>
 
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework-version}</version>
            <exclusions>
                <!-- Exclude Commons Logging in favor of SLF4j -->
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>
 
        <!-- AspectJ -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${org.aspectj-version}</version>
        </dependency>
 
        <!-- Logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${org.slf4j-version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.15</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.mail</groupId>
                    <artifactId>mail</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>javax.jms</groupId>
                    <artifactId>jms</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jdmk</groupId>
                    <artifactId>jmxtools</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jmx</groupId>
                    <artifactId>jmxri</artifactId>
                </exclusion>
            </exclusions>
            <scope>runtime</scope>
        </dependency>
 
        <!-- @Inject -->
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
 
        <!-- Servlet -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
 
        <!-- Test -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.7</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.9</version>
                <configuration>
                    <additionalProjectnatures>
                        <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                    </additionalProjectnatures>
                    <additionalBuildcommands>
                        <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                    </additionalBuildcommands>
                    <downloadSources>true</downloadSources>
                    <downloadJavadocs>true</downloadJavadocs>
                </configuration>
            </plugin>
<!--maven plugin 추가-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <!-- 사용할 자바버전 -->
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>org.test.int1.Main</mainClass>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
<!--Q클래스들이 생성될 위치입니다.-->
                            <outputDirectory>target/generated-sources/java</outputDirectory>
                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
 

cs




Entity class 작성


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
package com.spring.jpa.model;
 
import java.io.Serializable;
import java.util.Date;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import javax.persistence.Table;
 
@Entity
@Table(name="TBL_USER")
public class UserEntity implements Serializable{
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name="USER_ID")
    private long id;
    
    private String userName;
    private int age;
    private Date createdAt;
    @Column(name="role")
    @Enumerated(EnumType.ORDINAL) //ORDINAL -> int로 할당 STRING -> 문자열로 할당 
    private UserRole role;
    
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name="NATION_ID")
    private NationEntity nationEntity;
    
    @PrePersist
    public void beforeCreate() {
        createdAt=new Date();
    }
    
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Date getCreatedAt() {
        return createdAt;
    }
    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }
 
    public UserRole getRole() {
        return role;
    }
 
    public void setRole(UserRole role) {
        this.role = role;
    }
 
    public NationEntity getNationEntity() {
        return nationEntity;
    }
 
    public void setNationEntity(NationEntity nationEntity) {
        this.nationEntity = nationEntity;
    }
    
    
}
 
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
package com.spring.jpa.model;
 
import java.util.Set;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
 
@Entity
@Table(name="TBL_NATION")
public class NationEntity {
    
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name="NATION_ID")
    private long id;
    private String nationName;
    private String nationCode;
    
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getNationName() {
        return nationName;
    }
    public void setNationName(String nationName) {
        this.nationName = nationName;
    }
    public String getNationCode() {
        return nationCode;
    }
    public void setNationCode(String nationCode) {
        this.nationCode = nationCode;
    }
    
}
 
cs


maven plugin에서 JpaAnnotationProccesor가 @Entity를 보고 관련 Q클래스를 생성해줍니다.



maven build




run 해주면 target>generate-sources/java에 해당 Q클래스들이 생성됩니다. 만약 생성이 되었지만 Q클래스들을 인식하지 못하였을 경우에는 build path에 해당 폴더를 add folder해줍니다. 



Repository 작성



1
2
3
4
5
6
7
8
9
10
package com.spring.jpa.repository;
 
import java.util.List;
 
import com.spring.jpa.model.UserEntity;
 
public interface JpaUserRepositoryCustom {
    List<UserEntity> findAllLike(String keyword);
}
 
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
package com.spring.jpa.repository;
 
import java.util.List;
 
import javax.persistence.EntityManager;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;
 
import com.querydsl.jpa.JPQLQuery;
import com.spring.jpa.model.QUserEntity;
import com.spring.jpa.model.UserEntity;
 
@Repository
public class JpaUserRepositoryImpl extends QuerydslRepositorySupport implements JpaUserRepositoryCustom{
 
    public JpaUserRepositoryImpl() {
        super(UserEntity.class);
        // TODO Auto-generated constructor stub
    }
 
    @Override
    @Autowired
    public void setEntityManager(EntityManager entityManager) {
        super.setEntityManager(entityManager);
    }
 
    @Override
    public List<UserEntity> findAllLike(String keyword) {
        // TODO Auto-generated method stub
        QUserEntity qUserEntity=QUserEntity.userEntity;
        JPQLQuery<UserEntity> query=from(qUserEntity);
        List<UserEntity> userEntityList=query.where(qUserEntity.userName.like(keyword)).fetch();
        return userEntityList;
    }
 
}
 
cs


커스텀레포지터리 구현체를 만들어줍니다. 주의해야할점은 생성자에서 super(domain.class)입니다. 도메인클래스 인자는 해당 구현체가 필요한 것이 아니라, QuerydslRepositorySupport이 필요한 인자입니다. 꼭 넣어주어야합니다 !!!!




그리고 커스텀인터페이스에서 선언한 메소드를 오버라이드하여, querydsl을 이용한 type-safe한 동적인 쿼리를 작성해줍니다. 여기서 querydsl은 일반 Jpa 레포지토리의 기능을 업그레이드 해주는 역할이라고 보시면 됩니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.spring.jpa.repository;
 
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
 
import com.spring.jpa.model.UserEntity;
 
@Repository("jpaUserRepository")
public interface JpaUserRepository extends JpaRepository<UserEntity, Long>,JpaUserRepositoryCustom{
 
    UserEntity findByUserName(String userName);
    
    @Query(value="SELECT * FROM TBL_USER WHERE USERNAME=?1 AND AGE=?2",nativeQuery=true)
    UserEntity findByUserNameAndAge(String userName,int age);
}
 
cs


일반 Jpa 레포지토리에 커스텀인터페이스 상속을 해줍니다. 이렇게 커스텀인터페이스를 상속해주면 커스텀인터페이스 구현체에서 구현한 querydsl 메소드를 일반 jpa레포지토리에서 사용이 가능해집니다. 즉, 일반 jpa레포지터리의 기능을 그대로 이용하면서 업그레이드된 querydsl쿼리를 같이 이용할 수 있게 되는 것입니다.



Controller 작성


대충짰습니다....controller에서 repository를 다이렉트로 주입하고... 따라하시지 마시고 테스트용도이니 만약 사용하실때는 꼭 controller->service->repository 구조로 이용해주세요 !!!!


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
package com.spring.jpa.controller;
 
import java.util.Arrays;
import java.util.List;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
 
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.spring.jpa.model.NationEntity;
import com.spring.jpa.model.UserEntity;
import com.spring.jpa.model.UserRole;
import com.spring.jpa.repository.JpaNationRepository;
import com.spring.jpa.repository.JpaUserRepository;
import com.spring.jpa.service.UserService;
 
@Controller
public class UserController {
    
    private final static Logger log=LoggerFactory.getLogger(UserController.class);
    
    @Autowired
    UserService userService;
    @Autowired
    JpaNationRepository jpaNationRepository;
    @Autowired
    JpaUserRepository jpaUserRepository;
    
    @GetMapping("/user")
    public String getUserView() {
        return "user";
    }
    
    @GetMapping("/usersave")
    public String saveUser(ModelMap map,UserEntity userEntity,NationEntity nationEntity) throws JsonProcessingException {
        
        nationEntity.setNationCode("KR");
        nationEntity.setNationName("대한민국");
        jpaNationRepository.save(nationEntity);
        
        userEntity.setUserName("여성게");
        userEntity.setAge(27);
        userEntity.setRole(UserRole.USER);
        userEntity.setNationEntity(nationEntity);
        userService.save(userEntity);
        
        UserEntity saveUser=userService.findByUserName("여성게");
        UserEntity user=userService.findByUserNameAndAge(saveUser.getUserName(), saveUser.getAge());
        log.info("username={},user age={}",user.getUserName(),user.getAge());
        log.info("nationCode={},nationName={}",user.getNationEntity().getNationCode(),user.getNationEntity().getNationName());
        
        XmlMapper xmlMapper=new XmlMapper();
        String xmlString=xmlMapper.writeValueAsString(user);
        
        log.info(xmlString);
        
        List<UserEntity> userList=jpaUserRepository.findAllLike("여성게");
        log.info("userEntity list={}",userList.get(0).getUserName());
        
        map.addAttribute("userEntity", saveUser);
        return "userlist";
    }
}
 
cs



확실히.. 편하긴 하지만 여기까지 오기에 복잡한 설정은 아니지만 귀찮은 설정들이 많았내요... 조금 적응되면 이 기술도 자유롭게 사용가능하겠죠? mybatis를 선호하였던 저였지만, jpa+querydsl을 잠시나마 사용해보니 확실히 기술을 익히고 사용한다면 mybatis보다 훨씬 유연한 데이터베이스 관련 기술 일것 같습니다.



혹시 여기까지 오시다가 cannot change web module version 2.5 to ....어쩌고 맞나?.. 이런 에러가 뜬다면 자기가 사용중인 web module 버전과 maven에 명시되어 있는 버전과 비교를 하시고 꼭 maven jdk 버전이랑도 같이 맞춰주시면 해당 에러는 사라집니다... 설정을 다 다시 마추고 maven update하면 에러는 싹 사라집니다. 

posted by 여성게
:
Web/JPA 2018. 10. 13. 21:27

spring boot 환경이 아니라, 일반 springframework 환경에서 진행하였습니다.

모든 소스는 mac OS 환경에서 진행되었습니다.


이번에는 ORM을 이용한 데이터베이스 조작입니다. ORM이란 자바객체와 관계형데이터베이스의 테이블 간의 매핑을 아주 쉽게 해주는 기술입니다. 주로 mybatis만 이용하는 저였기에 이 기술을 접하고 아주 신세계...하지만 둘다 장단점이 있는 기술인 것같습니다. 아직까지는 우리나라는 mybatis를 더욱 많이 쓰고 있는 것 같기도 한데....맞나요..


project 우클릭 -> properties -> project facets -> JPA 선택.

resources 밑에 영속성 관련 설정을 할 수 있는 persistence.xml이 생성되어 있을 것입니다. 요 설정 파일에 영속성 엔티티 클래스 및 기타 설정파일을 따로 설정하실수 있습니다.


pom.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
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.spring</groupId>
    <artifactId>jpa</artifactId>
    <name>springjpa</name>
    <packaging>war</packaging>
    <version>1.0.0-BUILD-SNAPSHOT</version>
    <properties>
        <java-version>1.8</java-version>
        <org.springframework-version>5.0.2.RELEASE</org.springframework-version>
        <org.aspectj-version>1.6.10</org.aspectj-version>
        <org.slf4j-version>1.6.6</org.slf4j-version>
    </properties>
    <!-- 오라클 저장소 -->
    <repositories>
        <repository>
            <id>codelds</id>
            <url>https://code.lds.org/nexus/content/groups/main-repo</url>
        </repository>
    </repositories>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.1.11.Final</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>2.0.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
        <!-- <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${org.springframework-version}</version>
        </dependency> -->
 
        <!-- oracle -->
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <version>11.2.0.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ucp</artifactId>
            <version>11.2.0.3</version>
            <scope>compile</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.0</version>
        </dependency>
 
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework-version}</version>
            <exclusions>
                <!-- Exclude Commons Logging in favor of SLF4j -->
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>
 
        <!-- AspectJ -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${org.aspectj-version}</version>
        </dependency>
 
        <!-- Logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${org.slf4j-version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.15</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.mail</groupId>
                    <artifactId>mail</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>javax.jms</groupId>
                    <artifactId>jms</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jdmk</groupId>
                    <artifactId>jmxtools</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jmx</groupId>
                    <artifactId>jmxri</artifactId>
                </exclusion>
            </exclusions>
            <scope>runtime</scope>
        </dependency>
 
        <!-- @Inject -->
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
 
        <!-- Servlet -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
 
        <!-- Test -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.7</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.9</version>
                <configuration>
                    <additionalProjectnatures>
                        <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                    </additionalProjectnatures>
                    <additionalBuildcommands>
                        <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                    </additionalBuildcommands>
                    <downloadSources>true</downloadSources>
                    <downloadJavadocs>true</downloadJavadocs>
                </configuration>
            </plugin>
            <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>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>org.test.int1.Main</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
 
cs



application.properties, servlet-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
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
 
    <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
    
    <!-- Enables the Spring MVC @Controller programming model -->
    <mvc:annotation-driven />
 
    <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
    <mvc:resources mapping="/resources/**" location="/resources/" />
 
    <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>
    
    <context:component-scan base-package="com.spring.jpa.controller" />
    
    
    
</beans:beans>
 
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
<?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:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    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-4.3.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.8.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
    
    <!-- Root Context: defines shared resources visible to all other web components -->
    <context:component-scan base-package="com.spring.jpa">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    
    <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
    
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
    </bean>
    
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@127.0.0.1:59162:xe"/>
        <property name="username" value="springjpa"/>
        <property name="password" value="springjpa"/>
    </bean>
    
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
    
    <tx:annotation-driven/>        
    
    <jpa:repositories base-package="com.spring.jpa.repository"/><!-- transactionManager라는 이름이 아니면 명시적으로 등록해주어야한다. -->
</beans>
 
cs


만약 트랜잭션매니저 빈의 id을 transactionManager라고 지정하지 않았다면, jpa:repositories 태그에 명시적으로 트랜잭션 id값 설정을 넣어줘야한다.


UserEntity.java , NationEntity.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
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
package com.spring.jpa.model;
 
import java.io.Serializable;
import java.util.Date;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import javax.persistence.Table;
 
@Entity
@Table(name="TBL_USER")
public class UserEntity implements Serializable{
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name="USER_ID")
    private long id;
    
    private String userName;
    private int age;
    private Date createdAt;
    @Column(name="role")
    @Enumerated(EnumType.ORDINAL) //ORDINAL -> int로 할당 STRING -> 문자열로 할당 
    private UserRole role;
    
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name="NATION_ID")
    private NationEntity nationEntity;
    
    @PrePersist
    public void beforeCreate() {
        createdAt=new Date();
    }
    
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Date getCreatedAt() {
        return createdAt;
    }
    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }
 
    public UserRole getRole() {
        return role;
    }
 
    public void setRole(UserRole role) {
        this.role = role;
    }
 
    public NationEntity getNationEntity() {
        return nationEntity;
    }
 
    public void setNationEntity(NationEntity nationEntity) {
        this.nationEntity = nationEntity;
    }
    
    
}
 
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
package com.spring.jpa.model;
 
import java.util.Set;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
 
@Entity
@Table(name="TBL_NATION")
public class NationEntity {
    
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name="NATION_ID")
    private long id;
    private String nationName;
    private String nationCode;
    
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getNationName() {
        return nationName;
    }
    public void setNationName(String nationName) {
        this.nationName = nationName;
    }
    public String getNationCode() {
        return nationCode;
    }
    public void setNationCode(String nationCode) {
        this.nationCode = nationCode;
    }
    
}
 
cs


기타 어노테이션들은 구글링으로...



persistence.xml


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="springjpa">
        <class>com.spring.jpa.model.UserEntity</class>
        <class>com.spring.jpa.model.NationEntity</class>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.OracleDialect"/>
            
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="false"/>
            <property name="hibernate.id.new_generator_mappings" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="create"/>
        </properties>
    </persistence-unit>
</persistence>
 
cs



JpaUserRepository.java , JpaNationRepository.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.spring.jpa.repository;
 
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
 
import com.spring.jpa.model.UserEntity;
 
@Repository("jpaUserRepository")
public interface JpaUserRepository extends JpaRepository<UserEntity, Long> {
 
    UserEntity findByUserName(String userName);
    
    @Query(value="SELECT * FROM TBL_USER WHERE USERNAME=?1 AND AGE=?2",nativeQuery=true)
    UserEntity findByUserNameAndAge(String userName,int age);
}
 
cs


1
2
3
4
5
6
7
8
9
10
package com.spring.jpa.repository;
 
import org.springframework.data.jpa.repository.JpaRepository;
 
import com.spring.jpa.model.NationEntity;
 
public interface JpaNationRepository extends JpaRepository<NationEntity, Long> {
    NationEntity findByNationName(String nationName);
}
 
cs


JpaReposotory 인터페이스는 기본적으로 CRUDRepository,PagingAndSortingRepository,QueryByExampleExecutor,Repository 인터페이스들을 상속한 인터페이스입니다. 즉, JpaRepository 인터페이스 하나면 상속받으면 대부분 기본기능들은 모두 사용가능합니다. 하지만 조금은 복잡한 쿼리를 일반 쿼리로 작성하고 싶다면, 위의 예제처럼 native한 쿼리를 작성하여 메소드에 매핑해 줄 수 있습니다.


UserController.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
50
51
52
53
54
55
package com.spring.jpa.controller;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
 
import com.spring.jpa.model.NationEntity;
import com.spring.jpa.model.UserEntity;
import com.spring.jpa.model.UserRole;
import com.spring.jpa.repository.JpaNationRepository;
import com.spring.jpa.service.UserService;
 
@Controller
public class UserController {
    
    private final static Logger log=LoggerFactory.getLogger(UserController.class);
    
    @Autowired
    UserService userService;
    @Autowired
    JpaNationRepository jpaNationRepository;
    
    @GetMapping("/user")
    public String getUserView() {
        return "user";
    }
    
    @GetMapping("/usersave")
    public String saveUser(ModelMap map,UserEntity userEntity,NationEntity nationEntity) {
        
        nationEntity.setNationCode("KR");
        nationEntity.setNationName("대한민국");
        jpaNationRepository.save(nationEntity);
        
        userEntity.setUserName("여성게");
        userEntity.setAge(27);
        userEntity.setRole(UserRole.USER);
        userEntity.setNationEntity(nationEntity);
        userService.save(userEntity);
        
        UserEntity saveUser=userService.findByUserName("여성게");
        UserEntity user=userService.findByUserNameAndAge(saveUser.getUserName(), saveUser.getAge());
        log.info("username={},user age={}",user.getUserName(),user.getAge());
        log.info("nationCode={},nationName={}",user.getNationEntity().getNationCode(),user.getNationEntity().getNationName());
        map.addAttribute("userEntity", saveUser);
        return "userlist";
    }
}
 
cs









기타 Service클래스 등은 첨부하지 않았습니다.


굉장히 간단한 jpa 예제입니다. 추후 QueryDsl과 jpa를 이용한 예제를 업로드할 예정입니다.

부족한점은 많이 코멘트 남겨주시면 감사하겠습니다...

posted by 여성게
:
Web/Spring 2018. 10. 11. 22:14

요즘의 소프트웨어는 대부분 서버와 Json 형태의 데이터를 주고 받습니다. Json으로 주고 받게 되면서 이종소프트웨어 간의 통신도 자유로워 질수 있었습니다. 하지만 요즘은 클라이언트 단과 서버단의 Json 통신 이외에도 서버와 서버끼리도 Restful 한 통신을 하는 경우가 많아졌습니다.


RestTemplate의 동작원리


org.springframework.http.client 패키지에 있다. HttpClient는 HTTP를 사용하여 통신하는 범용 라이브러리이고, RestTemplate은 HttpClient 를 추상화(HttpEntity의 json, xml 등)해서 제공해준다. 따라서 내부 통신(HTTP 커넥션)에 있어서는 Apache HttpComponents 를 사용한다. 만약 RestTemplate 가 없었다면, 직접 json, xml 라이브러리를 사용해서 변환해야 했을 것이다.


  1. 어플리케이션이 RestTemplate를 생성하고, URI, HTTP메소드 등의 헤더를 담아 요청한다.

  2. RestTemplate 는 HttpMessageConverter 를 사용하여 requestEntity 를 요청메세지로 변환한다.

  3. RestTemplate 는 ClientHttpRequestFactory 로 부터 ClientHttpRequest 를 가져와서 요청을 보낸다.

  4. ClientHttpRequest 는 요청메세지를 만들어 HTTP 프로토콜을 통해 서버와 통신한다.

  5. RestTemplate 는 ResponseErrorHandler 로 오류를 확인하고 있다면 처리로직을 태운다.

  6. ResponseErrorHandler 는 오류가 있다면 ClientHttpResponse 에서 응답데이터를 가져와서 처리한다.

  7. RestTemplate 는 HttpMessageConverter 를 이용해서 응답메세지를 java object(Class responseType) 로 변환한다.

  8. 어플리케이션에 반환된다.



출처: https://sjh836.tistory.com/141 [빨간색코딩]




Restful(서버-서버) RestTemplate


dependency


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
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
    <groupId>com.fasterxml.jackson.core/groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.7</version>
</dependency>
 
cs


RestTemplate도 결국 내부는 httpClient를 사용합니다. (기본 설정으로 사용한다면 상관없지만 세부 설정을 위해 dependency를 추가해줍니다.)





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
package com.spring.javaconfig.controller;
 
import java.net.URI;
 
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
 
import com.spring.javaconfig.bean.RestDTO;
 
@RestController
public class RestApiController {
    
    @RequestMapping(value="/rest")
    public Object getMethod() {
        String botCode="D9UD878EAJ";
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory=
                new HttpComponentsClientHttpRequestFactory();
        clientHttpRequestFactory.setReadTimeout(5000);
        clientHttpRequestFactory.setConnectionRequestTimeout(5000);
        clientHttpRequestFactory.setConnectTimeout(5000);
        HttpClient httpClient=HttpClientBuilder.create()
                .setMaxConnTotal(100)
                .setMaxConnPerRoute(5)
                .build();
        clientHttpRequestFactory.setHttpClient(httpClient);
        RestTemplate restTemplate=new RestTemplate(clientHttpRequestFactory);
        /*UriComponentsBuilder.newInstance()
            .scheme("http")
            .host("127.0.0.1")
            .port("8983")
            .path("/solr/{botCode}/textanalysis")
            .queryParam("q", "faq테스트")
            .queryParam("rows", 1)
            .queryParam("knn.cinfo", true)
            .queryParam("knn.k", 1)
            .queryParam("intent", true)
            .queryParam("ner", false)
            .queryParam("log", false)
            .build().expand(botCode)
            .encode()
            .toUri();
            
            queryParams 로 map객체를 이용해 한번에 쿼리스트링을 넣을수도 있다.
            */
        return restTemplate.getForObject(UriComponentsBuilder.newInstance()
                .scheme("http")
                .host("localhost")
                .port("8983")
                .path("/solr/{botCode}/select")
                .queryParam("q""faq테스트")
                .build().expand(botCode)
                .encode()
                .toUri(), Object.class);
    }
}
 
cs



RestTemplate은 커낵션을 열고 사용을 마친 후에 커넥션을 닫게 되면 close Wait상태가 된다고 합니다. 즉, db의 커넥션 풀과 같은 설정을 추가하여 불필요한 리소스, 성능이슈를 적절히 해결할 필요가 있습니다. 이 예제는 검색엔진인 solr를 이용하여 간단히 쿼리를 날려 결과를 받아 보는 요청입니다. 각각 메소드의 기능은 구글링해보시길...

posted by 여성게
:
Web/Spring 2018. 10. 5. 22:57


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
@Repository
public class LearnRequestDAOImpl implements LearnRequestDAO{
    private static final String INSERT_SQL="INSERT INTO AIBOT_USER_LEARNREQUEST VALUES(LEARNREQUEST_SEQ.NEXTVAL,?,?,?,?,?)";
    private static final String SELECT_BY_QUESTION="SELECT LEARNREQUEST_ID,LEARN_QUESTION,LEARN_ANSWER,DEPT_NAME,EMP_CODE,USERNAME\n"
                                                    + "FROM USER_LEARNREQUEST\n"
                                                    + "WHERE LEARN_QUESTION =?";
    private static final String SELECT_INT_BY_QUESTION="SELECT COUNT(*) FROM USER_LEARNREQUEST WHERE LEARN_QUESTION=?";
    private static final String SELECT_LIST="SELECT * FROM USER_LEARNREQUEST";
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    
    @Override
    public int insert(LearnRequestDTO dto) {
        // TODO Auto-generated method stub
        return jdbcTemplate.update(INSERT_SQL
                ,dto.getLearn_question()
                ,dto.getLearn_answer()
                ,dto.getDept_name()
                ,dto.getEmp_code()
                ,dto.getUsername());
    }
    @Override
    public List<LearnRequestDTO> selectList() {
        // TODO Auto-generated method stub
        return jdbcTemplate.query(SELECT_LIST, new RowMapper<LearnRequestDTO>() {
                    @Override
                    public LearnRequestDTO mapRow(ResultSet rs, int rowNum) throws SQLException {
                        // TODO Auto-generated method stub
                        LearnRequestDTO learnRequestDto=new LearnRequestDTO();
                        learnRequestDto.setLearnRequest_id(rs.getInt("LEARNREQUEST_ID"));
                        learnRequestDto.setLearn_question(rs.getString("LEARN_QUESTION"));
                        learnRequestDto.setLearn_answer(rs.getString("LEARN_ANSWER"));
                        learnRequestDto.setDept_name("DEPT_NAME");
                        learnRequestDto.setEmp_code("EMP_CODE");
                        learnRequestDto.setUsername("USERNAME");
                        return learnRequestDto;
                    }
        });
    }
    @Override
    public LearnRequestDTO selectByQuestion(String question) {
        // TODO Auto-generated method stub
        Object[] args= {question};
        return (LearnRequestDTO) jdbcTemplate.queryForObject(SELECT_BY_QUESTION,args,
                new RowMapper<LearnRequestDTO>() {
                    @Override
                    public LearnRequestDTO mapRow(ResultSet rs, int rowNum) throws SQLException {
                        // TODO Auto-generated method stub
                        LearnRequestDTO learnRequestDto=new LearnRequestDTO();
                        learnRequestDto.setLearnRequest_id(rs.getInt("LEARNREQUEST_ID"));
                        learnRequestDto.setLearn_question(rs.getString("LEARN_QUESTION"));
                        learnRequestDto.setLearn_answer(rs.getString("LEARN_ANSWER"));
                        learnRequestDto.setDept_name("DEPT_NAME");
                        learnRequestDto.setEmp_code("EMP_CODE");
                        learnRequestDto.setUsername("USERNAME");
                        return learnRequestDto;
                    }
        });
    }
    
}
 
cs


JdbcTemplate 사용전의 준비과정들은 모두 생략하였습니다.(DataSource....)

posted by 여성게
:
Web/Maven 2018. 9. 30. 21:56

maven multi module project 만드는 방법과 해당 프로젝트를 SVN에 올리고 다른 개발환경에서 checkout하는 방법


모든 환경은 Mac OS 환경입니다.


오늘 설명 할것은 maven multi module project를 만드는 방법과 해당 프로젝트를 svn에 업로드하고 다른 개발환경에서 checkout하는 방법입니다.




1) 우선은 부모 maven 프로젝트를 만들어줍니다.





지금까지 잘 따라오셨다면 부모 maven 프로젝트가 만들어져 있을 것입니다. 주의해야할 점은 반드시 packaging을 pom으로 지정해주셔야하는 것입니다.


2)자식 메이븐 모듈 만들기


이렇게 하면 자식 메이븐 모듈 프로젝트까지 생성이 됩니다.



3)프로젝트 구조


제가 만든 별도의 프로젝트는 신경쓰시지 마시고 , 생성된 부모/자식 메이븐 프로젝트만 참고하시면 됩니다.


4)SVN에 업로드 및 checkout 

svn에 업로드하는 것과 checkout하는 것은 이전 글에 게시되어 있으니 참고바랍니다. 이전 글에서 언급되지 않은 부분만 설명합니다. 우선은 자식프로젝트는 신경쓰시지 마시고 부모프로젝트를 루트로 잡고 team>share project를 해줍니다. 그러면 svn에는 eclipse의 parent 프로젝트의 구조와 동일하게 업로드 됩니다. 그런 다음 다른 개발 환경에서 checkout을 하시면 위의 그림과 동일하게 parent 메이븐 폴더가 생기게 됩니다. 하지만 자식 메이븐 모듈은 보이지 않을 것입니다. 그렇다면 어떻게 자식 모듈을 위의 그림처럼 보이게 할 것인가? checkout받은 parent 프로젝트 밑의 child라는 폴더>우클릭>import>existing projects into workspace 누르시면 위와 같이 자식모듈 프로젝트 구조가 생기게 됩니다.


설명을 중간중간 하지 않았지만 parent/child의 pom.xml을 보시면 자식/부모관계를 명시하는 태그들이 생겨있을 것입니다. 그것은 보시고 구조를 이해하시는 것도 중요할 것같습니다.



'Web > Maven' 카테고리의 다른 글

Spring boot - Maven Multi Module project 만들기  (0) 2019.04.30
Apache Maven이란?(아파치 메이븐)  (0) 2019.04.04
posted by 여성게
:
Web/Spring 2018. 9. 30. 20:33

저와 같은 바보같은 분은 없을 것이라고 생각이 들지만 제가 겪었던 하나의 문제였기 때문에 작성해봅니다....


만약 @Controller 클래스에서 요청바디에 넘어온 데이터를 받기위하여 @RequestBody라는 어노테이션을 다들 많이 쓰실 겁니다. 다른 분들은 절대 저와 같은 바보같은 짓을 하시지는 않으셨겠지만, 저는 요청 바디로 넘어온 데이터를 적절히 2개의 객체로 나누기 위해서 메소드 파라미터에 2개의 @RequestBody를 사용하였습니다. 근데 이게 뭐람 에러 뚜뚱... 에러가 발생하였습니다. 무슨 에러인가 막 찾아보고 고민하던 중에 중요한 사실을 알게되었습니다. 

요청 바디로 넘어온 데이터를 받기위해 @RequestBody를 사용하는데 이 어노테이션은 스트림을 이용하여 요청바디의 데이터를 불러 온다는 것... 한번 열어서 사용한 스트림은 재사용이 불가능 하기 때문에 첫번째 @RequestBody로 받고 난 뒤, 다시 @RequestBody로는 당연히 받을 수 없는 것입니다... 혹시라도 저와 같은 분은 없을 것이라고 생각하지만...있으시다면 참고바라면서 작성하였습니다.


만약 2개의 객체의 요청바디의 데이터를 적절히 나눌 필요가 있다면 하나의 객체로 모든 데이터를 다 받아와서 그 객체를 이용해 다른 객체들에게 데이터를 나눠 담는 방법을 이용하시면 됩니다..




@RequestMapping("abc")

public String controllerMethod(@RequestBody A a,@RequestBody B b) {......} => 절대 안됩니다...




posted by 여성게
:

Solr 7.4.x version의 Tagger Handler를 이용한 NER(Named-Entity Recognition)


NER이란 자연어에서 뜻이 있는 단어를 뽑아내는 것이다. 챗봇처럼 자연어와 관련있는 기술에서 사용되는 기능인데, 예를 들어 "피자 주문할게요" 라는 질문이 있다. 이 문장에서 유추해 볼 수 있는 것은 "피자 주문" 이것을 더 보편화시켜 보면 "메뉴 주문"이라는 사용자질의 "의도"를 알 수 있다. 만약 "햄버거 주문할게요"라는 질의가 있으면 이 또한 "메뉴 주문"이라는 의도라는 것을 유추할 수 있다. 그럼 사용자가 어떠한 메뉴를 주문한다는 것은 알겠는데 그럼 그 메뉴가 무엇인가? 이러한 것이 챗봇에서는 NER이라는 기술로 추출해 낼 수 있는 단어라는 것이다. "피자 주문할게요"라는 질의의 의도는 "메뉴 주문" 개체명(Named-Entity)는 "피자"가 되는 것이다. 이렇게 의미있는 단어를 뽑아내는 것은 아주 중요한 챗봇의 기술이며 앞으로 더욱 발전해야할 기술이기도 하다. 이 NER 기술을 solr 7.4의 Tagger Handler를 이용하여 구현해 볼 것이다.


모든 구현은 Mac OS 환경에서 구현하였습니다.


우선 구현에 앞서 선행되어야 하는 것이, 바로 이전의 글인 Solr와 Zookeeper 연동입니다. 모든 환경은 solr cloud 환경에서 구현하였기에 중복되는 설명은 배제하였습니다.



$SOLR_HOME/server/solr 폴더 밑에 configset이 존재할 것입니다. 그 폴더를 보면 디폴트 환경설정 파일이 있는데, 그 안의 schema.xml과 solrconfig.xml 파일을 만질 것입니다. 이전에 따로 사용중이셨던 설정파일을 이용하셔도 무관합니다.




<schema.xml>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<field name="DOCID" type="string" indexed="true" stored="true" required="true" multiValued="false" />
<field name="NER_VALUE" type="text_general" indexed="true"/>
<field name="NER_KEY" type="string" indexed="true"/>
<field name="NER_TAG" type="tag" stored="false"/>
<copyField source="NER_VALUE" dest="NER_TAG"/>
<uniqueKey>DOCID</uniqueKey>
<fieldtype name="tag" class="solr.TextField" positionIncrementGap="100" postingsFormat="FST50"
        omitTermFreqAndPositions="true" omitNorms="true">
    <analyzer type="index">
      <tokenizer class="solr.StandardTokenizerFactory"/>
      <filter class="solr.ASCIIFoldingFilterFactory"/>
      <filter class="solr.LowerCaseFilterFactory"/>
      <filter class="solr.ConcatenateGraphFilterFactory" preservePositionIncrements="false"/>
    </analyzer>
    <analyzer type="query">
      <tokenizer class="solr.StandardTokenizerFactory"/>
      <filter class="solr.ASCIIFoldingFilterFactory"/>
      <filter class="solr.LowerCaseFilterFactory"/>
    </analyzer>  
</fieldtype>
cs



적당한 위치에 선언해줍니다. NER과 관련된 필드들과 필드 타입을 선언한 설정입니다.


<solrconfig.xml>

1
2
3
4
5
<requestHandler name="/tag" class="solr.TaggerRequestHandler">
    <lst name="defaults">
      <str name="field">NER_TAG</str>
    </lst>
</requestHandler>
cs


적당한 위치에 선언해줍니다. 개체명을 추출하기 위한 Tagger Handler를 등록하는 설정입니다.


만약 디폴트 설정파일을 이용하지 않고 새로 정의하셨다면 해당 설정 폴더를 SolrConifg/conf/ 밑으로 넣어줍니다.(SolrConfg는 맘대로정의 가능하지만 conf는 맞춰주셔야합니다.)


solr 디렉토리의 적당한 곳에 위치시켜줍니다. 그리고 난 후에 zookeeper에 upconfig를 합니다.(solr collection들이 사용할 설정파일을 zookeeper에 업로드하여 중앙에서 관리해줍니다.)

저는 앞으로 자주 사용할 가능성이 있다 판단이 되어서 쉘스크립트 실행파일을 만들어서 upconfig 했습니다.


<uploadSolrConfig.sh>

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
 
# usage uploadSolrConfig.sh zipConfigPath configName zkServers
 
CONFIG_PATH=$1
CONFIG_NAME=$2
ZK_SERVERS=$3
 
# upload config info
 
$HOME/solr/bin/solr zk upconfig -n $CONFIG_NAME -d $CONFIG_PATH -z $ZK_SERVERS
 
echo $?
cs


~$ ./uploadSolrConfig $SOLR_HOME/SolrConfig TaggerHandlerConf localhost:2181,localhost:2182,localhost:2183 으로 실행시켜줍니다.

이후 solr admin 페이지를 들어가서 cloud>file>confg에 생성한 이름으로 conf file이 생성되었는지 확인합니다. 그리고 Collection탭에 들어가서 생성한 conf file을 이용하여 컬렉션을 생성해줍니다.(collectionName = TaggerHandler)




<indexing file>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<add>
    <doc>
        <field name="DOCID">NER1</field>
        <field name="NER_VALUE">햄버거</field>
        <field name="NER_KEY">메뉴</field>
    </doc>    
    <doc>
        <field name="DOCID">NER2</field>
        <field name="NER_VALUE">피자</field>
        <field name="NER_KEY">메뉴</field>
    </doc>
    <doc>
        <field name="DOCID">NER3</field>
        <field name="NER_VALUE">치킨</field>
        <field name="NER_KEY">메뉴</field>
    </doc>    
    <doc>
        <field name="DOCID">NER</field>
        <field name="NER_VALUE">파스타</field>
        <field name="NER_KEY">메뉴</field>
    </doc>
</add>
cs


그리고 색인할 파일을 만듭니다.


$SOLR_HOME/bin/post -c TaggerHandler $SOLR_HOME/menu.xml 로 색인해줍니다.


이제 모든 작업이 끝이 났고, 적당한 request를 보내서 결과를 확인하면 됩니다.


요청은 POST 방식이고, 요청헤더에 Content-Type text/plain 요청바디에 "햄버거, 피자,피자 주문할게요"라는 질의를 넣고 밑의 요청을 보내면 (url 테스트를 위하여 저는 간편한 postman이라는 툴을 사용하였습니다. curl을 사용해도 무관합니다.)


http://localhost:8983/solr/TaggerHandler/tag?overlaps=NO_SUB&tagsLimit=5000&fl=DOCID,NER_VALUE,NER_KEY&wt=json&intent=on


{ "responseHeader": { "status": 0, "QTime": 35 }, "tagsCount": 3, "tags": [ [ "startOffset", 0, "endOffset", 3, "ids", [ "NER1" ] ], [ "startOffset", 5, "endOffset", 7, "ids", [ "NER2" ] ], [ "startOffset", 8, "endOffset", 10, "ids", [ "NER2" ] ] ], "response": { "numFound": 2, "start": 0, "docs": [ { "DOCID": "NER1", "NER_VALUE": "햄버거", "NER_KEY": "메뉴" }, { "DOCID": "NER2", "NER_VALUE": "피자", "NER_KEY": "메뉴" } ] } }


=>이런 결과가 나옵니다!!!


posted by 여성게
:
Web/Spring 2018. 9. 20. 23:22

모든 컨트롤러의 중복된 로직 코드량 확줄여버리는 코딩전략, 제네릭스와 매핑정보 상속을 이용한 컨트롤러 작성전략




웹개발을 하면서 지겹도록 작성하는 코드가 CRUD 관련 코드이다. 매번 똑같은 패턴이며 도메인 오브젝트만 바뀔뿐 딱히 로직이 크게 차이가 없다. 그렇지만 우리는 서비스 클래스는 물론 컨트롤러의 코드에 CRUD코드가 많이 중복되며 대부분 서비스클래스에게 작업을 위임해주는 작업뿐이다. 그렇다면 어떻게 중복된 것을 최대한 배제시킬 수 있을까?

그것이 바로 제네릭스를 이용한 상속 전략이다. 바로 예제 코드로 넘어가면,


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
package com.toby.spring.controller;
 
import java.util.ArrayList;
import java.util.List;
 
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
 
public abstract class GenericController<D,P,S> {
    S service;
    @RequestMapping(method=RequestMethod.POST)
    public int add(D dto) {
        System.out.println("DTO 객체에 대한 DAO 삽입로직");
        return 0;
    }
    @RequestMapping(method=RequestMethod.PUT)
    public int update(D dto) {
        System.out.println("DTO 객체에 대한 DAO 수정로직");
        return 1;
    }
    @RequestMapping(value="{id}",method=RequestMethod.GET)
    public D getByID(P primaryKey) {
        System.out.println("id 값을 이용하여 DTO 객체를 가져오는 DAO 로직");
        D d=(D) new Object();
        return d;
    }
    @RequestMapping(method=RequestMethod.DELETE)
    public int delete(P primaryKey) {
        System.out.println("id 값을 이용하여 해당 DTO 객체를 삭제하는 DAO 로직");
        return 1;
    }
    @RequestMapping(value="/all",method=RequestMethod.GET)
    public List<D> getByList(){
        System.out.println("DAO에서 DTO 타입의 객체 리스트를 가져오는 로직");
        List<D> dList=new ArrayList();
        return dList;
    }
}
 
cs


이것은 대부분의 컨트롤러에서 중복되는 CRUD관련 공통적인 컨트롤러 메소드 및 매핑선언이다. 여기서 집중해서 봐야하는 것이 제네릭스 타입이다. 물론 이것은 예제 코드라서 간단한 제네릭스 타입이지만(실무코드에서는 조금더 다듬어져야하는 부분) 대게 저렇게 3가지 타입으로 컨트롤러 단에서 CRUD 로직을 처리가능하다. 또하나 집중해서 봐야하는 것은 추상클래스의 메소드의 @RequestMapping이다. 이거 관련해서는 뒤에서 설명한다. 이 추상클래스를 상속하는 실 컨트롤러 클래스를 만들어준다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.toby.spring.controller;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
 
import com.toby.spring.dto.UserDTO;
import com.toby.spring.service.UserService;
 
@Controller
@RequestMapping("/user")
public class UserController extends GenericController<UserDTO, Integer, UserService>{
    @RequestMapping("/login")
    public void login(UserDTO userDto) {
        System.out.println("로그인 인증관련 로직");
    }
    @RequestMapping("/logout")
    public void logout() {
        System.out.println("로그아웃 관련 로직");
    }
}
 
cs


너무 간단하지 않은가? CRUD 관련 메소드는 보이지도 않으면서 메소드를 이용할 수 있다. 상속받은 컨트롤러 제네릭스 타입에 집중하자 그러면 추상클래스의 제네릭스 타입들에 들어가는 타입을 알 수 있을 것이며, 이것들은 많이 봐왔던 패턴일 것이다. 그리고 중요한 것이 실 컨트롤러 클래스 타입에 공통적인 url패턴을 등록하고 각각 CRUD 로직의 매핑은 추상클래스의 매핑타입을 그대로 상속받아 사용하는 것이다. 그렇다면 여타 게시판관련 CRUD에서는 똑같이 추상클래스를 상속하고 단순히 클래스 타입에 @RequestMapping url값만 바꿔주면 그대로 사용이 가능한 것이다. 물론 CRUD 이외의 매핑과 메소드는 실 컨트롤러 클래스에서 선언하여 사용할 수 있다. 이렇게 제네릭스 타입을 이용하여 추상클래스를 만들면 여타 엄청난 양의 코드가 줄어버린다. 지금은 컨트롤러에서만 이 전략을 이용하였지만 추후에 서비스클래스 쪽과 DAO쪽도 이와 같은 전략을 이용한다면 전체적으로 일관된 프로젝트 전략이 될것이며 덩달아 코드량도 엄청나게 줄어버릴 것이다. 



posted by 여성게
: