現(xiàn)在,讓我們開始
<?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <groupId>com.yiibai.springmvc</groupId> <artifactId>SpringHibernateExample</artifactId> <packaging>war</packaging> <version>1.0.0</version> <name>SpringHibernateExample</name> <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-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 插件聲明。由于我們使用的是全注解的配置,所以不包函 web.xml 文件在項(xiàng)目中,所以我們需要配置這個(gè)插件以避免 Maven 構(gòu)建 war 包失敗。因?yàn)樵谶@個(gè)例子中,我們將用一個(gè)表單來接受來自用戶的輸入,我們也需要驗(yàn)證用戶的輸入。在這里我們將選擇JSR303驗(yàn)證,所以我們包括驗(yàn)證,API 代表了規(guī)范,hibernate-validator它代表本規(guī)范的實(shí)現(xiàn)。hibernate-validator 還提供了一些它自己的注解(@Email,@NotEmpty等)不屬于規(guī)范的一部分。
伴隨著這一點(diǎn),我們也包括 JSP/Servlet/Jstl 依賴關(guān)系,也將需要為使用的 servlet API和JSTL視圖在代碼中。在一般情況下,容器可能已經(jīng)包含了這些庫,從而在 pom.xml 中“提供”了我們可以設(shè)置的范圍。
com.yiibai.springmvc.configuration.HibernateConfiguration
package com.yiibai.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.yiibai.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.yiibai.springmvc.model" }); sessionFactory.setHibernateProperties(hibernateProperties()); return sessionFactory; } @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; } 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 @Autowired public HibernateTransactionManager transactionManager(SessionFactory s) { HibernateTransactionManager txManager = new HibernateTransactionManager(); txManager.setSessionFactory(s); return txManager; } }
@Configuration表示該類包含注解為 @Bean生產(chǎn)Bean管理是由Spring容器的一個(gè)或多個(gè)bean的方法。在我們的例子中,這個(gè)類代表hibernate配置。
@ComponentScan 相當(dāng)于 context:component-scan base-package="..." 在xml文件中配置, 提供Spring在哪里尋找管理 beans/classes。
@EnableTransactionManagement 相當(dāng)于 Spring’s tx:* XML 命名空間, 使Spring注解驅(qū)動事務(wù)管理能力。
@PropertySource 用于聲明一組屬性(在屬性中定義的應(yīng)用程序類路徑文件)在Spring運(yùn)行時(shí) Environment, 提供了靈活性,可以在不同的應(yīng)用環(huán)境的不同值。
/src/main/resources/application.properties
jdbc.driverClassName = com.mysql.jdbc.Driver jdbc.url = jdbc:mysql://localhost:3306/yiibai jdbc.username = root jdbc.password = passwd123 hibernate.dialect = org.hibernate.dialect.MySQLDialect hibernate.show_sql = true hibernate.format_sql = true
com.yiibai.springmvc.configuration.AppConfig
package com.yiibai.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.yiibai.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; } }
在這篇文章中,我們提交表單并驗(yàn)證用戶輸入(通過JSR303注解)。在校驗(yàn)失敗后,默認(rèn)的錯(cuò)誤消息會顯示。要通過自己的自定義覆蓋默認(rèn)的[國際化]從外部消息包的消息[.properties文件],我們需要配置一個(gè)ResourceBundleMessageSource。messageSource方法有同樣的目的。請注意,以basename方法提供的參數(shù)(消息)。Spring將搜索應(yīng)用程序類路徑中一個(gè)名為 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}
此外,根據(jù)具體注解(如@Size),你也可以用傳遞參數(shù)給這些消息{0},{1},..{i}索引。
com.yiibai.springmvc.configuration.AppInitializer
package com.yiibai.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 { 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("/"); } }
上面的內(nèi)容類似于web.xml,因?yàn)槲覀兪褂玫氖乔岸丝刂破?nbsp;DispatcherServlet 的內(nèi)容,分配映射(URL模式的XML),而不是提供給Spring配置文件(spring-servlet.xml)的路徑,在這里我們正在注冊的配置類。
更新:請注意,上面的類可以寫成更加簡潔[最佳方法],通過擴(kuò)展 AbstractAnnotationConfigDispatcherServletInitializer 基類,如下所示:
package com.yiibai.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[] { "/" }; } }
com.yiibai.springmvc.controller.AppController
package com.yiibai.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.yiibai.springmvc.model.Employee; import com.yiibai.springmvc.service.EmployeeService; @Controller @RequestMapping("/") public class AppController { @Autowired EmployeeService service; @Autowired MessageSource messageSource; /* * This method will list all existing employees. */ @RequestMapping(value = { "/", "/list" }, method = RequestMethod.GET) public String listEmployees(ModelMap model) { List<Employee> employees = service.findAllEmployees(); model.addAttribute("employees", employees); return "allemployees"; } /* * This method will provide the medium to add a new employee. */ @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"; } /* * This method will be called on form submission, handling POST request for * saving employee in database. It also validates the user input */ @RequestMapping(value = { "/new" }, method = RequestMethod.POST) public String saveEmployee(@Valid Employee employee, BindingResult result, ModelMap model) { if (result.hasErrors()) { return "registration"; } /* * Preferred way to achieve uniqueness of field [ssn] should be implementing custom @Unique annotation * and applying it on field [ssn] of Model class [Employee]. * * Below mentioned peace of code [if block] is to demonstrate that you can fill custom errors outside the validation * framework as well while still using 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"; } /* * This method will provide the medium to update an existing employee. */ @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"; } /* * This method will be called on form submission, handling POST request for * updating employee in database. It also validates the user input */ @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); model.addAttribute("success", "Employee " + employee.getName() + " updated successfully"); return "success"; } /* * This method will delete an employee by it's SSN value. */ @RequestMapping(value = { "/delete-{ssn}-employee" }, method = RequestMethod.GET) public String deleteEmployee(@PathVariable String ssn) { service.deleteEmployeeBySsn(ssn); return "redirect:/list"; } }
這是一個(gè)非常直接的基于Spring的控制器。 @Controller表明這個(gè)類是一個(gè)控制器在處理與模式映射@RequestMapping請求。這里用“/”,它被作為默認(rèn)的控制器。
listEmployees方法標(biāo)注了@ RequestMethod.GET,同時(shí)處理默認(rèn)的網(wǎng)址 “/” 和 ‘/list’。它充當(dāng)處理應(yīng)用初始頁面,顯示現(xiàn)有雇員的列表。
newEmployee方法處理新員工注冊頁面的GET請求, 表示通過模型 Employee 對象支持頁面。
方法 saveEmployee 被注解為@ RequestMethod.POST,并且將處理新員工登記表單提交 POST 請求 (‘/new’)。注間這個(gè)方法的參數(shù)和它們的順序。
@Valid要求Spring來驗(yàn)證相關(guān)的對象(Employee)。 BindingResult包含此驗(yàn)證,并可能在此驗(yàn)證過程中發(fā)生任何錯(cuò)誤的結(jié)果。請注意,BindingResult必須出現(xiàn)在驗(yàn)證對象,否則Spring將無法驗(yàn)證并且拋出一個(gè)異常。 如果驗(yàn)證失敗,自定義錯(cuò)誤信息(因?yàn)槲覀円呀?jīng)配置在步驟4)中顯示。
我們還包括代碼檢查SSN唯一性,因?yàn)樗暶饕跀?shù)據(jù)庫中具有唯一必。保存/更新員工之前要檢查,如果SSN是否獨(dú)一無二。如果沒有,我們生成驗(yàn)證錯(cuò)誤和重定向到注冊頁面。 這個(gè)代碼展示出一種方式來填充在自定義錯(cuò)誤校驗(yàn)框架之外,同時(shí)仍使用國際化的信息。
com.yiibai.springmvc.dao.AbstractDao
package com.yiibai.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 abstract class AbstractDao<PK extends Serializable, T> { private final Class<T> persistentClass; @SuppressWarnings("unchecked") public AbstractDao(){ this.persistentClass =(Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]; } @Autowired private SessionFactory sessionFactory; protected Session getSession(){ return sessionFactory.getCurrentSession(); } @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); } protected Criteria createEntityCriteria(){ return getSession().createCriteria(persistentClass); } }
這個(gè)通用類是所有的DAO實(shí)現(xiàn)類的基類。它提供包裝方法也是常見的hibernate 操作。
com.yiibai.springmvc.dao.EmployeeDao
package com.yiibai.springmvc.dao; import java.util.List; import com.yiibai.springmvc.model.Employee; public interface EmployeeDao { Employee findById(int id); void saveEmployee(Employee employee); void deleteEmployeeBySsn(String ssn); List<Employee> findAllEmployees(); Employee findEmployeeBySsn(String ssn); }
com.yiibai.springmvc.dao.EmployeeDaoImpl
package com.yiibai.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.yiibai.springmvc.model.Employee; @Repository("employeeDao") public class EmployeeDaoImpl extends AbstractDao<Integer, Employee> implements EmployeeDao { public Employee findById(int id) { return getByKey(id); } public void saveEmployee(Employee employee) { persist(employee); } public void deleteEmployeeBySsn(String ssn) { Query query = getSession().createSQLQuery("delete from Employee where ssn = :ssn"); query.setString("ssn", ssn); query.executeUpdate(); } @SuppressWarnings("unchecked") public List<Employee> findAllEmployees() { Criteria criteria = createEntityCriteria(); return (List<Employee>) criteria.list(); } public Employee findEmployeeBySsn(String ssn) { Criteria criteria = createEntityCriteria(); criteria.add(Restrictions.eq("ssn", ssn)); return (Employee) criteria.uniqueResult(); } }
com.yiibai.springmvc.service.EmployeeService
package com.yiibai.springmvc.service; import java.util.List; import com.yiibai.springmvc.model.Employee; public interface EmployeeService { Employee findById(int id); void saveEmployee(Employee employee); void updateEmployee(Employee employee); void deleteEmployeeBySsn(String ssn); List<Employee> findAllEmployees(); Employee findEmployeeBySsn(String ssn); boolean isEmployeeSsnUnique(Integer id, String ssn); }
com.yiibai.springmvc.service.EmployeeServiceImpl
package com.yiibai.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.yiibai.springmvc.dao.EmployeeDao; import com.yiibai.springmvc.model.Employee; @Service("employeeService") @Transactional public class EmployeeServiceImpl implements EmployeeService { @Autowired private EmployeeDao dao; public Employee findById(int id) { return dao.findById(id); } public void saveEmployee(Employee employee) { dao.saveEmployee(employee); } /* * Since the method is running with Transaction, No need to call hibernate update explicitly. * Just fetch the entity from db and update it with proper values within transaction. * It will be updated in db once transaction ends. */ public void updateEmployee(Employee employee) { Employee entity = dao.findById(employee.getId()); if(entity!=null){ entity.setName(employee.g