Java spring hibernate postgresql

Hibernate и Spring Boot

Содержание

Ранее мы уже рассматривали, как работать с базой данных через jdbc в статье Работа с БД в Spring Boot на примере postgresql. А сегодня возьмём Hibernate — самый популярный фреймворк для работы с БД — и убедимся, что он значительно облегчает реализацию типовых операций над сущностями.

Предположим, в БД у нас есть две сущности: страна и город. В одной стране может быть несколько городов (отношение «один-ко-многим»). Структура таблиц выглядит примерно так:

CREATE SEQUENCE country_id_seq;

CREATE TABLE country
(
id integer NOT NULL DEFAULT nextval( ‘country_id_seq’ ::regclass),
name character varying ( 50 ) NOT NULL ,
CONSTRAINT country_id_pk PRIMARY KEY (id)
);

CREATE SEQUENCE city_id_seq;

CREATE TABLE city
(
id integer NOT NULL DEFAULT nextval( ‘city_id_seq’ ::regclass),
name character varying ( 50 ) NOT NULL ,
country_id integer NOT NULL
);

И мы хотим совершать типовые действия над этими сущностями: просмотр всего списка, поиск по id, добавление, обновление и удаление записей. Для этого создадим типовой Spring Boot проект. В pom-файле нужно прописать следующий parent:

Зависимости также стандартные:

spring-boot-starter-web нужен для функциональности rest-контроллера, spring-boot-starter-data-jpa — для работы с БД. В качестве СУБД мы выберем postgres, поэтому нам понадобится драйвер для работы с этой СУБД.

Этот pom-файл и остальные исходники вы сможете посмотреть на github (ссылка в конце статьи).

Main-класс также абсолютно стандартный:

@SpringBootApplication (scanBasePackages = «ru.devmark» )
public class Application <

public static void main(String[] args) <
SpringApplication.run(Application. class , args);
>
>

Параметр scanBasePackages указывает, какой пакет вместе со вложенными пакетами сканировать для создания spring-бинов.

В Hibernate класс, который мапится на таблицу в БД, называется сущностным классом (entity). Для таблицы country такой класс будет выглядеть следующим образом:

@Entity
@Table (name = «country» )
public class Country <

@Id
@GeneratedValue (strategy = GenerationType. IDENTITY )
private int id;

private String name;

@OneToMany (mappedBy = «country» , cascade = CascadeType. ALL , orphanRemoval = true )
private List cities;

// далее идут геттеры и сеттеры
>

Аннотация @Entity говорит Hibernate о том, что этот класс является сущностью. Аннотация @Table позволяет указать название таблицы в БД, которая связана с этой сущностью. Если имя класса и имя таблицы совпадает, то эту аннотацию можно не указывать. @Id указывает на то поле, которое является уникальным идентификатором сущности. @GeneratedValue позволяет задать несколько различных способов генерации этого идентификатора. В нашем случае мы используем GenerationType.IDENTITY, т.к. на уровне БД используем последовательности (sequence), которые заполняют это значение при вставке новой записи автоматически.

Аннотация @OneToMany задаёт отношение «один-ко-многим» между страной и городом, т.е. в одной стране может быть несколько городов. Поэтому такая аннотация всегда вешается на коллекцию (список). Параметр mappedBy указывает поле в сущности City, которое содержит ссылку на свой родительский объект (страну). Параметр cascade указывает, что действия по модификации родительского объекта также затрагивают и все дочерние. Параметр orphanRemoval указывает, что при удалении родительского объекта нужно также удалить все «осиротевшие» дочерние объекты.

Сущность City содержит обратную аннотацию:

@Entity
@Table (name = «city» )
public class City <

@Id
@GeneratedValue (strategy = GenerationType. IDENTITY )
private Integer id;

private String name;

@ManyToOne (cascade = CascadeType. ALL )
@JoinColumn (name = «country_id» )
private Country country;

@JsonIgnore
public Country getCountry() <
return country;
>

// остальные геттеры и сеттеры

@ManyToOne является обратной по отношению к @OneToMany и располагается в дочерней сущности. @JoinColumn указывает имя внешнего ключа в БД, по которому происходит связь с родительской сущностью.

Также обратите внимание, что на get-методе для поля country стоит аннотация @JsonIgnore. Если её не указать, то при просмотре вложенных в страну городов в формате json будет сгенерён очень большой json из-за того, что объекты указывают друг на друга. Поскольку мы хотим видеть города вложенными в страну, то просто не будем выводить информацию о стране в дочерних сущностях.

