Работа с базами данных

в Java

Зачем хранить данные в БД

  1. данные сохраняются между перезагрузками
  2. меньшее использование RAM
  3. транзакции
  4. гибкая выборка данных

JDBC

Java Database Connectivity

Особенности

  • Платформо-независимое API
  • Работа с БД через SQL

Особенности

  • разработчик может не знать специфики базы данных, с которой работает
  • Код практически не меняется, если компания переходит на другую базу данных (количество изменений зависит исключительно от различий между диалектами SQL)
  • Не нужно устанавливать громоздкую клиентскую программу
  • К любой базе можно подсоединиться через легко описываемый URL

Базовые интерфейсы

                        
java.sql.Connection
java.sql.Statement
java.sql.PreparedStatement
java.sql.CallableStatement
java.sql.ResultSet
                        
                    

Подключение к БД

                        
DriverManager.registerDriver(new org.postgresql.Driver());

Connection conn = DriverManager.getConnection(
   "jdbc:postgresql://localhost:5432/db_name",
   "user", "password");
                        
                    

Выполнение запроса на обновление данных

                        
Statement stmt = conn.createStatement();
stmt.executeUpdate(
    "INSERT INTO student( login, name ) VALUES ( 'some_login', 'my name' ) " );
                        
                    

Получение данных запроса

                        
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM student");

while (rs.next()) {
   System.out.println(
       rs.getRow() + ". " +
       rs.getString("login") + " " +
       rs.getString("name"));
}
                        
                    

Метаданные запроса

                        
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery( "SELECT * FROM student" );
while ( rs.next() ) {
   int numColumns = rs.getMetaData().getColumnCount();
   for ( int i = 1 ; i <= numColumns ; i++ ) {
       System.out.println("COLUMN " + i + " = " + rs.getObject(i));
   }
}
                        
                    

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

                        
PreparedStatement ps =
   conn.prepareStatement("SELECT login " +
       " FROM users WHERE name = ?");
ps.setString(1, "Kent");

ResultSet rs = ps.executeQuery();
while (rs.next()) {
   System.out.println(rs.getString(1));
}
                        
                    

Закрытие ресурсов

                        
try(Connection conn = DriverManager.getConnection(
   "jdbc:...",   "user", "password")) {
   try(Statement stmt = conn.createStatement();
       ResultSet rs = stmt.executeQuery("...")) {
             ...
   }
}
                        
                    

Транзакции

                        
conn.setAutoCommit(false);
try {
   ...
   conn.commit();
} catch (Throwable e) {
   try { conn.rollback(); } catch (Throwable e) {
       logger.warn("Could not rollback transaction", e);
   }
   throw e;
}
                        
                    

Массовые операции

                        
String sql = "insert into employee (login, name) values (?, ?)";
PreparedStatement ps = connection.prepareStatement(sql);

for (Student st: students) {
    ps.setString(1, st.getLogin());
    ps.setString(2, st.getName());
    ps.addBatch();
}
ps.executeBatch();
                        
                    

Проблемы и минусы JDBC

  • Объект в Java != Объект в БД
  • не ООП (наследование, переходы по графу объектов)
  • концентрация на внутреннем представлении БД, а не на бизнес логике

ORM/JPA

  • Object-Relational Mapping
  • Java Persistence API — спецификация API Java EE, предоставляет возможность сохранять в удобном виде Java-объекты в базе данных (JSR 220, 317, 338)

Реализации JPA

  • Eclipse Link
  • Hibernate

Типы данных приложения

  1. объекты
  2. типы/классы
  3. свойства
  4. идентификаторы
  5. связи

Типы данных в БД

  1. Вендор
  2. Соединение
  3. Cхема
  4. Транзакция
  5. Таблица
  6. Столбец
  7. Строка
  8. Ограничения (...)
  9. Индекс
  10. Последовательность
  11. Хранимая процедура

Entity

                        
@Entity
public class User {
    String login;
    String name;
    School school;
   ...
}
                        
                    

