背景
準(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)生成 getter
和 setter
方法麻削。
糾正:檢查編譯后產(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 以及它們的集群配置...