如产品官方“关于”页面所述, Hibernate是一种高性能的对象/关系持久性和查询服务。 Hibernate是市场上最灵活,功能最强大的对象/关系解决方案,它负责从Java类到数据库表以及从Java数据类型到SQL数据类型的映射。 它提供了数据查询和检索功能,大大减少了开发时间。
出于本文的目的,我们将使用上述众所周知的产品作为持久性API的实际实现。 我们的目标是能够比较对数据库应用CRUD(创建-检索-更新-删除)操作时的性能。
为此,我们将实现两个不同的基于Spring的WEB应用程序,这些应用程序将充当我们的“测试基础”。 这两个应用程序的服务和数据访问层定义(接口和实现类)将完全相同。 对于数据访问,我们将利用JPA2作为Java Persistence API。 对于数据库,我们将使用嵌入式Derby实例。 最后但并非最不重要的一点是,我们将针对以下所述的五个“基本”数据访问操作实施并执行相同的性能测试:
- 保持记录
- 通过其ID检索记录
- 检索所有记录
- 更新现有记录
- 删除记录
所有测试均针对具有以下特征的Sony Vaio进行:
- 系统:openSUSE 11.1(x86_64)
- 处理器(CPU):Intel(R)Core(TM)2 Duo CPU T6670 @ 2.20GHz
- 处理器速度:1,200.00 MHz
- 总内存(RAM):2.8 GB
- Java:OpenJDK 1.6.0_0 64位
使用以下工具:
- Spring框架3.0.1
- Apache Derby 10.6.1.0
- 休眠 3.5.1
- DataNucleus 3.0.0-m1
- c3p0 0.9.1.2
- Brent Boyer的Java Benchmarking框架
最终通知:
- 您可以在此处和此处下载两个“测试基础”的完整源代码。 这些是基于Eclipse – Maven的项目。
- 为了能够自己编译和运行测试,您需要将Java Benchmarking框架二进制– jar文件安装到Maven存储库。 另外,作为“一键式”解决方案,您可以使用我们创建的Java Benchmarking Maven软件包。 您可以从此处下载它,然后将其解压缩到您的Maven存储库中,一切都很好。
“测试基地”…
我们将首先提供有关如何实施“测试基础”项目的信息。 为了使我们的测试所针对的环境的细节清晰明了,这势在必行。 如前所述,我们已经实现了两个基于Spring的多层WEB应用程序。 在每个应用程序中,已经实现了两层,即服务层和数据访问层。 这些层具有相同的定义-接口和实现细节。
我们的领域模型仅包含一个“雇员”对象。 服务层提供了一个简单的“业务”服务,该服务公开了“员工”对象的CRUD(创建-检索-更新-删除)功能,而数据访问层则包括一个简单的数据访问对象,该对象利用Spring JpaDaoSupport抽象来提供与数据库的实际互操作性。
以下是数据访问层特定的类:
import javax.annotation.PostConstruct;
import javax.persistence.EntityManagerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.javacodegeeks.springdatanucleus.dto.EmployeeDTO;
@Repository("employeeDAO")
public class EmployeeDAO extends JpaDAO<Long, EmployeeDTO> {
@Autowired
EntityManagerFactory entityManagerFactory;
@PostConstruct
public void init() {
super.setEntityManagerFactory(entityManagerFactory);
}
}
如您所见,我们的数据访问对象(DAO)扩展了JpaDAO类。 该课程如下:
import java.lang.reflect.ParameterizedType;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import org.springframework.orm.jpa.JpaCallback;
import org.springframework.orm.jpa.support.JpaDaoSupport;
public abstract class JpaDAO<K, E> extends JpaDaoSupport {
protected Class<E> entityClass;
@SuppressWarnings("unchecked")
public JpaDAO() {
ParameterizedType genericSuperclass = (ParameterizedType) getClass()
.getGenericSuperclass();
this.entityClass = (Class<E>) genericSuperclass
.getActualTypeArguments()[1];
}
public void persist(E entity) {
getJpaTemplate().persist(entity);
}
public void remove(E entity) {
getJpaTemplate().remove(entity);
}
public E merge(E entity) {
return getJpaTemplate().merge(entity);
}
public void refresh(E entity) {
getJpaTemplate().refresh(entity);
}
public E findById(K id) {
return getJpaTemplate().find(entityClass, id);
}
public E flush(E entity) {
getJpaTemplate().flush();
return entity;
}
@SuppressWarnings("unchecked")
public List<E> findAll() {
Object res = getJpaTemplate().execute(new JpaCallback() {
public Object doInJpa(EntityManager em) throws PersistenceException {
Query q = em.createQuery("SELECT h FROM " +
entityClass.getName() + " h");
return q.getResultList();
}
});
return (List<E>) res;
}
@SuppressWarnings("unchecked")
public Integer removeAll() {
return (Integer) getJpaTemplate().execute(new JpaCallback() {
public Object doInJpa(EntityManager em) throws PersistenceException {
Query q = em.createQuery("DELETE FROM " +
entityClass.getName() + " h");
return q.executeUpdate();
}
});
}
}
以下是我们的域类EmployeeDTO类:
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "EMPLOYEE")
public class EmployeeDTO implements java.io.Serializable {
private static final long serialVersionUID = 7440297955003302414L;
@Id
@Column(name="employee_id")
private long employeeId;
@Column(name="employee_name", nullable = false, length=30)
private String employeeName;
@Column(name="employee_surname", nullable = false, length=30)
private String employeeSurname;
@Column(name="job", length=50)
private String job;
public EmployeeDTO() {
}
public EmployeeDTO(int employeeId) {
this.employeeId = employeeId;
}
public EmployeeDTO(long employeeId, String employeeName, String employeeSurname,
String job) {
this.employeeId = employeeId;
this.employeeName = employeeName;
this.employeeSurname = employeeSurname;
this.job = job;
}
public long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(long employeeId) {
this.employeeId = employeeId;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public String getEmployeeSurname() {
return employeeSurname;
}
public void setEmployeeSurname(String employeeSurname) {
this.employeeSurname = employeeSurname;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
}
最后但并非最不重要的是,下面提供了“业务”服务接口和实现类:
import java.util.List;
import com.javacodegeeks.springdatanucleus.dto.EmployeeDTO;
public interface EmployeeService {
public EmployeeDTO findEmployee(long employeeId);
public List<EmployeeDTO> findAllEmployees();
public void saveEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception;
public void updateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception;
public void saveOrUpdateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception;
public void deleteEmployee(long employeeId) throws Exception;
}
import java.util.List;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.javacodegeeks.springdatanucleus.dao.EmployeeDAO;
import com.javacodegeeks.springdatanucleus.dto.EmployeeDTO;
import com.javacodegeeks.springdatanucleus.services.EmployeeService;
@Service("employeeService")
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
private EmployeeDAO employeeDAO;
@PostConstruct
public void init() throws Exception {
}
@PreDestroy
public void destroy() {
}
public EmployeeDTO findEmployee(long employeeId) {
return employeeDAO.findById(employeeId);
}
public List<EmployeeDTO> findAllEmployees() {
return employeeDAO.findAll();
}
@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
public void saveEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception {
EmployeeDTO employeeDTO = employeeDAO.findById(employeeId);
if(employeeDTO == null) {
employeeDTO = new EmployeeDTO(employeeId, name,surname, jobDescription);
employeeDAO.persist(employeeDTO);
}
}
@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
public void updateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception {
EmployeeDTO employeeDTO = employeeDAO.findById(employeeId);
if(employeeDTO != null) {
employeeDTO.setEmployeeName(name);
employeeDTO.setEmployeeSurname(surname);
employeeDTO.setJob(jobDescription);
}
}
@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
public void deleteEmployee(long employeeId) throws Exception {
EmployeeDTO employeeDTO = employeeDAO.findById(employeeId);
if(employeeDTO != null)
employeeDAO.remove(employeeDTO);
}
@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
public void saveOrUpdateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception {
EmployeeDTO employeeDTO = new EmployeeDTO(employeeId, name,surname, jobDescription);
employeeDAO.merge(employeeDTO);
}
}
接下来是驱动Spring IoC容器的“ applicationContext.xml”文件。 在两个“测试基础”项目之间,该文件的内容也相同。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">
<context:component-scan base-package="com.javacodegeeks.springdatanucleus" />
<tx:annotation-driven />
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="MyPersistenceUnit" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
</beans>
为了能够从Servlet容器启动Spring应用程序(别忘了我们已经实现了基于Spring的WEB应用程序),我们在两个“测试基础”应用程序的“ web.xml”文件中都包含了以下侦听器:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
这两个“测试基础”项目之间唯一不同的文件是定义要使用的Java持久性API(JPA)的实际实现的文件-“ persistence.xml”文件。 以下是我们用来利用DataNucleus Access Platform的平台:
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="MyPersistenceUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
<class>com.javacodegeeks.springdatanucleus.dto.EmployeeDTO</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="datanucleus.storeManagerType" value="rdbms"/>
<property name="datanucleus.ConnectionDriverName" value="org.apache.derby.jdbc.EmbeddedDriver"/>
<property name="datanucleus.ConnectionURL" value="jdbc:derby:runtime;create=true"/>
<!--
<property name="datanucleus.ConnectionUserName" value=""/>
<property name="datanucleus.ConnectionPassword" value=""/>
-->
<property name="datanucleus.autoCreateSchema" value="true"/>
<property name="datanucleus.validateTables" value="false"/>
<property name="datanucleus.validateConstraints" value="false"/>
<property name="datanucleus.connectionPoolingType" value="C3P0"/>
<property name="datanucleus.connectionPool.minPoolSize" value="5" />
<property name="datanucleus.connectionPool.initialPoolSize" value="5" />
<property name="datanucleus.connectionPool.maxPoolSize" value="20" />
<property name="datanucleus.connectionPool.maxStatements" value="50" />
</properties>
</persistence-unit>
</persistence>
接下来是用于将Hibernate用作JPA2实现框架的“ persistence.xml”文件:
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="MyPersistenceUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.hibernatePersistence</provider>
<class>com.javacodegeeks.springhibernate.dto.EmployeeDTO</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.show_sql" value="false" />
<property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect" />
<property name="hibernate.connection.driver_class" value="org.apache.derby.jdbc.EmbeddedDriver" />
<property name="hibernate.connection.url" value="jdbc:derby:runtime;create=true" />
<!--
<property name="hibernate.connection.username" value="" />
<property name="hibernate.connection.password" value="" />
-->
<property name="hibernate.c3p0.min_size" value="5" />
<property name="hibernate.c3p0.max_size" value="20" />
<property name="hibernate.c3p0.timeout" value="300" />
<property name="hibernate.c3p0.max_statements" value="50" />
<property name="hibernate.c3p0.idle_test_period" value="3000" />
</properties>
</persistence-unit>
</persistence>
最后,我们演示实现所有要执行的测试用例的类。 这两个“测试基础”项目的类是相同的:
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import java.util.List;
import java.util.concurrent.Callable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import bb.util.Benchmark;
import com.javacodegeeks.springhibernate.dto.EmployeeDTO;
import com.javacodegeeks.springhibernate.services.EmployeeService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/applicationContext.xml"})
public class EmployeeServiceTest {
@Autowired
EmployeeService employeeService;
@Test
public void testSaveEmployee() {
try {
employeeService.saveEmployee(1, "byron", "kiourtzoglou", "master software engineer");
employeeService.saveEmployee(2, "ilias", "tsagklis", "senior software engineer");
} catch (Exception e) {
fail(e.getMessage());
}
}
@Test
public void testFindEmployee() {
assertNotNull(employeeService.findEmployee(1));
}
@Test
public void testFindAllEmployees() {
assertEquals(employeeService.findAllEmployees().size(), 2);
}
@Test
public void testUpdateEmployee() {
try {
employeeService.updateEmployee(1, "panagiotis", "paterakis", "senior software engineer");
assertEquals(employeeService.findEmployee(1).getEmployeeName(), "panagiotis");
} catch (Exception e) {
fail(e.getMessage());
}
}
@Test
public void testDeleteEmployee() {
try {
employeeService.deleteEmployee(1);
assertNull(employeeService.findEmployee(1));
} catch (Exception e) {
fail(e.getMessage());
}
}
@Test
public void testSaveOrUpdateEmployee() {
try {
employeeService.saveOrUpdateEmployee(1, "byron", "kiourtzoglou", "master software engineer");
assertEquals(employeeService.findEmployee(1).getEmployeeName(), "byron");
} catch (Exception e) {
fail(e.getMessage());
}
}
@Test
public void stressTestSaveEmployee() {
Callable<Integer> task = new Callable<Integer>() {
public Integer call() throws Exception {
int i;
for(i = 3;i < 2048; i++) {
employeeService.saveEmployee(i, "name-" + i, "surname-" + i, "developer-" + i);
}
return i;
}
};
try {
System.out.println("saveEmployee(...): " + new Benchmark(task, false, 2045));
} catch (Exception e) {
fail(e.getMessage());
}
assertNotNull(employeeService.findEmployee(1024));
}
@Test
public void stressTestFindEmployee() {
Callable<Integer> task = new Callable<Integer>() {
public Integer call() {
int i;
for(i = 1;i < 2048; i++) {
employeeService.findEmployee(i);
}
return i;
}
};
try {
System.out.println("findEmployee(...): " + new Benchmark(task, 2047));
} catch (Exception e) {
fail(e.getMessage());
}
}
@Test
public void stressTestFindAllEmployees() {
Callable<List<EmployeeDTO>> task = new Callable<List<EmployeeDTO>>() {
public List<EmployeeDTO> call() {
return employeeService.findAllEmployees();
}
};
try {
System.out.println("findAllEmployees(): " + new Benchmark(task));
} catch (Exception e) {
fail(e.getMessage());
}
}
@Test
public void stressTestUpdateEmployee() {
Callable<Integer> task = new Callable<Integer>() {
public Integer call() throws Exception {
int i;
for(i=1;i<2048;i++) {
employeeService.updateEmployee(i, "new_name-" + i, "new_surname-" + i, "new_developer-" + i);
}
return i;
}
};
try {
System.out.println("updateEmployee(...): " + new Benchmark(task, false, 2047));
} catch (Exception e) {
fail(e.getMessage());
}
assertEquals("new_name-1", employeeService.findEmployee(1).getEmployeeName());
}
@Test
public void stressTestDeleteEmployee() {
Callable<Integer> task = new Callable<Integer>() {
public Integer call() throws Exception {
int i;
for(i = 1;i < 2048; i++) {
employeeService.deleteEmployee(i);
}
return i;
}
};
try {
System.out.println("deleteEmployee(...): " + new Benchmark(task, false, 2047));
} catch (Exception e) {
fail(e.getMessage());
}
assertEquals(true, employeeService.findAllEmployees().isEmpty());
}
}
结果 …
下图显示了所有测试结果。 纵轴表示每个测试的平均执行时间(以微秒(us)为单位),因此值越低越好。 横轴表示测试类型。 从上面的测试案例中可以看到,我们在数据库中插入了总数为2047个“员工”记录。 对于检索测试用例(findEmployee(…)和findAllEmployees(…)),基准测试框架对每个测试用例进行了60次重复,以计算统计数据。 所有其他测试用例仅执行一次。
如您所见,在每个测试用例中, Hibernate的性能都优于DataNucleus 。 特别是在通过ID(查找)方案进行检索时, Hibernate比DataNucleus快9倍!
我认为DataNucleus是一个很好的平台。 当您要处理所有形式的数据(无论存储在何处)时,可以使用它。 从数据持久性到异构数据存储,到提供使用多种查询语言进行检索的方法。
使用这种多功能平台来管理应用程序数据的主要优点是,您无需花费大量时间来学习特定数据存储或查询语言的特殊性。 另外,您可以对所有数据使用单个通用接口,因此您的团队可以将他们的应用程序开发时间集中在添加业务逻辑上,并让DataNucleus处理数据管理问题。
另一方面,多功能性要付出代价。 作为关系映射(ORM)框架的“硬核”对象, Hibernate在我们所有的ORM测试中均轻松胜过DataNucleus 。
像大多数时候一样,由应用程序架构师决定最适合其需求的功能(多功能性或性能),直到DataNucleus团队将其产品开发到可以胜过Hibernate的地步为止;-)
祝您编码愉快,不要忘记分享!
拜伦
相关文章:
翻译自: https://www.javacodegeeks.com/2011/02/datanucleus-30-vs-hibernate-35.html