【Play framework 學(xué)習(xí)筆記】侍匙、訪問(wèn)數(shù)據(jù)庫(kù)

背景

準(zhǔn)備熟悉前任同事基于 Play framework 開(kāi)發(fā)的后臺(tái)項(xiàng)目氮惯,想著既然是 Java 語(yǔ)言,那么從數(shù)據(jù)層入手想暗,將會(huì)事半功倍妇汗。

正文

這篇筆記參考的官方文檔是:Play 2.6.x JavaDatabase

要點(diǎn):

  • JDBC Driver
  • JNDI
  • SQL Log
  • JPA and Hibernate
  • ORM Ebean

JDBC Driver

在根目錄下找到 build.sbt 文件说莫,添加 JDBC 驅(qū)動(dòng)依賴:

libraryDependencies += javaJdbc

接下來(lái)打開(kāi) conf/application.conf 配置文件杨箭,添加默認(rèn)的 JDBC 數(shù)據(jù)源:

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

其他類型的內(nèi)存數(shù)據(jù)源:

# 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"

SQLite 數(shù)據(jù)庫(kù):

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

PostgreSQL 數(shù)據(jù)庫(kù):

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

這里為了數(shù)據(jù)統(tǒng)一性,還是安裝了一個(gè) MySQL Windows版本 數(shù)據(jù)庫(kù)储狭。

安裝是挺簡(jiǎn)單的互婿,遇到問(wèn)題也都可以從百度找到答案,唯一需要注意的是辽狈,可能你下載的安裝程序附帶數(shù)據(jù)庫(kù)管理工具慈参,通常只需要選擇 Server Only 進(jìn)行安裝。

配置MySQL 數(shù)據(jù)庫(kù):

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

需要注意的是刮萌,訪問(wèn)數(shù)據(jù)庫(kù)需要統(tǒng)一編碼并且禁用 SSL 方式:

db.default.url="jdbc:mysql://localhost/playdb?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false"

如果你開(kāi)源了項(xiàng)目驮配,請(qǐng)記得不要泄露數(shù)據(jù)庫(kù)賬戶密碼。

你也可以這樣設(shè)置 JDBC 驅(qū)動(dòng)的依賴和 MySQL 的連接依賴:

libraryDependencies ++= Seq(
  javaJdbc,
  "mysql" % "mysql-connector-java" % "5.1.41",
)

這和下面的寫法是完全一樣的:

libraryDependencies += javaJdbc
libraryDependencies += "mysql" % "mysql-connector-java" % "5.1.41"

配置 CustomExecutionContext 并不表示 Play 是一個(gè)同步框架(相反着茸,它是純異步的)壮锻,只不過(guò)為了將內(nèi)核線程專注于頁(yè)面的渲染,把訪問(wèn)和操作數(shù)據(jù)庫(kù)的線程獨(dú)立出來(lái):

# 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}
  }
}

JNDI

公開(kāi)數(shù)據(jù)源給其他數(shù)據(jù)庫(kù)操作接口涮阔,比如 JPA:

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

SQL Log

為了快速找到問(wèn)題所在猜绣,以及跟蹤數(shù)據(jù)庫(kù)運(yùn)行情況,可以在開(kāi)發(fā)環(huán)境下澎语,開(kāi)放日志權(quán)限:

# 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

注意:這不是必須的操作途事,因?yàn)樗鼘⒂绊懶阅苎榘茫绻阃浽谏a(chǎn)環(huán)境中關(guān)閉它的話擅羞,會(huì)有很大的麻煩。

JPA and Hibernate

JPA 只是一系列接口义图,具體實(shí)現(xiàn)還是要依賴 Hibernate

libraryDependencies ++= Seq(
  javaJpa,
  "org.hibernate" % "hibernate-entitymanager" % "5.1.0.Final" // replace by your jpa implementation
)

為了讓 JPA 訪問(wèn)到數(shù)據(jù)源减俏,請(qǐng)確認(rèn)在 conf/application.conf 中配置:

db.default.jndiName=DefaultDS

然后在 conf 目錄下,創(chuàng)建一個(gè)叫 META-INF 的目錄碱工,并新建文件 persistence.xml

<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
             version="2.1">

    <persistence-unit name="defaultPersistenceUnit" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <non-jta-data-source>DefaultDS</non-jta-data-source>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
        </properties>
    </persistence-unit>

</persistence>

需要注意的是娃承,由于我們使用的是 MySQL 數(shù)據(jù)庫(kù)奏夫,需要將上面文件中:

<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>

改為:

<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>

接著在 build.sbt 中添加:

PlayKeys.externalizeResources := false

最后繼續(xù)在 conf/application.conf 中添加配置:

jpa.default=defaultPersistenceUnit

