在Ktor的服务端项目中植入数据库,实现对数据的增删改查

在上一篇文章从服务端到客户端,一次Ktor的跨端实践中我们已经知道了如何去使用Ktor创建一个简单的服务端项目,开发接口,并在自己的demo中去调用接口去展示数据,但是美中不足的是所使用的数据是我们自己造的假数据,真正的研发过程中,服务端往往是会通过数据库与数据打交道,所以今天我们就来一步步学习下如何在一个ktor的服务端项目中植入一个数据库,并实现基本的增删改查操作,以及优化下上一篇文章中做的客户端demo,让它这次可以跟服务端有个数据的交互

第一步:引入依赖

首先在gradle.properties文件中加入两个版本号,分别是Exposed和H2两个库

然后在build.gradle.kts文件中添加Exposed与H2两个库的依赖

第二步:创建数据模型与数据库表

新建一个PhotoTable.kt文件,在里面加入数据类Photo,另外创建一个objectPhotos作为我们的数据表,Photos继承自Exposed库里面的Table

在表中id作为主键自增长,title保存着图片的名字,imgUrl保存着图片的链接

第三步:连接数据库

连接数据库需要用到Exposed库里面的Database类,它会调用connect函数来连接数据库,connect函数里面需要传入两个参数,一个是jdbc链接,另一个是驱动名字,所以首先创建一个object类DatabaseFactory,建立一个init函数,在里面去连接数据库

注意这里的driverClassNamejdbcURL是写死的两个值,除非是自己去自定义数据库,建立好连接以后,在connect函数底下调用transaction函数,在函数体内部我们就可以去创建表,创建的方式是调用SchemaUtils.create函数,里面传入我们之前声明好的Photos类,这样表就创建好了,考虑到每次访问数据库都需要开启一个事务,我们在DatabaseFactory中新增一个挂起函数dbQuery,这个函数接受一个挂起的函数类型参数,在这个函数类型参数里面我们去执行每一次对数据库的操作

最后在程序启动的时候,在Application.module函数中调用一下Databasefactory.init函数,我们数据库就完成了所有初始化的工作

第四步:访问数据

完成了对数据库的初始化工作以后,就该对里面的数据进行操作了,对数据库最基本的操作无非就是增删改查,所以接下来我们也添加上增删改查的api,第一步先创建个interface,命名为PhotoDao.kt,在里面定义我们需要的操作,有这么几个

既然都是数据库操作,所以它们都属于比较耗时的,所以将它们都定义为挂起函数,然后就是去实现这些接口了,这里有个快捷方式可以一键生成impl类,那就是点击左上角的那个灯泡,在下来菜单中点击implement interface

接下去就会询问你是否创建PhotoDaoImpl.kt类,一直点ok就可以了,最终得到了我们的实现类

现在就可以在实现类里面写数据库操作的代码了,不过在这之前我们需要增加一个函数,这个函数用来将从数据库里获得的对象也就是ResultRow,转换成我们需要用的数据类也就是Photo

查询所有数据

函数allPhotos的功能就是将表中的所有数据都读出来,使用selectAll函数来实现,然后使用map操作符将数据通过resultRowToPhoto函数转换成List<Photo>

查询单条数据

查询单个数据使用select函数进行,与selectAll不同的是,select函数接受一个SqlExpressionBuilder为接收者的lambda表达式,我们在表达式里面添加查询的条件,比如我们这里的条件就是与传入的id相同的数据,那么就可以这样写

eq就表示相等的意思,除此之外还可以使用其他操作符比如less, greater, lessEq, greaterEq 等来表示不同的条件,singleOrNull表示如果返回结果数组大小为1,那么直接返回,否就返回空

新增数据

需要插入数据的时候我们使用insert函数,insert也接受一个lambda表达式,表达式里面有个InsertStatement对象,我们调用这个对象的set操作符将对应字段插入到对应Column里面去

更新数据

更新一条数据使用update函数,该函数接收三个参数,第一个where是一个SqlExpressionBuilder为接收者的lambda表达式,用来填写条件来查找需要更新的数据,第二个参数是limit,表示最大可更新的数据量,第三个参数是body,是调用UpdateStatementset操作符来执行更新数据的操作,update函数会返回一个int结果,当结果大于0的时候表示更新成功

删除数据

删除数据使用的是deleteWhere函数,这个函数接收三个参数,第一个参数为limit,表示删除的最大数量,第二个参数为offset,表示删除的偏移量,第三个参数删除的条件语句,也是一个SqlExpressionBuilder为接收者的lambda表达式,最终是否删除成功也是根据deleteWhere的返回值是否大于0来判断

就这样增删改查的所有操作的写完了,最后在PhotoDaoImpl.kt文件里面添加一个PhotoDao的顶层属性,用来提供给外界来访问对数据库进行操作

第五步:实战

展示数据

数据库部分写完了,我们现在可以来实际演练一下,先更改一下上一篇文章中写好的queryPhotos接口,原本这个接口返回的是造出来的假数据,现在我们可以让这个接口直接去数据库里面查询,将所有数据都读出来,代码更改如下

