【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



    
        org.hibernate.jpa.HibernatePersistenceProvider
        DefaultDS
        
            
        
    


需要注意的是,由于我们使用的是 MySQL 数据库,需要将上面文件中:


改为:


接着在 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 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 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 find = new Finder<>(Account.class);
}

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

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

List accounts = Account.find.all();

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

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

List 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 以及它们的集群配置...

你可能感兴趣的:(【Play framework 学习笔记】、访问数据库)