이번 포스트에서는, annotation 기반 환경설정을 사용하여 Spring과 hiberate를 통합시킬 것이다.
유저의 입력을 요청하는 form을 포함하고, Hibernate를 사용하여 Mysql database에 입력하는 web application 기반의 간단한 CURD를 만들 것이다. transaction과 함께 기록물을 검색,업데이트, 삭제를 할 것이며 모든 것은 annotation 환경설정으로 진행할 것이다.
이 포스트는 다음 포스트에서 TestNG, mockito, spring-test, DBUnit & H2 database를 사용하는 unti/interation test를 이행하기 위한 간단한 실습 예제이다.
• Spring 4.0.6.RELEASE
• Hibernate Core 4.3.6.Final
• validation-api 1.1.0.Final
• hibernate-validator 5.1.3.Final
• MySQL Server 5.6
• Maven 3
• JDK 1.7
• Tomcat 8.0.21
• Eclipse JUNO Service Release 2
• TestNG 6.9.4
• Mockito 1.10.19
• DBUnit 2.2
• H2 Database 1.4.187
Step 2: Update pom.xml to include required dependencies
<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.websystique.springmvc</groupId> <artifactId>SpringHibernateExample</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>SpringHibernateExample Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <springframework.version>4.0.6.RELEASE</springframework.version> <hibernate.version>4.3.6.Final</hibernate.version> <mysql.connector.version>5.1.31</mysql.connector.version> <joda-time.version>2.3</joda-time.version> <testng.version>6.9.4</testng.version> <mockito.version>1.10.19</mockito.version> <h2.version>1.4.187</h2.version> <dbunit.version>2.2</dbunit.version> </properties> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${springframework.version}</version> </dependency> <!-- Hibernate --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate.version}</version> </dependency> <!-- jsr303 validation --> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.1.0.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.1.3.Final</version> </dependency> <!-- MySQL --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.connector.version}</version> </dependency> <!-- Joda-Time --> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>${joda-time.version}</version> </dependency> <!-- To map JodaTime with database type --> <dependency> <groupId>org.jadira.usertype</groupId> <artifactId>usertype.core</artifactId> <version>3.0.0.CR1</version> </dependency> <!-- Servlet+JSP+JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- Testing dependencies --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${springframework.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>${testng.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>${mockito.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>${h2.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>dbunit</groupId> <artifactId>dbunit</artifactId> <version>${dbunit.version}</version> <scope>test</scope> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <warSourceDirectory>src/main/webapp</warSourceDirectory> <warName>SpringHibernateExample</warName> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </pluginManagement> <finalName>SpringHibernateExample</finalName> </build> </project>
여기서 주목해야 할 점은 maven-war-plugin 선언이다. 우리는 완전한 annotation 환경설정을 사용하기 떄문에, 우리의 프로젝트에는 web.xml을 포함하지 않는다. 그래서 war를 build할 때 maven의 실패 오류를 피하기 위해서 이 plugin 설정이 필요하다.
이 예제에서 유저의 입력을 받아들이기 위한 form을 사용하기 때문에, 유저의 입력 값을 유효성 검사할 필요하 있다. 여기서는 JSR303 Validation을 사용할 것이기 때문에 명세서를 나타내는 validation-api와 명세서의 구현체인 hibernate-validator를 포함시켰다. hibernate-validator은 자신만의 annotation(@Email, @NotEmpty 등)을 다수 제공한다.
추가적으로, servlet api와 jstl view 사용하기 위한 JSP/Servlet/Jstl dependencies를 포함시켰다.
대체적으로 contatiners는 이러한 libraries를 이미 포함하고 있을 수 있지만, 또한 이러한 것들 위한 ‘provided’로서 scope를 설정할 수 있다.
또한 testing dependencies를 포함했다, Testing 부분은 다음 포스트에서 세부적으로 다룰 것이다.
Step 3: Configure Hibernate
package com.websystique.springmvc.configuration; import java.util.Properties; import javax.sql.DataSource; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.orm.hibernate4.HibernateTransactionManager; import org.springframework.orm.hibernate4.LocalSessionFactoryBean; import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @EnableTransactionManagement @ComponentScan({ "com.websystique.springmvc.configuration" }) @PropertySource(value = {"classpath:application.properties"}) public class HibernateConfiguration { @Autowired private Environment environment; @Bean public LocalSessionFactoryBean sessionFactory(){ LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean(); sessionFactory.setDataSource(dataSource()); sessionFactory.setPackagesToScan(new String[]{"com.websystique.springmvc.model" }); sessionFactory.setHibernateProperties(hibernateProperties()); return sessionFactory; } private Properties hibernateProperties() { Properties properties = new Properties(); properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect")); properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql")); properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql")); return properties; } @Bean public DataSource dataSource(){ DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName")); dataSource.setUrl(environment.getRequiredProperty("jdbc.url")); dataSource.setUsername(environment.getRequiredProperty("jdbc.username")); dataSource.setPassword(environment.getRequiredProperty("jdbc.password")); return dataSource; } @Bean @Autowired public HibernateTransactionManager transactionManager(SessionFactory s){ HibernateTransactionManager txManager = new HibernateTransactionManager(); txManager.setSessionFactory(s); return txManager; } }
@Configuration는 이 클래스가 스프링 container에 의해서 관리 받는 빈을 처리하는 @Bean이 annotated 되어진 하나 이상의 빈 메소드를 포함하고 있는 클래스라는 것을 나타낸다.
@ComponentScan = context:component-scan base-package = “….”
: spring이 관리하는 beans/classes를 찾기 위한 위치를 제공한다.
@EnableTransactionManagement = Spring’s tx*XML namespace
: spring의 annotation-driven transaction management를 가능하게 함
@PropertySource는 Spring run-time Environment에 있는 properties file들(application의 cla1sspath에 정의되어 있음)을 사용하기 위한 선언이며, 다른 application environments에 있는 다른 값을 가지기 위한 유연성을 제공해준다.
메소드 sessionFactory()는 정확하게 XML 기반의 환경설정을 반영하고 있는LocalSessionFactoryBean을 생성했다.
dataSouce와 hibernate Properties 필요하다. @PropertySource 덕분에 properties file에 있는 값을 표면화할 수 있으며, 항목에 일치하는 값을 실행하기 위해서 Spring의 Environment를 사용할 수 있다. 일단 SessionFactory가 생성되면, sessionFactory에 의해서 생성되어지는 sessions이 transaction 지원을 위해서 transactionManager Bean Method에 inject(주입)될 거이다.
아래는 properties file의 내용이다
/src/main/resources/application.properties
jdbc.driverClassName = com.mysql.jdbc.Driver jdbc.url = jdbc:mysql://localhost:3306/selfStudy jdbc.username = selfStudyId jdbc.password = selfStudyPw hibernate.dialect = org.hibernate.dialect.MySQLDialect hibernate.show_sql = true hibernate.format_sql = true
Step 4: Configure Spring MVC
package com.websystique.springmvc.configuration; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.JstlView; @Configuration @EnableWebMvc @ComponentScan(basePackages = "com.websystique.springmvc") public class AppConfig { @Bean public ViewResolver viewResolver(){ InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setViewClass(JstlView.class); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); return viewResolver; } @Bean public MessageSource messageSource(){ ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("messages"); return messageSource; } }
@Configuration은 이 클래스가 하나 이상의 bean method(@Bean은 spring container에 의해서 관리되어질 수 있도록 처리한다)를 포함하고 있다고 나타낸다.
@EnableWebMvc는 xml에 있는 mvc:annotation-driven과 동일하다. 이것은 들어오는 요청을 특정한 메소드에 map하기 위한 @RequestMapping을 사용하는 @Controller-annotated classes를 지원할 수 있게 해준다.
@ComponentScan은 context:component-scan base-package=”…”와 동일하다. 이것은 spring이 관리하는 beans/classes가 어디에 있는지 찾을 수 있게 해준다.
ViewResolver 메소드는 view를 정의하기 위한 view resolver를 환경설정한다.
messageSource method는 properties파일에 있는 [internationalized] message를 지원하기 위한 Message bundle을 환경설정한다.
parameter는 (messages)를 baseName method에 제공했다. 스프링은 classpath에서messages,properties라고 이름지어진 파일을 찾을 것이다.
src/main/resources/messages.properties
Size.employee.name=Name must be between {2} and {1} characters long NotNull.employee.joiningDate=Joining Date can not be blank NotNull.employee.salary=Salary can not be blank Digits.employee.salary=Only numeric data with max 8 digits and with max 2 precision is allowed NotEmpty.employee.ssn=SSN can not be blank typeMismatch=Invalid format non.unique.ssn=SSN {0} already exist. Please fill in different value.
위의 메시지는 다음의 특정한 패턴을 가진다.
{ValidationAnnotationClass}.{modelObject}.{fieldName}
추가적으로, 특정한 annotation(예를들면, @Size)을 기반으로, {0}, {1},….{i}을 사용하는 메시지에 값을 넘겨줄 수 있다.
Step 5: Configure Initializer class
package com.websystique.springmvc.configuration; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; public class AppInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext container) throws ServletException { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(AppConfig.class); ctx.setServletContext(container); ServletRegistration.Dynamic servlet = container.addServlet("dispatcher", new DispatcherServlet(ctx)); servlet.setLoadOnStartup(1); servlet.addMapping("/"); } }
위의 내용은 DispatcherServlet을 사용하는 web.xml의 내용과 유사하다. 또한 Spring configuration file(spring-servlet.xml)을 제공하는 대신에, 여기서는 Configuration class를 등록하는 걸로 대신하고 있다.
Update: 위의 class는 심지어 더욱 간결하게 사용될 수 있는데(더 선호되어지는 방법), 아래처럼 AbstractAnnotationConfigDipatcherServletInitializer 클래스를 사용하는 것이다.
package com.websystique.springmvc.configuration; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class>[] getRootConfigClasses() { return new Class[] { AppConfig.class }; } @Override protected Class>[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
Step 6: Add Controller to handle the requests
package com.websystique.springmvc.controller; import java.util.List; import java.util.Locale; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.websystique.springmvc.model.Employee; import com.websystique.springmvc.service.EmployeeService; @Controller @RequestMapping("/") public class AppController { @Autowired EmployeeService service; @Autowired MessageSource messageSource; //employees 리스트 @RequestMapping(value = {"/", "/list"}, method = RequestMethod.GET) public String listEmployees(ModelMap model){ Listemployees = service.findAllEmployees(); model.addAttribute("employees", employees); return "allemployees"; } //employee 등록 Form @RequestMapping(value = {"/new"}, method = RequestMethod.GET) public String newEmployee(ModelMap model){ Employee employee = new Employee(); model.addAttribute("employee", employee); model.addAttribute("edit", false); return "registration"; } //employee 등록- db 저장 및 유효성 검사 @RequestMapping(value = {"/new"}, method = RequestMethod.POST) public String saveEmployee(@Valid Employee employee, BindingResult result, ModelMap model){ if(result.hasErrors()){ return "registration"; } /* [ssn] 필드의 유일성을 만족하기 위한 선호되어지는 방법은 custom @Unique annotion을 구현하는 것이다. * 그리고 Model class[Employee]의 필드[ssn]에 적용시킬 것이다. * * 아래에서 언급된[if 구간] 부분은, 외부 validation framework에 custom errors 채울 수 있는 것을 보여준다. * 뿐만 아니라 동시에 internationalized messages를 사용할 수 있다. */ if(!service.isEmployeeSsnUnique(employee.getId(), employee.getSsn())){ FieldError ssnError = new FieldError("employee", "ssn", messageSource.getMessage("non.unique.ssn", new String[]{employee.getSsn()}, Locale.getDefault())); result.addError(ssnError); return "registration"; } service.saveEmployee(employee); model.addAttribute("success", "Employee " + employee.getName() + " registered successfully"); return "success"; } //employee 업데이트 Form @RequestMapping(value = {"/edit-{ssn}-employee"}, method = RequestMethod.GET) public String editEmployee(@PathVariable String ssn, ModelMap model){ Employee employee = service.findEmployeeBySsn(ssn); model.addAttribute("employee", employee); model.addAttribute("edit", true); return "registration"; } //employee 업데이트- db 저장 및 유효성 검사 @RequestMapping(value = {"/edit-{ssn}-employee"}, method = RequestMethod.POST) public String updateEmployee(@Valid Employee employee, BindingResult result, ModelMap model, @PathVariable String ssn){ if(result.hasErrors()){ return "registration"; } if(!service.isEmployeeSsnUnique(employee.getId(), employee.getSsn())){ FieldError ssnError = new FieldError("employee", "ssn", messageSource.getMessage("non.unique.ssn", new String[]{employee.getSsn()}, Locale.getDefault())); result.addError(ssnError); return "registration"; } service.updateEmployee(employee); return "success"; } //employee 삭제 @RequestMapping(value = {"/delete-{ssn}-employee"}, method = RequestMethod.GET) public String deleteEmployee(@PathVariable String ssn){ service.deleteEmployeeBySsn(ssn); return "redirect:/list"; } }
@Valid: Spring에게 할당된 object(Employee)를 유효성 검사하라고 요청
BindingResult는 유효성 검사의 결과와 유효성 검사 동안 발생하는 에러를 포함한다.
주의)BindingResult는 유효성 검사되어지는 object 바로 뒤에 따라와야 한다. 그렇지 않으면 spring이 유효성 검사를 할 수 없거나 에러가 발생할 수 있다.
유효성 검사 결과 오류의 경우, custom error messages(step 4에서 설정한)가 보여진다.
우리는 database에 유일값 필드로 선언한 SSN의 유일성을 검사하기 위한 코드를 포함시켰다.
employee를 저장/업데이트하기 전에, SSN이 유일한 값인지 검사를 해야한다.
그렇지 않으면 validation error가 발생하고 registration page로 redirect 되어질 것이다.
@PathVariable: URL template 안에서(우리 코드에서는 SSN) 다양한 값을 연결시킨다.
Step 7: Add DAO Layer
package com.websystique.springmvc.dao; import java.io.Serializable; import java.lang.reflect.ParameterizedType; import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; public class AbstractDao{ private final Class persistentClass; @SuppressWarnings("unchecked") public AbstractDao(){ this.persistentClass = (Class ) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]; //구현 클래스 // 추상클래스 //추상클래스 2번째 제너릭 값 = T } @Autowired private SessionFactory sessionFactory; protected Session getSession(){ return sessionFactory.getCurrentSession(); } protected Criteria createEntityCriteria(){ return getSession().createCriteria(persistentClass); } @SuppressWarnings("unchecked") public T getByKey(PK key){ return (T) getSession().get(persistentClass, key); } public void persist(T entity){ getSession().persist(entity); } public void delete(T entity) { getSession().delete(entity); } }
Generic class는 모든 DAO 구현 클래스를 위한 기반 class이다 공통의 hibernate 작동을 위한 wrapper methods를 제공한다. step 3에서 생성시킨 SessionFactory를 이 부분에서 autowired하였다.
package com.websystique.springmvc.dao; import java.util.List; import com.websystique.springmvc.model.Employee; public interface EmployeeDao { Employee findById(int id); void saveEmployee(Employee employee); void deleteEmployeeBySsn(String ssn); ListfindAllEmployees(); Employee findEmployeeBySsn(String ssn); }
package com.websystique.springmvc.dao; import java.util.List; import org.hibernate.Criteria; import org.hibernate.Query; import org.hibernate.criterion.Restrictions; import org.springframework.stereotype.Repository; import com.websystique.springmvc.model.Employee; @Repository("employeeDao") public class EmployeeDaoImpl extends AbstractDaoimplements EmployeeDao { @Override public Employee findById(int id) { return getByKey(id); } @Override public void saveEmployee(Employee employee) { persist(employee); } @Override public void deleteEmployeeBySsn(String ssn) { Query query = getSession().createSQLQuery("delete from Employee where ssn = :ssn"); query.setString("ssn", ssn); query.executeUpdate(); } @SuppressWarnings("unchecked") @Override public List findAllEmployees() { Criteria criteria = createEntityCriteria(); return (List ) criteria.list(); } @Override public Employee findEmployeeBySsn(String ssn) { Criteria criteria = createEntityCriteria(); criteria.add(Restrictions.eq("ssn", ssn)); return (Employee) criteria.uniqueResult(); } }
Step 8: Add Service Layer
package com.websystique.springmvc.service; import java.util.List; import com.websystique.springmvc.model.Employee; public interface EmployeeService { Employee findById(int id); void saveEmployee(Employee employee); void updateEmployee(Employee employee); void deleteEmployeeBySsn(String ssn); ListfindAllEmployees(); Employee findEmployeeBySsn(String ssn); boolean isEmployeeSsnUnique(Integer id, String ssn); }
package com.websystique.springmvc.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.websystique.springmvc.dao.EmployeeDao; import com.websystique.springmvc.model.Employee; @Service("employeeService") @Transactional public class EmployeeServiceImpl implements EmployeeService { @Autowired private EmployeeDao dao; @Override public Employee findById(int id) { return dao.findById(id); } @Override public void saveEmployee(Employee employee) { dao.saveEmployee(employee); } /* * Transaction과 함께 실행되기 떄문에, hibernate update를 명확하게 호출할 필요는 없다. * 단지 db의 entity 객체를 실행시키고 적절한 값으로 그 객체를 업데이트 하면 된다. * transaction이 끝나면, db의 값이 업데이트될 것이다. */ public void updateEmployee(Employee employee) { Employee entity = dao.findById(employee.getId()); if(entity!=null){ entity.setName(employee.getName()); entity.setJoiningDate(employee.getJoiningDate()); entity.setSalary(employee.getSalary()); entity.setSsn(employee.getSsn()); } } @Override public void deleteEmployeeBySsn(String ssn) { dao.deleteEmployeeBySsn(ssn); } @Override public ListfindAllEmployees() { return dao.findAllEmployees(); } @Override public Employee findEmployeeBySsn(String ssn) { return dao.findEmployeeBySsn(ssn); } @Override public boolean isEmployeeSsnUnique(Integer id, String ssn) { Employee employee = findEmployeeBySsn(ssn); return (employee == null || ((id != null) && (employee.getId() == id))); } }
위에서 가장 흥미로운 부분은 @Transactional이다. @Transcation은 각각의 메소드 시작으로 transaction을 시작하며 각각의 메소드 완료 후 commit을 한다(메소드가 오류로 실패하면, 롤백).
transaction이 method 범위에 있고 그 범위 안에서 DAO를 사용하면, DAO method는 같은 transaction 안에서 실행되어질 것이다.
Step 9: Create Domain Entity Class(POJO)
package com.websystique.springmvc.model; import java.math.BigDecimal; 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.validation.constraints.Digits; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import org.hibernate.annotations.Type; import org.hibernate.validator.constraints.NotEmpty; import org.joda.time.LocalDate; import org.springframework.format.annotation.DateTimeFormat; @Entity @Table(name = "EMPLOYEE") public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Size(min = 3, max = 50) @Column(name = "NAME", nullable = false) private String name; @NotNull @DateTimeFormat(pattern = "dd/MM/yyyy") @Column(name = "JOINING_DATE", nullable = false) @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate") private LocalDate joiningDate; @NotNull @Digits(integer = 8, fraction = 2) @Column(name = "SALARY", nullable = false) private BigDecimal salary; @NotEmpty @Column(name = "SSN", unique = true, nullable = false) private String ssn; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public LocalDate getJoiningDate() { return joiningDate; } public void setJoiningDate(LocalDate joiningDate) { this.joiningDate = joiningDate; } public BigDecimal getSalary() { return salary; } public void setSalary(BigDecimal salary) { this.salary = salary; } public String getSsn() { return ssn; } public void setSsn(String ssn) { this.ssn = ssn; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof Employee)) return false; Employee other = (Employee) obj; if (id != other.id) return false; if (ssn == null) { if (other.ssn != null) return false; } else if (!ssn.equals(other.ssn)) return false; return true; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; result = prime * result + ((ssn == null) ? 0 : ssn.hashCode()); return result; } @Override public String toString() { return "Employee [id=" + id + ", name=" + name + ", joiningDate=" + joiningDate + ", salary=" + salary + ", ssn=" + ssn + "]"; } }
이 클래스는 JPA annotation @Entity, @Table, @Column가 annotated된 표준 Entity class이다.
@Column 과 함께 사용된 @Type은 database date type과 Joda-Time LocalDate 사이의 mapping이 가능하도록 제공하고 있다.
@DateTimeFormat은 주어진 형태의 date time으로써 필드가 형성되어지도록 선언한 스프링의 annoation이다.
Step 10: Add Views/JSP’s
WEB-INF/views/allemployees.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix = "c" uri = "http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>University Enrollments</title> <style> tr:first-child{ font-weight: bold; background-color: #C6C9C4; } </style> </head> <body> <h2>List of Employees</h2> <table> <tr> <td>NAME</td><td>Joining Date</td><td>Salary</td><td>SSN</td><td></td> </tr> <c:forEach items="${employees}" var="employee"> <tr> <td>${employee.name}</td> <td>${employee.joiningDate}</td> <td>${employee.salary}</td> <td><a href="<c:url value='/edit-${employee.ssn}-employee' />">${employee.ssn}</a></td> <td><a href="<c:url value='/delete-${employee.ssn}-employee' />">delete</a></td> </tr> </c:forEach> </table> <br/> <a href="<c:url value='/new' />">Add New Employee</a> </body>
WEB-INF/views/registration.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Employee Registration Form</title> <style> .error { color: #ff0000; } </style> </head> <body> <h2>Registration Form</h2> <form:form method="POST" modelAttribute="employee"> <form:input type="hidden" path="id" id="id" /> <table> <tr> <td><label for="name">Name: </label></td> <td><form:input path="name" id="name" /></td> <td><form:errors path="name" cssClass="error" /></td> </tr> <tr> <td><label for="joiningDate">Joining Date: </label></td> <td><form:input path="joiningDate" id="joiningDate" /></td> <td><form:errors path="joiningDate" cssClass="error" /></td> </tr> <tr> <td><label for="salary">Salary: </label></td> <td><form:input path="salary" id="salary" /></td> <td><form:errors path="salary" cssClass="error" /></td> </tr> <tr> <td><label for="ssn">SSN: </label></td> <td><form:input path="ssn" id="ssn" /></td> <td><form:errors path="ssn" cssClass="error" /></td> </tr> <tr> <td colspan="3"><c:choose> <c:when test="${edit }"> <input type="submit" value="Update" /> </c:when> <c:otherwise> <input type="submit" value="Register" /> </c:otherwise> </c:choose></td> </tr> </table> </form:form> <br /> <br /> Go back to <a href="<c:url value='/list' />">List of All Employees</a> </body> </html>
WEB-INF/views/success.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Registration Confirmation Page</title> </head> <body> message : ${success } <br /> <br /> Go back to <a href = "<c:url value = '/list' />">List of All Employees</a> </body> </html>
Step 11: Create Schema in database
CREATE TABLE EMPLOYEE( id INT NOT NULL auto_increment, name VARCHAR(50) NOT NULL, joining_date DATE NOT NULL, salary DOUBLE NOT NULL, ssn VARCHAR(30) NOT NULL UNIQUE, PRIMARY KEY (id) );
'SPRING' 카테고리의 다른 글
다음 에디터 적용(JSP) -기본설치, 이미지 ,파일첨부 (0) | 2018.01.12 |
---|---|
스프링 빈 등록 방법 (0) | 2017.12.09 |
@Autowired, @Resource, @Inject의 차이 (0) | 2017.12.06 |
@Controller VS @RestController, ResponseEntity (0) | 2017.12.06 |
Spring 4 MVC Form Validation and Resource Handling (Annotations) (0) | 2017.12.05 |