精华内容
下载资源
问答
  • sqlalchemy 数据库操作在Flask Mega-Tutorial系列的第四部分,我将告诉你如何使用数据库。本章的主题是重中之重!大多数应用都需要持久化存储数据,并高效地执行的增删查改的操作数据库为此而生。本章的GitHub链接...

    sqlalchemy 数据库操作

    在Flask Mega-Tutorial系列的第四部分,我将告诉你如何使用数据库。

    本章的主题是重中之重!大多数应用都需要持久化存储数据,并高效地执行的增删查改的操作,数据库为此而生。

    本章的GitHub链接为: Browse, Zip, Diff.

    Flask中的数据库

    Flask本身不支持数据库,相信你已经听说过了。 正如表单那样,这也是Flask有意为之。对使用的数据库插件自由选择,岂不是比被迫适应其中之一,更让人拥有主动权吗?

    绝大多数的数据库都提供了Python客户端包,它们之中的大部分都被封装成Flask插件以便更好地和Flask应用结合。数据库被划分为两大类,遵循关系模型的一类是关系数据库,另外的则是非关系数据库,简称NoSQL,表现在它们不支持流行的关系查询语言SQL(译者注:部分人也宣称NoSQL代表不仅仅只是SQL)。虽然两类数据库都是伟大的产品,但我认为关系数据库更适合具有结构化数据的应用程序,例如用户列表,用户动态等,而NoSQL数据库往往更适合非结构化数据。 本应用可以像大多数其他应用一样,使用任何一种类型的数据库来实现,但是出于上述原因,我将使用关系数据库。

    在第三章中,我向你展示了第一个Flask扩展,在本章中,我还要用到两个。 第一个是Flask-SQLAlchemy,这个插件为流行的SQLAlchemy包做了一层封装以便在Flask中调用更方便,类似SQLAlchemy这样的包叫做Object Relational Mapper,简称ORM。 ORM允许应用程序使用高级实体(如类,对象和方法)而不是表和SQL来管理数据库。 ORM的工作就是将高级操作转换成数据库命令。

    SQLAlchemy不只是某一款数据库软件的ORM,而是支持包含MySQL、PostgreSQL和SQLite在内的很多数据库软件。简直是太强大了,你可以在开发的时候使用简单易用且无需另起服务的SQLite,需要部署应用到生产服务器上时,则选用更健壮的MySQL或PostgreSQL服务,并且不需要修改应用代码(译者注:只需修改应用配置)。

    确认激活虚拟环境之后,利用如下命令来安装Flask-SQLAlchemy插件:

    (venv) $ pip install flask-sqlalchemy

    数据库迁移

    我所见过的绝大多数数据库教程都是关于如何创建和使用数据库的,却没有指出当需要对现有数据库更新或者添加表结构时,应当如何应对。 这是一项困难的工作,因为关系数据库是以结构化数据为中心的,所以当结构发生变化时,数据库中的已有数据需要被迁移到修改后的结构中。

    我将在本章中介绍的第二个插件是Flask-Migrate。 这个插件是Alembic的一个Flask封装,是SQLAlchemy的一个数据库迁移框架。 使用数据库迁移增加了启动数据库时候的一些工作,但这对将来的数据库结构稳健变更来说,是一个很小的代价。

    安装Flask-Migrate和安装你见过的其他插件的方式一样:

    (venv) $ pip install flask-migrate

    Flask-SQLAlchemy配置

    开发阶段,我会使用SQLite数据库,SQLite数据库是开发小型乃至中型应用最方便的选择,因为每个数据库都存储在磁盘上的单个文件中,并且不需要像MySQL和PostgreSQL那样运行数据库服务。

    让我们给配置文件添加两个新的配置项:

    import os

    basedir = os.path.abspath(os.path.dirname(__file__))

    class Config(object):

    # ...

    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \

    'sqlite:///' + os.path.join(basedir, 'app.db')

    SQLALCHEMY_TRACK_MODIFICATIONS = False

    Flask-SQLAlchemy插件从SQLALCHEMY_DATABASE_URI配置变量中获取应用的数据库的位置。 当回顾第三章可以发现,首先从环境变量获取配置变量,未获取到就使用默认值,这样做是一个好习惯。 本处,我从DATABASE_URL环境变量中获取数据库URL,如果没有定义,我将其配置为basedir变量表示的应用顶级目录下的一个名为app.db的文件路径。

    SQLALCHEMY_TRACK_MODIFICATIONS配置项用于设置数据发生变更之后是否发送信号给应用,我不需要这项功能,因此将其设置为False。

    数据库在应用的表现形式是一个数据库实例,数据库迁移引擎同样如此。它们将会在应用实例化之后进行实例化和注册操作。app/__init__.py文件变更如下:

    from flask import Flask

    from config import Config

    from flask_sqlalchemy import SQLAlchemy

    from flask_migrate import Migrate

    app = Flask(__name__)

    app.config.from_object(Config)

    db = SQLAlchemy(app)

    migrate = Migrate(app, db)

    from app import routes, models

    在这个初始化脚本中我更改了三处。首先,我添加了一个db对象来表示数据库。然后,我又添加了数据库迁移引擎migrate。这种注册Flask插件的模式希望你了然于胸,因为大多数Flask插件都是这样初始化的。最后,我在底部导入了一个名为models的模块,这个模块将会用来定义数据库结构。

    数据库模型

    定义数据库中一张表及其字段的类,通常叫做数据模型。ORM(SQLAlchemy)会将类的实例关联到数据库表中的数据行,并翻译相关操作。

    就让我们从用户模型开始吧,利用 WWW SQL Designer工具,我画了一张图来设计用户表的各个字段(译者注:实际表名为user):

    id字段通常存在于所有模型并用作主键。每个用户都会被数据库分配一个id值,并存储到这个字段中。大多数情况下,主键都是数据库自动赋值的,我只需要提供id字段作为主键即可。

    username,email和password_hash字段被定义为字符串(数据库术语中的VARCHAR),并指定其最大长度,以便数据库可以优化空间使用率。 username和email字段的用途不言而喻,password_hash字段值得提一下。 我想确保我正在构建的应用采用安全最佳实践,因此我不会将用户密码明文存储在数据库中。 明文存储密码的问题是,如果数据库被攻破,攻击者就会获得密码,这对用户隐私来说可能是毁灭性的。 如果使用哈希密码,这就大大提高了安全性。 这将是另一章的主题,所以现在不需分心。

    用户表构思完毕之后,我将其用代码实现,并存储到新建的模块app/models.py中,代码如下:

    from app import db

    class User(db.Model):

    id = db.Column(db.Integer, primary_key=True)

    username = db.Column(db.String(64), index=True, unique=True)

    email = db.Column(db.String(120), index=True, unique=True)

    password_hash = db.Column(db.String(128))

    def __repr__(self):

    return ''.format(self.username)

    上面创建的User类继承自db.Model,它是Flask-SQLAlchemy中所有模型的基类。 这个类将表的字段定义为类属性,字段被创建为db.Column类的实例,它传入字段类型以及其他可选参数,例如,可选参数中允许指示哪些字段是唯一的并且是可索引的,这对高效的数据检索十分重要。

    该类的__repr__方法用于在调试时打印用户实例。在下面的Python交互式会话中你可以看到__repr__()方法的运行情况:

    >>> from app.models import User

    >>> u = User(username='susan', email='susan@example.com')

    >>> u

    创建数据库迁移存储库

    上一节中创建的模型类定义了此应用程序的初始数据库结构(元数据)。 但随着应用的不断增长,很可能会新增、修改或删除数据库结构。 Alembic(Flask-Migrate使用的迁移框架)将以一种不需要重新创建数据库的方式进行数据库结构的变更。

    这是一个看起来相当艰巨的任务,为了实现它,Alembic维护一个数据库迁移存储库,它是一个存储迁移脚本的目录。 每当对数据库结构进行更改后,都需要向存储库中添加一个包含更改的详细信息的迁移脚本。 当应用这些迁移脚本到数据库时,它们将按照创建的顺序执行。

    Flask-Migrate通过flask命令暴露来它的子命令。 你已经看过flask run,这是一个Flask本身的子命令。 Flask-Migrate添加了flask db子命令来管理与数据库迁移相关的所有事情。 那么让我们通过运行flask db init来创建microblog的迁移存储库:

    (venv) $ flask db init

    Creating directory /home/miguel/microblog/migrations ... done

    Creating directory /home/miguel/microblog/migrations/versions ... done

    Generating /home/miguel/microblog/migrations/alembic.ini ... done

    Generating /home/miguel/microblog/migrations/env.py ... done

    Generating /home/miguel/microblog/migrations/README ... done

    Generating /home/miguel/microblog/migrations/script.py.mako ... done

    Please edit configuration/connection/logging settings in

    '/home/miguel/microblog/migrations/alembic.ini' before proceeding.

    请记住,flask命令依赖于FLASK_APP环境变量来知道Flask应用入口在哪里。 对于本应用,正如第一章,你需要设置FLASK_APP = microblog.py。

    运行迁移初始化命令之后,你会发现一个名为migrations的新目录。该目录中包含一个名为versions的子目录以及若干文件。从现在起,这些文件就是你项目的一部分了,应该添加到代码版本管理中去。

    第一次数据库迁移

    包含映射到User数据库模型的用户表的迁移存储库生成后,是时候创建第一次数据库迁移了。 有两种方法来创建数据库迁移:手动或自动。 要自动生成迁移,Alembic会将数据库模型定义的数据库模式与数据库中当前使用的实际数据库模式进行比较。 然后,使用必要的更改来填充迁移脚本,以使数据库模式与应用程序模型匹配。 当前情况是,由于之前没有数据库,自动迁移将把整个User模型添加到迁移脚本中。 flask db migrate子命令生成这些自动迁移:

    (venv) $ flask db migrate -m "users table"

    INFO [alembic.runtime.migration] Context impl SQLiteImpl.

    INFO [alembic.runtime.migration] Will assume non-transactional DDL.

    INFO [alembic.autogenerate.compare] Detected added table 'user'

    INFO [alembic.autogenerate.compare] Detected added index 'ix_user_email' on '['email']'

    INFO [alembic.autogenerate.compare] Detected added index 'ix_user_username' on '['username']'

    Generating /home/miguel/microblog/migrations/versions/e517276bb1c2_users_table.py ... done

    通过命令输出,你可以了解到Alembic在创建迁移的过程中执行了哪些逻辑。前两行是常规信息,通常可以忽略。 之后的输出表明检测到了一个用户表和两个索引。 然后它会告诉你迁移脚本的输出路径。 e517276bb1c2是自动生成的一个用于迁移的唯一标识(你运行的结果会有所不同)。 -m可选参数为迁移添加了一个简短的注释。

    生成的迁移脚本现在是你项目的一部分了,需要将其合并到源代码管理中。 如果你好奇,并检查了它的代码,就会发现它有两个函数叫upgrade()和downgrade()。 upgrade()函数应用迁移,downgrade()函数回滚迁移。 Alembic通过使用降级方法可以将数据库迁移到历史中的任何点,甚至迁移到较旧的版本。

    flask db migrate命令不会对数据库进行任何更改,只会生成迁移脚本。 要将更改应用到数据库,必须使用flask db upgrade命令。

    (venv) $ flask db upgrade

    INFO [alembic.runtime.migration] Context impl SQLiteImpl.

    INFO [alembic.runtime.migration] Will assume non-transactional DDL.

    INFO [alembic.runtime.migration] Running upgrade -> e517276bb1c2, users table

    因为本应用使用SQLite,所以upgrade命令检测到数据库不存在时,会创建它(在这个命令完成之后,你会注意到一个名为app.db的文件,即SQLite数据库)。 在使用类似MySQL和PostgreSQL的数据库服务时,必须在运行upgrade之前在数据库服务器上创建数据库。

    数据库升级和降级流程

    目前,本应用还处于初期阶段,但讨论一下未来的数据库迁移战略也无伤大雅。 假设你的开发计算机上存有应用的源代码,并且还将其部署到生产服务器上,运行应用并上线提供服务。

    而应用在下一个版本必须对模型进行更改,例如需要添加一个新表。 如果没有迁移机制,这将需要做许多工作。无论是在你的开发机器上,还是在你的服务器上,都需要弄清楚如何变更你的数据库结构才能完成这项任务。

    通过数据库迁移机制的支持,在你修改应用中的模型之后,将生成一个新的迁移脚本(flask db migrate),你可能会审查它以确保自动生成的正确性,然后将更改应用到你的开发数据库(flask db upgrade)。 测试无误后,将迁移脚本添加到源代码管理并提交。

    当准备将新版本的应用发布到生产服务器时,你只需要获取包含新增迁移脚本的更新版本的应用,然后运行flask db upgrade即可。 Alembic将检测到生产数据库未更新到最新版本,并运行在上一版本之后创建的所有新增迁移脚本。

    正如我前面提到的,flask db downgrade命令可以回滚上次的迁移。 虽然在生产系统上不太可能需要此选项,但在开发过程中可能会发现它非常有用。 你可能已经生成了一个迁移脚本并将其应用,只是发现所做的更改并不完全是你所需要的。 在这种情况下,可以降级数据库,删除迁移脚本,然后生成一个新的来替换它。

    数据库关系

    关系数据库擅长存储数据项之间的关系。 考虑用户发表动态的情况, 用户将在user表中有一个记录,并且这条用户动态将在post表中有一个记录。 标记谁写了一个给定的动态的最有效的方法是链接两个相关的记录。

    一旦建立了用户和动态之间的关系,数据库就可以在查询中展示它。最小的例子就是当你看一条用户动态的时候需要知道是谁写的。一个更复杂的查询是, 如果你好奇一个用户时,你可能想知道这个用户写的所有动态。 Flask-SQLAlchemy有助于实现这两种查询。

    让我们扩展数据库来存储用户动态,以查看实际中的关系。 这是一个新表post的设计(译者注:实际表名分别为user和post):

    post表将具有必须的id、用户动态的body和timestamp字段。 除了这些预期的字段之外,我还添加了一个user_id字段,将该用户动态链接到其作者。 你已经看到所有用户都有一个唯一的id主键, 将用户动态链接到其作者的方法是添加对用户id的引用,这正是user_id字段所在的位置。 这个user_id字段被称为外键。 上面的数据库图显示了外键作为该字段和它引用的表的id字段之间的链接。 这种关系被称为一对多,因为“一个”用户写了“多”条动态。

    修改后的app/models.py如下:

    from datetime import datetime

    from app import db

    class User(db.Model):

    id = db.Column(db.Integer, primary_key=True)

    username = db.Column(db.String(64), index=True, unique=True)

    email = db.Column(db.String(120), index=True, unique=True)

    password_hash = db.Column(db.String(128))

    posts = db.relationship('Post', backref='author', lazy='dynamic')

    def __repr__(self):

    return ''.format(self.username)

    class Post(db.Model):

    id = db.Column(db.Integer, primary_key=True)

    body = db.Column(db.String(140))

    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)

    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

    def __repr__(self):

    return ''.format(self.body)

    新的“Post”类表示用户发表的动态。 timestamp字段将被编入索引,如果你想按时间顺序检索用户动态,这将非常有用。 我还为其添加了一个default参数,并传入了datetime.utcnow函数。 当你将一个函数作为默认值传入后,SQLAlchemy会将该字段设置为调用该函数的值(请注意,在utcnow之后我没有包含(),所以我传递函数本身,而不是调用它的结果)。 通常,在服务应用中使用UTC日期和时间是推荐做法。 这可以确保你使用统一的时间戳,无论用户位于何处,这些时间戳会在显示时转换为用户的当地时间。

    user_id字段被初始化为user.id的外键,这意味着它引用了来自用户表的id值。本处的user是数据库表的名称,Flask-SQLAlchemy自动设置类名为小写来作为对应表的名称。 User类有一个新的posts字段,用db.relationship初始化。这不是实际的数据库字段,而是用户和其动态之间关系的高级视图,因此它不在数据库图表中。对于一对多关系,db.relationship字段通常在“一”的这边定义,并用作访问“多”的便捷方式。因此,如果我有一个用户实例u,表达式u.posts将运行一个数据库查询,返回该用户发表过的所有动态。 db.relationship的第一个参数表示代表关系“多”的类。 backref参数定义了代表“多”的类的实例反向调用“一”的时候的属性名称。这将会为用户动态添加一个属性post.author,调用它将返回给该用户动态的用户实例。 lazy参数定义了这种关系调用的数据库查询是如何执行的,这个我会在后面讨论。不要觉得这些细节没什么意思,本章的结尾将会给出对应的例子。

    一旦我变更了应用模型,就需要生成一个新的数据库迁移:

    (venv) $ flask db migrate -m "posts table"

    INFO [alembic.runtime.migration] Context impl SQLiteImpl.

    INFO [alembic.runtime.migration] Will assume non-transactional DDL.

    INFO [alembic.autogenerate.compare] Detected added table 'post'

    INFO [alembic.autogenerate.compare] Detected added index 'ix_post_timestamp' on '['timestamp']'

    Generating /home/miguel/microblog/migrations/versions/780739b227a7_posts_table.py ... done

    并将这个迁移应用到数据库:

    (venv) $ flask db upgrade

    INFO [alembic.runtime.migration] Context impl SQLiteImpl.

    INFO [alembic.runtime.migration] Will assume non-transactional DDL.

    INFO [alembic.runtime.migration] Running upgrade e517276bb1c2 -> 780739b227a7, posts table

    如果你对项目使用了版本控制,记得将新的迁移脚本添加进去并提交。

    表演时刻

    经历了一个漫长的过程来定义数据库,我却还没向你展示它们如何使用。 由于应用还没有任何数据库逻辑,所以让我们在Python解释器中来使用以便熟悉它。 立即运行python命令来启动Python(在启动解释器之前,确保您的虚拟环境已被激活)。

    进入Python交互式环境后,导入数据库实例和模型:

    >>> from app import db

    >>> from app.models import User, Post

    开始阶段,创建一个新用户:

    >>> u = User(username='john', email='john@example.com')

    >>> db.session.add(u)

    >>> db.session.commit()

    对数据库的更改是在会话的上下文中完成的,你可以通过db.session进行访问验证。 允许在会话中累积多个更改,一旦所有更改都被注册,你可以发出一个指令db.session.commit()来以原子方式写入所有更改。 如果在会话执行的任何时候出现错误,调用db.session.rollback()会中止会话并删除存储在其中的所有更改。 要记住的重要一点是,只有在调用db.session.commit()时才会将更改写入数据库。 会话可以保证数据库永远不会处于不一致的状态。

    添加另一个用户:

    >>> u = User(username='susan', email='susan@example.com')

    >>> db.session.add(u)

    >>> db.session.commit()

    数据库执行返回所有用户的查询:

    >>> users = User.query.all()

    >>> users

    [, ]

    >>> for u in users:

    ... print(u.id, u.username)

    ...

    1 john

    2 susan

    所有模型都有一个query属性,它是运行数据库查询的入口。 最基本的查询就是返回该类的所有元素,它被适当地命名为all()。 请注意,添加这些用户时,它们的id字段依次自动设置为1和2。

    另外一种查询方式是,如果你知道用户的id,可以用以下方式直接获取用户实例:

    >>> u = User.query.get(1)

    >>> u

    现在添加一条用户动态:

    >>> u = User.query.get(1)

    >>> p = Post(body='my first post!', author=u)

    >>> db.session.add(p)

    >>> db.session.commit()

    我不需要为timestamp字段设置一个值,因为这个字段有一个默认值,你可以在模型定义中看到。 那么user_id字段呢? 回想一下,我在User类中创建的db.relationship为用户添加了posts属性,并为用户动态添加了author属性。 我使用author虚拟字段来调用其作者,而不必通过用户ID来处理。 SQLAlchemy在这方面非常出色,因为它提供了对关系和外键的高级抽象。

    为了完成演示,让我们看看另外的数据库查询案例:

    >>> # get all posts written by a user

    >>> u = User.query.get(1)

    >>> u

    >>> posts = u.posts.all()

    >>> posts

    []

    >>> # same, but with a user that has no posts

    >>> u = User.query.get(2)

    >>> u

    >>> u.posts.all()

    []

    >>> # print post author and body for all posts

    >>> posts = Post.query.all()

    >>> for p in posts:

    ... print(p.id, p.author.username, p.body)

    ...

    1 john my first post!

    # get all users in reverse alphabetical order

    >>> User.query.order_by(User.username.desc()).all()

    [, ]

    Flask-SQLAlchemy文档是学习其对应操作的最好去处。

    学完本节内容,我们需要清除这些测试用户和用户动态,以便保持数据整洁和为下一章做好准备:

    >>> users = User.query.all()

    >>> for u in users:

    ... db.session.delete(u)

    ...

    >>> posts = Post.query.all()

    >>> for p in posts:

    ... db.session.delete(p)

    ...

    >>> db.session.commit()

    Shell上下文

    还记得上一节的启动Python解释器之后你做过什么吗?第一件事是运行两条导入语句:

    >>> from app import db

    >>> from app.models import User, Post

    开发应用时,你经常会在Python shell中测试,所以每次重复上面的导入都会变得枯燥乏味。 flask shell命令是flask命令集中的另一个非常有用的工具。 shell命令是Flask在继run之后的实现第二个“核心”命令。 这个命令的目的是在应用的上下文中启动一个Python解释器。 这意味着什么? 看下面的例子:

    (venv) $ python

    >>> app

    Traceback (most recent call last):

    File "", line 1, in

    NameError: name 'app' is not defined

    >>>

    (venv) $ flask shell

    >>> app

    使用常规的解释器会话时,除非明确地被导入,否则app对象是未知的,但是当使用flask shell时,该命令预先导入应用实例。 flask shell的绝妙之处不在于它预先导入了app,而是你可以配置一个“shell上下文”,也就是可以预先导入一份对象列表。

    在microblog.py中实现一个函数,它通过添加数据库实例和模型来创建了一个shell上下文环境:

    from app import app, db

    from app.models import User, Post

    @app.shell_context_processor

    def make_shell_context():

    return {'db': db, 'User': User, 'Post': Post}

    app.shell_context_processor装饰器将该函数注册为一个shell上下文函数。 当flask shell命令运行时,它会调用这个函数并在shell会话中注册它返回的项目。 函数返回一个字典而不是一个列表,原因是对于每个项目,你必须通过字典的键提供一个名称以便在shell中被调用。

    在添加shell上下文处理器函数后,你无需导入就可以使用数据库实例:

    (venv) $ flask shell

    >>> db

    >>> User

    >>> Post

    如果运行以上的db, User, Post命令,报 NameError异常,说明 make_shell_context() 没有被Flask注册。最有可能的原因是你的环境变量中没有设定 FLASK_APP=microblog.py。此时你可以回到第一章复习一下如何设置FLASK_APP环境变量。如果你经常忘记在新开终端时设置该环境变量,可以如第一章末尾处那样,在项目的根目录添加一个名为 .flaskenv 的文件,并将环境变量设置在里面。

    喜欢 (0)or分享 (0)

    展开全文
  • LitePal是一款开源的Android数据库框架,采用对象关系映射(ORM)模式,将常用的数据库功能进行封装,不使用SQL语句就可以完成创建表以及表单的CRUD操作,并且很轻量级,几乎零配置。(它将开发中常用的一些数据库...

    LitePal是一款开源的Android数据库框架,采用对象关系映射(ORM)模式,将常用的数据库功能进行封装,不使用SQL语句就可以完成创建表以及表单的CRUD操作,并且很轻量级,几乎零配置。(它将开发中常用的一些数据库功能进行了封装,因此可以在不使用SQL语句的情况下实现对数据库的CRUD操作。)

    依然,以MPD数据解析为例,后面可以发现之前建立的Representation正好是LitePal所需要的。

    使用LitePal的第一步就是在app/vbuild.gradle文件中添加远程包依赖:

    compile 'org.litepal.android:core:1.4.1'

    之后在app/src/main目录下新assets文件夹,并添加litepal.xml文件(包含数据库名、版本号、表单映射对象class的路径)

    <?xml version="1.0" encoding="UTF-8" ?>
    <litepal>
        <dbname value= "mpdfile"></dbname>
        <version value = "1"></version>
        <list>
        <mapping class="com.example.litepal.Representation"></mapping>
        </list>
    </litepal>

    编写相应的关系映射对象class(本质是一个简单的JavaBean,定义为Representation.java)

    package com.example.litepal;
    
    import org.litepal.crud.DataSupport;
    
    
    /**
     * @Author Fupenzi on 2019/1/7.
     * @Blog https://blog.csdn.net/qq_34041083
     */
    
    public class Representation extends DataSupport {
        //由litepal管理表单不需要继承DataSupport,但是要对表单进行CRUD操作,必须继承
        private int id;
        private String item;
        private  int width;
        private int height;
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public void setItem(String item) { this.item = item; }
    
        public void setHeight(int height) {
            this.height = height;
        }
    
        public void setWidth(int width) {
            this.width = width;
        }
    
        public String getItem() { return item; }
    
        public int getHeight() {
            return height;
        }
    
        public int getWidth() {
            return width;
        }
    }
    

    在Manifest清单文件中配置 LitePalApplication,此处意味着MainActivity的继承父类不再为默认

    <application
            android:name="org.litepal.LitePalApplication"
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            ……

    重新编写MainActivity.java

    package com.example.litepal;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    
    import org.litepal.LitePal;
    import org.xmlpull.v1.XmlPullParser;
    import org.xmlpull.v1.XmlPullParserFactory;
    
    import okhttp3.OkHttpClient;
    import okhttp3.Request;
    import okhttp3.Response;
    
    import java.io.StringReader;
    
    
    public class MainActivity extends AppCompatActivity{
    
        TextView responseText;//声明一个回应文本显示对象
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Button sendRequest = (Button) findViewById(R.id.send_request);
            responseText = (TextView) findViewById(R.id.response_text);
            sendRequest.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    sendRequestWithOkHttp();
                    LitePal.getDatabase();//创建数据库
                }
            });
        }
    
    
    
        public void sendRequestWithOkHttp() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        OkHttpClient client = new OkHttpClient();
                        Request request = new Request.Builder()
                                // 指定访问的服务器地址是电脑本机
                                .url("https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd")//MPD请求地址
                                .build();
                        Response response = client.newCall(request).execute();//发送请求并获取服务端返回数据
                        String responseData = response.body().string();//提取返回数据中请求的文件,并转化为字符串
                        parseXMLWithPull(responseData); //对请求的文件进行解析
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    
        private void parseXMLWithPull(String xmlData) { //MPD解析方法
    
            //Representation rep = new Representation();//实例化一个Representation对象
            String item = null, width = null,height = null;
            String line = "";
    
            try {
                XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); //获得一个XMLPULL工厂类的实例
                XmlPullParser xmlPullParser = factory.newPullParser(); //获得一个XML解析器的实例
                xmlPullParser.setInput(new StringReader(xmlData));
                int eventType = xmlPullParser.getEventType();
    
                while (eventType != XmlPullParser.END_DOCUMENT) {
                    String nodeName = xmlPullParser.getName();
    
                    switch (eventType) {// 通过判断事件类型来选择执行不同的代码
                        // 开始解析某个结点
                        case XmlPullParser.START_TAG: {
                            if ("Representation".equals(nodeName)) {//由<Representation开始解析
                                item = xmlPullParser.getAttributeValue(null, "id");
                                width = xmlPullParser.getAttributeValue(null, "width");
                                height = xmlPullParser.getAttributeValue(null, "height");
    
                            }
                            break;
                        }
                        // 完成解析某个结点
                        case XmlPullParser.END_TAG: {
                            if ("Representation".equals(nodeName)) {     //由Representation--/>结束解析
                                Log.d("Representation", "id is " + item +"; width is "
                                        + width+"; height is " + height+";");
                                //在调试窗口logcat打印出属性值
                                line = line +"ID: "+ item +";  WIDTH: "+ width +";  HEIGHT: "+ height+";\n";
                                //将已经遍历的属性值连接成一个字符串,并注意换行
    
                                Representation rep = new Representation();
                                //在程序块中初始化对象,称为局部变量,每次循环重新创建并赋值,实现数据的添加
                                rep.setItem(item);
                                try{
                                    rep.setWidth(Integer.parseInt(width));
                                    //对getAttributeValue()方法得到的String进行强制类型转换,Integer.parseInt()得到int类型
                                    //必须使用异常捕捉函数,不然无法正常运行!
                                    rep.setHeight(Integer.parseInt(height));
                                }catch(Exception e){
                                    e.printStackTrace();
                                }
                                rep.save();
                            }
                            break;
                        }
                        default:
                            break;
                    }
                    eventType = xmlPullParser.next();
                }
                showResponse(line);//将字符串对象传入showResponse()方法,打印在UI的TextView
                //          Save(line);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    
        private void showResponse(final String response) {//子线程中不允许进行UI操作,因此利用showResponse()方法切换到主线程更新
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    responseText.setText(response);//创建一个TextView显示传入字符串
                }
            });
        }
    }

    可以看到与之前使用文件存储和SQLite不一样,在Representation对象中添加了item属性代替之前的id属性,这是由于将id设置为String属性时,在adb中查看数据库创建语句:

    即首项id总是默认设为integer、主键和自增,因此将之前的id属性改为item属性。

    另外,由于每次使用save()方法之后,如果对象没有被释放,下次添加数据后知识在之前的数据上做了修改而是不是新增。因此,在对Representation对象进行实例化时选择在循环体内部,则完成一次数据添加即释放对象。

    在本次的Representation文件中,将width属性和height属性设置为int型,而XML解析的结果是String类型,所以在向数据库写入数据之前必须使用Integer.parseInt(s)方法,如果直接使用Integer.parseInt(s)方法,调试过程不会报错,但是在虚拟机上Run App时将无法创建数据库。查找资料,得知类型强制转换过程中可能会出现异常Exception,因此加入try{…}catch(){…}语句进行异常处理。

    在数据库创建成功后可以在FileExplore视窗查看,并使用adb shell进入设备的文件系统查勘表单具体内容

     

     

    展开全文
  • 项目进展到管理用户页面的设计了,对于用户管理,在数据库中是用两个表来进行管理,一个是记录用户详细信息的表A,另外一个是管理用户权限的表B。Struts中,网页表单与ActionForm一一对应的,但是在我最初设计显示...

     今天blogcn不好用,所以写在这里。

    项目进展到管理用户页面的设计了,对于用户管理,在数据库中是用两个表来进行管理,一个是记录用户详细信息的表A,另外一个是管理用户权限的表B。

    Struts中,网页表单与ActionForm一一对应的,但是在我最初设计显示用户详细信息的页面时,我希望能够同时也显示出所有的用户权限信息。这样导致了在显示和提交修改信息的时候比较复杂的关联在一起的后台操作。昨天基本上写完了。可是这样做是否弄复杂了呢?我想,如果页面设计的时候考虑到这个逻辑问题,而将详细用户信息与用户权限信息分开来管理,这样后台操作应该简单很多。修改后的流程应该是:点击用户,显示用户详细信息,并提供修改 和 显示权限 的两种操作。 如果只是修改用户详细信息,那么直接提交该表单,如果显示权限,那么提交到显示用户权限的Action上,然后返回显示用户的权限。显示用户权限的时候,提供修改、添加、删除权限的操作,提交,则单纯的处理权限操作。。。这样应该能简化很多后台的操作。

    可是,数据库表设计是属于模型层的,而client的UI(页面)设计是属于view层的,既然已经分层,就不应该因为其他层的结构设计而影响本层的结构设计。 但是我的这个例子又说明了,如果模型层结构设计如果和view层结构设计不一样,会大大加重control层的逻辑工作。那么是我对MVC模式理解不到位呢?还是因为项目中MVC层都是我一个人在做而想偷懒呢?搞不懂了。

     

    展开全文
  • 本文实例讲述了laravel框架学习记录之表单操作。分享给大家供大家参考,具体如下: 1、MVC数据流动 拿到一个laravel项目最基本的是弄清楚它的页面请求、数据流动是怎样进行的,比如当通过get请求index页面时,如何...
  • 上一篇blog介绍了django中静态资源的使用,下面,重点介绍下Django框架中的表单处理,数据库操作以及数据分页 1.表单处理

    上一篇blog介绍了django中静态资源的使用,下面,重点介绍下Django框架中的表单处理,数据库操作以及数据分页

    1.表单处理

    • 写好Html页面(我给你写好了)
      index.html
    <!DOCTYPE html>
    <html lang="cn">
    <head>
        <meta charset="UTF-8">
        <title>myAPP</title>
        <link rel="stylesheet" type="text/css" href="/static/css/index.css">
    </head>
    <body>
    
        <form method="post" action="/myApp/search/">
            <input type="text" name="myText">
            <input type="submit" value="submit">
        </form>
        <p>{{myText}}</p>  # python变量的写法,{{python variety}}
    
    </body>
    </html>
    

    search.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>MyProject</title>
    </head>
    <body>
        <h1>{{result}}</h1>
    
    </body>
    </html>
    
    • 修改views.py文件

    因为上面表单的提交路由是/myApp/search/,so,应该在views.py文件中新增search函数 ,django框架下,所有对于web页面的访问,都是通过views.py下的一个个函数间接实现的

    def search(request):
    	text = request.POST.get("myText")
    	answer = {"result":text}
    	return render(request, 'search.html', answer)
    
    • 添加路由设置,修改“myProject/myApp/urls.py“文件
    from django.conf.urls import include,url
    from . import views
    
    urlpatterns = [
    	url(r'index/', views.index),
    	url(r'search/', views.search),  # 新增
    ]
    

    注:由于是用POST方式提交表单(GET方式则略过),需要修改settings.py文件,把下图红框选中的部分注释掉

    在这里插入图片描述

    Now,启动项目,去浏览器测试下~~

    2.数据库操作(mysql与django的数据交互)

    - 首先,本地得有mysql数据库(没有,请先安装),启动mysql,在mysql中建立一个数据库“myDB”( 建议,建立数据库时字符集选择utf8,排序规则选择utf8_general_ci,因为有中文字符时,不这么建立会出现乱码以及数据库读写错误)
    - 修改配置文件settngs.py
    DATABASES = {
         'default': {
           'ENGINE': 'django.db.backends.mysql',
           'NAME': 'myDB',
           'USER': '', # 用户名
           'PASSWORD': '', # 密码
           'HOST': 'localhost',
           'PORT': 3306, # mysql默认端口是3306,如果你改了mysql的端口配置,请修改
       }
    }
    
    - 修改models.py文件
    from django.db import models
    
    # Create your models here.
    
    class myTable(models.Model):
    
       index = models.AutoField(primary_key=True)  # 自增列,在Django中不写也可自动生成
       word = models.CharField(max_length=32)
       comment = models.CharField(max_length=32, null=True)
    

    这里我解释下:models文件中的一个class对应数据库中的一张表,例如,上面代码中的‘myTable’;class中的变量,对应表中的字段,例如,上面的‘index,word,comment’,其中:

    • index是一个自增列,设置为主键
    • word是一个字符串列,最大长度设置为32
    • comment是一个字符串列,最大长度为32,其值可为空
    -在控制台,进入项目根目录下:

    运行 python manage.py makemigrations

    如果出现下面的错误 点击这里

    在这里插入图片描述

    若正常,则出现如下提示:

    在这里插入图片描述

    运行 python manage.py migrate,正常出现如下提示:

    在这里插入图片描述

    Now,去数据库看看,在myDB下应该多了一堆表,其中就有myTable,当然,也可以在数据库中建好表之后再执行上面两条命令

    -对表“myTable”进行增删改查

    结合前面的表单提交,现在从index页面提交一个数据存入’myTable’中
    • 修改views.py文件:
    from django.shortcuts import render
    from myApp.models import myTable  # 新增
    # Create your views here
    
    def index(request):
    	return render(request, 'index.html')
    	
    def search(request):
    	text = request.POST.get("myText")
    	seg = text.split(":")   # 新增
    	myTable.objects.create(word=seg[0], comment=seg[1]) # 新增
    	return render(request, 'index.html')  # 修改
    

    去浏览器测试下,输入格式为 word:comment,点击提交,你的表myTable里就多了一条数据,try~~,例如:

    在这里插入图片描述

    更多数据库操作,请看这里django数据库操作

    -分页

    当你有较多格式化数据需要在页面显示时,就需要用到分页了,在django中分页So easy

    • 修改你的应用下的urls.py文件
    from django.conf.urls import include,url
    from . import views
    
    urlpatterns = [
    	url(r'index/', views.index),
    	url(r'search/', views.search),
    	url(r'totalWords/(\d*)', views.totalWords), # 新增
    ]
    

    totalWords(具体叫什么自己定)指分页显示的页面,因此需要在views.py中新增一个totalWords函数:

    from django.core import paginator
    def totalWords(request, index):
    	# 获取所有的的数据,list是一个列表,包含有所有数据对应的实例对象
    	allWords = myTable.objects.all()
    
    	# 使用Paginator方法返回一个分页的对象
    	# 这个对象包括所有数据,分页的情况
    	pag = paginator.Paginator(allWords, 10)  # param:所有数据,每页的数据量
    
    	# 使用此判断语句是为了在用户在刚跳转时也能访问第一页
    	if index == '':
    		index = 1
    
    	# 返回指定(index)页的数据,用于呈现在指定页上
    	page = pag.page(index)
    
    	# 构造上下文,以便html文件中能调用对应页的数据
    	context = {
    		'page': page,
    	}
    	return render(request, 'totalWords.html', context)
    

    在index.html 文件中添加a标签:

    <a href="/myApp/totalWords/">totalWords</a>

    新建totalWords.html 文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>LUWR</title>
    </head>
    <body>
        <table>
            <tr>
                <td id="bt">word</td>
                <td id="bt">comment</td>
            </tr>
            {% for each in page %}
                <tr>
                    <td>{{ each.word }}</td>
                    <td>{{ each.comment }}</td>
                </tr>
            {% endfor %}
        </table>
    
    <hr>
    
        <div>
            {% for index in page.paginator.page_range %}
                {% if index == page.number %}
                <span>{{ index }}</span>
                {% else %}
                 <a href="/recorder/totalWords/{{index}}">{{ index }}</a>
                {% endif %}
            {% endfor %}
        </div>
    </body>
    
    </html>
    

    在浏览器中测试,应该能看到如下结果:

    在这里插入图片描述

    当然,现在数据库只有两条数据,当你有足够多(这里大于10条就可以)的数据,这里就分页显示了

    bingo!表单提交,数据库操作,分页你都学会了

    展开全文
  • 一、Flask_WTF表单和CSRF保护 安装语句: pip install flask-wtf CSRF保护 app = Flask(__name__) #设置app的config字典app的config字典用来存储框架、扩展和程序本身的配置变量。 app.config['vdsdshidfhuhiuidi' ...
  • 安装flask-sqlalchemy、pymysql模块pip install flask-sqlalchemy pymysql### Flask-SQLAlchemy的介绍1. ORM:Object ...3. ORM的好处:可以让我们操作数据库跟操作对象是一样的,非常方便。因为一个表就抽...
  • MVC_CRUD_Company 带有.Net框架和C#的MVC应用程序,用于在客户端数据库上进行CRUD操作,包括(创建,更新,删除,导出到文本文件,导入表单文本文件)
  • 注:此Demo是在SSM框架下完成的,数据库采用MySQL,关于ssm的相关知识,这里不做过多赘述。主要展示表单提交方式,暂不考虑代码健壮性。一、常见的form表单提交方式分析:常见的表单提交方式,在表单中添加一个...
  • .net 底层数据库传输程序 分为服务器端与客户端 此程序作用有: 1.解决直连数据库有相当大的安全隐串。...可以作为,程序提交表单,时时提醒功能。 4.兼容三层框架。三层框架程序几乎不需要改动就可以直接使用。
  • 来源:...1.代码生成器: [正反双向](单表、主表、明细表、树形表,快速开发利器)+快速表单构建器 freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面、建表sql脚本、处理类...
  • ssm框架下对数据库表的增查删改

    千次阅读 2016-07-08 17:25:33
    在ssm框架下对数据库表单的增查删改操作(分页查询) 配置ssm整合——创建java实体类——创建服务方法service接口——创建方法与参数的映射关系Mapper接口——创建实体类方法实现类Impl实现service接口中的方法——...
  • 1.代码生成器: [正反双向](单表、主表、明细表、树形表,快速开发利器)+快速表单构建器 freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面、建表sql脚本、处理类、service等完整模块 2.多数...
  • 上面的文字讲述了如何使用django的ORM连接数据库创建表,使用pycharm操作表等内容;下面我们来学习一下关于ORM对于表单的具体操作(增、删、改、查),尤其是查询API非常重要; 一、ORM对表单的增删改操作 1.如何...
  • js框架:jquery 系统大部分使用AJAX操作。大大提高了用户体验 功能描述: 1.支持N级菜单导航,菜单显示方式支持目前支持2种模式分别: 菜单(无限级),横向(2级) 2.表单验证,文本框高亮起来 3.可以动态分配...
  • flask插件之Flask-WTF表单先pip安装需要csrf保护创建表单创建用户模型数据库迁移实际操作顺序配置迁移过程 环境:python3.5,Flask1.0.3 查看官方文档:http://www.pythondoc.com/flask-wtf/form.html 先pip安装 ...
  • 1.代码生成器: [正反双向](单表、主表、明细表、树形表,快速开发利器)+快速表单构建器 freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面、建表sql脚本、处理类、service等完整模块 2.多数...
  • 上篇介绍了如何使用JDBC链接ORACLE数据库实现对数据库的增删改查,本例是使用框架SSH来对数据库的数据进行操作。 首先说框架,现在流行的框架很多,如Struts、Hibernate、Spring等,再加上各个公司自己编写的框架,...
  • Android数据库操作--greenDAO的入门使用

    千次阅读 2016-10-17 16:59:49
    目前android经常用的orm框架主要...greenDAO是一个可以快速将Java对象映射到SQLite数据库表单中的ORM解决方案。设计的主要目标:一个精简的库;性能最大化;内存开销最小化;易于使用的APIs;对Android进行高度优化。
  • Pylons 入门实例教程 – 数据库操作

    千次阅读 2010-11-22 18:37:00
    <br />前面两篇入门,讲述了 Pylons 大致开发的流程、表单以及文件上传,思路大致跟传统的开发类似。本篇简单讲述下在 Pylons 如何使用数据库。 本篇侧重点是使用 ORM 框架 SQLAlchemy。现在 Python ...
  • 模型层:表单操作

    2018-11-12 19:49:00
    MVC或者MTV框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因...
  • 在不到一个小时的时间里,您安装并配置了 CodeIgniter,创建了一个包含主页、将信息添加到数据库表单和 thank-you 页面的 Web 站点。 当然,要学习的东西还有很多。例如,您可自动加载所需的模型和任何帮助程序或...
  • 该文档是**敏捷开发框架手册, 1.业务数据库注册 2。自定义表单开发 。。。
  • -- 表单操作 --> ${pageContext.request.contextPath}/style/images/save.png"/> (-1);">${pageContext.request.contextPath}/style/images/goBack.png"/> ``` # web.xml ``` xmlns:xsi=...
  • SSM框架实现修改操作

    千次阅读 2018-09-15 14:49:21
    利用SSM框架在页面内的表单中实现编辑修改的主要步骤总结如下: 1.我们想要修改某个数据,首先应该知道我们需要获取想要数据在数据库中id的值,因为我们进行的删改查的等操作都是通过查找id的进行的。 2. ...
  • 目录 介绍 储存库和数据库 列表功能 ...项目结构与框架 服务——构建CRUD数据层 View组件——UI中的CRUD编辑和查看操作 UI组件——构建HTML / CSS控件 View组件——UI中的CRUD列表操作 逐步详细介

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 753
精华内容 301
关键字:

表单操作数据库框架