前言
本文将基于注解配置, 集成Spring 4和Hibernate 4,开发一个增删改查应用,涉及以下内容:
创建Hibernate实体
保存数据到mysql数据库中
在事务transaction内部执行增删改查操作
典型企业应用中不同层之间的交互
基于注解配置
当然,我们也会给出XML配置作为对比。
涉及的技术及开发工具
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中添加依赖
4.0.0
com.websystique.spring
Spring4HibernateExample
1.0.0
jar
Spring4HibernateExample
4.0.6.RELEASE
4.3.6.Final
5.1.31
2.3
org.springframework
spring-core
${springframework.version}
org.springframework
spring-context
${springframework.version}
org.springframework
spring-tx
${springframework.version}
org.springframework
spring-orm
${springframework.version}
org.hibernate
hibernate-core
${hibernate.version}
mysql
mysql-connector-java
${mysql.connector.version}
joda-time
joda-time
${joda-time.version}
org.jadira.usertype
usertype.core
3.0.0.CR1
org.apache.maven.plugins
maven-compiler-plugin
3.2
1.6
1.6
很明显我们需要添加Spring、Hibernate和Mysql连接器相关依赖,另外,由于我们使用了joda-time库来处理时间,所以也引入了joda-time依赖。usertype-core库引入是为了提供数据库时间类型与joda-time LocalDate之间的映射。
步骤二:配置Hibernate
com.websystique.spring.configuration.HibernateConfiguration
packagecom.websystique.spring.configuration;importjava.util.Properties;importjavax.sql.DataSource;importorg.hibernate.SessionFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.ComponentScan;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.PropertySource;importorg.springframework.core.env.Environment;importorg.springframework.jdbc.datasource.DriverManagerDataSource;importorg.springframework.orm.hibernate4.HibernateTransactionManager;importorg.springframework.orm.hibernate4.LocalSessionFactoryBean;importorg.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
@ComponentScan({"com.websystique.spring.configuration"})
@PropertySource(value= { "classpath:application.properties"})public classHibernateConfiguration {
@AutowiredprivateEnvironment environment;
@BeanpublicLocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory= newLocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(new String[] { "com.websystique.spring.model"});
sessionFactory.setHibernateProperties(hibernateProperties());returnsessionFactory;
}
@BeanpublicDataSource dataSource() {
DriverManagerDataSource dataSource= newDriverManagerDataSource();
dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));returndataSource;
}privateProperties hibernateProperties() {
Properties properties= newProperties();
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"));returnproperties;
}
@Bean
@AutowiredpublicHibernateTransactionManager transactionManager(SessionFactory s) {
HibernateTransactionManager txManager= newHibernateTransactionManager();
txManager.setSessionFactory(s);returntxManager;
}
}
@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配置如下
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">
com.websystique.spring.model
${hibernate.dialect}
${hibernate.show_sql:false}
${hibernate.format_sql:false}
步骤三:Spring配置
com.websystique.spring.configuration.AppConfig
packagecom.websystique.spring.configuration;importorg.springframework.context.annotation.ComponentScan;importorg.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages= "com.websystique.spring")public classAppConfig {
}
在我们这个示例中,即使该配置类内部是空的,但是使用了@ComponentScan注解,可以自动检测到对应包下所有的beans。
其实你可以完全去除以上的配置,将bean扫描功能放在application context级别去实现(main方法里)。
在成熟的应用里,你会发现使用配置类配置beans(如messageSource、PropertySourcesPlaceHolderConfigurer...)非常方便。
对应的基于XML的配置
以上就是关于本工程的所有配置了,现在,为了让工程能跑起来,我们还需要添加service、dao层,实体对象,数据库。
步骤四:DAO层
com.websystique.spring.dao.AbstractDao
packagecom.websystique.spring.dao;importorg.hibernate.Session;importorg.hibernate.SessionFactory;importorg.springframework.beans.factory.annotation.Autowired;public abstract classAbstractDao {
@AutowiredprivateSessionFactory sessionFactory;protectedSession getSession() {returnsessionFactory.getCurrentSession();
}public voidpersist(Object entity) {
getSession().persist(entity);
}public voiddelete(Object entity) {
getSession().delete(entity);
}
}
注意,我们在步骤二创建的SessionFactory会被自动装配到这里,这个类将作为基类用于执行数据库相关操作。
com.websystique.spring.dao.EmployeeDao
packagecom.websystique.spring.dao;importjava.util.List;importcom.websystique.spring.model.Employee;public interfaceEmployeeDao {voidsaveEmployee(Employee employee);
ListfindAllEmployees();voiddeleteEmployeeBySsn(String ssn);
Employee findBySsn(String ssn);voidupdateEmployee(Employee employee);
}
com.websystique.spring.dao.EmployeeDaoImpl
packagecom.websystique.spring.dao;importjava.util.List;importorg.hibernate.Criteria;importorg.hibernate.Query;importorg.hibernate.criterion.Restrictions;importorg.springframework.stereotype.Repository;importcom.websystique.spring.model.Employee;
@Repository("employeeDao")public class EmployeeDaoImpl extends AbstractDao implementsEmployeeDao{public voidsaveEmployee(Employee employee) {
persist(employee);
}
@SuppressWarnings("unchecked")public ListfindAllEmployees() {
Criteria criteria= getSession().createCriteria(Employee.class);return (List) criteria.list();
}public voiddeleteEmployeeBySsn(String ssn) {
Query query= getSession().createSQLQuery("delete from Employee where ssn = :ssn");
query.setString("ssn", ssn);
query.executeUpdate();
}publicEmployee findBySsn(String ssn){
Criteria criteria= getSession().createCriteria(Employee.class);
criteria.add(Restrictions.eq("ssn",ssn));return(Employee) criteria.uniqueResult();
}public voidupdateEmployee(Employee employee){
getSession().update(employee);
}
}
步骤五:添加Service层代码
com.websystique.spring.service.EmployeeService
packagecom.websystique.spring.service;importjava.util.List;importcom.websystique.spring.model.Employee;public interfaceEmployeeService {voidsaveEmployee(Employee employee);
ListfindAllEmployees();voiddeleteEmployeeBySsn(String ssn);
Employee findBySsn(String ssn);voidupdateEmployee(Employee employee);
}
com.websystique.spring.service.EmployeeServiceImpl
packagecom.websystique.spring.service;importjava.util.List;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Transactional;importcom.websystique.spring.dao.EmployeeDao;importcom.websystique.spring.model.Employee;
@Service("employeeService")
@Transactionalpublic class EmployeeServiceImpl implementsEmployeeService{
@AutowiredprivateEmployeeDao dao;public voidsaveEmployee(Employee employee) {
dao.saveEmployee(employee);
}public ListfindAllEmployees() {returndao.findAllEmployees();
}public voiddeleteEmployeeBySsn(String ssn) {
dao.deleteEmployeeBySsn(ssn);
}publicEmployee findBySsn(String ssn) {returndao.findBySsn(ssn);
}public voidupdateEmployee(Employee employee){
dao.updateEmployee(employee);
}
}
以上比较引人注目的部分是@Transactional注解,配置了该注解的类会在每个类方法开启事务,并在方法结束的时候提交事务(或者在方法内部出错时回滚事务)。
注意,由于以上事务范围是方法级别的,我们在方法内部使用DAO,DAO方法会在同样的事物内部执行。
步骤六:创建实体类(POJO)
com.websystique.spring.model.Employee
packagecom.websystique.spring.model;importjava.math.BigDecimal;importjavax.persistence.Column;importjavax.persistence.Entity;importjavax.persistence.GeneratedValue;importjavax.persistence.GenerationType;importjavax.persistence.Id;importjavax.persistence.Table;importorg.hibernate.annotations.Type;importorg.joda.time.LocalDate;
@Entity
@Table(name="EMPLOYEE")public classEmployee {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)private intid;
@Column(name= "NAME", nullable = false)privateString name;
@Column(name= "JOINING_DATE", nullable = false)
@Type(type="org.jadira.usertype.dateandtime.joda.PersistentLocalDate")privateLocalDate joiningDate;
@Column(name= "SALARY", nullable = false)privateBigDecimal salary;
@Column(name= "SSN", unique=true, nullable = false)privateString ssn;public intgetId() {returnid;
}public void setId(intid) {this.id =id;
}publicString getName() {returnname;
}public voidsetName(String name) {this.name =name;
}publicLocalDate getJoiningDate() {returnjoiningDate;
}public voidsetJoiningDate(LocalDate joiningDate) {this.joiningDate =joiningDate;
}publicBigDecimal getSalary() {returnsalary;
}public voidsetSalary(BigDecimal salary) {this.salary =salary;
}publicString getSsn() {returnssn;
}public voidsetSsn(String ssn) {this.ssn =ssn;
}
@Overridepublic inthashCode() {final int prime = 31;int result = 1;
result= prime * result +id;
result= prime * result + ((ssn == null) ? 0: ssn.hashCode());returnresult;
}
@Overridepublic booleanequals(Object obj) {if (this ==obj)return true;if (obj == null)return false;if (!(obj instanceofEmployee))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;
}
@OverridepublicString toString() {return "Employee [id=" + id + ", name=" + name + ", joiningDate="
+ joiningDate + ", salary=" + salary + ", ssn=" + ssn + "]";
}
}
这是一个标准的实体类,基于JPA注解@Entity, @Table, @Column以及hibernate注解@Type(用于提供数据库类型与Joda-Time LocalDate的映射)。
步骤七:在数据库里创建Schema
CREATE TABLEEMPLOYEE(
idINT NOT NULLauto_increment,
nameVARCHAR(50) NOT NULL,
joining_date DATENOT NULL,
salaryDOUBLE NOT NULL,
ssnVARCHAR(30) NOT NULL UNIQUE,PRIMARY KEY(id)
);
步骤八:创建main方法执行程序
packagecom.websystique.spring;importjava.math.BigDecimal;importjava.util.List;importorg.joda.time.LocalDate;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;importorg.springframework.context.support.AbstractApplicationContext;importcom.websystique.spring.configuration.AppConfig;importcom.websystique.spring.model.Employee;importcom.websystique.spring.service.EmployeeService;public classAppMain {public static voidmain(String args[]) {
AbstractApplicationContext context= new AnnotationConfigApplicationContext(AppConfig.class);
EmployeeService service= (EmployeeService) context.getBean("employeeService");/** Create Employee1*/Employee employee1= newEmployee();
employee1.setName("Han Yenn");
employee1.setJoiningDate(new LocalDate(2010, 10, 10));
employee1.setSalary(new BigDecimal(10000));
employee1.setSsn("ssn00000001");/** Create Employee2*/Employee employee2= newEmployee();
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 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 employeeList =service.findAllEmployees();for(Employee emp : employeeList) {
System.out.println(emp);
}
context.close();
}
}
注意,假如你想删除AppConfig文件,那么只需将
AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
替换为