my.life.logging.Blog

Конфигурация связки «Spring - Hibernate» без использования XML

В продолжение поста про конфигурацию Spring MVC без использования XML, решил рассказать о том, как, опять же не написав ни строчки XML, подключить к приложению Spring такой ORM-фреймворк, как Hibernate. И хотя программная конфигурация связки “Spring – Hibernate” не выглядит настолько же красивой, как и программная конфигурация Spring MVC – знать о подобной возможности уж точно не помешает.

Проектные зависимости

В дополнение к зависимостям нашего примера, фигурировавшим в предыдущей записи, добавим к проекту следующие зависимости, связанные с Hibernate:

  • Библиотеки Spring Framework 3.2.2, необходимые для интеграции с Hibernate – spring-orm и spring-jdbc
  • Собственно, сам фреймворк Hibernate 4.1.4
  • И в качестве базы данных будем использовать базу данных в памяти1 HSQLDB последней на момент написания поста версии 2.2.9

В виде, пригодном для Maven, все эти зависимости отражены в следующем фрагменте pom.xml:

pom.xml
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
  <!-- ... -->

  <properties>
      <spring.version>3.2.2.RELEASE</spring.version>
  </properties>

  <dependencies>
      <!-- Зависимости Spring 3 -->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-core</artifactId>
          <version>${spring.version}</version>
      </dependency>

      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>${spring.version}</version>
      </dependency>

      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-orm</artifactId>
          <version>${spring.version}</version>
      </dependency>

      <!-- ... Зависимости Spring 3 MVC ... -->
      
      <!-- Зависимость от Hibernate -->
      <dependency>
          <groupId>org.hibernate</groupId>
          <artifactId>hibernate-core</artifactId>
          <version>4.1.4.Final</version>
      </dependency>

      <!-- Зависимость от HSQLDB -->
      <dependency>
          <groupId>org.hsqldb</groupId>
          <artifactId>hsqldb</artifactId>
          <version>2.2.9</version>
          <scope>runtime</scope>
      </dependency>

      <!-- ... Другие зависимости проекта: Servlet API 3.0, JSTL и т.д. ... -->

  </dependencies>

  <!-- ... -->

Настройка компонентов Hibernate

Для настройки компонентов Hibernate мы будем использовать рассмотренный в предыдущей записи корневой контекст Spring, описываемый в классе-конфигурации AppConfig. Напомню, что этот класс, как и любой другой класс для программной конфигурации Spring, должен быть помечен аннотацией @Configuration:

AppConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
@ComponentScan(basePackages = { "net.shafranov.spring.noxml.core" })
@Import({ OrmConfig.class })
public class AppConfig {

    @Bean
    public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
        PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
        ppc.setLocation(new ClassPathResource("app.properties"));
        ppc.setIgnoreUnresolvablePlaceholders(true);
        return ppc;
    }
}

Аннотация @ComponentScan, следующая сразу за аннотацией @Configuration, указывает, как это должно быть известно, в каких пакетах будет произведен поиск компонентов Spring. В данном случае все компоненты Spring, связанные с корневым контекстом, находятся в пакете net.shafranov.spring.noxml.core.

А вот третья аннотация @Import перемещает фокус повествования с класса конфигурации AppConfig на другой класс – OrmConfig. Последний тоже является классом конфигурации, и при помощи аннотации @Import он будет присоединен к корневому контексту Spring. И именно в классе OrmConfig развернутся самые интересные события процесса настройки Hibernate!

В классе AppConfig мы опишем в итоге всего один компонент типа PropertyPlaceholderConfigurer. Он нам нужен для того, чтобы иметь возможность использовать значения свойств из файла app.properties (который находится в корне CLASSPATH) для настройки компонентов Spring.

Давайте же посмотрим, наконец, на листинг класса OrmConfig:

OrmConfig.java
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
@Configuration
@EnableTransactionManagement
public class OrmConfig {

    // Свойства источника данных
    @Value("${jdbc.driverClassName}")
    private String driverClassName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    /**
     * Компонент источника данных
     */
    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    // Свойства Hibernate
    @Value("${hibernate.dialect}")
    private String hibernateDialect;
    @Value("${hibernate.show_sql}")
    private String hibernateShowSql;
    @Value("${hibernate.hbm2ddl.auto}")
    private String hibernateHBM2DDLAuto;

    /**
     * Свойства Hibernate в виде объекта класса Properties
     */
    @Bean
    public Properties hibernateProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.dialect", hibernateDialect);
        properties.put("hibernate.show_sql", hibernateShowSql);
        properties.put("hibernate.hbm2ddl.auto", hibernateHBM2DDLAuto);
        return properties;
    }

    /**
     * Фабрика сессий Hibernate
     */
    @Bean
    @SuppressWarnings("deprecation")
    public SessionFactory sessionFactory() {
        return new LocalSessionFactoryBuilder(dataSource())
                .scanPackages("net.shafranov.spring.noxml.core.model")
                .addProperties(hibernateProperties())
                // используем устаревший метод, так как Spring не оставляет нам выбора
                .buildSessionFactory();
    }

    /**
     * Менеджер транзакций
     */
    @Bean
    public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
        HibernateTransactionManager htm = new HibernateTransactionManager();
        htm.setSessionFactory(sessionFactory);
        return htm;
    }

}