Параметры подключения к БД указываем в файле настроек, который можно положить либо в папку resources нашего maven-проекта, либо указать путь до него в командной строке через параметр —spring.config.location. Я предлагаю хранить настройки в yaml формате в файле application.yml:

Yaml — это формат для хранения иерархии настроек. Если вам привычнее обычные текстовые файлы, добавьте вместо него равноценный файл application.properties:

Здесь мы указываем драйвер для нашей СУБД, урл самой БД, логин и пароль к ней, а также диалект Hibernate, который учитывает специфику postgres.

Чтение данных

Создадим сервис для работы с БД:

@Repository
@Transactional
public class CountryDao <

@PersistenceContext
private EntityManager entityManager;

public List getAll() <
return entityManager.createQuery( «from Country c order by c.id desc» , Country. class ).getResultList();
>

public Country getById( int id) <
return entityManager.find(Country. class , id);
>
>

@Repository говорит о том, что это слой взаимодействия с БД. @Transactional делает каждый публичный метод транзакционным. @PersistenceContext подставляет имеющийся в контексте выполнения EntityManager — сервис, который позволяет все основные манипуляции с сущностями.

Далее объявляем два метода: просмотр всех стран и поиск страны по её id. В первом методе мы создаём запрос, очень похожий на sql, но на самом деле это hql — Hibernate Query Language. Hql, в отличие от sql, оперирует сущностями в стиле ООП. Метод поиска по id использует стандартный метод find(), который принимает тип сущности и значение id. Как видите, чтение записей из БД происходит буквально в одну строку!

Читайте также:  Hp принтер заела каретка

Создадим rest-контроллер, который на вход будет получать запросы в формате json:

@RestController
@RequestMapping (value = «/api» , produces = MediaType. APPLICATION_JSON_VALUE )
public class GeoController <

@Autowired
private CountryDao countryDao;

@GetMapping ( «/countries» )
public List getAllCountries() <
return countryDao.getAll();
>

@GetMapping ( «/countries/» )
public Country getCountryById( @PathVariable ( «countryId» ) int id) <
return countryDao.getById(id);
>
>

@RequestMapping позволяет указать базовый урл, на который следует слать rest-запросы. Параметр produces в данном случае указывает, что ответы на запросы также будут в формате json. При помощи @Autowired подгружаем наш репозиторий для работы с БД.

Здесь важно заметить, что в реальных проектах между контроллером и репозиторием должен быть ещё сервисный слой с аннотацией @Service, который инкапсулирует в себе всю бизнес-логику. Но, поскольку в нашем случае это будет лишь проксирование запросов из контроллера к репозиторию, то для краткости я пропущу его.

@GetMapping помечает те методы, которые будут обрабатывать GET-запросы, а @PathVariable позволяет использовать часть урла в качестве параметра запроса (в данном случае это числовой id страны).

Добавление новых записей

Давайте теперь добавим в наш репозиторий метод для добавления новой записи в БД:

Поскольку на вход вместе со страной будут приходить вложенные сущности городов, причём только с указанием их имён, мы должны сначала привязать каждый новый город к текущей стране, а затем вызвать метод persist(), передав ему родительскую сущность. Этот метод вызывается только для создания новых записей и благодаря правильно размеченным сущностям вместе с родительской в БД будут добавлены и дочерние!

В контроллер также добавим соответствующий метод:

@PostMapping указывает на обработчик POST-запросов, а @ResponseStatus задаёт http-код ответа на данный запрос. Согласно архитектуре rest это не 200, а 201 — Created. @RequestBody указывает, какой класс будет использоваться для маппинга тела запроса. В нашем случае это класс Country.

Теперь мы готовы к тому, чтобы добавить страну вместе с городами! Запустим приложением и выполним следующий post-запрос по адресу http://127.0.0.1:8080/api/countries/. Не забудьте также указать заголовок Content-Type: application/json.

Если всё сделано правильно, в двух наших таблицах появится по одной записи. Обратите внимание, что для обоих сущностей мы указываем только имена. В ответ вы получите ту же самую сущность, только с указанием id, которое было присвоено записи при добавлении в БД. Это очень удобная возможность, которая работает в Hibernate «из коробки».

Для просмотра списка всех стран с городами достаточно отправить get-запрос на адрес http://127.0.0.1:8080/api/countries/.

Обновление записей

Теперь давайте разберёмся с обновлением. Будем считать, что при обновлении мы можем изменить название страны, а связанные с ней города каждый раз будем удалять и добавлять заново. Метод в репозитории будет выглядеть так:

Сначала пробуем подгрузить по указанному id уже существующую в БД сущность вместе с городами. Если нашли такую, то устанавливаем новое имя, затем проходимся по всем существующим городам и удаляем их при помощи метода entityManager.remove(). Заметьте, что для удаления записи достаточно передать связанный с ней объект. Затем очищаем список городов и добавляем новые, которые пришли к нам в запросе. Для каждого города вызываем метод persist(). В конце вызываем метод merge() для родительской сущности. Этот метод нужно использовать только для обновления уже существующей записи.

Добавим соответствующий метод в контроллер:

Согласно rest-архитектуре, для обновления данных используется PUT-запрос, о чём говорит аннотация @PutMapping.

Тело запроса на обновление по формату точно совпадает с запросом на добавление новой записи, но слать его нужно на урл, который будет содержать id обновляемой страны. Например, http://127.0.0.1:8080/api/countries/27.

Удаление записей

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

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

На уровне контроллера метод выглядит так:

@DeleteMapping указывает на обработчик DELETE-запроса. @ResponseStatus в этом методе говорит, что в ответ на запрос будет приходить статус 204 — «No content» и пустое тело запроса. Урл запроса также содержит id удаляемой записи, как и в случае с обновлением.

Выводы

Благодаря Hibernate мы реализовали основные виды операций над сущностями буквально в одну строку. А благодаря Spring Boot сделали это согласно rest-архитектуре. Аннотации @OneToMany и @ManyToOne задают связь между сущностями, что позволяет легко манипулировать всеми связанными сущностями в рамках вызова одного метода.

При этом Hibernate скрывает от вас детали sql-запроса, что делает ваш код лёгким для понимания и не зависимым от реализации конкретной СУБД. Вы легко можете перейти с postgres на mysql или oracle, поменяв буквально пару строк кода в файле настроек.

Источник

Implementing Hibernate with Spring Boot and PostgreSQL

Introduction

As the use of software becomes more common and more and more systems are built to handle various tasks, data plays a more important role in the current and future technology scene. Information is increasingly becoming more valuable as technology advances and opens up more opportunities for its use.

Читайте также:  Краска для принтера сделай сам

It is for this reason, and many more, that safe storage and manipulation of data have become an important aspect of any system or application that is built.

What is Object-Relational Mapping?

In many systems, real-life objects are modeled as objects in systems to ease the representation and manipulation of their attributes. For instance, a phone can be modeled as an object with attributes such as its name, operating system, manufacturer, and much more as its attributes and this can be easily manipulated and stored in a database.

Object-Relational Mapping (ORM) is a technique of mapping such objects and their attributes in the database through Object-Relational Mappers. This technique also helps us convert data between incompatible systems using object-oriented programming applications.

An ORM is a library that helps us interact with multiple databases or systems easily using our language of choice. Our code is now mapped to the databases’ specific query language.

Normally, we need to use a database-specific language to interact with a database. For example, to interact with a MySQL database, we need to use the Structured Query Language (SQL), but these languages may differ from platform to platform.

For example, while they are still similar, the syntax on a Postgres database is different from the query language used on a Microsoft SQL database. An ORM helps bridge that difference and plug in our software into different database systems with ease.

Other benefits of using an ORM includes speeding up of the development process since the developers do not have to write the database access code and repeat it every time they want to access a database. Once a model is designed and manipulation code is written, it does not need to be done again, hence making the code easy to update, maintain, and reuse.

However, there are some drawbacks associated with ORMs and they include:

  • ORMs have a tendency to be slow in some situations performance-wise
  • For complex queries like joins, ORMs sometimes cannot substitute raw SQL queries
  • Due to the abstractions introduced by an ORM, the developer may lose understanding of SQL and how database management is achieved behind the scenes

Hibernate

Hibernate is a framework that enables developers to easily persist application data in relational databases using JDBC. It is an implementation of the Java Persistence API (JPA), which means that it can be utilized in any system that supports JPA, such as the standard edition (Java SE) and the enterprise edition (Java EE).

Hibernate is a lightweight and open-source tool that simplifies the creation, manipulation, and access of data from a database in Java-based applications. It works by mapping an object created from a Java class and its attributes, to data stored in the database.

Some advantages of using Hibernate include:

  • It is open-source and lightweight, which means that it’s free to use and has a community of contributors constantly improving it
  • Hibernate utilizes a cache internally that enhances its performance
  • It is database-independent, which means it can be used to access and manipulate data in various different databases
  • It provides the functionality to simplify joins when fetching data from multiple tables
  • By creating tables automatically, the developer can focus on doing other logic
  • It’s a stable framework that has been around for 18 years

