文件

§存取 SQL 資料庫

注意:JDBC 是會導致執行緒等待的封鎖作業。在控制器中直接執行 JDBC 查詢可能會對 Play 應用程式的效能造成負面影響!請參閱「設定自訂執行緒環境」區段。

§設定 JDBC 連線池

Play 提供一個用於管理 JDBC 連線池的外掛程式。您可以根據需要設定任意數量的資料庫。

若要啟用資料庫外掛程式,請新增建置相依性

Java
libraryDependencies ++= Seq(
  javaJdbc
)
Scala
libraryDependencies ++= Seq(
  jdbc
)

§設定 JDBC 驅動程式相依性

Play 不提供任何資料庫驅動程式。因此,若要在製作環境中部署,您必須將資料庫驅動程式新增為應用程式相依性。

例如,如果您使用 MySQL5,則需要為連接器新增一個 相依性

libraryDependencies ++= Seq(
  "com.mysql" % "mysql-connector-j" % "8.0.33"
)

§資料庫設定

然後您必須在 conf/application.conf 檔案中設定一個連線池。依慣例,預設的 JDBC 資料來源必須稱為 default,對應的設定屬性為 db.default.driverdb.default.url

# Default database configuration
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"

如果某項設定不正確,您會直接在瀏覽器中收到通知

您也可以透過設定 play.db.default 來變更 default 名稱,例如

play.db.default = "primary"

db.primary.driver=org.h2.Driver
db.primary.url="jdbc:h2:mem:play"

§如何設定多個資料來源

若要設定多個資料來源

# Orders database
db.orders.driver=org.h2.Driver
db.orders.url="jdbc:h2:mem:orders"

# Customers database
db.customers.driver=org.h2.Driver
db.customers.url="jdbc:h2:mem:customers"

§H2 資料庫引擎連線屬性

記憶體中資料庫

# Default database configuration using H2 database engine in an in-memory mode
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"

基於檔案的資料庫

# Default database configuration using H2 database engine in a persistent mode
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:/path/to/db-file"

H2 資料庫 URL 的詳細資料可從 H2 資料庫引擎秘笈 中取得。

§SQLite 資料庫引擎連線屬性

# Default database configuration using SQLite database engine
db.default.driver=org.sqlite.JDBC
db.default.url="jdbc:sqlite:/path/to/db-file"

§PostgreSQL 資料庫引擎連線屬性

# Default database configuration using PostgreSQL database engine
db.default.driver=org.postgresql.Driver
db.default.url="jdbc:postgresql://database.example.com/playdb"

§MySQL 資料庫引擎連線屬性

# Default database configuration using MySQL database engine
# Connect to playdb as playdbuser
db.default.driver=com.mysql.jdbc.Driver
db.default.url="jdbc:mysql://127.0.0.1/playdb"
db.default.username=playdbuser
db.default.password="a strong password"

§透過 JNDI 公開資料來源

有些函式庫預期會從 JNDI 擷取 Datasource 參照。您可以在 conf/application.conf 中新增此組態,藉由 JNDI 公開任何 Play 管理的資料來源

db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"
db.default.jndiName=DefaultDS

§如何設定 SQL 記錄陳述

並非所有連線池都提供 (開箱即用) 記錄 SQL 陳述的方法。例如,HikariCP 建議您使用資料庫廠商的記錄功能。來自 HikariCP 文件

記錄陳述文字 / 慢速查詢記錄

如同陳述快取,大多數主要的資料庫廠商都支援透過其自身驅動程式的屬性來進行陳述記錄。這包括 Oracle、MySQL、Derby、MSSQL 等。有些甚至支援慢速查詢記錄。我們將其視為「開發時間」功能。對於少數不支援此功能的資料庫,jdbcdslog-exp 是個不錯的選擇。在開發和生產前階段非常棒。

因此,Play 使用 jdbcdslog-exp 來啟用對支援的池一致的 SQL 記錄陳述支援。SQL 記錄陳述可以使用 logSql 屬性按資料庫設定

# Default database configuration using PostgreSQL database engine
db.default.driver=org.postgresql.Driver
db.default.url="jdbc:postgresql://database.example.com/playdb"
db.default.logSql=true

