Dart学习笔记(19):数据库编程之MySQL

发表于2018-07-04 17:51 阅读(27)

现如今数据库的发展很快,犹如百花齐放
关系型数据库、NoSQL、图数据库、嵌入式数据库……层出不穷
虽然各自应用的场景不同
但无论如何,关系型数据库使用面最广,NoSQL人气最高

本文地址:http://www.cndartlang.com/821.html

Dart SDK中并没有原生的数据库驱动
但是无论是PostgreSQL还是SQLite还是MongoDB,都能找到对于的包
看了一下Pub和Github
不得不说,对数据库支持最好还是PostgreSQL和MongoDB

接下来的几篇帖子会对常用数据库作简单演示
这里说一下MySQL

MySQL应用广泛,资料也很全
MySQL驱动做得比较好的应该是sqljocky
但是Pub中READMI.md的一句话着实让人、、、汗颜
It isn’t finished, but should work for most normal use.
好吧!只能说,正式应用需谨慎

注:
sqljocky只能运行在虚拟机中
不能在浏览器端运行

pubspec.yaml

name: Note19
dependencies:
  sqljocky: "^0.14.1"

一般情况下我们并不会在程序中创建数据库
因为大多时候虚拟主机提供的都是固定的数据库
用户并没有创建的权限

另外,删除和新建数据库、表
可以用Drop、Create if not exists命令
如果要查询数据库或表是否存在,可以通过以下sql语句

//查询数据库TempDB是否存在
select count(*) from information_schema.tables where table_schema='tempdb';

//查询数据库TempDB中是否存在Users表
select count(*) from information_schema.tables where table_schema='tempdb' and table_name='users';

main.dart

import 'package:sqljocky/sqljocky.dart';
import 'package:sqljocky/utils.dart';

main() async {
  /**
   * 连接服务器,host默认'localhost',port默认3306
   * 密码为空时,不设置password,数据库名称db可以省略
   * 如果权限足够,连接服务器可以新建数据库
   */
  print("Opening connection");
  var pool = new ConnectionPool(host: 'localhost', port:3306, user:'root');
  await pool.query('drop database if exists TempDB');
  await pool.query('create database TempDB');

  /**
   * 新建连接的时候,之前的连接资源并不会自动释放
   * 需要调用closeConnectionsWhenNotInUse
   * 或closeConnectionsNow
   * 如果程序运行结束的时候,进程并没有退出
   * 说明资源未及时释放
   */
  pool.closeConnectionsNow();

  /**
   * 新连接刚建的数据库
   * 赋值给之前的pool变量
   */
  pool = new ConnectionPool(
    host: 'localhost', port: 3306, user: 'root', db: 'TempDB');

  /**
   * 除了ConnectionPool.query
   * 在'package:sqljocky/utils.dart'中
   * utils提供了两个实用的类用来删除表和执行多条sql语句
   */
  print("Drop tables");
  await new TableDropper(pool, ['users']).dropTables();

  /**
   * 也许是出于安全考虑,为了预防SQL攻击
   * sqljocky并不支持单个字符串执行多条分号分隔的sql语句
   * 如:"'insert into ...; insert into ..."
   * 有可能你习惯拼接sql语句,但是
   * 程序员不应该执行“删除地球”这样的SQL语句
   * 而是写“删除一个行星”,然后将“地球”当作参数传入
   * 也就是预处理
   * 下面的代码仅仅是演示QueryRunner(ConnectionPool, List)的用法
   * 适用于初始化数据库
   */
  print("Create tables");
  await new QueryRunner(pool, [
    'CREATE TABLE users ('
        'name varchar(100) NOT NULL,'
        'email varchar(100) NOT NULL)',
    'INSERT INTO users(name, email) VALUES ("Wang","wang@qq.com")',
    'INSERT INTO users(name, email) VALUES ("Li","li@163.com")'
  ]).executeQueries();

  /**
   * 预处理
   * 如果仅执行一次sql语句,可使用prepareExecute(sql, list)
   * 如果需要执行多次sql语句,可使用executeMulti函数
   * 再通过executeMulti(List<List>)传入参数
   * 如果sql语句有返回值,可遍历Future<List<Results>>
   */
  print('Prepare insert multiLine sql');
  var preSql = await pool.prepare(
      "insert into users(name, email) values(?, ?)");
  await preSql.executeMulti([
    ['Chen', 'chen@sina.com'],
    ['Yu', 'yu@gmail.com']
  ]);

  /**
   * ConnectionPool也提供了执行sql语句的函数
   * 如果sql语句有返回值,可遍历Future<Results>
   */
  print('Select users');
  var results = await pool.query('select * from users');
  results.forEach((row) {
    print('Name: ${row[0]}, email: ${row[1]}');
  });

  /**
   * 事务处理
   * 下面的代码综合了事务处理和预处理
   * 代码风格上,并没有用之前的await关键字
   * 而是在then中注册回调函数,浓浓的Dart味道
   * 这样省了不少临时变量的声明过程
   * 
   * 需要说明的是
   * sqljocky中不能通过query('start transaction')开启事务
   * 同时,事务处理会保持连接,直到调用commit或者rollback
   * 在提交或回滚事务的之后,不能再查询数据,否则会抛出错误
   * 
   * commit和rollback并不会直接释放资源 
   * 需要在then函数中注册回调函数,调用close函数
   * 否则,会进入阻塞状态
   */
  print('Transaction start');
  pool.startTransaction().then((Transaction trans) {
    trans.prepareExecute(
        'update users set email=? where name=?',
        ['yu@sohu.com', 'Yu']).then((_) {
      trans.commit().then((_) {
        print("Transaction end");
        pool.closeConnectionsNow();
      });
    });
  });

  /**
   * 释放资源
   * 由于在上面事务处理的代码中,commit已经释放了资源
   * 因此本句代码可以省略
   */
  pool.closeConnectionsWhenNotInUse();
}

运行结果: