数据必须以某种方式来存储才可以有用,数据库实际上是一组相关数据的集合。例如,某个医疗机构中所有信息的集合可以被称为一个“医疗机构数据库”,这个数据库中的所有数据都与医疗机构的相关。
数据库编程相关的技术很多,涉及具体的数据库安装、配置和管理,还要掌握SQL语句,最后才能编写程序访问数据库。本章重点介绍MySQL数据库的安装和配置,以及Exposed框架进行数据库编程。
在介绍Exposed框架前先介绍一下数据库管理系统。数据库管理系统负责对数据进行管理、维护和使用。现在主流数据库管理系统有Oracle、SQL Server、DB 2、Sysbase和MySQL等,本节介绍MySQL数据库管理系统使用和管理。
MySQL(https://www.mysql.com)是流行的开放源码SQL数据库管理系统,它是由MySQL AB公司开发,先被Sun公司收购,后来又被Oracle公司收购,现在MySQL数据库是Oracle旗下的数据库产品,Oracle负责提供技术支持和维护。
25.1.1 数据库安装与配置
目前Oracle提供了多个MySQL版本,其中社区版MySQL Community Edition是免费的,社区版本比较适合中小企业数据库,本书也采用这个版本介绍。
社区版下载地址是https://dev.mysql.com/downloads/windows/installer/5.7.html,如图25-1所示,可以选择不同的平台版本,MySQL可运行在Windows、Linux和UNIX等操作系统上安装和运行。本书选择的是Windows 版中的mysql-installer-community-5.7.18.1.msi安装文件。
下载成功后,可以双击.msi文件启动安装过程,安装过程比较简单,这里介绍一个关键步骤。
1.安装类型选择
如图25-2所示是安装类型选择对话框。在这个页面中可以选择安装类型,有5种安装类型:DeveloperDefault(开发者安装)、Server only(只安装服务器)、Client only(只安装客户端)、Full(全部安装)和Custom(自定义安装)。对于学习和开发可以选择Developer Default安装。
2.安装环境检查
在Windows下安装时,由于Windows版本多样性。安装过程会检查你的需要,缺少哪些Windows安装包,安装过程会给出提示,如图25-3所示,安装MySQL Server需要Microsoft Visual C++ 2013 Runtime,则需要到微软网站下载Microsoft Visual C++ 2013 Runtime安装包,安装好Microsoft Visual C++ 2013 Runtime后,再重新安装MySQL。
3.配置过程
所需要的文件安装完成后,就会进入MySQL的配置过程。首先如图25-4所示是数据库类型选择对话框,Standalone是单个服务器,innoDB Cluster是数据库集群。
在图25-4所示的对话框中选择Standalone,单击Next按钮进入如图25-5所示的服务器配置类型选择对话框。在这里可以选择配置类型、通信协议和端口等,单击Config Type下拉列表可以选择如下的配置类型:
o Development Machine(开发机器):该选项代表典型个人用桌面工作站,假定机器上运行着多个桌面应用程序。将MySQL服务器配置成使用最少的系统资源。
o Server Machine(服务器):该选项代表服务器,MySQL服务器可以同其他应用程序一起运行,例如FTP、email和web服务器。MySQL服务器配置成使用适当比例的系统资源。
o Dedicated Machine(专用MySQL服务器):该选项代表只运行MySQL服务的服务器。假定没有运行其它应用程序。MySQL服务器配置成使用所有可用系统资源。
根据自己的需要选择配置类型,其他的配置项目保持默认值,单击Next按钮进入如图25-6所示的账号和用户角色设置对话框。
在图25-6所示的对话框中可以进行设置root密码,以及添加其他账号等操作。root密码必须是4位以上,根据需要设置root密码。此外,还可以单击Add User按钮添加其他的账号。
在图25-6对话框设置完成后,单击Next按钮进入图25-7所示的配置Windows服务对话框,在这里可以将MySQL数据库配置成为一个Windows服务,Windows服务可以在后台随着Windows已启动而启动,不需要认为干预。其实默认的服务名是MySQL57。
在图25-7所示配置界面之后,不需要再进行配置了,只需要单击Next按钮,这里不再赘述。
25.1.2 连接MySQL服务器
由于MySQL是C/S(客户端/服务器)结构的,所以应用程序包括它的客户端必须连接到服务器才能使用其服务功能。下面主要介绍MySQL本身客户端如何连接到服务器。
1.快速连接服务器方式
MySQL for Windows版本提供一个菜单项目可以快速连接服务器,打开过程右击屏幕左下角的Windows图标,在“最近添加”中找到MySQL
5.7 Command Line Client,则会在打开一个终端窗口如图25-8所示对话框。
这个工具就是MySQL命令行客户端工具,可以使用MySQL命令行客户端工具连接到MySQL服务器,要求输入root密码。输入root密码按Enter键,如果密码正确则连接到MySQL服务器,如图25-9所示。
2.通用的连接方式
快速连接服务器方式连接的是本地数据库,如果服务器不在本地,而是在一个远程主机上,那么需要可以使用通用的连接方式。
首先在操作系统下打开一个终端窗口,Windows下是命令行工具,在次输入mysql -h localhost -u root –p命令。如图25-10所示,如果出现“‘MySQL’
不是内部或外部命令,也不是可运行的程序或批处理文件。” 的错误,则说明在环境变量的Path没有配置MySQL的Path。参考2.1.2节追加C:\Program
Files\MySQL\MySQL Server 5.7\bin到环境变量Path之后。
如果Path环境变量添加成功,重新打开命令行,再次输入mysql -h localhost -u root -p命令,然后系统会提示你输入root密码,输入密码按下Enter键,如果密码正确成功连接到服务器,会看到如图25-9所示的界面。
25.1.3 常见的管理命令
通过命令行客户端管理MySQL数据库,需要了解一些常用的命令。
1.help
第一个应该熟悉的就是help命令,help命令能够列出MySQL其他命令的帮助,在命令行客户端中输入help,不需要分号结尾,直接按下Enter键,如图25-11所示,这里都是MySQL的管理命令,这些命令大部分不需要分号结尾。
2.退出命令
如果命令行客户端中退出,可以命令行客户端中使用quit或exit命令,如图25-12所示。注意这两个命令也不需要分号结尾。
3.数据库管理
在使用数据库的过程中,有时需要知道服务器中哪些数据库或自己创建和删除数据库。查看数据库的命令是show databases;,如图25-13所示,注意该命令后面是有分号结尾的。
创建数据库可以使用create database testdb;命令,如图25-14所示,testdb是自定义数据库名,注意该命令后面是有分号结尾的。
想要删除数据库可以使用database testdb;命令,如图25-15所示,testdb是自定义数据库名,注意该命令后面是有分号结尾的。
4.数据表管理
在使用数据库的过程中,有时需要知道某个数据库下有多少个数据表,并想查看表结构等信息。
查看有多少个数据表的命令是show tables;,如图25-16所示,注意该命令后面是有分号结尾的。因为一个服务器中有很多数据库,应该先使用use 选择数据库,如图25-16所示,use world命令结尾没有分号。如果没有选择数据库,会发生错误,如图25-16所示。
知道了有哪些表后,还需要知道表结构,可以使用desc命令,例如像知道city表结构可以使用命令desc city;命令,如图25-17所示,注意该命令后面是有分号结尾的。
DSL(领域特定语言,Domain Specific Language)针对某个领域所设计出来的一个特定的语言,如SQL语言、正则表达式和HTML等。而Java、C和C++等语言称为GPL(通用编程语言,General Purpose Language)。DSL专注于特定领域或任务,放弃了与该领域无关的功能,使得其更加专注,也会变得简单易用。
Kotlin提供了DSL支持,如函数式编程和Lambda表达式都是DSL的基础。此外,还有Kotlin中集合和数组中的函数selectAll、groupBy、orderBy和sortedBy等,这些函数方便DSL进行处理数据。
目前支持DSL的Kotlin库或框架有很多,下面列举了三个:
o kotlinx.html(https://github.com/Kotlin/kotlinx.html)。生成HTML页面。
o Exposed(https://github.com/JetBrains/Exposed)。轻量级SQL框架。
o Klaxon(https://github.com/cbeust/klaxon)。JSON解码和编码库。
下面看一段个kotlinx.html代码:
//代码文件:chapter25/src/main/kotlin/com/a51work6/section2/ch25.2.kt
package com.a51work6.section2
import kotlinx.html.*
import kotlinx.html.stream.appendHTML
fun main(args: Array) {
System.out.appendHTML().html { ①
body {
div {
a(“http://zhijieketang.com”) {
target =ATarget.blank
+“智捷课堂视频网站”
}
}
} ②
}
}
上述代码第①行~第②行是生成HTML代码,在html {…}中的内容是不是非常接近HTML语言呢?这就是DSL它使用起来非常简单易用。生成之后的代码如下:
25.3.1 配置项目
为了能够在项目中使用Exposed框架,需要创建IntelliJ IDEA+Gradle项目,项目创建完成后在打开build.gradle文件,修改文件内容如下:
version ‘1.0-SNAPSHOT’
buildscript {
ext.kotlin_version = ‘1.1.60’
repositories {
mavenCentral()
}
dependencies {
classpath"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: ‘java’
apply plugin: ‘kotlin’
sourceCompatibility = 1.8
repositories {
mavenCentral()
maven { ①
url “https://dl.bintray.com/kotlin/exposed”
} ②
}
dependencies {
compile"org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
compile’org.jetbrains.exposed:exposed:0.9.1’ ③
compile(“mysql:mysql-connector-java:5.1.6”) ④
testCompile group: ‘junit’, name: ‘junit’, version: ‘4.12’
}
compileKotlin {
kotlinOptions.jvmTarget = “1.8”
}
compileTestKotlin {
kotlinOptions.jvmTarget = “1.8”
}
在build.gradle文件中添加代码第①行~第②行,这是添加Gradle仓库maven,其中"https://dl.bintray.com/kotlin/exposed"是Exposed地址。代码第③行添加Exposed依赖关系。代码第④行是添加MySQL数据库JDBC驱动程序依赖关系。
25.3.2 面向DSL API
Exposed框架提供了两种形式的API,一种是面向DSL的API;另一种是 面向对象的API。本节介绍面向DSL的API,这里的DSL类似于标准SQL。
下面通过示例介绍一下如何使用Exposed DSL API,示例中有一个公司部门表(Departments),它有两个字段id和name,如表25-1所示。
示例代码如下:
//代码文件:chapter25/src/main/kotlin/com/a51work6/section3/s2/ch25.3.2.kt
package com.a51work6.section3.s2
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SchemaUtils.create
import org.jetbrains.exposed.sql.transactions.transaction
// 声明部门表
object Departments : Table() { ①
//声明表中字段
val id =integer(“id”).autoIncrement().primaryKey() ②
val name = varchar(“name”,length = 30) ③
}
const val URL =“jdbc:mysql://localhost:3306/testDB?useSSL=false&verifyServerCertificate=false” ④
const val DRIVER_CLASS = “com.mysql.jdbc.Driver” ⑤
fun main(args: Array) {
//连接数据库
Database.connect(URL, user =
“root”, password = “12345”, driver = DRIVER_CLASS) ⑥
//操作数据库
transaction { ⑦
//创建部门表Departments
create(Departments) ⑧
//部门表插入数据
Departments.insert { ⑨
it[name] = “销售部”
}
Departments.insert {
it[name] = “技术部”
}
showDatas() ⑩
//更新数据
Departments.update({Departments.name eq “销售部” }) { ⑪
it[name] = “市场部”
}
showDatas()
//删除数据
Departments.deleteWhere {Departments.id lessEq 1 } ⑫
showDatas()
}
}
//查询所有数据,并打印
fun showDatas() {
println("---------------------")
Departments.selectAll().forEach { ⑬
println("${it[Departments.id]}: ${it[Departments.name]}") ⑭
}
}
上述代码实现了创建Departments表,以及对Departments进行了数据CRUD操作[1]。代码第①行声明一个部门表,Exposed SQL DSL中要操作的数据库中所有的表要在此声明,声明采用Kotlin对象声明语法,代码第②行和第③行为表声明字段,integer和varchar函数对应SQL中的字段类型整数和可变字符串类型,类似的函数有:
o 数值类型:integer、long、decimal
o 字符类型:char、text、varchar
o 日期、时间类型:date、datetime
o 布尔类型:bool
o 大二进对象类型:blob
这些函数都与SQL字段类型对应,从函数名可知字段类型,这里不再赘述。
另外,代码第②行的autoIncrement函数设置字段是自增长得 ,primaryKey函数设置字段是主键。
上述代码第④行是设置数据库连续需要的URL字符串,代码第⑤行是设置JDBC驱动程序,不同数据库URL和JDBC驱动程序是不同的,在这里总结了几个常用数据库URL和驱动程序,如表25-2所示。对照表25-2可知代码第④行的URL字符串中,testDB是数据库名,另外3306是MySQL服务器所用端口,useSSL=false是设置不使用SSL进行网络通信,verifyServerCertificate=false是设置不进行SSL安全认证。
[1] CRUD操作是指对数据库表中数据可以进行4类操作:数据插入(Create)、数据查询(Read)、数据更新(Update)和数据删除(Delete),也是俗称的“增、删、改、查”。
代码第⑥行函数Database.connect是连接数据库,代码第⑦行transaction{…}函数是开启数据库事务管理,transaction{…}范围内的SQL操作都是一个事物。
代码第⑧行create函数是创建数据库表,它的参数是可变参数。代码第⑨行是调用表的insert函数插入数据,it[name]
= "销售部"表达式是为name字段设置数值。
代码第⑩行调用showDatas函数查询所有数据并打印,其中代码第⑬行中的selectAll函数查询所有数据,代码⑭行it[Departments.id]是访问id字段内容,it[Departments name]是访问name字段内容。
代码第⑪行update函数是更新表,Departments.name eq "销售部"表达式是是更新条件,相当于SQL的name = ‘销售部’,eq是Exposed框架提供的中缀运算符,即等于。Exposed框架中缀运算符与SQL运算符对照,如表25-3所示。
代码第⑫行deleteWhere是按照条件删除数据,Departments.id lessEq 1表达式是删除条件,相当于SQL语句的id <= 1。
2: 技术部
25.3.3 面向对象API
上一节介绍了Exposed DSL API,代码风格非常类似于SQL语句。在Exposed框架中不仅提供了DSL API,还提供了面向对象的API,本节介绍Exposed对象API。
所谓“面向对象的API”主要是通过对象来操作数据库,它提供了一种对象关系型映射技术(ORM),类似于Hibernate框架[1]。
示例代码如下:
//代码文件:chapter25/src/main/kotlin/com/a51work6/section3/s3/ch25.3.3.kt
package com.a51work6.section3.s3
…
// 声明部门表
object Departments : IntIdTable() { ①
//声明表中字段
val name = varchar(“name”,length = 30) ②
}
// 声明部门实体
class Department(id: EntityID) : IntEntity(id) { ③
//为数据表Departments与实体Department建立映射关系
companion object :IntEntityClass(Departments) ④
var name by Departments.name ⑤
}
const val URL =“jdbc:mysql://localhost:3306/testDB?useSSL=false&verifyServerCertificate=false”
const val DRIVER_CLASS = “com.mysql.jdbc.Driver”
fun main(args: Array) {
//连接数据库
Database.connect(URL, user =
“root”, password = “12345”, driver = DRIVER_CLASS)
//操作数据库
transaction {
//创建部门表Departments
create(Departments) ⑥
//部门实体中插入数据
Department.new { ⑦
name = "销售部"
}
val dept = Department.new { ⑧
name = "技术部"
}
showDatas()
//修改部门实体属性
dept.name = "市场部" ⑨
showDatas()
//删除部门实体
dept.delete() ⑩
showDatas()
}
}
[2] Hibernate是一种Java语言下的对象关系映射解决方案。它为面向对象的领域模型到传统的关系型数据库的映射提供了一个使用方便的框架。
代码第①行是声明创建数据库中的部门表,注意父类是IntIdTable,IntIdTable是一种主键命名为id,且自增长的整数类型字段。代码第②行是声明表中name字段,由于继承IntIdTable,所以还有一个id字段。
代码第③行是声明部门实体[1]类,父类是IntEntity,即主键为Int类型的实体。应用程序中的实体类与数据库中的表是有对应关系的,这就是ORM(对象关系映射),Exposed对象API就是面向这些实体对象的操作。代码第④行是将数据库在表Departments与程序中实体Department类建立起映射关系,IntEntityClass是指明实体类,Departments是指明数据中的表。代码第⑤行是指定实体类Department的属性与表Departments的字段映射关系。
代码第⑥行是创建部门表Departments。代码第⑦行和第⑧行都是调用实体类的new函数创建一个Department实体对象,这个操作的结果会使Exposed框架在数据库Departments表中增加一条记录。代码第⑧行是有返回值的,这个返回值就是成功创建Department实体对象的引用,通过这个引用可以修改和删除实体,Exposed框架会同步这些操作到数据库表中。代码第⑨行是修改部门实体name属性为"市场部",相应的数据库Departments表也会更新对应的记录的name字段。代码第⑩行是删除当前的部门实体,相应的数据库Departments表也会删除对应的记录。
[3]“实体”是系统中的“人”、“事”、“物”等名词,如部门、员工、商品、订单和订单明细等。
25.3节示例中只是使用了一个表,在实际开发工作中经常会遇到多表之间有外键约束情况,而且经常用到多表连接查询。
25.4.1 创建数据库
下面通过案例介绍一下使用Exposed DSL API实现多表连接查询操作。案例中公司部门表和员工表,它们的E-R图[1]如图25-18所示,其中有两个表,部门表(Departments)和员工表(Employees),它们有外键关联Employees的dept_id(所在部门id)字段外键关联到Departments的id字段,外键约束了所有员工的所在部门一定是在部门表中存在的数据。
[4] E-R图也称实体-联系图(Entity Relationship Diagram),提供了表示实体(或表)、属性(或字段)和联系的表示方法。
根据如图25-18所示,编程创建数据库程序代码如下:
// 声明部门表
object Departments : Table() {
//声明表中字段
val id =integer(“id”).autoIncrement().primaryKey()
val name = varchar(“name”,
length = 30)
}
// 声明员工表
object Employees : Table() {
//声明表中字段
val id = integer(“id”).autoIncrement().primaryKey()
val name = varchar(“name”,length = 50)
val deptId =(integer(“dept_id”) references Departments.id).nullable() ①
}
…
transaction {
…
create(Departments,Employees)
…
}
上述代码在数据库中创建两个表,两个表的id字段都是自增长的整数类型。代码第①行是设置dept_id字段,“references Departments.id”指定外键关联到Departments表的id字段。
25.4.2 配置SQL日志
为了更好地调试,往往需要参看SQL DSL执行过程的生成的SQL语句,特别是那些复杂的查询语句。Exposed框架提供了SQL日志工具类SqlLogger,使用时需要进行一些配置。打开build.gradle文件,修改dependencies内容如下:
dependencies {
compile"org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
compile’org.jetbrains.exposed:exposed:0.9.1’
compile(“mysql:mysql-connector-java:5.1.6”)
compile ‘org.slf4j:slf4j-api:1.7.25’ ①
compile’org.slf4j:slf4j-simple:1.7.25’ ②
compile"org.jetbrains.kotlinx:kotlinx-html-jvm:0.6.8"
testCompile group: ‘junit’, name:
‘junit’, version: ‘4.12’
}
在dependencies添加代码第①行和第②行,这是因为SqlLogger依赖于slf4j日志框架。
在程序代码中的transaction{…}还需要添加如下语句:
transaction {
logger.addLogger(StdOutSqlLogger)
…
}
25.4.3 实现查询
下面具体介绍几个表连接查询。案例代码如下:
//代码文件:chapter25/src/main/kotlin/com/a51work6/section4/ch25.4.kt
package com.a51work6.section4
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SchemaUtils.create
import org.jetbrains.exposed.sql.transactions.transaction
// 声明部门表
object Departments : Table() {
//声明表中字段
val id =integer(“id”).autoIncrement().primaryKey()
val name = varchar(“name”,length = 30)
}
// 声明员工表
object Employees : Table() {
//声明表中字段
val id =integer(“id”).autoIncrement().primaryKey()
val name = varchar(“name”,length = 50)
val deptId =
(integer(“dept_id”) references Departments.id).nullable()
}
const val URL =“jdbc:mysql://localhost:3306/testDB?useSSL=false&verifyServerCertificate=false”
const val DRIVER_CLASS = “com.mysql.jdbc.Driver”
fun main(args: Array) {
//连接数据库
Database.connect(URL, user =
“root”, password = “12345”, driver = DRIVER_CLASS)
//操作数据库
transaction {
logger.addLogger(StdOutSqlLogger)
//创建表
create(Departments, Employees)
//部门表插入数据
val deptId1 = Departments.insert{ ①
it[name] = “销售部”
} get Departments.id
val deptId2 = Departments.insert
{
it[name] = “技术部”
} get Departments.id
Departments.insert {
it[name] = “财务部”
}
//员工表插入数据
Employees.insert {
it[name] = "张三"
it[deptId] = deptId1
}
Employees.insert {
it[name] = "李四"
it[deptId] = deptId2
}
Employees.insert {
it[name] = "王五"
it[deptId] = deptId2
} ②
//1.查询“技术部”的所有员工信息
(Employees innerJoin
Departments).slice(Employees.id, Employees.name, Departments.name).
select { Departments.name eq “技术部” }.forEach {
println(" i t [ E m p l o y e e s . i d ] : {it[Employees.id]}: it[Employees.id]:{it[Employees.name]} 所在部门:KaTeX parse error: Expected 'EOF', got '}' at position 34: …me]}") }̲ …{it[Employees.name]} 所在部门: i t [ D e p a r t m e n t s . i d ] : {it[Departments.id]}: it[Departments.id]:{it[Departments.name]}")
} ④
}
}
上述代码第①行~第②行向数据库中插入一些测试数据。
代码第③行实现了查询“技术部”的所有员工信息,其中Employees innerJoin Departments是声明查询是内连接(Inner Join)查询,Exposed还支持左外连接(Left Join)和交叉连接(Cross Join)查询,使用的函数分别是leftJoin和crossJoin。slice函数是设置选定的字段列表,所有后面用到的字段都要在此列出。select函数指定查询条件。
代码第④行实现了查询员工“张三”所在部门信息,其中函数与代码第③行一样,这里不再赘述。
上述代码执行结果如下:
SQL: CREATE TABLE IF NOT EXISTS departments (id INT
AUTO_INCREMENT PRIMARY KEY, name VARCHAR(30) NOT NULL)
SQL: CREATE TABLE IF NOT EXISTS employees (id INT
AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, dept_id INT NULL, FOREIGN KEY (dept_id) REFERENCES
departments(id) ON DELETE RESTRICT)
SQL: INSERT INTO departments (name) VALUES (‘销售部’)
SQL: INSERT INTO departments (name) VALUES (‘技术部’)
SQL: INSERT INTO departments (name) VALUES (‘财务部’)
SQL: INSERT INTO employees (name, dept_id) VALUES (‘张三’, 1)
SQL: INSERT INTO employees (name, dept_id) VALUES (‘李四’, 2)
SQL: INSERT INTO employees (name, dept_id) VALUES (‘王五’, 2)
SQL: SELECT employees.id, employees.name,
departments.name FROM employees INNER JOIN departments ON departments.id =
employees.dept_id
WHERE departments.name = ‘技术部’
2:李四 所在部门:技术部
3:王五 所在部门:技术部
SQL: SELECT departments.id, departments.name,
employees.name FROM employees INNER JOIN departments ON departments.id =
employees.dept_id
WHERE employees.name = ‘张三’
员工:张三 所在部门:1:销售部
其中SQL:内容都是SqlLogger日志输出的,这里可以查看生成的内连接查询SQL语句。
本章首先介绍MySQL数据库的安装、配置和日常的管理命令。然后介绍了DSL,以及Kotlin对于DSL的支持。最后重点讲解了Exposed框架,读者需要重点掌握Exposed框架。