Spring облегчает использование Hibernate в приложениях,беря на себя создание объектов Hibernate и управление ими. Кроме того, Spring позволяет разделить конфигурацию Hibernate от конфигурации базы данных и конфигурации пула соединений.
Подготовка
Нам понадобится пустой maven проект с Spring, Spring ORM, H2, Hibernate и библиотеками тестирования:
1
2
3
4
5
6
7
8
9
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
|
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javaee.version>7.0</javaee.version>
<lombok.version>1.16.12</lombok.version>
<org.springframework.version>4.3.4.RELEASE</org.springframework.version>
<hibernate.version>5.2.5.Final</hibernate.version>
<h2.version>1.4.190</h2.version>
<junit.version>4.12</junit.version>
<hamcrest.version>1.3</hamcrest.version>
<easymock.version>3.3.1</easymock.version>
</properties>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>${easymock.version}</version>
</dependency>
</dependencies>
|
Настройка Hibernate
Конфигурация Hibernate традиционно располагается в файле hibernate.cfg.xml В моём примере я использую H2 в качестве базы данных:
1
2
3
4
5
6
7
8
|
<hibernate-configuration>
<session-factory>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property>
<property name="hibernate.connection.url">jdbc:h2:mem:test</property>
<mapping class="ru.easyjava.spring.data.hibernate.entity.Greeter"/>
</session-factory>
</hibernate-configuration>
|
Можно использовать любые базы данных и пулы соединений.
Настройка Spring
Для включения поддержки Hibernate в Spring необходимо добавить в контекст особый бин. Можно сделать это программно, а можно с использованием xml конфигурации:
1
2
3
|
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="configLocation" value="hibernate.cfg.xml"/>
</bean>
|
Свойство configLocation указывает на имя и расположение файла конфигурации Hibernate.
Вместо настройки базы в конфигурации hibernate можно использовать Spring для разделения настроек соединения, пула соединений и Hibernate:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<beans>
<bean id="h2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.h2.Driver"/>
<property name="url" value="dbc:h2:mem:test"/>
</bean>
<bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="h2"/>
<property name="mappingResources">
<list>
<value>greeter.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.H2Dialect
</value>
</property>
</bean>
</beans>
|
В примере выше создаётся пул соединений DBCP к базе H2, который передаётся в конфигурацию Hibernate.
Схема данных
ORM отображает объектную модель данных на реляционную модель данных. Чтобы не загораживать пример, используем как можно более простую модель:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Entity
public class Greeter {
@Id
@GeneratedValue
@Getter
@Setter
private Integer id;
@Getter
@Setter
private String greeting;
@Getter
@Setter
private String target;
}
|
Аннотация @Entity говорит Hibernate, что это класс сущности, а @Getter и @Setter генерируют код для доступа к полям.
Уровень DAO
Хорошим тоном разработки является разделение кода, который работает непосредственно с базами данных (уровень DAO), от кода, который обрабатывает данные (уровень сервисов). Это позволяет абстрагировать сервисы от конкретной реализации DAO и, при необходимости, менять эти реализации без изменения кода сервисов.
1
2
3
4
5
|
public interface GreeterDao {
void addGreet(Greeter g);
List<Greeter> getGreetings();
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@Repository
public class GreeterDaoImpl implements GreeterDao {
@Inject
private SessionFactory sessionFactory;
@Override
public final void addGreet(final Greeter g) {
Session s = sessionFactory.openSession();
s.getTransaction().begin();
s.persist(g);
s.getTransaction().commit();
}
@SuppressWarnings("unchecked")
@Override
public final List<Greeter> getGreetings() {
return sessionFactory.openSession()
.createCriteria(Greeter.class)
.list();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@ContextConfiguration("/applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class GreeterDaoImplIT {
@Inject
private GreeterDao testedObject;
@DirtiesContext
@Test
public void testRetrieve() {
Greeter expected = new Greeter();
expected.setGreeting("TEST");
expected.setTarget("TEST");
testedObject.addGreet(expected);
List<Greeter> actual = testedObject.getGreetings();
Iterator<Greeter> it = actual.iterator();
Greeter actualGreet =it.next();
assertThat(actualGreet.getGreeting(), is("TEST"));
assertThat(actualGreet.getTarget(), is("TEST"));
}
}
|
Ключевой частью нашего DAO класса является объект SessionFactory, который Spring самостоятельно внедряет в класс.
Использование JPA в приложении
Наконец, напишем сервис который будет использовать DAO, определённый выше, и делать что-нибудь с этими данными.
1
2
3
|
public interface GreeterService {
String greet();
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Service
public class GreeterServiceImpl implements GreeterService {
@Inject
private GreeterDao dao;
@Override
public final String greet() {
List<Greeter> greets = dao.getGreetings();
Iterator<Greeter> it = greets.iterator();
if (!it.hasNext()) {
return "No greets";
}
Greeter greeter = it.next();
return greeter.getGreeting() + ", " + greeter.getTarget();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public class GreeterServiceImplTest extends EasyMockSupport {
@Rule
public EasyMockRule em = new EasyMockRule(this);
@Mock
private GreeterDao dao;
@TestSubject
private GreeterServiceImpl testedObject = new GreeterServiceImpl();
@Test
public void testNoGreets() {
expect(dao.getGreetings()).andReturn(Collections.EMPTY_LIST);
replayAll();
assertThat(testedObject.greet(), is("No greets"));
}
@Test
public void testGreets() {
Greeter expected = new Greeter();
expected.setGreeting("TEST");
expected.setTarget("TEST");
expect(dao.getGreetings()).andReturn(Collections.singletonList(expected));
replayAll();
assertThat(testedObject.greet(), is("TEST, TEST"));
}
}
|
Обратите внимание, что тест сервиса проверяет только сервис, заменяя GreeterDao его подделкой. В то время как тесты DAO являются интеграционными и работают непосредственно с базой.
Поскольку я писал код сразу с модульными и интеграционными тестами, то у меня есть некоторая уверенность, что он работает, но, тем не менее, попробуем его запустить:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public static void main(final String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("/applicationContext.xml");
GreeterService greeterService = context.getBean(GreeterService.class);
GreeterDao dao = context.getBean(GreeterDao.class);
Greeter greeter = new Greeter();
greeter.setGreeting("Hello");
greeter.setTarget("World");
dao.addGreet(greeter);
System.out.println(greeterService.greet());
System.exit(0);
}
|
1
2
3
4
5
|
Dec 23, 2016 12:23:09 PM org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@35293c05] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
Dec 23, 2016 12:23:09 PM org.hibernate.internal.SessionImpl createCriteria
WARN: HHH90000022: Hibernate's legacy org.hibernate.Criteria API is deprecated; use the JPA javax.persistence.criteria.CriteriaQuery instead
Hello, World
|
Код примера доступен на github