数据持久层
数据持久层的缩写: persistant layer. 大家往往把它简称为: 持久层.
我们获取到一些数据的时候, 往往是这样:
# 假设 这个变量,来自于 网络的其他服务器
String content = SomeTool.query_from_remote ....
# 如何把它保存到本地?
经典方法: 保存到数据库
# 不怕断电了。 数据就持久了。
所以,在实际项目中, 持久层有两个实现方式:
- 保存到数据库. (这个是 99% 的选择)
- 保存到文件.
所以,持久层的架构是:
本地代码 < === > 持久层 < === > 数据库/文件
从例子来看持久层
例如: 希望读取所有的 book 记录。
- 浏览器发起一个请求给 Rails. 例如 http://localhost:3000/books
- Rails 调起持久层。 在 books_controller#index_action 中处理这个 /books 请求。
class BooksController < ApplicationController
def index
@books = Book.all
end
end
持久层查询数据库。并且把数据加载过来。
Book.all
- Book 它是个 model. 它映射到了 books 表。
- Book.all, 就会被 Rails 的持久层的机制,转换成一段 SQL 语句:
select * from books;
然后, Rails 的持久层,再把上面的结果,从 " 数据库结果集”,转换成:“ruby 代码” 的格式。
数据库与持久层
数据库:
- sqlite: 是一个 .sqlite3 文件(例如 Rails 中,默认就会创建:db/development.sqlite3)
- mysql: 也是一个文件。 对于 ubuntu, 默认放在: /var/lib/mysql/ 数据库名下面,例如: /var/lib/mysql/me/students.frm, .ibd
- 文件放在硬盘上。
持久层:
以代码的形式来存在的。
可以认为, 持久层, 是对数据库的一种封装。
把原始的数据库操作, 封装成 ruby 代码。
例子:
- 对于
select * from books
Book.all
- 对于
select * from books where author = '大刘‘
Book.where(name: '大刘')
- 对于 insert into books(title, author) values (“«十万个为什么»“, “李博士”) :
book = Book.new
book.title = "《十万个为什么》"
book.author = "李博士"
book.save
上面四行代码,可以简写为:
Book.create({ :title => '十万个为什么', :authro => '李博士'})
你会发现一个巨大的区别:
- SQL 语句的形式: 特别原始。 不好操作。不好维护。特别容易出错。
- 持久层的形式: 特别贴近于自然语言。 好维护。 好学习。 而且:菜鸟写的持久层操作, 都会被自动转换成高手写的 SQL 语句。
例如:
分页:
如果用 MYSQL:
select ... from ... order by id limit(100) offset(2000)
oracle:
写法就变了…
select ... from ... order by id limit(100) top(2000)
所以,想兼容数据库,那就是一场噩梦。(例如: mysql 支持 select … from (select … from …) 这样的嵌套 select, 其他好多数据库就不支持)
所以:持久层最大的卖点:
学好一个持久层, 可以操作所有数据库。
例如:
学好 Rails ActiveRecord, 可以在所有数据库上操作。 而且持久层生成的代码,就是专家级别的。(持久层在生成代码时会自动作优化。)
下面是持久层把 代码 转换成 SQL 语句的例子:
City.first:
SELECT `cities`.* FROM `cities` ORDER BY `cities`.`id` ASC LIMIT 1
City.first.airport_managers:
SELECT `cities`.* FROM `cities` ORDER BY `cities`.`id` ASC LIMIT 1
SELECT `airport_managers`.* FROM `airport_managers` WHERE `airport_managers`.`city_id` = 1
City.first.airport_managers.count
SELECT `cities`.* FROM `cities` ORDER BY `cities`.`id` ASC LIMIT 1
SELECT COUNT(*) FROM `airport_managers` WHERE `airport_managers`.`city_id` = 1
CRUD
先创建一个表: books
- 新建一个 migration
$ rails g migration create_books
- 为它增加内容:
def change
create_table :books do |t|
t.string :author
t.string :title
t.timestamp
end
end
- 运行 migrate:
$ rails db:migrate
== 20210926221205 CreateBooks: migrating ======================================
-- create_table(:books)
-> 0.0030s
== 20210926221205 CreateBooks: migrated (0.0031s) =============================
- 创建 model:
# app/models/book.rb
class Book < ActiveRecord::Base
end
- 进入到控制台
$ rails c
接下来, 我可以创建一个 book, author 为 王博士
> book = Book.create :author => '王博士'
```sql
sqlite> select * from books;
1|十万个为什么|王博士
```ruby
>book = Book.first
>book.title = '二十万个为什么'
>book.save
可以看到, 上面的代码, 被转换成了下面的 SQL 语句:
(0.2ms) begin transaction
SQL (0.5ms) UPDATE "books" SET "title" = ? WHERE "books"."id" = 1 [["title", "二十万个为什么"]]
(173.7ms) commit transaction
```ruby
> book.delete
SQL (52.4ms) DELETE FROM "books" WHERE "books"."id" = 1
=> #<Book id: 1, title: "二十万个为什么", author: "王博士">
插入 100 条语句:
> (1..100).each { |e| Book.create :title => "#{e}个为什么" }
会有 100 条 SQL 被转换出来, 并且被执行.
查询:
> Book.where('title like "5%"')
Book Load (0.6ms) SELECT "books".* FROM "books" WHERE (title like "5%")
```ruby
Book.where('title like "5%"').order('title desc') # 根据title倒序
总结: 如何在 Rails 中实现 数据库的映射呢?
Rails 中声明持久层极其简单:
如果你的表,名字叫: teachers, 那么,就在 app/models 目录下,新建一个 rb 文件: teacher.rb
class Teacher < ActiveRecord::Base
end
然后就可以在 Rails console 中调用这个 teacher 了。
评论区