Spring облегчает использование Hibernate в приложениях,беря на себя создание объектов Hibernate и управление ими. Кроме того, Spring позволяет разделить конфигурацию Hibernate от конфигурации базы данных и конфигурации пула соединений.
Подготовка
Нам понадобится пустой maven проект с Spring, Spring ORM, H2, Hibernate и библиотеками тестирования:
<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 в качестве базы данных:
<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 конфигурации:
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> <property name="configLocation" value="hibernate.cfg.xml"/> </bean>
Свойство configLocation указывает на имя и расположение файла конфигурации Hibernate.
Вместо настройки базы в конфигурации hibernate можно использовать Spring для разделения настроек соединения, пула соединений и Hibernate:
<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 отображает объектную модель данных на реляционную модель данных. Чтобы не загораживать пример, используем как можно более простую модель:
@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 и, при необходимости, менять эти реализации без изменения кода сервисов.
public interface GreeterDao { void addGreet(Greeter g); List<Greeter> getGreetings(); }
@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(); } }
@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, определённый выше, и делать что-нибудь с этими данными.
public interface GreeterService { String greet(); }
@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(); } }
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 являются интеграционными и работают непосредственно с базой.
Поскольку я писал код сразу с модульными и интеграционными тестами, то у меня есть некоторая уверенность, что он работает, но, тем не менее, попробуем его запустить:
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); }
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