之後,您可以設定 jdbcdslog-exp 記錄層級,如其手冊中所述。基本上,您需要將根記錄器設定為 INFO,然後決定 jdbcdslog-exp 要記錄什麼 (連線、陳述和結果集)。以下是使用 logback.xml 設定記錄的範例

<?xml version="1.0" encoding="UTF-8" ?>
<!--
   Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
-->

<!DOCTYPE configuration>

<configuration>
  <import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
  <import class="ch.qos.logback.classic.AsyncAppender"/>
  <import class="ch.qos.logback.core.FileAppender"/>
  <import class="ch.qos.logback.core.ConsoleAppender"/>

  <appender name="FILE" class="FileAppender">
    <file>${application.home:-.}/logs/application.log</file>
    <encoder class="PatternLayoutEncoder">
      <pattern>%date [%level] from %logger in %thread - %message%n%xException</pattern>
    </encoder>
  </appender>

  <appender name="STDOUT" class="ConsoleAppender">
    <encoder class="PatternLayoutEncoder">
      <pattern>%highlight(%-5level) %logger{15} - %message%n%xException{10}</pattern>
    </encoder>
  </appender>

  <appender name="ASYNCFILE" class="AsyncAppender">
    <appender-ref ref="FILE"/>
  </appender>

  <appender name="ASYNCSTDOUT" class="AsyncAppender">
    <appender-ref ref="STDOUT"/>
  </appender>

  <logger name="play" level="INFO"/>
  <logger name="org.jdbcdslog.ConnectionLogger" level="OFF"/> <!-- Won' log connections -->
  <logger name="org.jdbcdslog.StatementLogger" level="INFO"/> <!-- Will log all statements -->
  <logger name="org.jdbcdslog.ResultSetLogger" level="OFF"/>  <!-- Won' log result sets -->

  <root level="WARN">
    <appender-ref ref="ASYNCFILE"/>
    <appender-ref ref="ASYNCSTDOUT"/>
  </root>

</configuration>

警告:請記住,這僅供在開發環境中使用,您不應在生產環境中設定它,因為它會造成效能降低,並會污染您的記錄。

§存取 JDBC 資料來源

Play 資料庫套件提供對預設資料來源的存取,主要透過 Database (請參閱 JavaScala 的文件) 類別。

Java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import javax.inject.*;
import play.db.*;

@Singleton
class JavaApplicationDatabase {

  private Database db;
  private DatabaseExecutionContext executionContext;

  @Inject
  public JavaApplicationDatabase(Database db, DatabaseExecutionContext context) {
    this.db = db;
    this.executionContext = executionContext;
  }

  public CompletionStage<Integer> updateSomething() {
    return CompletableFuture.supplyAsync(
        () -> {
          return db.withConnection(
              connection -> {
                // do whatever you need with the db connection
                return 1;
              });
        },
        executionContext);
  }
}
Scala
import javax.inject.Inject

import scala.concurrent.Future

import play.api.db.Database

class ScalaApplicationDatabase @Inject() (db: Database, databaseExecutionContext: DatabaseExecutionContext) {
  def updateSomething(): Unit = {
    Future {
      db.withConnection { conn =>
        // do whatever you need with the db connection
      }
    }(databaseExecutionContext)
  }
}

對於非預設的資料庫

Java

