上一次在使用paly
时,使用的还是14年发布的2.2.x
版本,现在Play
已经发布到了2.6.x
版本,使用方式发生了很大变化,API
也有很大变化,并且在集成JPA
上,网络上没有发现基于Scala
的2.6.x
新版本的可用教程和说明,这里对这个版本的集成过程做了一个探索和梳理,也当是对play
的一次回顾。
添加依赖
因play
采用的是sbt
构建工具,因此需在sbt
中加入JPA
所需的依赖,打开项目根目录下的build.sbt
文件,末尾加入以下配置:
libraryDependencies ++= Seq(
javaJpa, //jpa依赖
ehcache, //缓存管理,可以提供Hibernate的缓存实现,此依赖不是必需的
"org.hibernate" % "hibernate-entitymanager" % "5.1.0.Final", //hibernate提供JPA规范实现,目前最新版本为5.1
"mysql" % "mysql-connector-java" % "8.0.11" //数据库连接依赖,目前最新版本为8.0.11
)
对项目进行构建,下载相关依赖包:
.\sbt compile #若用的本地sbt环境,命令为:sbt compile
配置XML
文件
JPA
需要有有自己的XML
配置文件,JPA
规范要求,配置文件要位于配置目录的META-INF
文件夹下,且默认名称为persistence.xml
,因此,在项目的conf
目录下新建一个名为META-INF
的目录,再在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> <!-- 采用hibernate的jpa规范实现 -->
<non-jta-data-source>DefaultDS</non-jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
</properties>
</persistence-unit>
</persistence>
配置application.conf
application.conf
是play
的全局配置文件,位于conf
目录下,打开该文件加入以下配置:
db { #数据库配置
default.jndiName=DefaultDS
default.driver=com.mysql.cj.jdbc.Driver #这是最新mysql驱动的名称,旧版本mysql驱动配置名为com.mysql.jdbc.Driver
default.url="jdbc:mysql://localhost/test?characterEncoding=utf-8&serverTimezone=UTC&useSSL=false" #数据库连接,其中test是数据库名称
default.username="root"
default.password="root"
}
jpa { #jpa配置
default=defaultPersistenceUnit #采用默认的jpa配置,会自动默认寻找读取persistence.xml文件中的配置
}
配置工作到此便结束,下面开始进行开发工作。
新建数据库表格及其对应的实体类
在mysql
数据库中新建用于测试的数据库及表格,这里新建一个名为test
的数据库,在其中建立一个名为post
的表,表中只有id
和name
两个字段,类型为bigint
和varchar
,然后编写其对应的实体类。在app
目录下新建目录(包)model
,用于存放实体类,在model
下新建scala
类Post
,代码如下:
//Post.scala
package model;
import javax.persistence._;
@Entity @Table(name = "post") //jpa注解,对应post表
class Post {
@Id @Column(name = "id") //主键
@GeneratedValue(strategy = GenerationType.IDENTITY) //指定主键策略为自动递增
var id: Long = 0L
@Column(name = "name") var name:String = ""
override def toString: String = "id = " + id + ", " + "name = " + name //重写toString方法
}
创建数据库访问层接口及其实现
在app
目录下新建新建repository
目录(包),在此文件夹下新建scala
特质PostRep
,代码如下:
//PostRep.scala
package repository
import com.google.inject.ImplementedBy
import model.Post
import repository.impl.PostRepImpl
@ImplementedBy(classOf[PostRepImpl]) //指定特质的实现类
trait PostRep {
def findById(id: Long): Post //根据id查询
def findAll(): List[Post] //查询所有记录
def addPost(post: Post): Post //添加一条记录
}
在repository
文件夹下新建文件夹impl
,用于存放特质的实现,在impl
目录下新建scala
类PostRepImpl
,实现PostRep
特质,代码如下:
//PostRepImpl.scala
package repository.impl
import javax.inject.Inject
import javax.persistence.EntityManager
import javax.inject._
import collection.JavaConverters._
import play.db.jpa.JPAApi;
import repository.PostRep
import model.Post
class PostRepImpl @Inject()(jpaApi: JPAApi) extends PostRep{ //依赖注入JPAApi
override def findById(id: Long): Post = {
return wrap(em => {
//调用EntityManager的find方法,第一个参数是实体类的类型,第二个参数是主键
return em.find(classOf[Post], id)
})
}
override def findAll(): List[Post] = {
return wrap(em => {
//创建查询语句并执行,返回的结果类型为java.util.List,这里手动转换成scala的List返回
return em.createQuery("select p from Post p", classOf[Post]).getResultList().asScala.toList
})
}
override def addPost(post: Post): Post = {
return wrap(em => {
em.persist(post) //EntityManager的persist方法,即insert,取名persist是为了更符合持久性的特性
em.getTransaction().commit() //若是不进行手动提交,可能存在虽然EntityManager托管了实体,但不持久化到数据库的问题
return post
})
}
//这里利用了jdk8的函数式编程接口
private def wrap[T](function: java.util.function.Function[EntityManager, T]): T= {
return jpaApi.withTransaction(function);
}
}
测试
在app/controller
目录下新建HomeController
类,用于验证jpa
集成的正确性(当然,用单元测试也是更好的),代码如下:
//HomeController.scala
package controllers
import javax.inject._
import play.api._
import play.api.mvc._
import repository.PostRep
import model.Post
@Singleton
class HomeController @Inject()(cc: ControllerComponents, postRep: PostRep) extends AbstractController(cc) {
def index() = Action { implicit request: Request[AnyContent] =>
val post1 = new Post
post1.name = "post1"
val post2 = new Post
post2.name = "post2"
postRep.addPost(post1)
postRep.addPost(post2)
val post = postRep.findById(1L)
println(post.toString)
val posts = postRep.findAll
for (p <- posts) println(p.toString)
Ok(views.html.index())
}
}
其中的views.html.index()
对应了app/views
目录下的一个模板,对应的文件名为index.scala.html
,这里模板中没有任何有效内容,只是为了让程序能跑起来,模板代码如下:
@()
<h1>Hello</h1>
当然,在conf
目录下的routes
文件中有以下一条路由定义:
GET /index controllers.HomeController.index
输入命令运行项目:
.\sbt run #若用的本地sbt环境,命令为:sbt run
浏览器访问localhost:9000/index
,可看到控制台打印消息如下: