Skip to content
Gallery
Theory for java developer
Share
Explore
Java

icon picker
Persistent

JDBC API

Java Database Connectivity API (с JDK 1.1) - Это стандарт взаимодействия (спецификация) выступает в качестве посредника Java-приложения и различными СУБД. Это упрощает как процесс создания приложения, так и переход на базу данных другого типа.
Java Application
v
JDBC API
v
JDBC Driver Manager
v v v
JDBC Driver JDBC Driver JDBC Driver
v v v
Oracle Postgres Casandra
Чтобы с базой данных можно было работать из Java приложения поставщик базы данных пишет драйвер, который является своего образа адаптером.
Два способа подключения к БД (DriverManager, DataSource) DriverManager.getConnection("jdbc:h2:mem:test", user, passwd);
ResultSet предоставляет методы для получения и манипуляции результатами выполненных запросов. next() для перемещения по строкам.
Раньше разработчики каждый раз сталкивались с Boilerplate code для тривиальных операций по сохранению Java объектов в БД и наоборот, созданию Java объектов по данным из БД. И тогда для решения этих проблем на свет появилось ORM.
— (англ. Object-relational mapping). В двух словах ORM — это отображение объектов какого-либо объектно-ориентированного языка в структуры реляционных баз данных. Именно объектов, со всеми полями, значениями, отношениями м/у друг другом.

JPA API

JPA (Java Persistence API) это спецификация Java EE и Java SE, описывающая систему управления сохранением или извлечением java объектов из таблиц в реляционных DB. Сама Java не содержит реализации JPA, однако есть существует много реализаций данной спецификации например Hibernate.
java persistence api - модуль упрощает процесс создания приложений которые работают с базой данных, маппит сущности (объекты) в соответствующие им таблицы.
image.png
EntityManagerFactory emf = Persistence.createEntityManagerFactory("Something");


Query Languages (JPQL, HQL...)

Criteria

Использует CriteriaBuilder и его методы как язык взаимодействия с DB, где where() = WHERE в SQL и т.д.
public class Employee {
private Integer id;
private String name;
private Long salary;
}

Session session = HibernateUtil.getHibernateSession();
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Employee> cr = cb.createQuery(Employee.class);
Root<Employee> root = cr.from(Employee.class);
cr.select(root);

Query<Employee> query = session.createQuery(cr);
List<Employee> results = query.getResultList();
session.close();
return results;
cr.select(root).where(cb.gt(root.get("salary"), 50000));

JPQL (Java Persistence Query Language)

When we use JPQL for a query definition, then Spring Data can handle sorting without any problem — all we have to do is add a method parameter of type Sort:
@Query(value = "SELECT u FROM Author u")
List<Author> findAllAuthor(Sort sort);
We can call this method and pass a Sort parameter, which will order the result by the name property of the User object:
authorRepository.findAllAuthor(Sort.by("name"));

Spring Data JPA

Дополнительный механизм для взаимодействия с сущностями базы данных, организации их в репозитории, извлечение данных, изменение, в каких то случаях для этого будет достаточно объявить интерфейс и метод в нем, без имплементации.
public interface AuthorRepository extends JpaRepository<Author, Long> {
List<Author> findByFirstName(String firstName);
List<Author> findByFirstNameAndLastName(String firstName, String lastName);
}
// Теперь можно выполнять NamedQueryes точно также как и дефолтные методы в репозитории
// тоже самое что и findAllAuthor(), Spring формирует NamedQuery из названия метода
List<Author> a = authorRepository.findByFirstName("Thorben");

Pagination

Позволяет возвращать подмножество каких-то строк из БД. К примеру когда мы ходим подгружать только по нескольку товаров в каталоге на сайте а не сразу все. Минимизирует нагрузку на сеть, отсылая маленькие куски информации по запросу.
@Query(value = "SELECT u FROM User u ORDER BY id")
Page<User> findAllUsersWithPagination(Pageable pageable);

HQL (Hibernate Query Language)