以上操作都是為了讓 JPA 正常訪問(wèn)數(shù)據(jù)源,接下來(lái)還要看如何使用 play.db.jpa.JPAApi 這個(gè)類历筝。

如何使用 JPA 進(jìn)行數(shù)據(jù)訪問(wèn)酗昼?

  • 創(chuàng)建 Account 數(shù)據(jù)實(shí)體類:
@Entity(name = "account")
public class Account {
  @Id
  public String username;
  public String password;
  public long u_id;
  public int type;
}

這里將 Account 設(shè)為 public 并不奇怪,文檔說(shuō) Play framework 中的 Entity 在編譯后梳猪,會(huì)自動(dòng)生成 gettersetter 方法麻削。

糾正:檢查編譯后產(chǎn)生的 class 文件并沒(méi)有 getter 和 setter 方法,你需要安裝一個(gè) IDEA 插件春弥,用來(lái)增強(qiáng)實(shí)體類呛哟,以便自動(dòng)生成需要的方法。

  • 創(chuàng)建 AccountDao 數(shù)據(jù)映射接口:
public interface AccountDao {
  void save(Account entity);
  void delete(String username);
  Account findByName(String username);
  Account findById(long userId);
  List<Account> findAll();
}
  • 實(shí)現(xiàn) AccountDao 接口:
public class AccountDaoImp implements AccountDao {
  private final JPAApi jpaApi;
  @Inject
  public AccountDaoImp(JPAApi jpaApi) {
     this.jpaApi = jpaApi;
  }
  @Override
  public void save(Account entity) {
      jpaApi.em().merge(entity);
  }
  @Override
  public void delete(String username) {
      Account entity = findByName(username);
      jpaApi.em().remove(entity);
  }
  @Override
  public Account findByName(String username) {
      return jpaApi.em().find(Account.class, username);
  }
  @Override
  public Account findById(long userId) {
      try {
          return jpaApi.em().createQuery("SELECT entity FROM Account entity where entity.u_id = :userId", Account.class)
                  .setParameter("userId", userId)
                  .getSingleResult();
      } catch (NoResultException e) {
          return null;
      }
  }
  @Override
  public List<Account> findAll() {
      Query query = jpaApi.em().createQuery("SELECT entity FROM Account entity", Account.class);
      try {
          return query.getResultList();
      } catch (NoResultException e) {
          return null;
      }
  }
}
  • Guice 框架的運(yùn)行時(shí)依賴注入綁定:
@ImplementedBy(AccountDaoImp.class)
public interface AccountDao {
  // ...
}
  • 在控制器中使用 AccountDao:
public class AccountController extends RestController {
  private final AccountDao accountDao;
  @Inject
  public AccountController(AccountDao accountDao) {
      this.accountDao= accountDao;
  }
  @Transactional
  public Result home() {
      Account account = new Account();
      // account的一些賦值操作已省略匿沛,請(qǐng)自行完成
      accountDao.save(account);
      return created();
  }
  // 其他代碼省略...
}

需要注意的是扫责,必須在使用了 Dao 接口的方法上,標(biāo)記一個(gè)叫 @Transactional 的注解逃呼,表示啟用事務(wù)來(lái)處理數(shù)據(jù)鳖孤。

Hibernate 用起來(lái)還是不太方便,盡管已經(jīng)用 JPA 把數(shù)據(jù)查詢做到了完美蜘渣,但 ORM 可以節(jié)省你一半的寫 SQL 語(yǔ)句時(shí)間淌铐。

ORM Ebean

要使用 Ebean 框架,需要在 project/plugins.sbt 中添加:

addSbtPlugin("com.typesafe.sbt" % "sbt-play-ebean" % "4.0.1")

然后修改 build.sbt 中的配置為:

lazy val myProject = (project in file(".")).enablePlugins(PlayJava, PlayEbean)

為了使 Ebean 得知數(shù)據(jù)實(shí)體所在蔫缸,還需要在 conf/application.conf 中添加:

ebean.default = ["models.*"]

當(dāng)然腿准,其他數(shù)據(jù)源也可以這樣添加:

ebean.orders = ["models.Order", "models.OrderItem"]
ebean.customers =  ["models.Customer", "models.Address"]

完成以上操作后,接下來(lái)就可以稍微改動(dòng)一下拾碌,前面的 Account 了:

@Entity
@Table(name = "account")
public class Account extends Model {
  @Id
  public String username;
  public String password;
  public long u_id;
  public int type;

  public static final Finder<String, Account> find = new Finder<>(Account.class);
}

事實(shí)上只要繼承 Model 類就可以了吐葱,這里為了規(guī)范代碼,將 Entity 注解和 Table 注解分開(kāi)使用校翔。