现在如果重新运行一下之前写好的图片列表demo,访问的接口就是从我们的数据库里面拿数据了,不过在运行之前我们先给数据库里面默认添加一条,因为现在整个表没数据,就算运行了客户端代码,页面也是空的

当创建PhotoDao的时候,就判断如果表中没有数据,就默认添加一条进去,现在我们运行一下服务端项目,然后打开我们的客户端,看看效果

我们看到界面上展示的数据与ktor打印出来的日志相吻合,说明我们已经将数据库中的数据展示出来了。

添加数据

现在让我们扩展一下,增加一个添加图片的功能,首先服务端那边需要添加一个addPhoto的接口,当客户端调用addPhoto接口的时候,服务端获取到请求,判断传过来的数据是否不为空,如果不是空的话就往数据库中插入一条数据,所以首先我们在服务端项目中打开Routing.kt文件,在原有的queryPhotos接口下面添加上下面这段代码

一般这种添加数据的操作都是post的请求,所以这里先调用了post函数,里面传入我们接口的路径addPhoto,在函数体里面我们调用call.receive函数用来接受客户端传过来的参数body,AddPhotoRequest是接收过来的参数的数据模型

当接口从参数中获取title字符串后,判断是否为空,如果不为空就插入到数据库,图片链接我们就随机获取的一个链接,最终再将数据库中的所有数据再返还给客户端,在客户端项目中,我们也添加上addPhoto接口的post请求,代码如下

AddImageRequest为客户端调用addPhoto接口的请求体,接口加好之后,我们在界面上做一下修改,由于是要添加图片,所以要有一个编辑框去编辑图片的title,然后还需要一个按钮去触发addPhoto接口,更改后的界面代码如下所示

然后在ButtononClick事件中调用添加图片的接口

当接口成功返回数据之后,我们将编辑框的内容清空,然后把返回的新的图片数据赋值给feedList,界面就刷新了,分别运行下服务端与客户端项目,看看效果

我们看到当点击添加按钮之后,控制台那边返回了两条数据,我们界面上也对应着展示出了两条数据,说明我们这个添加图片的功能完成了

删除数据

最后我们再来一个删除数据的操作,删除数据就是把id传给服务端,服务端接收到id之后,再用这个id去数据库中执行删除操作,删除成功后再返回一个状态给客户端,客户端刷新列表,首先我们在服务端这里新增一个接收删除数据传参的数据类DeletePhotoRequest.kt

然后在Routing.kt文件里面新增一个接口叫deletePhoto,代码如下

当删除数据成功之后,服务端返给客户端一个status:ok的json数据,如果删除失败,那么返给客户端一个status:fail的json数据,然后运行一遍服务端代码重启下服务,我们回到客户端的代码中,首先在LazyColumn的每个item底下新增一个删除按钮

clickable函数里面就是调用删除图片的接口,现在我们添加上调用接口的代码,在之前添加图片的接口底下新增deletePhoto接口

DeleteImageResponseDeleteImageRequest分别是调用deletePhoto接口的返回数据和请求参数数据类

然后在删除按钮的clickable函数中调用删除图片的接口

我们看到在接口请求成功的回调那里,我们将被删除的id赋值给了一个变量deleteId,deleteIdremember出来的一个Int变量,目的是当deleteId值发生改变之后可以刷新列表数据,所以我们还需要将请求列表数据的接口包在LaunchedEffect函数体内,deleteId作为参数传给LaunchedEffect,这样当deleteId发生改变的时候就又会请求一遍列表数据

现在来跑一遍客户端代码看看效果

总结

头一次写demo有种既当爹又当妈的感觉,自己要啥接口自己写,写完接口自己调,虽说目前这些服务端代码写的还比较不规范,不能跟真正的大项目代码做比较,但是也是托Ktor的福,让我也接触到了一点服务端开发的皮毛,后面也会继续调研这方面的技术,有收获了就会拿出来分享给大家~

相关推荐
wowocpp2 小时前
spring boot Controller 和 RestController 的区别
java·spring boot·后端
后青春期的诗go2 小时前
基于Rust语言的Rocket框架和Sqlx库开发WebAPI项目记录(二)
开发语言·后端·rust·rocket框架
freellf2 小时前
go语言学习进阶
后端·学习·golang
圈圈编码3 小时前
MVVM框架
android·学习·kotlin
全栈派森4 小时前
云存储最佳实践
后端·python·程序人生·flask
CircleMouse4 小时前
基于 RedisTemplate 的分页缓存设计
java·开发语言·后端·spring·缓存
獨枭5 小时前
使用 163 邮箱实现 Spring Boot 邮箱验证码登录
java·spring boot·后端
维基框架5 小时前
Spring Boot 封装 MinIO 工具
java·spring boot·后端
秋野酱5 小时前
基于javaweb的SpringBoot酒店管理系统设计与实现(源码+文档+部署讲解)
java·spring boot·后端
橙子199110165 小时前
在 Kotlin 中,什么是解构,如何使用?
android·开发语言·kotlin