Требования к Entity

  1. легковесный объект
  2. обычно, представление таблицы в реляционной БД
  3. удовлетворяет ограничениям
    • должен быть конструктор без аргументов
    • класс не должен быть помечен как final

Идентификатор

                        
@Entity
public class Student {
    @Id
    String login;
    String name;
    School school;
    ...
}
                        
                    

Имена в БД

                        
@Entity
@Table(name = "STUDENT")
public class Student {
    @Id
    @Column(name=”STUDENT_LOGIN”, length = 32)
    String login;

    @Column(name=”FULL_NAME”)
    String name;

    School school;
                        
                    

Сохранение даты/времени

  • @Temporal(TemporalType.DATE)
  • @Temporal(TemporalType.TIME)
  • @Temporal(TemporalType.TIMESTAMP)
                        
@Entity
public class Student {
    ...
    @Temporal(TemporalType.TIMESTAMP)
    Date creationDate;
    ...
}
                        
                        

Связи между объектами

  • one-to-one
  • one-to-many
  • many-to-many
  • many-to-one

Маппинг Many-To-One

                        
@Entity
public class Student {
    ...
    @ManyToOne(name=”SCHOOL_ID”, fetch = FetchType.LAZY)
    School school;

    @ManyToOne(name=”Man_ID”, fetch = FetchType.EAGER)
    Man father;
    ...
}
                        
                    

Persistence Context

  • множество управляемых сущностей
  • в конкретном хранилище

интерфейс EntityManager

  • определяет методы для общения с PersistenceContext
  • создание и удаление объектов
  • поиск объекта по первичному ключу
  • позволяет выполнять запросы по сущностям

Доступ к EntityManager

                        
@PersistenceUnit
EntityManagerFactory emf;
EntityManager em = emf.createEntityManager();

или

@PersistenceContext
EntityManager em;
                        
                    

persistence.xml

                            
<?xml version="1.0" encoding="UTF-8" ?>
<persistence>
    <persistence-unit name="my-pu">
        <description>My Persistence Unit</description>
        <provider>
            org.hibernate.jpa.HibernatePersistenceProvider
        </provider>
        <class>sample.Student</class>
        <class>sample.School</class>
        <properties>
            <property name="javax.persistence.jdbc.url"
                value="jdbc:postgresql://localhost:5432/somedb"/>
            <property name="javax.persistence.jdbc.user" 
                value="name"/>
            <property name="javax.persistence.jdbc.password" 
                value="pass"/>
        </properties>
    </persistence-unit>
</persistence>
                        
                    

Сохранение

                        
@PersistenceContext
private EntityManager entityManager;

@Transactional
public void insert() {
    School school = new School(1, "Школа №1");
    Student student = new Student("pupkin", "Вася пупкин", school);

    entityManager.persist(school);
    entityManager.persist(student);
}
                        
                    

Поиск

                            
@PersistenceContext
private EntityManager entityManager;

@Transactional
public Student find(String login) {
    return entityManager.find(Student.class, "pupkin");
}
                            
                        

Удаление

                        
@PersistenceContext
private EntityManager entityManager;

@Transactional
public void delete(String login) {
    Student student = find(login);
    entityManager.remove(student);
}
                        
                    

Обновление

                        
@PersistenceContext
private EntityManager entityManager;

@Transactional
public void update(String login) {
    Student student = find(login);
    student.setName("Петров");
    entityManager.merge(student);
}
                        
                    

JPQL

                        
@Transactional
public List<Student> listStudents(String name) {
    List<Student> students = entityManager
        .createQuery("from " + Student.class.getName()
            + " where name like :name", Student.class)
        .setParameter("name", "%" + name + "%")
        .getResultList();

    return students;
}
                        
                    

NativeQuery

                            
@Transactional
public List<String> listLogins(String name) {
    List<String> students = entityManager
        .createNativeQuery("SELECT login FROM students "
            + " WHERE name like :name")
        .setParameter("name", "%" + name + "%")
        .getResultList();

    return students;
}
                        
                    

JPA Criteria

                            
@Transactional
public List<Student> listStudentsByName(String name) {
    CriteriaBuilder builder = entityManager.getCriteriaBuilder();
    CriteriaQuery<Student> query = builder.createQuery(Student.class);
    Root<Student> from = query.from(Student.class);
    query.where(builder.equal(from.get("name"), name));
    return entityManager.createQuery(query).getResultList();
}
                        
                    

Именованные запросы

                        
@Entity
@NamedQuery(name="Country.findAll",
            query="SELECT c FROM Country c")
public class Country {
    ...
}

TypedQuery<Country> query =
    em.createNamedQuery("Country.findAll", Country.class);
List<Country> results = query.getResultList();
                        
                    

JPA Criteria