import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import javax.inject.Inject; import javax.inject.Singleton; import play.db.Database; import play.db.NamedDatabase; @Singleton class JavaNamedDatabase { private Database db; private DatabaseExecutionContext executionContext; @Inject public JavaNamedDatabase( // inject "orders" database instead of "default" @NamedDatabase("orders") Database db, DatabaseExecutionContext executionContext) { this.db = db; this.executionContext = executionContext; } public CompletionStage<Integer> updateSomething() { return CompletableFuture.supplyAsync( () -> db.withConnection( connection -> { // do whatever you need with the db connection return 1; }), executionContext); } }
Scala
import javax.inject.Inject

import scala.concurrent.Future

import play.api.db.Database
import play.db.NamedDatabase

class ScalaNamedDatabase @Inject() (
    @NamedDatabase("orders") ordersDatabase: Database,
    databaseExecutionContext: DatabaseExecutionContext
) {
  def updateSomething(): Unit = {
    Future {
      ordersDatabase.withConnection { conn =>
        // do whatever you need with the db connection
      }
    }(databaseExecutionContext)
  }
}

在兩種情況下,當使用 withConnection 時,連線會在區塊結束時自動關閉。

§取得 JDBC 連線

您可以以相同的方式擷取 JDBC 連線

Java
import java.sql.Connection;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import javax.inject.Inject;
import play.db.Database;

class JavaJdbcConnection {
  private Database db;
  private DatabaseExecutionContext executionContext;

  @Inject
  public JavaJdbcConnection(Database db, DatabaseExecutionContext executionContext) {
    this.db = db;
    this.executionContext = executionContext;
  }

  public CompletionStage<Void> updateSomething() {
    return CompletableFuture.runAsync(
        () -> {
          // get jdbc connection
          Connection connection = db.getConnection();

          // do whatever you need with the db connection
          return;
        },
        executionContext);
  }
}
Scala

import javax.inject.Inject import scala.concurrent.Future import play.api.db.Database class ScalaJdbcConnection @Inject() (db: Database, databaseExecutionContext: DatabaseExecutionContext) { def updateSomething(): Unit = { Future { // get jdbc connection val connection = db.getConnection() // do whatever you need with the db connection // remember to close the connection connection.close() }(databaseExecutionContext) } }

請務必注意,產生的連線並不會在請求週期結束時自動處置。換句話說,您有責任在程式碼中的某個地方呼叫其 close() 方法,以便可以立即將其傳回池中。

§使用 CustomExecutionContext

使用 JDBC 時,您應該始終使用自訂執行緒環境,以確保 Play 的呈現執行緒池完全專注於呈現結果,並充分利用核心。您可以使用 Play 的 CustomExecutionContext(請參閱 JavaScala 的文件)類別來設定一個專門用於服務 JDBC 作業的自訂執行緒環境。有關更多詳細資訊,請參閱 JavaAsync/ScalaAsyncThreadPools

Play 下載頁面 上所有使用封鎖 API(例如 Anorm、JPA)的 Play 範例範本,都已更新為在適當的地方使用自訂執行緒環境。例如

  1. Scala:前往 playframework/play-scala-anorm-example/ 會顯示 CompanyRepository 類別會採用包含所有資料庫作業的 DatabaseExecutionContext
  2. Java:前往 playframework/play-java-jpa-example 會顯示 JPAPersonRepository 類別會採用包含所有資料庫作業的 DatabaseExecutionContext

對於涉及 JDBC 連線池的執行緒池大小調整,您需要使用執行緒池執行器,將固定執行緒池大小設定為與連線池相符。依照 HikariCP 的池大小調整頁面 中的建議,您應將 JDBC 連線池設定為實體核心數量的兩倍,加上磁碟主軸數量,例如,如果您有四核心 CPU 和一個磁碟,則池中共有 9 個 JDBC 連線

# db connections = ((physical_core_count * 2) + effective_spindle_count)
fixedConnectionPool = 9

database.dispatcher {
  executor = "thread-pool-executor"
  throughput = 1
  thread-pool-executor {
    fixed-pool-size = ${fixedConnectionPool}
  }
}

§設定連線池

Play 預設使用 HikariCP 作為預設資料庫連線池實作。此外,您可以透過指定完全限定類別名稱來使用實作 play.api.db.ConnectionPool 的自訂池

play.db.pool=your.own.ConnectionPool

可以在 Play 的 JDBC reference.conf 中檢查 play.db.prototype 屬性,以找到連線池的完整設定選項範圍。

§測試

如需有關使用資料庫進行測試的資訊,包括如何設定記憶體中資料庫,請參閱

§啟用 Play 資料庫演進

請閱讀 演進 以瞭解 Play 資料庫演進的用途,並遵循說明使用它。

下一步:使用記憶體中 H2 資料庫


在這個文件中發現錯誤?此頁面的原始碼可以在 這裡 找到。在閱讀 文件指南 後,請隨時提交拉取請求。有問題或建議要分享?請前往 我們的社群論壇 與社群展開對話。