EntityController
의 테스트 코드는 다음과 같다.
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="file:src/main/webapp/WEB-INF/spring/root-context.xml") @WebAppConfiguration public class ControllerTest{ protected final Logger logger = LoggerFactory.getLogger(getClass()); @Autowired protected WebApplicationContext applicationContext; protected MockMvc mockMvc; @Before public void setup() throws Exception { if(mockMvc == null){ this.mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext).build(); } super.setup(); } @Test public void testMethod() throws Exception{ this.mockMvc.perform( get("/foo/find") ) .andDo(print()) .andExpect(status().isOk()); } }
Section 1.4, “Springfield Example”
에서 자동 생성하는 Bean 중
Validator 레이어에 에 해당하는 "fooValidator" Bean 을
AbstractEntityValidator
을 extends 한 MyFooValidator 와
<context:component-scan> 을 사용하여
교체할수 있다.
package com.yourcompany.yourproject.foo; @Component("fooValidator") public class MyFooValidator extends AbstractEntityValidator<Foo, Foo>{ @Autowired @Qualifier("fooRepository") private EntityRepository<Foo, String> fooRepository; @Override @Transactional public void create(ExtendsBean target, Errors errors) { //your validation logic... } }
등록 되는 MyFooValidator 의 Bean Name 은 Section 1.3, “<springfield:modules>” 를 에 따라 "{@Springfield class short name}Validator" 가 되어야 한다. | |
validation logic 을 위해 DAO 가 필요하다면, Bean 을 주입받는다.
해당 메소드에 |
<beans> <jdbc:embedded-database id="yourDataSource" type="HSQL"/> <context:component-scan base-package="com.yourcompany.yourproject"/> <springfield:modules base-package="com.yourcompany.yourproject" data-source-ref="yourDataSource" /> ... </beans>
Bean 을 교체하기 위해 <springfield:modules> 보다 앞서 <context:component-scan> 으로 MyFooValidator 를 등록 하여야 한다. |
Section 1.4, “Springfield Example”
에서 자동 생성하는 Bean 중
Service 레이어에 에 해당하는 "fooService" Bean 을
AbstractEntityService
을 extends 한 MyFooService 와
<context:component-scan> 을 사용하여
교체할수 있다.
package com.yourcompany.yourproject.foo; @Service("fooService") public class MyFooService extends AbstractEntityService<Foo, Foo>{ @Autowired @Qualifier("fooRepository") private EntityRepository<Foo, String> fooRepository; @Autowired private TransactionTemplate transactionTemplate; @Override protected EntityRepository<Foo, String> getRepository() { return fooRepository; } @Override protected TransactionTemplate getTransactionTemplate() { return transactionTemplate; } @Override public Foo create(Foo entity) { return getTransactionTemplate().execute(new TransactionCallback<Foo>() { public ExtendsBean doInTransaction(TransactionStatus status) { //your service logic.. ExtendsBean result = getRepository().save(entity); return result; } }); } @Override @Transactional public Foo read(Foo entity) { //your service logic.. ExtendsBean result = getRepository().save(entity); return result; } }
등록 되는 레이어의 Bean Name 은 Section 1.3, “<springfield:modules>” 를 에 따라 "{@Springfield class short name}Service" 가 되어야 한다. | |
MyFooService 의 기본 동작을 위해, getRepository() 와 getTransactionTemplate() 를 override 하여야 한다. | |
getTransactionTemplate() 을 이용하여 Transaction 을 수행 할 수 있다. | |
|
<beans> <jdbc:embedded-database id="yourDataSource" type="HSQL"/> <context:component-scan base-package="com.yourcompany.yourproject"/> <springfield:modules base-package="com.yourcompany.yourproject" data-source-ref="yourDataSource" /> ... </beans>
Bean 을 교체하기 위해 <springfield:modules> 보다 앞서 <context:component-scan> 으로 MyFooService 를 등록 하여야 한다. |
Section 1.2, “@Springfield” 의 strategy 속성이
Strategy.DTO
인 경우
Repository 레이어가 생성되지 않는다. 따라서 아무 동작도 하지 않는 Service 레이어 가 생성된다.
이 경우 Service 레이어 는
EntityService
를 implements 한 Bean 으로 교체하여야 한다.
Important | |
---|---|
Strategy.DTO 방식은 WebMVC 의 요청파라미터 Command 객체와
도메인 객체를 분리하기 위해 사용되는 방식이다.
|
package com.yourcompany.yourproject.dto; @Springfield( strategy=Strategy.DTO, identity="param1") public class DtoBean { private String param1; private Integer param2; //... }
|
package com.yourcompany.yourproject.dto; @Service("dtoBeanService") public class DtoBeanService implements EntityService<DtoBean, DtoBean>{ @Autowired @Qualifier("fooRepository") private EntityRepository<Foo, String> fooRepository; @Autowired @Qualifier("barRepository") private EntityRepository<Bar, Integer> barRepository; @Override @Transactional public DtoBean create(DtoBean entity) { Foo foo = new Foo(); foo.setName(entity.getParam1()); foo.setAge(entity.getParam2()); fooRepository.save(foo); Bar bar = new Bar(); bar.setSeq(entity.getParam2()); bar.setDesc(entity.getParam1()); barRepository.save(bar); return entity; } .... }
|
<beans> <jdbc:embedded-database id="yourDataSource" type="HSQL"/> <context:component-scan base-package="com.yourcompany.yourproject"/> <springfield:modules base-package="com.yourcompany.yourproject" data-source-ref="yourDataSource" /> ... </beans>
Bean 을 교체하기 위해 <springfield:modules> 보다 앞서 <context:component-scan> 으로 MyFooService 를 등록 하여야 한다. |
EntityRepository
의 테스트 코드는 다음과 같다.
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="file:src/main/webapp/WEB-INF/spring/root-context.xml") @Transactional public class RepositoryTest{ protected final Logger logger = LoggerFactory.getLogger(getClass()); @Autowired @Qualifier("fooRepository") private EntityRepository<Foo, String> fooRepository; @Autowired @Qualifier("barRepository") private EntityRepository<Bar, Integer> barRepository; @Test public void testMethod() throws Exception{ //Test Code... } }
QueryMethod Argument 란
EntityRepository
메소드의 argument 를 의미한다.
public interface EntityRepository<T, ID extends Serializable> { ... public long deleteAll(Object queryMethod); public long count(Object queryMethod); public List<T> findAll(Object queryMethod); public List<T> findAll(Object queryMethod, Sort sort); public Page<T> findAll(Object queryMethod, Pageable pageable); }
Query Keyword 는 QueryMethod Argument 의 Class Short Name 을 의미한다.
Query Keyword 는
@QueryMethod
을 이용하여 변경 할 수 있다.
QueryKeyword 는 "FindBy" 로 시작하며,
Spring-data 의 Query keyword
규칙을 따른다.
public class FindByNameAndAgeOrderByAgeAsc{ private String name; private Integer age; } @QueryMethod("findByNameAndAgeOrderByAgeAsc") public class MyQuery{ private String name; private Integer age; }
QueryMethod Argument 는 Query Keyword 에 따라 필여한 property 를 선언하여야 한다. property 값의 null 여부에 따라 동적으로 where 조건을 바꿀수 있다.
//Test Code... FindByNameAndAge queryMethod1 = new FindByNameAndAge(); //or //MyQuery queryMethod1 = new MyQuery(); List<Person> result1 = fooRepository.findAll(queryMethod1); Assert.assertEquals(result1.size(), 3);
//Test Code... FindByNameAndAge queryMethod2 = new FindByNameAndAge(); //or //MyQuery queryMethod2 = new MyQuery(); queryMethod2.setName("a"); List<Person> result2 = fooRepository.findAll(queryMethod2); Assert.assertEquals(result2.size(), 1);
//Test Code... FindByNameAndAge queryMethod3 = new FindByNameAndAge(); //or //MyQuery queryMethod3 = new MyQuery(); queryMethod3.setName("a"); queryMethod3.setAge(1); List<Person> result3 = fooRepository.findAll(queryMethod3); Assert.assertEquals(result3.size(), 1);
TemplateCallback
을 이용하면
EntityRepository
에서 로우레벨의 ORM 관련 객체에 바로 접근 할 수 있다.
public interface EntityRepository<T, ID extends Serializable> { ... public <R, X> R execute(TemplateCallback<R, X> callback); }
다음표는 자동 생성되는
EntityRepository
의 구현체별로 사용가능한
TemplateCallback
의 Generic Type 을 나타낸다.
Table 4.1. TemplateCallback Generic Type
Repository Implementation | TemplateCallback |
---|---|
JpaRepository
|
TemplateCallback<R, javax.persistence.EntityManager>
|
HibernateRepository
|
TemplateCallback<R, org.hibernate.Session>
|
SqlSessionRepository
|
TemplateCallback<R, org.mybatis.spring.SqlSession>
|
JdbcRepository
|
TemplateCallback<R, org.springframework.jdbc.core.JdbcTemplate>
|
TemplateCallback<R, java.sql.Connection>
|
//Test Code... List<Foo> result2 = fooRepository.execute(new TemplateCallback<List<Foo>, EntityManager>() { public List<Foo> doInTemplate(EntityManager em) { //Using QueryDsl Foo alias = Alias.alias(Foo.class, "foo"); EntityPath<Foo> foo = Alias.$(alias); StringPath fooName = Alias.$(alias.getName()); NumberPath<Integer> fooAge = Alias.$(alias.getAge()); JPAQuery query = new JPAQuery(em); query.from(foo); query.where(fooName.eq("a")); query.where(fooAge.eq(1)); return query.list(foo); } }); Assert.assertEquals(result2.size(), 1);
//Test Code... List<Foo> result2 = fooRepository.execute(new TemplateCallback<List<Foo>, Session>() { @Override public List<Foo> doInTemplate(Session session) { //Using Hibernate Criteria Criteria criteria = session.createCriteria(Foo.class); criteria.add(Restrictions.eq("name", "a")); criteria.add(Restrictions.eq("age", 1)); return criteria.list(); } }); Assert.assertEquals(result2.size(), 1);
Section 1.3, “<springfield:modules>” 가 제공하는
MultipartFileHandler
는 File Upload , Download 를 지원한다.
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="file:src/main/webapp/WEB-INF/spring/root-context.xml") public class MultipartFileHandlerTest { @Autowired protected MultipartFileHandler multipartFileHandler; @Test public void test() throws Exception { String contentFile1 = multipartFileHandler.uploadFile(f); String contentFile2 = multipartFileHandler.uploadFile(f, new UploadFileNameResolver() { @Override public String resolveFileName(MultipartFile multipartFile) throws IOException { return "Your Prefix"+multipartFile.getOriginalFilename()+"Your Suffix"; } }); File uploadedFile = multipartFileHandler.findFile(contentFile1); Assert.assertEquals(true, uploadedFile.exists()); multipartFileHandler.deleteFile(contentFile1); Assert.assertEquals(false, uploadedFile.exists()); }
지정돤 경로로 업로드하고 contentFile(저장된 파일의 이름)을 리턴한다. See ??? contentFile 은 디폴트로 {System.currentTimeMillis}_{Original Filename} 로 결정된다. | |
| |
contentFile 로 물리적인 파일 객체를 리턴받을 수 있다. | |
contentFile 로 물리적인 파일을 삭제 할 수 있다. |
다음은 Service 레이어를 변경하여 파일을 업로드 하고 메타정보를 데이터 베이스에 저장하고, 업로드된 파일을 리스트업하고 다운로드하는 예제이다.
package com.yourcompany.yourproject.file; @Springfield( methodLevelMapping={"find", "createForm", "create", "read.download","read.stream"}) @Entity public class FileBean implements MultipartFileBean{ @Id public String contentFile; public String contentName; public String contentType; public Long contentSize; @Transient public MultipartFile uploadFile; //... }
[GET:/file/{contentName}.download] 또는 [GET:/file/{contentName}.stream] 요청일 경우
| |
Section 1.3, “<springfield:modules>” 가 지원하는
확장자중, *.download , *.stream 은
도메인 객체가
| |
<form method="POST" enctype="multipart/form-data">
의 <input type="file" name="uploadFile"> 를
도메인 객체에 담아두기 위해 필요한 property 이다.
uploadFile 은 데이터베이스에 저장하지 않는 필드이므로
|
package com.yourcompany.yourproject.file; @Service("fileBeanService") public class FileBeanService extends AbstractEntityService<FileBean,FileBean>{ @Autowired @Qualifier("fileBeanRepository") private EntityRepository<FileBean, String> fileBeanRepository; @Autowired private TransactionTemplate transactionTemplate; @Override protected EntityRepository<FileBean, String> getRepository() { return fileBeanRepository; } @Override protected TransactionTemplate getTransactionTemplate() { return transactionTemplate; } @Autowired private MultipartFileHandler multipartFileHandler; @Override public FileBean create(FileBean entity) { MultipartFile f = entity.getUploadFile(); try { String contentFile = multipartFileHandler.uploadFile(f); String contentName = f.getOriginalFilename(); String contentType = f.getContentType(); Long contentSize = f.getSize(); entity.setContentFile(contentFile); entity.setContentName(contentName); entity.setContentType(contentType); entity.setContentSize(contentSize); return super.create(entity); } catch (IOException e) { throw new RuntimeException(e); } } }
MultipartFileHandler
를 이용하여 파일을 업로드하고 메타정보를 도매인 객체에 담아서 이를 데이터베이스에 저장한다.
|
<beans> <jdbc:embedded-database id="yourDataSource" type="HSQL"/> <context:component-scan base-package="com.yourcompany.yourproject"/> <springfield:modules base-package="com.yourcompany.yourproject" data-source-ref="yourDataSource" /> ... </beans>
Bean 을 교체하기 위해 <springfield:modules> 보다 앞서 <context:component-scan> 으로 FileBeanService 를 등록 하여야 한다. |
Section 1.3, “<springfield:modules>”는 spring security 관련 설정값을 properties-ref 로 변경 할 수 있다. properties-ref 가 선언되지 않은 경우 디폴트 값이 사용된다.
<beans> <util:properties id="yourProp" location="classpath:com/yourcompany/yourproject/config.properties" /> <jdbc:embedded-database id="yourDataSource" type="HSQL"/> <springfield:modules base-package="com.yourcompany.yourproject" data-source-ref="yourDataSource" properties-ref="yourProp" /> ... </beans>
# # com/yourcompany/yourproject/config.properties # springfield.security.formPage=/security/user/loginForm.html springfield.security.formUsername=j_username springfield.security.formPassword=j_password springfield.security.formRememberme=_spring_security_remember_me springfield.security.loginUrl=/j_spring_security_check springfield.security.logoutUrl=/j_spring_security_logout
다음은 사용자 계정을 데이터베이스로 관리하는 예제이다.
package com.yourcompany.yourproject.security; @Springfield @Entity public class User implements org.springframework.security.core.userdetails.UserDetails{ @Id private String username; private String password; private boolean enabled = true; private boolean accountNonExpired = true; private boolean accountNonLocked = true; private boolean credentialsNonExpired = true; private String salt; private String role; //getter / setter @Transient public Collection<? extends GrantedAuthority> getAuthorities() { return Role.valueOf(role).getAuthorities(); } public enum Role { USER(new SimpleGrantedAuthority("ROLE_ANONYMOUS"), new SimpleGrantedAuthority("ROLE_USER")), ADMIN(new SimpleGrantedAuthority("ROLE_ANONYMOUS"), new SimpleGrantedAuthority("ROLE_USER"), new SimpleGrantedAuthority("ROLE_ADMIN")); private Collection<GrantedAuthority> grantedAuthorities = new HashSet<GrantedAuthority>(); Role(GrantedAuthority... authorities){ for(GrantedAuthority authority : authorities){ grantedAuthorities.add(authority); } } public Collection<? extends GrantedAuthority> getAuthorities(){ return grantedAuthorities; } } }
사용자 계정을 데이터베이스로 관리하기 위해
| |
권한을 가진 사용자 ROLE 을 필요에 따라 정의한다. |
package com.yourcompany.yourproject.security; @Service("userService") public class UserService extends AbstractEntityService<User, User>{ @Autowired private AuthenticationManager authenticationManager; @Autowired private PasswordEncoder passwordEncoder; @Autowired private SaltSource saltSource; @Autowired @Qualifier("userRepository") private EntityRepository<User, String> userRepository; @Autowired private TransactionTemplate transactionTemplate; @Override protected EntityRepository<User, String> getRepository() { return userRepository; } @Override protected TransactionTemplate getTransactionTemplate() { return transactionTemplate; } @Override @Transactional public User create(User entity) { String salt = ""+System.currentTimeMillis(); String password = passwordEncoder.encodePassword("password", salt); entity.setSalt(salt); entity.setPassword(password); entity.setRole("USER"); return super.create(entity); } }
등록 되는 Service 레이어의 Bean Name 은 Section 1.3, “<springfield:modules>” 를 에 따라 "{@Springfield class short name}Service" 가 되어야 한다. | |
암호화된 패스워드와 password salt 를 저장한다. |
<beans> <jdbc:embedded-database id="yourDataSource" type="HSQL"/> <context:component-scan base-package="com.yourcompany.yourproject"/> <springfield:modules base-package="com.yourcompany.yourproject" data-source-ref="yourDataSource" /> ... </beans>
Bean 을 교체하기 위해 <springfield:modules> 보다 앞서 <context:component-scan> 으로 UserService 를 등록 하여야 한다. |
org.springframework.security.core.userdetails.UserDetailsService
를 구현한 Bean 이 등록되어 있다면,
Section 1.3, “<springfield:modules>” 이 동적으로 이 Bean 을 추적하여
spring security
가 로그인을 처리하도록 설정한다.
org.springframework.security.web.authentication.AuthenticationSuccessHandler
를 구현한 Bean 이 등록되어 있다면, 로그인 성공 이벤트를 수신할수 있으며,
org.springframework.security.web.authentication.AuthenticationFailureHandler
를 구현한 Bean 이 등록되어 있다면, 로그인 실패 이벤트를 수신할수 있으며,
package com.yourcompany.yourproject.security; @Component public class LoginService implements org.springframework.security.core.userdetails.UserDetailsService{ @Autowired @Qualifier("userRepository") private EntityRepository<User, String> userRepository; @Override @Transactional public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findOne(username); if (user == null) { throw new DataRetrievalFailureException("Query returned no results for user '" + username + "'"); } return user; } }
사용자 계정을 데이터베이스에서 조회하여 로그인 처리 한다. |
package com.yourcompany.yourproject.security; @Component public class LoginSuccess implements org.springframework.security.web.authentication.AuthenticationSuccessHandler{ @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { //로그인 성공 이벤트 } }
로그인 성공 이벤트를 수신하여 로직을 구현할수 있다. |
package com.yourcompany.yourproject.security; @Component public class LoginFailure implements org.springframework.security.web.authentication.AuthenticationSuccessHandler{ @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)throws IOException, ServletException { //로그인 실패 이벤트 } }
로그인 실패 이벤트를 수신하여 로직을 구현할수 있다. |
<beans> <jdbc:embedded-database id="yourDataSource" type="HSQL"/> <context:component-scan base-package="com.yourcompany.yourproject"/> <springfield:modules base-package="com.yourcompany.yourproject" data-source-ref="yourDataSource" /> ... </beans>
<context:component-scan> 으로 LoginService , LoginSuccess , LoginFailure 를 등록 하여야 한다. |