A Named Query defines a query with a predefined, unchangeable query string. These queries are fail-fast since they’re validated during the creation of the session factory. Let’s define a Named Query using the org.hibernate.annotations.NamedQuery annotation:
@NamedQuery(name = "Employee_FindByEmployeeId", query = "from Employee where id = :id")
The main advantage of Criteria Queries over HQL is the nice, clean, object-oriented API. As a result, we can detect errors in Criteria API during the compile time.
In addition, JPQL queries and Criteria queries have the same performance and efficiency.
Criteria queries are more flexible and provide better support for writing dynamic queries as compared to HQL and JPQL.
But HQL and JPQL provide native query support that isn’t possible with the Criteria queries. This is one of the disadvantages of the Criteria query.
We can easily write complex joins using JPQL native queries, whereas it gets difficult to manage while applying the same with Criteria API.

Hibernate

ORM-решением для языка
, является технология , которая не только заботится о связи Java классов с таблицами базы данных (и типов данных Java в типы данных
), но также предоставляет средства для автоматического построения запросов и извлечения данных и может значительно уменьшить время разработки, которое обычно тратится на ручное написание SQL и
кода. Hibernate генерирует SQL вызовы и освобождает разработчика от ручной обработки результирующего набора данных и конвертации объектов, сохраняя приложение портируемым во все SQL базы данных.
@Entity //аннотация, регистрирующая класс как сущность БД
@Table(name = "sessions") //связываем с конкретной таблицей по названию
public class SessionBean implements Serializable{

@Column(name = "username") //название таблицы в БД
private String username; //название поля класса
@Id// поле является ID и будет использоваться для поиска по умолчанию
@Column(name = "series")
private String series;
}

Level 1 Cache- Session

Session cache(Всегда включен) - кэш в рамках одной session (аналог EntityManager в JPA).
L1 Cache используется в save(),update(),saveOrUpdate(),load(),get(),list(),iterate(),scroll() всегда.
Em em1 = session.get(Em.class, 4);
Em em2 = session.get(Em.class, 4);
// assert(em1 != em2)
assert(em1.equals(em2));
После первого запроса в базу объект Em будет закэширован. При повторном запросе в той же сессии, вернет объект с такими же полями.
Session – обёртка вокруг подключения JDBC к БД, используется один раз.
Session кэширует объект-сущность в Map: key - id сущности, value - ResultSet (поля объекта).
Перед тем, как отправить объект в БД:
Поиск в кэше по id
Если есть - вернет сущность, без select в DB
Если нет - сделает select и сохранит ResultSet в кэш

Level 2 Cache- SessionFactory

SessionFactory cache(Включается отдельно) - общий кэш всех сессий SessionFactory (для нескольких сессий).
var session = factory.openSession();
Em director1 = session.get(Em.class, 4);
session.close();
var session = factory.openSession();
Em director2 = session.get(Em.class, 4);
session.close();
// только один запрос в базу
То есть одна сессия извлекла сущность, а другая может получить к этой сущности потом доступ. Очевидно, что с такой прослойкой есть проблема — данные могут устареть: в базе данные одни, а в кэше второго уровня — другие.
L2 Cash можно включить в настройках hibernate.cfg.xml, требуется провайдер кеша.

L2 Query Cache (Кэш запросов)

В Hibernate предусмотрен кэш для запросов, и он интегрирован с L2 Cache.
Это требует двух дополнительных физических мест для хранения кэшированных запросов и временных меток для обновления таблицы БД. Этот вид кэширования эффективен только для часто используемых запросов с одинаковыми параметрами.
Важно: хранит результат запроса, причём ключом является сам запрос и те параметры, которые были переданы в запросе. Хитро устроен: сохраняет не объекты целиком, а их id -шники.


Аннотации

