PyMySQL 和 SQLAlchemy使用

MySQL 命令大全

DDL(数据定义语句)

CREATE TABLE/DATABASE

ALTER TABLE/DATABASE

DROP TABLE/DATABASE

DML(数据管理语句)

INSERT => 新增

DELETE => 删除

UPDATE => 更新

SELECT => 查询

创建数据库

—创建并使用数据库

CREATE DATABASE mydatabase; 创建数据库

USE mydatabase; 使用数据库

—查看已有的数据库

SHOW DATABASES;

建表

建表语句

<pre class="md-fences md-end-block" lang="sql" contenteditable="false" cid="n554" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">CREATE TABLE students(
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(200) NOT NULL
);</pre>

常见类型

int,char,varchar,datetime

例子:

<pre class="md-fences md-end-block" lang="sql" contenteditable="false" cid="n580" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">-- 新建数据库
CREATE DATABASE school;

-- 使用数据库
USE school;

-- id : 学生的ID
-- name: 学生名称
-- nickname: 学生的昵称
-- sex: 性别
-- in_time: 入学的时间
CREATE TABLE students(
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(20) NOT NULL,
nickname VARCHAR(20) NULL,
sex CHAR(1) NULL,
in_time DATETIME(20) NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;

-- 插入数据
INSERT INTO students(1,'张三','san',now());

-- 删除表
DROP TABLE IF EXISTS students;

-- 查询
SELECT * FROM students;

-- 查询语句
SELECT
select_expr, ...
FROM table_references -- table_references 表来源
[WHERE where_definition] -- where_definition 条件
[GROUP BY {col_name | expr | position}]
[HAVING where_definition]
[ORDER BY {col_name | expr | position}
[ASC | DESC],...]
[LIMIT {[offset,] row_count}] -- LIMIT限制查询条数 offset(偏移量,从哪开始), row_count(查询的条数)

-- 条件查询
SELECT * FROM student WHERE sex='男';
SELECT id,name,nickname FROM student WHERE sex='男' ORDER BY id DESC;

-- 修改(更新)
UPDATE table_references
SET col_name1=expr1[, col_name2=expr2 ...]
[WHERE where_definition]

-- 更新操作
UPDATE student SET sex = '女' WHERE sex = '男';
UPDATE student SET sex = '女' WHERE id > 5;

-- 其他操作
CREATE INDEX -- 新建索引
ALTER TABLE -- 修改表
DROP -- 删除数据库、表、索引、视图等</pre>

<figure class="md-table-fig" contenteditable="false" cid="n307" mdtype="table" style="box-sizing: border-box; margin: -8px 0px 0px -8px; overflow-x: auto; max-width: calc(100% + 16px); padding: 8px;">

命令 说明
net start mysql 启动数据库
net stop mysql 关闭数据库
mysql -u root -p 根据用户名密码,登录数据库
mysql -u root 如果数据库没有密码则使用
show databases; 查看在当前服务器中有多少个数据库
drop database databaseName; 删除某个数据库
commit; 提交
CREATE DATABASE db_name DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; 创建数据库
ALTER DATABASE db_name DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; 更改数据库的字符编码
use databaseName; 选择使用某个数据库
show tables; 查看数据库中有多少的表
drop table tableName; 删除表
describe tableName; 显示表结构
SET PASSWORD FOR 'root'``@``'localhost' = ``PASSWORD``(``'newpass'``); 方法1: 用SET PASSWORD命令
方法2:用mysqladmin

</figure>

方法1: 用SET PASSWORD命令

MySQL -u root

mysql> ``SET PASSWORD FOR 'root'``@``'localhost' = ``PASSWORD``(``'newpass'``);

方法2:用mysqladmin

mysqladmin -u root ``password "newpass"

如果root已经设置过密码,采用如下方法

mysqladmin -u root ``password oldpass ``"newpass"

方法3: 用UPDATE直接编辑user表

mysql -u root

mysql> use mysql;

mysql> ``UPDATE user SET Password = ``PASSWORD``(``'newpass'``) ``WHERE user = ``'root'``;

mysql> FLUSH ``PRIVILEGES``;

在丢失root密码的时候,可以这样

mysqld_safe ``--skip-grant-tables&

mysql -u root mysql

mysql> ``UPDATE user SET password``=``PASSWORD``(``"new password"``) ``WHERE user``=``'root'``;

mysql> FLUSH ``PRIVILEGES``;

快捷命令说明:

​ command + w 关闭标签command + 数字 command + 左右方向键 切换标签command + enter 切换全屏command + f 查找command + d 垂直分屏command + shift + d 水平分屏command + option + 方向键 command + [ 或 command + ] 切换屏幕command + ; 查看历史命令command + shift + h 查看剪贴板历史ctrl + u 清除当前行ctrl + l 清屏ctrl + a 到行首ctrl + e 到行尾ctrl + f/b 前进后退ctrl + p 上一条命令ctrl + r 搜索命令历史

新闻表设计

字段说明

<pre class="md-fences md-end-block" lang="sql" contenteditable="false" cid="n599" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">-- ID: 新闻的唯一标识
-- title: 新闻的标题
-- content: 新闻的内容
-- create_time: 新闻添加的时间
-- types: 新闻的类型
-- image: 新的缩略图
-- author: 作者
-- view_count: 浏览量
-- is_valid: 删除标记
CREATE TABLE news(
id INT NOT NULL AUTO_INCREMENT,
title VARCHAR(200) NOT NULL,
content VARCHAR(2000) NOT NULL,
types VARCHAR(10) NOT NULL,
image VARCHAR(300) NULL,
author VARCHAR(20) NULL,
view_count INT DEFAULT 0,
create_time DATETIME NULL,
is_valid SMALLINT DEFAULT 1,
PRIMARY KEY (id)
) DEFAULT CHARSET = 'UTF8'

-- 任务
-- 1、创建一个数据库,然后设计一个新闻表(数据类型要使用合理)
-- 2、使用SQL语句向数据表写入十五条不同的数据
-- 3、使用SQL语句查询类别为"百家"的新闻数据
-- 4、使用SQL语句删除一条新闻数据
-- 5、使用SQL语句查询所有的新闻,以添加时间的倒序进行排列
-- 6、使用SQL语句查询第二页(每一页5条数据)</pre>

Flask-SQLAlchemy使用

Queck-Start快速上手

<pre class="md-fences md-end-block" lang="python" contenteditable="false" cid="n625" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">"""

"""
from flask import Flask
from flask-sqlachemy improt SQLAlchemy

app = Flask(name)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'

class User(db.Model)
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nuallable=False)
email = db.Column(db.String(120), unique=True, nullable=False)

def repr(self):
return '<User %r>' % self.username</pre>

为了创建初始数据库,只需db从交互式Python shell 导入对象并运行该SQLAlchemy.create_all()方法来创建表和数据库:

<pre class="md-fences md-end-block" lang="shell" contenteditable="false" cid="n644" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">>>> from yourapplication import db

db.create_all()</pre>

接着,需要给你的数据库添加一些数据,例如新增两个用户:

<pre class="md-fences md-end-block" lang="shell" contenteditable="false" cid="n655" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">>>> from yourapplication import User

admin = User(username='admin', email='admin@example.com')
guest = User(username='guest', email='guest@example.com')</pre>

但是它们还没有真正地写入到数据库中,因此让我们来确保它们已经写入到数据库中:

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n668" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">>>> db.session.add(admin)

db.session.add(guest)
db.session.commit()</pre>

访问数据库中的数据很简单:

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n679" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">>>> User.query.all()
[<User u'admin'>, <User u'guest'>]

User.query.filter_by(username='admin').first()
<User u'admin'></pre>

最简单的关系型数据库

SQLAlchemy 连接到关系型数据库,关系型数据最擅长的东西就是关系。因此,我们将创建一个使用两张相互关联的表的应用作为例子:

<pre class="md-fences md-end-block" lang="python" contenteditable="false" cid="n721" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">from datetime import datetime


class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(80))
body = db.Column(db.Text)
pub_date = db.Column(db.DateTime)

category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
category = db.relationship('Category',
backref=db.backref('posts', lazy='dynamic'))

def init(self, title, body, category, pub_date=None):
self.title = title
self.body = body
if pub_date is None:
pub_date = datetime.utcnow()
self.pub_date = pub_date
self.category = category

def repr(self):
return '<Post %r>' % self.title


class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))

def init(self, name):
self.name = name

def repr(self):
return '<Category %r>' % self.name</pre>

首先让我们创建一些对象:

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n739" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">>>> py = Category('Python')

p = Post('Hello Python!', 'Python is pretty cool', py)
db.session.add(py)
db.session.add(p)</pre>

现在因为我们在 backref 中声明了 posts 作为动态关系,查询显示为:

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n757" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">>>> py.posts
<sqlalchemy.orm.dynamic.AppenderBaseQuery object at 0x1027d37d0></pre>

它的行为像一个普通的查询对象,因此我们可以查询与我们测试的 “Python” 分类相关的所有文章(posts):

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n775" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">>>> py.posts.all()
[<Post 'Hello Python!'>]</pre>

启蒙之路

您仅需要知道与普通的 SQLAlchemy 不同之处:

  1. SQLAlchemy允许您访问下面的东西:

  2. Model 声明基类行为类似一个常规的 Python 类,不过有个 query 属性,可以用来查询模型 (ModelBaseQuery)

  3. 您必须提交会话,但是没有必要在每个请求后删除它(session),Flask-SQLAlchemy 会帮您完成删除操作。

配置

下面是 Flask-SQLAlchemy 中存在的配置值。Flask-SQLAlchemy 从您的 Flask 主配置中加载这些值。 注意其中的一些在引擎创建后不能修改,所以确保尽早配置且不在运行时修改它们。

配置键

Flask-SQLAlchemy 扩展能够识别的配置键的清单:

<figure class="md-table-fig" contenteditable="false" cid="n953" mdtype="table" style="box-sizing: border-box; margin: -8px 0px 0px -8px; overflow-x: auto; max-width: calc(100% + 16px); padding: 8px;">

SQLALCHEMY_DATABASE_URI 用于连接数据的数据库。例如:sqlite:////tmp/test.db``mysql://username:password@server/db
SQLALCHEMY_BINDS 一个映射绑定 (bind) 键到 SQLAlchemy 连接 URIs 的字典。 更多的信息请参阅 绑定多个数据库
SQLALCHEMY_ECHO 如果设置成 True,SQLAlchemy 将会记录所有 发到标准输出(stderr)的语句,这对调试很有帮助。
SQLALCHEMY_RECORD_QUERIES 可以用于显式地禁用或者启用查询记录。查询记录 在调试或者测试模式下自动启用。更多信息请参阅get_debug_queries()
SQLALCHEMY_NATIVE_UNICODE 可以用于显式地禁用支持原生的 unicode。这是 某些数据库适配器必须的(像在 Ubuntu 某些版本上的 PostgreSQL),当使用不合适的指定无编码的数据库 默认值时。
SQLALCHEMY_POOL_SIZE 数据库连接池的大小。默认是数据库引擎的默认值 (通常是 5)。
SQLALCHEMY_POOL_TIMEOUT 指定数据库连接池的超时时间。默认是 10。
SQLALCHEMY_POOL_RECYCLE 自动回收连接的秒数。这对 MySQL 是必须的,默认 情况下 MySQL 会自动移除闲置 8 小时或者以上的连接。 需要注意地是如果使用 MySQL 的话, Flask-SQLAlchemy 会自动地设置这个值为 2 小时。
SQLALCHEMY_MAX_OVERFLOW 控制在连接池达到最大值后可以创建的连接数。当这些额外的 连接回收到连接池后将会被断开和抛弃。
SQLALCHEMY_TRACK_MODIFICATIONS 如果设置成 True (默认情况),Flask-SQLAlchemy 将会追踪对象的修改并且发送信号。这需要额外的内存, 如果不必要的可以禁用它。

</figure>

连接 URI 格式

完整连接 URI 格式列表请跳转到 SQLAlchemy 下面的文档(支持的数据库)。这里展示了一些常见的连接字符串。

SQLAlchemy 把一个引擎的源表示为一个连同设定引擎选项的可选字符串参数的 URI。URI 的形式是:

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n1003" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">dialect+driver://username:password@host:port/database</pre>

该字符串中的许多部分是可选的。如果没有指定驱动器,会选择默认的(确保在这种情况下 包含 + )。

Postgres:

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n1008" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">postgresql://scott:tiger@localhost/mydatabase</pre>

MySQL:

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n1011" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">mysql://scott:tiger@localhost/mydatabase</pre>

Oracle:

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n1014" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">oracle://scott:tiger@127.0.0.1:1521/sidname</pre>

SQLite (注意开头的四个斜线):

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n1017" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">sqlite:////absolute/path/to/foo.db</pre>

声明模型

通常下,Flask-SQLAlchemy 的行为就像一个来自 declarative 扩展配置正确的 declarative 基类。因此,我们强烈建议您阅读 SQLAlchemy 文档以获取一个全面的参考。尽管如此,我们这里还是给出了最常用的示例。

需要牢记的事情:

  • 您的所有模型的基类叫做 db.Model。它存储在您必须创建的 SQLAlchemy 实例上。 细节请参阅 快速入门

  • 有一些部分在 SQLAlchemy 上是必选的,但是在 Flask-SQLAlchemy 上是可选的。 比如表名是自动地为您设置好的,除非您想要覆盖它。它是从转成小写的类名派生出来的,即 “CamelCase” 转换为 “camel_case”。

简单示例

一个非常简单的例子:

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n1037" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(120), unique=True)

def init(self, username, email):
self.username = username
self.email = email

def repr(self):
return '<User %r>' % self.username</pre>

Column 来定义一列。列名就是您赋值给那个变量的名称。如果您想要在表中使用不同的名称,您可以提供一个想要的列名的字符串作为可选第一个参数。主键用 primary_key=True 标记。可以把多个键标记为主键,此时它们作为复合主键。

列的类型是 Column 的第一个参数。您可以直接提供它们或进一步规定(比如提供一个长度)。下面的类型是最常用的:

<figure class="md-table-fig" contenteditable="false" cid="n1042" mdtype="table" style="box-sizing: border-box; margin: -8px 0px 0px -8px; overflow-x: auto; max-width: calc(100% + 16px); padding: 8px;">

Integer 一个整数
String (size) 有长度限制的字符串
Text 一些较长的 unicode 文本
DateTime 表示为 Python datetime 对象的 时间和日期
Float 存储浮点值
Boolean 存储布尔值
PickleType 存储为一个持久化的 Python 对象
LargeBinary 存储一个任意大的二进制数据

</figure>

一对多(one-to-many)关系

最为常见的关系就是一对多的关系。因为关系在它们建立之前就已经声明,您可以使用 字符串来指代还没有创建的类(例如如果 Person 定义了一个到 Article 的关系,而 Article在文件的后面才会声明)。

关系使用 relationship() 函数表示。然而外键必须用类 sqlalchemy.schema.ForeignKey 来单独声明:

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n1072" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
addresses = db.relationship('Address', backref='person',
lazy='dynamic')

class Address(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(50))
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))</pre>

db.relationship() 做了什么?这个函数返回一个可以做许多事情的新属性。在本案例中,我们让它指向 Address 类并加载多个地址。它如何知道会返回不止一个地址?因为 SQLALchemy 从您的声明中猜测了一个有用的默认值。 如果您想要一对一关系,您可以把 uselist=False 传给 relationship()

那么 backref 和 lazy 意味着什么了?backref 是一个在 Address 类上声明新属性的简单方法。您也可以使用 my_address.person 来获取使用该地址(address)的人(person)。lazy 决定了 SQLAlchemy 什么时候从数据库中加载数据:

  • 'select' (默认值) 就是说 SQLAlchemy 会使用一个标准的 select 语句必要时一次加载数据。

  • 'joined' 告诉 SQLAlchemy 使用 JOIN 语句作为父级在同一查询中来加载关系。

  • 'subquery' 类似 'joined' ,但是 SQLAlchemy 会使用子查询。

  • 'dynamic' 在有多条数据的时候是特别有用的。不是直接加载这些数据,SQLAlchemy 会返回一个查询对象,在加载数据前您可以过滤(提取)它们。

您如何为反向引用(backrefs)定义惰性(lazy)状态?使用 backref() 函数:

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n1092" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
addresses = db.relationship('Address',
backref=db.backref('person', lazy='joined'), lazy='dynamic')</pre>

多对多(many-to-many)关系

如果您想要用多对多关系,您需要定义一个用于关系的辅助表。对于这个辅助表, 强烈建议 使用模型,而是采用一个实际的表:

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n1096" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">tags = db.Table('tags',
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')),
db.Column('page_id', db.Integer, db.ForeignKey('page.id'))
)

class Page(db.Model):
id = db.Column(db.Integer, primary_key=True)
tags = db.relationship('Tag', secondary=tags,
backref=db.backref('pages', lazy='dynamic'))

class Tag(db.Model):
id = db.Column(db.Integer, primary_key=True)</pre>

这里我们配置 Page.tags 加载后作为标签的列表,因为我们并不期望每页出现太多的标签。而每个 tag 的页面列表( Tag.pages)是一个动态的反向引用。 正如上面提到的,这意味着您会得到一个可以发起 select 的查询对象。

选择(Select),插入(Insert), 删除(Delete)

现在您已经有了 declared models,是时候从数据库中查询数据。我们将会使用 快速入门章节中定义的数据模型。

插入记录

在查询数据之前我们必须先插入数据。您的所有模型都应该有一个构造函数,如果您 忘记了,请确保加上一个。只有您自己使用这些构造函数而 SQLAlchemy 在内部不会使用它, 所以如何定义这些构造函数完全取决与您。

向数据库插入数据分为三个步骤:

  1. 创建 Python 对象

  2. 把它添加到会话

  3. 提交会话

这里的会话不是 Flask 的会话,而是 Flask-SQLAlchemy 的会话。它本质上是一个 数据库事务的加强版本。它是这样工作的:

<pre class="md-fences md-end-block" lang="shell" contenteditable="false" cid="n1133" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">>>> from yourapp import User

me = User('admin', 'admin@example.com')
db.session.add(me)
db.session.commit()</pre>

好吧,这不是很难吧。但是在您把对象添加到会话之前, SQLAlchemy 基本不考虑把它加到事务中。这是好事,因为您仍然可以放弃更改。比如想象 在一个页面上创建文章,但是您只想把文章传递给模板来预览渲染,而不是把它存进数据库。

调用 add() 函数会添加对象。它会发出一个 INSERT 语句给数据库,但是由于事务仍然没有提交,您不会立即得到返回的 ID 。如果您提交,您的用户会有一个 ID:

<pre class="md-fences md-end-block" lang="shell" contenteditable="false" cid="n1138" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">>>> me.id
1</pre>

删除记录

删除记录是十分类似的,使用 delete() 代替 add():

<pre class="md-fences md-end-block" lang="shell" contenteditable="false" cid="n1142" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">>>> db.session.delete(me)

db.session.commit()</pre>

查询记录

那么我们怎么从数据库中查询数据?为此,Flask-SQLAlchemy 在您的 Model 类上提供了 query 属性。当您访问它时,您会得到一个新的所有记录的查询对象。在使用 all()或者 first() 发起查询之前可以使用方法 filter() 来过滤记录。如果您想要用主键查询的话,也可以使用 get()

下面的查询假设数据库中有如下条目:

<figure class="md-table-fig" contenteditable="false" cid="n1148" mdtype="table" style="box-sizing: border-box; margin: -8px 0px 0px -8px; overflow-x: auto; max-width: calc(100% + 16px); padding: 8px;">

id username email
1 admin admin@example.com
2 peter peter@example.org
3 guest guest@example.com

</figure>

通过用户名查询用户:

<pre class="md-fences md-end-block" lang="shell" contenteditable="false" cid="n1167" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">>>> peter = User.query.filter_by(username='peter').first()

peter.id
1
peter.email
u'peter@example.org'</pre>

同上但是查询一个不存在的用户名返回 None:

<pre class="md-fences md-end-block" lang="shell" contenteditable="false" cid="n1170" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">>>> missing = User.query.filter_by(username='missing').first()

missing is None
True</pre>

使用更复杂的表达式查询一些用户:

<pre class="md-fences md-end-block" lang="shell" contenteditable="false" cid="n1173" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">>>> User.query.filter(User.email.endswith('@example.com')).all()
[<User u'admin'>, <User u'guest'>]</pre>

按某种规则对用户排序:

<pre class="md-fences md-end-block" lang="shell" contenteditable="false" cid="n1176" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">>>> User.query.order_by(User.username)
[<User u'admin'>, <User u'guest'>, <User u'peter'>]</pre>

限制返回用户的数量:

<pre class="md-fences md-end-block" lang="shell" contenteditable="false" cid="n1179" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">>>> User.query.limit(1).all()
[<User u'admin'>]</pre>

用主键查询用户:

<pre class="md-fences md-end-block" lang="shell" contenteditable="false" cid="n1182" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">>>> User.query.get(1)
<User u'admin'></pre>

在视图中查询

当您编写 Flask 视图函数,对于不存在的条目返回一个 404 错误是非常方便的。因为这是一个很常见的问题,Flask-SQLAlchemy 为了解决这个问题提供了一个帮助函数。可以使用 get_or_404() 来代替 get(),使用 first_or_404() 来代替 first()。这样会抛出一个 404 错误,而不是返回 None:

<pre class="md-fences md-end-block" lang="python" contenteditable="false" cid="n1186" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">@app.route('/user/<username>')
def show_user(username):
user = User.query.filter_by(username=username).first_or_404()
return render_template('show_user.html', user=user)</pre>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容