Alternatives

Hibernate is not the only ORM framework that we can use in our Java applications, others include:

  • JOOQ (Java Object Oriented Querying) is a light database-mapping software library
  • JDBI provides access to relational data in Java in a convenient way
  • MyBatis is an SQL mapper framework to integrate relational databases
  • Ebean which can be used for both Java and Kotlin-based applications
  • ORMLite which is a lightweight framework to persist Java objects to SQL databases

These are but a few of the alternatives for Hibernate, there are definitely even more libraries and frameworks out there that suit many different scenarios and databases.

Implementing Hibernate with Spring Boot

Project Setup

For this demo project, we are going to use a PostgreSQL database and installation instructions can be found here for Mac OS, Linux, and Windows platforms.

Once set up, we can create our demo database, phonesdemo . PgAdmin provides a user interface to interact with a PostgreSQL database, but a terminal can also be used.

Let us now see Hibernate in action by using it in a sample Spring Boot API, which we will bootstrap using the Spring Initializr tool:

We will be using Java 8 and Maven for our dependency management with a few dependencies:

  • Spring Web Starter to help us build a web-based application
  • Spring Data JPA to provide the data access API that Hibernate will utilize
  • H2 Database to bring in Hibernate functionality into our project
  • PostgreSQL to enable us to connect to a PostgreSQL database
Читайте также:  Кто больше всех печатает на принтере

Once we click on «Generate», we will receive a zip file containing the project and we can start implementing the web application that requires a database. Once we unpack this zip file into our working folder, we can test that it is ready to be worked on by running the command:

Implementation

Let us modify our application.properties to include our database details:

Hibernate provides an H2 Console that we can use to check the status of the database and even perform data entry via a user interface. We enable it by adding the following line to our application.properties :

Then we fire up our application and navigate to http://localhost:8080/h2-console to test if everything is working. We get a page where we can test if the connection to our database works:

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

For the saved settings dropdown, we are going to choose Generic PostgreSQL and we are also going to update the JDBC URL to match the name of our test database. Then we fill in our database username and password and click on «Test Connection» just to make sure that our Spring Application can connect to our database. If everything is well set up, we get a success message.

If we click on «Connect», we get this page:

Here we can navigate our database and even perform SQL queries.

The API we will be building will be used to store and manipulate phones and their attributes such as name and operating system. With our database in place and connected, let us create an entity class ( Phone.java ) that will map our object’s attributes to the database and enable us to perform CRUD operations in the database:

The @Entity annotation tells Hibernate that this class represents an entity that should be persisted.

The @Table annotation is used for naming the table. If this annotation is omitted, the table will simply use the name of the class/entity.

Similarly the @Column annotations can also be omitted, but the database columns will use the field names as they are, and sometimes this isn’t the preferred behavior since your column names might be snake case and field names are camel case, for example.

When we save this file and restart our server and check our database, there will be a new table called phones and the columns of id , phone_name , and os will be present.

There will be no data but this is Hibernate at work, if the table specified in the entity class does not exist, Hibernate will create it for us.

Let us now implement the controller to help us perform operations on our database through an API:

In our controller class, we annotate our class by @RestController to indicate that this is the request handler class that will be handling the REST functionality for our API. We then define methods to handle each of the four RESTful operations: GET , POST , PUT , and DELETE . These methods will provide an interface for us to interact with our API and manage data.

Our API, in turn, will utilize Hibernate to reflect our operations on the said data to our database.

Let us start by creating a single phone through our API:

We get the response from our API, but let us check the database using the H2 Console to confirm:

As you can see, from the screenshot above, to fetch the data in our database, we use the SQL command SELECT * FROM phones . In order to achieve the same in our code through the ORM, it is as simple as using the line:

This is more friendly and familiar to us since it is achieved in the same language that we are using while implementing the rest of our project.

Conclusion

We have successfully created a phone object and saved its attributes to our PostgreSQL database by using Hibernate in our Spring Boot API. We can add more phones, delete phones, and update phone data by interacting with the API and Hibernate will reflect the changes in our database.

Hibernate has made it easier for us to interact with a database from our Spring Boot application and manage our data. The development time has also been significantly reduced since we do not have to write the SQL commands to manage the data ourselves.

The source code for this project is available here on GitHub.

Источник

Поделиться с друзьями
КомпСовет
Adblock
detector