Hibernate , как указано на официальной странице «о продукте», — это высокопроизводительная служба объектно-реляционной устойчивости и запросов. Hibernate, самое гибкое и мощное объектно-реляционное решение на рынке, обеспечивает преобразование классов Java в таблицы базы данных и типов данных Java в типы данных SQL. Он предоставляет средства для запроса и поиска данных, которые значительно сокращают время разработки.
Для этой статьи мы будем использовать вышеупомянутые, хорошо известные продукты в качестве фактических реализаций API постоянства. Наша цель — сравнить их производительность при применении операций CRUD (Create — Retrieve — Update — Delete) к базе данных.
Для этого мы собираемся реализовать два отдельных веб-приложения на основе Spring, которые будут выступать в качестве наших «тестовых баз». Эти два приложения будут идентичны с точки зрения их определения уровня обслуживания и доступа к данным — интерфейсов и классов реализации. Для доступа к данным мы будем использовать JPA2 в качестве API персистентности Java. Для базы данных мы будем использовать встроенный экземпляр Derby . И последнее, но не менее важное: мы собираемся реализовать и выполнить такой же набор тестов производительности для пяти «базовых» операций доступа к данным, описанных ниже:
- Сохранить запись
- Получить запись по ее идентификатору
- Получить все записи
- Обновить существующую запись
- Удалить запись
Все тесты выполняются на Sony Vaio со следующими характеристиками:
- Система: openSUSE 11.1 (x86_64)
- Процессор (ЦП): Процессор Intel® Core ™ 2 Duo T6670 с частотой 2,20 ГГц
- Скорость процессора: 1200,00 МГц
- Общий объем памяти (ОЗУ): 2,8 ГБ
- Java: OpenJDK 1.6.0_0 64-битная
Используются следующие инструменты:
- Spring Framework 3.0.1
- Apache Derby 10.6.1.0
- Hibernate 3.5.1
- DataNucleus 3.0.0-m1
- c3p0 0.9.1.2
- Java Benchmarking Framework от Brent Boyer
Заключительные уведомления:
- Вы можете скачать полный исходный код для двух «тестовых баз» здесь и здесь . Это проекты, основанные на Eclipse — Maven.
- Чтобы иметь возможность самостоятельно скомпилировать и запустить тесты, вам потребуется установить двоичные файлы jar фреймворка Java Benchmarking Framework в свой репозиторий Maven. В качестве альтернативы, в качестве решения «в один клик» вы можете использовать созданный нами пакет Java Benchmarking Maven. Вы можете скачать его отсюда , разархивировать в свой репозиторий Maven, и все готово.
«Тестовые базы»…
Мы начнем с предоставления информации о том, как мы реализовали проекты «тестовой базы». Это необходимо для того, чтобы быть предельно ясным относительно специфики среды, в которой будут проходить наши тесты. Как было сказано ранее, мы реализовали два многоуровневых веб-приложения на основе Spring . В каждом приложении были реализованы два уровня: уровень обслуживания и уровень доступа к данным. Эти уровни имеют идентичные определения — интерфейсы и особенности реализации.
Наша модель предметной области состоит только из объекта «Сотрудник». Уровень обслуживания предоставляет тривиальный «бизнес» сервис, который предоставляет функциональность CRUD (Create — Retrieve — Update — Delete) для объекта «Employee», тогда как уровень доступа к данным состоит из тривиального объекта доступа к данным, который использует абстракцию Spring JpaDaoSupport для обеспечения фактическая совместимость с базой данных.
Ниже приведены конкретные классы уровня доступа к данным:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
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. Этот класс представлен ниже:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
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:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
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; } } |
И последнее, но не менее важное: «бизнес» интерфейс сервиса и классы реализации представлены ниже:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
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; } |
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
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); } } |
Далее следует файл applicationContext.xml, который управляет контейнером IoC Spring . Содержимое этого файла также идентично для двух проектов «тестовой базы».
Чтобы иметь возможность запускать приложение Spring из контейнера сервлетов (не забывайте, что мы реализовали веб-приложения на основе Spring ), мы включили следующий слушатель в файл «web.xml» для обоих наших «тестовых базовых» приложений:
1
2
3
|
< listener > < listener-class >org.springframework.web.context.ContextLoaderListener</ listener-class > </ listener > |
Единственный файл, который отличается между двумя проектами «тестовой базы», - это файл, который определяет фактическую реализацию используемого Java Persistent API (JPA) — файл «persistence.xml». Ниже приводится тот, который мы использовали для использования платформы доступа DataNucleus :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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 > |
Далее следует файл «persistence.xml», который мы использовали для использования Hibernate в качестве нашей среды реализации JPA2 :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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 > |
Наконец, мы демонстрируем класс, который реализует все тесты, которые должны быть выполнены. Этот класс идентичен для обоих проектов «тестовой базы»:
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
|
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()); } } |
Результаты …
Все результаты испытаний представлены на графике ниже. Вертикальная ось представляет среднее время выполнения каждого теста в микросекундах (нас), поэтому более низкие значения лучше. Горизонтальная ось представляет типы испытаний. Как видно из приведенных выше тестов, мы добавляем в базу данных общее количество 2047 записей «сотрудников». Для поисковых тестовых случаев (findEmployee (…) и findAllEmployees (…)) платформа сравнительного анализа выполнила 60 повторений каждого тестового примера для вычисления статистики. Все остальные тесты выполняются только один раз.
Как видите, Hibernate превосходит DataNucleus в каждом тестовом примере. Особенно в сценарии поиска по идентификатору (Find) Hibernate почти в 9 раз быстрее, чем DataNucleus !
На мой взгляд, DataNucleus — прекрасная платформа. Его можно использовать, когда вы хотите обрабатывать данные во всех их формах, где бы они ни хранились. Это происходит от сохранения данных в неоднородных хранилищах данных до предоставления методов поиска с использованием ряда языков запросов.
Основное преимущество использования такой универсальной платформы для управления данными вашего приложения заключается в том, что вам не нужно тратить значительное время на изучение странностей конкретных хранилищ данных или языков запросов. Кроме того, вы можете использовать единый общий интерфейс для всех ваших данных, поэтому ваша команда может сосредоточить время разработки приложений на добавлении бизнес-логики и позволить DataNucleus позаботиться о проблемах управления данными.
С другой стороны, универсальность приводит к затратам. Будучи «жесткой» средой Object-Relational Mapping (ORM), Hibernate легко превзошла DataNucleus во всех наших тестах ORM.
Как и в большинстве случаев, именно разработчик приложений должен решить, что лучше всего соответствует его потребностям — универсальность или производительность, пока команда DataNucleus не разовьет свой продукт до такой степени, что он сможет превзойти Hibernate ?
Приятного кодирования и не забудьте поделиться!
Byron
Статьи по Теме: