В предыдущей части мы определили интерфейс GenericDao, который сообщает нам, какие операции нам нужно будет выполнить над объектами. Теперь нам нужно обеспечить реализацию. Мы напишем класс, который выполняет эти операции в общем с помощью средств Hibernate (используя SessionFactory). Поэтому любой предоставленный DAO автоматически наследует эти основные операции. Мы поговорим об этом позже.
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
|
package org.timesheet.service.impl; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.timesheet.service.GenericDao; import java.io.Serializable; import java.lang.reflect.ParameterizedType; import java.util.List; /** * Basic DAO operations dependent with Hibernate's specific classes * @see SessionFactory */ @Transactional (propagation= Propagation.REQUIRED, readOnly= false ) public class HibernateDao<E, K extends Serializable> implements GenericDao<E, K> { private SessionFactory sessionFactory; protected Class<? extends E> daoType; public HibernateDao() { daoType = (Class<E>) ((ParameterizedType) getClass().getGenericSuperclass()) .getActualTypeArguments()[ 0 ]; } @Autowired public void setSessionFactory(SessionFactory sessionFactory) { this .sessionFactory = sessionFactory; } protected Session currentSession() { return sessionFactory.getCurrentSession(); } @Override public void add(E entity) { currentSession().save(entity); } @Override public void update(E entity) { currentSession().saveOrUpdate(entity); } @Override public void remove(E entity) { currentSession().delete(entity); } @Override public E find(K key) { return (E) currentSession().get(daoType, key); } @Override public List<E> list() { return currentSession().createCriteria(daoType).list(); } } |
Я хочу, чтобы вы отметили несколько вещей об этом коде:
- Мы используем @Transcational аннотацию в верхней части класса. В основном это означает, что методы DAO будут выполняться в транскрипциях. Чтобы это работало, нам нужно изменить наш файл persistence-beans.xml и объявить там менеджер транзакций, который будет обрабатывать транзакции. Просто добавьте следующие строки (новое определение компонента):
1234
<
bean
id
=
'transactionManager'
class
=
'org.springframework.orm.hibernate3.HibernateTransactionManager'
>
<
property
name
=
'sessionFactory'
ref
=
'sessionFactory'
/>
</
bean
>
- Мы автоматически подключаем (@Autowired) SessionFactory, используя метод установки. Как вы должны знать, существует больше видов инъекций (поле, сеттер, конструктор). Инжекция поля лучше всего выглядит в Spring, потому что аннотация находится непосредственно в поле, а не в методе конструктора или установщика. С другой стороны, внедрение полей является наиболее бесполезным, потому что мы не можем вручную установить другие зависимости для частных полей (например, в модульном тесте). Я предпочитаю внедрение конструктора всякий раз, когда могу, потому что мне не нужно использовать мутатор (сеттер) для зависимости. Поэтому объекты строятся более безопасным способом. В этом конкретном случае мы будем использовать инъекцию сеттера, потому что мы проектируем этот класс для расширения. Если бы мы выбрали внедрение конструктора, все расширяющиеся классы должны иметь конструктор, соответствующий одному из суперкласса.
Если вы хотите узнать больше об этом, я рекомендую эту замечательную книгу, написанную Дханджи Р. Прасанной.
Также обратите внимание, что первая строка конструктора делает некоторую отражающую магию. Это потому, что Java не имеет обобщений во время выполнения, только во время компиляции, поэтому она не позволяет нам писать что-то вроде E.class . Поэтому мы использовали этот уродливый хак.
Теперь у нас есть базовый шаблон для операций DAO. В реальных системах обычно существует DAO для каждого объекта. Это потому, что иногда этих унаследованных CRUD-операций недостаточно, и вам нужны дополнительные бизнес-операции . Мы определим интерфейсы (наборы операций для каждого DAO), которые безопасны для типов, и мы будем зависеть только от тех, которые будут позже в контроллерах. Мы внедрим их с помощью Hibernate и сделаем так, чтобы они были подключены автоматически. Создайте новый пакет org.timesheet.service.dao и добавьте туда следующие интерфейсы — DAO для каждого объекта:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
package org.timesheet.service.dao; import org.timesheet.domain.Employee; import org.timesheet.service.GenericDao; /** * DAO of employee. */ public interface EmployeeDao extends GenericDao<Employee, Long> { /** * Tries to remove employee from the system. * @param employee Employee to remove * @return {@code true} if employee is not assigned to any task * or timesheet. Else {@code false}. */ boolean removeEmployee(Employee employee); } |
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
package org.timesheet.service.dao; import org.timesheet.domain.Manager; import org.timesheet.service.GenericDao; /** * DAO of Manager. */ public interface ManagerDao extends GenericDao<Manager, Long> { /** * Tries to remove manager from the system. * @param manager Manager to remove * @return {@code true} if manager is not assigned to any task. * Else {@code false}. */ boolean removeManager(Manager manager); } |
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
package org.timesheet.service.dao; import org.timesheet.domain.Task; import org.timesheet.service.GenericDao; /** * DAO of Task. */ public interface TaskDao extends GenericDao<Task, Long> { /** * Tries to remove task from the system. * @param task Task to remove * @return {@code true} if there is no timesheet created on task. * Else {@code false}. */ boolean removeTask(Task task); } |
01
02
03
04
05
06
07
08
09
10
11
|
package org.timesheet.service.dao; import org.timesheet.domain.Timesheet; import org.timesheet.service.GenericDao; /** * DAO of Timesheet. */ public interface TimesheetDao extends GenericDao<Timesheet, Long> { // no additional business operations atm } |
Время для реализации. Мы просто расширим HibernateDao и реализуем соответствующий интерфейс. Нам нужны эти конкретные классы, потому что они будут внедрены в соответствующие поля (которые объявлены интерфейсом). Возможно, вы слышали что-то об этом подходе — это называется программирование для интерфейсов, и это то, что вы определенно хотите принять.
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
|
package org.timesheet.service.impl; import org.hibernate.Query; import org.springframework.stereotype.Repository; import org.timesheet.domain.Employee; import org.timesheet.service.dao.EmployeeDao; @Repository ( 'employeeDao' ) public class EmployeeDaoImpl extends HibernateDao<Employee, Long> implements EmployeeDao { @Override public boolean removeEmployee(Employee employee) { Query employeeTaskQuery = currentSession().createQuery( 'from Task t where :id in elements(t.assignedEmployees)' ); employeeTaskQuery.setParameter( 'id' , employee.getId()); // employee mustn't be assigned on no task if (!employeeTaskQuery.list().isEmpty()) { return false ; } Query employeeTimesheetQuery = currentSession().createQuery( 'from Timesheet t where t.who.id = :id' ); employeeTimesheetQuery.setParameter( 'id' , employee.getId()); // employee mustn't be assigned to any timesheet if (!employeeTimesheetQuery.list().isEmpty()) { return false ; } // ok, remove as usual remove(employee); return true ; } } |
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
|
package org.timesheet.service.impl; import org.hibernate.Query; import org.springframework.stereotype.Repository; import org.timesheet.domain.Manager; import org.timesheet.service.dao.ManagerDao; @Repository ( 'managerDao' ) public class ManagerDaoImpl extends HibernateDao<Manager, Long> implements ManagerDao { @Override public boolean removeManager(Manager manager) { Query managerQuery = currentSession().createQuery( 'from Task t where t.manager.id = :id' ); managerQuery.setParameter( 'id' , manager.getId()); // manager mustn't be assigned on no task if (!managerQuery.list().isEmpty()) { return false ; } // ok, remove as usual remove(manager); return true ; } } |
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
|
package org.timesheet.service.impl; import org.hibernate.Criteria; import org.hibernate.Query; import org.springframework.stereotype.Repository; import org.timesheet.domain.Task; import org.timesheet.domain.Timesheet; import org.timesheet.service.dao.TaskDao; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @Repository ( 'taskDao' ) public class TaskDaoImpl extends HibernateDao<Task, Long> implements TaskDao { @Override public boolean removeTask(Task task) { Query taskQuery = currentSession().createQuery( 'from Timesheet t where t.task.id = :id' ); taskQuery.setParameter( 'id' , task.getId()); // task mustn't be assigned to no timesheet if (!taskQuery.list().isEmpty()) { return false ; } // ok, remove as usual remove(task); return true ; } @Override public List<Task> list() { return currentSession().createCriteria(Task. class ) .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) .list(); } } |
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
package org.timesheet.service.impl; import org.hibernate.Criteria; import org.springframework.stereotype.Repository; import org.timesheet.domain.Timesheet; import org.timesheet.service.dao.TimesheetDao; import java.util.List; @Repository ( 'timesheetDao' ) public class TimesheetDaoImpl extends HibernateDao<Timesheet, Long> implements TimesheetDao { @Override public List<Timesheet> list() { return currentSession().createCriteria(Timesheet. class ) .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) .list(); } } |
Я хочу, чтобы вы заметили, что у нас есть аннотация Spring @Repository для каждого класса DAO. Это потому, что мы не будем создавать их вручную, но будем вводить их и управлять ими в контейнере IoC Spring. Кстати, это более новый подход, ориентированный на аннотации. Конфигурация XML отсутствует, Spring выяснит это за нас Мы можем использовать множество аннотаций, которые регистрируют классы как бины:
- @Component — компонент автоматического сканирования (Spring bean)
- @Repository — компонент в постоянном слое (обычно DAO)
- @Service — компонент в слое обслуживания
- @Controller — контроллер в архитектуре MVC
Еще один важный момент — мы передаем строковое значение в аннотацию @Repository. Я процитирую здесь Javadoc Spring, так как это самое ясное объяснение: «Значение может указывать на предложение для имени логического компонента, который будет превращен в bean-компонент Spring в случае автоопределенного компонента».
Теперь — время для тестирования! Создайте новый пакет: / src / test / java / org / timesheet / service / dao и поместите туда тесты.
Мы будем использовать некоторые внешние сценарии SQL для проверки состояния базы данных. Под src / main / resources создайте папку sql . Теперь давайте добавим два скрипта; cleanup.sql и create-data.sql . Теперь мы будем использовать только скрипт cleanup.sql, позже будет использован create-data.sql.
создание-data.sql
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
|
-- delete old data delete from task_employee; delete from timesheet; delete from task; delete from employee; delete from manager; -- add few employees insert into employee values( 1 , 'management' , 'Steve Jobs' ); insert into employee values( 2 , 'management' , 'Bill Gates' ); insert into employee values( 3 , 'engineering' , 'Steve Wozniak' ); insert into employee values( 4 , 'engineering' , 'Paul Allen' ); -- add few managers insert into manager values( 1 , 'Eric Schmidt' ); insert into manager values( 2 , 'Steve Ballmer' ); -- add some tasks insert into task values( 1 , 0 , 'task 1' , 1 ); insert into task values( 2 , 0 , 'task 2' , 2 ); -- connect tasks to some employees insert into task_employee values ( 1 , 1 ); insert into task_employee values ( 1 , 3 ); insert into task_employee values ( 1 , 4 ); insert into task_employee values ( 2 , 2 ); insert into task_employee values ( 2 , 1 ); -- create some timesheets on tasks insert into timesheet values( 1 , 5 , -- hours 1 , -- first task 1 -- employee steve jobs ); insert into timesheet values( 2 , 8 , -- hours 2 , -- second task 3 -- employee bill gates ); |
cleanup.sql
1
2
3
4
5
|
delete from task_employee; delete from timesheet; delete from task; delete from employee; delete from manager; |
Вам не нужно использовать мои данные; не стесняйтесь создавать некоторые по своему усмотрению. Просто убедитесь, что они имеют какой-то смысл для вас.
Прежде чем писать тест, нам нужен новый Spring bean. Он называется jdbcTemplate и хорошо известен для работы с JDBC в Spring. Это в основном оболочка для простого JDBC, которая упрощает многие вещи. Благодаря этому мы можем запустить скрипт простым вызовом, как вы увидите позже.
А пока добавьте этот bean-компонент в свой файл Spring Config persistence-beans.xml :
1
2
3
4
|
< bean id = 'jdbcTemplate' class = 'org.springframework.jdbc.core.simple.SimpleJdbcTemplate' > < constructor-arg type = 'javax.sql.DataSource' ref = 'dataSource' /> </ bean > |
Я не буду уделять особое внимание каждому тесту, поэтому давайте кратко поговорим о том, что тестировать и как. Мы тестируем наши DAO, и нам нужно убедиться, что основные операции CRUD работают правильно. Мы очищаем все данные после каждого метода тестирования и, если необходимо, создаем их до запуска метода тестирования. Основная идея наших тестов такова:
- Если что-то было добавлено, проверьте, можете ли это найти
- Если что-то было удалено, проверьте, что мы больше не можем это найти
- Добавьте пару элементов в базу данных, подсчитайте их и убедитесь, что они были добавлены
- Обновить элемент, сохранить его. Найдите это и проверьте, что это было изменено
Мне нравится думать о таких тестах, как интеграционные тесты, больше как модульные тесты. В огромном домене подобные тесты потребуют (в отличие от простых модульных тестов) довольно большого количества времени для запуска. На этот раз мы создадим специальный базовый класс с именем org.timesheet.DomainAwareBase . Это расширяет AbstractJUnit4SpringContextTests, так что мы можем автоматически подключать наши DAO, но также удаляет все данные из базы данных, прежде чем любой метод тестирования будет выполнен с использованием deleteScript.
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
|
package org.timesheet; import org.junit.Before; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.FileSystemResource; import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; import org.springframework.test.jdbc.SimpleJdbcTestUtils; /** * Base makes sure that before any test empty database is available. */ @ContextConfiguration (locations = { '/persistence-beans.xml' }) public abstract class DomainAwareBase extends AbstractJUnit4SpringContextTests { private final String deleteScript = 'src/main/resources/sql/cleanup.sql' ; @Autowired private SimpleJdbcTemplate jdbcTemplate; @Before public void deleteAllDomainEntities() { SimpleJdbcTestUtils.executeSqlScript(jdbcTemplate, new FileSystemResource(deleteScript), false ); } } |
Об автопроводке и оснастке:
Для меня оснастка особенно важна при автопроводке бобов. Это немного трудно перемещаться по коду без какой-либо дополнительной поддержки. Например, если вы используете окончательную редакцию IntelliJ IDEA, вы можете перейти непосредственно от поля к зависимостям с автосвязью, потому что IntelliJ добавит небольшой маркер.
Или вы также можете видеть автосвязанные зависимости вместе с объявленными в XML в представлении зависимостей.
Давайте посмотрим код для тестов сейчас:
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
|
package org.timesheet.service.dao; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.timesheet.DomainAwareBase; import org.timesheet.domain.Employee; import org.timesheet.domain.Manager; import org.timesheet.domain.Task; import org.timesheet.domain.Timesheet; import java.util.Arrays; import java.util.List; import static org.junit.Assert.*; @ContextConfiguration (locations = '/persistence-beans.xml' ) public class EmployeeDaoTest extends DomainAwareBase { @Autowired private EmployeeDao employeeDao; @Autowired private ManagerDao managerDao; @Autowired private TaskDao taskDao; @Autowired private TimesheetDao timesheetDao; @Test public void testAdd() { int size = employeeDao.list().size(); employeeDao.add( new Employee( 'test-employee' , 'hackzorz' )); // list should have one more employee now assertTrue (size < employeeDao.list().size()); } @Test public void testUpdate() { Employee employee = new Employee( 'test-employee' , 'hackzorz' ); employeeDao.add(employee); employee.setName( 'updated' ); employeeDao.update(employee); Employee found = employeeDao.find(employee.getId()); assertEquals( 'updated' , found.getName()); } @Test public void testFind() { Employee employee = new Employee( 'test-employee' , 'hackzorz' ); employeeDao.add(employee); Employee found = employeeDao.find(employee.getId()); assertEquals(found, employee); } @Test public void testList() { assertEquals( 0 , employeeDao.list().size()); List<Employee> employees = Arrays.asList( new Employee( 'test-1' , 'testers' ), new Employee( 'test-2' , 'testers' ), new Employee( 'test-3' , 'testers' )); for (Employee employee : employees) { employeeDao.add(employee); } List<Employee> found = employeeDao.list(); assertEquals( 3 , found.size()); for (Employee employee : found) { assertTrue(employees.contains(employee)); } } @Test public void testRemove() { Employee employee = new Employee( 'test-employee' , 'hackzorz' ); employeeDao.add(employee); // successfully added assertEquals(employee, employeeDao.find(employee.getId())); // try to remove employeeDao.remove(employee); assertNull(employeeDao.find(employee.getId())); } @Test public void testRemoveEmployee() { Manager manager = new Manager( 'task-manager' ); managerDao.add(manager); Employee employee = new Employee( 'Jaromir' , 'Hockey' ); employeeDao.add(employee); Task task = new Task( 'test-task' , manager, employee); taskDao.add(task); Timesheet timesheet = new Timesheet(employee, task, 100 ); timesheetDao.add(timesheet); // try to remove -> shouldn't work assertFalse(employeeDao.removeEmployee(employee)); // remove stuff timesheetDao.remove(timesheet); taskDao.remove(task); // should work -> employee is now free assertTrue(employeeDao.removeEmployee(employee)); } } |
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
|
package org.timesheet.service.dao; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.timesheet.DomainAwareBase; import org.timesheet.domain.Employee; import org.timesheet.domain.Manager; import org.timesheet.domain.Task; import java.util.Arrays; import java.util.List; import static org.junit.Assert.*; @ContextConfiguration (locations = '/persistence-beans.xml' ) public class ManagerDaoTest extends DomainAwareBase { @Autowired private ManagerDao managerDao; @Autowired private EmployeeDao employeeDao; @Autowired private TaskDao taskDao; @Test public void testAdd() { int size = managerDao.list().size(); managerDao.add( new Manager( 'test-manager' )); assertTrue (size < managerDao.list().size()); } @Test public void testUpdate() { Manager manager = new Manager( 'test-manager' ); managerDao.add(manager); manager.setName( 'updated' ); managerDao.update(manager); Manager found = managerDao.find(manager.getId()); assertEquals( 'updated' , found.getName()); } @Test public void testFind() { Manager manager = new Manager( 'test-manager' ); managerDao.add(manager); Manager found = managerDao.find(manager.getId()); assertEquals(found, manager); } @Test public void testList() { assertEquals( 0 , managerDao.list().size()); List<Manager> managers = Arrays.asList( new Manager( 'test-1' ), new Manager( 'test-2' ), new Manager( 'test-3' ) ); for (Manager manager : managers) { managerDao.add(manager); } List<Manager> found = managerDao.list(); assertEquals( 3 , found.size()); for (Manager manager : found) { assertTrue(managers.contains(manager)); } } @Test public void testRemove() { Manager manager = new Manager( 'test-manager' ); managerDao.add(manager); // successfully added assertEquals(manager, managerDao.find(manager.getId())); // try to remove managerDao.remove(manager); assertNull(managerDao.find(manager.getId())); } @Test public void testRemoveManager() { Manager manager = new Manager( 'task-manager' ); managerDao.add(manager); Employee employee = new Employee( 'Jaromir' , 'Hockey' ); employeeDao.add(employee); Task task = new Task( 'test-task' , manager, employee); taskDao.add(task); // try to remove -> shouldn't work assertFalse(managerDao.removeManager(manager)); // remove task taskDao.remove(task); // should work -> no more tasks for manager assertTrue(managerDao.removeManager(manager)); } } |
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
|
package org.timesheet.service.dao; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.timesheet.DomainAwareBase; import org.timesheet.domain.Employee; import org.timesheet.domain.Manager; import org.timesheet.domain.Task; import java.util.Arrays; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @ContextConfiguration (locations = '/persistence-beans.xml' ) public class TaskDaoTest extends DomainAwareBase { @Autowired private TaskDao taskDao; @Autowired private ManagerDao managerDao; @Autowired private EmployeeDao employeeDao; @Test public void testAdd() { int size = taskDao.list().size(); Task task = newSpringTask(); taskDao.add(task); assertTrue(size < taskDao.list().size()); } @Test public void testUpdate() { Task task = newSpringTask(); taskDao.add(task); // update task task.setDescription( 'Learn Spring 3.1' ); taskDao.update(task); Task found = taskDao.find(task.getId()); assertEquals( 'Learn Spring 3.1' , found.getDescription()); } @Test public void testFind() { Task task = newSpringTask(); taskDao.add(task); assertEquals(task, taskDao.find(task.getId())); } @Test public void testList() { assertEquals( 0 , taskDao.list().size()); Task templateTask = newSpringTask(); List<Task> tasks = Arrays.asList( newTaskFromTemplate(templateTask, '1' ), newTaskFromTemplate(templateTask, '2' ), newTaskFromTemplate(templateTask, '3' ) ); for (Task task : tasks) { taskDao.add(task); } List<Task> found = taskDao.list(); assertEquals( 3 , found.size()); for (Task task : found) { assertTrue(tasks.contains(task)); } } @Test public void testRemove() { Task task = newSpringTask(); taskDao.add(task); // successfully added assertEquals(task, taskDao.find(task.getId())); // try to remove taskDao.remove(task); assertNull(taskDao.find(task.getId())); } /** * @return Dummy task for testing */ private Task newSpringTask() { Manager bob = new Manager( 'Bob' ); managerDao.add(bob); Employee steve = new Employee( 'Steve' , 'Business' ); Employee woz = new Employee( 'Woz' , 'Engineering' ); employeeDao.add(steve); employeeDao.add(woz); return new Task( 'Learn Spring' , bob, steve, woz); } /** * Creates dummy task fo testing as copy of existing task and * adds aditional information to every field. * @param templateTask Task to copy * @param randomInfo Info to append everywhere * @return Random task for testing */ private Task newTaskFromTemplate(Task templateTask, String randomInfo) { String description = templateTask.getDescription() + randomInfo; Manager manager = new Manager( templateTask.getManager().getName()); managerDao.add(manager); List<Employee> templateEmployees = templateTask.getAssignedEmployees(); Employee[] employees = new Employee[templateEmployees.size()]; int idx = 0 ; for (Employee templateEmployee : templateEmployees) { Employee employee = new Employee( templateEmployee.getName() + randomInfo, templateEmployee.getDepartment() + randomInfo); employees[idx++] = employee; employeeDao.add(employee); } return new Task(description, manager, employees); } } |
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
|
package org.timesheet.service.dao; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.timesheet.DomainAwareBase; import org.timesheet.domain.Employee; import org.timesheet.domain.Manager; import org.timesheet.domain.Task; import org.timesheet.domain.Timesheet; import java.util.Arrays; import java.util.List; import static org.junit.Assert.*; @ContextConfiguration (locations = '/persistence-beans.xml' ) public class TimesheetDaoTest extends DomainAwareBase { @Autowired private TimesheetDao timesheetDao; // daos needed for integration test of timesheetDao @Autowired private TaskDao taskDao; @Autowired private EmployeeDao employeeDao; @Autowired private ManagerDao managerDao; // common fields for timesheet creation private Task task; private Employee employee; @Override public void deleteAllDomainEntities() { super .deleteAllDomainEntities(); setUp(); } public void setUp() { employee = new Employee( 'Steve' , 'Engineering' ); employeeDao.add(employee); Manager manager = new Manager( 'Bob' ); managerDao.add(manager); task = new Task( 'Learn Spring' , manager, employee); taskDao.add(task); } @Test public void testAdd() { int size = timesheetDao.list().size(); Timesheet timesheet = newTimesheet(); timesheetDao.add(timesheet); assertTrue (size < timesheetDao.list().size()); } @Test public void testUpdate() { Timesheet timesheet = newTimesheet(); timesheetDao.add(timesheet); // update timesheet timesheet.setHours( 6 ); taskDao.update(timesheet.getTask()); timesheetDao.update(timesheet); Timesheet found = timesheetDao.find(timesheet.getId()); assertTrue( 6 == found.getHours()); } @Test public void testFind() { Timesheet timesheet = newTimesheet(); timesheetDao.add(timesheet); assertEquals(timesheet, timesheetDao.find(timesheet.getId())); } @Test public void testList() { assertEquals( 0 , timesheetDao.list().size()); Timesheet templateTimesheet = newTimesheet(); List<Timesheet> timesheets = Arrays.asList( newTimesheetFromTemplate(templateTimesheet, 4 ), newTimesheetFromTemplate(templateTimesheet, 7 ), newTimesheetFromTemplate(templateTimesheet, 10 ) ); for (Timesheet timesheet : timesheets) { timesheetDao.add(timesheet); } List<Timesheet> found = timesheetDao.list(); assertEquals( 3 , found.size()); for (Timesheet timesheet : found) { assertTrue (timesheets.contains(timesheet)); } } @Test public void testRemove() { Timesheet timesheet = newTimesheet(); timesheetDao.add(timesheet); // successfully added assertEquals(timesheet, timesheetDao.find(timesheet.getId())); // try to remoce timesheetDao.remove(timesheet); assertNull (timesheetDao.find(timesheet.getId())); } /** * @return Dummy timesheet for testing */ private Timesheet newTimesheet() { return new Timesheet(employee, task, 5 ); } private Timesheet newTimesheetFromTemplate(Timesheet template, Integer hours) { return new Timesheet( template.getWho(), template.getTask(), hours ); } } |
Вы можете запускать свои тесты как отдельные классы или все вместе из вашей IDE, или вы можете запускать их как «тестовую» цель из Maven, например, так (переключитесь на каталог проекта):
1
|
$ mvn test |
Тесты во многом похожи, поэтому, если вы можете понять хотя бы один из них, вам, вероятно, будет хорошо просто скопировать и вставить их в свой собственный проект. Если вы хотите потратить немного больше времени, не стесняйтесь писать их самостоятельно и немного экспериментируйте, чтобы лучше узнать Hibernate.
Что касается DAO, мы почти закончили. Однако осталось одно — наш интерфейс TimesheetService. Это набор бизнес-операций, в которые мы вмешиваемся, поэтому давайте реализуем его с помощью Hibernate. Мы поместим класс TimesheetServiceImpl в пакет org.timesheet.service.impl :
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
86
87
88
89
90
|
package org.timesheet.service.impl; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; 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 org.timesheet.domain.Employee; import org.timesheet.domain.Manager; import org.timesheet.domain.Task; import org.timesheet.service.TimesheetService; import org.timesheet.service.dao.TaskDao; import java.util.ArrayList; import java.util.List; import java.util.Random; @Transactional (propagation= Propagation.REQUIRED, readOnly= false ) @Service ( 'timesheetService' ) public class TimesheetServiceImpl implements TimesheetService { // dependencies private SessionFactory sessionFactory; private TaskDao taskDao; private Random random = new Random(); @Autowired public void setSessionFactory(SessionFactory sessionFactory) { this .sessionFactory = sessionFactory; } @Autowired public void setTaskDao(TaskDao taskDao) { this .taskDao = taskDao; } public SessionFactory getSessionFactory() { return sessionFactory; } public TaskDao getTaskDao() { return taskDao; } private Session currentSession() { return sessionFactory.getCurrentSession(); } @Override public Task busiestTask() { List<Task> tasks = taskDao.list(); if (tasks.isEmpty()) { return null ; } Task busiest = tasks.get( 0 ); for (Task task : tasks) { if (task.getAssignedEmployees().size() > busiest.getAssignedEmployees().size()) { busiest = task; } } return busiest; } @Override public List<Task> tasksForEmployee(Employee employee) { List<Task> allTasks = taskDao.list(); List<Task> tasksForEmployee = new ArrayList<Task>(); for (Task task : allTasks) { if (task.getAssignedEmployees().contains(employee)) { tasksForEmployee.add(task); } } return tasksForEmployee; } @Override public List<Task> tasksForManager(Manager manager) { Query query = currentSession() .createQuery( 'from Task t where t.manager.id = :id' ); query.setParameter( 'id' , manager.getId()); return query.list(); } } |
Обратите внимание, что на этот раз мы используем аннотацию @Service (мы говорили об этом раньше). Также мы вводим некоторые DAO с помощью сеттера . Некоторые бизнес-методы не реализованы наиболее эффективно, но мы демонстрируем, что мы можем либо смешивать общую логику DAO, либо создавать собственные запросы с использованием HQL. Мы могли бы выбрать Criteria API, сейчас это не имеет значения. Самым большим недостатком HQL является то, что это простые строки, поэтому он не удобен для рефакторинга — если вы не используете надлежащие инструменты. Например, IntelliJ имеет автозаполнение даже для простых строк. Просто выясняется, что вы пишете HQL. Довольно полезна также консоль HQL, у IntelliJ есть такая, и есть плагин для Eclipse .
Подсветка IntelliJ и автозаполнение для HQL:
Теперь мы должны протестировать этот сервис. На этот раз мы не хотим создавать экземпляры сущностей в Java, мы будем использовать внешние сценарии SQL, которые мы создали ранее — для настройки и очистки данных.
Давайте поместим тестовый класс TimesheetServiceTest в пакет org.timesheet.service в папке src / test / java. В следующем коде обратите внимание, как мы используем bean-компонент jdbcTemplate:
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
|
package org.timesheet.service; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.FileSystemResource; import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; import org.springframework.test.jdbc.SimpleJdbcTestUtils; import org.timesheet.domain.Employee; import org.timesheet.domain.Manager; import org.timesheet.domain.Task; import org.timesheet.service.dao.EmployeeDao; import org.timesheet.service.dao.ManagerDao; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @ContextConfiguration (locations = '/persistence-beans.xml' ) public class TimesheetServiceTest extends AbstractJUnit4SpringContextTests { @Autowired private TimesheetService timesheetService; // resources for accessing data during the testing @Autowired private SimpleJdbcTemplate jdbcTemplate; @Autowired private EmployeeDao employeeDao; @Autowired private ManagerDao managerDao; private final String createScript = 'src/main/resources/sql/create-data.sql' ; private final String deleteScript = 'src/main/resources/sql/cleanup.sql' ; @Before public void insertData() { SimpleJdbcTestUtils.executeSqlScript(jdbcTemplate, new FileSystemResource(createScript), false ); } @After public void cleanUp() { SimpleJdbcTestUtils.executeSqlScript(jdbcTemplate, new FileSystemResource(deleteScript), false ); } @Test public void testBusiestTask() { Task task = timesheetService.busiestTask(); assertTrue( 1 == task.getId()); } @Test public void testTasksForEmployees() { Employee steve = employeeDao.find(1L); Employee bill = employeeDao.find(2L); assertEquals( 2 , timesheetService.tasksForEmployee(steve).size()); assertEquals( 1 , timesheetService.tasksForEmployee(bill).size()); } @Test public void testTasksForManagers() { Manager eric = managerDao.find(1L); assertEquals( 1 , timesheetService.tasksForManager(eric).size()); } } |
Хорошо, вот и все. Мы внедрили DAO и сервисный уровень. Это включает в себя довольно много кода, поэтому прежде чем продолжить, убедитесь, что ваша структура проекта выглядит следующим образом:
Ссылка: Часть 3 — DAO и уровень обслуживания от нашего партнера JCG Михала Вртиака в блоге vrtoonjava .