快学Scala+Playframework之增删改查—— 数据库操作(三)

November 1, 2016

目标

知识点

  • 路由配置

  • playframework第三方库的安装(mysql操作库)

  • 数据库操作

  • API请求 列表数据请求实现


静态页面和API路由的配置

/conf/routes文件中定义静态页面和API的路由。

GET /users controllers.UserController.list //列表
GET /users/add controllers.UserController.add //添加
GET /users/detail/:id controllers.UserController.detail(id: Int) //详情
GET /users/:id controllers.UserController.edit(id: Int) //修改
GET /users/:id/delete controllers.UserController.delete(id: Int) //删除
#API
GET /api/users controllers.Application.list
POST /api/users controllers.Application.add
GET /api/users/:id controllers.Application.detail(id:Int)
POST /api/users/search controllers.Application.search
POST /api/users/:id controllers.Application.edit(id:Int)
DELETE /api/users/:id controllers.Application.delete(id:Int)

数据库操作

数据库、表的创建

数据库采用mysql,在OS X下,为了加快速度,我使用了mysql的可视化工具——Sequel Pro进行数据库的创建,表的建立和数据的插入。

Sequel Pro是一款非常强大的数据库管理工具。支持标准的用户名密码登录、SSH登录、Socket登录。先在本地启动mysql服务。

$ mysql.server start

命令行mysql

启动mysql之后,需要通过sequel pro连接本地服务,通过如下图配置:

sequel pro

点击connect,建立如下所示表test

CREATE TABLE `test` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(30) DEFAULT NULL,
`password` varchar(20) DEFAULT NULL,
`description` varchar(40) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;

可以通过书写sql语句或者手动添加字段。并且插入一些数据如下: sql insert

配置、连接数据库

play中需要连接mysql需要在build.sbt中添加mysql驱动,并且这里我添加了mysql的操作函数库来高效率的查询数据库。

"com.typesafe.play" %% "anorm" % "2.5.0"
"mysql" % "mysql-connector-java" % "5.1.36"

接着需要在/conf/application.conf中配置数据库的名称、地址、密码等。

db{
default.driver=com.mysql.jdbc.Driver
default.url="jdbc:mysql://localhost/play"
default.username= root
default.password= ""
}

接下来进行在/models中创建一个名为UserInfo的类:

case class Userinfo (id: Int, username: String, description: String, password: String)

根据上述定义的路由,我们需要在Controllers中新建Application.scala作为后端API的控制器。

我们期望,返回一个json array的数据类型给前端接收,并且前端根据约定好的规则去获取并渲染.虽然在后面的处理中,又将json类型转化为了Seq,这是为了单纯搞明白play中的页面传值的写法。但是对于json格式的数据返回是必须掌握的,利于AJAX请求等进阶的学习。

scala操作数据库

在play的高版本中,controller中的类,依赖注入。写法如下,这里可以参考play-anorm的API

class Application @Inject()(db: Database,ws: WSClient) extends Controller {
def list = Action { implicit request =>
val parser: RowParser[Userinfo] = Macro.namedParser[Userinfo]
db.withConnection { implicit c =>
val result = SQL("SELECT * FROM test").as(parser.*)
Ok(Json.toJson(result))
}
}
}

这里,我们通过注入db来进行数据库的连接,查询数据库后获取result,并且返回了JSON格式的数据,这个时候可以刷新下页面,可以看到控制台报出这样的错误。

No Json serializer found for type List[models.Userinfo]. Try to implement an implicit Writes or Format for this type.

可以看出数据库的连接没有出错,出错在于我们没有Userinfo这样JSON类型,需要隐式声明。解决办法如下:

implicit val userWrites = Writes[Userinfo] {
case Userinfo(id: Int, username: String, description: String, password: String) =>
Json.obj(
"id" -> id,
"username" -> username,
"description" -> description,
"password" -> password
)
}

在这里我创建了Userinfo类型下的序列化Json。可以通过postman来请求下我们事先定义好的url http://localhost:9000/api/users,postman帮我们输出了请求到的数据,

json

整个过程大体是这样。postman请求服务器的/api/users路由,play接收到请求,去执行controller下的list方法,list方法通过查询数据库后,再将查询到的数据List类型处理为json,并返回给postman。

那接收就比较容易了。

同样,UserController中的list方法,就是充当了postman。不同于postman的是它比较"智能",不是打印数据,而是将数据填充到html中,返回html文件,浏览器经过渲染,生成了html页面。

列表数据的获取

//UserController中的list方法
class UserController @Inject()(ws: WSClient) extends Controller {
def list = Action.async {
val url = s"http://localhost:9001/api/users"
ws.url(url).get().map {
response =>
val json = Json.parse(response.body)
val jsonSeq = json.as[Seq[Userinfo]]
Ok(views.html.users("User list", jsonSeq))
}
}
}

我这里使用了play的ws方法,来请求一个url获取数据。细节内容可以参考play的WSClient内容。这里请求到json数据后,由于没有用到AJAX,所以还是转化成了play页面传值能够识别的Seq类型。

可以看下/views/users.scala.html中的内容,就是遍历了所有的数据。

@for(user <- users) {
<tr>
<td>@user.id</td>
<td>@user.username</td>
<td>@user.description</td>
<td>@user.password</td>
<td><a href="@routes.UserController.detail(user.id)">详情</a>
<span style="margin: 0 10px;">|</span>
<a href="@routes.UserController.edit(user.id)">修改</a>
<span style="margin: 0 10px;">|</span>
<a href="@routes.UserController.delete(user.id)">删除</a>
</td>
</tr>
}

最终生成的html截图如下:

userlist

详情的获取

同样的详情(details)页面的API写法也非常简单了,除了sql语句不同,其他都类似。

//Application.scala
def detail(id: Int) = Action { implicit request =>
val parser: RowParser[Userinfo] = Macro.namedParser[Userinfo]
db.withConnection { implicit c =>
val result = SQL(s"SELECT * FROM test WHERE id=$id").as(parser.*)
Ok(Json.toJson(result))
}
}

同样的前端请求并渲染数据。

//UserController.scala
def detail(id: Int) = Action.async {
val url = s"http://localhost:9001/api/users/$id"
ws.url(url).get().map {
response =>
val json = Json.parse(response.body)
val jsonSeq = json.as[Seq[Userinfo]]
Ok(views.html.details("user details",jsonSeq))
}
}
<div class="panel panel-default">
<div class="panel-heading">
<h4>@d.username 的信息</h4>
</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>User name</dt>
<dd>@d.username</dd>
<dt>Description</dt>
<dd>@d.description</dd>
<dt>Password</dt>
<dd>@d.password</dd>
</dl>
</div>
</div>

截图如下:

details