Spring Data JPA. Работа с БД. Часть 1

Spring — достаточно многофункциональный framework, и если вы надумали делать enterprise проект, то вам не обойтись без возможности работать с базами данных. В этой серии уроков, мы научимся работать с БД используя возможности Spring Data.

 

Шаг 0. Постановка задачи

Все проект начинаются именно с постановки задачи. Вы должны определится с тем какая главная цель проект, и какие проблемы либо задачи будет решать ваш проект.

Нашей задачей будет: написать банковскую систему управления клиентами и персоналом. Может звучит немного и сложно, но на самом деле все довольно просто.

Давайте посмотри на рисунок ниже:

Примерно так выглядит облегченная модель банковской системы.

 

Шаг 1. Моделируем ERD

Теперь нужно на основании построенной модели выше на изображении, смоделировать ERD диаграмму.

ERD (Entity-Relationship Diagram) — это отображение модели сущностей в базе данных. Попросту мы моделируем сущности (таблицы) базы данных для определенной модели.

Вот такая ERD получилась у меня для нашей модели Банковской Системы.

Для построения ERD я использую сервис lucidchart.com

Теперь немного разберем нашу ERD:

worker — таблица, которая будет хранить все сотрудников определенного банка.

bank — таблица, которая будет хранить банки системы.

client — таблица клиентов банка, или нескольких банков.

bank_of_account — таблица, которая будет хранить счета клиентов банка.

Как вы видите между таблицами есть связи, о них вы можете почитать тут — Как связать Entity в JPA?

 

Шаг 2. Создание проекта и добавление Dependencies

Создаем новый проект и называем его com.devcolibri.dataexam.

И pom.xml будет иметь следующие зависимости:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.devcolibri.dataexam</groupId>
    <artifactId>com.devcolibri.dataexam</artifactId>
    <version>1.0</version>
    <packaging>war</packaging>

    <properties>
        <java.version>1.7</java.version>
        <spring.mvc>4.0.3.RELEASE</spring.mvc>
        <spring.data>1.3.4.RELEASE</spring.data>
        <javax.servlet>3.0.1</javax.servlet>
        <mysql.version>5.1.29</mysql.version>
        <hb.manager>4.2.5.Final</hb.manager>
        <spring.test>3.2.4.RELEASE</spring.test>
        <junit.version>4.11</junit.version>
    </properties>

    <dependencies>

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

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${javax.servlet}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

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

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hb.manager}</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

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

    </dependencies>

    <build>
        <finalName>devcolibri-dataexam</finalName>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Как вы уже могли заметить использую я MySQL поэтому и зависимость подключил именно для этой БД.

Если вы захотите использовать другую БД, то ничего вам не мешает поменять зависимость и данные в файле свойств.

 

Шаг 3. Конфигурация проекта

Теперь нам нужно сконфигурировать проект, а именно Spring Configuration.

Для начало посмотрите на текущую структуру проекта ниже и создайте себе такую же.

В resources есть файл app.properties в нем хранятся все свойства, необходимые проекта, в нашем случае это доступ к БД и т.п.

#DB properties:
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/testdb
db.username=root
db.password=root

#Hibernate Configuration:
db.hibernate.dialect=org.hibernate.dialect.MySQLDialect
db.hibernate.show_sql=true
db.entitymanager.packages.to.scan=com.devcolibri.dataexam.entity
db.hibernate.hbm2ddl.auto = create

Данные свойства мы будем использовать в конфигурации EntityManager ниже.

Теперь обратите своё внимание на пакет configuration, в класс AppInitializer добавим следующее содержимое:

package com.devcolibri.dataexam.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{
                DataConfig.class
        };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[0];
    }

    @Override
    protected String[] getServletMappings() {
        return new String[0];
    }
}

Я выделил 9-ю строку, так как в ней мы регистрируем конфигурацию необходимых нам бинов для работы с БД.

А теперь давайте создадим саму конфигурацию DataConfig, которая будет инлдектить JPA бины:

package com.devcolibri.dataexam.config;

import org.hibernate.ejb.HibernatePersistence;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@EnableTransactionManagement
@ComponentScan("com.devcolibri.dataexam")
@PropertySource("classpath:app.properties")
@EnableJpaRepositories("com.devcolibri.dataexam.repository")
public class DataConfig {

    private static final String PROP_DATABASE_DRIVER = "db.driver";
    private static final String PROP_DATABASE_PASSWORD = "db.password";
    private static final String PROP_DATABASE_URL = "db.url";
    private static final String PROP_DATABASE_USERNAME = "db.username";
    private static final String PROP_HIBERNATE_DIALECT = "db.hibernate.dialect";
    private static final String PROP_HIBERNATE_SHOW_SQL = "db.hibernate.show_sql";
    private static final String PROP_ENTITYMANAGER_PACKAGES_TO_SCAN = "db.entitymanager.packages.to.scan";
    private static final String PROP_HIBERNATE_HBM2DDL_AUTO = "db.hibernate.hbm2ddl.auto";

    @Resource
    private Environment env;

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();

        dataSource.setDriverClassName(env.getRequiredProperty(PROP_DATABASE_DRIVER));
        dataSource.setUrl(env.getRequiredProperty(PROP_DATABASE_URL));
        dataSource.setUsername(env.getRequiredProperty(PROP_DATABASE_USERNAME));
        dataSource.setPassword(env.getRequiredProperty(PROP_DATABASE_PASSWORD));

        return dataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(dataSource());
        entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistence.class);
        entityManagerFactoryBean.setPackagesToScan(env.getRequiredProperty(PROP_ENTITYMANAGER_PACKAGES_TO_SCAN));

        entityManagerFactoryBean.setJpaProperties(getHibernateProperties());

        return entityManagerFactoryBean;
    }

    @Bean
    public JpaTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());

        return transactionManager;
    }

    private Properties getHibernateProperties() {
        Properties properties = new Properties();
        properties.put(PROP_HIBERNATE_DIALECT, env.getRequiredProperty(PROP_HIBERNATE_DIALECT));
        properties.put(PROP_HIBERNATE_SHOW_SQL, env.getRequiredProperty(PROP_HIBERNATE_SHOW_SQL));
        properties.put(PROP_HIBERNATE_HBM2DDL_AUTO, env.getRequiredProperty(PROP_HIBERNATE_HBM2DDL_AUTO));

        return properties;
    }

}

В дальнейшем я еще буду ссылаться на конфигурацию выше, для разъяснения некоторых вещей с этой конфигурации.

Разберем аннотации:

@Configuration — говорит, что данный класс является Spring конфигурацией;

@EnableTransactionManagement — включает TransactionManager для управления транзакциями БД;

@ComponentScan(«com.devcolibri.dataexam») — указываем Spring где нужно искать Entity, DAO, Service и т.п.;

@PropertySource(«classpath:app.properties») — подключаем файл свойств созданный выше;

@EnableJpaRepositories(«com.devcolibri.dataexam.repository») — включаем возможность использования JPARepository и говорим, где их искать. (будем рассматривать позже детальней).

private Environment env;

В нашем случае нужен для возможности получать свойства из property файла.

 

Шаг 4. Создаем Entity на основе нашей ERD диаграммы

Начнем с Банка (Bank):

package com.devcolibri.dataexam.entity;

import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;

@Entity
@Table(name = "bank")
public class Bank {

    @Id
    @GeneratedValue(generator = "increment")
    @GenericGenerator(name= "increment", strategy= "increment")
    @Column(name = "id", length = 6, nullable = false)
    private long id;

    @Column(name = "name")
    private String name;

    public Bank() {
    }

    public Bank(String name) {
        this.name = name;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Подробней о JPA можно почитать тут.

Теперь создаем BankAccount:

package com.devcolibri.dataexam.entity;

import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;

@Entity
@Table(name = "bank_account")
public class BankAccount {

    @Id
    @GeneratedValue(generator = "increment")
    @GenericGenerator(name= "increment", strategy= "increment")
    @Column(name = "id", length = 6, nullable = false)
    private long id;

    @Column(name = "currency")
    private double currency;

    @Column(name = "amount")
    private double amount;

    @Column(name = "amount_of_credit")
    private double amountOfCredit;

    @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST})
    @JoinColumn(name = "client_id", nullable = false)
    private Client client;

