Spring Data JPA - kontynuacja aplikacji lifetimer

Kontynuujmy aplikacje z poprzedniego wpisu licznik życia.

Dodamy do niego połączenie z bazą danych. Wykorzystamy do tego bazę h2, HikariDataSource, Hibernate i tytułowy JPA Spring Data. Pokażę i wytłumaczę jak to działa w praktyce z uwzględnieniem teorii na ten temat. Proponuję zajrzeć do mojego poprzedniego wpisu w którym przeczytacie jak działają warstwy połączenia z bazą danych aplikacji Java.

W celu prezentacji stworzę osobny projekt, który będzie zaczątkiem do aplikacji samodzielnej. Dołączę do niego poprzedni projekt, który będzie służył za logikę aplikacji. Efekt ten zyskuje dodając do pom'a dane o projekcie. Grupa oraz wersja jest identyczna jak w nowym projekcie. Różnią się jedynie nazwa projektu (artifactId).

Wykonuję to poniższym fragmentem pliku:

<dependency> <groupId>${project.groupId}</groupId> <artifactId>lifetimer</artifactId> <version>${project.version}</version> </dependency>

Zajmijmy się teraz samym połączeniem do bazy danych. Potrzebujemy do tego sterownika bazy danych. Znajduję sobie najnowszą wersję sterownika do bazy h2 i dodaje go do pom'a.

<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.192</version> </dependency>

Moglibyśmy już zacząć się bawić obsługą bazy danych, ale dodamy kilka warstw, które pozwolą nam na łatwą i obiektową obsługę repozytorium. Wykorzystamy do tego wspomniane na początku biblioteki HikariDataSource, Hibernate i JPA Spring Data.

Dobrze! Mamy wszystkie biblioteki, które potrzebujemy do samego połączenia z bazą.

Trzeba teraz je odpowiednio skonfigurować. Wykorzystując spring context mamy dwie opcje skonfigurowania aplikacji. Tworząc konfigurację poprzez adnotacje lub plik .xml. Przedstawię wam obydwa rozwiązania:

Adnotacje:

Plik xml:

Wytłumaczę teraz za co odpowiadają poszczególne części konfiguracji.

<context:component-scan base-package="pl.devpragmatic.lifetimer.service"/>

lub

@ComponentScan(basePackages = "pl.devpragmatic.lifetimer.service")

Powyższy kod to są polecenia dla springa, które przekazują mu informacje, żeby automatycznie wyszukiwał komponenty pod ścieżką "base-package" (czyli u nas: "pl.devpragmatic.lifetimer.service").

<jpa:repositories base-package="pl.devpragmatic.lifetimer.repository" />

lub

@EnableJpaRepositories(basePackages = {"pl.devpragmatic.lifetimer.repository"})

Przekazuję infomacje do aplikacji, że repozytorium JPA ma szukać pod ścieżką "base-package" (czyli u nas: "pl.devpragmatic.lifetimer.repository").

<bean id="propertyPlaceholder" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/> <property name="ignoreResourceNotFound" value="true"/> <property name="locations"> <list> <value>classpath:application.properties</value> </list> </property> </bean>

lub

@PropertySource("classpath:application.properties")

Powyższa konfiguracja pozwala na używanie zmiennych środowiskowych, które są umieszczone w pliku application.properties. Celowo wyciągnąłem zmienne takie jak konfiguracja bazy danych i hibernate. Przydaje się to w celu szybkiej zmiany na działającym już środowisku bez kompilowania kodu (jeżeli używamy adnotacji) lub wyszukiwania i podmieniania tych wartości (jeżeli używamy plików .xml).

<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close"> <property name="driverClassName" value="${db.driver}" /> <property name="jdbcUrl" value="${db.url}" /> <property name="username" value="${db.username}" /> <property name="password" value="${db.password}" /> </bean>

lub

@Bean(destroyMethod = "close") DataSource dataSource(Environment env) { HikariConfig dataSourceConfig = new HikariConfig(); dataSourceConfig.setDriverClassName(env.getRequiredProperty("db.driver")); dataSourceConfig.setJdbcUrl(env.getRequiredProperty("db.url")); dataSourceConfig.setUsername(env.getRequiredProperty("db.username")); dataSourceConfig.setPassword(env.getRequiredProperty("db.password")); return new HikariDataSource(dataSourceConfig); }

Tworzone jest tutaj źródło danych. Przekazujemy do niego parametry takie jak sterownik do bazy danych, adres bazy, użytkownika i hasło do niego.