關(guān)于 Finder 類弟跑,實(shí)際上是參照了文檔中的做法,可以看看它的使用效果:

List<Account> accounts = Account.find.all();

Account anyAccount = Account.find.byId("username");

Account.find.ref("username").delete();

List<Account> account007 = Account.find.query().where()
        .ilike("u_id", "%007%")
        .orderBy("type asc")
        .setFirstRow(0)
        .setMaxRows(25)
        .findPagedList()
        .getList();

由于原本同事的項(xiàng)目并沒(méi)有使用 Ebean 框架防症,所以這部分內(nèi)容只能到這里孟辑,未來(lái)若有實(shí)踐機(jī)會(huì),再逐步完善這里的細(xì)節(jié)蔫敲。

另外:由于使用了數(shù)據(jù)庫(kù)饲嗽,Play framework 會(huì)自動(dòng)啟用演化這個(gè)功能,通過(guò)在 conf/evolutions/default 目錄下生成的 1.sql 來(lái)跟蹤數(shù)據(jù)庫(kù)的演變歷程奈嘿,據(jù)官網(wǎng)介紹說(shuō)貌虾,這是為了在不同機(jī)器下,以及不同開(kāi)發(fā)者之間裙犹,來(lái)保持項(xiàng)目完整性尽狠。

演化功能會(huì)在數(shù)據(jù)庫(kù)中自動(dòng)建立一張表來(lái)記錄演變歷史衔憨,如果希望數(shù)據(jù)庫(kù)是獨(dú)立又純粹的,可以在 conf/application.conf 中袄膏,使用下面的代碼來(lái)禁掉演化功能:

play.evolutions.enabled=false

上面的禁用只能讓數(shù)據(jù)庫(kù)不再自動(dòng)創(chuàng)建演化表践图,卻由于使用了 Ebean 而導(dǎo)致 conf/evolutions/default 目錄下,依然會(huì)記錄數(shù)據(jù)庫(kù)的演變歷程沉馆。你需要手動(dòng)執(zhí)行這些演變歷程平项,以保證項(xiàng)目在更新之后可以正常運(yùn)行。

總結(jié)

數(shù)據(jù)庫(kù)訪問(wèn)是后臺(tái)的根本悍及,掌握住這個(gè)套路闽瓢,你將無(wú)所畏懼...啊心赶?好吧扣讼,除了 Redis、Cassandra缨叫、Elasticsearch 以及它們的集群配置...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末椭符,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子耻姥,更是在濱河造成了極大的恐慌销钝,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件琐簇,死亡現(xiàn)場(chǎng)離奇詭異蒸健,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)婉商,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門似忧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人丈秩,你說(shuō)我怎么就攤上這事盯捌。” “怎么了蘑秽?”我有些...
    開(kāi)封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵饺著,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我肠牲,道長(zhǎng)幼衰,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任埂材,我火速辦了婚禮塑顺,結(jié)果婚禮上汤求,老公的妹妹穿的比我還像新娘俏险。我一直安慰自己严拒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布竖独。 她就那樣靜靜地躺著裤唠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪莹痢。 梳的紋絲不亂的頭發(fā)上种蘸,一...
    開(kāi)封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音竞膳,去河邊找鬼航瞭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛坦辟,可吹牛的內(nèi)容都是我干的刊侯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼锉走,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼滨彻!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起挪蹭,我...
    開(kāi)封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤亭饵,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后梁厉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體辜羊,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年词顾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了只冻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡计技,死狀恐怖喜德,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情垮媒,我是刑警寧澤舍悯,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站睡雇,受9級(jí)特大地震影響萌衬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜它抱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一秕豫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦混移、人聲如沸祠墅。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)毁嗦。三九已至,卻和暖如春回铛,著一層夾襖步出監(jiān)牢的瞬間狗准,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工茵肃, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腔长,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓验残,卻偏偏與公主長(zhǎng)得像饼酿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子胚膊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,811評(píng)論 6 342
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理故俐,服務(wù)發(fā)現(xiàn),斷路器紊婉,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi...
    大大大浣熊閱讀 3,146評(píng)論 0 2
  • 凌晨十二點(diǎn)喻犁,接到朋友的哭訴槽片,內(nèi)容大概就是,自己有多么多么努力肢础,卻得不到賞識(shí)还栓、得不到回報(bào)云云,然后扯到工作上的“傻逼...
    鳳歌兒閱讀 880評(píng)論 0 5
  • TCP/IP模型四層模型: 1传轰、應(yīng)用層 2剩盒、傳輸層 3、網(wǎng)絡(luò)層 4慨蛙、網(wǎng)絡(luò)接口 助記:應(yīng)傳網(wǎng)接 一只老鷹辽聊,飛到船上,...
    lengol閱讀 802評(píng)論 0 51