    public BankAccount() {
    }

    public BankAccount(double currency, double amount, double amountOfCredit, Client client) {
        this.currency = currency;
        this.amount = amount;
        this.amountOfCredit = amountOfCredit;
        this.client = client;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public double getCurrency() {
        return currency;
    }

    public void setCurrency(double currency) {
        this.currency = currency;
    }

    public double getAmount() {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }

    public double getAmountOfCredit() {
        return amountOfCredit;
    }

    public void setAmountOfCredit(double amountOfCredit) {
        this.amountOfCredit = amountOfCredit;
    }

    public Client getClient() {
        return client;
    }

    public void setClient(Client client) {
        this.client = client;
    }

}

Как вы уже заметили BankAccount использует Client, поэтому создаем Client entity:

package com.devcolibri.dataexam.entity;

import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;

@Entity
@Table(name = "client")
public class Client {

    @Id
    @GeneratedValue(generator = "increment")
    @GenericGenerator(name= "increment", strategy= "increment")
    @Column(name = "id", length = 6, nullable = false)
    private long id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Column(name = "phone_number")
    private String phoneNumber;

    @Column(name = "address")
    private String address;

    @Column(name = "email")
    private String email;

    @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST})
    @JoinColumn(name = "bank_id", nullable = false)
    private Bank bank;

    public Client(String firstName, String lastName, String phoneNumber,
                  String address, String email, Bank bank) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.phoneNumber = phoneNumber;
        this.address = address;
        this.email = email;
        this.bank = bank;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Bank getBank() {
        return bank;
    }

    public void setBank(Bank bank) {
        this.bank = bank;
    }
}

Ну и рабочие (Worker):

package com.devcolibri.dataexam.entity;

import com.devcolibri.dataexam.entity.enums.WorkerStatus;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;

@Entity
@Table(name = "worker")
public class Worker {

    @Id
    @GeneratedValue(generator = "increment")
    @GenericGenerator(name= "increment", strategy= "increment")
    @Column(name = "id", length = 6, nullable = false)
    private long id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Column(name = "status")
    private WorkerStatus status;

    @Column(name = "phone_number")
    private String phoneNumber;

    @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST})
    @JoinColumn(name = "bank_id", nullable = false)
    private Bank bank;

    public Worker() {
    }

    public Worker(String firstName, String lastName, WorkerStatus status, String phoneNumber, Bank bank) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.status = status;
        this.phoneNumber = phoneNumber;
        this.bank = bank;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public WorkerStatus getStatus() {
        return status;
    }

    public void setStatus(WorkerStatus status) {
        this.status = status;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public Bank getBank() {
        return bank;
    }

    public void setBank(Bank bank) {
        this.bank = bank;
    }

}

У каждого рабочего есть своя должность, эти должности мы положим в enum WorkerStatus:

package com.devcolibri.dataexam.entity.enums;

public enum WorkerStatus {
    MANAGER;
}

Как работать с Enums в JPA я показывал тут.

Дальше мы будем создавать DAO и Сервисы.

Spring Data JPA. Пишем DAO и Services. Часть 2

Урок создан: 22 марта 2014 | Просмотров: 71371 | Автор: Александр Барчук | Правила перепечатки


Добавить комментарий

Добавить комментарий

Ваш e-mail не будет опубликован.