  • type safe
  • динамичные запросы
  • оверхед

Кэши

  • ehcache
  • oscache
  • infinispan
  • ...

@Cacheable

                                
@Entity
@Cacheable
class Student {
    ...
}
                            
                        

Уровни кэшей

  • Кеш первого уровня (First-level cache)
  • Кеш второго уровня (Second-level cache)
  • Кеш запросов (Query cache)
PS Нужно включать в конфигурации

Минусы ORM

  • SQL не всегда оптимален
  • больше нагрузка по сравнению с JDBC

Пул коннектов

  • Hikari
  • c3p0
  • apache dbcp
  • ...

Задачи

  • Подготовка новых соединений
  • БЫСТРОЕ получение соединений из пула
  • Контроль количества соединений в пуле
  • Контроль ошибок при возврате в пул

Spring Data JPA

CrudRepository<T, ID>

                        
T findOne(ID primaryKey);
Iterable<T> findAll();
Long count();
boolean exists(ID primaryKey);
T save(T entity);
void delete(T entity);
...
                        
                    
                        
userRepository.findAll();
userRepository.findOne(id);
userRepository.count();
userRepository.save(user);
userRepository.exists(userId);
                        
                    

PagingAndSortingRepository<T, ID>

                        
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
...
                        
                    

Примеры

                        
Long countByFirstName(String firstName);

List<User> findByUserTypeOrderByFirstNameDesc(UserTypeuserType);

User findByFirstNameAndLastName(String firstName, String lastName);

User findByFirstNameAndLastNameAllIgnoreCase(
                                String firstName, String lastName);

List<Task> findByAssignedToUserFirstNameAndAssignedToUserLastName(
                                String firstName, String lastName);
                        
                    

Конструкции

                        
find...By..., count...By..., delete...By...
...Distinct...
...And..., ...Or...
...Equals..., (...Is...), ...Not..., ...In..., ...NotIn...,
...Like..., ...NotLike..., ...StartingWith...,
...EndingWith..., ...Containing...
...Between..., ...LessThan..., ...LessThenEqual...,
...GreaterThan..., ...GreaterThanEqual...
...After..., ...Before...
...IsNull..., ...isNotNull, (...notNull...), True, False
...IgnoreCase..., ...AllIgnoreCase...
...OrderBy...Asc, ...OrderBy...Desc
                        
                    

Ограничение количества

                        
...first10...
Pageable

List<Task> findFirst3ByCreatedByUser(User user, Sort sort);
                        
                    

Собственные запросы

  • @Query, @Param
  • JPQL (Java Persistence Query Language)
  • Проверка на старте приложения
                        
@Query("select t from Task t where t.number IN (:numbers)")
List<Task> findByNumberIn(
             @Param("numbers") Collection<String> numbers);
                        
                    

Собственные интерфейсы

  1. Создать интерфейс
  2. Создать реализацию
  3. Добавить аннотацию @Repository
                        
public interface CustomTaskRepository {
    String findTaskNumberByTitleFragment(String fragment);
}

@Repository
public class TaskRepositoryImpl implements CustomTaskRepository {
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public String findTaskNumberByTitleFragment(String fragment) {
        Query query = entityManager.createNativeQuery(...);
        query.setParameter("fragment", "%" + fragment + "%");
        return (String) query.getSingleResult();
    }
}
                        
                    

Минусы

  • Очень длинные имена методов
  • SQL может быть более эффективным
  • Нет поддержки агрегаций

Конец