@Transactional - метод выполняется в рамках транзакции. То есть в начале и в конце теста неявно выполняется begin() и commit() транзакции. Мы эти методы не видим, их вызывает Spring с помощью AOP.
@Cache - L2 Стратегии кэширования. Несколько потоков могут одновременно в разных транзакциях работать с одним и тем же объектом. В Hibernate существует четыре стратегии одновременного доступа к объектам в кэше:
Read-only - Для сущностей, которые никогда не изменяются (будет исключение, если попытаться обновить сущность).
Read-write - Эта стратегия гарантирует строгую согласованность, блокировка которая снимается после коммита транзакции. Все параллельные транзакции, которые пытаются получить доступ к записям в кэше с наложенной мягкой блокировкой, не смогут их прочитать или записать и отправят запрос в БД. Ehcache использует эту стратегию по умолчанию.
Non strict-read-write (не строгое чтение-запись) - Кэш обновляется после совершения транзакции, которая изменила данные в БД и закоммитила их. Строгая согласованность не гарантируется, и существует небольшое временное окно между обновлением данных в БД и обновлением тех же данных в кэше, параллельная транзакция может получить из кэша устаревшие данные.
Transactional - Полноценное разделение транзакций. Каждая сессия и каждая транзакция видят объекты, как если бы только они с ним работали последовательно одна транзакция за другой. Плата за это — блокировки и потеря производительности.

Отношения

Параметры в отношениях
fetch - Позволяет управлять режимами загрузки зависимых объектов. Обычно он принимает одно из двух значений:
FetchType.LAZY - загружает данные по запросу к конретному зависимому обьекту-сущности.
FetchType.EAGER - (По умолчанию в OneToOne и т.д.) будет загружаться гораздо больше данных, чем нужно. Стратегия также подвержена проблемам N + 1, при загрузке какого-нибудь корневого объекта, который связан со всеми остальными объектами и коллекциями, можно случайно загрузить в память и всю базу.
cascade - Параметр описывает что должно происходить с зависимыми объектами, если мы меняем их родительский (главный объект). Чаще всего этот параметр используется вместе с аннотациями @ManyToMany, @OneToOne и т.д.
CascadeType.ALL - Все действия c родительским объектом, нужно повторить и для его зависимых объектов.
CascadeType.PERSIST - Сохраняет родительский и зависимые объекты в базе.
CascadeType.MERGE - Обновляет родительский и зависимые объекты в базе.
CascadeType.REMOVE / CascadeType.DELETE - Удаляет родительский и зависимые объекты в базе.
CascadeType.DETACH - Удаляет родительский и зависимые объекты из сессии.
CascadeType.REFRESH / CascadeType.SAVE_UPDATE - дублируют действия, которые выполняются с родительским к зависимому.
Отношения
Может быть односторонним (unidirectional) или двусторонним (bidirectional), Hibernate будет считать владельцем отношения ту сущность, в которой будет поставлена аннотация отношения.
@OneToOne - один к одному
@OneToMany - один ко многим
@ManyToOne - многие к одному
@ManyToMany - многие ко многим

Проблемы Hibernate

N + 1

Проблема N + 1 возникает, когда выполняются N дополнительных SQL-запросов для получения тех же данных, которые можно получить при выполнении одного SQL-запроса в сумме выполняются существенное время, влияющее на быстродействие.
@Data
@NoArgsConstructor
@Entity
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String text;
// initialize topic instance at the moment
@ManyToOne //(fetch = FetchType.EAGER)
private Topic topic;
public Comment(String text){
this.text=text;
}
}
@Data
@NoArgsConstructor
@Entity
public class Topic {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String title;
public Topic(String title){
this.title=title;
}
}


Для выбора комментариев напишем не простой select, а select с join fetch, который преобразуется в SQL с inner join.
Как мы уже сказали выше, у аннотации LazyCollectionOption.EXTRA есть проблема — она выполняет по отдельному запросу к базе для каждого объекта. Нужно как-то объяснить Hibernate, что мы хотим, чтобы он сразу загрузил все дочерние объекты для наших родительских объектов.

Если вызвать @Lazy вне транзакции получим LazyException.
Решение - нужно использовать join fetch + Lazy везде

Cartesian product problem

Если select содержит два и более join, это приводит к выборке огромного количества лишних данных, которые передаются по сети, занимают оперативную память. Что также отрицательно сказывается на производительности. И таких join-ов тоже надо избегать.
Решение - нужно выполнить вместо одного select с несколькими join (в нашем случае двумя) несколько select-ов (в нашем случае два — по одному на коллекцию), в каждом из который ровно один join.
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.