Комментарии:

  • 23 марта 2014 в 14:53

    Alex

    шикарно!

  • 31 марта 2014 в 11:10

    Аноним

    Круто , наконец-то новые статьи !

  • 01 апреля 2014 в 17:18

    Сергей

    а можно что нибудь про Spring + MVC?

  • 07 июня 2014 в 16:19

    Павел

    Странно, но у меня проект не создаёт необходимые таблицы. При запуске вылетает много «мусора» в консоли, например: 07-Jun-2014 16:10:26.171 INFO [RMI TCP Connection(2)-127.0.0.1] org.hibernate.cfg.Environment. HHH000206: hibernate.properties not found.
    И ещё классы-сущности не помечаются никакими отметками связи с Spring data.
    При обращении к сервисам вылетает NullPointerException при выполнении addBank();
    Приложение сначала сам писал, потом скачал с сайта. В чем может быть проблема?

  • 22 июля 2014 в 01:24

    theNatd

    Всё, что ниже написано просто личные пожелания и возможно совет какие темы затронуть для дальнейшего развития.

    Если будет у Вас время, уделяйте больше внимания spring mvc — он хоть и громоздкий, но это сейчас практически один из нормальных способов писать под веб на java не считая Java EE 7.

    Видео про jdbc показать как использовать connection pools как в консольном так потом как подключить это добро в spring mvc
    Например одна из популярных библиотек для этого. http://sourceforge.net/projects/c3p0/

    Было бы ещё неплохо если бы вы рассказали про http://netty.io/ — Если вы конечно в курсе про этот framework.
    Ещё было бы неплохо дополнить курс java SE урока про мультитреадинг — екзекуторы, producer — consumer патерн показать как пример, Blockingqueue, semaphore и reentrantlock — я как бы это всё знаю, просто предлагаю вектор развития.

    А так, ваш сайт принёс мне больше позитива и интересного чтива, чем хабр. Уж очень мало там последнее время именно вот таких старых добрых технических статей.

    Подписался на rss и youtube, вы молодцы.

  • 30 ноября 2014 в 18:09

    Александр

    У меня в public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{} IDEA ругается на AbstractAnnotationConfigDispatcherServletInitializer. Дословно: «can not resolve symbol AbstractAnnotationConfigDispatcherServletInitializer» . Подскажите, как решить проблему?

    • 14 апреля 2015 в 06:32

      Андрей

      Эта библиотека появилась с версии Spring 3.2 возможно ее убрали. Потому что я поставил версию спринг 4.1.6, у меня тоже не импортировалась библиотека. Я поменял на версию 4.0.3 как в туториале и все заработало. Скорей всего єту библиотеку или заменили или удалили.(Хотя єто странно, так как в джаве одна из главніх идей это то что должно работать на новых версиях то что написано на старых версиях)

  • 04 декабря 2014 в 15:36

    provisota

    Очень хотелось бы что бы вы ещё выложили конфигурации спринга и хибера в XML виде.
    Не знаю как кому, а мне XML конфиги как — то уже привычней стали…

  • 14 января 2015 в 19:55

    Рыжий Кот

    Александр, после прочтения предыдущей статьи про связывание сущностей в JPA http://devcolibri.com/2046 у меня содалось впечатление, что в сущности Bank не хватает объявления @OneToMany. Или там как-то по другому делается?

    • 14 апреля 2015 в 08:40

      Андрей

      Там в остальных сущностях, есть @JointoColumn, оно так объединяется(как мне кажется). А one to many это просто обобщение понятия.

      • 30 сентября 2015 в 12:46

        ildar

        Александр, добрый день! У меня вопрос по транзакциям. Проаннотировав DataConfig аннотацией @EnableTransactionManagement и добавив bean, нам не обязательно в явном виде писать в CRUD-методах begin, commit и rollback?

  • 25 сентября 2015 в 15:53

    Александр

    Александр, здравствуйте! Подскажите пожалуйста, в этом примере не используется connection pool? В продакшене использовать connection pool или можно обойтись DataSource? Возможно в чем-то ошибаюсь. Спасибо.

  • 13 июня 2016 в 15:59

    Сергей

    Возник такой вопрос. Если мы захотим через @Query обратиться к bank_id в классе Client(в Client как такого нет bank_id, но он находится в bank ), то компилятор ругается, что несоответствие типов и что-то в этом роде. Как можно выйти из данной ситуации??

  • 06 февраля 2017 в 13:02

    lalala

    Автор, описанная диаграмма не является ERD. Это скорей схема базы данных. ERD несет немного другой смысл и строится по другому.