Model 入门
学习收获
- 知道如何进入Rails Console.
- 知道持久层的概念
- 知道如何在Rails中使用model来操作数据库.
Model是 MVC中的M, 作用是操作数据库。
数据持久层
数据持久层的缩写: 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 代码" 的格式。
数据库结果集?
数据库结果集也就是 "ResultSet" (它很有名。几乎在所有语言连接数据库的时候,做查询的时候, 都要用到它)
下面是java代码中,访问mysql (这个就是所有语言访问mysql的过程)
// 第一步: 先建立一个连接。我也不知道干吗要搞个连接。 但是mysql就是这么规定的。
// 所以这个概念,对于程序员来说, 无意义。只是对mysql底层,才会有意义。
Connection con = DriverManager.getConnection(
"jdbc:mysql://instance23389.db.xeround.com:15296/Inventory","user","password");
// 第二步: 再建立一个 prepared statement:
PreparedStatement Statement = con.prepareStatement("Select * from inventory");
// 第三步: 执行这个 prepareStatement 得到了 ResultSet
ResultSet result = Statement.executeQuery();
// 来一个一个的遍历这个结果
while (result.next()) {
// 这一行, new Product(), 就是创建了一个java的对象。(在内存中)
Product product = new Product();
// result.getDouble : 获得某个列的值(还的预先知道这个列的类型)
product.setTotal(result.getDouble("Total")); (result.getDouble. .. 是在硬盘中 -- mysql文件)
product.setName(result.getString("Name"));
// etc.
productList.add(product);
}
上面的概念(ResultSet, Connection, PreparedConnection)虽然很学究,但是要读取数据库就是要这样的, 但是没它们不行,
数据库与持久层
数据库:
- sqlite: 是一个 .sqlite3 文件(例如 Rails中,默认就会创建:db/development.sqlite3)
- mysql: 也是一个文件。 对于ubuntu, 默认放在: /var/lib/mysql/数据库名下面,例如: /var/lib/mysql/me/students.frm, .ibd
文件放在硬盘上。
持久层: 以代码的形式来存在的。
可以认为 , 持久层, 是对数据库的一种封装。
把 原始的数据库操作, 封装成 java/ruby/c 代码。
例如, 上面的, 10多行java代码, 转换成: 持久层的操作:
(对于 "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)
ms sql server: 写法又变了:
select ... from ... order by id between 2000 and 2100
其他的: postgres, ... 都不一样。
所以,想兼容数据库,那就是一场噩梦。
(例如: mysql支持 select ... from (select ... from ... )
这样的嵌套 select, 其他好多数据库就不支持)
市面上, 根本找不到 熟悉所有数据库的人。 而且各个数据库的“方言(dialact )“ 都不一样。
十年以前的环境,比现在的还糟糕。(当年作java都没现在多。)
所以:持久层最大的卖点:
学好一个持久层, 可以操作所有数据库。
例如: 学好hibernate/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
- Rails的XX机制,让页面显示了持久层的数据。
CRUD:
先创建一个表: books
4.1 新建一个migration
$ bundle exec rails g migration create_books
invoke active_record
create db/migrate/20160926041205_create_books.rb
4.2 为它增加内容:
def change
create_table :books do |t|
t.string :author
t.string :title
t.timestamp
end
end
4.3 运行migrate:
$ bundle exec rake db:migrate
== 20160926041205 CreateBooks: migrating ======================================
-- create_table(:books)
-> 0.0030s
== 20160926041205 CreateBooks: migrated (0.0031s) =============================
4.4 创建model:
# app/models/book.rb
class Book < ActiveRecord::Base
end
4.5 进入到控制台
(说明: 所有 irb>
开头的代码,都表示本行从 rails console中敲入 )
$ bundle exec rails console
就可以进入到控制台, 输入Book.connection
就可以连接并操作数据库了:
Loading development environment (Rails 4.1.6)
irb(main):001:0> Book
=> Book (call 'Book.connection' to establish a connection)
irb(main):002:0> Book.connection
接下来,我可以创建一个book, author为 王博士
irb> book = Book.create :author => '王博士'
sqlite> select * from books;
1|十万个为什么|王博士
irb>book = Book.first
irb>book.title = '二十万个为什么'
irb>book.save
可以看到, 上面的代码,被转换成了下面的SQL语句:
(0.2ms) begin transaction
SQL (0.5ms) UPDATE "books" SET "title" = ? WHERE "books"."id" = 1 [["title", "二十万个为什么"]]
(173.7ms) commit transaction
irb> book.delete
SQL (52.4ms) DELETE FROM "books" WHERE "books"."id" = 1
=> #<Book id: 1, title: "二十万个为什么", author: "王博士">
插入100条语句:
irb> (1..100).each { |e| Book.create :title => "#{e}个为什么" }
会有100条SQL被转换出来,并且被执行.
查询:
irb> Book.where('title like "5%"')
Book Load (0.6ms) SELECT "books".* FROM "books" WHERE (title like "5%")
Book.where('title like "5%"').order('title desc') # 根据title倒序
TODO 补充
与hibernate的比较
Hibernate是 持久层的 java世界的鼻祖. 2003年出现. 它的用法可以归纳为下面三个步骤:
1.声明一个大的配置文件, 定义好了数据库的连接方式.
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">
org.hibernate.dialect.MySQLDialect
</property>
<property name="hibernate.connection.driver_class">
com.mysql.jdbc.Driver
</property>
<!-- List of XML mapping files -->
<mapping resource="Employee.hbm.xml"/>
</session-factory>
</hibernate-configuration>
这个在Rails中,是 config/database.yml
文件, 例如:
default: &default
adapter: mysql2
encoding: utf8
pool: 5
username: root
password:
development:
<<: *default
database: tuling
test:
<<: *default
database: tuling_test
production:
<<: *default
database: tuling
2.为每一个表,都要声明一个配置文件(XML), 例如:
<hibernate-mapping>
<class name="Employee" table="EMPLOYEE">
<meta attribute="class-description">
This class contains the employee detail.
</meta> <id name="id" type="int" column="id">
<generator class="native"/>
</id>
<property name="firstName" column="first_name" type="string"/>
</class>
</hibernate-mapping>
这个在Rails中没有.
3.为每一个数据库的表,都要声明一个java class:
/**
* @doclet table='employees'
*/
public class Employee {
private int id;
/**
* @doclet column = 'first_name'
*/
private String firstName;
private String lastName;
private int salary;
public Employee() {}
public Employee(String fname, String lname, int salary) {
this.firstName = fname;
this.lastName = lname;
this.salary = salary;
}
public int getId() {
return id;
}
public void setId( int id ) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName( String first_name ) {
this.firstName = first_name;
}
// ...
}
可以看出, 要做一个映射,Hibernate需要两个文件: java class, xml. 很麻烦.
Rails只要两行代码:
class Employee < ActiveRecord::Base
end
总结: 如何在Rails中实现 数据库的映射呢?
Rails中声明持久层极其简单:
如果你的表,名字叫: teachers, 那么,就在 app/models 目录下,新建一个rb文件: teacher.rb
class Teacher < ActiveRecord::Base
end
然后就可以在 Rails console中调用这个 teacher了。
作业:
- 在console下面, 实现book表的增删改查。
- 在action里面, 实现 book表的增删改查。