Core Java. Лекция 8

JDBC API

Иван Пономарёв, КУРС/МФТИ

RDBMS — реляционные базы данных

  • Существуют с 1970-х годов.

  • Наиболее распространённый способ хранения информации в самых разнообразных системах.

  • Большое разнообразие зрелых, стабильных и богатых возможностями продуктов: IBM DB2, Oracle, MS SQL Server, Postgres, etc. etc.

  • Язык SQL стандартизован и слабо различается в разных системах.

  • В современных реалиях больших данных теснятся различными NoSQL системами, но пока ещё лидируют по распространённости

JDBC

  • "Java Database Connectivity"

  • Стандарт взаимодействия с реляционными СУБД

  • Назван по аналогии с ODBC (Open Database Connectivity) для языка C, разработанного Microsoft

  • Первая версия в 1996 году, текущая — 4.3 (2017-09-21)

  • Активно используется десятилетиями

Структура приложения

Failed to generate image: Could not load PlantUML. Either require 'asciidoctor-diagram-plantuml' or specify the location of the PlantUML JAR(s) using the 'DIAGRAM_PLANTUML_CLASSPATH' environment variable. Alternatively a PlantUML binary can be provided (plantuml-native in $PATH).
@startuml
skinparam dpi 180
rectangle Application {
rectangle "Code" as app
rectangle JDBC {
rectangle "Driver" as driver
}
}
database "database" as db
app->JDBC
driver-->db

@enduml

JDBC Drivers

  • Разрабатываются производителями БД или сообществами

  • У хороших БД — стабильные, высокого качества драйверы

Connection Strings aka Database URLs

В специфичной для каждого драйвера манере указывают сервер, порт, базу данных и иные параметры подключения:

jdbc:sqlserver://172.16.1.114:52836;databaseName=celesta

jdbc:postgresql://127.0.0.1:5432/celesta

jdbc:oracle:thin:192.168.110.128:1521:XE

jdbc:derby://localhost:1527/corejava;create=true

Создание Connection, Statement, получение и итерация по ResultSet

Failed to generate image: Could not load PlantUML. Either require 'asciidoctor-diagram-plantuml' or specify the location of the PlantUML JAR(s) using the 'DIAGRAM_PLANTUML_CLASSPATH' environment variable. Alternatively a PlantUML binary can be provided (plantuml-native in $PATH).
skinparam dpi 150
hide footbox
client-> DataSource : getConnection()
DataSource -> connection **: new

connection-->client: connection

client->connection: createStatement()
connection->statement **:new
statement-->client: statement

client->statement: executeQuery()
statement->resultSet **:new
resultSet-->client: resultSet

client->resultSet: next()
client->resultSet: next()
client->resultSet: next()

Получение данных и итерация

try (Connection conn = dataSource.getConnection();
     Statement stmt = conn.createStatement()){

  ResultSet result = stmt.executeQuery("select name from speaker");

  while (result.next()) {

      System.out.println(result.getString("name"));

  }
}

Как получить JDBC Connection

То, что написано во многих тьюториалах и учебниках, и то, что вы никогда не будете делать в реальной жизни:

String connString = "jdbc:postgresql://127.0.0.1:5432/celesta"
Connection conn = DriverManager.getConnection(connString);

JDBC Connection: особенности

  • Корневой объект, «входная точка» ко всем возможностям взаимодействия с СУБД.

  • Медленно создаётся (связь по сети с СУБД, авторизация и т. д.).

  • Захватывает ресурсы: необходимо явно закрывать после использования.

  • Имеет тенденцию «портиться» («отваливаться»).

  • Не потокобезопасный. Нужен thread confinement.

  • Выход: connection pooling.

Connection Pool

Failed to generate image: Could not load PlantUML. Either require 'asciidoctor-diagram-plantuml' or specify the location of the PlantUML JAR(s) using the 'DIAGRAM_PLANTUML_CLASSPATH' environment variable. Alternatively a PlantUML binary can be provided (plantuml-native in $PATH).
skinparam dpi 150
hide footbox
client->pool : getConnection()
pool->pool: poll()
alt Pool is not empty
  pool-->client: pooledConnection
else Pool is empty
  pool->DriverManager: getConnection()
  DriverManager-->pool: connection
  pool-->pooledConnection **:new(connection)
  pooledConnection-->client: pooledConnection
end
client->pooledConnection: close()
pooledConnection->pool: add(this)

Connection Pool

  • Грамотно реализовать самостоятельно — сложно

  • Предоставляет ваш фреймворк / JDBC driver

  • Есть javax.sql.DataSource, являющийся стандартным интерфейсом для Connection Pool.

DataSource connectionPool = ...
try (Connection conn = connectionPool.getConnection()) {
    ...
}

Реализации Connection Pools (DataSource)

  • Apache Commons DBCP

  • Hikari CP

  • Другие фреймворки или JDBC-драйверы (например, H2)