@Bean LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, Environment env) { LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); entityManagerFactoryBean.setDataSource(dataSource); entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); entityManagerFactoryBean.setPackagesToScan("pl.devpragmatic.lifetimer.domain"); Properties jpaProperties = new Properties(); jpaProperties.put("hibernate.dialect", env.getRequiredProperty("hibernate.dialect")); jpaProperties.put("hibernate.hbm2ddl.auto", env.getRequiredProperty("hibernate.hbm2ddl.auto") ); jpaProperties.put("hibernate.ejb.naming_strategy", env.getRequiredProperty("hibernate.ejb.naming_strategy") ); jpaProperties.put("hibernate.show_sql", env.getRequiredProperty("hibernate.show_sql") ); jpaProperties.put("hibernate.format_sql", env.getRequiredProperty("hibernate.format_sql") ); entityManagerFactoryBean.setJpaProperties(jpaProperties); return entityManagerFactoryBean; }

lub

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="packagesToScan" value="pl.devpragmatic.lifetimer.domain" /> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/> </property> <property name="jpaProperties"> <props> <prop key="hibernate.dialect">${hibernate.dialect}</prop> <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop> <prop key="hibernate.ejb.naming_strategy">${hibernate.ejb.naming_strategy}</prop> <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> <prop key="hibernate.format_sql">${hibernate.format_sql}</prop> </props> </property> </bean>

Tutaj posiadamy już konfiguracje JPA. Musimy podać mu źródło danych, ścieżke do encji(gdy używamy adnotacji do deklaracji encji), dostawce (u nas Hibernate) oraz parametry dla dostawcy.

Opis parametrów tutaj umieszczonych:

  • hibernate.dialect - czyli sterownik komunikacji (u nas to będzie org.hibernate.dialect.H2Dialect)
  • hibernate.hbm2ddl.auto - automatyczna walidacja lub eksportowanie schematu DDL (języka definicji danych) do bazy danych. Definiujemy tutaj właśnie strategie jaką ma przyjąć przy otwarciu sesji z bazą danych. Możliwe są:
    • validate - weryfikacja poprawności schematu, nie robi żadnych zmian na bazie,
    • update - modyfikuje schemat bazy danych,
    • create - tworzy tabele na nowo w schemacie bazy i usuwa poprzednie dane,
    • create-drop - wykonuje czynność operacji create i na końcu sesji usuwa schemat bazy danych,
  • hibernate.ejb.naming_strategy - czyli strategia tworzenia nazw tabel,
  • hibernate.show_sql - flaga czy powinny być logowane zapytania sql,
  • hibernate.format_sql - flaga czy powinny zapytania być formatowane.

Parametrów konfiguracyjnych jest o wiele, wiele więcej. Opisałem tutaj tylko te, które zostały wykorzystane w kodzie naszego programu.

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean>

lub

@Bean JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { JpaTransactionManager transactionManager = new JpaTransactionManager(); transactionManager.setEntityManagerFactory(entityManagerFactory); return transactionManager; }

To deklaracja menadżera tranzakcji. Tutaj jest podstawowa konfiguracja w której ustawiany jest tylko EntityManagerFactory.

<tx:annotation-driven transaction-manager="transactionManager"/>

lub

@EnableTransactionManagement

Tutaj zaznaczamy Springowi, że zezwalamy na obsługę tranzakcji poprzez adnotacje.

Ok, starczy tej konfiguracji. Zróbmy CrudRepository dla klasy Time. W konfiguracji zaznaczyliśmy, że JPA Repository ma wyszukiwać w ścieżce "pl.devpragmatic.lifetimer.repository". Posiadamy pod tą ścieżką naszą klasę TimeRepository. Wystarczą małe modyfikacje i dostosujemy ją do nowego interfejsu.

Oznaczmy także klase Time adnotacjami, aby hibernate wiedział jak ma się zachować z tą klasą.

Właściwie już koniec implementacji naszego połączenia z bazą. Możemy teraz napisać program obsługujący to. Zróbmy prosty program, który będzie tworzył i pobierał obiekty z bazy.

Wywołanie programu z użyciem konfiguracji w klasie Javowej.

Wywołanie programu z użyciem konfiguracji w pliku .xml.

Przedstawiłem Wam w tym wpisie jak prosto zrobić aplikację, która korzysta z bazy danych. Zapraszam do przeczytania mojego poprzedniego wpisu, który wytłumaczy Wam jak działają poszczególne warstwy baz danych. Projekty są umieszczone na repozytorium github LifeTimerDesktop i LifeTimer.

Comments

Popular posts from this blog

Why TDD is bad practice?

How correctly imitate dependencies?

Software development using Angular 5 with Spring Boot