【Play framework 学习笔记】、访问数据库

背景

准备熟悉前任同事基于 Play framework 开发的后台项目,想着既然是 Java 语言,那么从数据层入手,将会事半功倍。

正文

这篇笔记参考的官方文档是:Play 2.6.x JavaDatabase

要点:

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

JDBC Driver

在根目录下找到 build.sbt 文件,添加 JDBC 驱动依赖:

libraryDependencies += javaJdbc

接下来打开 conf/application.conf 配置文件,添加默认的 JDBC 数据源:

# Default database configuration
db.default.driver=org.h2.Driver
db.default.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"

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 Windows版本 数据库。

安装是挺简单的,遇到问题也都可以从百度找到答案,唯一需要注意的是,可能你下载的安装程序附带数据库管理工具,通常只需要选择 Server Only 进行安装。

配置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://localhost/playdb"
db.default.username=playdbuser
db.default.password="a strong password"

需要注意的是,访问数据库需要统一编码并且禁用 SSL 方式:

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

如果你开源了项目,请记得不要泄露数据库账户密码。

你也可以这样设置 JDBC 驱动的依赖和 MySQL 的连接依赖:

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

这和下面的写法是完全一样的:

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

配置 CustomExecutionContext 并不表示 Play 是一个同步框架(相反,它是纯异步的),只不过为了将内核线程专注于页面的渲染,把访问和操作数据库的线程独立出来:

# 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

公开数据源给其他数据库操作接口,比如 JPA:

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

SQL Log

为了快速找到问题所在,以及跟踪数据库运行情况,可以在开发环境下,开放日志权限:

# 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

注意:这不是必须的操作,因为它将影响性能,如果你忘记在生产环境中关闭它的话,会有很大的麻烦。

JPA and Hibernate

JPA 只是一系列接口,具体实现还是要依赖 Hibernate

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

为了让 JPA 访问到数据源,请确认在 conf/application.conf 中配置:

db.default.jndiName=DefaultDS

然后在 conf 目录下,创建一个叫 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 数据库,需要将上面文件中:

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

改为:

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

接着在 build.sbt 中添加:

PlayKeys.externalizeResources := false

最后继续在 conf/application.conf 中添加配置:

jpa.default=defaultPersistenceUnit

以上操作都是为了让 JPA 正常访问数据源,接下来还要看如何使用 play.db.jpa.JPAApi 这个类。

如何使用 JPA 进行数据访问?

  • 创建 Account 数据实体类:
@Entity(name = "account")
public class Account {
  @Id
  public String username;
  public String password;
  public long u_id;
  public int type;
}

这里将 Account 设为 public 并不奇怪,文档说 Play framework 中的 Entity 在编译后,会自动生成 gettersetter 方法。

纠正:检查编译后产生的 class 文件并没有 getter 和 setter 方法,你需要安装一个 IDEA 插件,用来增强实体类,以便自动生成需要的方法。

  • 创建 AccountDao 数据映射接口:
public interface AccountDao {
  void save(Account entity);
  void delete(String username);
  Account findByName(String username);
  Account findById(long userId);
  List<Account> findAll();
}
  • 实现 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 框架的运行时依赖注入绑定:
@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的一些赋值操作已省略,请自行完成
      accountDao.save(account);
      return created();
  }
  // 其他代码省略...
}

需要注意的是,必须在使用了 Dao 接口的方法上,标记一个叫 @Transactional 的注解,表示启用事务来处理数据。

Hibernate 用起来还是不太方便,尽管已经用 JPA 把数据查询做到了完美,但 ORM 可以节省你一半的写 SQL 语句时间。

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 得知数据实体所在,还需要在 conf/application.conf 中添加:

ebean.default = ["models.*"]

当然,其他数据源也可以这样添加:

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

完成以上操作后,接下来就可以稍微改动一下,前面的 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);
}

事实上只要继承 Model 类就可以了,这里为了规范代码,将 Entity 注解和 Table 注解分开使用。

关于 Finder 类,实际上是参照了文档中的做法,可以看看它的使用效果:

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();

由于原本同事的项目并没有使用 Ebean 框架,所以这部分内容只能到这里,未来若有实践机会,再逐步完善这里的细节。

另外:由于使用了数据库,Play framework 会自动启用演化这个功能,通过在 conf/evolutions/default 目录下生成的 1.sql 来跟踪数据库的演变历程,据官网介绍说,这是为了在不同机器下,以及不同开发者之间,来保持项目完整性。

演化功能会在数据库中自动建立一张表来记录演变历史,如果希望数据库是独立又纯粹的,可以在 conf/application.conf 中,使用下面的代码来禁掉演化功能:

play.evolutions.enabled=false

上面的禁用只能让数据库不再自动创建演化表,却由于使用了 Ebean 而导致 conf/evolutions/default 目录下,依然会记录数据库的演变历程。你需要手动执行这些演变历程,以保证项目在更新之后可以正常运行。

总结

数据库访问是后台的根本,掌握住这个套路,你将无所畏惧...啊?好吧,除了 Redis、Cassandra、Elasticsearch 以及它们的集群配置...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容

  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,800评论 6 342
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi...
    大大大浣熊阅读 3,144评论 0 2
  • 凌晨十二点,接到朋友的哭诉,内容大概就是,自己有多么多么努力,却得不到赏识、得不到回报云云,然后扯到工作上的“傻逼...
    凤歌儿阅读 873评论 0 5
  • TCP/IP模型四层模型: 1、应用层 2、传输层 3、网络层 4、网络接口 助记:应传网接 一只老鹰,飞到船上,...
    lengol阅读 800评论 0 51