Методы Connection

  • управление транзакциями

    • setAutoCommit

    • commit

    • rollback

  • создание statements:

    • createStatement() — создаёт универсальный объект для передачи SQL-команд

    • prepareStatement(String sql) — создаёт параметризованный шаблон SQL команды

    • prepareCall(String sql) — cоздаёт шаблон для вызова хранимой процедуры

Разновидности Statement

Failed to generate image: Could not load PlantUML. Either require 'asciidoctor-diagram-plantuml' or specify the location of the PlantUML JAR(s) using the 'DIAGRAM_PLANTUML_CLASSPATH' environment variable. Alternatively a PlantUML binary can be provided (plantuml-native in $PATH).
@startuml

skinparam dpi 180

interface AutoCloseable
interface Statement {
boolean execute(String sql)
int executeUpdate(String sql)
ResultSet executeQuery(String sql)

ResultSet getResultSet()
}

interface PreparedStatement {
void setXXX(int parameterIndex, XXX x)
}

interface CallableStatement {
void registerOutParameter(int parameterIndex, int sqlType)
}

AutoCloseable <|- Statement
Statement <|- PreparedStatement
PreparedStatement <|- CallableStatement
@enduml

Statement и PreparedStatement

int confId = ...
try (PreparedStatement stmt =
  conn.prepareStatement(
    "select name from talk where conferenceid = ?")) {
  //устанавливаем значение параметра
  //индексация с единицы!!
  stmt.setInt(1, confId);

  ResultSet result = stmt.executeQuery();
  while (result.next()) {
      System.out.println(result.getString("name"));
  }

}

SQL Injection

  • Запомним раз и навсегда: нельзя конструировать запросы в базу на основании данных от пользователя

НЕПРАВИЛЬНО

ПРАВИЛЬНО

stmt.executeQuery(
 "SELECT * FROM users " +
 "WHERE name = '" +
 userName + "'");
PreparedStatement stmt =
 conn.prepareStatement(
  "SELECT * FROM users WHERE name = ?");
stmt.setString(1, userName);
stmt.executeQuery();

SQL Injection

sql = "SELECT * FROM users WHERE name = '" + userName + "'"

userName = "' OR '1'='1"

userName = "a';DROP TABLE users;"

//... и много другой гадости

SQL Injection

sqlinjection

Демо-пример

  • Пишем базу данных выступлений на Java-конференциях

  • Основные сущности: на конференциях выступают докладчики с докладами.

  • Бывает, что один доклад делают несколько докладчиков одновременно.

ER model

Failed to generate image: Could not load PlantUML. Either require 'asciidoctor-diagram-plantuml' or specify the location of the PlantUML JAR(s) using the 'DIAGRAM_PLANTUML_CLASSPATH' environment variable. Alternatively a PlantUML binary can be provided (plantuml-native in $PATH).
@startuml
skinparam dpi 180

hide circle

class speaker {
  id: INT
  name: VARCHAR
}

class conference {
  id: INT
  name: VARCHAR
}

class talk {
  id: INT
  conferenceid: INT
  name: VARCHAR
}

class talkspeakers {
 talkid: INT
 speakerid: INT
}

talk --> conference: conferenceid

talkspeakers --> speaker: speakerid

talkspeakers --> talk: talkid

@enduml

Наш учебный пример про доклады и спикеров

Failed to generate image: Could not load PlantUML. Either require 'asciidoctor-diagram-plantuml' or specify the location of the PlantUML JAR(s) using the 'DIAGRAM_PLANTUML_CLASSPATH' environment variable. Alternatively a PlantUML binary can be provided (plantuml-native in $PATH).
@startuml
skinparam dpi 180
object ConnectionPool
object JdbcTemplate
object ConferenceDao
object SpeakerDao
object TalkDao
object Controller

JdbcTemplate --> ConnectionPool
SpeakerDao --> JdbcTemplate
TalkDao --> JdbcTemplate
ConferenceDao --> JdbcTemplate
Controller --> SpeakerDao
Controller --> TalkDao
Controller --> ConferenceDao
@enduml

Реальная «слоистая» архитектура серверного приложения

Failed to generate image: Could not load PlantUML. Either require 'asciidoctor-diagram-plantuml' or specify the location of the PlantUML JAR(s) using the 'DIAGRAM_PLANTUML_CLASSPATH' environment variable. Alternatively a PlantUML binary can be provided (plantuml-native in $PATH).
@startuml
skinparam dpi 180
object Controller1
object Controller2

object Service1
object Service2
object Service3

object Dao1
object Dao2
object Dao3

Controller1 --> Service1
Controller1 --> Service2
Controller2 --> Service2
Controller2 --> Service1
Controller2 --> Service3
note on link
  «соединение
  проводами»,
  aka "wiring"
end note

Service1 --> Dao1
Service2 --> Dao1
Service3 --> Dao2
Service3 --> Dao3
Service1 --> Dao2

@enduml