Audrey 2015-04-17
使用spring访问数据的框架就少不了使用到hibernate。 最近尝试手动去构建一些这样的示例时碰到了不少问题。本文结合hibernate 3, hibernate 4两个版本实现一个示例的过程,顺便也对它们的配置与使用做一个总结。
我们从一个简单的对一个实体类进行增删查改的操作来看怎么使用hibernate。因为针对不同版本的hibernate,后面的配置有点不一样,这里会专门标注出来。
假设我们使用hibernate4和spring4集成,则依赖的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>org.springframework.samples</groupId> <artifactId>SpringExample</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>SpringExample</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <!-- Spring --> <spring-framework.version>4.0.3.RELEASE</spring-framework.version> <!-- Hibernate / JPA --> <hibernate.version>4.3.5.Final</hibernate.version> </properties> <dependencies> <!-- Spring and Transactions --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-framework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring-framework.version}</version> </dependency> <!-- Spring ORM support --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring-framework.version}</version> </dependency> <!-- Hibernate --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.9</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-dbcp2</artifactId> <version>2.1</version> </dependency> <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.1.3.Final</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build> </project>
以上是程序主要所依赖的包,主要包含的和程序相关的有spring-context, spring-tx,和hibernate相关的有hibernate-core, hibernate-validator。留意到前面配置里设定的hibernate-core的版本是4,所以这部分主要针对hibernate4的配置。而对于hibernate3的依赖,只需要修改一下前面hibernate-core依赖项的版本号就可以了。
我们首先需要在数据库中创建一个表contact:
drop table if exists contact; create table contact ( id bigint unsigned not null auto_increment primary key, last_name varchar(40) not null, first_name varchar(40) not null, mi char(1), email varchar(80), date_created timestamp default 0, date_modified timestamp default current_timestamp on update current_timestamp, unique index contact_idx1 (last_name, first_name, mi) ) engine = InnoDB;
以上仅仅是一个普通的表定义。为了实现对应的实体类映射,我们需要做一个如下的定义:
package com.yunzero.model; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.NamedQuery; import javax.persistence.Table; import javax.persistence.Transient; import javax.validation.constraints.NotNull; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.Length; /** * @author Willie Wheeler ([email protected]) */ @Entity @Table(name = "contact") @NamedQuery( name = "findContactsByEmail", query = "from Contact where email like :email") public class Contact { private Long id; private String lastName; private String firstName; private String middleInitial; private String email; @Id @Column @GeneratedValue(strategy = GenerationType.AUTO) public Long getId() { return id; } public void setId(Long id) { this.id = id; } @NotNull @Length(min = 1, max = 40) @Column(name = "last_name") public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } @NotNull @Length(min = 1, max = 40) @Column(name = "first_name") public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } @Length(max = 1) @Column(name = "mi") public String getMiddleInitial() { return middleInitial; } public void setMiddleInitial(String mi) { this.middleInitial = mi; } @Email @Column public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Transient public String getFullName() { String fullName = lastName + ", " + firstName; if (! (middleInitial == null || "".equals(middleInitial.trim()))) { fullName += " " + middleInitial + "."; } return fullName; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return "[Contact: id=" + id + ", firstName=" + firstName + ", middleInitial=" + middleInitial + ", lastName=" + lastName + ", email=" + email + "]"; } }其实上述的代码也没有什么特殊的。Table标记对应映射到某个数据库表,Length, Column对应某些列和它的长度。针对对数据库操作的实现常用的模式是首先定义一个DAO接口如下:
package com.yunzero.service; import java.util.List; import com.yunzero.model.Contact; public interface ContactService { void createContact(Contact contact); List<Contact> getContacts(); List<Contact> getContactsByEmail(String email); Contact getContact(Long id); void updateContact(Contact contact); void deleteContact(Long id); }有了这个接口之后然后就是对它的具体实现了:
package com.yunzero.service.impl; import static org.springframework.util.Assert.notNull; import java.util.List; import javax.inject.Inject; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.yunzero.model.Contact; import com.yunzero.service.ContactService; @Service @Transactional public class ContactServiceImpl implements ContactService { @Inject private SessionFactory sessionFactory; @Override public void createContact(Contact contact) { notNull(contact, "contact can't be null"); getSession().save(contact); } @Override @SuppressWarnings("unchecked") public List<Contact> getContacts() { return getSession() .createQuery("from Contact") .list(); } @Override @SuppressWarnings("unchecked") public List<Contact> getContactsByEmail(String email) { notNull(email, "email can't be null"); return getSession() .getNamedQuery("findContactsByEmail") .setString("email", "%" + email + "%") .list(); } @Override public Contact getContact(Long id) { notNull(id, "id can't be null"); return (Contact) getSession().get(Contact.class, id); } @Override public void updateContact(Contact contact) { notNull(contact, "contact can't be null"); getSession().update(contact); } @Override public void deleteContact(Long id) { notNull(id, "id can't be null"); getSession().delete(getContact(id)); } private Session getSession() { return sessionFactory.getCurrentSession(); } }ContactServiceImpl的实现能够操作数据库就依赖于sessionFactory。而后面最关键的部分就是对sessionFactory的定义和注入。sessionFactory在这里类似于一个实现了一部分功能的类,可以将一些执行对应sql语句的功能强化一下。
针对Hibernate 3, 4,他们的定义和实现有不同。我们再来看看他们的配置。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd"> <context:property-placeholder location="classpath:/environment.properties" /> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close" p:driverClassName="${dataSource.driverClassName}" p:url="${dataSource.url}" p:username="${dataSource.username}" p:password="${dataSource.password}" /> <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="annotatedClasses"> <list> <value>com.yunzero.model.Contact</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop> <prop key="hibernate.show_sql">false</prop> </props> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <tx:annotation-driven /> <context:component-scan base-package="com.yunzero.service.impl" /> </beans>一个需要注意的地方就是,在hibernate4里,我们定义的sessionFactory需要用到的类是:org.springframework.orm.hibernate4.LocalSessionFactoryBean。另外,在前面定义的ContactServiceImpl类里我们定义了Transactional修饰,表示所有的方法执行都是事物性的。为了能够支持事物,需要在配置里定义transactionManager以及对这些Transactional修饰的支持。spring需要扫描给定的包范围来找到需要进行事物包装的类和方法。所以这里还需要增加一个<tx:annotation-driven/>。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd"> <context:property-placeholder location="classpath:/environment.properties" /> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close" p:driverClassName="${dataSource.driverClassName}" p:url="${dataSource.url}" p:username="${dataSource.username}" p:password="${dataSource.password}" /> <util:properties id="hibernateProperties"> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> <prop key="hibernate.show_sql">false</prop> </util:properties> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" p:dataSource-ref="dataSource" p:packagesToScan="com.yunzero.model" p:hibernateProperties-ref="hibernateProperties" /> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" p:sessionFactory-ref="sessionFactory" /> <tx:annotation-driven /> <context:component-scan base-package="com.yunzero.service.impl" /> </beans>
hibernate3使用的sessionFactory类不一样,是org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean。它的配置其实和前面的没什么区别。
程序执行的代码如下:
package org.springframework.samples.SpringExample; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.yunzero.model.Contact; import com.yunzero.service.ContactService; public class App { public static void main( String[] args ) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans-service.xml"); ContactService service = context.getBean(ContactService.class); for(Contact contact : service.getContacts()) { System.out.println(contact.toString()); } context.close(); } }
这里是列出来了表里面所有项。和普通使用spring的方式没什么差别。
用spring集成hibernate去操作数据库算是最典型的orm应用。其实这种方式没有什么特别的,都是固定的套路,就是里面的配置要小心。尤其针对hibernate 3和hibernate 4这两个版本。由于版本的变化带来一些使用的差别,在配置的时候一旦引用错了库会导致错误,而且查找这些错误还比较麻烦。在测试上面的示例程序时也存在一些环境依赖的坑,比如说如果安装的java版本是8的话,修改配置之后运行程序会出现一些错误,而在低版本的环境下没有。
上述办法其实还存在一个不足的地方,就是我们需要定义数据库表,然后定义实体类和表的映射。这样其实还是比较繁琐的。如果有一种办法可以让我们更关注于业务逻辑的定义而不用太操心数据库表的定义是不是更好呢?其实在之前讨论JPA的文章里已经实现过类似的示例了。如果用过ruby on rails或者django之类框架的会很熟悉这种套路。这种模式是采用定义实体对象类,通过它来映射生成对应的数据库表。这样一方面可以提高效率,同时也提高了代码的可移植性。在后面的文章里我会结合spring和jpa来讨论实现这种套路。