Если мы говорим о настройке Hibernate в контексте Spring, то программная конфигурация ничем по большому счету не отличается от настройки с помощью XML. То есть в данном случае мы сначала создаем компоненты источника данных и свойств Hibernate, которые затем используются для инициализации компонента SessionFactory. Компонент SessionFactory в свою очередь оборачивается в компонент менеджера транзакции HibernateTransactionManager. В плане поддержки транзакции главное – не забыть про аннотацию @EnableTransactionManagement, которая по сути своей аналогична элементу XML-конфигурации <tx:annotation-driven />.

Единственный некрасивый момент в программной конфигурации – это дублирование, связанное с необходимостью создавать отдельное поле в классе-конфигурации для каждого используемого свойства, а затем при помощи аннотации @Value записывать в это поле соответствующее значение из вышеупомянутого файла app.properties. Но этот момент, наверное, можно пережить…

Внимательные читатели должны были заметить, что используемый в нашем примере метод buildSessionFactory() класса LocalSessionFactoryBuilder помечен как @Deprecated. Без паники! Такое положение вещей связано с тем, что в Hibernate 4 появился новый метод buildSessionFactory(ServiceRegistry), а старый метод buildSessionFactory() был объявлен устаревшим, но оставлен для обратной совместимости. В данный момент одной из тех библиотек, которые в том числе полагаются на обратную совместимость в Hibernate 4, как раз является Spring Framework. Так что использование устаревшего метода в нашем примере связано лишь с тем, что Spring пока совсем никак не поддерживает новый метод. Некоторые подробности есть в этом ответе на StackOverflow.

“Настоящая поэзия”

Очень бы хотелось, чтобы наш пример приложения, помимо вышеописанной конфигурации Spring, включал в себя также некоторый код, реализующий хотя бы основные CRUD-операции. Ведь надо же как-то проверить, что конфигурация действительно рабочая! Но в то же время писать скудный пользовательский интерфейс с кривой табличкой и кнопками “Добавить” и “Удалить” мне лично не очень хотелось. Во-первых, это долго, во-вторых, скучно, в-третьих, это клише.

В итоге, немного пораскинув мозгами, я решил полностью отказаться от какого-либо взаимодействия с пользователем при разработке интерфейса приложения. Короче говоря, поступим следующим образом2.

Через некоторое время после загрузки главной страницы приложения с надписью “The Poem” происходит обновление главной страницы. При каждом обновлении в базу данных записывается строка из стихотворения, которая затем отображается на странице при следующей ее загрузке. После записи в базу всех четырех строк стихотворения, происходит полная очистка таблицы строк. Выглядит все это так, как показано на рисунке:

Стишок

Код сущности “Строка стихотворения”:

VerseLine.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
@Table(name = "VERSE_LINES" )
public class VerseLine {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "ORD", nullable = false)
    private Integer order;

    @Column(name = "TEXT", nullable = false)
    private String text;

    // ... Геттеры и сеттеры ...
}

Код контроллера главной страницы:

GoodbyeXmlController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Controller
public class GoodbyeXmlController {

    @Autowired
    private VerseService verseService;

    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public String printGoodbye(ModelMap model) {

        model.addAttribute( "verseLines", verseService.loadVerse());

        verseService.updateVerse();

        return "goodbye" ;
    }
}

Метод loadVerse() сервиса VerseService загружает все имеющиеся в данный момент в базе строки стихотворения для отображения их на странице. Метод updateVerse() в свою очередь ведет себя по-разному в зависимости от количества строк в базе: если число строк стихотворения еще не достигло 4, то он просто добавляет в базу еще одну строку; в противном случае – он просто удаляет все строки из таблицы строк, и процесс начинается заново.

Полагаю, что с кодом сервиса VerseService, используемого в контроллере, а также с кодом DAO-класса, используемого в сервисе, проще будет ознакомиться непосредственно в исходниках примера, которые можно скачать по этой ссылке.

Счастливого кодинга! И до скорых встреч!


  1. Все-таки некоторые вещи, как и в частности термин “in-memory database”, лучше никогда не переводить с английского.

  2. Если эта статья не даст вам ничего нового в плане понимания Spring, но натолкнет на какие-либо мысли о том, как можно писать собственные приложения так, чтобы в них не возникало необходимости взаимодействовать с пользователем - я также вполне буду считать свою задачу выполненной!

Comments