前言
譯文鏈接:http://websystique.com/spring/spring4-hibernate4-mysql-maven-integration-example-using-annotations/
本文將基於注解配置, 集成Spring 4和Hibernate 4,開發一個增刪改查應用,涉及以下內容:
- 創建Hibernate實體
- 保存數據到mysql數據庫中
- 在事務
transaction
內部執行增刪改查操作 - 典型企業應用中不同層之間的交互
- 基於注解配置
當然,我們也會給出XML配置作為對比。
如果你的應用是基於Spring MVC開發,那么可以參考該鏈接:Spring4 MVC Hibernate and MySQL integration.
涉及的技術及開發工具
- Spring 4.0.6.RELEASE
- Hibernate Core 4.3.6.Final
- MySQL Server 5.6
- Joda-time 2.3
- Maven 3
- JDK 1.6
- Eclipse JUNO Service Release 2
工程結構目錄
步驟一:往pom.xml中添加依賴
<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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.websystique.spring</groupId> <artifactId>Spring4HibernateExample</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <name>Spring4HibernateExample</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> </properties> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</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> <!-- 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> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </pluginManagement> </build> </project>
很明顯我們需要添加Spring、Hibernate和Mysql連接器相關依賴,另外,由於我們使用了joda-time庫來處理時間,所以也引入了joda-time依賴。usertype-core庫引入是為了提供數據庫時間類型與joda-time LocalDate之間的映射。
步驟二:配置Hibernate
com.websystique.spring.configuration.HibernateConfiguration
package com.websystique.spring.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.spring.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.spring.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
標注的方法,這些被@Bean
標注的方法可以生成bean並交由spring容器管理,在這里例子中,這個類代表了hibernate的配置。
@ComponentScan注解
與xml配置中的“context:component-scan base-package="..."
”等價,提供了掃描bean的包路徑。
@EnableTransactionManagement
注解與xml配置中Spring的tx:*命名空間等價,主要用於開啟基於注解的事務管理。
@PropertySource
注解用於在Spring運行時Environment
中聲明一組屬性(在應用classpath路徑下的properties文件中定義),可根據不同環境靈活改變屬性值。
sessionFactory()
方法創建了一個LocalSessionFactoryBean
,與基於XML的配置類似,我們需要一個數據源dataSource和hibernate配置文件(如hibernate.properties)。
多虧了@PropertySource
注解,我們可以從.properties文件中得到具體屬性值,使用Spring的Environment
接口獲取對應項目的配置值。
一旦創建了SessionFactory,該bean將會被注入到transactionManager
方法中,最終對sessionFactory創建的sessions提供事務支持功能。
如下是本文使用的屬性配置文件:
/src/main/resources/application.properties
jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/websystique
jdbc.username = myuser
jdbc.password = mypassword
hibernate.dialect = org.hibernate.dialect.MySQLDialect
hibernate.show_sql = false
hibernate.format_sql = false
另外,對應的基於XML配置如下
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"> <context:property-placeholder location="classpath:application.properties" /> <context:component-scan base-package="com.websystique.spring" /> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}"/> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" > <property name="dataSource" ref="dataSource"/> <property name="packagesToScan"> <list> <value>com.websystique.spring.model</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">${hibernate.dialect}</prop> <prop key="hibernate.show_sql">${hibernate.show_sql:false}</prop> <prop key="hibernate.format_sql">${hibernate.format_sql:false}</prop> </props> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="persistenceExceptionTranslationPostProcessor" class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/> </beans>
步驟三:Spring配置
com.websystique.spring.configuration.AppConfig
package com.websystique.spring.configuration; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackages = "com.websystique.spring") public class AppConfig { }
在我們這個示例中,即使該配置類內部是空的,但是使用了@ComponentScan注解,可以自動檢測到對應包下所有的beans。
其實你可以完全去除以上的配置,將bean掃描功能放在application context級別去實現(main方法里)。
在成熟的應用里,你會發現使用配置類配置beans(如messageSource、PropertySourcesPlaceHolderConfigurer...)非常方便。
對應的基於XML的配置
<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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scan base-package="com.websystique.spring" /> </beans>
以上就是關於本工程的所有配置了,現在,為了讓工程能跑起來,我們還需要添加service、dao層,實體對象,數據庫。
步驟四:DAO層
com.websystique.spring.dao.AbstractDao
package com.websystique.spring.dao; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; public abstract class AbstractDao { @Autowired private SessionFactory sessionFactory; protected Session getSession() { return sessionFactory.getCurrentSession(); } public void persist(Object entity) { getSession().persist(entity); } public void delete(Object entity) { getSession().delete(entity); } }
注意,我們在步驟二創建的SessionFactory會被自動裝配到這里,這個類將作為基類用於執行數據庫相關操作。
com.websystique.spring.dao.EmployeeDao
package com.websystique.spring.dao; import java.util.List; import com.websystique.spring.model.Employee; public interface EmployeeDao { void saveEmployee(Employee employee); List<Employee> findAllEmployees(); void deleteEmployeeBySsn(String ssn); Employee findBySsn(String ssn); void updateEmployee(Employee employee); }
com.websystique.spring.dao.EmployeeDaoImpl
package com.websystique.spring.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.spring.model.Employee; @Repository("employeeDao") public class EmployeeDaoImpl extends AbstractDao implements EmployeeDao{ public void saveEmployee(Employee employee) { persist(employee); } @SuppressWarnings("unchecked") public List<Employee> findAllEmployees() { Criteria criteria = getSession().createCriteria(Employee.class); return (List<Employee>) criteria.list(); } public void deleteEmployeeBySsn(String ssn) { Query query = getSession().createSQLQuery("delete from Employee where ssn = :ssn"); query.setString("ssn", ssn); query.executeUpdate(); } public Employee findBySsn(String ssn){ Criteria criteria = getSession().createCriteria(Employee.class); criteria.add(Restrictions.eq("ssn",ssn)); return (Employee) criteria.uniqueResult(); } public void updateEmployee(Employee employee){ getSession().update(employee); } }
步驟五:添加Service層代碼
com.websystique.spring.service.EmployeeService
package com.websystique.spring.service; import java.util.List; import com.websystique.spring.model.Employee; public interface EmployeeService { void saveEmployee(Employee employee); List<Employee> findAllEmployees(); void deleteEmployeeBySsn(String ssn); Employee findBySsn(String ssn); void updateEmployee(Employee employee); }
com.websystique.spring.service.EmployeeServiceImpl
package com.websystique.spring.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.spring.dao.EmployeeDao; import com.websystique.spring.model.Employee; @Service("employeeService") @Transactional public class EmployeeServiceImpl implements EmployeeService{ @Autowired private EmployeeDao dao; public void saveEmployee(Employee employee) { dao.saveEmployee(employee); } public List<Employee> findAllEmployees() { return dao.findAllEmployees(); } public void deleteEmployeeBySsn(String ssn) { dao.deleteEmployeeBySsn(ssn); } public Employee findBySsn(String ssn) { return dao.findBySsn(ssn); } public void updateEmployee(Employee employee){ dao.updateEmployee(employee); } }
以上比較引人注目的部分是@Transactional
注解,配置了該注解的類會在每個類方法開啟事務,並在方法結束的時候提交事務(或者在方法內部出錯時回滾事務)。
注意,由於以上事務范圍是方法級別的,我們在方法內部使用DAO,DAO方法會在同樣的事物內部執行。
步驟六:創建實體類(POJO)
com.websystique.spring.model.Employee
package com.websystique.spring.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 org.hibernate.annotations.Type; import org.joda.time.LocalDate; @Entity @Table(name="EMPLOYEE") public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Column(name = "NAME", nullable = false) private String name; @Column(name = "JOINING_DATE", nullable = false) @Type(type="org.jadira.usertype.dateandtime.joda.PersistentLocalDate") private LocalDate joiningDate; @Column(name = "SALARY", nullable = false) private BigDecimal salary; @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 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 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 String toString() { return "Employee [id=" + id + ", name=" + name + ", joiningDate=" + joiningDate + ", salary=" + salary + ", ssn=" + ssn + "]"; } }
這是一個標准的實體類,基於JPA注解@Entity
, @Table
, @Column以及hibernate注解
@Type(用於提供數據庫類型與Joda-Time
LocalDate
的映射)。
步驟七:在數據庫里創建Schema
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) );
步驟八:創建main方法執行程序
package com.websystique.spring; import java.math.BigDecimal; import java.util.List; import org.joda.time.LocalDate; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.AbstractApplicationContext; import com.websystique.spring.configuration.AppConfig; import com.websystique.spring.model.Employee; import com.websystique.spring.service.EmployeeService; public class AppMain { public static void main(String args[]) { AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); EmployeeService service = (EmployeeService) context.getBean("employeeService"); /* * Create Employee1 */ Employee employee1 = new Employee(); employee1.setName("Han Yenn"); employee1.setJoiningDate(new LocalDate(2010, 10, 10)); employee1.setSalary(new BigDecimal(10000)); employee1.setSsn("ssn00000001"); /* * Create Employee2 */ Employee employee2 = new Employee(); employee2.setName("Dan Thomas"); employee2.setJoiningDate(new LocalDate(2012, 11, 11)); employee2.setSalary(new BigDecimal(20000)); employee2.setSsn("ssn00000002"); /* * Persist both Employees */ service.saveEmployee(employee1); service.saveEmployee(employee2); /* * Get all employees list from database */ List<Employee> employees = service.findAllEmployees(); for (Employee emp : employees) { System.out.println(emp); } /* * delete an employee */ service.deleteEmployeeBySsn("ssn00000002"); /* * update an employee */ Employee employee = service.findBySsn("ssn00000001"); employee.setSalary(new BigDecimal(50000)); service.updateEmployee(employee); /* * Get all employees list from database */ List<Employee> employeeList = service.findAllEmployees(); for (Employee emp : employeeList) { System.out.println(emp); } context.close(); } }
注意,假如你想刪除AppConfig文件,那么只需將
AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
替換為
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.scan("com.websystique.spring");
context.refresh();
Employee [id=1, name=Han Yenn, joiningDate=2010-10-10, salary=10000, ssn=ssn00000001]
Employee [id=2, name=Dan Thomas, joiningDate=2012-11-11, salary=20000, ssn=ssn00000002]
Employee [id=1, name=Han Yenn, joiningDate=2010-10-10, salary=50000, ssn=ssn00000001]
工程代碼
http://websystique.com/?smd_process_download=1&download_id=802
注:源碼EmployeeDaoImpl類中有一處錯誤,將26行表名Employee改成EMPLOYEE即可。