ZetCode-数据库教程-一-

ZetCode 数据库教程(一)

原文:ZetCode

协议:CC BY-NC-SA 4.0

MySQL 表达式

http://zetcode.com/databases/mysqltutorial/expressions/

在 MySQL 教程的这一部分中,我们将介绍表达式。

编程语言中的表达式是值,变量,运算符和函数的组合,这些值,变量,运算符和函数根据特定的优先级规则和特定编程语言的关联规则进行解释(求值),然后计算并生成(返回) 有状态的环境)的另一个值。 据说该表达式可以计算出该值。 (维基百科)

字面值

字面值是某种常量。 字面值可以是字符串,数字,十六进制值,布尔值和NULL

mysql> SELECT 3, 'Wolf', 34.5, 0x34, 0+b'10111';
+---+------+------+------+------------+
| 3 | Wolf | 34.5 | 0x34 | 0+b'10111' |
+---+------+------+------+------------+
| 3 | Wolf | 34.5 | 4    |         23 |
+---+------+------+------+------------+

在这里,我们返回五个字面值。 即整数,字符串浮点数,十六进制数和二进制值。 十六进制值的前面是0x,这是编程语言中的标准。 二进制值前面带有b字符,并用单引号引起来。 为了显示可打印的值,我们在二进制符号上添加零。

mysql> SELECT NULL, \N;
+------+------+
| NULL | NULL |
+------+------+
| NULL | NULL |
+------+------+

这是NULL值。 这是没有值的。 NULL的同义词是\N

mysql> SELECT TRUE, FALSE;
+------+-------+
| TRUE | FALSE |
+------+-------+
|    1 |     0 |
+------+-------+

MySQL 还可以识别布尔值TRUEFALSE。 它们可以用大写字母书写。

mysql> SELECT '2011-01-11', '23:33:01', '98/11/31/ 14:22:20';
+------------+----------+--------------------+
| 2011-01-11 | 23:33:01 | 98/11/31/ 14:22:20 |
+------------+----------+--------------------+
| 2011-01-11 | 23:33:01 | 98/11/31/ 14:22:20 |
+------------+----------+--------------------+

MySQL 数据库支持各种日期和时间字面值。

变量

变量是与值关联的符号名称。 该值可能会随着时间而改变。 MySQL 中的变量以@字符开头。

mysql> SET @name = 'Jane';

mysql> SELECT @name;
+-------+
| @name |
+-------+
| Jane  |
+-------+

我们设置一个变量,然后显示其内容。

运算符

运算符用于构建表达式。 SQL 运算符与数学运算符非常相似。 有两种运算符。 二元和一元。 二元运算符使用两个操作数,一元运算符使用一个。 一个运算符可以有一个或两个操作数。 操作数是运算符的输入(参数)之一。

我们有几种类型的运算符:

  • 算术运算符
  • 布尔运算符
  • 关系运算符
  • 按位运算符
  • 其他运算符

一元运算符

我们将展示一些一元运算符。

mysql> SELECT +3, 3;
+---+---+
| 3 | 3 |
+---+---+
| 3 | 3 |
+---+---+

+是无操作的。 它什么也没做。

mysql> SELECT -(3-44);
+---------+
| -(3-44) |
+---------+
|      41 |
+---------+

-一元运算符将正值更改为负值,反之亦然。

mysql> SELECT NOT (3>9);
+-----------+
| NOT (3>9) |
+-----------+
|         1 |
+-----------+

NOT运算符取反一个值。 3>9比较的结果为false,而否定运算符将其取为true

算术运算符

常见的算术运算符是:乘法,除法,整数除法,加法,减法和模。

mysql> SELECT 3 + 4 - 5;
+-----------+
| 3 + 4 - 5 |
+-----------+
|         2 |
+-----------+

加减法运算符。

mysql> SELECT 3*3/9;
+--------+
| 3*3/9  |
+--------+
| 1.0000 |
+--------+

这些是我们从数学上知道的乘法和除法运算符。

mysql> SELECT 9/2, 9 DIV 2;
+--------+---------+
| 9/2    | 9 DIV 2 |
+--------+---------+
| 4.5000 |       4 |
+--------+---------+

上面的 SQL 语句显示了除法运算符和整数除法运算符之间的区别。 第一个返回浮点数,第二个返回整数。

mysql> SELECT 11 % 3;
+--------+
| 11 % 3 |
+--------+
|      2 |
+--------+

%运算符称为模运算符。 它找到一个数除以另一个的余数。 11 % 3的 11 模 3 为 2,因为 3 乘以 3 变成 11,余数为 2。

逻辑运算符

使用逻辑运算符,我们执行布尔运算。 MySQL 理解以下逻辑运算符:ANDORNOTXOR。 逻辑运算符返回TRUEFALSE。 在 MySQL 中,1 为true,0 为false

如果两个操作数均为true,则AND运算符的计算结果为true

mysql> SELECT FALSE AND FALSE, FALSE AND TRUE,
    -> TRUE AND FALSE, TRUE AND TRUE;
+-----------------+----------------+----------------+---------------+
| FALSE AND FALSE | FALSE AND TRUE | TRUE AND FALSE | TRUE AND TRUE |
+-----------------+----------------+----------------+---------------+
|               0 |              0 |              0 |             1 |
+-----------------+----------------+----------------+---------------+

前三个操作求值为false,最后一个求值为true

mysql> SELECT 3=3 AND 4=4;
+-------------+
| 3=3 AND 4=4 |
+-------------+
|           1 |
+-------------+

两个操作数都为true,因此结果为true(1)。

如果至少一个操作数为true,则OR运算符的计算结果为true

mysql> SELECT FALSE OR FALSE, FALSE OR TRUE, 
    -> TRUE OR FALSE, TRUE OR TRUE;
+----------------+---------------+---------------+--------------+
| FALSE OR FALSE | FALSE OR TRUE | TRUE OR FALSE | TRUE OR TRUE |
+----------------+---------------+---------------+--------------+
|              0 |             1 |             1 |            1 |
+----------------+---------------+---------------+--------------+

第一个操作求值为 false,其他操作求值为 true。

如果恰好其中一个操作数为 true,则XOR运算符的计算结果为 true。

mysql> SELECT FALSE XOR FALSE, FALSE XOR TRUE,
    -> TRUE XOR FALSE, TRUE XOR TRUE;
+-----------------+----------------+----------------+---------------+
| FALSE XOR FALSE | FALSE XOR TRUE | TRUE XOR FALSE | TRUE XOR TRUE |
+-----------------+----------------+----------------+---------------+
|               0 |              1 |              1 |             0 |
+-----------------+----------------+----------------+---------------+

其中两个操作为true

NOT运算符是反运算符。 它使真假成为假。

mysql> SELECT NOT TRUE, NOT FALSE;
+----------+-----------+
| NOT TRUE | NOT FALSE |
+----------+-----------+
|        0 |         1 |
+----------+-----------+

mysql> SELECT NOT (3=3);
+-----------+
| NOT (3=3) |
+-----------+
|         0 |
+-----------+

关系运算符

关系运算符用于比较值。 这些运算符总是产生布尔值。

mysql> SELECT 3*3=9, 9=9;
+-------+-----+
| 3*3=9 | 9=9 |
+-------+-----+
|     1 |   1 |
+-------+-----+

=是相等运算符。

mysql> SELECT 3 < 4, 3 <> 5, 4 <= 4, 5 != 5;
+-------+--------+--------+--------+
| 3 < 4 | 3 <> 5 | 4 <= 4 | 5 != 5 |
+-------+--------+--------+--------+
|     1 |      1 |      1 |      0 |
+-------+--------+--------+--------+

关系运算符的用法从数学上是已知的。

按位运算符

小数对人类是自然的。 二进制数是计算机固有的。 二进制,八进制,十进制或十六进制符号仅是相同数字的符号。 按位运算符使用二进制数的位。 我们有二进制逻辑运算符和移位运算符。

按位,运算符在两个数字之间进行逐位比较。 仅当操作数中的两个对应位均为 1 时,位位置的结果才为 1。


    00110
  & 00011
  = 00010

第一个数字是二进制符号 6,第二个数字是 3,结果是 2。

mysql> SELECT 6 & 3, 3 & 6;
+-------+-------+
| 6 & 3 | 3 & 6 |
+-------+-------+
|     2 |     2 |
+-------+-------+

按位或运算符在两个数字之间进行逐位比较。 如果操作数中的任何对应位为 1,则位位置的结果为 1。


     00110
  |  00011
   = 00111

结果为00110或十进制 7。

mysql> SELECT 6 | 3, 3 | 6;
+-------+-------+
| 6 | 3 | 3 | 6 |
+-------+-------+
|     7 |     7 |
+-------+-------+

按位移位运算符向右或向左移位。

number << n : multiply number 2 to the nth power
number >> n : divide number by 2 to the nth power

这些运算符也称为算术移位。

     00110
 >>  00001
   = 00011

我们将数字 6 的每个位向右移动。 等于将 6 除以 2。结果为00011或十进制 3。

mysql> SELECT 6 >> 1;
+--------+
| 6 >> 1 |
+--------+
|      3 |
+--------+

     00110
  << 00001
   = 01100

我们将数字 6 的每个位向左移动。 等于将数字 6 乘以 2。结果为01100或十进制 12。

mysql> SELECT 6 << 1;
+--------+
| 6 << 1 |
+--------+
|     12 |
+--------+

其他运算符

还剩下一些其他运算符。 这些包括ISINLIKEREGEXPBETWEEN

IS运算符测试操作数是否为布尔值。

mysql> SET @running = FALSE;
mysql> SELECT @running IS FALSE;
+-------------------+
| @running IS FALSE |
+-------------------+
|                 1 |
+-------------------+

我们将变量设置为布尔值false。 我们使用IS运算符检查变量是否为FALSE

在两种情况下,我们可以使用IN运算符。

mysql> SELECT 'Tom' IN ('Tom', 'Frank', 'Jane');
+-----------------------------------+
| 'Tom' IN ('Tom', 'Frank', 'Jane') |
+-----------------------------------+
|                                 1 |
+-----------------------------------+

在这里,我们检查IN运算符之后的字符串值'Tom'是否在名称列表中。 返回值是布尔值。

对于以下示例,我们概括了Cars表中的内容。

mysql> SELECT * FROM Cars;
+----+------------+--------+
| Id | Name       | Cost   |
+----+------------+--------+
|  1 | Audi       |  52642 |
|  2 | Mercedes   |  57127 |
|  3 | Skoda      |   9000 |
|  4 | Volvo      |  29000 |
|  5 | Bentley    | 350000 |
|  6 | Citroen    |  21000 |
|  7 | Hummer     |  41400 |
|  8 | Volkswagen |  21600 |
+----+------------+--------+

在第二种情况下,IN运算符允许您在WHERE子句中指定多个值。

mysql> SELECT * FROM Cars Where Name IN ('Audi', 'Hummer');
+----+--------+-------+
| Id | Name   | Cost  |
+----+--------+-------+
|  1 | Audi   | 52642 |
|  7 | Hummer | 41400 |
+----+--------+-------+

Cars表中,我们选择在IN运算符之后列出的汽车。

WHERE子句中使用LIKE运算符在列中搜索指定的模式。

mysql> SELECT * FROM Cars WHERE Name LIKE 'Vol%';
+----+------------+-------+
| Id | Name       | Cost  |
+----+------------+-------+
|  4 | Volvo      | 29000 |
|  8 | Volkswagen | 21600 |
+----+------------+-------+

在这里,我们选择名称以"Vol"开头的汽车。

mysql> SELECT * FROM Cars WHERE Name LIKE '____';
+----+------+-------+
| Id | Name | Cost  |
+----+------+-------+
|  1 | Audi | 52642 |
+----+------+-------+

在这里,我们选择正好有四个字符的汽车名称。 有四个下划线。

LIKE运算符仅提供简单的模式匹配。 REGEXP运算符功能更强大。 它提供与正则表达式匹配的模式。 RLIKEREGEXP的同义词。

mysql> SELECT * FROM Cars WHERE Name REGEXP 'e.$';
+----+------------+--------+
| Id | Name       | Cost   |
+----+------------+--------+
|  2 | Mercedes   |  57127 |
|  5 | Bentley    | 350000 |
|  6 | Citroen    |  21000 |
|  7 | Hummer     |  41400 |
|  8 | Volkswagen |  21600 |
+----+------------+--------+

在这里,我们有汽车,它们的最后一个字符是'e'。

mysql> SELECT * FROM Cars WHERE Name REGEXP '^.e.*e.$';
+----+----------+--------+
| Id | Name     | Cost   |
+----+----------+--------+
|  2 | Mercedes |  57127 |
|  5 | Bentley  | 350000 |
+----+----------+--------+

我们选择的汽车中,倒数第二个也是一个字符是"e"

BETWEEN运算符等效于一对比较。 a BETWEEN b AND c等效于a>=b AND a<=c

mysql> SELECT * FROM Cars WHERE Cost BETWEEN 20000 AND 55000;
+----+------------+-------+
| Id | Name       | Cost  |
+----+------------+-------+
|  1 | Audi       | 52642 |
|  4 | Volvo      | 29000 |
|  6 | Citroen    | 21000 |
|  7 | Hummer     | 41400 |
|  8 | Volkswagen | 21600 |
+----+------------+-------+

在此 SQL 语句中,我们选择了价格在 20000 至 55000 单位之间的汽车。

优先顺序

运算符优先级告诉我们首先求值哪个运算符。 优先级对于避免表达式中的歧义是必要的。

以下表达式 28 或 40 的结果是什么?

3 + 5 * 5

像数学中一样,乘法运算符的优先级高于加法运算符。 结果是 28。

(3 + 5) * 5

要更改求值顺序,可以使用方括号。 方括号内的表达式始终首先被求值。

mysql> SELECT 3+5*5, (3+5)*5;
+-------+---------+
| 3+5*5 | (3+5)*5 |
+-------+---------+
|    28 |      40 |
+-------+---------+

第一个表达式的计算结果为 28,因为乘法运算符的优先级高于加法运算符。 在第二个示例中,我们使用方括号更改了优先顺序。 因此第二个表达式的值为 40。

关联性

有时,优先级不能令人满意地确定表达式的结果。 还有另一个规则称为关联性。 运算符的关联性确定优先级相同的运算符的求值顺序。

9 / 3 * 3

此表达式的结果是 9 还是 1? 乘法,删除和模运算符从左到右关联。 因此,该表达式的计算方式为:(9 / 3) * 3,结果为 9。

mysql> SELECT 9 / 3 * 3;
+-----------+
| 9 / 3 * 3 |
+-----------+
|    9.0000 |
+-----------+

关联规则是从左到右。

mysql> SELECT 0 AND 0 OR 1;
+--------------+
| 0 AND 0 OR 1 |
+--------------+
|            1 |

关联规则再次从左到右。 如果从右到左,则结果为 0。

算术,布尔,关系和按位运算符都是从左到右关联的。

在 MySQL 教程的这一部分中,我们介绍了 MySQL 表达式。

SQLAlchemy 简介

原文: http://zetcode.com/db/sqlalchemy/intro/

这是 SQLAlchemy 教程。 它涵盖了它。它涵盖了 SQLAlchemy SQL 工具包和对象关系映射器的基础。 在本教程中,我们将使用 PostgreSQL,MySQL 和 SQLite 数据库。

SQLAlchemy

SQLAlchemy SQL 工具包和对象关系映射器是一组用于处理数据库和 Python 的综合工具。 它提供了一整套知名的企业级持久性模式,旨在实现高效和高性能的数据库访问。 SQLAlchemy 和 Django 的 ORM 是 Python 社区中使用最广泛的两个对象关系映射工具。

SQLAlchemy 具有三种处理数据库数据的方式:

  • 原始 SQL
  • SQL 表达式语言
  • ORM

与许多其他 ORM(对象关系映射)工具不同,SQLAlchemy 允许使用纯 SQL 语句。 我们总是可以求助于原始 SQL。 SQL 表达式 API 允许您使用 Python 对象和运算符来构建 SQL 查询。 表达式 API 是纯 SQL 语句的抽象,处理数据库之间的各种实现差异。 SQLAlchemy 对象关系映射器提供了一种将用户定义的 Python 类与数据库表相关联的方法。 SQLAlchemy ORM 基于 SQL 表达式语言。

SQLAlchemy 组件

SQLAlchemy 由几个组件组成。 引擎是任何 SQLAlchemy 应用的起点。 该引擎是数据库及其 API 的抽象。 它与连接池和方言组件一起使用,以将 SQL 语句从 SQLAlchemy 传递到数据库。 使用create_engine()函数创建引擎。 它可以用于直接与数据库进行交互,也可以传递给会话对象以使用对象关系映射器。

方言是 SQLAlchemy 用于与各种类型的 DBAPI 实现和数据库进行通信的系统。 所有方言都要求安装适当的 DBAPI 驱动程序。 SQLAlchemy 具有许多流行数据库系统的方言,包括 Firebird,Informix,Microsoft SQL Server,MySQL,Oracle,PostgreSQL,SQLite 或 Sybase。 从提供的连接字符串创建方言。

元数据由描述表和其他模式级对象的 Python 对象组成。 可以通过使用诸如表,列或外键之类的结构明确命名各种组件及其属性来表示数据库元数据。 SQLAlchemy 可以使用称为反射的过程轻松生成元数据。

在 ORM 内部,持久性操作的主要接口是会话。 会话建立与数据库的所有对话,并代表我们在其生命周期中已加载或与其关联的所有对象的容器。 它提供了获取查询对象的入口点,该对象使用会话对象的当前数据库连接将查询发送到数据库,将结果行填充到对象中,然后将这些对象存储在会话中。

装置

在这里,我们展示了如何在基于 Debian 的 Linux 系统上安装 SQLAlchemy 和其他必要的包。

$ sudo apt-get install python-pip
$ sudo pip install SQLAlchemy

使用pip Python 包管理器,我们安装了 SQLAlchemy。

$ sudo apt-get install python-psycopg2
$ sudo apt-get install python-mysqldb

我们为 PostgreSQL 和 MySQL 安装了 DBAPI 驱动程序。 SQLAlchemy 取决于这些模块。 sqlite模块随 Python 一起分发。

$ sudo apt-get install mysql-server

上面的命令安装 MySQL 服务器。 有关其他安装说明,您可以查看 MySQL Python 教程

$ sudo apt-get install postgresql

上面的命令安装 PostgreSQL 服务器。 有关其他说明,您可以查看 PostgreSQL Python 教程

$ sudo apt-get install sqlite3

最后,我们安装sqlite3命令行界面。

SQLAlchemy 版本

在以下脚本中,我们检查 SQLAlchemy 的安装是否成功。

sqlalchemy_version.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sqlalchemy

print sqlalchemy.__version__

我们打印 SQLAlchemy 的版本。

$ ./version.py 
1.0.8

当前使用的 SQLAlchemy 版本是 1.0.8。

使用的表

下面的 SQL 脚本创建在本教程中使用的表。

sqlite> .read cars.sql

对于 SQLite 数据库,我们使用.read命令读取 SQL 脚本。

mysql> source cars.sql

对于 MySQL 数据库,我们使用source命令读取 SQL 脚本。

testdb=> \i cars.sql

对于 PostgreSQL 数据库,我们使用\i命令读取 SQL 脚本。

cars.sql

-- SQL for the Cars table

BEGIN TRANSACTION;
DROP TABLE IF EXISTS Cars;

CREATE TABLE Cars(Id INTEGER PRIMARY KEY, Name TEXT, Price INTEGER);
INSERT INTO Cars VALUES(1, 'Audi', 52642);
INSERT INTO Cars VALUES(2, 'Mercedes', 57127);
INSERT INTO Cars VALUES(3, 'Skoda', 9000);
INSERT INTO Cars VALUES(4, 'Volvo', 29000);
INSERT INTO Cars VALUES(5, 'Bentley', 350000);
INSERT INTO Cars VALUES(6, 'Citroen', 21000);
INSERT INTO Cars VALUES(7, 'Hummer', 41400);
INSERT INTO Cars VALUES(8, 'Volkswagen', 21600);
COMMIT;

这是Cars表。

authors_books.sql

-- SQL for the Authors & Books tables

BEGIN TRANSACTION;
DROP TABLE IF EXISTS Books;
DROP TABLE IF EXISTS Authors;

CREATE TABLE Authors(AuthorId INTEGER PRIMARY KEY, Name TEXT);
INSERT INTO Authors VALUES(1, 'Jane Austen');
INSERT INTO Authors VALUES(2, 'Leo Tolstoy');
INSERT INTO Authors VALUES(3, 'Joseph Heller');
INSERT INTO Authors VALUES(4, 'Charles Dickens');

CREATE TABLE Books(BookId INTEGER PRIMARY KEY, Title TEXT, AuthorId INTEGER, 
    FOREIGN KEY(AuthorId) REFERENCES Authors(AuthorId));
INSERT INTO Books VALUES(1,'Emma',1);
INSERT INTO Books VALUES(2,'War and Peace',2);
INSERT INTO Books VALUES(3,'Catch XII',3);
INSERT INTO Books VALUES(4,'David Copperfield',4);
INSERT INTO Books VALUES(5,'Good as Gold',3);
INSERT INTO Books VALUES(6,'Anna Karenia',2);
COMMIT;

这些是AuthorsBooks表。

数据来源

SQLAlchemy 的文档用于创建本教程。

本章是 SQLAlchemy 工具箱的简介。

原始 SQL

原文: http://zetcode.com/db/sqlalchemy/rawsql/

在 SQLite 教程的这一部分中,我们使用原始 SQL。 SQLAlchemy 并不是纯粹的 ORM 工具包。 它还允许在需要时执行原始 SQL 语句。

标量数据

在第一个示例中,我们连接到内存中的 SQLite 数据库并执行一个简单的 SQL 语句。

scalar_data.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine

eng = create_engine('sqlite:///:memory:')

with eng.connect() as con:

    rs = con.execute('SELECT 5')

    data = rs.fetchone()[0]

    print "Data: %s" % data  

该示例打印由SELECT语句返回的值。

eng = create_engine('sqlite:///:memory:')

create_engine方法创建一个引擎,该引擎用于将 SQL 语句传递到数据库。 该方法将连接字符串作为参数。 我们将连接到内存中的 SQLite 数据库。

with eng.connect() as con:

使用connect()方法,我们连接到连接字符串中指定的数据库。

rs = con.execute('SELECT 5')

使用连接的execute()方法,我们提供了一个简单的SELECT SQL 语句。

data = rs.fetchone()[0]

使用fetchone()方法,我们检索了一行。 从这一行,我们得到标量值。

print "Data: %s" % data 

该值将打印到控制台。

$ ./scalar_data.py 
Data: 5

我们执行脚本。

PostgreSQL 版本

在下一个示例中,我们连接到 PostgreSQL 数据库并打印其版本。

postgres_version.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine

eng = create_engine('postgresql:///testdb')
con = eng.connect()

rs = con.execute("SELECT VERSION()")
print rs.fetchone()

con.close()

要选择数据库的版本,我们使用SELECT VERSION() SQL 命令。

eng = create_engine('postgresql:///testdb')

我们连接到 PostgreSQL 中的testdb数据库。 我们在连接字符串中不包含用户名和密码。 这是因为 PostgreSQL 允许在其信任认证策略中对本地连接不进行认证就进行连接。

rs = con.execute("SELECT VERSION()")
print rs.fetchone()

我们执行 SQL 命令并将返回的数据打印到控制台。

$ ./postgres_version.py 
(u'PostgreSQL 9.3.9 on x86_64-unknown-linux-gnu, compiled by gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4, 64-bit',)

这是一个示例输出。

创建数据库表

在下面的示例中,我们将创建一个表并将其填充数据。

raw_create_table.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine
from sqlalchemy.sql import text

eng = create_engine("mysql://testuser:test623@localhost/testdb")

with eng.connect() as con:

    con.execute(text('DROP TABLE IF EXISTS Cars'))
    con.execute(text('''CREATE TABLE Cars(Id INTEGER PRIMARY KEY, 
                 Name TEXT, Price INTEGER)'''))

    data = ( { "Id": 1, "Name": "Audi", "Price": 52642 },
             { "Id": 2, "Name": "Mercedes", "Price": 57127 },
             { "Id": 3, "Name": "Skoda", "Price": 9000 },
             { "Id": 4, "Name": "Volvo", "Price": 29000 },
             { "Id": 5, "Name": "Bentley", "Price": 350000 },
             { "Id": 6, "Name": "Citroen", "Price": 21000 },
             { "Id": 7, "Name": "Hummer", "Price": 41400 },
             { "Id": 8, "Name": "Volkswagen", "Price": 21600 }
    )

    for line in data:
        con.execute(text("""INSERT INTO Cars(Id, Name, Price) 
            VALUES(:Id, :Name, :Price)"""), **line)

使用绑定参数的后端中立方式创建Cars表。

eng = create_engine("mysql://testuser:test623@localhost/testdb")

我们将连接到 MySQL 数据库。 我们使用特定的 MySQL 连接字符串。

for line in data:
    con.execute(text("""INSERT INTO Cars(Id, Name, Price) 
        VALUES(:Id, :Name, :Price)"""), **line)

使用for循环,我们将数据插入数据库表中。 数据库使用不同的绑定参数构造。 借助text()函数,我们使用了后端中立的方式来绑定参数。

$ mysql -u testuser -p
mysql> USE testdb;
mysql> SELECT * FROM Cars;
+----+------------+--------+
| Id | Name       | Price  |
+----+------------+--------+
|  1 | Audi       |  52642 |
|  2 | Mercedes   |  57127 |
|  3 | Skoda      |   9000 |
|  4 | Volvo      |  29000 |
|  5 | Bentley    | 350000 |
|  6 | Citroen    |  21000 |
|  7 | Hummer     |  41400 |
|  8 | Volkswagen |  21600 |
+----+------------+--------+
8 rows in set (0.00 sec)

我们验证数据。

列名

下面的示例打印Cars表的列名。

raw_column_names.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine
from sqlalchemy.sql import text

eng = create_engine('sqlite:///:memory:')

with eng.connect() as con:

    con.execute(text('''CREATE TABLE Cars(Id INTEGER PRIMARY KEY,
        Name TEXT, Price INTEGER)'''))
    rs = con.execute(text('SELECT * FROM Cars'))

    print rs.keys()

该示例在内存中创建一个数据库表并打印其列名。

rs = con.execute(text('SELECT * FROM Cars'))

SELECT语句中,我们选择所有列。

print rs.keys()

keys()方法返回列的名称。

$ ./raw_column_names.py 
[u'Id', u'Name', u'Price']

这是示例的输出。

在 SQLite 教程的这一部分中,我们使用 SQLAlchemy 执行了原始 SQL 语句。

模式定义语言

原文: http://zetcode.com/db/sqlalchemy/schema/

在 SQLAlchemy 教程的这一部分中,我们描述了 SQLAlchemy 的模式定义语言。

SQLAlchemy 模式元数据是一个描述和检查数据库模式的综合系统。 数据库元数据支持 SQLAlchemy 的查询和对象映射操作的核心。

元数据是有关数据库中数据的信息。 例如有关存储数据的表和列的信息。

Table

Table类用于表示数据库表。

schema.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import (create_engine, Table, Column, Integer, 
    String, MetaData)

meta = MetaData()
cars = Table('Cars', meta,
     Column('Id', Integer, primary_key=True),
     Column('Name', String),
     Column('Price', Integer)
)

print "The Name column:"
print cars.columns.Name
print cars.c.Name

print "Columns: "
for col in cars.c:
    print col

print "Primary keys:"
for pk in cars.primary_key:
    print pk    

print "The Id column:"
print cars.c.Id.name
print cars.c.Id.type
print cars.c.Id.nullable
print cars.c.Id.primary_key

在示例中,我们使用架构定义语言来描述一个简单的表。

from sqlalchemy import (create_engine, Table, Column, Integer, 
    String, MetaData)

TableColumnIntegerStringMetaData是表定义所需的类。

meta = MetaData()

MetaDataTable对象的容器,以及到引擎或连接的可选绑定。

cars = Table('Cars', meta,
     Column('Id', Integer, primary_key=True),
     Column('Name', String),
     Column('Price', Integer)
)

我们创建Cars表的元数据定义。 该表具有三列,由Column类定义。 列的数据类型由IntegerString类定义。

print cars.columns.Name
print cars.c.Name

我们访问Name列。 这些列可通过columnsc属性使用。

for col in cars.c:
    print col

在此for循环中,我们打印表的所有列名。

for pk in cars.primary_key:
    print pk    

我们在表中打印主键。

print cars.c.Id.name
print cars.c.Id.type
print cars.c.Id.nullable
print cars.c.Id.primary_key

在这里,我们打印Id列的四个属性:其名称,类型,是否可为空以及是否具有主键。

$ ./schema.py 
The Name column:
Cars.Name
Cars.Name
Columns: 
Cars.Id
Cars.Name
Cars.Price
Primary keys:
Cars.Id
The Id column:
Id
INTEGER
False
True

这是示例的输出。

reflect()方法

reflect()方法为数据库中可用但尚未出现在MetaData中的任何表自动在MetaData对象中创建Table条目。

schema_reflect.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import (create_engine, Table, Column, Integer, 
    String, MetaData)

eng = create_engine("mysql://testuser:test623@localhost/testdb")

meta = MetaData()
meta.reflect(bind=eng)

for table in meta.tables:
    print table

在示例中,我们使用reflect()方法来打印数据库中的所有表名。

meta = MetaData()
meta.reflect(bind=eng)

reflect()方法绑定到创建的引擎。 MetaData充满了Table对象。

for table in meta.tables:
    print table

可通过tables属性访问Table对象,该属性是Table对象的字典。 表名是字典的键。

$ ./schema_reflect.py 
Images
Cars
Books
Testing
Authors

这是示例的输出。

检查器

检查器执行低级数据库模式检查。 使用inspect()方法创建一个检查器。

schema_inspector.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine, inspect

eng = create_engine("mysql://testuser:test623@localhost/testdb")

insp = inspect(eng)
print insp.get_table_names()
print insp.get_columns("Cars")
print insp.get_primary_keys("Cars")    
print insp.get_schema_names()

在示例中,我们使用检查器进行了一些元数据反射。

insp = inspect(eng)

检查器对象是使用inspect()方法创建的。 该方法以引擎为参数。

print insp.get_table_names()

get_table_names()获取可用表的名称。

print insp.get_columns("Cars")

get_columns()获取Cars表的列的名称。

print insp.get_primary_keys("Cars")

get_primary_keys()获取Cars表的主键。

print insp.get_schema_names()

get_schema_names()返回所有模式名称。

$ ./schema_inspector.py 
[u'Authors', u'Books', u'Cars', u'Images', u'Testing']
[{'default': None, 'autoincrement': False, 'type': INTEGER(display_width=11), 'name': u'Id', 'nullable': False}, 
{'default': None, 'type': TEXT(), 'name': u'Name', 'nullable': True}, 
{'default': None, 'autoincrement': False, 'type': INTEGER(display_width=11), 'name': u'Price', 'nullable': True}]
[u'Id']
['information_schema', 'testdb']

这是示例的输出。

SQLAlchemy 教程的这一部分专门用于架构元数据。

SQL 表达式语言

原文: http://zetcode.com/db/sqlalchemy/exprlang/

在 SQLAlchemy 教程的这一部分中,我们使用 SQLAlchemy 的 SQL 表达式语言。

SQLAlchemy 表达式语言使用 Python 构造表示关系数据库结构和表达式。 表达式语言通过隐藏 SQL 语言来提高代码的可维护性,因此不允许混合使用 Python 代码和 SQL 代码。

对象关系映射器(ORM)建立在表达式语言之上。

选择所有行

在第一个示例中,我们使用表达式语言从表中选择所有行。

exp_select_all.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine, Table, MetaData
from sqlalchemy.sql import select    

eng = create_engine('sqlite:///test.db')

with eng.connect() as con:

    meta = MetaData(eng)
    cars = Table('Cars', meta, autoload=True)  

    stm = select([cars])
    rs = con.execute(stm) 

    print rs.fetchall()

该示例使用select()方法从Cars表中检索所有行。

meta = MetaData(eng)
cars = Table('Cars', meta, autoload=True)  

我们加载Cars表的定义。

stm = select([cars])

使用select()方法,我们创建了一条 SQL SELECT语句。 该特定表达式从提供的表中选择所有列和行。

rs = con.execute(stm) 

该语句被执行。

print rs.fetchall()

使用fetchall()方法,我们将打印所有返回的数据。

$ ./exp_select_all.py 
[(1, u'Audi', 52642), (2, u'Mercedes', 57127), (3, u'Skoda', 9000), (4, u'Volvo', 29000), 
(5, u'Bentley', 350000), (6, u'Citroen', 21000), (7, u'Hummer', 41400), (8, u'Volkswagen', 21600)]

这是示例的输出。

限制所选输出

在第二个示例中,我们限制从表中检索的数据。

exp_select_limit.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine, Table, MetaData
from sqlalchemy.sql import select    

eng = create_engine('sqlite:///test.db')

with eng.connect() as con:

    meta = MetaData(eng)
    cars = Table('Cars', meta, autoload=True)  

    stm = select([cars.c.Name, cars.c.Price]).limit(3)
    rs = con.execute(stm) 

    print rs.fetchall()

该示例从Cars表中打印三行的两列。

stm = select([cars.c.Name, cars.c.Price]).limit(3)

在方括号之间,我们提供了要显示的列。 limit()方法将结果集限制为三行。

$ ./exp_select_limit.py 
[(u'Audi', 52642), (u'Mercedes', 57127), (u'Skoda', 9000)]

这是exp_select_limit.py程序的输出。

where()方法

where()方法将WHERE子句添加到select()方法生成的语句中。

exp_select_where.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine, Table, MetaData
from sqlalchemy.sql import select, and_    

eng = create_engine('sqlite:///test.db')

with eng.connect() as con:

    meta = MetaData(eng)
    cars = Table('Cars', meta, autoload=True)  

    stm = select([cars]).where(and_(cars.c.Price > 10000, 
                                  cars.c.Price < 40000))
    rs = con.execute(stm) 

    print rs.fetchall()

该示例选择价格在 10000 和 40000 之间的所有汽车。

stm = select([cars]).where(and_(cars.c.Price > 10000, 
                                cars.c.Price < 40000))

为了构建预期的 SQL 语句,我们使用select()where()方法以及and_()运算符。

$ ./exp_select_where.py 
[(4, u'Volvo', 29000), (6, u'Citroen', 21000), (8, u'Volkswagen', 21600)]

该代码示例将打印价格在 10000 和 40000 之间的所有汽车。

like()方法

like()方法将LIKE子句添加到select()方法生成的语句中。

exp_select_like.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine, Table, MetaData
from sqlalchemy.sql import select    

eng = create_engine('sqlite:///test.db')

with eng.connect() as con:

    meta = MetaData(eng)
    cars = Table('Cars', meta, autoload=True)  

    stm = select([cars]).where(cars.c.Name.like('%en'))
    rs = con.execute(stm) 

    print rs.fetchall()

通过like()方法,我们选择名称以'en'结尾的所有汽车。

stm = select([cars]).where(cars.c.Name.like('%en'))

like()方法应用于列名。

$ ./exp_select_like.py 
[(6, u'Citroen', 21000), (8, u'Volkswagen', 21600)]

有两辆汽车的名字以"en"结尾。

排序行

order_by()方法将ORDER BY子句添加到select()方法生成的语句中。

exp_select_order.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine, Table, MetaData
from sqlalchemy.sql import select, asc

eng = create_engine('sqlite:///test.db')

with eng.connect() as con:

    metadata = MetaData(eng)
    cars = Table('Cars', metadata, autoload=True)  

    s = select([cars]).order_by(asc(cars.c.Name))
    rs = con.execute(s) 

    for row in rs:
        print row['Id'], row['Name'], row['Price']

该示例打印按汽车名称排序的所有行。

s = select([cars]).order_by(asc(cars.c.Name))

order_by()方法具有asc()运算符,该运算符以升序方式进行排序。

$ ./exp_select_order.py
1 Audi 52642
5 Bentley 350000
6 Citroen 21000
7 Hummer 41400
2 Mercedes 57127
3 Skoda 9000
8 Volkswagen 21600
4 Volvo 29000

该示例打印所有行。 这些行按汽车名称升序排列。

in_()运算符

in_()运算符用于将IN子句添加到生成的SELECT语句中。

exp_select_in.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine, Table, MetaData, tuple_
from sqlalchemy.sql import select

eng = create_engine('sqlite:///test.db')

with eng.connect() as con:

    meta = MetaData(eng)
    cars = Table('Cars', meta, autoload=True)  

    k = [(2,), (4,), (6,), (8,)]
    stm = select([cars]).where(tuple_(cars.c.Id).in_(k))
    rs = con.execute(stm) 

    for row in rs:
        print row['Id'], row['Name'], row['Price']

该示例打印由in_()运算符指定的表的四行。

stm = select([cars]).where(tuple_(cars.c.Id).in_(k))

借助tuple_()in_()运算符,我们构建了包含IN子句的语句。

$ ./exp_select_in.py 
2 Mercedes 57127
4 Volvo 29000
6 Citroen 21000
8 Volkswagen 21600

这是示例的输出。

建立表

下一个示例使用表达式语言在内存中创建一个表。

exp_create_table.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import (create_engine, Table, Column, Integer, 
    String, MetaData)
from sqlalchemy.sql import select    

eng = create_engine('sqlite:///:memory:')

with eng.connect() as con:

    meta = MetaData(eng)
    cars = Table('Cars', meta,
         Column('Id', Integer, primary_key=True),
         Column('Name', String),
         Column('Price', Integer)
    )

    cars.create()

    ins1 = cars.insert().values(Id=1, Name='Audi', Price=52642)
    con.execute(ins1)

    ins2 = cars.insert().values(Id=2, Name='Mercedes', Price=57127)
    con.execute(ins2)

    ins3 = cars.insert().values(Id=3, Name='Skoda', Price=6000)
    con.execute(ins3)    

    s = select([cars])
    rs = con.execute(s) 

    for row in rs:
        print row['Id'], row['Name'], row['Price']

该示例在内存中创建一个新表,将其填充数据,然后在该表上执行SELECT语句。

eng = create_engine('sqlite:///:memory:')

创建的表将是 SQLite 的内存表。

meta = MetaData(eng)
cars = Table('Cars', meta,
    Column('Id', Integer, primary_key=True),
    Column('Name', String),
    Column('Price', Integer)
)

我们提供表的定义。

cars.create()

该表是使用create()方法创建的。

ins1 = cars.insert().values(Id=1, Name='Audi', Price=52642)
con.execute(ins1)

使用insert()方法,我们将新行插入表中。

s = select([cars])
rs = con.execute(s) 

for row in rs:
    print row['Id'], row['Name'], row['Price']

在最后一步,我们执行SELECT语句并将所有返回的数据打印到控制台。

$ ./exp_create_table.py 
1 Audi 52642
2 Mercedes 57127
3 Skoda 6000

这是示例的输出。

连接表

在下面的示例中,我们连接了两个表中的字段。 我们使用join()方法。

exp_join_tables.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import (create_engine, Table, Column, Integer, 
    String, ForeignKey, MetaData)
from sqlalchemy.sql import select    

eng = create_engine('sqlite:///test.db')

with eng.connect() as con:

    meta = MetaData(eng)

    authors = Table('Authors', meta, autoload=True)
    books = Table('Books', meta, autoload=True)

    stm = select([authors.join(books)])
    rs = con.execute(stm) 

    for row in rs:
        print row['Name'], row['Title']

该示例在两个表上执行内连接。 我们得到了作者及其相应的标题。

authors = Table('Authors', meta, autoload=True)
books = Table('Books', meta, autoload=True)

这两个表是从数据库加载的。

stm = select([authors.join(books)])

我们使用JOIN子句创建SELECT语句。

$ ./exp_join_tables.py 
Jane Austen Emma
Leo Tolstoy War and Peace
Joseph Heller Catch XII
Charles Dickens David Copperfield
Joseph Heller Good as Gold
Leo Tolstoy Anna Karenia

这是示例的输出。

在 SQLAlchemy 教程的这一部分中,我们使用了 SQL 表达式语言。

SQLAlchemy 中的对象关系映射器

原文: http://zetcode.com/db/sqlalchemy/orm/

在 SQLAlchemy 教程的这一部分中,我们介绍了 SQLAlchemy 的对象关系映射器。

对象关系映射

使用 Python 数据库 API 进行编程可为开发者提供直接访问数据库的全部功能。 这种直接访问也有一些缺点。 它们在大型项目中尤其引人注目。 我们将两种语言混合在一起:SQL 和 Python。 结果是它使 SQL 语句更难测试和维护。 在典型的 Web 应用中,除了 Python 和 SQL(或任何其他服务器端编程语言)外,我们还具有 HTML,CSS,JavaScript。 Python 和 SQL 捆绑在一起使项目变得更加复杂。 在编程理论中,我们尝试将业务逻辑与数据访问以及表示分离。 因此,需要一种将 Python 代码与 SQL 代码分开的解决方案。

另一个问题是我们所谓的对象关系阻抗不匹配。 当关系数据库管理系统被以面向对象的编程语言或风格编写的程序使用时,通常会遇到一系列概念和技术难题。 在 Python 中,我们处理放置在对象内的数据。 在数据库系统中,数据存储在表中。 程序员需要在两种处理数据的方式之间进行转换。 这与我们应用的核心问题无关。

解决方案之一是对象关系映射。 ORM 工具解决了上述问题。 有几种用于 Python 语言的 ORM 工具。 SQLAlchemy 是使用最广泛的方法之一。

SQLAlchemy ORM

SQLAlchemy 对象关系映射器将(a)用户定义的 Python 类映射到数据库表,(b)将表行映射到实例对象,并且(c)将列映射到实例属性。 SQLAlchemy ORM 建立在 SQLAlchemy 表达式语言之上。

使用 ORM 时,我们首先配置将要使用的数据库表。 然后,我们定义将要映射到它们的类。 现代 SQLAlchemy 使用声明式系统来执行这些任务。 将创建一个声明性基类,该基类维护类和表的目录。 使用declarative_base()函数创建一个声明性基类。

会话

完成配置后,我们创建一个会话。 会话是 SQLAlchemy ORM 中持久性操作的主要接口。 它建立并维护我们的程序与数据库之间的所有对话。

建立表

以下程序在内存中创建一个表,然后将数据打印到控制台。

orm_create_table.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker

eng = create_engine('sqlite:///:memory:')

Base = declarative_base()

class Car(Base):
    __tablename__ = "Cars"

    Id = Column(Integer, primary_key=True)
    Name = Column(String)  
    Price = Column(Integer)

Base.metadata.bind = eng        
Base.metadata.create_all()        

Session = sessionmaker(bind=eng)
ses = Session()    

ses.add_all(
   [Car(Id=1, Name='Audi', Price=52642), 
    Car(Id=2, Name='Mercedes', Price=57127),
    Car(Id=3, Name='Skoda', Price=9000),
    Car(Id=4, Name='Volvo', Price=29000),
    Car(Id=5, Name='Bentley', Price=350000),
    Car(Id=6, Name='Citroen', Price=21000),
    Car(Id=7, Name='Hummer', Price=41400),
    Car(Id=8, Name='Volkswagen', Price=21600)])
ses.commit()

rs = ses.query(Car).all()

for car in rs:
    print car.Name, car.Price

Cars表中创建了八辆汽车。

Base = declarative_base()

使用declarative_base()函数创建一个声明性基类。

class Car(Base):
    __tablename__ = "Cars"

    Id = Column(Integer, primary_key=True)
    Name = Column(String)  
    Price = Column(Integer)

用户定义的Car类映射到Cars表。 该类继承自声明性基类。

Base.metadata.bind = eng

声明式Base绑定到数据库引擎。

Base.metadata.create_all() 

create_all()方法创建所有已配置的表; 在我们的例子中,只有一张表。

Session = sessionmaker(bind=eng)
ses = Session() 

创建会话对象。

ses.add_all(
   [Car(Id=1, Name='Audi', Price=52642), 
    Car(Id=2, Name='Mercedes', Price=57127),
...    

使用add_all()方法,我们将Car类的指定实例添加到会话中。

ses.commit()

使用commit()方法将更改提交到数据库。

rs = ses.query(Car).all()

我们从 Cars 表中查询所有数据。 query()方法加载Car类的所有实例,其all()方法将查询表示的所有结果作为列表返回。

for car in rs:
    print car.Name, car.Price

我们遍历结果集并为所有返回的行打印两列。

$ ./orm_create_table.py 
Audi 52642
Mercedes 57127
Skoda 9000
Volvo 29000
Bentley 350000
Citroen 21000
Hummer 41400
Volkswagen 21600

这是示例的输出。

添加新的汽车

在下一个示例中,我们将单个汽车添加到Cars表中。

orm_add_car.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker

eng = create_engine('sqlite:///test.db')

Base = declarative_base()

class Car(Base):
    __tablename__ = "Cars"

    Id = Column(Integer, primary_key=True)
    Name = Column(String)  
    Price = Column(Integer)

Session = sessionmaker(bind=eng)
ses = Session()    

c1 = Car(Name='Oldsmobile', Price=23450)
ses.add(c1)
ses.commit()

rs = ses.query(Car).all()

for car in rs:
    print car.Name, car.Price

该脚本连接到 SQLite 数据库,并向Cars表添加新行。

eng = create_engine('sqlite:///test.db')

我们连接到 SQLite test.db数据库。

Base = declarative_base()

class Car(Base):
    __tablename__ = "Cars"

    Id = Column(Integer, primary_key=True)
    Name = Column(String)  
    Price = Column(Integer)

执行用户定义的类到数据库表的映射。

Session = sessionmaker(bind=eng)
ses = Session()    

创建会话对象,该对象是 ORM 到数据库的中介。

c1 = Car(Name='Oldsmobile', Price=23450)

创建映射的Car类的新实例。

ses.add(c1)

add()方法将新对象添加到会话中。

ses.commit()

所做的更改将提交到数据库。

$ ./orm_add_car.py 
Audi 52642
Mercedes 57127
Skoda 9000
Volvo 29000
Bentley 350000
Citroen 21000
Hummer 41400
Volkswagen 21600
Oldsmobile 23450

我们确认新车已成功添加到数据库中。

过滤数据

会话查询的filter()方法用于在查询对象上应用过滤条件。

orm_query_like.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker

eng = create_engine('sqlite:///test.db')

Base = declarative_base()
Base.metadata.bind = eng

class Car(Base):
    __tablename__ = "Cars"

    Id = Column(Integer, primary_key=True)
    Name = Column(String)  
    Price = Column(Integer)

Session = sessionmaker(bind=eng)
ses = Session()    

rs = ses.query(Car).filter(Car.Name.like('%en'))

for car in rs:
    print car.Name, car.Price

该示例打印名称以"en"字符串结尾的汽车。

rs = ses.query(Car).filter(Car.Name.like('%en'))

filter()方法采用一个过滤条件,它是一个 SQL 表达式对象。 使用like()方法创建标准。

$ ./orm_query_like.py 
Citroen 21000
Volkswagen 21600

表中有两辆以"en"字符串结尾的汽车。

in_()方法实现 SQL IN运算符。

orm_query_in.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker

eng = create_engine('sqlite:///test.db')

Base = declarative_base()

class Car(Base):
    __tablename__ = "Cars"

    Id = Column(Integer, primary_key=True)
    Name = Column(String)  
    Price = Column(Integer)

Session = sessionmaker(bind=eng)
ses = Session()    

rs = ses.query(Car).filter(Car.Id.in_([2, 4, 6, 8]))

for car in rs:
    print car.Id, car.Name, car.Price

该代码示例选择并打印 SQL IN运算符选择的具有 ID 的行的列。

rs = ses.query(Car).filter(Car.Id.in_([2, 4, 6, 8]))

过滤条件是通过in_()方法创建的。 该方法采用 ID 列表。

$ ./orm_query_in.py 
2 Mercedes 57127
4 Volvo 29000
6 Citroen 21000
8 Volkswagen 21600

这是示例的输出。

外键

在最后一个示例中,我们处理了两个表之间的关系。 建立外键。

orm_foreign_key.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker, relationship

eng = create_engine('sqlite:///test.db')

Base = declarative_base()

class Author(Base):
    __tablename__ = "Authors"

    AuthorId = Column(Integer, primary_key=True)
    Name = Column(String)  
    Books = relationship("Book")

class Book(Base):
    __tablename__ = "Books"

    BookId = Column(Integer, primary_key=True)
    Title = Column(String)      
    AuthorId = Column(Integer, ForeignKey("Authors.AuthorId"))    

    Author = relationship("Author")                           

Session = sessionmaker(bind=eng)
ses = Session()   

res = ses.query(Author).filter(Author.Name=="Leo Tolstoy").first()

for book in res.Books:
    print book.Title

res = ses.query(Book).filter(Book.Title=="Emma").first()    
print res.Author.Name

我们有AuthorBook类,它们映射到AuthorsBooks数据库表。 (用于创建表的 SQL 在第一章中列出)。 在两个表之间实现了外键约束。 外键由ForeignKey类型和relationship()函数定义。

Books = relationship("Book")

在两个类之间建立了一对多的关系。 relationship()函数的第一个参数是与之建立关系的类的名称。 结果,作者对象将具有Books属性。

AuthorId = Column(Integer, ForeignKey("Authors.AuthorId"))

Book类的AuthorId是外键。 它由ForeignKey类型定义。 它引用Authors表中的AuthorId列。

Author = relationship("Author")

此行为Book类创建Author属性。

res = ses.query(Author).filter(Author.Name=="Leo Tolstoy").first()

在此查询中,我们获得了列夫·托尔斯泰(Leo Tolstoy)撰写的所有书籍。 filter()方法对查询应用过滤条件。 first()方法获取作者对象。

for book in res.Books:
    print book.Title

我们浏览结果集并打印所有检索到的书。 Books属性是使用relationship()函数创建的。

res = ses.query(Book).filter(Book.Title=="Emma").first()    
print res.Author.Name

该查询返回Emma标题的作者。 该查询返回具有内置Author属性的book对象。

$ ./orm_foreign_key.py 
War and Peace
Anna Karenia
Jane Austen

这是示例的输出。

在 SQLAlchemy 教程的这一部分中,我们使用了 SQLAlchemy 的 ORM。

MongoDB PHP 教程

原文: http://zetcode.com/db/mongodbphp/

在本教程中,我们将展示如何在 PHP 中使用 MongoDB。 我们将新的mongodb驱动程序用于 PHP。 在 ZetCode 上有一个简洁的 PHP 教程

Tweet

MongoDB 是 NoSQL 跨平台的面向文档的数据库。 它是可用的最受欢迎的数据库之一。 MongoDB 由 MongoDB Inc. 开发,并作为免费和开源软件发布。

MongoDB 中的记录是一个文档,它是由字段和值对组成的数据结构。 MongoDB 文档与 JSON 对象相似。 字段的值可以包括其他文档,数组和文档数组。 MongoDB 将文档存储在集合中。 集合类似于关系数据库中的表,文档类似于行。

安装 MongoDB

以下命令可用于在基于 Debian 的 Linux 上安装 MongoDB。

$ sudo apt-get install mongodb

该命令将安装 MongoDB 随附的必要包。

$ sudo service mongodb status
mongodb start/running, process 975

使用sudo service mongodb status命令,我们检查mongodb服务器的状态。

$ sudo service mongodb start
mongodb start/running, process 6448

mongodb服务器由sudo service mongodb start命令启动。

建立数据库

mongo工具是 MongoDB 的交互式 JavaScript Shell 界面,它为系统管理员提供了一个界面,并为开发者提供了一种直接测试数据库查询和操作的方法。

$ mongo testdb
MongoDB shell version: 2.4.9
connecting to: testdb
> db
testdb
> db.cars.insert({name: "Audi", price: 52642})
> db.cars.insert({name: "Mercedes", price: 57127})
> db.cars.insert({name: "Skoda", price: 9000})
> db.cars.insert({name: "Volvo", price: 29000})
> db.cars.insert({name: "Bentley", price: 350000})
> db.cars.insert({name: "Citroen", price: 21000})
> db.cars.insert({name: "Hummer", price: 41400})
> db.cars.insert({name: "Volkswagen", price: 21600})

我们创建一个testdb数据库,并在cars集合中插入八个文档。

安装 PHP 驱动程序

有两种驱动程序可用:旧版mongo驱动程序和新版mongodb驱动程序。 在本教程中,我们将使用新的mongodb驱动程序。

接下来,我们展示如何手动安装 PHP MongoDB 驱动程序。

$ git clone https://github.com/mongodb/mongo-php-driver.git
$ cd mongo-php-driver
$ git submodule sync && git submodule update --init
$ phpize
$ ./configure
$ make 
$ sudo make install

我们下载源代码并从中安装驱动程序。

extension=mongodb.so

我们将mongodb.so扩展名添加到php.ini文档中。

数据库统计

第一个示例连接到testdb数据库并获取其统计信息。

MongoDB\Driver\Manager负责维护与 MongoDB 的连接。 MongoDB\Driver\Command表示数据库命令。 成功后,命令返回MongoDB\Driver\Cursor

dbstats.php

<?php

try {

    $mng = new MongoDB\Driver\Manager("mongodb://localhost:27017");

    $stats = new MongoDB\Driver\Command(["dbstats" => 1]);
    $res = $mng->executeCommand("testdb", $stats);

    $stats = current($res->toArray());

    print_r($stats);

} catch (MongoDB\Driver\Exception\Exception $e) {

    $filename = basename(__FILE__);

    echo "The $filename script has experienced an error.\n"; 
    echo "It failed with the following exception:\n";

    echo "Exception:", $e->getMessage(), "\n";
    echo "In file:", $e->getFile(), "\n";
    echo "On line:", $e->getLine(), "\n";       
}

?>

该示例连接到testdb数据库并执行dbstats命令。 它显示了一些数据库统计信息。

$mng = new MongoDB\Driver\Manager("mongodb://localhost:27017");

使用MongoDB\Driver\Manager类,我们连接到testdb数据库。 27017 是 MongoDB 服务器监听的默认端口。

$res = $mng->executeCommand("testdb", $stats);

MongoDB\Driver\Command用于执行dbstats命令。

$stats = current($res->toArray());

toArray()方法返回一个数组,其中包含此游标的所有结果,current()函数返回该数组的当前元素。 在我们的例子中,数组只有一个元素。

print_r($stats);

print_r()函数打印$stats变量的人类可读表示。

$ php dbstats.php 
stdClass Object
(
    [db] => testdb
    [collections] => 3
    [objects] => 12
    [avgObjSize] => 52.666666666667
    [dataSize] => 632
    [storageSize] => 12288
    [numExtents] => 3
    [indexes] => 1
    [indexSize] => 8176
    [fileSize] => 201326592
    [nsSizeMB] => 16
    [dataFileVersion] => stdClass Object
        (
            [major] => 4
            [minor] => 5
        )

    [ok] => 1
)

这是dbstats.php程序的输出。

列出数据库

listDatabases命令提供所有现有数据库的列表。

list_databases.php

<?php

try {

    $mng = new MongoDB\Driver\Manager("mongodb://localhost:27017");

    $listdatabases = new MongoDB\Driver\Command(["listDatabases" => 1]);
    $res = $mng->executeCommand("admin", $listdatabases);

    $databases = current($res->toArray());

    foreach ($databases->databases as $el) {

        echo $el->name . "\n";
    }

} catch (MongoDB\Driver\Exception\Exception $e) {

    $filename = basename(__FILE__);

    echo "The $filename script has experienced an error.\n"; 
    echo "It failed with the following exception:\n";

    echo "Exception:", $e->getMessage(), "\n";
    echo "In file:", $e->getFile(), "\n";
    echo "On line:", $e->getLine(), "\n";       
}

?>

该示例在 MongoDB 中打印可用的数据库。

$listdatabases = new MongoDB\Driver\Command(["listDatabases" => 1]);
$res = $mng->executeCommand("admin", $listdatabases);

我们执行listDatabases命令。 该命令在admin数据库上执行。

$databases = current($res->toArray());

该命令返回单个结果文档,其中包含databases数组字段中所有数据库的信息。

foreach ($databases->databases as $el) {

    echo $el->name . "\n";
}

我们遍历数据库数组并打印可用数据库的名称。

$ php list_databases.php 
testdb
test
local

在本地计算机上,我们具有这三个数据库。

读取数据

MongoDB\Driver\Query是代表数据库查询的值对象。

read_all.php

<?php

try {

    $mng = new MongoDB\Driver\Manager("mongodb://localhost:27017");
    $query = new MongoDB\Driver\Query([]); 

    $rows = $mng->executeQuery("testdb.cars", $query);

    foreach ($rows as $row) {

        echo "$row->name : $row->price\n";
    }

} catch (MongoDB\Driver\Exception\Exception $e) {

    $filename = basename(__FILE__);

    echo "The $filename script has experienced an error.\n"; 
    echo "It failed with the following exception:\n";

    echo "Exception:", $e->getMessage(), "\n";
    echo "In file:", $e->getFile(), "\n";
    echo "On line:", $e->getLine(), "\n";       
}

?>

该示例从testdb.cars集合读取所有数据。

$query = new MongoDB\Driver\Query([]); 

创建一个MongoDB\Driver\Query对象。 如果我们传递一个空数组,它将读取所有数据。

$rows = $mng->executeQuery("testdb.cars", $query);

executeQuery()执行查询。 第一个参数是集合名称,第二个参数是查询。

foreach ($rows as $row) {

    echo "$row->name : $row->price\n";
}

我们遍历所有匹配的文档。

$ php read_all.php 
Audi : 52642
Mercedes : 57127
Skoda : 9000
Volvo : 29000
Bentley : 350000
Citroen : 21000
Hummer : 41400
Volkswagen : 21600

这是read_all.php脚本的输出。

过滤数据

MongoDB\Driver\Query包含用于过滤数据的过滤参数。

filtering.php

<?php

try {

    $mng = new MongoDB\Driver\Manager("mongodb://localhost:27017");

    $filter = [ 'name' => 'Volkswagen' ]; 
    $query = new MongoDB\Driver\Query($filter);     

    $res = $mng->executeQuery("testdb.cars", $query);

    $car = current($res->toArray());

    if (!empty($car)) {

        echo $car->name, ": ", $car->price, PHP_EOL;
    } else {

        echo "No match found\n";
    }

} catch (MongoDB\Driver\Exception\Exception $e) {

    $filename = basename(__FILE__);

    echo "The $filename script has experienced an error.\n"; 
    echo "It failed with the following exception:\n";

    echo "Exception:", $e->getMessage(), "\n";
    echo "In file:", $e->getFile(), "\n";
    echo "On line:", $e->getLine(), "\n";    
}

?>

该示例搜索大众汽车的价格。

$filter = [ 'name' => 'Volkswagen' ]; 
$query = new MongoDB\Driver\Query($filter); 

我们将过滤器参数提供给MongoDB\Driver\Query

$car = current($res->toArray());

if (!empty($car)) {

    echo $car->name, ": ", $car->price, PHP_EOL;
} else {

    echo "No match found\n";
}

我们会打印所选汽车的名称和价格。 我们使用empty()函数确保返回的变量不为空。

$ php filtering.php 
Volkswagen: 21600

这是filtering.php脚本的输出。

投影

投影可用于指定应返回哪些字段。

projection.php

<?php

try {

    $filter = [];
    $options = ["projection" => ['_id' => 0]];

    $mng = new MongoDB\Driver\Manager("mongodb://localhost:27017");
    $query = new MongoDB\Driver\Query($filter, $options);

    $rows = $mng->executeQuery("testdb.cars", $query);

    foreach ($rows as $row) {

           print_r($row);
    }    

} catch (MongoDB\Driver\Exception\Exception $e) {

    $filename = basename(__FILE__);

    echo "The $filename script has experienced an error.\n"; 
    echo "It failed with the following exception:\n";

    echo "Exception:", $e->getMessage(), "\n";
    echo "In file:", $e->getFile(), "\n";
    echo "On line:", $e->getLine(), "\n";    
}

?>

在示例中,我们隐藏了第一个字段_id

$options = ["projection" => ['_id' => 0]];

投影以projection数组指定。 在这里,我们隐藏_id字段。

$query = new MongoDB\Driver\Query($filter, $options);     

投影在MongoDB\Driver\Query的第二个参数中传递。

$ php projection.php 
stdClass Object
(
    [name] => Audi
    [price] => 52642
)
stdClass Object
(
    [name] => Mercedes
    [price] => 57127
)
...

这是projection.php脚本的部分输出。 仅返回名称和价格字段。

限制数据输出

limit查询选项指定要返回的文档数,sort选项指定排序顺序。

read_limit.php

<?php

try {

    $mng = new MongoDB\Driver\Manager("mongodb://localhost:27017");
    $query = new MongoDB\Driver\Query([], ['sort' => [ 'name' => 1], 'limit' => 5]);     

    $rows = $mng->executeQuery("testdb.cars", $query);

    foreach ($rows as $row) {

        echo "$row->name : $row->price\n";
    }

} catch (MongoDB\Driver\Exception\Exception $e) {

    $filename = basename(__FILE__);

    echo "The $filename script has experienced an error.\n"; 
    echo "It failed with the following exception:\n";

    echo "Exception:", $e->getMessage(), "\n";
    echo "In file:", $e->getFile(), "\n";
    echo "On line:", $e->getLine(), "\n";       
}

?>

该示例从testdb.cars集合中读取所有数据,将输出限制为五辆汽车,并按照汽车名称升序排序。

$query = new MongoDB\Driver\Query([], ['sort' => [ 'name' => 1], 'limit' => 5]); 

我们在查询的第二个参数中指定sortlimit选项。

$ php read_limit.php 
Audi : 52642
Bentley : 350000
Citroen : 21000
Hummer : 41400
Mercedes : 57127

这是read_limit.php脚本的输出。

批量写入

MongoDB\Driver\Manager::executeBulkWrite方法执行一个或多个写操作,包括插入,更新和删除。

bulkwrite.php

<?php

try {

    $mng = new MongoDB\Driver\Manager("mongodb://localhost:27017");

    $bulk = new MongoDB\Driver\BulkWrite;

    $doc = ['_id' => new MongoDB\BSON\ObjectID, 'name' => 'Toyota', 'price' => 26700];
    $bulk->insert($doc);
    $bulk->update(['name' => 'Audi'], ['$set' => ['price' => 52000]]);
    $bulk->delete(['name' => 'Hummer']);

    $mng->executeBulkWrite('testdb.cars', $bulk);

} catch (MongoDB\Driver\Exception\Exception $e) {

    $filename = basename(__FILE__);

    echo "The $filename script has experienced an error.\n"; 
    echo "It failed with the following exception:\n";

    echo "Exception:", $e->getMessage(), "\n";
    echo "In file:", $e->getFile(), "\n";
    echo "On line:", $e->getLine(), "\n";    
}

?>

该脚本会插入一辆新车,更新一辆车,然后删除一辆车。

$bulk = new MongoDB\Driver\BulkWrite();

MongoDB\Driver\BulkWrite收集一个或多个应发送到服务器的写操作。

$doc = ['_id' => new MongoDB\BSON\ObjectID, 'name' => 'Toyota', 'price' => 26700];

这是要插入的新文档。 MongoDB\BSON\ObjectID生成一个新的ObjectId。 它是用于唯一标识集合中文档的值。

$bulk->insert($doc);

使用insert()方法创建一个插入操作。

$bulk->update(['name' => 'Audi'], ['$set' => ['price' => 52000]]);

使用update()方法创建更新操作。 $set运算符将字段的值替换为指定的值。

$bulk->delete(['name' => 'Hummer']);

使用delete()方法创建删除操作。

$mng->executeBulkWrite('testdb.cars', $bulk);

executeBulkWrite()testdb.cars集合执行三个操作。

> db.cars.find()
{ "_id" : ObjectId("571e05a6c4a3bc7dc758b457"), "name" : "Audi", "price" : 52000 }
{ "_id" : ObjectId("571e05b5c4a3bc7dc758b458"), "name" : "Mercedes", "price" : 57127 }
{ "_id" : ObjectId("571e05bec4a3bc7dc758b459"), "name" : "Skoda", "price" : 9000 }
{ "_id" : ObjectId("571e05c7c4a3bc7dc758b45a"), "name" : "Volvo", "price" : 29000 }
{ "_id" : ObjectId("571e05d0c4a3bc7dc758b45b"), "name" : "Bentley", "price" : 350000 }
{ "_id" : ObjectId("571e05e0c4a3bc7dc758b45c"), "name" : "Citroen", "price" : 21000 }
{ "_id" : ObjectId("571e05fcc4a3bc7dc758b45e"), "name" : "Volkswagen", "price" : 21600 }
{ "_id" : ObjectId("5720a4e581365b0e9414d0e1"), "name" : "Toyota", "price" : 26700 }

使用mongo工具确认更改。

在本教程中,我们使用了 MongoDB 和 PHP。

MongoDB JavaScript 教程

原文: http://zetcode.com/db/mongodbjavascript/

MongoDB JavaScript 教程展示了如何在 JavaScript 中创建与 MongoDB 一起使用的程序。 本教程使用本地 mongodb 驱动程序。 (还有其他解决方案,例如 Mongoose 或 Monk。)

Tweet

MongoDB

MongoDB 是 NoSQL 跨平台的面向文档的数据库。 它是可用的最受欢迎的数据库之一。 MongoDB 由 MongoDB Inc.开发,并作为免费和开源软件发布。

MongoDB 中的记录是一个文档,它是由字段和值对组成的数据结构。 MongoDB 文档与 JSON 对象相似。 字段的值可以包括其他文档,数组和文档数组。 MongoDB 将文档存储在集合中。 集合类似于关系数据库中的表以及行中的文档。

安装 MongoDB 服务器

以下命令可用于在基于 Debian 的 Linux 上安装 MongoDB。

$ sudo apt-get install mongodb

该命令将安装 MongoDB 随附的必要包。

$ sudo service mongodb status
mongodb start/running, process 975

使用sudo service mongodb status命令,我们检查mongodb服务器的状态。

$ sudo service mongodb start
mongodb start/running, process 6448

mongodb服务器由sudo service mongodb start命令启动。

MongoDB 驱动程序安装

我们设置了项目。

$ node -v
v11.5.0

我们使用 Node.js 版本 11.5.0。

$ npm i mongodb

我们安装mongodb本机 JavaScript 驱动程序。 npm是 Node.js 包管理器。 MongoDB Node.js 驱动程序提供基于回调和基于Promise的交互。

MongoDB 创建数据库

mongo工具是 MongoDB 的交互式 JavaScript Shell 界面,它为系统管理员提供了一个界面,并为开发者提供了一种直接测试数据库查询和操作的方法。

$ mongo testdb
MongoDB shell version v4.0.7
...
> db
testdb
> db.cars.insert({name: "Audi", price: 52642})
> db.cars.insert({name: "Mercedes", price: 57127})
> db.cars.insert({name: "Skoda", price: 9000})
> db.cars.insert({name: "Volvo", price: 29000})
> db.cars.insert({name: "Bentley", price: 350000})
> db.cars.insert({name: "Citroen", price: 21000})
> db.cars.insert({name: "Hummer", price: 41400})
> db.cars.insert({name: "Volkswagen", price: 21600})

我们创建一个testdb数据库,并在cars集合中插入八个文档。

MongoDB Promise

Promise是用于延迟和异步计算的对象。 它表示尚未完成的操作,但有望在将来进行。

asyncFunc()
  .then(value => { /* success */ })
  .catch(error => { /* failure */ })
  .finally( => { /* cleanup */};

then()方法始终返回Promise,这使我们能够链接方法调用。

注意:如果没有传递回调,MongoClient的连接将返回一个Promise

我们也可以使用async/await语法来处理Promise

MongoDB JS 驱动程序

在第一个示例中,我们打印 Node.js 驱动程序的版本。

driver_version.js

const mongo = require('mongodb');
const MongoClient = mongo.MongoClient;

const url = 'mongodb://localhost:27017';

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

    if (err) throw err;

    console.log(client.topology.clientInfo);

    client.close();
});

在该示例中,我们连接到服务器并找到客户端信息。

const mongo = require('mongodb');

我们使用mongodb模块。

const client = mongo.MongoClient;

MongoClient用于连接到 MongoDB 服务器。

const url = 'mongodb://localhost:27017';

这是数据库的 URL。 27017 是 MongoDB 服务器监听的默认端口。

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

使用connect()创建到数据库的连接。

$ node driver_version.js
{ driver: { name: 'nodejs', version: '3.2.2' },
    os:
    { type: 'Windows_NT',
        name: 'win32',
        architecture: 'x64',
        version: '10.0.17134' },
    platform: 'Node.js v11.5.0, LE' }

驱动程序版本为 3.2.2。

MongoDB 列出数据库集合

listCollections()方法列出了数据库中的可用集合。

list_collections.js

const mongo = require('mongodb');

const MongoClient = mongo.MongoClient;

const url = 'mongodb://localhost:27017';

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

    if (err) throw err;

    const db = client.db("testdb");

    db.listCollections().toArray().then((docs) => {

        console.log('Available collections:');
        docs.forEach((doc, idx, array) => { console.log(doc.name) });

    }).catch((err) => {

        console.log(err);
    }).finally(() => {

        client.close();
    });
});

该示例连接到testdb数据库并检索其所有集合。

db.listCollections().toArray().then((docs) => {

    console.log('Available collections:');
    docs.forEach((doc, idx, array) => { console.log(doc.name) });
...

listCollection()方法在testdb数据库中找到所有集合; 它们被打印到控制台。

注意:我们应该谨慎使用toArray()方法,因为它会导致大量的内存使用。

}).catch((err) => {

    console.log(err);
}).finally(() => {

    client.close();
});

catch块中,我们捕获了任何潜在的异常,并在finally块中关闭了与数据库的连接。

注意:我们的应用是控制台程序; 因此,我们在程序结束时关闭连接。 在 Web 应用中,应重新使用连接。

$ node list_collections.js
Available collections:
continents
cars
cities

在我们的数据库中,我们有这三个集合。

MongoDB 数据库统计

dbstats()方法获取数据库的统计信息。

dbstats.js

const mongo = require('mongodb');

const MongoClient = mongo.MongoClient;
const url = 'mongodb://localhost:27017';

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

    if (err) throw err;

    const db = client.db("testdb");

    db.stats((err, stats) => {

        if (err) throw err;

        console.log(stats);

        client.close();
    })
});

该示例连接到testdb数据库并显示其统计信息。

$ node dbstats.js
{ db: 'testdb',
  collections: 3,
  views: 0,
  objects: 18,
  avgObjSize: 57.888888888888886,
  dataSize: 1042,
  storageSize: 69632,
  numExtents: 0,
  indexes: 3,
  indexSize: 69632,
  fsUsedSize: 136856346624,
  fsTotalSize: 254721126400,
  ok: 1 }

这是一个示例输出。

MongoDB 查找

find()函数为查询创建一个游标,该游标可用于遍历 MongoDB 的结果。

find_all.js

const mongo = require('mongodb');

const MongoClient = mongo.MongoClient;

const url = 'mongodb://localhost:27017';

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

    if (err) throw err;

    const db = client.db("testdb");

    db.collection('cars').find({}).toArray().then((docs) => {

        console.log(docs);

    }).catch((err) => {

        console.log(err);
    }).finally(() => {

        client.close();
    });
});

在示例中,我们从cars集合中检索所有文档。

db.collection('cars').find({}).toArray().then((docs) => {

传递空查询将返回所有文档。

$ node find_all.js
[ { _id: 5cfcfc3438f62aaa09b52175, name: 'Audi', price: 52642 },
  { _id: 5cfcfc3a38f62aaa09b52176, name: 'Mercedes', price: 57127 },
  { _id: 5cfcfc3f38f62aaa09b52177, name: 'Skoda', price: 9000 },
  { _id: 5cfcfc4338f62aaa09b52178, name: 'Volvo', price: 29000 },
  { _id: 5cfcfc4838f62aaa09b52179, name: 'Bentley', price: 350000 },
  { _id: 5cfcfc4b38f62aaa09b5217a, name: 'Citroen', price: 21000 },
  { _id: 5cfcfc4f38f62aaa09b5217b, name: 'Hummer', price: 41400 },
  { _id: 5cfcfc5438f62aaa09b5217c,
    name: 'Volkswagen',
    price: 21600 } ]

这是输出。

MongoDB 计数文档

count()函数返回集合中匹配文档的数量。

count_documents.js

const mongo = require('mongodb');
const MongoClient = mongo.MongoClient;

const url = 'mongodb://localhost:27017';

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

    if (err) throw err;

    const db = client.db("testdb");

    db.collection('cars').find({}).count().then((n) => {

        console.log(`There are ${n} documents`);

    }).catch((err) => {

        console.log(err);
    }).finally(() => {

        client.close();
    });
});

该示例计算cars集合中的文档数。

$ node count_documents.js
There are 8 documents

现在,汽车集合中有八个文档。

MongoDB findOne

findOne()方法返回一个满足指定查询条件的文档。 如果多个文档满足查询条件,则此方法将根据反映磁盘上文档顺序的自然顺序返回第一个文档。

find_one.js

const MongoClient = require('mongodb').MongoClient;

const url = 'mongodb://localhost:27017';

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

    if (err) throw err;

    const db = client.db("testdb");

    let collection = db.collection('cars');
    let query = { name: 'Volkswagen' }

    collection.findOne(query).then(doc => {

        console.log(doc);

    }).catch((err) => {

        console.log(err);
    }).finally(() => {

        client.close();
    });
});

该示例从cars集合中读取一个文档。

let query = { name: 'Volkswagen' }

该查询包含汽车的名称-大众汽车。

collection.findOne(query).then(doc => {

该查询将传递给findOne()方法。

$ node find_one.js
{ _id: 8, name: 'Volkswagen', price: 21600 }

这是示例的输出。

MongoDB 异步/等待示例

使用async/await,我们可以轻松地以同步方式处理Promise

async_await.js

const MongoClient = require('mongodb').MongoClient;

const url = 'mongodb://localhost:27017';

async function findCar() {

    const client = await MongoClient.connect(url, { useNewUrlParser: true })
        .catch(err => { console.log(err); });

    if (!client) {
        return;
    }

    try {

        const db = client.db("testdb");

        let collection = db.collection('cars');

        let query = { name: 'Volkswagen' }

        let res = await collection.findOne(query);

        console.log(res);

    } catch (err) {

        console.log(err);
    } finally {

        client.close();
    }
}

findCar();

该示例使用async/await读取一个文档。

async function findCar() {

该函数具有async关键字。

let res = await collection.findOne(query);

使用await,我们等待findOne()函数的结果。

MongoDB 查询运算符

可以使用 MongoDB 查询运算符(例如$gt$lt$ne)过滤数据。

read_gt.js

const mongo = require('mongodb');

const MongoClient = mongo.MongoClient;

const url = 'mongodb://localhost:27017';

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

    if (err) throw err;

    const db = client.db("testdb");

    let query = { price: { $gt: 30000 } };

    db.collection('cars').find(query).toArray().then((docs) => {

        console.log(docs);

    }).catch((err) => {

        console.log(err);
    }).finally(() => {

        client.close();
    });
});

该示例打印汽车价格大于 30,000 的所有文档。

let query = { price: { $gts: 30000 } };

$gt运算符用于获取价格大于 30,000 的汽车。

$ node read_gt.js
[ { _id: 5d03e40536943362cffc84a7, name: 'Audi', price: 52642 },
  { _id: 5d03e40a36943362cffc84a8, name: 'Mercedes', price: 57127 },
  { _id: 5d03e41936943362cffc84ab, name: 'Bentley', price: 350000 },
  { _id: 5d03e42236943362cffc84ad, name: 'Hummer', price: 41400 } ]

这是示例的输出。 仅包括价格超过 30,000 的汽车。

$and逻辑运算符可用于组合多个表达式。

read_gt_lt.js

const mongo = require('mongodb');
const MongoClient = mongo.MongoClient;

const url = 'mongodb://localhost:27017';

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

   if (err) throw err;

   const db = client.db("testdb");

   let query = { $and: [{ price: { $gt: 20000 } }, { price: { $lt: 50000 } }] };

   db.collection('cars').find(query).toArray().then((docs) => {

      console.log(docs);
   }).catch((err) => {

      console.log(err);
   }).finally(() => {

      client.close();
   });
});

在示例中,我们检索价格在 20,000 到 50,000 之间的汽车。

let query = { $and: [{ price: { $gt: 20000 } }, { price: { $lt: 50000 } }] };

$and运算符将$gt$lt组合在一起以获得结果。

$ node read_gt_lt.js
[ { _id: 5d03e41336943362cffc84aa, name: 'Volvo', price: 29000 },
  { _id: 5d03e41e36943362cffc84ac, name: 'Citroen', price: 21000 },
  { _id: 5d03e42236943362cffc84ad, name: 'Hummer', price: 41400 },
  { _id: 5d03e42636943362cffc84ae,
    name: 'Volkswagen',
    price: 21600 } ]

这是示例的输出。

MongoDB 预测

投影确定从数据库传递哪些字段。

projections.js

const mongo = require('mongodb');
const MongoClient = mongo.MongoClient;

const url = 'mongodb://localhost:27017';

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

   if (err) throw err;

   const db = client.db("testdb");

   db.collection('cars').find({}).project({_id: 0}).toArray().then((docs) => {

      console.log(docs);
   }).catch((err) => {

      console.log(err);
   }).finally(() => {

      client.close();
   });
});

该示例从输出中排除_id字段。

db.collection('cars').find({}).project({_id: 0}).toArray().then((docs) => {

project()方法设置查询的投影; 它不包括_id字段。

$ node projections.js
[ { name: 'Audi', price: 52642 },
  { name: 'Mercedes', price: 57127 },
  { name: 'Skoda', price: 9000 },
  { name: 'Volvo', price: 29000 },
  { name: 'Bentley', price: 350000 },
  { name: 'Citroen', price: 21000 },
  { name: 'Hummer', price: 41400 },
  { name: 'Volkswagen', price: 21600 } ]

这是示例的输出。

MongoDB 限制数据输出

limit()方法指定要返回的文档数,skip()方法指定要跳过的文档数。

skip_limit.js

const mongo = require('mongodb');
const MongoClient = mongo.MongoClient;

const url = 'mongodb://localhost:27017';

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

   if (err) throw err;

   const db = client.db("testdb");

   db.collection('cars').find({}).skip(2).limit(5).toArray().then((docs) => {

      console.log(docs);
   }).catch((err) => {

      console.log(err);
   }).finally(() => {

      client.close();
   });
});

该示例从cars集合中读取,跳过前两个文档,并将输出限制为五个文档。

db.collection('cars').find({}).skip(2).limit(5).toArray().then((docs) => {

skip()方法跳过前两个文档,limit()方法将输出限制为五个文档。

$ node skip_limit.js
[ { _id: 5d03e40f36943362cffc84a9, name: 'Skoda', price: 9000 },
  { _id: 5d03e41336943362cffc84aa, name: 'Volvo', price: 29000 },
  { _id: 5d03e41936943362cffc84ab, name: 'Bentley', price: 350000 },
  { _id: 5d03e41e36943362cffc84ac, name: 'Citroen', price: 21000 },
  { _id: 5d03e42236943362cffc84ad, name: 'Hummer', price: 41400 } ]

这是示例的输出。

MongoDB 聚合

聚合计算集合中数据的聚合值。

sum_all_cars.js

const mongo = require('mongodb');
const MongoClient = mongo.MongoClient;

const url = 'mongodb://localhost:27017';

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

   if (err) throw err;

   const db = client.db("testdb");

   let myagr = [{$group: {_id: 1, all: { $sum: "$price" } }}];

   db.collection('cars').aggregate(myagr).toArray().then((sum) => {

      console.log(sum);
   }).catch((err) => {

      console.log(err);
   }).finally(() => {

      client.close();
   });
});

该示例计算集合中所有汽车的价格。

let myagr = [{$group: {_id: 1, all: { $sum: "$price" } }}];

$sum运算符计算并返回数值的总和。 $group运算符通过指定的标识符表达式对输入文档进行分组,并将累加器表达式(如果指定)应用于每个组。

db.collection('cars').aggregate(myagr).toArray().then((sum) => {

aggregate()函数将聚合操作应用于cars集合。

$ node sum_all_cars.js
[ { _id: 1, all: 581769 } ]

所有价格的总和是 581,769。

我们可以使用$match运算符来选择要汇总的特定汽车。

sum_two_cars.js

const mongo = require('mongodb');
const MongoClient = mongo.MongoClient;

const url = 'mongodb://localhost:27017';

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

    if (err) throw err;

    const db = client.db("testdb");

    let myagr = [
        { $match: { $or: [{ name: "Audi" }, { name: "Volvo" }] } },
        { $group: { _id: 1, sum2cars: { $sum: "$price" } } }
    ];

    db.collection('cars').aggregate(myagr).toArray().then((sum) => {

        console.log(sum);
    }).catch((err) => {

        console.log(err);
    }).finally(() => {

        client.close();
    });
});

该示例计算奥迪和沃尔沃汽车的价格总和。

let myagr = [
    { $match: { $or: [{ name: "Audi" }, { name: "Volvo" }] } },
    { $group: { _id: 1, sum2cars: { $sum: "$price" } } }
];

该表达式使用$match$or$group$sum运算符执行任务。

$ node sum_two_cars.js
[ { _id: 1, sum2cars: 81642 } ]

两辆车的价格之和为 81,642。

MongoDB insertOne

insertOne()方法将单个文档插入到集合中。

insert_one.js

const mongo = require('mongodb');
const MongoClient = mongo.MongoClient;
const ObjectID = mongo.ObjectID;

const url = 'mongodb://localhost:27017';

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

    if (err) throw err;

    const db = client.db("testdb");

    let doc = {_id: new ObjectID(), name: "Toyota", price: 37600 };

    db.collection('cars').insertOne(doc).then((doc) => {

        console.log('Car inserted')
        console.log(doc);
    }).catch((err) => {

        console.log(err);
    }).finally(() => {

        client.close();
    });
});

该示例将一辆汽车插入cars集合。

let doc = {_id: new ObjectID(), name: "Toyota", price: 37600 };

这是要插入的文档。 使用ObjectID生成一个新的 ID。

db.collection('cars').insertOne(doc).then((doc) => {

insertOne()函数将文档插入到集合中。

> db.cars.find({name:'Toyota'})
{ "_id" : ObjectId("5d03d4321f9c262a50e671ee"), "name" : "Toyota", "price" : 37600 }

我们用mongo工具确认插入。

MongoDB insertMany

insertMany()函数可将多个文档插入一个集合中。

insert_many.js

const mongo = require('mongodb');
const MongoClient = mongo.MongoClient;
const ObjectID = mongo.ObjectID;

const url = 'mongodb://localhost:27017';

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

    if (err) throw err;

    const db = client.db("testdb");

    let collection = db.collection('continents');

    let continents = [
        { _id: new ObjectID(), name: "Africa" }, { _id: new ObjectID(), name: "America" },
        { _id: new ObjectID(), name: "Europe" }, { _id: new ObjectID(), name: "Asia" },
        { _id: new ObjectID(), name: "Australia" }, { _id: new ObjectID(), name: "Antarctica" }
    ];

    collection.insertMany(continents).then(result => {

        console.log("documents inserted into the collection");
    }).catch((err) => {

        console.log(err);
    }).finally(() => {

        client.close();
    });
});

该示例创建一个continents集合并将六个文档插入其中。

let collection = db.collection('continents');

collection()方法检索集合; 如果集合不存在,则会创建它。

let continents = [
    { _id: new ObjectID(), name: "Africa" }, { _id: new ObjectID(), name: "America" },
    { _id: new ObjectID(), name: "Europe" }, { _id: new ObjectID(), name: "Asia" },
    { _id: new ObjectID(), name: "Australia" }, { _id: new ObjectID(), name: "Antarctica" }
];

这是要插入新集合的六个记录的数组。 ObjectID()创建一个新的 ObjectID,这是用于标识文档的唯一值,而不是整数。

collection.insertMany(continents).then(result => {

    console.log("documents inserted into the collection");
}).catch((err) => {

    console.log(err);
}).finally(() => {

    client.close();
});

insertMany()方法将文档数组插入continents集合。

> db.continents.find()
{ "_id" : ObjectId("5cfcf97732fc4913748c9669"), "name" : "Africa" }
{ "_id" : ObjectId("5cfcf97732fc4913748c966a"), "name" : "America" }
{ "_id" : ObjectId("5cfcf97732fc4913748c966b"), "name" : "Europe" }
{ "_id" : ObjectId("5cfcf97732fc4913748c966c"), "name" : "Asia" }
{ "_id" : ObjectId("5cfcf97732fc4913748c966d"), "name" : "Australia" }
{ "_id" : ObjectId("5cfcf97732fc4913748c966e"), "name" : "Antarctica" }

continents集合已成功创建。

MongoDB deleteOne

deleteOne()方法用于删除文档。

delete_one.js

const mongo = require('mongodb');
const MongoClient = mongo.MongoClient;

const url = 'mongodb://localhost:27017';

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

    if (err) throw err;

    const db = client.db("testdb");

    let query = { name: "Volkswagen" };

    db.collection('cars').deleteOne(query).then((result) => {

        console.log('Car deleted');
        console.log(result);
    }).catch((err) => {

        console.log(err);
    }).finally(() => {

        client.close();
    });
});

该示例删除文档。

let query = { name: "Volkswagen" };

db.collection('cars').deleteOne(query).then((result) => {
...

deleteOne()删除Volkswagen的文档。

MongoDB updateOne

updateOne()函数用于更新文档。

update_one.js

const mongo = require('mongodb');
const MongoClient = mongo.MongoClient;

const url = 'mongodb://localhost:27017';

MongoClient.connect(url, { useNewUrlParser: true }, (err, client) => {

    if (err) throw err;

    const db = client.db("testdb");

    let filQuery = { name: "Audi" };
    let updateQuery = { $set: { "price": 52000 }};

    db.collection('cars').updateOne(filQuery, updateQuery).then(result => {

        console.log('Car updated');
        console.log(result);

    }).catch((err) => {

        console.log(err);
    }).finally(() => {

        client.close();
    });
});

该示例更新了汽车的价格。

let filQuery = { name: "Audi" };
let updateQuery = { $set: { "price": 52000 }};

db.collection('cars').updateOne(filQuery, updateQuery).then(result => {

通过updateOne()方法将 Audi 的价格更改为 52,000。 $set运算符用于更改价格。

> db.cars.find({name:'Audi'})
{ "_id" : ObjectId("5cfcfc3438f62aaa09b52175"), "name" : "Audi", "price" : 52000 }

我们使用mongo工具确认更改。

在本教程中,我们使用了 MongoDB 和 JavaScript。 列出所有 JavaScript 教程。

MongoDB Ruby 教程

原文: http://zetcode.com/db/mongodbruby/

在本教程中,我们展示了如何在 Ruby 中使用 MongoDB。 在 ZetCode 上有一个简洁的 Ruby 教程

Tweet

MongoDB 是 NoSQL 跨平台的面向文档的数据库。 它是可用的最受欢迎的数据库之一。 MongoDB 由 MongoDB Inc.开发,并作为免费和开源软件发布。

MongoDB 中的记录是一个文档,它是由字段和值对组成的数据结构。 MongoDB 文档与 JSON 对象相似。 字段的值可以包括其他文档,数组和文档数组。 MongoDB 将文档存储在集合中。 集合类似于关系数据库中的表以及行中的文档。

安装 MongoDB

以下命令可用于在基于 Debian 的 Linux 上安装 MongoDB。

$ sudo apt-get install mongodb

该命令将安装 MongoDB 随附的必要包。

$ sudo service mongodb status
mongodb start/running, process 975

使用sudo service mongodb status命令,我们检查mongodb服务器的状态。

$ sudo service mongodb start
mongodb start/running, process 6448

mongodb服务器由sudo service mongodb start命令启动。

$ sudo gem install mongo

MongoDB Ruby 驱动程序通过sudo gem install mongo命令安装。

建立数据库

mongo工具是 MongoDB 的交互式 JavaScript Shell 界面,它为系统管理员提供了一个界面,并为开发者提供了一种直接测试数据库查询和操作的方法。

$ mongo testdb
MongoDB shell version: 2.4.9
connecting to: testdb
> db
testdb
> db.cars.insert({name: "Audi", price: 52642})
> db.cars.insert({name: "Mercedes", price: 57127})
> db.cars.insert({name: "Skoda", price: 9000})
> db.cars.insert({name: "Volvo", price: 29000})
> db.cars.insert({name: "Bentley", price: 350000})
> db.cars.insert({name: "Citroen", price: 21000})
> db.cars.insert({name: "Hummer", price: 41400})
> db.cars.insert({name: "Volkswagen", price: 21600})

我们创建一个testdb数据库,并在cars集合中插入八个文档。

列出数据库集合

在数据库中Mongo::Clientcollections方法列出可用的集合。

list_collections.rb

#!/usr/bin/ruby

require 'mongo'

Mongo::Logger.logger.level = ::Logger::FATAL

client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb')

client.collections.each { |coll| puts coll.name }

`client.close`

该示例连接到testdb数据库并检索其所有集合。

require 'mongo'

我们包含mongo驱动程序。

Mongo::Logger.logger.level = ::Logger::FATAL

默认日志记录级别为::Logger::DEBUG,其中包含许多调试信息。 为了使输出更具可读性,我们选择::Logger::FATAL调试级别。

client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb')

Mongo::Client用于连接到 MongoDB 服务器。 我们指定 URL 和数据库名称。 27017 是 MongoDB 服务器监听的默认端口。

client.collections.each { |coll| puts coll.name }

我们遍历集合列表,并将其名称打印到控制台。

`client.close`

最后,我们关闭连接。 通常,不建议应用调用close。 连接很昂贵,正在重用。 但是,由于它是一个一次性程序,而不是长时间运行的可重用连接的应用,因此我们确实调用了该方法。

$ ./list_collections.rb 
test
cars

这是list_collections.rb程序的示例输出。

服务器选择超时

:server_selection_timeout是选择操作服务器的超时时间(以秒为单位)。 当我们无法连接到数据库服务器时,将引发Mongo::Error::NoServerAvailable

server_selection_timeout.rb

#!/usr/bin/ruby

require 'mongo'

Mongo::Logger.logger.level = ::Logger::DEBUG

begin

    client = Mongo::Client.new([ '127.0.0.1:2717' ], :database => "testdb", 
                               :server_selection_timeout => 5)

    client[:cars].find.each { |doc| puts doc }

    client.close

rescue Mongo::Error::NoServerAvailable => e

    p "Cannot connect to the server"
    p e

end

该示例的端口号错误。 默认情况下,服务器选择超时为三十秒。 我们将其设置为五秒钟。

rescue Mongo::Error::NoServerAvailable => e

未建立连接且超时已过期时,抛出Mongo::Error::NoServerAvailable

$ ./server_selection_timeout.rb 
D, [2016-05-02T15:32:20.231750 #8070] DEBUG -- : MONGODB | Adding 127.0.0.1:2717 to the cluster.
D, [2016-05-02T15:32:20.232486 #8070] DEBUG -- : MONGODB | Connection refused - connect(2)
D, [2016-05-02T15:32:20.732627 #8070] DEBUG -- : MONGODB | Connection refused - connect(2)
D, [2016-05-02T15:32:21.232724 #8070] DEBUG -- : MONGODB | Connection refused - connect(2)
...

调试日志记录级别在尝试连接到服务器时会提供这些消息。

数据库统计

dbstats命令获取数据库的统计信息。

dbstats.rb

#!/usr/bin/ruby

require 'mongo'

Mongo::Logger.logger.level = ::Logger::FATAL

client = Mongo::Client.new([ '127.0.0.1:27017' ])

db = client.use("testdb")

db.command({"dbstats" => 1}).documents[0].each do |key, value|

    puts "#{key}: #{value}"
end

`client.close`

该示例连接到testdb数据库并显示其统计信息。 数据库对象的command方法用于执行命令。

db = client.use("testdb")

use方法选择testdb数据库。

db.command({"dbstats" => 1}).documents[0].each do |key, value|

    puts "#{key}: #{value}"
end

command方法执行dbstats命令并解析返回的哈希值。

$ ./dbstats.rb 
db: testdb
collections: 4
objects: 21
avgObjSize: 43.23809523809524
dataSize: 908
storageSize: 16384
numExtents: 4
indexes: 2
indexSize: 16352
fileSize: 201326592
nsSizeMB: 16
dataFileVersion: {"major"=>4, "minor"=>5}
ok: 1.0

这是dbstats.rb程序的输出。

读取数据

find方法在集合中查找文档。

read_all.rb

#!/usr/bin/ruby

require 'mongo'

Mongo::Logger.logger.level = ::Logger::FATAL

client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb')

client[:cars].find.each { |doc| puts doc }

`client.close`

在示例中,我们遍历cars集合的所有数据。

client[:cars].find.each { |doc| puts doc }

传递空查询将返回所有文档。 我们使用each方法遍历:cars集合的文档。

$ ./read_all.rb 
{"_id"=>1, "name"=>"Audi", "price"=>52642}
{"_id"=>2, "name"=>"Mercedes", "price"=>57127}
{"_id"=>3, "name"=>"Skoda", "price"=>9000}
{"_id"=>4, "name"=>"Volvo", "price"=>29000}
{"_id"=>5, "name"=>"Bentley", "price"=>350000}
{"_id"=>6, "name"=>"Citroen", "price"=>21000}
{"_id"=>7, "name"=>"Hummer", "price"=>41400}
{"_id"=>8, "name"=>"Volkswagen", "price"=>21600}

这是read_all.rb示例的输出。

计数文档

count方法返回集合中匹配文档的数量。

count_documents.rb

#!/usr/bin/ruby

require 'mongo'

Mongo::Logger.logger.level = ::Logger::FATAL

client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb')

docs = client[:cars].find

puts "There are #{docs.count} documents"

`client.close`

该示例计算:cars集合中的文档数。

docs = client[:cars].find

我们从cars集合中检索所有文档。

puts "There are #{docs.count} documents"

我们打印退回文档的数量。

$ ./count_documents.rb 
There are 8 documents

cars集合中有八个文档。

读取文档

find方法采用可选的filter参数,该参数用于过滤传入的数据。

read_one.rb

#!/usr/bin/ruby

require 'mongo'

Mongo::Logger.logger.level = ::Logger::FATAL

client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb')

client[:cars].find(:name => 'Volkswagen').each do |doc|

    puts doc
end

`client.close`

该示例从:cars集合中读取一个文档。

client[:cars].find(:name => 'Volkswagen').each do |doc|

find方法仅显示包含大众汽车的文档。

$ ./read_one.rb 
{"_id"=>8, "name"=>"Volkswagen", "price"=>21600}

这是示例的输出。

查询运算符

可以使用 MongoDB 查询运算符(例如$gt$lt$ne)过滤数据。

read_op.rb

#!/usr/bin/ruby

require 'mongo'

Mongo::Logger.logger.level = ::Logger::FATAL

client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb')

puts client[:cars].find("price" => {'$lt' => 30000}).to_a

puts "**************************"

client[:cars].find("price" => {'$gt' => 30000}).each do |doc|

    puts doc
end

`client.close`

该示例打印所有汽车价格低于 30,000 的文档,然后打印所有汽车价格高于 30,000 的文档。

puts client[:cars].find("price" => {'$lt' => 30000}).to_a

$lt运算符用于获取价格低于 30,000 的汽车。

$ ./read_op.rb 
{"_id"=>3, "name"=>"Skoda", "price"=>9000}
{"_id"=>4, "name"=>"Volvo", "price"=>29000}
{"_id"=>6, "name"=>"Citroen", "price"=>21000}
{"_id"=>8, "name"=>"Volkswagen", "price"=>21600}
**************************
{"_id"=>1, "name"=>"Audi", "price"=>52642}
{"_id"=>2, "name"=>"Mercedes", "price"=>57127}
{"_id"=>5, "name"=>"Bentley", "price"=>350000}
{"_id"=>7, "name"=>"Hummer", "price"=>41400}

这是示例的输出。

$and$or逻辑运算符可用于组合多个表达式。

read_and_or.rb

#!/usr/bin/ruby

require 'mongo'

Mongo::Logger.logger.level = ::Logger::FATAL

client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb')

puts client[:cars].find('$or' => [{:name => "Audi"}, {:name => "Skoda" }]).to_a

puts "*************************************"

puts client[:cars].find('$and' => [{:price => {'$gt' => 20000}}, 
    {:price => {'$lt' => 50000 }}]).to_a

client.close    

该示例同时显示了$or$and运算符。

puts client[:cars].find('$or' => [{:name => "Audi"}, {:name => "Skoda" }]).to_a

$or运算符用于返回名称为 Audi 或 Skoda 的文档。

puts client[:cars].find('$and' => [{:price => {'$gt' => 20000}}, 
    {:price => {'$lt' => 50000 }}]).to_a

$and运算符检索价格介于 20,000 和 50,000 之间的汽车。

$ ./read_and_or.rb 
{"_id"=>1, "name"=>"Audi", "price"=>52642}
{"_id"=>3, "name"=>"Skoda", "price"=>9000}
*************************************
{"_id"=>4, "name"=>"Volvo", "price"=>29000}
{"_id"=>6, "name"=>"Citroen", "price"=>21000}
{"_id"=>7, "name"=>"Hummer", "price"=>41400}
{"_id"=>8, "name"=>"Volkswagen", "price"=>21600}

这是示例的输出。

投影

投影确定要在结果集中的每个文档中包括或排除的字段。

projection.rb

#!/usr/bin/ruby

require 'mongo'

Mongo::Logger.logger.level = ::Logger::FATAL

client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb')

cursor = client[:cars].find({}, { :projection => {:_id => 0} })

cursor.each { |doc| puts doc }

`client.close`

该示例从输出中排除_id字段。

cursor = client[:cars].find({}, { :projection => {:_id => 0} })

我们在find方法的第二个参数中指定:projection选项。

$ ./projection.rb 
{"name"=>"Audi", "price"=>52642}
{"name"=>"Mercedes", "price"=>57127}
{"name"=>"Skoda", "price"=>9000}
{"name"=>"Volvo", "price"=>29000}
{"name"=>"Bentley", "price"=>350000}
{"name"=>"Citroen", "price"=>21000}
{"name"=>"Hummer", "price"=>41400}
{"name"=>"Volkswagen", "price"=>21600}

这是示例的输出。 尚未包含_id

限制数据输出

limit方法指定要返回的文档数,skip方法指定要跳过的文档数。

skip_limit.rb

#!/usr/bin/ruby

require 'mongo'

Mongo::Logger.logger.level = ::Logger::FATAL

client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb')

docs = client[:cars].find().skip(2).limit(5)

docs.each do |doc|

    puts doc
end

`client.close`

该示例从testdb.cars集合中读取,跳过前两个文档,并将输出限制为五个文档。

docs = client[:cars].find().skip(2).limit(5)

skip方法跳过前两个文档,limit方法将输出限制为五个文档。

$ ./skip_limit.rb 
{"_id"=>3, "name"=>"Skoda", "price"=>9000}
{"_id"=>4, "name"=>"Volvo", "price"=>29000}
{"_id"=>5, "name"=>"Bentley", "price"=>350000}
{"_id"=>6, "name"=>"Citroen", "price"=>21000}
{"_id"=>7, "name"=>"Hummer", "price"=>41400}

这是示例的输出。

聚合

聚合计算集合中数据的聚合值。

sum_all_cars.rb

#!/usr/bin/ruby

require 'mongo'

Mongo::Logger.logger.level = ::Logger::FATAL

agr = [{"$group" => {:_id => 1, :all => { "$sum" => "$price" } }}];

client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb')

client[:cars].aggregate(agr).each { |doc| puts doc }

该示例计算集合中所有汽车的价格。

agr = [{"$group" => {:_id => 1, :all => { "$sum" => "$price" } }}];

$sum运算符计算并返回数值的总和。 $group运算符通过指定的标识符表达式对输入文档进行分组,并将累加器表达式(如果指定)应用于每个组。

client[:cars].aggregate(agr).each { |doc| puts doc }

aggregate方法将聚合操作应用于cars集合。

$ ./sum_all_cars.rb 
{"_id"=>1, "all"=>609727}

所有价格的总和是 619,369。

我们可以使用$match运算符来选择要汇总的特定汽车。

sum_two_cars.rb

#!/usr/bin/ruby

require 'mongo'

Mongo::Logger.logger.level = ::Logger::FATAL

agr = [{"$match" => {"$or" => [ { :name => "Audi" }, { :name => "Volvo" }]}}, 
       {"$group" => {:_id => 1, :sumOfTwo => { "$sum" => "$price" } }}];

client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb')

client[:cars].aggregate(agr).each { |doc| puts doc }

`client.close`

该示例计算奥迪和沃尔沃汽车的价格总和。

agr = [{"$match" => {"$or" => [ { :name => "Audi" }, { :name => "Volvo" }]}}, 
       {"$group" => {:_id => 1, :sumOfTwo => { "$sum" => "$price" } }}];

该表达式使用$match$or$group$sum运算符执行任务。

$ ./sum_two_cars.rb 
{"_id"=>1, "sumOfTwo"=>81000}

两辆车的价格之和为 81,642。

插入文档

insert_one方法将单个文档插入到集合中。

insert_doc.rb

#!/usr/bin/ruby

require 'mongo'

Mongo::Logger.logger.level = ::Logger::FATAL

client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb')

doc = { :_id => 9, :name => "Toyota", :price => 37600 } 

client[:cars].insert_one doc 

`client.close`

该示例将一辆汽车插入cars集合。

doc = { :_id => 9, :name => "Toyota", :price => 37600 } 

这是要插入的文档。

client[:cars].insert_one doc 

insert_one方法将文档插入到集合中。

> db.cars.find({_id:9})
{ "_id" : 9, "name" : "Toyota", "price" : 37600 }

我们用mongo工具确认插入。

插入许多文档

insert_many方法将多个文档插入一个集合中。

create_collection.rb

#!/usr/bin/ruby

require 'mongo'

Mongo::Logger.logger.level = ::Logger::FATAL

client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb')

result = client[:continents].insert_many([
  { :_id => BSON::ObjectId.new, :name => 'Africa' },
  { :_id => BSON::ObjectId.new, :name => 'America' },
  { :_id => BSON::ObjectId.new, :name => 'Antarctica' },
  { :_id => BSON::ObjectId.new, :name => 'Australia' },
  { :_id => BSON::ObjectId.new, :name => 'Asia' },
  { :_id => BSON::ObjectId.new, :name => 'Europe' }
])

puts "#{result.inserted_count} documents were inserted"

`client.close`

该示例创建一个大洲集合并将六个文档插入其中。

result = client[:continents].insert_many([
  { :_id => BSON::ObjectId.new, :name => 'Africa' },
  { :_id => BSON::ObjectId.new, :name => 'America' },
  { :_id => BSON::ObjectId.new, :name => 'Antarctica' },
  { :_id => BSON::ObjectId.new, :name => 'Australia' },
  { :_id => BSON::ObjectId.new, :name => 'Asia' },
  { :_id => BSON::ObjectId.new, :name => 'Europe' }
])

使用insert_many方法将六个记录的数组插入到新集合中。 BSON::ObjectId.new()创建一个新的ObjectID,这是用于标识文档的唯一值,而不是整数。

puts "#{result.inserted_count} documents were inserted"

返回结果的inserted_count给出成功插入的文档数。

> db.continents.find()
{ "_id" : ObjectId("57263c0f81365b266b17358c"), "name" : "Africa" }
{ "_id" : ObjectId("57263c0f81365b266b17358d"), "name" : "America" }
{ "_id" : ObjectId("57263c0f81365b266b17358e"), "name" : "Antarctica" }
{ "_id" : ObjectId("57263c0f81365b266b17358f"), "name" : "Australia" }
{ "_id" : ObjectId("57263c0f81365b266b173590"), "name" : "Asia" }
{ "_id" : ObjectId("57263c0f81365b266b173591"), "name" : "Europe" }

continents集合已成功创建。

修改文档

delete_one方法用于删除文档,update_one用于更新文档。

mofify.js

#!/usr/bin/ruby

require 'mongo'

Mongo::Logger.logger.level = ::Logger::FATAL

client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb')

client[:cars].delete_one({:name => "Skoda"})
client[:cars].update_one({:name => "Audi"}, '$set' => {:price => 52000})

`client.close`

该示例删除包含 Skoda 的文档并更新 Audi 的价格。

client[:cars].delete_one({:name => "Skoda"})

delete_one删除Skoda的文档。

client[:cars].update_one({:name => "Audi"}, '$set' => {:price => 52000})

通过update_one方法将 Audi 的价格更改为 52,000。 $set运算符用于更改价格。

> db.cars.find()
{ "_id" : 1, "name" : "Audi", "price" : 52000 }
{ "_id" : 2, "name" : "Mercedes", "price" : 57127 }
{ "_id" : 4, "name" : "Volvo", "price" : 29000 }
{ "_id" : 5, "name" : "Bentley", "price" : 350000 }
{ "_id" : 6, "name" : "Citroen", "price" : 21000 }
{ "_id" : 7, "name" : "Hummer", "price" : 41400 }
{ "_id" : 8, "name" : "Volkswagen", "price" : 21600 }
{ "_id" : 9, "name" : "Toyota", "price" : 37600 }

我们使用mongo工具确认更改。

在本教程中,我们使用了 MongoDB 和 Ruby。

Spring JdbcTemplate教程

原文: http://zetcode.com/db/jdbctemplate/

Spring JdbcTemplate教程展示了如何使用 Spring 的JdbcTemplate处理数据。 我们是 MySQL 数据库。 我们创建使用JdbcTemplate的经典 Spring 和 Spring Boot 应用。 ZetCode 拥有用于 MySQL Java 的完整电子书,其中包含扩展的JdbcTemplate章节: MySQL Java 编程电子书

Tweet

目录

  1. 在 MySQL 中创建数据库
  2. Maven 依赖项
  3. queryForObject()方法
  4. RowMapper
  5. BeanPropertyRowMapper
  6. queryForList()方法
  7. 使用命名参数
  8. 使用JdbcTemplate的经典 Spring
  9. 使用JdbcTemplate的 Spring Boot

Spring

Spring 是流行的 Java 应用框架。 JdbcTemplate是用于简化 JDBC 编程的工具。 它处理乏味且容易出错的底层细节,例如处理事务,清理资源以及正确处理异常。 JdbcTemplate包含在 Spring 的spring-jdbc模块中。 Spring Boot 是 Spring 的解决方案,用于创建独立的,生产级的基于 Spring 的应用。

MySQL 是一个开源关系数据库管理系统。 它是最受欢迎的数据库之一。 它通常在 Web 应用中使用。

MySQL 创建数据库

我们使用mysql监视器创建一个新的testdb数据库。

cars_mysql.sql

DROP TABLE IF EXISTS cars;
CREATE TABLE cars(id INT PRIMARY KEY AUTO_INCREMENT,
                  name VARCHAR(255), price INTEGER) ENGINE=InnoDB;

INSERT INTO cars(name, price) VALUES('Audi', 52642);
INSERT INTO cars(name, price) VALUES('Mercedes', 57127);
INSERT INTO cars(name, price) VALUES('Skoda', 9000);
INSERT INTO cars(name, price) VALUES('Volvo', 29000);
INSERT INTO cars(name, price) VALUES('Bentley', 350000);
INSERT INTO cars(name, price) VALUES('Citroen', 21000);
INSERT INTO cars(name, price) VALUES('Hummer', 41400);
INSERT INTO cars(name, price) VALUES('Volkswagen', 21600);

这是在 MySQL 中创建cars表的 SQL。

要创建数据库和表,我们使用mysql监视工具。

$ sudo service mysql start

MySQL 用sudo service mysql start命令启动。

$ mysql -u user7 -p

我们使用mysql监视器连接到数据库。

mysql> CREATE DATABASE testdb;
Query OK, 1 row affected (0.02 sec)

使用CREATE DATABASE语句创建一个新数据库。

mysql> USE testdb;
mysql> SOURCE cars_mysql.sql

使用source命令,加载并执行cars_mysql.sql文件。

mysql> SELECT * FROM cars;
+----+------------+--------+
| id | name       | price  |
+----+------------+--------+
|  1 | Audi       |  52642 |
|  2 | Mercedes   |  57127 |
|  3 | Skoda      |   9000 |
|  4 | Volvo      |  29000 |
|  5 | Bentley    | 350000 |
|  6 | Citroen    |  21000 |
|  7 | Hummer     |  41400 |
|  8 | Volkswagen |  21600 |
+----+------------+--------+
8 rows in set (0.00 sec)

我们验证数据。

Maven 依赖

对于我们的应用,我们需要下载数据库驱动程序和 Spring 模块。 我们使用 Maven 做到这一点。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.1.3.RELEASE</version>
</dependency>

这将下载spring-jdbc模块。

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

这是 MySQL 驱动程序的 Maven 依赖关系。

queryForObject()方法

queryForObject()方法执行 SQL 查询并返回结果对象。 结果类型在参数中指定。

com/zetcode/SpringDBQueryObjectEx.java

package com.zetcode;

import java.sql.SQLException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;

public class SpringDBQueryObjectEx {

    public static void main(String[] args) throws SQLException {

        var ds = new SimpleDriverDataSource();
        ds.setDriver(new com.mysql.jdbc.Driver());
        ds.setUrl("jdbc:mysql://localhost:3306/testdb");
        ds.setUsername("user7");
        ds.setPassword("s$cret");

        var sql = "SELECT COUNT(*) FROM cars";

        var jtm = new JdbcTemplate(ds);
        int numOfCars = jtm.queryForObject(sql, Integer.class);

        System.out.format("There are %d cars in the table", numOfCars);
    }
}

在示例中,我们使用queryForObject()方法获取cars表中的汽车数量。

var sql = "SELECT COUNT(*) FROM cars";

该 SQL 返回cars表中的行数。

int numOfCars = jtm.queryForObject(sql, Integer.class);

queryForObject()方法的第二个参数指定结果的类型; 在我们的案例中是Integer

行映射器

RowMapper逐行映射结果集的行。 此接口的实现执行将每一行映射到结果对象的实际工作。

com/zetcode/model/Car.java

package com.zetcode.model;

import java.util.Objects;

public class Car {

    private Long id;
    private String name;
    private int price;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o;
        return price == car.price &&
                Objects.equals(id, car.id) &&
                Objects.equals(name, car.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, price);
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Car{");
        sb.append("id=").append(id);
        sb.append(", name='").append(name).append('\'');
        sb.append(", price=").append(price);
        sb.append('}');
        return sb.toString();
    }
}

我们有一个Car bean。 它具有idnameprice属性。

com/zetcode/SpringDBRowMapper.java

package com.zetcode;

import java.sql.ResultSet;
import java.sql.SQLException;

import com.zetcode.model.Car;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;

public class SpringDBRowMapper {

    public static void main(String[] args) throws SQLException {

        var ds = new SimpleDriverDataSource();
        ds.setDriver(new com.mysql.jdbc.Driver());
        ds.setUrl("jdbc:mysql://localhost:3306/testdb");
        ds.setUsername("user7");
        ds.setPassword("s$cret");

        var rm = (RowMapper<Car>) (ResultSet result, int rowNum) -> {

            var car = new Car();

            car.setId(result.getLong("id"));
            car.setName(result.getString("name"));
            car.setPrice(result.getInt("price"));

            return car;
        };

        var sql = "SELECT * FROM cars WHERE id=?";
        Long id = 1L;

        var jtm = new JdbcTemplate(ds);
        var car = (Car) jtm.queryForObject(sql, new Object[]{id}, rm);

        System.out.println(car);
    }
}

在示例中,我们使用RowMapper将结果集的行映射到Car对象。

var rm = (RowMapper<Car>) (ResultSet result, int rowNum) -> {

    var car = new Car();
    car.setId(result.getLong("id"));
    car.setName(result.getString("name"));
    car.setPrice(result.getInt("price"));

    return car;
};

这是结果集行到Car对象的映射。

var car = (Car) jtm.queryForObject(sql, new Object[] {id}, rm);

RowMapper的实例作为第三个参数传递到queryForObject()

BeanPropertyRowMapper

BeanPropertyRowMapper是一种RowMapper实现,可将行转换为指定映射目标类的新实例。 映射的目标类必须是顶级类,并且必须具有默认或无参数构造器。 结果集元数据中的列名与相应属性的公共设置器匹配。

com/zetcode/model/Car.java

package com.zetcode.model;

public class Car {

    private Long id;
    private String name;
    private int price;

    // getters and setters etc.
}

这是我们将cars表行映射到的Car bean。

com/zetcode/SpringBeanPropertyRowMapper.java

package com.zetcode;

import com.zetcode.model.Car;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;

import java.sql.SQLException;

public class SpringBeanPropertyRowMapper {

    public static void main(String[] args) throws SQLException {

        var dataSource = new SimpleDriverDataSource();

        dataSource.setDriver(new com.mysql.jdbc.Driver());
        dataSource.setUrl("jdbc:mysql://localhost:3306/testdb");
        dataSource.setUsername("user7");
        dataSource.setPassword("s$cret");

        var sql = "SELECT * FROM cars WHERE id=?";
        Long id = 1L;

        var jtm = new JdbcTemplate(dataSource);

        var car = (Car) jtm.queryForObject(sql, new Object[]{id},
                 BeanPropertyRowMapper.newInstance(Car.class));

        System.out.println(car);
    }
}

该示例连接到testdb数据库,并从cars表中检索一辆汽车。

var sql = "SELECT * FROM cars WHERE id=?";

该 SQL 语句从数据库中选择汽车对象。

var jtm = new JdbcTemplate(dataSource);

JdbcTemplate已创建; 它以数据源为参数。

var car = (Car) jtm.queryForObject(sql, new Object[]{id},
    BeanPropertyRowMapper.newInstance(Car.class));

使用queryForObject()方法,我们查询一个对象。 我们提供了 SQL 语句,参数和行映射器。 BeanPropertyRowMapper将一行转换为Car目标类的新实例。

System.out.println(car);

取回的汽车被打印到终端。

Car{id=1, name='Audi', price=52642}

应用从cars表中打印第一行。

queryForList()方法

queryForList()方法执行对结果列表的查询。 在以下示例中,我们从cars表中检索所有汽车。

pom.xml
src
├───main
│   ├───java
│   │   └───com
│   │       └───zetcode
│   │               SpringDBQueryForList.java
│   └───resources
│           db.properties
└───test
    └───java

这是项目结构。

我们将数据源属性放入db.properties文件。 最好将资源与 Java 文件分开。

resources/db.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/testdb
jdbc.username=user7
jdbc.password=s$cret

这些是 MySQL 数据库的属性。

com/zetcode/SpringDBQueryForList.java

package com.zetcode;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Driver;
import java.util.List;
import java.util.Map;
import java.util.Properties;

public class SpringDBQueryForList {

    public static void main(String[] args) throws IOException,
            ClassNotFoundException {

        var prop = new Properties();
        prop.load(new FileInputStream("src/main/resources/db.properties"));

        var ds = new SimpleDriverDataSource();

        ds.setDriverClass(((Class<Driver>) Class.forName(prop.getProperty("jdbc.driver"))));
        ds.setUrl(prop.getProperty("jdbc.url"));
        ds.setUsername(prop.getProperty("jdbc.username"));
        ds.setPassword(prop.getProperty("jdbc.password"));

        var sql = "SELECT * FROM cars";

        var jtm = new JdbcTemplate(ds);
        var rows = (List<Map<String, Object>>) jtm.queryForList(sql);

        rows.forEach(System.out::println);
    }
}

该示例连接到 MySQL testdb数据库,并从cars表中检索所有行。

var prop = new Properties();
prop.load(new FileInputStream("src/main/resources/db.properties"));

数据源属性是从db.properties文件加载的。

var ds = new SimpleDriverDataSource();

ds.setDriverClass(((Class<Driver>) Class.forName(prop.getProperty("jdbc.driver"))));
ds.setUrl(prop.getProperty("jdbc.url"));
ds.setUsername(prop.getProperty("jdbc.username"));
ds.setPassword(prop.getProperty("jdbc.password"));

我们用属性填充SimpleDriverDataSource's属性。

var jtm = new JdbcTemplate(ds);
var rows = (List<Map<String, Object>>) jtm.queryForList(sql);

JdbcTemplatequeryForList()方法返回表中的行列表。

rows.forEach(System.out::println);

我们遍历列表并将数据打印到终端。

{id=1, name=Audi, price=52642}
{id=2, name=Mercedes, price=57127}
{id=3, name=Skoda, price=9000}
{id=4, name=Volvo, price=29000}
{id=5, name=Bentley, price=350000}
{id=6, name=Citroen, price=21000}
{id=7, name=Hummer, price=41400}
{id=8, name=Volkswagen, price=21600}

这是示例的输出。

使用命名参数

NamedParameterJdbcTemplate是具有一组基本 JDBC 操作的模板类,允许使用命名参数而不是传统的?占位符。

resources/db.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/testdb
jdbc.username=user7
jdbc.password=s$cret

这些是 MySQL 数据库的属性。

com/zetcode/model/Car.java

package com.zetcode.model;

public class Car {

    private Long id;
    private String name;
    private int price;

    // getters and setters etc.
}

这是Car bean。

com/zetcode/SpringDBNamedParameters.java

package com.zetcode;

import com.zetcode.model.Car;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Driver;
import java.util.Properties;

public class SpringDBNamedParameters {

    public static void main(String[] args) throws IOException,
            ClassNotFoundException {

        var prop = new Properties();
        prop.load(new FileInputStream("src/main/resources/db.properties"));

        var ds = new SimpleDriverDataSource();
        ds.setDriverClass(((Class<Driver>) Class.forName(prop.getProperty("jdbc.driver"))));
        ds.setUrl(prop.getProperty("jdbc.url"));
        ds.setUsername(prop.getProperty("jdbc.username"));
        ds.setPassword(prop.getProperty("jdbc.password"));

        var sql = "SELECT * FROM cars WHERE name LIKE :name";
        var carName = "Volvo";

        var jtm = new NamedParameterJdbcTemplate(ds);

        var namedParams = new MapSqlParameterSource("name", carName);
        var car = (Car) jtm.queryForObject(sql, namedParams,
                BeanPropertyRowMapper.newInstance(Car.class));

        System.out.println(car);
    }
}

该示例查找汽车名称; 其 SQL 代码使用命名参数。

var sql = "SELECT * FROM cars WHERE Name LIKE :name";

SQL 具有:name令牌,这是一个命名参数。

var jtm = new NamedParameterJdbcTemplate(ds);

NamedParameterJdbcTemplate用于命名参数。

var namedParams = new MapSqlParameterSource("name", carName);

MapSqlParameterSource用于将参数值的简单映射传递给NamedParameterJdbcTemplate类的方法。

var car = (Car) jtm.queryForObject(sql, namedParams,
        BeanPropertyRowMapper.newInstance(Car.class));

命名的参数作为第二个参数传递给queryForObject()方法。

Car{id=4, name='Volvo', price=29000}

这是示例的输出。

使用JdbcTemplate的经典 Spring 示例

在下面的示例中,我们创建一个经典的命令行 Spring 应用,该应用使用JdbcTemplate连接到数据库并发出 SQL 语句。

pom.xml
src
├───main
│   ├───java
│   │   └───com
│   │       └───zetcode
│   │           │   ClassicSpringJdbcTemplate.java
│   │           ├───config
│   │           │       DBConfig.java
│   │           ├───model
│   │           │       Car.java
│   │           └───service
│   │                   CarService.java
│   │                   ICarService.java
│   └───resources
│           db.properties
└───test
    └───java

这是项目结构。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zetcode</groupId>
    <artifactId>classicspringex</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <spring-version>5.1.3.RELEASE</spring-version>

    </properties>

    <dependencies>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring-version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring-version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring-version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring-version}</version>
        </dependency>

    </dependencies>

</project>

Maven pom.xml文件包含 MySQL 驱动程序,核心 Spring 库和 JdbcTemplate 的依赖项。

com/zetcode/model/Car.java

package com.zetcode.model;

public class Car {

    private Long id;
    private String name;
    private int price;

    // getters and setters etc.
}

这是Car bean。

resources/db.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/testdb
jdbc.username=user7
jdbc.password=s$cret

这些是数据库属性。

com/zetcode/config/DBConfig.java

package com.zetcode.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

@Configuration
@PropertySource(value = "classpath:db.properties", ignoreResourceNotFound = true)
public class DBConfig {

    @Autowired
    private Environment env;

    @Bean
    public DataSource dataSource() {

        var dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(env.getProperty("jdbc.driver"));
        dataSource.setUrl(env.getProperty("jdbc.url"));
        dataSource.setUsername(env.getProperty("jdbc.username"));
        dataSource.setPassword(env.getProperty("jdbc.password"));

        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate() {

        var template = new JdbcTemplate();
        template.setDataSource(dataSource());

        return template;
    }
}

DBConfig生成两个 bean:dataSourcejdbcTemplate。 从db.properties中读取数据库属性。

com/zetcode/service/ICarService.java

package com.zetcode.service;

import com.zetcode.model.Car;

import java.util.List;

public interface ICarService {

    Car findById(Long id);
    List<Car> all();
}

ICarService定义了两种签约方法:findById()all()

com/zetcode/service/CarService.java

package com.zetcode.service;

import com.zetcode.model.Car;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class CarService implements ICarService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public List<Car> all() {

        return jdbcTemplate.query("SELECT * FROM cars",
                BeanPropertyRowMapper.newInstance(Car.class));
    }

    public Car findById(Long id) {

        var sql = "SELECT * FROM cars WHERE id=?";

        return jdbcTemplate.queryForObject(sql, new Object[]{id},
                BeanPropertyRowMapper.newInstance(Car.class));
    }
}

CarService具有与JdbcTemplate一起使用的代码。

@Autowired
private JdbcTemplate jdbcTemplate;

Spring 允许使用@Autowired注入依赖项。 使用字段注入,我们添加了DBConfig中生成的jdbcTemplate bean。

com/zetcode/ClassicSpringJdbcTemplate.java

package com.zetcode;

import com.zetcode.model.Car;
import com.zetcode.service.ICarService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan("com.zetcode")
public class ClassicSpringJdbcTemplate {

    public static void main(String[] args) {

        var ctx = new AnnotationConfigApplicationContext(ClassicSpringJdbcTemplate.class);
        var app = ctx.getBean(ClassicSpringJdbcTemplate.class);

        app.run();

        ctx.close();
    }

    @Autowired
    private ICarService carService;

    private void run() {

        System.out.println("Fetching a car with Id 3");
        Long id = 3L;
        var car = (Car) carService.findById(id);
        System.out.println(car);

        System.out.println("Fetching all cars");
        var cars = carService.all();
        cars.forEach(System.out::println);
    }
}

该示例使用JdbcTemplate从表中检索特定行和所有行。

var ctx = new AnnotationConfigApplicationContext(ClassicSpringJdbcTemplate.class);

AnnotationConfigApplicationContext允许创建带有特定注解的 Spring bean,例如@Service@Configuration

@Autowired
private CarService carService;

数据库功能委托给CarService,并随同@Autowired一起注入。

private void run() {

    System.out.println("Fetching a car with Id 3");
    Long id = 3L;
    var car = (Car) carService.findById(id);
    System.out.println(car);

    System.out.println("Fetching all cars");
    var cars = carService.all();
    cars.forEach(System.out::println);
}

我们调用服务方法来获取特定行并获取所有行。

Fetching a car with Id 3
Car{id=3, name='Skoda', price=9000}
Fetching all cars
Car{id=1, name='Audi', price=52642}
Car{id=2, name='Mercedes', price=57127}
Car{id=3, name='Skoda', price=9000}
Car{id=4, name='Volvo', price=29000}
Car{id=5, name='Bentley', price=350000}
Car{id=6, name='Citroen', price=21000}
Car{id=7, name='Hummer', price=41400}
Car{id=8, name='Volkswagen', price=21600}

这是输出。

使用JdbcTemplate的 Spring Boot 示例

在这个例子中,我们创建一个命令行 Spring Boot 应用,它将使用JdbcTemplate连接到数据库。 我们将有两个数据源:一个用于 Derby,一个用于 MySQL。 该项目可在作者的 Github 页面上找到。

pom.xml
src
├───main
│   ├───java
│   │   └───com
│   │       └───zetcode
│   │           │   Application.java
│   │           │   MyRunner.java
│   │           ├───config
│   │           │       AppConfig.java
│   │           ├───model
│   │           │       Car.java
│   │           ├───repository
│   │           │       CarRepository.java
│   │           │       ICarRepository.java
│   │           └───service
│   │                   CarService.java
│   │                   ICarService.java
│   └───resources
│           application.properties
└───test
    └───java

这是项目结构。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zetcode</groupId>
    <artifactId>springbootjdbctemplate</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
    </parent>

    <dependencies>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

pom.xml文件包含 Spring Boot 和 MySQL 的依赖项。

resources/application.properties

mysql.datasource.driverClassName=com.mysql.jdbc.Driver
mysql.datasource.jdbcUrl=jdbc:mysql://localhost:3306/testdb
mysql.datasource.username=user7
mysql.datasource.password=s$cret

application.properties文件中,我们定义 MySQL 数据源。

com/zetcode/config/AppConfig.java

package com.zetcode.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class AppConfig {

    @Bean
    @ConfigurationProperties(prefix = "mysql.datasource")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }
}

AppConfig中,我们使用DataSourceBuilder创建一个 MySQL 数据源。

com/zetcode/model/Car.java

package com.zetcode.model;

public class Car {

    private Long id;
    private String name;
    private int price;

    // getters and setters etc.
}

这是Car bean。

com/zetcode/repository/ICarRepository.java

package com.zetcode.repository;

import com.zetcode.model.Car;

import java.util.List;

public interface ICarRepository {

    void saveCar(Car car);
    Car findCarByName(String name);
    List<Car> findAll();
}

ICarRepository包含以下方法:保存新车,以其名称取回汽车并取回所有汽车。

com/zetcode/repository/CarRepository.java

package com.zetcode.repository;

import com.zetcode.model.Car;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class CarRepository implements ICarRepository {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void saveCar(Car car) {

        var sql = "INSERT INTO cars(name, price) VALUES (?, ?)";
        Object[] params = new Object[] {car.getName(), car.getPrice()};

        jdbcTemplate.update(sql, params);
    }

    @Override
    public Car findCarByName(String name) {

        var sql = "SELECT * FROM cars WHERE name = ?";
        Object[] param = new Object[] {name};

        return jdbcTemplate.queryForObject(sql, param,
                BeanPropertyRowMapper.newInstance(Car.class));
    }

    @Override
    public List<Car> findAll() {

        var sql = "SELECT * FROM cars";

        return jdbcTemplate.query(sql, BeanPropertyRowMapper.newInstance(Car.class));
    }
}

CarRepository包含ICarRepository合同的实现。 这是与JdbcTemplate一起使用的层。

com/zetcode/service/ICarService.java

package com.zetcode.service;

import com.zetcode.model.Car;

import java.util.List;

public interface ICarService {

    Car findByName(String name);
    List<Car> findAll();
}

我们有两种合同服务方式。

com/zetcode/service/CarService.java

package com.zetcode.service;

import com.zetcode.model.Car;
import com.zetcode.repository.ICarRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class CarService implements ICarService {

    @Autowired
    private ICarRepository carRepository;

    public Car findByName(String name) {

        return carRepository.findCarByName(name);
    }

    public List<Car> findAll() {

        return carRepository.findAll();
    }
}

CarService包含ICarService合同的实现。 服务方法委托给ICarRepository

com/zetcode/MyRunner.java

package com.zetcode;

import com.zetcode.model.Car;
import com.zetcode.service.ICarService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Component;

@Component
public class MyRunner implements CommandLineRunner {

    @Autowired
    private ICarService carService;

    @Override
    public void run(String... args) {

        try {
            var car = carService.findByName("Citroen");
            System.out.println(car);

        } catch (EmptyResultDataAccessException e) {
            System.out.println("Car was not found");
        }

        var cars = carService.findAll();

        for (Car car: cars) {
            System.out.println(car);
        }
    }
}

Spring Boot 命令行应用必须实现CommandLineRunner接口。 我们将要执行的代码放入run()方法中。

@Autowired
private ICarService carService;

Spring 注入carService bean。

try {
    var car = carService.findByName("Citroen");
    System.out.println(car);

} catch (EmptyResultDataAccessException e) {
    System.out.println("Car was not found");
}

我们试图找到一辆名为雪铁龙的汽车。 如果没有这样的汽车,Spring 会抛出EmptyResultDataAccessException异常。

var cars = carService.findAll();

for (Car car: cars) {
    System.out.println(car);
}

我们使用findAll()方法从数据库中检索所有汽车。 数据被打印到控制台。

com/zetcode/Application.java

package com.zetcode;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {

        SpringApplication.run(Application.class, args);
    }
}

这是 Spring Boot 应用的入口。

在本教程中,我们介绍了 Spring 的JdbcTemplate模块。 我们创建了一个使用JdbcTemplate的 Spring Boot 应用。 ZetCode 具有以下相关教程: Java 教程Spring JdbcTemplate教程Spring EmbeddedDatabaseBuilder教程EclipseLink 教程Hibernate Derby 教程Servlet FreeMarker JdbcTemplate教程MySQL Java 教程PostgreSQL Java 教程

JDBI 教程

原文: http://zetcode.com/db/jdbi/

在本教程中,我们展示了如何使用 JDBI 处理数据。 我们选择 MySQL 作为数据库。 ZetCode 拥有用于 MySQL Java 的完整电子书,其中包含 JDBI 章节: MySQL Java 编程电子书

Tweet

JDBI 是建立在 JDBC 之上的便捷库。 它使数据库编程容易得多。 它管理异常。 它具有用于自动资源管理和将结果集映射到类的工具。 JDBI 在概念上类似于 Spring 的JdbcTemplate,为此 ZetCode 具有教程

DBI实例通过Handle实例提供与数据库的连接。 Handle表示与数据库系统的连接; 它是 JDBC 连接对象的包装。

JDBI 提供了两种不同的样式 API:流利的样式和对象样式。

在 MySQL 中创建数据库

在本节中,我们将在 MySQL 中创建一个新的testdb数据库。 我们使用mysql监视器来完成这项工作,但是我们也可以使用 NetBeans 数据库工具。

cars_mysql.sql

DROP TABLE IF EXISTS Cars;
CREATE TABLE Cars(Id INT PRIMARY KEY AUTO_INCREMENT, 
                  Name TEXT, Price INT) ENGINE=InnoDB;

INSERT INTO Cars(Name, Price) VALUES('Audi', 52642);
INSERT INTO Cars(Name, Price) VALUES('Mercedes', 57127);
INSERT INTO Cars(Name, Price) VALUES('Skoda', 9000);
INSERT INTO Cars(Name, Price) VALUES('Volvo', 29000);
INSERT INTO Cars(Name, Price) VALUES('Bentley', 350000);
INSERT INTO Cars(Name, Price) VALUES('Citroen', 21000);
INSERT INTO Cars(Name, Price) VALUES('Hummer', 41400);
INSERT INTO Cars(Name, Price) VALUES('Volkswagen', 21600);

这是在 MySQL 中创建Cars表的 SQL。

要创建数据库和表,我们使用mysql监视工具。

$ sudo service mysql start

MySQL 用sudo service mysql start命令启动。

$ mysql -u testuser -p 

我们使用mysql监视器连接到数据库。

mysql> CREATE DATABASE testdb;
Query OK, 1 row affected (0.02 sec)

使用CREATE DATABASE语句创建一个新数据库。

mysql> USE testdb;
mysql> SOURCE cars_mysql.sql

使用source命令,加载并执行cars_mysql.sql文件。

mysql> SELECT * FROM Cars;
+----+------------+--------+
| Id | Name       | Price  |
+----+------------+--------+
|  1 | Audi       |  52642 |
|  2 | Mercedes   |  57127 |
|  3 | Skoda      |   9000 |
|  4 | Volvo      |  29000 |
|  5 | Bentley    | 350000 |
|  6 | Citroen    |  21000 |
|  7 | Hummer     |  41400 |
|  8 | Volkswagen |  21600 |
+----+------------+--------+
8 rows in set (0.00 sec)

我们验证数据。

pom.xml文件

这些示例将使用以下 Maven POM 文件:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zetcode</groupId>
    <artifactId>JDBIEx</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.jdbi</groupId>
            <artifactId>jdbi</artifactId>
            <version>2.73</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.39</version>
        </dependency>

    </dependencies>    

</project>

我们已经为 JDBI 库和 MySQL 驱动程序定义了依赖项。

流利的 API

在以下示例中,我们将使用 JDBI 流利 API 与 MySQL 数据库一起使用。

取回所有汽车

在第一个示例中,我们从Cars表中提取所有汽车。

JDBIEx.java

package com.zetcode;

import java.util.List;
import java.util.Map;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.Query;

public class JDBIEx {

    public static void main(String[] args) {

        Handle handle = null;
        DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb",
                "testuser", "test623");

        String sql = "SELECT * FROM Cars";

        try {

            handle = dbi.open();
            Query<Map<String, Object>> q = handle.createQuery(sql);
            List<Map<String, Object>> l = q.list();

            for (Map<String, Object> m : l) {

                System.out.printf("%d ", m.get("Id"));
                System.out.printf("%s ", m.get("Name"));
                System.out.println(m.get("Price"));
            }

        } finally {
            if (handle != null) {
                handle.close();
            }
        }
    }
}

该示例连接到testdb数据库,并从Cars表中检索所有汽车。

DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb",
        "testuser", "test623");

使用DBI类为数据库创建一个访问点。

handle = dbi.open();

使用DBIopen()方法创建数据库的Handle。 它表示与数据库的连接。 使用DriverManager创建到数据库的连接。

Query<Map<String, Object>> q = handle.createQuery(sql);

使用createQuery()方法创建Query对象。

List<Map<String, Object>> l = q.list();

从查询对象中,我们获得键/值对的列表。

for (Map<String, Object> m : l) {

    System.out.printf("%d ", m.get("Id"));
    System.out.printf("%s ", m.get("Name"));
    System.out.println(m.get("Price"));
}

我们遍历列表并打印所有列。

} finally {
    if (handle != null) {
        handle.close();
    }
}

最后,我们关闭手柄。

1 Audi 52642
2 Mercedes 57127
3 Skoda 9000
4 Volvo 29000
5 Bentley 350000
6 Citroen 21000
7 Hummer 41400
8 Volkswagen 21600

这是示例的输出。

通过 ID 检索汽车

在下一个示例中,我们通过Cars表中的 ID 提取汽车名称。

JDBIEx2.java

package com.zetcode;

import java.util.Map;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.Query;
import org.skife.jdbi.v2.util.StringColumnMapper;

public class JDBIEx2 {

    public static void main(String[] args) {

        Handle handle = null;
        DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb",
                "testuser", "test623");

        try {

            handle = dbi.open();

            String sql = "SELECT Name FROM Cars WHERE Id = ?";

            Query<Map<String, Object>> q = handle.createQuery(sql);
            q.bind(0, 1);

            String carName = q.map(StringColumnMapper.INSTANCE).first();

            System.out.println(carName);

        } finally {

            if (handle != null) {
                handle.close();
            }
        }
    }
}

在示例中,我们从Cars表中选择汽车名称。 SQL 查询采用一个稍后绑定的参数。

String sql = "SELECT Name FROM Cars WHERE Id = ?";

这是用于从表中选择汽车名称的 SQL 代码。 问号是一个令牌,稍后将在代码中填充。

Query<Map<String, Object>> q = handle.createQuery(sql);

从 SQL 语句创建一个新的Query对象。

q.bind(0, 1);

使用bind()方法,我们绑定缺少的参数。 参数在位置上绑定。

String carName = q.map(StringColumnMapper.INSTANCE).first();

我们将带有StringColumnMapper的结果集的列映射到字符串类型。 first()方法用于返回一个值。

System.out.println(carName);

汽车的名称被打印到控制台上。

数据源

在此示例中,我们使用数据源连接到数据库。 数据源的使用可以提高应用的性能和可伸缩性。

db.properties

# mysql properties
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/testdb
mysql.username=testuser
mysql.password=test623

db.properties文件中,我们具有连接属性。

Database properties

图:数据库属性

该文件放置在项目的Resources目录中。

JDBIEx3.java

package com.zetcode;

import com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.Query;
import org.skife.jdbi.v2.util.IntegerColumnMapper;

public class JDBIEx3 {

    public static MysqlDataSource getMySQLDataSource() throws 
            FileNotFoundException, IOException {

        Properties props = new Properties();
        FileInputStream fis = null;
        MysqlDataSource ds = null;

        fis = new FileInputStream("src/main/Resources/db.properties");
        props.load(fis);

        ds = new MysqlConnectionPoolDataSource();
        ds.setURL(props.getProperty("mysql.url"));
        ds.setUser(props.getProperty("mysql.username"));
        ds.setPassword(props.getProperty("mysql.password"));

        return ds;
    }

    public static void main(String[] args) throws IOException {

        Handle handle = null;
        MysqlDataSource ds = getMySQLDataSource();

        DBI dbi = new DBI(ds);

        try {

            handle = dbi.open();

            String sql = "SELECT Price FROM Cars WHERE Id = ?";

            Query<Map<String, Object>> q = handle.createQuery(sql);
            q.bind(0, 1);

            Integer price = q.map(IntegerColumnMapper.WRAPPER).first();

            System.out.println(price);

        } finally {
            if (handle != null) {
                handle.close();
            }
        }
    }
}

该示例选择通过 ID 查找的汽车价格。

fis = new FileInputStream("src/main/Resources/db.properties");
props.load(fis);

我们从Resources目录加载属性。

ds = new MysqlConnectionPoolDataSource();
ds.setURL(props.getProperty("mysql.url"));
ds.setUser(props.getProperty("mysql.username"));
ds.setPassword(props.getProperty("mysql.password"));

创建了MysqlConnectionPoolDataSource。 我们从属性文件中设置参数。

Integer price = q.map(IntegerColumnMapper.WRAPPER).first();

由于 SQL 查询返回整数,因此我们使用IntegerColumnMapper类。

withHandle()方法

DBI类具有称为withHandle()的便捷方法,该方法管理句柄的生命周期,并将其提供给回调以供客户端使用。

JDBIEx4.java

package com.zetcode;

import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.util.IntegerColumnMapper;

public class JDBIEx4 {

    public static void main(String[] args) {

        DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb",
                "testuser", "test623");
        String sql = "SELECT Price FROM Cars WHERE Id = :id";
        int id = 3;

        Integer price = dbi.withHandle((Handle h) -> {

            return h.createQuery(sql)
                    .map(IntegerColumnMapper.WRAPPER)
                    .bind("id", id) 
                    .first(); 
        });

        System.out.println(price);
    }
}

该示例选择由其 ID 标识的汽车价格。

String sql = "SELECT Price FROM Cars WHERE Id = :id";

此 SQL 查询使用命名参数。

Integer price = dbi.withHandle((Handle h) -> {

    return h.createQuery(sql)
            .map(IntegerColumnMapper.WRAPPER)
            .bind("id", id) 
            .first(); 
});

创建和执行查询时,我们不必担心关闭句柄。

映射自定义类

可以将自定义类映射到结果集。 映射类必须实现ResultSetMapper<T>接口。

Car.java

package com.zetcode;

public class Car {

    private Long Id;
    private String Name;
    private int Price;

    public Car(Long Id, String Name, int Price) {
        this.Id = Id;
        this.Name = Name;
        this.Price = Price;
    }

    public Long getId() {
        return Id;
    }

    public void setId(Long Id) {
        this.Id = Id;
    }

    public String getName() {
        return Name;
    }

    public void setName(String Name) {
        this.Name = Name;
    }

    public int getPrice() {
        return Price;
    }

    public void setPrice(int Price) {
        this.Price = Price;
    }

    @Override
    public String toString() {
        return "Car{" + "Id=" + Id + ", Name=" + Name + ", Price=" + Price + '}';
    }
}

这是一个自定义Car类,我们将结果集映射到该类。

CarMapper.java

package com.zetcode;

import java.sql.ResultSet;
import java.sql.SQLException;
import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.tweak.ResultSetMapper;

public class CarMapper implements ResultSetMapper<Car> {

    @Override
    public Car map(int idx, ResultSet rs, StatementContext ctx) throws SQLException {
        return new Car(rs.getLong("Id"), rs.getString("Name"), rs.getInt("Price"));
    }
}

我们提供了映射类。 它返回一个新的Car对象,其中填充了结果集中的数据。

JDBIEx5.java

package com.zetcode;

import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;

public class JDBIEx5 {

    public static void main(String[] args) {

        DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb",
                "testuser", "test623");

        String sql = "SELECT * FROM Cars WHERE Id = :id";
        int id = 3;

        Car car = dbi.withHandle((Handle h) -> {

            return h.createQuery(sql)
                    .map(new CarMapper())
                    .bind("id", id) 
                    .first(); 
        });

        System.out.println(car);
    }
}

该示例从由其 ID 标识的表中选择一个Car对象。

Car car = dbi.withHandle((Handle h) -> {

    return h.createQuery(sql)
            .map(new CarMapper())
            .bind("id", id) 
            .first(); 
});

自定义CarMapper对象将传递给map()方法。

批量操作

批处理允许我们将相关的 SQL 语句分组为批,然后通过一次调用将其提交到数据库。 这可以大大提高我们的应用的性能。

批处理操作不是原子操作; 他们没有提供全部或全部解决方案。 例如,如果我们创建不正确的INSERT语句,它将失败,但是将执行其他INSERT语句。

JDBIEx6.java

package com.zetcode;

import org.skife.jdbi.v2.Batch;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;

public class JDBIEx6 {

    public static void main(String[] args) {

        DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb",
                "testuser", "test623");

        Handle handle = dbi.open();
        Batch batch = handle.createBatch();

        batch.add("DROP TABLE IF EXISTS Friends");
        batch.add("CREATE TABLE Friends(Id INT AUTO_INCREMENT PRIMARY KEY, Name TEXT)");
        batch.add("INSERT INTO Friends(Name) VALUES ('Monika')");
        batch.add("INSERT INTO Friends(Name) VALUES ('Tom')");
        batch.add("INSERT INTO Friends(Name) VALUES ('Jane')");
        batch.add("INSERT INTO Friends(Name) VALUES ('Robert')");

        batch.execute();
    }
}

该示例创建一个新的Friends表。 SQL 命令被分组为一个批处理操作。

Batch batch = handle.createBatch();

Batch代表一组未预备语句; 它是使用createBatch()方法创建的。

batch.add("DROP TABLE IF EXISTS Friends");

add()方法将语句添加到批处理中。

batch.execute();

批处理通过execute()方法执行。

事务

事务是针对一个或多个数据库中数据的数据库操作的基本单位。 事务中所有 SQL 语句的影响可以全部提交给数据库,也可以全部回滚。

还要注意,在 MySQL 中,DDL 语句(例如DROP TABLECREATE TABLE)会导致对事务的隐式提交。

JDBIEx7.java

package com.zetcode;

import org.skife.jdbi.v2.Batch;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.TransactionStatus;
import org.skife.jdbi.v2.VoidTransactionCallback;

public class JDBIEx7 {

    public static void main(String[] args) {

        DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb",
                "testuser", "test623");

        dbi.inTransaction(new VoidTransactionCallback() {
            @Override
            protected void execute(Handle handle, TransactionStatus status)
                    throws Exception {

                Batch batch = handle.createBatch();

                batch.add("DROP TABLE IF EXISTS Friends");
                batch.add("CREATE TABLE Friends(Id INT AUTO_INCREMENT PRIMARY KEY, Name TEXT)");
                batch.add("INSERT INTO Friends(Name) VALUES ('Monika')");
                batch.add("INSERT INTO Friends(Name) VALUES ('Tom')");
                batch.add("INSERT INTO Friends(Name) VALUES ('Jane')");
                batch.add("INSERT INTO Friends(Name) VALUES ('Robert')");

                batch.execute();
            }
        });
    }
}

该示例将批处理操作置于事务中。 由于 MYSQL 中 DDL 语句的隐式提交,因此只有INSERT语句处于全有或全无模式。

dbi.inTransaction(new VoidTransactionCallback() {
    @Override
    protected void execute(Handle handle, TransactionStatus status)
            throws Exception {

        ...
    }
});

使用inTransaction()方法创建事务。 VoidTransactionCallback是不返回值的事务回调。

SQL 对象 API

SQL 对象 API 为常见的 JDBI 操作提供了一种声明性机制。 要使用 SQL 对象 API,我们创建带有注解的接口或抽象类,例如@SqlQuery@SqlUpdate

简单的例子

我们创建了一个示例,其中将使用 SQL 对象 API 创建简单查询。

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.8</version>
</dependency>     

在示例中,我们还使用了lombok库,该库减少了一些样板代码。

Car.java

package com.zetcode;

import lombok.Data;

@Data 
public class Car {

    private final Long Id;
    private final String Name;
    private final int Price;
}

Car类装饰有 Lombok 的@Data注解。 它将自动创建获取器和设置器方法,equals()方法,toString()方法,hashCode()方法和参数构造器。

CarMapper.java

package com.zetcode;

import java.sql.ResultSet;
import java.sql.SQLException;
import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.tweak.ResultSetMapper;

public class CarMapper implements ResultSetMapper<Car> {

    @Override
    public Car map(int idx, ResultSet rs, StatementContext ctx) throws SQLException {
        return new Car(rs.getLong("Id"), rs.getString("Name"), rs.getInt("Price"));
    }
}

CarMapper将结果集映射到Car类。

MyDAO.java

package com.zetcode;

import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.customizers.Mapper;

public interface MyDAO {

    @SqlQuery("SELECT * FROM Cars WHERE Id = :id")
    @Mapper(CarMapper.class)
    Car findById(@Bind("id") int id);

    @SqlQuery("SELECT COUNT(Id) FROM Cars")
    int countCars();
}

在这里,我们有一个MyDAO接口,装饰有两个@SqlQuery注解。 这些方法通过其 ID 查找汽车并计算表中的所有汽车。

@SqlQuery("SELECT * FROM Cars WHERE Id = :id")

@SqlQuery注解指示该方法执行指定的查询。

@Mapper(CarMapper.class)

@Mapper指定查询方法上的结果集映射器。

Car findById(@Bind("id") int id);

@Bind注解将方法的参数绑定到 SQL 查询参数。

JDBIEx8.java

package com.zetcode;

import org.skife.jdbi.v2.DBI;

public class JDBIEx8 {

    public static void main(String[] args) {

        DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb",
                "testuser", "test623");

        int id = 3;

        MyDAO dao = dbi.onDemand(MyDAO.class);
        Car car = dao.findById(id);

        System.out.println(car);

        int nCars = dao.countCars();

        System.out.printf("There are %d cars in the table", nCars);
    }
}

在此客户端应用中,我们找到 ID 等于 3 的汽车,并计算表中的所有汽车。

MyDAO dao = dbi.onDemand(MyDAO.class);

onDemand()方法创建一个新的 sql 对象,该对象将分别根据需要和可以从该 dbi 实例获取和释放连接。 我们不应该显式关闭此 sql 对象。

Car car = dao.findById(id);

我们得到了具有指定 ID 的汽车。

int nCars = dao.countCars();

我们在数据库表中计算汽车的数量。

事务

在 SQL 对象 API 中,我们可以使用@Transaction注解创建事务。

authors_books.sql

CREATE TABLE IF NOT EXISTS Authors(Id BIGINT PRIMARY KEY AUTO_INCREMENT, 
    Name VARCHAR(25)) ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS Books(Id BIGINT PRIMARY KEY AUTO_INCREMENT, 
    AuthorId BIGINT, Title VARCHAR(100), 
    FOREIGN KEY(AuthorId) REFERENCES Authors(Id) ON DELETE CASCADE)
    ENGINE=InnoDB;

对于此示例,我们创建两个表:AuthorsBooks

MyDAO.java

package com.zetcode;

import java.util.List;
import org.skife.jdbi.v2.exceptions.TransactionFailedException;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.Transaction;

public abstract class MyDAO {

    @SqlUpdate("INSERT INTO Authors(Name) VALUES(:author)")
    public abstract void createAuthor(@Bind("author") String author);

    @SqlQuery("SELECT Id FROM Authors WHERE Name = :name")
    abstract long getAuthorId(@Bind("name") String name);        

    @SqlUpdate("INSERT INTO Books(AuthorId, Title) VALUES(:authorId, :title)")
    abstract void insertBook(@Bind("authorId") Long authorId, @Bind("title") String title);

    @Transaction
    public void insertBooksForAuthor(String author, List<String> titles) {

        Long authorId = getAuthorId(author);

        if (authorId == null) {
            throw new TransactionFailedException("No author found");
        }

        for (String title : titles) {

            insertBook(authorId, title);
        }
    }
}

我们有一个抽象的MyDAO类,其中利用了@SqlUpdate@SqlQuery@Transaction注解。

@SqlUpdate("INSERT INTO Authors(Name) VALUES(:author)")
public abstract void createAuthor(@Bind("author") String author);

此方法添加了一个新作者。

@SqlQuery("SELECT Id FROM Authors WHERE Name = :name")
abstract long getAuthorId(@Bind("name") String name);   

getAuthorId()用于获取作者的 ID。 当我们将新书插入Books表时,需要 ID。

@SqlUpdate("INSERT INTO Books(AuthorId, Title) VALUES(:authorId, :title)")
abstract void insertBook(@Bind("authorId") Long authorId, @Bind("title") String title);

insertBook()方法将一本书插入Books表中。

@Transaction
public void insertBooksForAuthor(String author, List<String> titles) {

    Long authorId = getAuthorId(author);

    if (authorId == null) {
        throw new TransactionFailedException("No author found");
    }

    for (String title : titles) {

        insertBook(authorId, title);
    }
}

@Transaction注解使insertBooksForAuthor()在事务内运行。 因此,要么插入所有书籍,要么不插入任何书籍。

JDBIEx9.java

package com.zetcode;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.skife.jdbi.v2.DBI;

public class JDBIEx9 {

    public static void main(String[] args) {

        DBI dbi = new DBI("jdbc:mysql://localhost:3306/testdb",
                "testuser", "test623");

        List<Map<String, List<String>>> authorsBooks = new ArrayList<>();

        Map<String, List<String>> autMap1 = new HashMap<>();

        List<String> books1 = new ArrayList<>();
        books1.add("Call of the Wild");
        books1.add("Martin Eden");
        books1.add("The Iron Heel");
        books1.add("White Fang");

        autMap1.put("Jack London", books1);

        Map<String, List<String>> autMap2 = new HashMap<>();

        List<String> books2 = new ArrayList<>();
        books2.add("Father Goriot");
        books2.add("Colonel Chabert");
        books2.add("Cousing Pons");

        autMap2.put("Honore de Balzac", books2);        

        authorsBooks.add(autMap1);
        authorsBooks.add(autMap2);

        MyDAO dao = dbi.onDemand(MyDAO.class);

        for (Map<String, List<String>> map : authorsBooks) {

            Set<String> ks = map.keySet();

            for (String author : ks) {

                dao.createAuthor(author);

                List<String> titles = map.get(author);

                dao.insertBooksForAuthor(author, titles);
            }
        }
    }
}

该示例将两名作者及其书籍插入数据库。

在本教程中,我们介绍了 JDBI 库。 ZetCode 具有以下相关教程: Java 教程MySQL Java 教程MySQL 教程

在 MySQL 中插入,更新和删除数据

原文: http://zetcode.com/databases/mysqltutorial/datamanipulation/

在 MySQL 教程的这一部分中,我们将从 MySQL 表中插入,更新和删除数据。 我们将使用INSERTDELETEUPDATE语句。 这些语句是 SQL 数据操作语言 DML 的一部分。

插入数据

INSERT语句用于将数据插入表中。

我们将创建一个新表,并在其中进行示例。

mysql> CREATE TABLE Books(Id INTEGER PRIMARY KEY, Title VARCHAR(100),
    -> Author VARCHAR(60));

我们使用IdTitleAuthor列创建一个新表Books

mysql> INSERT INTO Books(Id, Title, Author) VALUES(1, 'War and Peace', 
    -> 'Leo Tolstoy');

这是经典的INSERT SQL 语句。 我们在表名之后指定了所有列名,并在VALUES关键字之后指定了所有值。 我们将第一行添加到表中。

mysql> SELECT * FROM Books;
+----+---------------+-------------+
| Id | Title         | Author      |
+----+---------------+-------------+
|  1 | War and Peace | Leo Tolstoy |
+----+---------------+-------------+

我们已经将第一行插入到Books表中。

mysql> INSERT INTO Books(Title, Author) VALUES ('The Brothers Karamazov',
    -> 'Fyodor Dostoyevsky');

我们将新标题添加到Books表中。 我们省略了Id列。 Id列具有AUTO_INCREMENT属性。 这意味着 MySQL 将自动增加 Id 列。 AUTO_INCREMENT列增加的值由auto_increment_increment系统变量控制。 默认为 1。

mysql> SELECT * FROM Books;
+----+------------------------+--------------------+
| Id | Title                  | Author             |
+----+------------------------+--------------------+
|  1 | War and Peace          | Leo Tolstoy        |
|  2 | The Brothers Karamazov | Fyodor Dostoyevsky |
+----+------------------------+--------------------+

这是Books表中的内容。

mysql> INSERT INTO Books VALUES(3, 'Crime and Punishment',
    -> 'Fyodor Dostoyevsky');

在此 SQL 语句中,我们没有在表名之后指定任何列名。 在这种情况下,我们必须提供所有值。

mysql> REPLACE INTO Books VALUES(3, 'Paradise Lost', 'John Milton');
Query OK, 2 rows affected (0.00 sec)

REPLACE语句是对 SQL 标准的 MySQL 扩展。 如果它与现有行发生冲突,它将插入新行或替换旧行。 在我们的表中,带有Id=3的行。 因此,我们之前的语句将其替换为新行。 有一则消息说两行受到影响。 删除了一行并插入了一行。

mysql> SELECT * FROM Books WHERE Id=3;
+----+---------------+-------------+
| Id | Title         | Author      |
+----+---------------+-------------+
|  3 | Paradise Lost | John Milton |
+----+---------------+-------------+

这就是我们现在在第三栏中。

我们可以在一个语句中同时使用INSERTSELECT语句。

mysql> CREATE TABLE Books2(Id INTEGER PRIMARY KEY AUTO_INCREMENT, 
    -> Title VARCHAR(100), Author VARCHAR(60)) type=MEMORY;

首先,我们在内存中创建一个名为Books2的临时表。

mysql> INSERT INTO Books2 SELECT * FROM Books;
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

在这里,我们将所有数据插入从Books表中选择的Books2中。

mysql> SELECT * FROM Books2;
+----+------------------------+--------------------+
| Id | Title                  | Author             |
+----+------------------------+--------------------+
|  1 | War and Peace          | Leo Tolstoy        |
|  2 | The Brothers Karamazov | Fyodor Dostoyevsky |
|  3 | Paradise Lost          | John Milton        |
+----+------------------------+--------------------+

我们验证。 一切都好。

mysql> INSERT INTO Books(Title, Author) VALUES ('The Insulted and Humiliated',
    -> 'Fyodor Dostoyevsky'), ('Cousin Bette', 'Honore de Balzac');
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

我们可以使用INSERT语句在表中插入多行。 这里我们展示如何。

我们可以从文件系统中的文件插入数据。 首先,我们将Books表中的数据转储到books.csv文件中。

mysql> SELECT * INTO OUTFILE '/tmp/books.csv'
    -> FIELDS TERMINATED BY ','
    -> LINES TERMINATED BY '\n'
    -> FROM Books;

我们将Books表中的数据写入books.csv文件。 数据将为 CSV 格式。

$ cat /tmp/books.csv 
1,War and Peace,Leo Tolstoy
2,The Brothers Karamazov,Fyodor Dostoyevsky
3,Paradise Lost,John Milton
4,The Insulted and Humiliated,Fyodor Dostoyevsky
5,Cousin Bette,Honore de Balzac

我们显示books.csv文件的内容。

mysql> TRUNCATE Books;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM Books;
Empty set (0.00 sec)

我们从表中删除所有数据。

mysql> LOAD DATA INFILE '/tmp/books.csv'    
    -> INTO TABLE Books    
    -> FIELDS TERMINATED BY ','    
    -> LINES TERMINATED BY '\n';

我们使用LOAD DATA INFILE语法从books.csv文件填充Books表。

mysql> SELECT * FROM Books;
+----+-----------------------------+--------------------+
| Id | Title                       | Author             |
+----+-----------------------------+--------------------+
|  1 | War and Peace               | Leo Tolstoy        |
|  2 | The Brothers Karamazov      | Fyodor Dostoyevsky |
|  3 | Paradise Lost               | John Milton        |
|  4 | The Insulted and Humiliated | Fyodor Dostoyevsky |
|  5 | Cousin Bette                | Honore de Balzac   |
+----+-----------------------------+--------------------+

一切都好。

我们也可以从 XML 文件加载数据。 首先,我们将 Books 表中的数据写入 XML 文件。

$ mysql -uroot -p --xml -e 'SELECT * FROM mydb.Books' > books.xml

mysql监视器具有--xml选项,使我们能够以 XML 格式转储数据。 -e选项执行一条语句并退出监视器。

$ cat books.xml 
<?xml version="1.0"?>

<resultset statement="SELECT * FROM mydb.Books
" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <row>
    <field name="Id">1</field>
    <field name="Title">War and Peace</field>
    <field name="Author">Leo Tolstoy</field>
  </row>

  <row>
    <field name="Id">2</field>
    <field name="Title">The Brothers Karamazov</field>
    <field name="Author">Fyodor Dostoyevsky</field>
  </row>

  <row>
    <field name="Id">3</field>
    <field name="Title">Paradise Lost</field>
    <field name="Author">John Milton</field>
  </row>

  <row>
    <field name="Id">4</field>
    <field name="Title">The Insulted and Humiliated</field>
    <field name="Author">Fyodor Dostoyevsky</field>
  </row>

  <row>
    <field name="Id">5</field>
    <field name="Title">Cousin Bette</field>
    <field name="Author">Honore de Balzac</field>
  </row>
</resultset>

这是我们的 XML 文件。

mysql> LOAD XML INFILE '/home/vronskij/programming/mysql/books.xml' INTO TABLE Books;

我们从 XML 文件加载数据。 请注意,LOAD XML语句可用于 MySQL 5.5 及更高版本。

删除数据

在 MySQL 中,我们可以使用DELETETRUNCATE语句删除数据。 TRUNCATE语句是 SQL 规范的 MySQL 扩展。 首先,我们将从表中删除一行。 我们将使用之前创建的Books2表。

mysql> DELETE FROM Books2 WHERE Id=1;

我们用Id=1删除一行。

mysql> SELECT * FROM Books2;
+----+------------------------+--------------------+
| Id | Title                  | Author             |
+----+------------------------+--------------------+
|  2 | The Brothers Karamazov | Fyodor Dostoyevsky |
|  3 | Paradise Lost          | John Milton        |
+----+------------------------+--------------------+

我们验证数据。

mysql> DELETE FROM Books2;
mysql> TRUNCATE Books2;

这两个 SQL 语句删除表中的所有数据。

更新数据

UPDATE语句用于更改表的选定行中的列的值。

mysql> SELECT * FROM Books;
+----+-----------------------------+--------------------+
| Id | Title                       | Author             |
+----+-----------------------------+--------------------+
|  1 | War and Peace               | Leo Tolstoy        |
|  2 | The Brothers Karamazov      | Fyodor Dostoyevsky |
|  3 | Paradise Lost               | John Milton        |
|  4 | The Insulted and Humiliated | Fyodor Dostoyevsky |
|  5 | Cousin Bette                | Honore de Balzac   |
+----+-----------------------------+--------------------+

我们重新创建表Books。 这些是行。

假设我们想将"Leo Tolstoy"更改'Lev Nikolayevich Tolstoy'。 以下语句显示了如何完成此操作。

mysql> UPDATE Books SET Author='Lev Nikolayevich Tolstoy'
    -> WHERE Id=1;

SQL 语句将Id=1列的author列设置为'Lev Nikolayevich Tolstoy'

mysql> SELECT * FROM Books WHERE Id=1;
+----+---------------+--------------------------+
| Id | Title         | Author                   |
+----+---------------+--------------------------+
|  1 | War and Peace | Lev Nikolayevich Tolstoy |
+----+---------------+--------------------------+

该行已正确更新。

在 MySQL 教程的这一部分中,我们已经在数据库表中插入,删除和更新了数据。

MyBatis 教程

原文: http://zetcode.com/db/mybatis/

这是 MyBatis Java 教程。 本教程介绍了使用 Java 和 MyBatis 进行 MySQL 编程的基础。

Tweet

ZetCode 拥有用于 MySQL Java 的完整电子书,其中包含 MyBatis 章节: MySQL Java 编程电子书

MyBatis

MyBatis 是 Java 持久性框架,使用 XML 描述符或注解将对象与存储过程或 SQL 语句耦合。 与 ORM 框架不同,MyBatis 不会将 Java 对象映射到数据库表,而是将 Java 方法映射到 SQL 语句。 MyBatis 允许使用所有数据库功能,例如存储过程,视图,任何复杂性和供应商专有功能的查询。

使用 MyBatis 的好处是:

  • 开箱即用的表/查询缓存
  • 减少了许多 JDBC 样板
  • 提高生产力
  • SQL 代码与 Java 类的分离

关于 MySQL 数据库

MySQL 是领先的开源数据库管理系统。 它是一个多用户,多线程的数据库管理系统。 MySQL 在网络上特别流行。 MySQL 有两个版本:MySQL 服务器系统和 MySQL 嵌入式系统。

Maven 依赖

pom.xml文件中,添加以下依赖项:

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.40</version>
    </dependency>    

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.2</version>
    </dependency>      

</dependencies>           

POM 文件具有两个依赖项:MyBatis 库和 MySQL 驱动程序。

MyBooks

本教程中的某些示例使用MyBooks表。

mybooks.sql

CREATE TABLE MyBooks(Id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, 
  Author VARCHAR(30), Title VARCHAR(60),  Published INTEGER, Remark VARCHAR(150));
INSERT INTO MyBooks(Author, Title, Published, Remark) VALUES ('Leo Tolstoy', 'War and Peace', 1869, 'Napoleonic wars');    
INSERT INTO MyBooks(Author, Title, Published, Remark) VALUES ('Leo Tolstoy', 'Anna Karenina', 1878, 'Greatest book of love');
INSERT INTO MyBooks(Author, Title, Published, Remark) VALUES ('Jeff Prosise', 'Programming Windows with MFC', 1999, 'Classic book about MFC');
INSERT INTO MyBooks(Author, Title, Published, Remark) VALUES ('Tom Marrs', 'JBoss at Work', 2005, 'JBoss practical guide');
INSERT INTO MyBooks(Author, Title, Published, Remark) VALUES ('Debu Panda', 'EJB3 in Action', 2007, 'Introduction to Enterprice Java Beans');

这些 SQL 命令在 MySQL testdb数据库中创建MyBooks表。

MySQL 版本

在第一个示例中,我们获得 MySQL 的版本。 在此示例中,我们使用注解将对象映射到 SQL 语句。

MyBatisMySQLVersion project structure

图:MyBatisMySQLVersion 项目结构

这是 NetBeans 中的项目结构。

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/testdb"/>
                <property name="username" value="testuser"/>
                <property name="password" value="test623"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

每个 MyBatis 项目都有一个主要的 XML 配置文件。 在这里,我们为 MySQL 定义了一个数据源。

MyMapper.java

package com.zetcode.version;

import org.apache.ibatis.annotations.Select;

public interface MyMapper {

    @Select("SELECT VERSION()")
    public String getMySQLVersion();
}

使用@Select注解,我们将getMySQLVersion()方法映射到该注解中指定的 SQL 语句。 获取 MySQL 版本的 SQL 语句为SELECT VERSION()

MyBatisMySQLVersion.java

package com.zetcode.version;

import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class MyBatisMySQLVersion {

    private static SqlSessionFactory factory = null;

    public static void main(String[] args) throws IOException {

        String resource = "mybatis-config.xml";
        Reader reader = null;
        SqlSession session = null;

        reader = Resources.getResourceAsReader(resource);

        factory = new SqlSessionFactoryBuilder().build(reader);
        factory.getConfiguration().addMapper(MyMapper.class);

        reader.close();

        try {
            session = factory.openSession();
            String version = session.selectOne("getMySQLVersion");
            System.out.println(version);

        } finally {

            if (session != null) {
                session.close();
            }
        }
    }
}

我们连接到数据库并获取 MySQL 的版本。

String resource = "mybatis-config.xml";
Reader reader = null;
SqlSession session = null;

reader = Resources.getResourceAsReader(resource);

读取配置文件。

factory = new SqlSessionFactoryBuilder().build(reader);

SqlSessionFactoryBuilder用于构建SqlSession实例。

factory.getConfiguration().addMapper(MyMapper.class);

使用addMapper()方法,将映射类添加到工厂。

session = factory.openSession();

openSession()方法创建一个SqlSessionSqlSession是用于 MyBatis 的主要 Java 接口。 通过该接口,我们执行命令,获取映射器并管理事务。

String version = session.selectOne("getMySQLVersion");

selectOne()方法从语句键检索映射的单个行。 语句键是映射器类中方法的名称。

System.out.println(version);

该版本将打印到控制台。

} finally {

    if (session != null) {
        session.close();
    }
}

最后,会话关闭。

MySQL 版本 2

在第二个示例中,我们还将检索 MySQL 的版本。 这次我们使用 XML 映射器代替注释。

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/testdb"/>
                <property name="username" value="testuser"/>
                <property name="password" value="test623"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mymapper.xml"/>
    </mappers>    

</configuration>

使用<mappers>标签,指定映射文件。

mymapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.zetcode">

    <select id="mysqlVersion" resultType="String">
        SELECT VERSION()
    </select>

</mapper>

我们使用<select>标签定义映射。

MyBatisMySQLVersion2.java

package com.zetcode.version2;

import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class MyBatisMySQLVersion2 {

    private static SqlSessionFactory factory = null;

    public static void main(String[] args) throws IOException {

        String resource = "mybatis-config.xml";
        Reader reader = null;
        SqlSession session = null;

        reader = Resources.getResourceAsReader(resource);

        factory = new SqlSessionFactoryBuilder().build(reader);

        reader.close();

        try {
            session = factory.openSession();
            String version = session.selectOne("mysqlVersion");
            System.out.println(version);

        } finally {

            if (session != null) {
                session.close();
            }
        }
    }
}

这是主要的类。 区别在于我们没有在映射器中添加addMapper(),而是从配置文件中读取映射器。

MyBatis Java 配置

可以在不使用 XML 的情况下以纯 Java 配置 MyBatis。 在下面的示例中,我们将查找表中的书籍数量。

MyMapper.java

package com.zetcode.map;

import org.apache.ibatis.annotations.Select;

public interface MyMapper {

    @Select("SELECT COUNT(*) FROM MyBooks")
    public int getNumberOfBooks();
}

MyMapper包含对MyBooks表中的行数进行计数的 SQL 代码。

MyDataSourceFactory.java

package com.zetcode.jconfig;

import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.datasource.pooled.PooledDataSource;

public class MyDataSourceFactory implements DataSourceFactory {

    private Properties prop;

    @Override
    public DataSource getDataSource() {

        PooledDataSource ds = new PooledDataSource();

        ds.setDriver(prop.getProperty("driver"));
        ds.setUrl(prop.getProperty("url"));
        ds.setUsername(prop.getProperty("user"));
        ds.setPassword(prop.getProperty("password"));

        return ds;
    }

    @Override
    public void setProperties(Properties prprts) {

        prop = prprts;
    }
}

MyDataSourceFactory从给定的属性创建一个PooledDataSourcePooledDataSource是一个简单,同步,线程安全的数据库连接池。

MyBatisJavaConfClient.java

package com.zetcode.client;

import com.zetcode.jconfig.MyDataSourceFactory;
import com.zetcode.jconfig.MyMapper;
import java.io.IOException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;

public class MyBatisJavaConfClient {

    private static SqlSessionFactory sesFact = null;

    public static void main(String[] args) throws IOException {

        Properties prop = new Properties();
        prop.setProperty("driver", "com.mysql.jdbc.Driver");
        prop.setProperty("url", "jdbc:mysql://localhost:3306/testdb");
        prop.setProperty("user", "testuser");
        prop.setProperty("password", "test623");

        MyDataSourceFactory mdsf = new MyDataSourceFactory();
        mdsf.setProperties(prop);
        DataSource ds = mdsf.getDataSource();

        TransactionFactory trFact = new JdbcTransactionFactory();
        Environment environment = new Environment("development", trFact, ds);
        Configuration config = new Configuration(environment);
        config.addMapper(MyMapper.class);

        sesFact = new SqlSessionFactoryBuilder().build(config);

        try (SqlSession session = sesFact.openSession()) {

            int numOfBooks = session.selectOne("getNumberOfBooks");
            System.out.format("There are %d books", numOfBooks);
        }
    }
}

MyBatisJavaConfClient中,我们用 Java 代码配置 MyBatis,构建 SQL 会话,并执行getNumberOfBooks()方法。

Properties prop = new Properties();
prop.setProperty("driver", "com.mysql.jdbc.Driver");
prop.setProperty("url", "jdbc:mysql://localhost:3306/testdb");
prop.setProperty("user", "testuser");
prop.setProperty("password", "test623");

在这里,我们设置数据库属性。

MyDataSourceFactory mdsf = new MyDataSourceFactory();
mdsf.setProperties(prop);
DataSource ds = mdsf.getDataSource();

我们使用MyDataSourceFactory获得数据源。

TransactionFactory trFact = new JdbcTransactionFactory();
Environment environment = new Environment("development", trFact, ds);
Configuration config = new Configuration(environment);
config.addMapper(MyMapper.class);

此代码替换 XML 配置。 我们使用JdbcTransactionFactoryEnvironmentConfiguration类。

sesFact = new SqlSessionFactoryBuilder().build(config);

Configuration类的实例传递给SqlSessionFactoryBuilderbuild()方法。

动态 SQL

动态 SQL 允许我们使用<if><where><foreach>之类的标签创建动态 SQL 查询。

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <typeAliases>
        <typeAlias alias="Book" 
                   type="com.zetcode.mybatisdynamicsql.bean.Book"/>  
    </typeAliases>      

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" 
                          value="jdbc:mysql://localhost:3306/testdb"/>
                <property name="username" value="testuser"/>
                <property name="password" value="test623"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mymapper.xml"/>
    </mappers>    

</configuration>

首先,我们提供mybatis-config.xml配置文件。

mymapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.zetcode">

    <select id = "getBook" resultType = "Book">
        SELECT * FROM MyBooks

        <where>
            <if test = "_parameter != null">
                Id = #{id}
            </if>
        </where>

    </select>    

</mapper>

mymapper.xml包含动态 SQL 表达式。

<where>
    <if test = "_parameter != null">
        Id = #{id}
    </if>
</where>

仅当Id参数不为null时,才包含<where>标记的内容。 实际上,SQL 表达式返回一本由其 ID 标识的书,否则返回所有书。

Book.java

package com.zetcode.mybatisdynamicsql.bean;

public class Book {

    private Long id;
    private String author;
    private String title;
    private int published;
    private String remark;

    public Book() {};

    public Book(String author, String title, int published, 
            String remark) {

        this.author = author;
        this.title = title;
        this.published = published;
        this.remark = remark;
    }    

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getPublished() {
        return published;
    }

    public void setPublished(int published) {
        this.published = published;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    @Override
    public String toString() {
        return "Book{" + "id=" + id + ", author=" + author + ", "
                + "title=" + title + ", published=" + published 
                + ", remark=" + remark + '}';
    }    
}

这是Book bean,它映射到我们的结果数据。

Book.java

package com.zetcode.mybatisdynamicsql;

import com.zetcode.mybatisdynamicsql.bean.Book;
import java.io.IOException;
import java.io.Reader;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class MyBatisDynamicSQL {

    private static SqlSessionFactory factory = null;

    public static void main(String[] args) throws IOException {

        String resource = "mybatis-config.xml";
        Reader reader = null;
        SqlSession session = null;

        reader = Resources.getResourceAsReader(resource);

        factory = new SqlSessionFactoryBuilder().build(reader);

        try {
            session = factory.openSession();
            Book book = session.selectOne("getBook", 1);
            System.out.println(book);

            List<Book> books = session.selectList("getBook");

            for (Book b : books) {
                System.out.println(b);
            }            

        } finally {

            if (session != null) {
                session.close();
            }
        }
    }
}

使用一个声明键,我们检索一本特定的书和所有书。

Book book = session.selectOne("getBook", 1);

在这里,我们检索由 ID 标识的一本书。

List<Book> books = session.selectList("getBook");

在这里,我们检索所有书籍; 第二个参数未传递。

图书

在下一个示例中,我们将插入数据库表并从中读取书籍。

MyBatisMySQLBooks project structure

图:MyBatisMySQLBooks 项目结构

这是 NetBeans 中的项目结构。

Book.java

package com.zetcode.books.bean;

public class Book {

    private Long id;
    private String author;
    private String title;
    private int published;
    private String remark;

    public Book() {};

    public Book(String author, String title, int published, 
            String remark) {

        this.author = author;
        this.title = title;
        this.published = published;
        this.remark = remark;
    }    

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getPublished() {
        return published;
    }

    public void setPublished(int published) {
        this.published = published;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    @Override
    public String toString() {
        return "Book{" + "id=" + id + ", author=" + author + ", "
                + "title=" + title + ", published=" + published 
                + ", remark=" + remark + '}';
    }    
}

这是Book bean。 MyBatis 将表列映射到此类。 注意空构造器的显式用法。

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <typeAliases>
        <typeAlias alias="Book" type="com.zetcode.books.bean.Book"/>  
    </typeAliases>  

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/testdb"/>
                <property name="username" value="testuser"/>
                <property name="password" value="test623"/>
            </dataSource>
        </environment>
    </environments>

</configuration>

mybatis-config.xml文件中,我们使用<typeAlias>标签定义新的Book类型。

MyMapper.java

package com.zetcode.map;

import com.zetcode.books.bean.Book;
import java.util.List;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;

public interface MyMapper {

    @Select("SELECT * FROM MyBooks WHERE Id = #{id}")
    public Book getBookById(Long id);

    @Select("SELECT * FROM MyBooks WHERE Author = #{author}")
    public List<Book> getBooksByAuthor(String author);   

    @Insert("INSERT INTO MyBooks(Author, Title, Published, Remark) "
            + "VALUES(#{author}, #{title}, #{published}, #{remark})")
    public void insertBook(String author, String title, int published, 
            String remark);
}

MyMapper接口中,我们有三个注解。

@Select("SELECT * FROM MyBooks WHERE Id = #{id}")
public Book getBookById(Long id);

该注解将getBookById()方法映射到指定的SELECT语句。 该方法返回一个Book对象。

@Select("SELECT * FROM MyBooks WHERE Author = #{author}")
public List<Book> getBooksByAuthor(String author);

我们将SELECT语句映射到getBooksByAuthor()方法的列表,该方法返回书对象的列表。

@Insert("INSERT INTO MyBooks(Author, Title, Published, Remark) "
        + "VALUES(#{author}, #{title}, #{published}, #{remark})")
public void insertBook(String author, String title, int published, 
        String remark);

使用@Insert注解,我们将INSERT语句映射到insertBook()方法名称。

MyBatisBooks.java

package com.zetcode.client;

import com.zetcode.map.MyMapper;
import com.zetcode.books.bean.Book;
import com.zetcode.util.MyBatisUtils;
import java.io.IOException;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

public class MyBatisBooks {

    private static SqlSessionFactory factory = null;

    public static void main(String[] args) throws IOException {

        SqlSession session = null;

        factory = MyBatisUtils.getSqlSessionFactory();
        factory.getConfiguration().addMapper(MyMapper.class);

        try {
            session = factory.openSession();
            Book book = session.selectOne("getBookById", 4L);
            System.out.println(book);

            List<Book> books = session.selectList("getBooksByAuthor", "Leo Tolstoy");

            for (Book b : books) {
                System.out.println(b);
            }

            Book newBook = new Book("Miguel de Cervantes", "Don Quixote",
                    1605, "First modern novel");

            session.update("insertBook", newBook);
            session.commit();

        } finally {

            if (session != null) {
                session.close();
            }
        }
    }
}

在主类中,我们通过 ID 来选择一本书,从作者中选择所有书籍,然后将新书籍插入表中。

Book book = session.selectOne("getBookById", 4L);

使用会话的selectOne()方法检索一本新书。

List<Book> books = session.selectList("getBooksByAuthor", "Leo Tolstoy");

for (Book b : books) {
    System.out.println(b);
}

列夫·托尔斯泰的所有书籍都是使用会话的selectList()方法检索的。

session.update("insertBook", newBook);
session.commit();

使用会话的update()方法插入一本新书。 该方法将Book实例作为第二个参数。 更改将通过commit()提交到数据库。

这是 MyBatis 教程。 您可能也对 JDBI 教程PostgreSQL Java 教程MongoDB Java 教程MySQL 教程感兴趣。

Hibernate Derby 教程

原文: http://zetcode.com/db/hibernatederby/

在本教程中,我们将学习如何在 Derby 数据库中使用 Hibernate ORM 工具。 本教程中的项目是在 NetBeans IDE 中创建的。

Tweet

Hibernate 是 Java 语言的对象关系映射框架。 它提供了一个框架,用于将面向对象的域模型映射到关系数据库。 对象关系映射(ORM)是一种编程技术,用于在面向对象的编程语言中的不兼容类型系统之间转换数据。

Apache Derby 是一个完全用 Java 实现的开源关系数据库。 Derby 占用空间小,易于部署和安装。 它支持嵌入式和客户端/服务器模式。

Hibernate 查询语言(HQL)是类似于 SQL 的面向对象的查询语言。 SQL 在表和列上运行,而 HQL 在持久对象及其属性上运行。 HQL 了解继承,多态和关联。 最终,HQL 查询由 Hibernate 转换为 SQL 查询,从而对数据库执行某些操作。

除了其本地 API,Hibernate 还包含 Java Persistence API(JPA)的实现。

配置信息

hibernate.cfg.xml文件定义了 Hibernate 配置信息。 它包含有关数据库连接,资源映射和其他连接属性的信息。

SessionFactory是工厂类,通过它我们可以获取会话并执行数据库操作。 Session是 Java 应用和 Hibernate 之间的主要运行时接口。 会话的主要功能是为映射实体类的实例提供创建,读取和删除操作。

在 Derby 中创建数据库

我们在 Derby 中创建一个新的testdb数据库。 它会有一个简单的Cars表。

Database creation

图:数据库创建

在 NetBeans 的“服务”选项卡中,右键单击 Java DB 节点,然后选择“创建数据库”选项。 我们给它命名为testdb。 请注意,数据库位于用户主目录的.netbeans_derby目录中。

cars_derby.sql

CREATE TABLE CARS(ID INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY 
    (START WITH 1, INCREMENT BY 1), NAME VARCHAR(30), PRICE INT);

INSERT INTO CARS(Name, Price) VALUES('Audi', 52642);
INSERT INTO CARS(Name, Price) VALUES('Mercedes', 57127);
INSERT INTO CARS(Name, Price) VALUES('Skoda', 9000);
INSERT INTO CARS(Name, Price) VALUES('Volvo', 29000);
INSERT INTO CARS(Name, Price) VALUES('Bentley', 350000);
INSERT INTO CARS(Name, Price) VALUES('Citroen', 21000);
INSERT INTO CARS(Name, Price) VALUES('Hummer', 41400);
INSERT INTO CARS(Name, Price) VALUES('Volkswagen', 21600);

这是创建Cars表的 SQL。 汽车对象的 ID 会自动增加。 我们可以使用 NetBeans 工具创建Cars表。 我们右键单击“数据库”节点,然后选择“新建连接”选项。

Connections

图:连接

创建一个新的连接对象; 它由橙色图标表示。 其上下文菜单提供了用于连接到指定数据库并执行命令的选项。 “执行命令”选项显示了执行 SQL 命令的工具。 在此窗口中,我们可以使用上面的 SQL 创建Cars表。

本机 Hibernate API 和 XML 映射

在本部分中,我们将创建一个 Java 控制台应用,该应用在 Derby 数据库上执行一些数据库任务。 我们使用 Hibernate 本机 API 和 XML 映射。

Project structure in NetBeans

NetBeans 中的项目结构

在上图中,我们可以看到 NetBeans IDE 中的项目结构。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zetcode</groupId>
    <artifactId>HibernateDerbyEx</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.apache.derby</groupId>
            <artifactId>derbyclient</artifactId>
            <version>10.12.1.1</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.2.2.Final</version>
        </dependency>

    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>                      
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
        </resources>
    </build>   
</project>

pom.xml文件中,我们定义两个依赖项:hibernate-core库和derbyclient驱动程序。 在<build>元素中,我们让构建系统包含 XML 文件-我们将 XML 映射文件放入src/main/java目录中。

hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" 
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">org.apache.derby.jdbc.ClientDriver</property>
        <property name="hibernate.connection.username">app</property>
        <property name="hibernate.connection.password">ap</property>
        <property name="hibernate.connection.url">jdbc:derby://localhost:1527/testdb</property>

        <!-- Enable Hibernate's automatic session context management -->
        <property name="current_session_context_class">thread</property>

        <property name="hibernate.dialect"<org.hibernate.dialect.DerbyTenSevenDialect</property>
        <mapping resource="com/zetcode/hibernate/Car.hbm.xml"></mapping>
    </session-factory>
</hibernate-configuration>

在 Hibernate 配置文件中,我们提供了 Derby 数据库的连接属性。 我们启用了 Hibernate 的自动会话上下文管理,并指定了 Derby SQL 方言。 映射与<mapping>元素一起添加。 我们有一个Car对象到CARS表的映射。

Car.java

package com.zetcode.bean;

public class Car {

    private Long Id;
    private String Name;
    private Integer Price;

    public Long getId() {
        return Id;
    }

    public void setId(Long Id) {
        this.Id = Id;
    }

    public String getName() {
        return Name;
    }

    public void setName(String Name) {
        this.Name = Name;
    }

    public Integer getPrice() {
        return Price;
    }

    public void setPrice(Integer Price) {
        this.Price = Price;
    }

    @Override
    public String toString() {

        return String.format("Car Id: %d Name: %s; Price: %d", 
                Id, Name, Price);
    }    
}

这是一个Car bean。 它具有三个属性以及相应的获取器和设置器。

Car.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" 
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-mapping>
    <class name="com.zetcode.bean.Car" table="CARS" catalog="app">
        <id name="Id" type="java.lang.Long">
            <column name="Id" />
            <generator class="identity" />
        </id>
        <property name="Name" type="string">
            <column name="Name" length="30"/>
        </property>
        <property name="Price" type="integer">
            <column name="Price" />
        </property>
    </class>
</hibernate-mapping>

Car.hbm.xml文件中,我们提供Car类和CARS表之间的映射。 我们将类的属性映射到数据库表的列。 在<hibernate-mapping></hibernate-mapping>元素之间指定了映射。

<generator class="identity" />

generator元素通知 Hibernate 使用什么策略来生成主键。 identity生成器类允许根据需要自动增加integer/bigint列。 此生成器受 Derby 支持。

HibernateUtils.java

package com.zetcode.util;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtils {

    private HibernateUtils() {}

    private static final SessionFactory sessionFactory;

    static {
        try {
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }

   public static void shutdown() {

        getSessionFactory().close();
    }    
}

HibernateUtils是一个帮助程序类,用于处理启动并访问SessionFactory以获取会话对象。 然后,使用会话对象访问数据库。

sessionFactory = new Configuration().configure().buildSessionFactory();

该行从hibernate.cfg.xml文件创建一个SessionFactory

getSessionFactory().close();

该行关闭缓存和连接池。

CarService.java

package com.zetcode.service;

import com.zetcode.bean.Car;
import com.zetcode.util.HibernateUtils;
import java.util.List;
import org.hibernate.Session;

public class CarService {

    private CarService() {};

    public static Car getCarById(Long id) {

        Car car;
        try (Session session = HibernateUtils.getSessionFactory().openSession()) {
            car = session.get(Car.class, id);
        }

        return car;
    }    

    public static List<Car> getCars() {

        List<Car> cars;
        try (Session session = HibernateUtils.getSessionFactory().openSession()) {
            cars = session.createQuery("from Car").list();
        }
        return cars;
    }

    public static void save(Car car) {

        try (Session session = HibernateUtils.getSessionFactory().openSession()) {
            session.beginTransaction();

            session.save(car);

            session.getTransaction().commit();
        }
    }
}

CarService类中,我们有一些服务方法,可通过其 ID 获取汽车,获取所有汽车并保存新汽车。

try (Session session = HibernateUtils.getSessionFactory().openSession()) {
    car = session.get(Car.class, id);
}

HibernateUtils用于获取并打开会话对象。 Sessionget()方法返回具有给定标识符的给定实体类的持久实例,如果没有这样的持久实例,则返回null

cars = session.createQuery("from Car").list();

createQuery()方法为给定的 HQL 查询字符串创建Query的新实例。 from Car查询返回Car类的所有实例。

session.save(car);

save()方法保留给定实例。

HibernateDerbyEx.java

package com.zetcode.main;

import com.zetcode.bean.Car;
import com.zetcode.service.CarService;
import com.zetcode.util.HibernateUtils;
import java.util.List;

public class HibernateDerbyEx {

    public static void main(String[] args) {

        Long id = 1L;

        Car car = CarService.getCarById(id);

        System.out.println(car);

        Car newCar = new Car();

        newCar.setName("Toyota");
        newCar.setPrice(34500);

        CarService.save(newCar);

        List<Car> cars = CarService.getCars();

        for (Car mycar : cars) {

            System.out.println(mycar);
        }

        HibernateUtils.shutdown();
    }
}

这是主要的应用类。 我们通过其 ID 获得一辆汽车,保存一辆新汽车,并从数据库表中列出所有汽车。

Long id = 1L;

Car car = CarService.getCarById(id);

我们使用CarServicegetCarById()方法通过汽车 ID 检索汽车。

Car newCar = new Car();

newCar.setName("Toyota");
newCar.setPrice(34500);

CarService.save(newCar);

将创建新车并将其保存到数据库中。

List<Car> cars = CarService.getCars();

for (Car mycar : cars) {

    System.out.println(mycar);
}

我们从CARS表中列出了所有汽车。

HibernateUtils.shutdown();

最后,我们关闭打开的资源。

本机 Hibernate API 和注解映射

在本部分中,我们将创建一个 Java 控制台应用,该应用在 Derby 数据库上执行一些数据库任务。 我们使用 Hibernate 本机 API 和注解映射。

pom.xmlCarService.javaHibernateDerbyNativeEx.javaHibernateUtils.java文件不变。 hibernate.cfg.xmlCar.java确实发生了变化。

hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" 
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">org.apache.derby.jdbc.ClientDriver</property>
        <property name="hibernate.connection.username">app</property>
        <property name="hibernate.connection.password">ap</property>
        <property name="hibernate.connection.url">jdbc:derby://localhost:1527/testdb</property>

        <!-- Enable Hibernate's automatic session context management -->
        <property name="current_session_context_class">thread</property>

        <property name="hibernate.dialect">org.hibernate.dialect.DerbyTenSevenDialect</property>
        <mapping class="com.zetcode.bean.Car"></mapping>
    </session-factory>
</hibernate-configuration>

hibernate.cfg.xml文件中,<mapping>元素已更改。

<mapping class="com.zetcode.bean.Car"></mapping>

我们使用class属性指向 Java 实体,其中包含映射注解。

Car.java

package com.zetcode.bean;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="CARS")
public class Car implements Serializable {

    private Long Id;
    private String Name;
    private Integer Price;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long getId() {
        return Id;
    }

    public void setId(Long Id) {
        this.Id = Id;
    }

    public String getName() {
        return Name;
    }

    public void setName(String Name) {
        this.Name = Name;
    }

    public Integer getPrice() {
        return Price;
    }

    public void setPrice(Integer Price) {
        this.Price = Price;
    }

    @Override
    public String toString() {

        return String.format("Car Id: %d Name: %s; Price: %d", 
                Id, Name, Price);
    }    
}

Car.java类中,我们使用注解定义映射。 类属性的名称和表列的名称将自动配对。 如果名称不同,则必须使用@Column注解指定列名称。

@Entity
@Table(name="CARS")
public class Car implements Serializable {

该类用@Entity注解装饰; @Table注解指定被注解实体的主表。

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {

@Id注解指定实体的主键,@GeneratedValue提供规范主键值的生成策略。

在本教程中,我们介绍了 Hibernate ORM。 我们已经使用了 Derby 数据库。 ZetCode 具有以下相关教程: EclipseLink 教程MySQL Java 教程JDBC 模板教程Apache Derby 教程

MySQL 中的SELECT语句

原文: http://zetcode.com/databases/mysqltutorial/select/

MySQL 教程的这一部分将详细介绍 MySQL 理解的SELECT语句。

检索数据

以下 SQL 语句是最常见的语句之一。 它也是最昂贵的之一。

mysql> SELECT * FROM Cars;
+----+------------+--------+
| Id | Name       | Cost   |
+----+------------+--------+
|  1 | Audi       |  52642 |
|  2 | Mercedes   |  57127 |
|  3 | Skoda      |   9000 |
|  4 | Volvo      |  29000 |
|  5 | Bentley    | 350000 |
|  6 | Citroen    |  21000 |
|  7 | Hummer     |  41400 |
|  8 | Volkswagen |  21600 |
+----+------------+--------+
8 rows in set (0.00 sec)

在这里,我们从Cars表中检索所有数据。

选择特定的列

我们可以使用SELECT语句来检索特定的列。 列名紧随SELECT字。

mysql> SELECT Name, Cost FROM Cars;
+------------+--------+
| Name       | Cost   |
+------------+--------+
| Audi       |  52642 |
| Mercedes   |  57127 |
| Skoda      |   9000 |
| Volvo      |  29000 |
| Bentley    | 350000 |
| Citroen    |  21000 |
| Hummer     |  41400 |
| Volkswagen |  21600 |
+------------+--------+
8 rows in set (0.00 sec) 

我们检索NameCost列。 列名用逗号分隔。

重命名列名

我们可以重命名返回结果集的列名。 为此,我们使用AS子句。

mysql> SELECT Name, Cost AS Price FROM Cars;
+------------+--------+
| Name       | Price  |
+------------+--------+
| Audi       |  52642 |
| Mercedes   |  57127 |
| Skoda      |   9000 |
| Volvo      |  29000 |
| Bentley    | 350000 |
| Citroen    |  21000 |
| Hummer     |  41400 |
| Volkswagen |  21600 |
+------------+--------+
8 rows in set (0.00 sec)

假设我们要命名列价格而不是成本。 通过上面的 SQL 语句,我们已经完成了这一步。

限制数据输出

如上所述,在处理大量数据时,检索所有数据非常昂贵。 我们可以使用LIMIT子句来限制该语句返回的数据量。

mysql> SELECT * FROM Cars LIMIT 4;
+----+----------+-------+
| Id | Name     | Cost  |
+----+----------+-------+
|  1 | Audi     | 52642 |
|  2 | Mercedes | 57127 |
|  3 | Skoda    |  9000 |
|  4 | Volvo    | 29000 |
+----+----------+-------+
4 rows in set (0.00 sec) 

LIMIT子句将返回的行数限制为 4。

LIMIT有两个参数,返回从偏移值开始的行。

mysql> SELECT * FROM Cars LIMIT 2, 4;
+----+---------+--------+
| Id | Name    | Cost   |
+----+---------+--------+
|  3 | Skoda   |   9000 |
|  4 | Volvo   |  29000 |
|  5 | Bentley | 350000 |
|  6 | Citroen |  21000 |
+----+---------+--------+
4 rows in set (0.00 sec)   

第一个值是偏移量,第二个值是要返回的行数。 在这里,我们从最多四行中选择所有数据,然后从第三行开始。

mysql> SELECT * FROM Cars LIMIT 4 OFFSET 2;
+----+---------+--------+
| Id | Name    | Cost   |
+----+---------+--------+
|  3 | Skoda   |   9000 |
|  4 | Volvo   |  29000 |
|  5 | Bentley | 350000 |
|  6 | Citroen |  21000 |
+----+---------+--------+
4 rows in set (0.00 sec)

为了与 PostgreSQL 数据库兼容,MySQL 还具有OFFSET关键字。 上面的代码等效于前面的示例。

排序数据

我们使用ORDER BY子句对返回的数据集进行排序。 ORDER BY子句后面是进行排序的列。 ASC关键字以升序对数据进行排序,DESC则以降序对数据进行排序。

mysql> SELECT Name, Cost FROM Cars ORDER BY Cost DESC;
+------------+--------+
| Name       | Cost   |
+------------+--------+
| Bentley    | 350000 |
| Mercedes   |  57127 |
| Audi       |  52642 |
| Hummer     |  41400 |
| Volvo      |  29000 |
| Volkswagen |  21600 |
| Citroen    |  21000 |
| Skoda      |   9000 |
+------------+--------+
8 rows in set (0.00 sec)

在上面的 SQL 语句中,我们从Cars表中选择名称,成本列,然后按汽车成本降序对其进行排序。 因此,最昂贵的汽车排在第一位。

使用WHERE子句选择特定的行

在以下示例中,我们将使用Orders表。

mysql> SELECT * FROM Orders;
+----+------------+------------+
| Id | OrderPrice | Customer   |
+----+------------+------------+
|  1 |       1200 | Williamson |
|  2 |        200 | Robertson  |
|  3 |         40 | Robertson  |
|  4 |       1640 | Smith      |
|  5 |        100 | Robertson  |
|  6 |         50 | Williamson |
|  7 |        150 | Smith      |
|  8 |        250 | Smith      |
|  9 |        840 | Brown      |
| 10 |        440 | Black      |
| 11 |         20 | Brown      |
+----+------------+------------+
11 rows in set (0.00 sec)

在这里,我们看到Orders表中的所有数据。

接下来,我们要选择一个特定的行。

mysql> SELECT * FROM Orders WHERE Id=6;
+----+------------+------------+
| Id | OrderPrice | Customer   |
+----+------------+------------+
|  6 |         50 | Williamson |
+----+------------+------------+
1 row in set (0.00 sec)

上面的 SQL 语句选择具有 ID 6 的行。

mysql> SELECT * FROM Orders WHERE Customer="Smith";
+----+------------+----------+
| Id | OrderPrice | Customer |
+----+------------+----------+
|  4 |       1640 | Smith    |
|  7 |        150 | Smith    |
|  8 |        250 | Smith    |
+----+------------+----------+
3 rows in set (0.00 sec)

上面的 SQL 语句选择Smith客户创建的所有订单。

我们可以使用LIKE关键字在数据中查找特定的模式。

mysql> SELECT * FROM Orders WHERE Customer LIKE "B%";
+----+------------+----------+
| Id | OrderPrice | Customer |
+----+------------+----------+
|  9 |        840 | Brown    |
| 10 |        440 | Black    |
| 11 |         20 | Brown    |
+----+------------+----------+
3 rows in set (0.00 sec)

该 SQL 语句从名称以 B 字符开头的客户中选择所有订单。

删除重复项

DISTINCT关键字仅用于从结果集中选择唯一项。

mysql> SELECT Customer FROM Orders WHERE Customer LIKE 'B%';
+----------+
| Customer |
+----------+
| Brown    |
| Black    |
| Brown    |
+----------+
3 rows in set (0.00 sec)

这次,我们选择了名称以 B 字符开头的客户。 我们可以看到布朗两次被提及。 要删除重复项,我们使用DISTINCT关键字。

mysql> SELECT DISTINCT Customer FROM Orders WHERE Customer LIKE 'B%';
+----------+
| Customer |
+----------+
| Brown    |
| Black    |
+----------+
2 rows in set (0.00 sec)

这是正确的解决方案。

假设我们想算出布朗客户下了多少订单。 我们将利用COUNT()函数。

mysql> SELECT COUNT(Customer) AS "Orders by Brown" FROM Orders WHERE Customer="Brown";
+-----------------+
| Orders by Brown |
+-----------------+
|               2 |
+-----------------+
1 row in set (0.00 sec)

客户下了两个订单。

分组数据

GROUP BY子句用于将具有相同值的数据库记录组合到单个记录中。 它通常与聚合函数一起使用。

说我们想找出每个客户的订单总和。

mysql> SELECT SUM(OrderPrice) AS Total, Customer FROM Orders GROUP BY Customer;
+-------+------------+
| Total | Customer   |
+-------+------------+
|   440 | Black      |
|   860 | Brown      |
|   340 | Robertson  |
|  2040 | Smith      |
|  1250 | Williamson |
+-------+------------+
5 rows in set (0.00 sec)

SUM()关键字返回数字列的总和。 GROUP BY子句将总金额分配给客户。 因此,我们可以看到Black订购了 440,Smith订购了 2040 个商品。

使用聚合函数时,不能使用WHERE子句。 我们改用HAVING子句。

mysql> SELECT SUM(OrderPrice) AS Total, Customer FROM Orders
    -> GROUP BY Customer HAVING SUM(OrderPrice)>1000;
+-------+------------+
| Total | Customer   |
+-------+------------+
|  2040 | Smith      |
|  1250 | Williamson |
+-------+------------+
2 rows in set (0.00 sec)

上面的 SQL 语句选择总订单量大于 1000 个单位的客户。

选择数据到文件

SELECT语句可用于将表中的数据写入文件。

mysql> SELECT * INTO OUTFILE '/tmp/cars.txt'
    -> FIELDS TERMINATED BY ','
    -> LINES TERMINATED BY '\n'
    -> FROM Cars;
Query OK, 8 rows affected (0.00 sec)

我们将Cars表中的数据写入cars.txt文件。 输出文件是 CSV(逗号分隔值)文件。 请注意,此操作容易出错,我们很容易遇到权限被拒绝的错误。

$ cat /tmp/cars.txt 
1,Audi,52642
2,Mercedes,57127
3,Skoda,9000
4,Volvo,29000
5,Bentley,350000
6,Citroen,21000
7,Hummer,41400
8,Volkswagen,21600

我们可以做相反的操作; 将文件中的数据加载到表中。

mysql> DELETE FROM Cars;
Query OK, 8 rows affected (0.00 sec)

mysql> SELECT * FROM Cars;
Empty set (0.00 sec)

我们从Cars表中删除所有行。

mysql> LOAD DATA INFILE '/tmp/cars.txt' 
    -> INTO TABLE Cars
    -> FIELDS TERMINATED BY ','
    -> LINES TERMINATED BY '\n';
Query OK, 8 rows affected (0.00 sec)
Records: 8  Deleted: 0  Skipped: 0  Warnings: 0

mysql> SELECT *  FROM Cars;
+----+------------+--------+
| Id | Name       | Cost   |
+----+------------+--------+
|  1 | Audi       |  52642 |
|  2 | Mercedes   |  57127 |
|  3 | Skoda      |   9000 |
|  4 | Volvo      |  29000 |
|  5 | Bentley    | 350000 |
|  6 | Citroen    |  21000 |
|  7 | Hummer     |  41400 |
|  8 | Volkswagen |  21600 |
+----+------------+--------+
8 rows in set (0.00 sec)

我们使用LOAD DATA INFILE语句将数据加载回表中。 我们验证数据是否正确加载。

在 MySQL 教程的这一部分中,我们更详细地提到了 SQL SELECT语句。

MySQL 子查询

http://zetcode.com/databases/mysqltutorial/subqueries/

在 MySQL 教程的这一部分中,我们将提到 MySQL 中的子查询。

子查询是查询中的查询。 也称为内部查询或嵌套查询。 子查询可在允许表达式的任何地方使用。 它是一个用括号括起来的查询表达式。 子查询可以与SELECTINSERTUPDATEDELETE语句一起使用。

有多种方法可以执行 SQL 任务。 许多子查询可以用 SQL 连接代替。 SQL 连接通常更快。

在本章中,我们将使用以下表:

mysql> SELECT * FROM Cars;
+----+------------+--------+
| Id | Name       | Cost   |
+----+------------+--------+
|  1 | Audi       |  52642 |
|  2 | Mercedes   |  57127 |
|  3 | Skoda      |   9000 |
|  4 | Volvo      |  29000 |
|  5 | Bentley    | 350000 |
|  6 | Citroen    |  21000 |
|  7 | Hummer     |  41400 |
|  8 | Volkswagen |  21600 |
+----+------------+--------+

Cars表中的数据。

mysql> SELECT * FROM Customers; SELECT * FROM Reservations;
+------------+-------------+
| CustomerId | Name        |
+------------+-------------+
|          1 | Paul Novak  |
|          2 | Terry Neils |
|          3 | Jack Fonda  |
|          4 | Tom Willis  |
+------------+-------------+
4 rows in set (0.00 sec)

+----+------------+------------+
| Id | CustomerId | Day        |
+----+------------+------------+
|  1 |          1 | 2009-11-22 |
|  2 |          2 | 2009-11-28 |
|  3 |          2 | 2009-11-29 |
|  4 |          1 | 2009-11-29 |
|  5 |          3 | 2009-12-02 |
+----+------------+------------+
5 rows in set (0.00 sec)

我们总结一下CustomersReservations表中的内容。 子查询通常在具有某种关系的表上执行。

带有INSERT语句的子查询

我们要创建Cars表的副本。 进入另一个称为Cars2的表。 我们将为此创建一个子查询。

mysql> CREATE TABLE Cars2(Id INT NOT NULL PRIMARY KEY, 
    -> Name VARCHAR(50) NOT NULL, Cost INT NOT NULL);

我们创建一个新的Cars2表,其列和数据类型与Cars表相同。 要了解如何创建表,我们可以使用SHOW CREATE TABLE语句。

mysql> INSERT INTO Cars2 SELECT * FROM Cars;

这是一个简单的子查询。 我们将Cars表中的所有行插入Cars2表中。

mysql> SELECT * FROM Cars2;
+----+------------+--------+
| Id | Name       | Cost   |
+----+------------+--------+
|  1 | Audi       |  52642 |
|  2 | Mercedes   |  57127 |
|  3 | Skoda      |   9000 |
|  4 | Volvo      |  29000 |
|  5 | Bentley    | 350000 |
|  6 | Citroen    |  21000 |
|  7 | Hummer     |  41400 |
|  8 | Volkswagen |  21600 |
+----+------------+--------+

数据已复制到新的Cars2表。

标量子查询

标量子查询返回单个值。

mysql> SELECT Name FROM Customers WHERE 
    -> CustomerId=(SELECT CustomerId FROM Reservations WHERE Id=5);
+------------+
| Name       |
+------------+
| Jack Fonda |
+------------+

括号中的查询是子查询。 它返回一个单个标量值。 然后,在外部查询中使用返回的值。 在此标量子查询中,我们从Customers表中返回客户的名称,该客户的预订在Reservations表中的 ID 等于 5。

表子查询

一个表子查询返回一个零或更多行的结果表。

mysql> SELECT Name FROM Customers WHERE CustomerId IN    
    -> (SELECT DISTINCT CustomerId FROM Reservations);
+-------------+
| Name        |
+-------------+
| Paul Novak  |
| Terry Neils |
| Jack Fonda  |
+-------------+

上面的查询返回进行预订的客户的姓名。 内部查询从Reservations表返回客户 ID。 我们使用IN谓词来选择从内部选择查询返回其CustomerId的那些客户名称。

mysql> SELECT DISTINCT Name FROM Customers JOIN Reservations
    -> ON Customers.CustomerId=Reservations.CustomerId;
+-------------+
| Name        |
+-------------+
| Paul Novak  |
| Terry Neils |
| Jack Fonda  |
+-------------+

可以使用 SQL 连接重写以前的子查询。

相关子查询

相关子查询是使用WHERE子句中外部查询的值的子查询。 对于外部查询处理的每一行,子查询都会被求值一次。

mysql> SELECT Name FROM Cars WHERE Cost <
    -> (SELECT AVG(Cost) FROM Cars);
+------------+
| Name       |
+------------+
| Audi       |
| Mercedes   |
| Skoda      |
| Volvo      |
| Citroen    |
| Hummer     |
| Volkswagen |
+------------+

在上面的相关子查询中,我们返回价格低于表中所有汽车平均价格的所有汽车。

具有EXISTS(不存在)的子查询

如果子查询返回任何值,则谓词EXISTS返回TRUENOT EXISTS FALSE

mysql> SELECT Name FROM Customers WHERE EXISTS
    -> (SELECT * FROM Reservations WHERE
    -> Customers.CustomerId=Reservations.CustomerId);
+-------------+
| Name        |
+-------------+
| Paul Novak  |
| Terry Neils |
| Jack Fonda  |
+-------------+

在上面的 SQL 语句中,我们选择所有客户的名称,这些名称在Reservations表中都有一个条目。

mysql> SELECT Name FROM Customers WHERE NOT EXISTS    
    -> (SELECT * FROM Reservations WHERE 
    -> Customers.CustomerId=Reservations.CustomerId);
+------------+
| Name       |
+------------+
| Tom Willis |
+------------+

在此查询中,我们返回Reservations表中没有条目的所有客户。 这两个 SQL 查询都是相关查询。

MySQL 教程的这一部分专门针对 MySQL 子查询。

MySQL 约束

原文: http://zetcode.com/databases/mysqltutorial/constraints/

在 MySQL 教程的这一部分中,我们将处理约束。

约束放在列或表上。 它们限制了可以插入表中的数据。

我们有以下限制:

  • 非空
  • 唯一
  • 主键
  • 外键
  • 枚举

其他数据库也具有CHECK约束,该约束为有效数据设置了条件。 MySQL 解析了这个约束,但是没有强制执行。

非空约束

具有NOT NULL约束的列不能具有NULL值。

mysql> CREATE TABLE People(Id INTEGER, LastName TEXT NOT NULL,
    ->                     FirstName TEXT NOT NULL, City VARCHAR(55));
Query OK, 0 rows affected (0.07 sec)

我们创建两个具有NOT NULL约束的列。

mysql> INSERT INTO People VALUES(1, 'Hanks', 'Robert', 'New York');
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO People VALUES(1, NULL, 'Marianne', 'Chicago');
ERROR 1048 (23000): Column 'LastName' cannot be null

第一个SELECT语句执行成功,第二个失败。 SQL 错误说,LastName列不能为空。

唯一约束

UNIQUE约束确保所有数据在列中都是唯一的。

mysql> CREATE TABLE Brands(Id INTEGER, BrandName VARCHAR(30) UNIQUE);
Query OK, 0 rows affected (0.08 sec)

在这里,我们创建一个表BrandsBrandName列设置为UNIQUE。 不能有两个名称相同的品牌。

mysql> INSERT INTO Brands VALUES(1, 'Coca Cola');
Query OK, 1 row affected (0.03 sec)

mysql> INSERT INTO Brands VALUES(2, 'Pepsi');
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO Brands VALUES(3, 'Pepsi');
ERROR 1062 (23000): Duplicate entry 'Pepsi' for key 'BrandName'

对于键"BrandName",我们收到一个 SQL 错误,重复项"Pepsi"。 只能有一个百事可乐品牌。

注意,PRIMARY KEY约束自动在其上定义了UNIQUE约束。

主键

PRIMARY KEY约束唯一地标识数据库表中的每个记录。 这是唯一键的特例。 主键不能为NULL,唯一键可以为。 可以有更多UNIQUE列,但是表中只有一个主键。 在设计数据库表时,主键很重要。 主键是唯一的 ID。 我们使用它们来引用表行。 在表之间创建关系时,主键成为其他表中的外键。

mysql> DROP TABLE Brands;
mysql> CREATE TABLE Brands(Id INTEGER PRIMARY KEY, BrandName VARCHAR(30) UNIQUE);

Brands表的Id列成为主键。

mysql> DESCRIBE Brands;
+-----------+-------------+------+-----+---------+-------+
| Field     | Type        | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+-------+
| Id        | int(11)     | NO   | PRI | NULL    |       |
| BrandName | varchar(30) | YES  | UNI | NULL    |       |
+-----------+-------------+------+-----+---------+-------+

DESCRIBE语句显示有关表中各列的信息。 我们可以看到Id列定义了PRIMARY KEYBrandName设置了UNIQUE约束。 在处理特定表时,主键用于唯一标识表中的行。 唯一键强制列中的所有数据都不重复。

外键

一个表中的FOREIGN KEY指向另一表中的PRIMARY KEY。 它是两个表之间的引用约束。 外键标识一个(引用)表中的一列或一组列,该列或表引用另一(引用)表中的一列或一组列。

我们将在两个表上显示此约束:AuthorsBooks

mysql> CREATE TABLE Authors(AuthorId INTEGER PRIMARY KEY, Name VARCHAR(70))
    -> type=InnoDB;

在这里,我们创建Authors表。 在 MySQL 中,引用表和被引用表必须是 InnoDB 或 BDB 存储引擎。 在 MyISAM 存储引擎中,将解析外键,但不会强制使用外键。

mysql> CREATE TABLE Books(BookId INTEGER PRIMARY KEY, Title VARCHAR(50),
    -> AuthorId INTEGER, FOREIGN KEY(AuthorId) REFERENCES Authors(AuthorId))
    -> type=InnoDB;

我们创建Books表。 在这里,我们有一个AuthorId列名,它用作外键。 它引用Authors表的主键。

在我们的示例中,外键强制执行意味着什么? 我们无法使用Authors本书中没有的AuthorIdBooks表中插入一行。

ENUM约束

ENUM是一个字符串对象,其值是从允许值列表中选择的。 在创建表时,它们在列规范中显式枚举。

mysql> CREATE TABLE Shops(Id INTEGER, Name VARCHAR(55), 
    -> Quality ENUM('High', 'Average', 'Low'));

我们有一个Shops表。 该表具有定义的IdNameQuality列。 Quality列是ENUM。 它允许具有三个指定值之一:HighAverageLow

mysql> INSERT INTO Shops VALUES(1, 'Boneys', 'High');
mysql> INSERT INTO Shops VALUES(2, 'AC River', 'Average');
mysql> INSERT INTO Shops VALUES(3, 'AT 34', '**');
mysql> SELECT * FROM Shops;
+------+----------+---------+
| Id   | Name     | Quality |
+------+----------+---------+
|    1 | Boneys   | High    |
|    2 | AC River | Average |
|    3 | AT 34    |         |
+------+----------+---------+

在前两个语句中,我们插入了两行。 在第三种情况下,该值在ENUM中不可用。 在这种情况下,将插入一个空字符串。

SET约束

SET可以具有零个或多个值。 每个值都必须从允许值列表中选择。

mysql> CREATE TABLE Students(Id INTEGER, Name VARCHAR(55), 
    -> Certificates SET('A1', 'A2', 'B1', 'C1')); 

我们有一个Students表。 在此表中,我们有一个Certificates列。 每个学生可以拥有 0、1 个或多个这些证书。 这与ENUM约束不同,在ENUM约束中,允许值列表中只能有一个不同的值。

mysql> INSERT INTO Students VALUES(1, 'Paul', 'A1,B1');
mysql> INSERT INTO Students VALUES(2, 'Jane', 'A1,B1,A2');
mysql> INSERT INTO Students VALUES(3, 'Mark', 'A1,A2,D1,D2');
mysql> SELECT * FROM Students;
+------+------+--------------+
| Id   | Name | Certificates |
+------+------+--------------+
|    1 | Paul | A1,B1        |
|    2 | Jane | A1,A2,B1     |
|    3 | Mark | A1,A2        |
+------+------+--------------+

保罗有两个证书,珍妮有三个,马克有四个,但是只有两个被认可,因此只有前两个被写到了表中。 证书用逗号分隔。 不允许有空格。

在 MySQL 教程的这一部分中,我们介绍了 MySQL 支持的约束。

在 MySQL 中导出和导入数据

原文: http://zetcode.com/databases/mysqltutorial/exportimport/

在 MySQL 教程的这一部分中,我们将从 MySQL 数据库导出数据并将数据导入回来。

简单的数据导出

在第一个示例中,我们将数据保存在文本文件中。

mysql> SELECT * FROM Cars INTO OUTFILE '/tmp/cars';
Query OK, 8 rows affected (0.00 sec)

我们从Cars表中选择所有行(8)到/tmp目录中的cars文件中。 我们需要具有写入该目录的权限。

$ cat /tmp/cars
1       Audi    52642
2       Mercedes        57127
3       Skoda   9000
4       Volvo   29000
5       Bentley 350000
6       Citroen 21000
7       Hummer  41400
8       Volkswagen      21600

我们显示文件的内容。

mysql> DELETE FROM Cars;

mysql> LOAD DATA INFILE '/tmp/cars' INTO TABLE Cars;

在第一条语句中,我们删除表中的所有行。 在第二条语句中,我们将所有数据从文本文件加载到Cars表中。

mysql> SELECT * FROM Cars INTO OUTFILE '/tmp/cars.csv'
    -> FIELDS TERMINATED BY ',';

在上面的 SQL 语句中,我们将Cars表中的所有数据转储到cars.csv文件中。 FIELDS TERMINATED BY子句控制在文本文件中如何终止数据。 我们选择了一个逗号字符。 CSV 代表逗号分隔值,它是一种非常常见且可移植的文件格式。 它可以由许多其他应用(如 OpenOffice,其他数据库等)导入。

$ cat /tmp/cars.csv 
1,Audi,52642
2,Mercedes,57127
3,Skoda,9000
4,Volvo,29000
5,Bentley,350000
6,Citroen,21000
7,Hummer,41400
8,Volkswagen,21600

这是cars.csv文件的内容。

mysql> DELETE FROM Cars;

mysql> LOAD DATA INFILE '/tmp/cars.csv' INTO TABLE Cars
    -> FIELDS TERMINATED BY ',';

mysql> SELECT * FROM Cars;
+----+------------+--------+
| Id | Name       | Cost   |
+----+------------+--------+
|  1 | Audi       |  52642 |
|  2 | Mercedes   |  57127 |
|  3 | Skoda      |   9000 |
|  4 | Volvo      |  29000 |
|  5 | Bentley    | 350000 |
|  6 | Citroen    |  21000 |
|  7 | Hummer     |  41400 |
|  8 | Volkswagen |  21600 |
+----+------------+--------+

我们删除所有数据,然后从cars.csv文件中将其还原。

导出到 XML 文件

可以使用mysql监视器导出和导入 XML 数据。

$ mysql -uroot -p --xml -e 'SELECT * FROM mydb.Cars' > /tmp/cars.xml

mysql监视器具有--xml选项,使我们能够以 XML 格式转储数据。 -e选项执行一条语句并退出监视器。

$ cat /tmp/cars.xml 
<?xml version="1.0"?>

<resultset statement="SELECT * FROM mydb.Cars
" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <row>
        <field name="Id">1</field>
        <field name="Name">Audi</field>
        <field name="Cost">52642</field>
  </row>

  <row>
        <field name="Id">2</field>
        <field name="Name">Mercedes</field>
        <field name="Cost">57127</field>
  </row>

  <row>
        <field name="Id">3</field>
        <field name="Name">Skoda</field>
        <field name="Cost">9000</field>
  </row>

  <row>
        <field name="Id">4</field>
        <field name="Name">Volvo</field>
        <field name="Cost">29000</field>
  </row>

  <row>
        <field name="Id">5</field>
        <field name="Name">Bentley</field>
        <field name="Cost">350000</field>
  </row>

  <row>
        <field name="Id">6</field>
        <field name="Name">Citroen</field>
        <field name="Cost">21000</field>
  </row>

  <row>
        <field name="Id">7</field>
        <field name="Name">Hummer</field>
        <field name="Cost">41400</field>
  </row>

  <row>
        <field name="Id">8</field>
        <field name="Name">Volkswagen</field>
        <field name="Cost">21600</field>
  </row>
</resultset>

这是mysql监视器生成的 XML 文件。

mysql> TRUNCATE Cars;

mysql> LOAD XML /tmp/cars.xml INTO TABLE Cars;

我们截断Cars表。 我们从 XML 文件加载数据。 请注意,LOAD XML语句可用于 MySQL 5.5 及更高版本。

使用mysqldump工具

mysqldump是用于为 MySQL 创建备份的命令工具。 当我们将数据从一个地方传输到另一个地方时,会使用转储一词。 从数据库文件到文本文件。 从内存到文件。 和类似。

转储表结构

mysqldump -u root -p --no-data mydb > bkp1.sql

上面的命令将mydb数据库中所有表的表结构转储到bkq1.sql文件中。 --no-data选项导致不保存数据,仅保存表结构。

--
-- Table structure for table `Cars`
--

DROP TABLE IF EXISTS `Cars`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `Cars` (
  `Id` int(11) NOT NULL,
  `Name` varchar(50) DEFAULT NULL,
  `Cost` int(11) DEFAULT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

在这里,我们看到了bkp1.sql文件的一部分。 这是用于创建Cars表的 SQL。

仅转储数据

$ mysqldump -uroot -p --no-create-info mydb > bkp2.sql

此命令转储mydb数据库的所有表中的所有数据。 它省略了表结构。 表结构的遗漏是由--no-create-info选项引起的。

--
-- Dumping data for table `Cars`
--

LOCK TABLES `Cars` WRITE;
/*!40000 ALTER TABLE `Cars` DISABLE KEYS */;
INSERT INTO `Cars` VALUES (1,'Audi',52642),(2,'Mercedes',57127),(3,'Skoda',9000),
(4,'Volvo',29000),(5,'Bentley',350000),(6,'Citroen',21000),
(7,'Hummer',41400),(8,'Volkswagen',21600);
/*!40000 ALTER TABLE `Cars` ENABLE KEYS */;
UNLOCK TABLES;

在这里,我们可以看到Cars表的数据。

转储整个数据库

$ mysqldump -uroot -p mydb > bkp3.sql

此命令将所有表从mydb数据库转储到bkp3.sql文件。

恢复数据

我们展示了如何从备份 SQL 文件还原数据库。

mysql> DROP DATABASE mydb;
ERROR 1010 (HY000): Error dropping database (can't rmdir './mydb/', errno: 17)

mysql> SHOW TABLES;
Empty set (0.00 sec)

我们删除mydb数据库。 显示错误。 表已删除,但数据库未删除。

$ sudo ls /var/lib/mysql/mydb
cars  cars.txt
$ sudo rm /var/lib/mysql/mydb/cars
$ sudo rm /var/lib/mysql/mydb/cars.txt

原因是(在我的情况下)在进行备份时,一些数据写入了mydb目录,MySQL 在其中存储了mydb数据库。 这两个外来文件无法删除,因此出现上述错误。 通过删除文件,错误已得到解决。

mysql> DROP DATABASE mydb;
Query OK, 0 rows affected (0.04 sec)

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| testdb             |
| world              |
+--------------------+
4 rows in set (0.00 sec)

mydb数据库已完全删除。

mysql> CREATE DATABASE mydb;

mysql> USE mydb;

mysql> source bkp3.sql

我们创建mydb数据库。 更改为数据库。 并使用source命令执行bkp3.sql脚本。 重新创建数据库。

mysql> SHOW TABLES;
+----------------+
| Tables_in_mydb |
+----------------+
| AA             |
| Ages           |
| Animals        |
| Authors        |
| BB             |
| Books          |
| Books2         |
| Brands         |
| Cars           |
...

mysql> SELECT * FROM Cars;
+----+------------+--------+
| Id | Name       | Cost   |
+----+------------+--------+
|  1 | Audi       |  52642 |
|  2 | Mercedes   |  57127 |
|  3 | Skoda      |   9000 |
|  4 | Volvo      |  29000 |
|  5 | Bentley    | 350000 |
|  6 | Citroen    |  21000 |
|  7 | Hummer     |  41400 |
|  8 | Volkswagen |  21600 |
+----+------------+--------+

数据已验证。

在 MySQL 教程的这一部分中,我们展示了几种在 MySQL 中导出和导入数据的方法。

在 MySQL 中连接表

原文: http://zetcode.com/databases/mysqltutorial/joins/

在 MySQL 教程的这一部分中,我们将连接 MySQL 中的表。

关系数据库的真正力量和收益来自于连接表。 SQL JOIN子句合并数据库中两个或多个表中的记录。 基本上有两种连接类型:INNEROUTER

在本教程的这一部分中,我们将使用CustomersReservations表。

mysql> SELECT * FROM Customers;
+------------+-------------+
| CustomerId | Name        |
+------------+-------------+
|          1 | Paul Novak  |
|          2 | Terry Neils |
|          3 | Jack Fonda  |
|          4 | Tom Willis  |
+------------+-------------+

这些是Customers表中的值。

mysql> SELECT * FROM Reservations;
+----+------------+------------+
| Id | CustomerId | Day        |
+----+------------+------------+
|  1 |          1 | 2009-11-22 |
|  2 |          2 | 2009-11-28 |
|  3 |          2 | 2009-11-29 |
|  4 |          1 | 2009-11-29 |
|  5 |          3 | 2009-12-02 |
+----+------------+------------+

这些是Reservations表中的值。

CustomersReservations都有CustomerId列。 它是关系列。 如果两个表中的名称相同,则可以使用USING (CustomerId) syntax。 如果名称不同,则说我们有CustomerIdCId,我们将使用ON Customers.CustomerId = Reservations.CId语法。

内连接

内连接是最常见的连接类型。 这也是默认的连接。 内连接仅从数据库表中选择具有匹配值的记录。 我们有内连接的三种类型:INNER JOINNATURAL INNER JOINCROSS INNER JOININNER关键字可以省略。

内连接

mysql> SELECT Name, Day FROM Customers AS C JOIN Reservations 
    -> AS R ON C.CustomerId=R.CustomerId;
+-------------+------------+
| Name        | Day        |
+-------------+------------+
| Paul Novak  | 2009-11-22 |
| Terry Neils | 2009-11-28 |
| Terry Neils | 2009-11-29 |
| Paul Novak  | 2009-11-29 |
| Jack Fonda  | 2009-12-02 |
+-------------+------------+
5 rows in set (0.00 sec)

在此SELECT语句中,我们选择了所有已进行预订的客户。 保罗·诺瓦克(Paul Novak)和特里·尼尔斯(Terry Neils)提出了两项​​保留。 杰克·方达(Jack Fonda)制造了一个。 汤姆·威利斯(Tom Willis)失踪了,他尚未做出任何保留。 请注意,我们省略了INNER关键字。

该语句等效于以下语句:

mysql> SELECT Name, Day FROM Customers, Reservations
    -> WHERE Customers.CustomerId=Reservations.CustomerId;
+-------------+------------+
| Name        | Day        |
+-------------+------------+
| Paul Novak  | 2009-11-22 |
| Terry Neils | 2009-11-28 |
| Terry Neils | 2009-11-29 |
| Paul Novak  | 2009-11-29 |
| Jack Fonda  | 2009-12-02 |
+-------------+------------+

我们得到相同的数据。

交叉内连接

CROSS INNER JOIN将一个表中的所有记录与另一表中的所有记录组合在一起。 这种连接几乎没有实用价值。 它也称为记录的笛卡尔积。

mysql> SELECT Name, Day FROM Customers CROSS JOIN Reservations;
+-------------+------------+
| Name        | Day        |
+-------------+------------+
| Paul Novak  | 2009-11-22 |
| Paul Novak  | 2009-11-28 |
| Paul Novak  | 2009-11-29 |
| Paul Novak  | 2009-11-29 |
| Paul Novak  | 2009-12-02 |
| Terry Neils | 2009-11-22 |
| Terry Neils | 2009-11-28 |
| Terry Neils | 2009-11-29 |
| Terry Neils | 2009-11-29 |
| Terry Neils | 2009-12-02 |
| Jack Fonda  | 2009-11-22 |
...

使用以下 SQL 语句可以达到相同的结果:

SELECT Name, Day FROM Customers, Reservations;

外连接

外连接不需要两个连接表中的每个记录都具有匹配的记录。 外连接有三种类型。 左外连接,右外连接和全外连接。 在创建教程时,MySQL 不支持全外连接。

如前所述,内连接是最常见的连接。 外连接对于查找孤立记录可能很有用。 如果一个人没有预订,是顾客吗? 如果我们无法与客户匹配,预订是否有效?

左外连接

即使与右表不匹配,LEFT OUTER JOIN也将从左表返回所有值。 在这样的行中,将有NULL值。 换句话说,左外连接返回左表中的所有值,以及右表中的匹配值。 注意,可以省略OUTER关键字。

mysql> SELECT Name, Day FROM Customers LEFT JOIN Reservations
    -> ON Customers.CustomerId=Reservations.CustomerId;
+-------------+------------+
| Name        | Day        |
+-------------+------------+
| Paul Novak  | 2009-11-22 |
| Paul Novak  | 2009-11-29 |
| Terry Neils | 2009-11-28 |
| Terry Neils | 2009-11-29 |
| Jack Fonda  | 2009-12-02 |
| Tom Willis  | NULL       |
+-------------+------------+

在这里,我们有所有预订的客户,还有一位没有预订的客户。 他的行中有NULL值。

我们可以使用USING关键字获得相同的结果。 这是因为关系列在两个表中具有相同的名称。 SQL 语句将不再那么冗长。

mysql> SELECT Name, Day FROM Customers LEFT JOIN Reservations
    -> USING (CustomerId);
+-------------+------------+
| Name        | Day        |
+-------------+------------+
| Paul Novak  | 2009-11-22 |
| Paul Novak  | 2009-11-29 |
| Terry Neils | 2009-11-28 |
| Terry Neils | 2009-11-29 |
| Jack Fonda  | 2009-12-02 |
| Tom Willis  | NULL       |
+-------------+------------+

结果相同,但 SQL 语句较短。

右外连接

RIGHT OUTER JOINRIGHT JOIN相同。 它提供了两个表中所有记录的匹配以及正确表的所有可能性。 右侧孤立记录在左侧显示NULL

mysql> SELECT Name, Day FROM Customers RIGHT JOIN
    -> Reservations USING (CustomerId);
+-------------+------------+
| Name        | Day        |
+-------------+------------+
| Paul Novak  | 2009-11-22 |
| Terry Neils | 2009-11-28 |
| Terry Neils | 2009-11-29 |
| Paul Novak  | 2009-11-29 |
| Jack Fonda  | 2009-12-02 |
+-------------+------------+

这是两个表的正确连接的输出。 右侧表的所有记录(预订)在左侧均具有匹配的记录(客户)。

自然连接

自然连接将两个具有相同名称的表中的所有列链接在一​​起。 在ustomersReservations表中,有一个名为CustomerId的列。

自然内连接

NATURAL INNER JOIN自动使用所有匹配的列名进行连接。 在我们的表中,两个表中都有一个名为CustomerId的列。

mysql> SELECT Name, Day FROM Customers NATURAL JOIN Reservations;
+-------------+------------+
| Name        | Day        |
+-------------+------------+
| Paul Novak  | 2009-11-22 |
| Terry Neils | 2009-11-28 |
| Terry Neils | 2009-11-29 |
| Paul Novak  | 2009-11-29 |
| Jack Fonda  | 2009-12-02 |
+-------------+------------+

我们得到相同的数据。 SQL 语句不太冗长。

自然左外连接

NATURAL LEFT OUTER JOIN提供表中所有匹配的记录以及左表中的所有其他记录。 它会自动使用所有匹配的列名进行连接。

mysql> SELECT Name, Day FROM Customers 
    -> NATURAL LEFT JOIN Reservations;
+-------------+------------+
| Name        | Day        |
+-------------+------------+
| Paul Novak  | 2009-11-22 |
| Paul Novak  | 2009-11-29 |
| Terry Neils | 2009-11-28 |
| Terry Neils | 2009-11-29 |
| Jack Fonda  | 2009-12-02 |
| Tom Willis  | NULL       |
+-------------+------------+

结果相同,但击键次数更少。

自然右外连接

NATURAL RIGHT OUTER JOIN提供表中的所有匹配记录以及右表中的所有其他记录。 它会自动使用匹配的列名进行连接。

mysql> SELECT Name, Day FROM Customers
    -> NATURAL RIGHT JOIN Reservations;
+-------------+------------+
| Name        | Day        |
+-------------+------------+
| Paul Novak  | 2009-11-22 |
| Terry Neils | 2009-11-28 |
| Terry Neils | 2009-11-29 |
| Paul Novak  | 2009-11-29 |
| Jack Fonda  | 2009-12-02 |
+-------------+------------+

快速回顾

接下来,我们将创建两个小表来回顾我们在这里学到的东西。

mysql> CREATE TABLE AA(A INTEGER);
mysql> CREATE TABLE BB(B INTEGER);
mysql> INSERT INTO AA VALUES(1);
mysql> INSERT INTO AA VALUES(2);
mysql> INSERT INTO AA VALUES(3);
mysql> INSERT INTO AA VALUES(4);
mysql> INSERT INTO BB VALUES(3);
mysql> INSERT INTO BB VALUES(4);
mysql> INSERT INTO BB VALUES(5);
mysql> INSERT INTO BB VALUES(6);

mysql> SELECT * FROM AA;
+------+
| A    |
+------+
|    1 |
|    2 |
|    3 |
|    4 |
+------+

mysql> SELECT * FROM BB;
+------+
| B    |
+------+
|    3 |
|    4 |
|    5 |
|    6 |
+------+

我们创建了两个表,并填充了数值数据。 表AA具有两个唯一编号(1、2),表BB也具有两个唯一编号(5、6)。 他们共享两个数字(3、4)。

内连接

mysql> SELECT * FROM AA JOIN BB ON A = B;
+------+------+
| A    | B    |
+------+------+
|    3 |    3 |
|    4 |    4 |
+------+------+

这是两个表上的INNER JOIN。 我们仅从两个表中获得匹配的值。

左外连接

mysql> SELECT * FROM AA LEFT JOIN BB ON A = B;
+------+------+
| A    | B    |
+------+------+
|    1 | NULL |
|    2 | NULL |
|    3 |    3 |
|    4 |    4 |
+------+------+

这是两个表上的LEFT OUTER JOIN。 我们获得匹配的值以及左表中没有数学记录的值。

右外连接

mysql> SELECT * FROM AA RIGHT JOIN BB ON A = B;
+------+------+
| A    | B    |
+------+------+
|    3 |    3 |
|    4 |    4 |
| NULL |    5 |
| NULL |    6 |
+------+------+

这是两个表上的RIGHT OUTER JOIN。 我们得到匹配的值,再加上右表中没有匹配记录的值。

在 MySQL 教程的这一部分中,我们将连接表。

MySQL 函数

http://zetcode.com/databases/mysqltutorial/functions/

在 MySQL 教程的这一部分中,我们将介绍 MySQL 内置函数。

MySQL 内置函数可以分为几组。

  • 数学函数
  • 汇总函数
  • 字符串函数
  • 日期和时间函数
  • 系统函数

在这里,我们仅显示所有 MySQL 函数的一部分。 要获取可用函数的完整列表,请查阅 MySQL 参考手册。

数学函数

MySQL 支持多种数学函数。

mysql> SELECT RAND();
+-------------------+
| RAND()            |
+-------------------+
| 0.786536605829873 |
+-------------------+

RAND()函数从 0,1 间隔返回一个随机数。

mysql> SELECT ABS(-3), PI(), SIN(0.5);
+---------+----------+-------------------+
| ABS(-3) | PI()     | SIN(0.5)          |
+---------+----------+-------------------+
|       3 | 3.141593 | 0.479425538604203 |
+---------+----------+-------------------+

ABS()函数返回数字的绝对值。 PI()函数给出 PI 的值。 SIN()函数计算参数的正弦值。

mysql> SELECT BIN(22), OCT(22), HEX(22);
+---------+---------+---------+
| BIN(22) | OCT(22) | HEX(22) |
+---------+---------+---------+
| 10110   | 26      | 16      |
+---------+---------+---------+

我们使用函数给出十进制 22 的二进制,八进制和十六进制表示形式。

mysql> SELECT CEIL(11.256), FLOOR(11.256), ROUND(11.256, 2);
+--------------+---------------+------------------+
| CEIL(11.256) | FLOOR(11.256) | ROUND(11.256, 2) |
+--------------+---------------+------------------+
|           12 |            11 |            11.26 |
+--------------+---------------+------------------+

CEIL()函数将值舍入为最小的后续整数。 FLOOR()函数将值舍入为最大的前一个整数。 ROUND()返回一个四舍五入到指定小数位数的数字。

mysql> SELECT POW(3, 3), SQRT(9);
+-----------+---------+
| POW(3, 3) | SQRT(9) |
+-----------+---------+
|        27 |       3 |
+-----------+---------+

幂和平方根函数。

mysql> SELECT DEGREES(2*PI());
+-----------------+
| DEGREES(2*PI()) |
+-----------------+
|             360 |
+-----------------+

DEGREES()函数根据弧度计算度数。

汇总函数

集合函数对值集进行操作。

mysql> SELECT * FROM Cars;
+----+------------+--------+
| Id | Name       | Cost   |
+----+------------+--------+
|  1 | Audi       |  52642 |
|  2 | Mercedes   |  57127 |
|  3 | Skoda      |   9000 |
|  4 | Volvo      |  29000 |
|  5 | Bentley    | 350000 |
|  6 | Citroen    |  21000 |
|  7 | Hummer     |  41400 |
|  8 | Volkswagen |  21600 |
+----+------------+--------+

我们有汽车表。

mysql> SELECT MIN(Cost), MAX(Cost), AVG(Cost)
    -> FROM Cars;
+-----------+-----------+------------+
| MIN(Cost) | MAX(Cost) | AVG(Cost)  |
+-----------+-----------+------------+
|      9000 |    350000 | 72721.1250 |
+-----------+-----------+------------+

我们使用MIN()MAX()AVG()聚合函数来计算表中汽车的最低价格,最高价格和平均价格。

mysql> SELECT SUM(Cost), COUNT(Id), STD(Cost), 
    -> VARIANCE(Cost) FROM Cars;
+-----------+-----------+-------------+------------------+
| SUM(Cost) | COUNT(Id) | STD(Cost)   | VARIANCE(Cost)   |
+-----------+-----------+-------------+------------------+
|    581769 |         8 | 105931.1676 | 11221412265.3594 |
+-----------+-----------+-------------+------------------+

我们使用SUM()函数来获取Cost列中所有值的总和。 我们使用COUNT()函数计算表中的汽车数量。 最后,我们使用STD()VARIANCE()函数获得标准偏差和方差。

字符串函数

在这一组中,我们有各种与字符串相关的函数。

mysql> SELECT LENGTH('ZetCode'), UPPER('ZetCode'), LOWER('ZetCode');
+-------------------+------------------+------------------+
| LENGTH('ZetCode') | UPPER('ZetCode') | LOWER('ZetCode') |
+-------------------+------------------+------------------+
|                 7 | ZETCODE          | zetcode          |
+-------------------+------------------+------------------+

LENGTH()函数返回字符串的长度。 UPPER()函数将字符转换为大写字母。 LOWER()函数将字符转换为小写字母。

ysql> SELECT LPAD(RPAD("ZetCode", 10, "*"), 13, "*");
+-----------------------------------------+
| LPAD(RPAD("ZetCode", 10, "*"), 13, "*") |
+-----------------------------------------+
| ***ZetCode***                           |
+-----------------------------------------+

我们使用LPAD()RPAD()函数将字符附加和添加到指定的字符串之前。 "ZetCode"字符串包含 7 个字符。 RPAD()函数将 3 个"*"字符附加到字符串中,该字符现在将为 10 个字符长。

mysql> SELECT REVERSE('ZetCode'), REPEAT('*', 6);
+--------------------+----------------+
| REVERSE('ZetCode') | REPEAT('*', 6) |
+--------------------+----------------+
| edoCteZ            | ******         |
+--------------------+----------------+

REVERSE()函数可反转字符串中的字符。 REPEAT()函数重复指定次数的字符串。

mysql> SELECT LEFT('ZetCode', 3), RIGHT('ZetCode', 3), 
    -> SUBSTRING('ZetCode', 3, 3);
+--------------------+---------------------+----------------------------+
| LEFT('ZetCode', 3) | RIGHT('ZetCode', 3) | SUBSTRING('ZetCode', 3, 3) |
+--------------------+---------------------+----------------------------+
| Zet                | ode                 | tCo                        |
+--------------------+---------------------+----------------------------+

LEFT()函数返回最左边的 3 个字符,RIGHT()函数返回最右边的 3 个字符。 SUBSTRING()函数从字符串的第三位置返回三个字符。

mysql> SELECT STRCMP('byte', 'byte'), CONCAT('three', ' apples');
+------------------------+----------------------------+
| STRCMP('byte', 'byte') | CONCAT('three', ' apples') |
+------------------------+----------------------------+
|                      0 | three apples               |
+------------------------+----------------------------+

STRCMP()比较两个字符串,如果相同则返回 0。 CONCAT()函数连接两个字符串。

mysql> SELECT REPLACE('basketball', 'basket', 'foot');
+-----------------------------------------+
| REPLACE('basketball', 'basket', 'foot') |
+-----------------------------------------+
| football                                |
+-----------------------------------------+

REPLACE()函数返回一个字符串,其中我们替换了一些文本。 第一个参数是原始字符串。 第二个参数是一个字符串,我们要替换。 最后一个参数是新的替换字符串。

日期&时间函数

在这个组中,我们具有各种日期和时间函数。

mysql> SELECT DAYNAME('2011-01-23'), YEAR('2011/01/23'),
    -> MONTHNAME('110123');
+-----------------------+--------------------+---------------------+
| DAYNAME('2011-01-23') | YEAR('2011/01/23') | MONTHNAME('110123') |
+-----------------------+--------------------+---------------------+
| Sunday                |               2011 | January             |
+-----------------------+--------------------+---------------------+

在 MySQL 中,日期以YYYY-MM-DD格式编写。 年份之后是月份和日期。 它们可以用斜杠或连字符分隔。 MySQL 还支持缩短的日期格式,没有分隔符。 时间以标准格式HH:MM:SS编写。 小时后是分钟和秒。

mysql> SELECT NOW();
+---------------------+
| NOW()               |
+---------------------+
| 2011-01-22 00:24:49 |
+---------------------+

NOW()函数返回当前日期和时间。

mysql> SELECT CURTIME(), CURDATE();
+-----------+------------+
| CURTIME() | CURDATE()  |
+-----------+------------+
| 00:25:03  | 2011-01-22 |
+-----------+------------+

CURTIME()返回当前时间,CURDATE()返回当前日期。

mysql> SELECT DATEDIFF('2011-3-12', '2011-1-12');
+------------------------------------+
| DATEDIFF('2011-3-12', '2011-1-12') |
+------------------------------------+
|                                 59 |
+------------------------------------+

使用DATEDIFF(),我们可以得出两个日期之间的天数。

mysql> SELECT DAYNAME('1982-4-12'), MONTHNAME('1982-4-12') ;
+----------------------+------------------------+
| DAYNAME('1982-4-12') | MONTHNAME('1982-4-12') |
+----------------------+------------------------+
| Monday               | April                  |
+----------------------+------------------------+

DAYNAME()函数返回日期的日期名称。 MONTHNAME()函数返回日期的月份名称。

mysql> SELECT WEEKOFYEAR('110123'), WEEKDAY('110123'),
    -> QUARTER('110123');
+----------------------+-------------------+-------------------+
| WEEKOFYEAR('110123') | WEEKDAY('110123') | QUARTER('110123') |
+----------------------+-------------------+-------------------+
|                    3 |                 6 |                 1 |
+----------------------+-------------------+-------------------+

2011 年 1 月 23 日可以用缩短的日期格式 110123 书写。我们使用WEEKOFYEAR()来确定一年中的星期。 WEEKDAY()返回 6,即星期日。 并且QUARTER()函数返回一年的季度。

mysql> SELECT DATE_FORMAT('110123', '%d-%m-%Y');
+-----------------------------------+
| DATE_FORMAT('110123', '%d-%m-%Y') |
+-----------------------------------+
| 23-01-2011                        |
+-----------------------------------+

要以其他格式显示日期,我们使用DATE_FORMAT()

mysql> SELECT DATE_ADD('110123', INTERVAL 45 DAY), 
    -> SUBDATE('110309', INTERVAL 45 DAY);
+-------------------------------------+------------------------------------+
| DATE_ADD('110123', INTERVAL 45 DAY) | SUBDATE('110309', INTERVAL 45 DAY) |
+-------------------------------------+------------------------------------+
| 2011-03-09                          | 2011-01-23                         |
+-------------------------------------+------------------------------------+

我们可以使用DATE_ADD()为日期添加时间间隔,并使用SUBDATE()从日期中减去时间间隔。

系统函数

系统函数提供了有关 MySQL 数据库的一些系统信息。

mysql> SELECT VERSION(), DATABASE();
+--------------------+------------+
| VERSION()          | DATABASE() |
+--------------------+------------+
| 5.1.41-3ubuntu12.8 | mydb       |
+--------------------+------------+

我们得到 MySQL 数据库的版本和当前的数据库名称。

mysql> SELECT USER();
+----------------+
| USER()         |
+----------------+
| root@localhost |
+----------------+

USER()函数返回客户端提供的用户名和主机名。

mysql> SELECT CHARSET('ZetCode'), COLLATION('ZetCode');
+--------------------+----------------------+
| CHARSET('ZetCode') | COLLATION('ZetCode') |
+--------------------+----------------------+
| utf8               | utf8_general_ci      |
+--------------------+----------------------+

CHARSET()函数返回参数的字符集。 COLLATION()返回当前字符串参数的排序规则。 它们取决于使用中的客户端的字符集和排序规则。

在 MySQL 教程的这一部分中,我们使用了内置的 MySQL 函数。

MySQL 中的视图

原文: http://zetcode.com/databases/mysqltutorial/views/

在 MySQL 教程的这一部分中,我们将提及视图。

查看定义

视图是来自一个或多个表的数据的特定外观。 它可以按特定顺序排列数据,突出显示或隐藏某些数据。 视图由存储的查询组成,该查询可作为由查询结果集组成的虚拟表访问。 与普通表不同,视图不构成物理模式的一部分。 它是根据数据库中的数据计算或整理的动态虚拟表。

视图是伪表。 它是一个存储查询,看起来像一个表。 它可以像表一样被引用。

视图可以将用户限制为特定的行或列,从而增强安全性。 它们可用于连接多个表中的列,使它们看起来像一个表。 它们可用于提供汇总信息。

有几个限制适用于视图。 这里是其中的一些:

  • SELECT语句不能包含子查询
  • SELECT语句不能引用系统或用户变量
  • 定义中引用的任何表或视图都必须存在
  • 无法创建临时视图
  • VIEW不能与触发器关联

创建,修改和删除视图

在下一个示例中,我们创建一个简单的视图。 我们使用CREATE VIEW语法创建视图。

mysql> SELECT * FROM Cars;
+----+------------+--------+
| Id | Name       | Cost   |
+----+------------+--------+
|  1 | Audi       |  52642 |
|  2 | Mercedes   |  57127 |
|  3 | Skoda      |   9000 |
|  4 | Volvo      |  29000 |
|  5 | Bentley    | 350000 |
|  6 | Citroen    |  21000 |
|  7 | Hummer     |  41400 |
|  8 | Volkswagen |  21600 |
+----+------------+--------+ 

这是我们的数据,我们在该数据上创建视图。

mysql> CREATE VIEW CheapCars AS 
    -> SELECT Name FROM Cars WHERE Cost<25000;

我们创建一个视图CheapCars。 这些是价格低于 25000 的汽车。

mysql> SELECT * FROM CheapCars;
+------------+
| Name       |
+------------+
| Skoda      |
| Citroen    |
| Volkswagen |
+------------+

视图是无法查询的数据库对象。 有三辆便宜的汽车。

mysql> ALTER VIEW CheapCars AS SELECT Name FROM Cars
    -> WHERE Cost<30000;

mysql> SELECT * FROM CheapCars;
+------------+
| Name       |
+------------+
| Skoda      |
| Volvo      |
| Citroen    |
| Volkswagen |
+------------+

我们可以重新定义视图。 假设我们现在认为如果汽车的价格低于 30000,则它会便宜。我们使用ALTER VIEW语句修改了我们的视图。

如果我们删除从中选择数据的表,对视图会发生什么?

mysql> DROP TABLE Cars;

mysql> SELECT * FROM CheapCars;
ERROR 1356 (HY000): View 'mydb.CheapCars' references invalid table(s) 
or column(s) or function(s) or definer/invoker of view lack rights to use them

查询视图时,我们收到上述错误。

mysql> SOURCE cars.sql

mysql> SELECT * FROM CheapCars;
+------------+
| Name       |
+------------+
| Skoda      |
| Citroen    |
| Volkswagen |
+------------+

当我们重新创建表时,视图将再次起作用。

mysql> DROP VIEW CheapCars;

最后,使用DROP VIEW语法删除视图。

寻找视图

我们将提到几种在 MySQL 数据库中查找视图的方法。

mysql> SHOW FULL TABLES;
+----------------+------------+
| Tables_in_mydb | Table_type |
+----------------+------------+
| AA             | BASE TABLE |
...
| Chars          | BASE TABLE |
| CheapCars      | VIEW       |
| Customers      | BASE TABLE |
| Dates          | BASE TABLE |
| Decimals       | BASE TABLE |
| FavoriteCars   | VIEW       |
...

我们可以使用SHOW FULL TABLES语句列出数据库中的所有表。 在Table_type列中,我们可以看到它是表还是视图。

mysql> SELECT TABLE_NAME, TABLE_TYPE FROM information_schema.TABLES;
+---------------------------------------+-------------+
| TABLE_NAME                            | TABLE_TYPE  |
+---------------------------------------+-------------+
| CHARACTER_SETS                        | SYSTEM VIEW |
| COLLATIONS                            | SYSTEM VIEW |
| COLLATION_CHARACTER_SET_APPLICABILITY | SYSTEM VIEW |
| COLUMNS                               | SYSTEM VIEW |
| COLUMN_PRIVILEGES                     | SYSTEM VIEW |
| ENGINES                               | SYSTEM VIEW |
...
| Chars                                 | BASE TABLE  |
| CheapCars                             | VIEW        |
| Customers                             | BASE TABLE  |
| Dates                                 | BASE TABLE  |
| Decimals                              | BASE TABLE  |
| FavoriteCars                          | VIEW        |
...

information_schema数据库中,有一个TABLES表。 TABLE_NAMETABLE_TYPE列为我们提供了有关表名及其类型的信息。

mysql> SELECT TABLE_NAME FROM information_schema.VIEWS;
+--------------+
| TABLE_NAME   |
+--------------+
| CheapCars    |
| FavoriteCars |
+--------------+

这是查找视图的最直接方法。 我们查询information_schema数据库的VIEWS表。

使用UNION创建视图

UNION运算符用于结果集组合两个或更多SELECT语句。 每个选择必须具有相同的列数。

mysql> CREATE VIEW FavoriteCars AS
    -> SELECT * FROM Cars WHERE Id=7
    -> UNION SELECT * FROM Cars WHERE Id=4
    -> UNION SELECT * FROM Cars WHERE Id=5;

我们创建一个名为FavoriteCars的视图。 在此视图中,我们有三行被认为是最喜欢的。 三个SELECT语句与UNION运算符组合在一起。

mysql> SELECT * FROM FavoriteCars;
+----+---------+--------+
| Id | Name    | Cost   |
+----+---------+--------+
|  7 | Hummer  |  41400 |
|  4 | Volvo   |  29000 |
|  5 | Bentley | 350000 |
+----+---------+--------+

这是视图中的SELECT

在 MySQL 教程的这一部分中,我们使用了视图。

MySQL 中的事务

原文: http://zetcode.com/databases/mysqltutorial/transactions/

在 MySQL 教程的这一部分中,我们将提到事务。

事务的定义

事务是针对一个或多个数据库中数据的数据库操作的基本单位。 事务中所有 SQL 语句的影响可以全部提交给数据库,也可以全部回滚。

MySQL 支持多种存储引擎。 InnoDB 完全符合 ACID。 ACID 代表原子性,一致性,隔离性和耐久性。 可靠的事务必须支持所有这四个属性。

事务内的操作必须是原子的。 这意味着所有操作都将成功或失败。 这是全有或全无的规则。 一致性属性可确保事务完成后数据库处于一致状态。 数据有效,没有半完成的记录。 例如,没有没有付款记录的客户,或者没有客户的付款记录。 隔离是其他操作无法访问在尚未完成的事务期间修改的数据的要求。 在并发事务的情况下会出现隔离问题。 如果没有隔离,则数据可能最终处于不一致状态。 持久性是数据库系统针对任何类型的系统故障恢复已提交事务更新的能力。

隔离等级

在高度并发的环境中,高度隔离的事务可能导致死锁。 僵局是一种情况,其中事务争用资源并有效阻止彼此访问资源。 此外,在隔离级别和数据库性能之间需要权衡。 因此,数据库系统为事务提供了几个隔离级别。

MySQL 提供了四个级别的事务隔离:

  • 可序列化
  • 可重复读
  • 已提交读
  • 未提交读

在可序列化的隔离级别中,所有事务都以完全隔离的方式发生。 所有事务都一个接一个地执行。 在可重复读取的隔离级别中,语句无法读取已被其他事务修改但尚未提交的数据。 在当前事务完成之前,没有其他事务可以修改当前事务已读取的数据。 这是 InnoDB 的默认隔离级别。 其中,已提交读隔离级别的语句无法读取已被其他事务修改但未提交的数据。 语句等待直到被其他事务写锁定的数据行被解锁,然后才能获取自己的锁。 这样可以防止他们读取脏数据。 在未提交读的隔离级别中,语句可以读取已被其他事务修改但尚未提交的行。

当事务未完全分离时,可能会遇到幻影读取,不可重复读取和脏读取的问题。 当事务重新执行返回返回满足搜索条件的行的集合的查询,并发现满足条件的行的集合由于另一个最近提交的事务而发生更改时,会发生幻像读取。 不可重复读取是在事务重新读取其先前已读取的数据并发现该数据已被另一个事务修改后发生的。 自从初次阅读以来就实现了。 当事务从已被另一个事务修改但尚未提交的行中读取数据时,会发生脏读。

下表显示了所有隔离级别以及它们遇到的可能的问题。

隔离级别 幻读 不可重复读 脏读
可序列化 不可能 不可能 不可能
可重复读 可能 不可能 不可能
已提交读 可能 可能 不可能
未提交读 可能 可能 可能

MySQL 的默认事务隔离级别是可重复读取。

mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+

当前隔离级别存储在tx_isolation服务器变量中。

mysql> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

mysql> SELECT @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE   |
+----------------+

我们可以使用SET TRANSACTION ISOLATION LEVEL语句更改隔离级别。

自动提交

MySQL 还自动提交不属于事务的语句。 任何UPDATEINSERT语句之前没有START的结果将立即对所有连接可见。

mysql> SELECT @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            1 |
+--------------+

默认情况下设置自动提交变量。

mysql> SET autocommit=0;

mysql> SELECT @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            0 |
+--------------+

自动提交可以关闭。

现在,我们将演示autocommint变量。

mysql> SELECT @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            1 |
+--------------+

CREATE TABLE Test(Num INTEGER NOT NULL) engine=InnoDB;

自动提交已设置。 我们使用支持事务的 InnoDB 存储引擎创建一个简单的Test表。

mysql> INSERT INTO Test VALUES (1), (2), (3);

mysql> SELECT * FROM Test;
+-----+
| Num |
+-----+
|   1 |
|   2 |
|   3 |
+-----+

我们将三行插入到表的列中。 这些值将立即提交。

mysql> SET autocommit=0;

mysql> INSERT INTO Test VALUES (4), (5);

mysql> SELECT * FROM Test;
+-----+
| Num |
+-----+
|   1 |
|   2 |
|   3 |
|   4 |
|   5 |
+-----+

现在,我们将autocommit变量设置为false。 我们插入两个值,然后从表中选择所有数据。 现在表中有 5 行。

mysql> ROLLBACK;

mysql> SELECT * FROM Test;
+-----+
| Num |
+-----+
|   1 |
|   2 |
|   3 |
+-----+

但是,数据不会永久写入表中。 使用ROLLBACK语句,我们将其收回。

mysql> INSERT INTO Test VALUES (4), (5);

mysql> COMMIT;

mysql> ROLLBACK;

mysql> SELECT * FROM Test;
+-----+
| Num |
+-----+
|   1 |
|   2 |
|   3 |
|   4 |
|   5 |
+-----+

现在我们再次插入值 4、5。 这次,使用COMMIT语句提交行。 后续回滚语句无效。

启用事务

启用自动提交功能后,每个单个 SQL 语句都会自动包装在其自己的事务中。 要开始我们自己的事务,我们发出START TRANSACTION语句。 稍后使用COMMITROLLBACK语句完成事务。 事务主体中可能会发布多个语句。 全部作为一个单元提交或回滚。

mysql> TRUNCATE Test;
Query OK, 0 rows affected (0.02 sec)

mysql> SELECT * FROM Test;
Empty set (0.00 sec)

我们将使用相同的Test表。 我们截断表中的数据。

mysql> START TRANSACTION;

mysql> INSERT INTO Test VALUES (1), (2);

mysql> INSERT INTO Test VALUES (3), (4);

mysql> SELECT * FROM Test;
+-----+
| Num |
+-----+
|   1 |
|   2 |
|   3 |
|   4 |
+-----+

在上面的代码中,我们开始一个事务并将四行插入到表中。 这些值尚未提交。 从当前连接中,行可见。

$ mysql -uroot -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 65
Server version: 5.1.41-3ubuntu12.9 (Ubuntu)

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SELECT * FROM mydb.Test;
Empty set (0.00 sec)

但是,从另一个连接,测试表为空。 我们启动mysql客户端程序的新实例。 这是与 MySQL 数据库的不同连接。 从此连接,这些值尚不可见。

mysql> COMMIT;

最后,COMMIT语句将数据提交到表中。 这些行在两个连接中均可见。

我们开始另一笔事务。 这次将回滚数据。

mysql> START TRANSACTION;

mysql> INSERT INTO Test VALUES (5), (6);

mysql> INSERT INTO Test VALUES (7), (8);

mysql> ROLLBACK;

mysql> SELECT * FROM Test;
+-----+
| Num |
+-----+
|   1 |
|   2 |
|   3 |
|   4 |
+-----+

在上面的 SQL 代码中,我们开始一个新事务。 我们在测试表中插入四个值。 我们使用ROLLBACK语句回滚更改。 从表中进行的后续选择显示数据未提交到表。

在 MySQL 教程的这一部分中,我们处理了事务。

MySQL 教程

原文: http://zetcode.com/databases/mysqltutorial/

这是 MySQL 教程。 MySQL 教程涵盖了 MySQL 数据库引擎和数据库引擎支持的 SQL 语言。

  1. 简介
  2. 安装
  3. 第一步
  4. 快速 MySQL 教程
  5. 存储引擎
  6. 数据类型
  7. 创建,更改和删除表
  8. MySQL 表达式
  9. 插入,更新和删除数据
  10. SELECT 语句
  11. 子查询
  12. 约束
  13. 导出和导入数据
  14. 连接表
  15. MySQL 函数
  16. 观看次数
  17. 事务
  18. 存储的例程

MySQL

MySQL 是领先的开源数据库管理系统。 它是一个多用户,多线程的数据库管理系统。 MySQL 在网络上特别流行。 它是流行的 LAMP 开发平台的一部分。

Tweet

相关教程

MySQL 存储过程

原文: http://zetcode.com/databases/mysqltutorial/routines/

本章介绍 MySQL 中的存储过程。 在 MySQL 中,有两种存储过程:存储过程和存储函数。

使用CALL语句调用存储过程。 它们不返回值。 存储的函数返回值。 与SELECT语句一起使用。

存储的过程是一组可以存储在服务器中的 SQL 语句。 通常不接受存储的过程。 它们既有优点也有缺点。 存储的过程通常用于数据验证或访问控制。

在存在许多用不同语言编写或在不同平台上工作的客户端应用但需要执行相同数据库操作的情况下,存储过程可能会很有用。 它们可以导致一些性能提升。 存储的过程存储在服务器中,因此网络负载减少。 在某些数据库系统中,可以对存储的过程进行预编译,从而提高性能。 如果更改数据库上的某些逻辑,则它会自动为所有可能的客户端准备。 当我们在客户端更改某些逻辑时,必须在所有可能的客户端中执行此操作。

另一方面,存储的过程有一些缺点。 存储的过程违反了主要的设计模式,在该模式中,业务逻辑,数据和表示在特定的层中分开。 存储的过程会破坏业务逻辑和数据。 存储的过程更难以调试和测试。 在存储的过程中具有大量业务逻辑的应用可伸缩性较差。 而且,没有用于存储过程的版本控制系统。 最后,在各种数据库系统中,存储过程的实现方式有所不同。 这使得数据库系统之间的潜在迁移更加困难。

一个简单的过程

该过程是使用CREATE PROCEDURE语句创建的。

mysql> CREATE PROCEDURE AllCars() SELECT * FROM Cars;

在此语句中,我们创建了一个名为AllCars()的新简单过程。 过程名称后面的select语句是过程的主体,当我们调用过程时会执行该主体。 该过程从Cars表中选择所有数据。

mysql> CALL AllCars();
+----+------------+--------+
| Id | Name       | Cost   |
+----+------------+--------+
|  1 | Audi       |  52642 |
|  2 | Mercedes   |  57127 |
|  3 | Skoda      |   9000 |
|  4 | Volvo      |  29000 |
|  5 | Bentley    | 350000 |
|  6 | Citroen    |  21000 |
|  7 | Hummer     |  41400 |
|  8 | Volkswagen |  21600 |
+----+------------+--------+

我们调用AllCars()过程并执行它的主体。

一个简单的函数

使用CREATE FUNCTION语句创建一个函数。 函数返回一个值。 通过SELECT语句调用它。

-- this function computes the area
-- of a circle; it takes a radius as
-- a parameter

DELIMITER $$

DROP FUNCTION IF EXISTS CircleArea;

CREATE FUNCTION CircleArea(r DOUBLE) RETURNS DOUBLE
BEGIN
    DECLARE area DOUBLE;

    SET area = r * r * pi();
    RETURN area;
END 

$$

DELIMITER ;

在此代码中,我们创建了一个CircleArea()函数,该函数计算圆的面积。 它以半径为参数。 创建具有多个行的过程或函数的最佳方法是创建一个 SQL 文件并使用source命令读取该文件。

-- this function computes the area
-- of a circle; it takes a radius as
-- a parameter

注释以双破折号开头。

DELIMITER $$

SQL 语句以分号结束。 要创建过程或函数,我们需要多个语句。 因此,我们需要暂时使用其他定界符。 在这里,我们使用$$作为分隔符。 我们可以使用不同的字符。 在函数定义的末尾,我们使用此定界符。

DROP FUNCTION IF EXISTS CircleArea;

在开发存储过程时,我们将遇到各种语法或其他错误。 该函数可能已经部分创建。 因此,我们使用上面的语句来消除任何有缺陷的尝试,并从一开始就创建一个函数。

CREATE FUNCTION CircleArea(r DOUBLE) RETURNS DOUBLE

我们创建一个名为CircleArea的函数。 它采用类型为DOUBLE的参数r。 该函数返回DOUBLE类型的值。

BEGIN
   ...
END 

函数主体位于BEGINEND关键字之间。

DECLARE area DOUBLE;

我们在过程中声明一个新变量。 它的名称是area,数据类型是DOUBLE

SET area = r * r * pi();

我们用给定的半径计算圆的面积。

RETURN area;

我们返回变量。

$$

过程到此结束。

DELIMITER ;

我们再次使用默认的定界符。

mysql> source circlearea.sql

mysql> SELECT CircleArea(5.5);
+-------------------+
| CircleArea(5.5)   |
+-------------------+
| 95.03317777109125 |
+-------------------+

我们创建CircleArea()函数,并使用SELECT语句调用它。

程序参数

过程无法返回值。 但是,它可以使用三种类型的变量:

  • IN
  • OUT
  • INOUT

IN是默认参数类型。 未明确指定类型时使用。 IN参数传递给该过程。 可以在过程内部进行修改,但在外部保持不变。 对于OUT参数,不会将任何值传递给过程。 可以在过程内部进行修改。 并且该变量在过程外部可用。 INOUT变量是INOUT参数的混合。 可以将其传递给过程,在此进行更改,也可以在过程外部进行检索。

-- this procedure computes the power 
-- of a given value

DELIMITER $$

DROP PROCEDURE IF EXISTS Pow;

CREATE PROCEDURE Pow(IN val DOUBLE, OUT p DOUBLE) 
BEGIN
    SET p = val * val;
END 

$$

DELIMITER ;

在此过程中,我们计算给定值的功效。

CREATE PROCEDURE Pow(IN val DOUBLE, OUT p DOUBLE) 

该过程采用两个参数。 第一个是计算功效的值。 声明为IN。 它被传递到过程并在那里使用。 第二个变量是OUT变量。 这是我们存储此过程结果的参数。 过程完成后即可使用。

mysql> source power.sql

mysql> CALL Pow(3, @p);

mysql> SELECT @p;
+------+
| @p   |
+------+
|    9 |
+------+

我们创建过程Pow()。 我们使用CALL语句来调用它。 结果存储在@p变量中。 最后,我们选择@p变量以查看其内容。

随机数

在下面的示例中,我们将创建一个生成五个随机数的过程。 从 0 到 9。

-- this procedure generates
-- five random numbers from 0 to 9

DELIMITER $$

DROP PROCEDURE IF EXISTS FiveRandomNumbers;

CREATE PROCEDURE FiveRandomNumbers()
BEGIN
    SET @i = 0;
    REPEAT 
        SELECT FLOOR(RAND() * 10) AS 'Random Number'; 
        SET @i = @i + 1; 
    UNTIL @i >=5 END REPEAT;
END 

$$

DELIMITER ;

在此过程中,我们将使用RAND()FLOOR()内置函数。

SET @i = 0;

此变量是一个计数器。

REPEAT 
    SELECT FLOOR(RAND() * 10) AS 'Random Number'; 
    SET @i = @i + 1; 
UNTIL @i >=5 END REPEAT;

关键字REPEATUNTIL创建一个循环。 计数器用于控制迭代次数。 就我们而言,我们有五个。 RAND()函数返回一个十进制数字,FLOOR()函数用于将其舍入。

mysql> source fiverandomnumbers.sql;

mysql> CALL FiveRandomNumbers;
+---------------+
| Random Number |
+---------------+
|             9 |
+---------------+
1 row in set (0.00 sec)

+---------------+
| Random Number |
+---------------+
|             1 |
+---------------+
...

我们使用source命令创建该过程。 然后调用它。

查找过程

在 MySQL 中,我们可以使用SHOW PROCEDURE STATUSSHOW FUNCTION STATUS在我们的数据库中查看过程及其特征。

information_schema数据库中还有一个ROUTINES表。 我们可以查询表以获取有关存储过程的信息。

mysql> SELECT SPECIFIC_NAME from information_schema.ROUTINES  
    -> WHERE ROUTINE_TYPE='PROCEDURE';
+-------------------+
| SPECIFIC_NAME     |
+-------------------+
| AllCars           |
| FiveRandomNumbers |
| Pow               |
+-------------------+

该语句显示数据库中的所有过程。

mysql> SELECT SPECIFIC_NAME from information_schema.ROUTINES 
    -> WHERE ROUTINE_TYPE='FUNCTION';
+---------------+
| SPECIFIC_NAME |
+---------------+
| CircleArea    |
+---------------+

该语句显示数据库中的所有函数。

在本章中,我们介绍了 MySQL 过程。

MySQL Python 教程

原文: http://zetcode.com/db/mysqlpython/

这是 MySQL Python 编程教程。 它涵盖了使用 Python 进行 MySQL 编程的基础。 它使用MySQLdb模块。 这些示例是在 Ubuntu Linux 上创建和测试的。

MySQLdb 是 MySQL 的 Python 2 旧版数据库模块。 对于现代 Python 3 MySQL 编程,请使用 PyMySQL 模块。 参见 PyMySQL 教程

Tweet

关于 MySQL 数据库

MySQL 是领先的开源数据库管理系统。 它是一个多用户,多线程的数据库管理系统。 MySQL 在网络上特别流行。 它是非常流行的 LAMP 平台的一部分,该平台由 Linux,Apache,MySQL 和 PHP 组成。 目前,MySQL 由 Oracle 拥有。

MySQL 数据库在最重要的 OS 平台上可用。 它可以在 BSD Unix,Linux,Windows 或 Mac OS 上运行。 维基百科和 YouTube 使用 MySQL。 这些站点每天管理数百万个查询。 MySQL 有两个版本:MySQL 服务器系统和 MySQL 嵌入式系统。

MySQLdb 安装

$ apt-cache search MySQLdb
python-mysqldb - A Python interface to MySQL
python-mysqldb-dbg - A Python interface to MySQL (debug extension)
bibus - bibliographic database
eikazo - graphical frontend for SANE designed for mass-scanning

我们在包名称中搜索MySQLdb模块。 我们使用apt-cache命令找出答案。

$ sudo apt-get install python-mysqldb

在这里,我们将 Python 接口安装到 MySQL 数据库。 _mysqlmysql模块。

MySQL 安装

如果您尚未安装 MySQL,则必须安装它。

$ sudo apt-get install mysql-server

此命令将安装 MySQL 服务器和其他各种包。 在安装包时,提示我们输入 MySQL 根帐户的密码。

接下来,我们将创建一个新的数据库用户和一个新的数据库。 我们使用mysql客户端。

$ mysql -u root -p
Enter password: 

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema | 
| mysql              | 
+--------------------+
2 rows in set (0.00 sec)

我们使用根帐户连接到数据库。 我们用SHOW DATABASES语句显示所有可用的数据库。

mysql> CREATE DATABASE testdb;
Query OK, 1 row affected (0.02 sec)

我们创建一个新的testdb数据库。 在整个教程中,我们将使用此数据库。

mysql> CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'test623';
Query OK, 0 rows affected (0.00 sec)

mysql> USE testdb;
Database changed

mysql> GRANT ALL ON testdb.* TO 'testuser'@'localhost';
Query OK, 0 rows affected (0.00 sec)

mysql> quit;
Bye

我们创建一个新的数据库用户。 我们授予该用户testdb数据库所有表的所有特权。

_mysql模块

_mysql模块直接实现 MySQL C API。 它与 Python DB API 接口不兼容。 通常,程序员更喜欢面向对象的MySQLdb模块。 我们将关注后一个模块。 在这里,我们仅介绍_mysql模块的一个小示例。

version_capi.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import _mysql
import sys

try:
    con = _mysql.connect('localhost', 'testuser', 'test623', 'testdb')

    con.query("SELECT VERSION()")
    result = con.use_result()

    print "MySQL version: %s" % \
        result.fetch_row()[0]

except _mysql.Error, e:

    print "Error %d: %s" % (e.args[0], e.args[1])
    sys.exit(1)

finally:

    if con:
        con.close()

该示例将获取并打印 MySQL 数据库的版本。 为此,我们使用SELECT VERSION() SQL 语句。

MySQLdb模块

MySQLdb是围绕_mysql的薄型 Python 包装器。 它与 Python DB API 兼容,这使代码更易于移植。 使用此模型是使用 MySQL 的首选方式。

MySQLdb 版本示例

在第一个示例中,我们将获取 MySQL 数据库的版本。

version.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import MySQLdb as mdb
import sys

try:
    con = mdb.connect('localhost', 'testuser', 'test623', 'testdb')

    cur = con.cursor()
    cur.execute("SELECT VERSION()")

    ver = cur.fetchone()

    print "Database version : %s " % ver

except mdb.Error, e:

    print "Error %d: %s" % (e.args[0],e.args[1])
    sys.exit(1)

finally:    

    if con:    
        con.close()

在此脚本中,我们连接到testdb数据库并执行SELECT VERSION()语句。 这将返回 MySQL 数据库的当前版本。 我们将其打印到控制台。

import MySQLdb as mdb

我们导入MySQLdb模块。

con = mdb.connect('localhost', 'testuser', 
    'test623', 'testdb');

我们使用connect()方法连接到数据库。 我们传递四个参数:主机名,数据库用户名,密码和数据库名。

cur = con.cursor()
cur.execute("SELECT VERSION()")

从连接中,我们得到游标对象。 游标用于遍历结果集中的记录。 我们调用游标的execute()方法并执行 SQL 语句。

ver = cur.fetchone()

我们获取数据。 由于只检索一条记录,因此我们称为fetchone()方法。

print "Database version : %s " % ver

我们将检索到的数据打印到控制台。

except mdb.Error, e:

    print "Error %d: %s" % (e.args[0],e.args[1])
    sys.exit(1)

我们检查错误。

finally:    

    if con:    
        con.close()

在最后一步,我们释放资源。

$ ./version.py
Database version : 5.7.23-0ubuntu0.16.04.1

这是一个示例输出。

MySQLdb 创建并填充表

我们创建一个表,并用一些数据填充它。

create_table.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import MySQLdb as mdb

con = mdb.connect('localhost', 'testuser', 'test623', 'testdb')

with con:

    cur = con.cursor()
    cur.execute("DROP TABLE IF EXISTS Writers")
    cur.execute("CREATE TABLE Writers(Id INT PRIMARY KEY AUTO_INCREMENT, \
                 Name VARCHAR(25))")
    cur.execute("INSERT INTO Writers(Name) VALUES('Jack London')")
    cur.execute("INSERT INTO Writers(Name) VALUES('Honore de Balzac')")
    cur.execute("INSERT INTO Writers(Name) VALUES('Lion Feuchtwanger')")
    cur.execute("INSERT INTO Writers(Name) VALUES('Emile Zola')")
    cur.execute("INSERT INTO Writers(Name) VALUES('Truman Capote')")

我们创建一个Writers表,并向其中添加五个作者。

with con:

使用with关键字,Python 解释器会自动释放资源。 它还提供错误处理。

cur.execute("CREATE TABLE Writers(Id INT PRIMARY KEY AUTO_INCREMENT, \
                Name VARCHAR(25))")

该 SQL 语句创建一个名为Writers的新数据库表。 它具有两列:IdName

cur.execute("INSERT INTO Writers(Name) VALUES('Jack London')")
cur.execute("INSERT INTO Writers(Name) VALUES('Honore de Balzac')")
...

我们使用INSERT语句在表中插入作者。 在这里,我们添加两行。

mysql> SELECT * FROM Writers;
+----+-------------------+
| Id | Name              |
+----+-------------------+
|  1 | Jack London       |
|  2 | Honore de Balzac  |
|  3 | Lion Feuchtwanger |
|  4 | Emile Zola        |
|  5 | Truman Capote     |
+----+-------------------+
5 rows in set (0.00 sec)

执行脚本后,我们使用mysql客户端工具从Writers表中选择所有数据。

MySQLdb fetchall

fetchall()方法获取查询结果集的所有(或所有剩余)行,并返回一个元组列表。

fetch_all.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import MySQLdb as mdb

con = mdb.connect('localhost', 'testuser', 'test623', 'testdb')

with con: 

    cur = con.cursor()
    cur.execute("SELECT * FROM Writers")

    rows = cur.fetchall()

    for row in rows:
        print row

在此示例中,我们从Writers表中检索所有数据。

cur.execute("SELECT * FROM Writers")

该 SQL 语句从Writers表中选择所有数据。

rows = cur.fetchall()

fetchall()方法获取所有记录。 它返回一个结果集。 从技术上讲,它是一个元组的元组。 每个内部元组代表表中的一行。

for row in rows:
    print row

我们将数据逐行打印到控制台。

$ ./fetch_all.py
(1L, 'Jack London')
(2L, 'Honore de Balzac')
(3L, 'Lion Feuchtwanger')
(4L, 'Emile Zola')
(5L, 'Truman Capote')

这是示例的输出。

一次返回所有数据可能不可行。 我们可以一一读取行。

fetch_onebyone.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import MySQLdb as mdb

con = mdb.connect('localhost', 'testuser', 'test623', 'testdb')

with con:

    cur = con.cursor()
    cur.execute("SELECT * FROM Writers")

    for i in range(cur.rowcount):

        row = cur.fetchone()
        print row[0], row[1]

我们再次将数据从Writers表打印到控制台。 这次,我们一张一行地获取行。

for i in range(cur.rowcount):

    row = cur.fetchone()
    print row[0], row[1]

我们使用fetchone()方法一张一行地获取行。 rowcount属性给出了 SQL 语句返回的行数。

$ ./fetch_onebyone.py
1 Jack London
2 Honore de Balzac
3 Lion Feuchtwanger
4 Emile Zola
5 Truman Capote

这是示例的输出。

MySQLdb 字典游标

MySQLdb模块中有多种游标类型。 默认游标以元组的元组返回数据。 当我们使用字典游标时,数据以 Python 字典的形式发送。 这样,我们可以通过列名称来引用数据。

dictionary_cursor.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import MySQLdb as mdb

con = mdb.connect('localhost', 'testuser', 'test623', 'testdb')

with con:

    cur = con.cursor(mdb.cursors.DictCursor)
    cur.execute("SELECT * FROM Writers LIMIT 4")

    rows = cur.fetchall()

    for row in rows:
        print row["Id"], row["Name"]

在此示例中,我们使用字典游标获取Writers表的前四行。

cur = con.cursor(mdb.cursors.DictCursor)

我们使用DictCursor字典游标。

cur.execute("SELECT * FROM Writers LIMIT 4")

我们从Writers表中获取四行。

for row in rows:
    print row["Id"], row["Name"]

我们通过Writers表的列名引用数据。

$ ./dictcur.py
1 Jack London
2 Honore de Balzac
3 Lion Feuchtwanger
4 Emile Zola

示例输出。

MySQLdb 列标题

接下来,我们将展示如何使用数据库表中的数据打印列标题。

column_headers.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import MySQLdb as mdb

con = mdb.connect('localhost', 'testuser', 'test623', 'testdb')

with con:

    cur = con.cursor()
    cur.execute("SELECT * FROM Writers LIMIT 5")

    rows = cur.fetchall()

    desc = cur.description

    print "%s %3s" % (desc[0][0], desc[1][0])

    for row in rows:    
        print "%2s %3s" % row

同样,我们将Writers表的内容打印到控制台。 现在,我们也包括列的名称。 列名被认为是“元数据”。 它是从游标对象获得的。

desc = cur.description

游标的描述属性返回有关查询的每个结果列的信息。

print "%s %3s" % (desc[0][0], desc[1][0])

在这里,我们打印并格式化表的列名。

for row in rows:    
    print "%2s %3s" % row

在这里,我们遍历并打印数据。

$ ./columnheaders.py
Id Name
 1 Jack London
 2 Honore de Balzac
 3 Lion Feuchtwanger
 4 Emile Zola
 5 Truman Capote

这是输出。

MySQLdb 预备语句

在编写预备语句时,我们使用占位符,而不是直接将值写入语句中。 预准备的语句可提高安全性和性能。 Python DB API 规范建议了五种不同的方式来构建预备语句。 MySQLdb模块支持其中之一,即 ANSI printf格式代码。

prepared_statement.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import MySQLdb as mdb

con = mdb.connect('localhost', 'testuser', 'test623', 'testdb')

with con:    

    cur = con.cursor()

    cur.execute("UPDATE Writers SET Name = %s WHERE Id = %s", 
        ("Guy de Maupasant", "4"))        

    print "Number of rows updated:",  cur.rowcount

我们在第四行更改作者的姓名。

cur.execute("UPDATE Writers SET Name = %s WHERE Id = %s", 
    ("Guy de Maupasant", "4"))   

我们使用由%s标记标识的两个占位符。 在执行 SQL 语句之前,将值绑定到它们的占位符。

$ ./prepared.py
Number of rows updated: 1

我们更新了一行。

mysql> SELECT Name FROM Writers WHERE Id=4;
+------------------+
| Name             |
+------------------+
| Guy de Maupasant |
+------------------+
1 row in set (0.00 sec)

第四行的作者已成功更改。

MySQLdb 插入图像

人们经常寻找将图像插入数据库的方法。 我们将展示如何在 SQLite 和 Python 中完成它。 请注意,有些人不建议将图像放入数据库。 图像是二进制数据。 MySQL 数据库具有一种特殊的数据类型来存储称为BLOB(二进制大对象)的二进制数据。 TINYBLOBBLOBMEDIUMBLOBLONGBLOB是二进制对象类型的变体。

mysql> CREATE TABLE Images(Id INT PRIMARY KEY, Data MEDIUMBLOB);
Query OK, 0 rows affected (0.08 sec)

对于此示例,我们创建一个名为Images的新表。

insert_image.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import MySQLdb as mdb

def read_image():

    with open("sid.jpg") as f:

        img = f.read()
        return img

con = mdb.connect('localhost', 'testuser', 'test623', 'testdb')

with con:

    cur = con.cursor()
    data = read_image()
    cur.execute("INSERT INTO Images VALUES(1, %s)", (data, ))

在上面的脚本中,我们从磁盘读取 JPG 图像并将其插入到Images表中。

def read_image():

    with open("sid.jpg") as f:

        img = f.read()
        return img

read_image()方法从位于当前工作目录中的 JPG 文件读取二进制数据。

cur.execute("INSERT INTO Images VALUES(1, %s)", (data, ))

我们将图像数据插入Images表。

MySQLdb 读取图像

在前面的示例中,我们已将图像插入数据库表中。 现在,我们将从表中读取图像。

read_image.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import MySQLdb as mdb 

def writeImage(data):

    fout = open('sid2.jpg', 'wb')

    with fout:

        fout.write(data)

con = mdb.connect('localhost', 'testuser', 'test623', 'testdb')

with con:

    cur = con.cursor()

    cur.execute("SELECT Data FROM Images WHERE Id=1")
    data = cur.fetchone()[0]
    writeImage(data)  

我们从Images表中读取了一张图像。

cur.execute("SELECT Data FROM Images WHERE Id=1")

我们从表中选择一条记录。

fout = open('sid2.jpg', 'wb')

我们打开一个可写的二进制文件。

fout.write(data)

我们将数据写入磁盘。

现在我们在当前目录中应该有一个名为woman2.jpg的映像。 我们可以检查它是否与我们插入表中的图像相同。

MySQLdb 事务支持

事务是针对一个或多个数据库中数据的数据库操作的基本单位。 事务中所有 SQL 语句的影响可以全部提交给数据库,也可以全部回滚。

对于支持事务的数据库,在创建游标时,Python 接口会静默启动事务。 commit()方法将提交使用该游标进行的更新,而rollback()方法将丢弃这些更新。 每种方法都会启动一个新事务。

MySQL 数据库具有不同类型的存储引擎。 最常见的是 MyISAM 和 InnoDB 引擎。 从 MySQL 5.5 开始,InnoDB 成为默认的存储引擎。 在数据安全性和数据库速度之间需要权衡。 MyISAM 表的处理速度更快,并且不支持事务。 commit()rollback()方法未实现。 他们什么都不做。 另一方面,InnoDB 表可以更安全地防止数据丢失。 他们支持事务。 它们处理较慢。

transaction.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import MySQLdb as mdb
import sys

try:
    con = mdb.connect('localhost', 'testuser', 'test623', 'testdb')

    cur = con.cursor()
    cur.execute("DROP TABLE IF EXISTS Writers")
    cur.execute("CREATE TABLE Writers(Id INT PRIMARY KEY AUTO_INCREMENT, \
                 Name VARCHAR(25)) ENGINE=INNODB")
    cur.execute("INSERT INTO Writers(Name) VALUES('Jack London')")
    cur.execute("INSERT INTO Writers(Name) VALUES('Honore de Balzac')")
    cur.execute("INSERT INTO Writers(Name) VALUES('Lion Feuchtwanger')")
    cur.execute("INSERT INTO Writers(Name) VALUES('Emile Zola')")
    cur.execute("INSERT INTO Writers(Name) VALUES('Truman Capote')")
    cur.execute("INSERT INTO Writers(Name) VALUES('Terry Pratchett')")

    con.commit()

except mdb.MySQLError, e:

    if con:
        con.rollback()

    print "Error %d: %s" % (e.args[0],e.args[1])
    sys.exit(1)

finally:    

    if con:    
        con.close()

我们重新创建Writers表。 我们明确地处理事务。

cur = con.cursor()

在 Python DB API 中,我们不调用BEGIN语句来启动事务。 创建游标后,将启动一个事务。

cur.execute("CREATE TABLE Writers(Id INT PRIMARY KEY AUTO_INCREMENT, \
                Name VARCHAR(25)) ENGINE=INNODB")

我们正在处理一个 InnoDB 表类型。 对于较早的 MySQL 版本(< 5.5),我们需要使用ENGINE=INNODB选项指定引擎类型。

con.commit()

我们必须使用commit()rollback()方法结束事务。 如果我们在这行中添加注释,则会创建表,但不会将数据写入表中。

在本教程中,我们一直在处理事务而未明确说明它们。 我们使用了上下文管理器。 上下文管理器处理所需的运行时上下文的入口和出口,以执行代码块。 通常使用with语句来调用上下文管理器。

MySQLdb模块中的连接对象可用作上下文管理器。 他们自动提交或回滚事务。 连接上下文管理器通过分解tryexceptfinally语句来清理代码。

transaction2.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import MySQLdb as mdb

con = mdb.connect('localhost', 'testuser', 'test623', 'testdb')

with con:

    cur = con.cursor()
    cur.execute("DROP TABLE IF EXISTS Writers")
    cur.execute("CREATE TABLE Writers(Id INT PRIMARY KEY AUTO_INCREMENT, \
                 Name VARCHAR(25))")
    cur.execute("INSERT INTO Writers(Name) VALUES('Jack London')")
    cur.execute("INSERT INTO Writers(Name) VALUES('Honore de Balzac')")
    cur.execute("INSERT INTO Writers(Name) VALUES('Lion Feuchtwanger')")
    cur.execute("INSERT INTO Writers(Name) VALUES('Emile Zola')")
    cur.execute("INSERT INTO Writers(Name) VALUES('Truman Capote')")
    cur.execute("INSERT INTO Writers(Name) VALUES('Terry Pratchett')")

在我们的代码示例中,上下文管理器处理错误处理所需的所有工作。 它会自动提交或回滚事务。

这是 MySQL Python 教程。 它使用旧版MySQLdb模块与 MySQL 一起使用。 请参考 PyMySQL 教程以使用现代的 PyMySQL 模块。

MySQL Perl 教程

原文: http://zetcode.com/db/mysqlperl/

这是针对 MySQL 数据库的 Perl 编程教程。 它涵盖了使用 Perl 语言进行 MySQL 编程的基础。

目录

Perl DBI

Perl DBI (数据库接口)是用于 Perl 编程语言的数据库访问模块。 它定义了一组提供标准数据库接口的方法,变量和约定。

其他语言也创建了用于数据库的类似通用接口。 Java 具有 JDBC,PHP PDO。

Tweet

相关教程

MySQL 教程涵盖了 MySQL 数据库。 MySQL Ruby 教程是针对 Ruby 语言的 MySQL 编程教程。 MySQL C# 教程是针对 C# 语言的 MySQL 编程教程。 Perl LWP 编程文章介绍了使用 LWP 模块进行 Perl 编程。

MySQL & Perl DBI

原文: http://zetcode.com/db/mysqlperl/dbi/

在 MySQL Perl 教程的第一章中,我们将介绍 Perl DBI 模块和 MySQL 数据库。 我们将提供一些定义并显示如何安装必要的元素。

先决条件

要使用本教程,我们必须安装 Perl 语言,MySQL 数据库,Perl DBI 和DBD::MySQL模块。 DBI 是标准的 Perl 数据库接口。 每个数据库都有其驱动程序。 在我们的例子中,DBD::mysql是 MySQL 数据库的驱动程序。

$ sudo perl -MCPAN -e shell
cpan> install DBI
cpan[2]> install DBD::mysql

上面的命令显示了如何安装 Perl DBI 和DBD::mysql模块。

MySQL 数据库

MySQL 是领先的开源数据库管理系统。 它是一个多用户,多线程的数据库管理系统。 MySQL 在网络上特别流行。 它是非常流行的 LAMP 平台的一部分,该平台由 Linux,Apache,MySQL 和 PHP 组成。 目前,MySQL 由 Oracle 拥有。 MySQL 数据库在最重要的 OS 平台上可用。 它运行在 BSD UNIX,LINUX,Windows 或 Mac OS。 维基百科和 YouTube 使用 MySQL。 这些站点每天管理数百万个查询。 MySQL 有两个版本:MySQL 服务器系统和 MySQL 嵌入式系统。

MySQL 随附mysql命令行工具。 它可用于对数据库发出 SQL 命令。 现在,我们将使用mysql命令行工具创建一个新数据库。

$ sudo apt-get install mysql-server

此命令将安装 MySQL 服务器和其他各种包。 在安装包时,提示我们输入 MySQL 根帐户的密码。 要从源代码安装 MySQL,请查看 MySQL 安装页面。

$ service mysql status
mysql start/running, process 1238

我们检查 MySQL 服务器是否正在运行。 如果没有,我们需要启动服务器。

$ sudo service mysql start

如果我们已经从包中安装了 MySQL 数据库,则上述命令是启动 MySQL 的常用方法。

$ sudo -b /usr/local/mysql/bin/mysqld_safe

上面的命令使用 MySQL 服务器启动脚本启动 MySQL 服务器。 我们启动 MySQL 服务器的方式可能有所不同。 这取决于我们是否从源代码或包安装了 MySQL,也取决于 Linux 发行版。 有关更多信息,请查阅 MySQL 的第一步或您的 Linux 发行版信息。

接下来,我们将创建一个新的数据库用户和一个新的数据库。 我们使用mysql客户端。

$ mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 30
Server version: 5.0.67-0ubuntu6 (Ubuntu)

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema | 
| mysql              | 
+--------------------+
2 rows in set (0.00 sec)

我们使用 mysql 监视器客户端应用连接到服务器。 我们使用根帐户连接到数据库。 我们用SHOW DATABASES语句显示所有可用的数据库。

mysql> CREATE DATABASE mydb;
Query OK, 1 row affected (0.02 sec)

我们创建一个新的mydb数据库。 在整个教程中,我们将使用此数据库。

mysql> CREATE USER user12@localhost IDENTIFIED BY '34klq*';
Query OK, 0 rows affected (0.00 sec)

mysql> USE mydb;
Database changed

mysql> GRANT ALL ON mydb.* to user12@localhost;
Query OK, 0 rows affected (0.00 sec)

mysql> quit;
Bye

我们创建一个新的数据库用户。 我们授予该用户mydb数据库所有表的所有特权。

Perl DBI

Perl DBI(数据库接口)是用于 Perl 编程语言的数据库访问模块。 它定义了一组提供标准数据库接口的方法,变量和约定。 DBI 还负责驱动程序的动态加载,错误检查和处理,提供方法的默认实现以及许多其他非数据库特定的职责。 DBI 将方法调用分派到适当的数据库驱动程序。DBD(数据库驱动程序)是一个 Perl 模块,它转换特定数据库引擎的 DBI 方法。 数据库驱动程序由数据库供应商提供。

#!/usr/bin/perl

use strict;
use DBI;

my @ary = DBI->available_drivers();
print join("\n", @ary), "\n";

该代码示例列出了系统上所有可用的驱动程序。

use DBI;

我们为脚本导入 DBI 模块。

my @ary = DBI->available_drivers();

available_drivers()类方法获取系统上所有当前可用的驱动程序。

print join("\n", @ary), "\n";

此行将驱动程序打印到控制台,每个驱动程序都在单独的行上。

$ ./available_drivers.pl 
DBM
ExampleP
File
Gofer
Proxy
SQLite
Sponge
mysql

示例输出。

常见的 DBI 方法

下表列出了一些常见的 DBI 方法。

方法名称 描述
available_drivers() 返回所有可用驱动程序的列表
connect() 建立与请求的数据源的连接
disconnect() 与数据库服务器断开连接
prepare() 准备要执行的 SQL 语句
execute() 执行预备语句
do() 准备并执行一条 SQL 语句
bind_param() 在预备语句中将值与占位符关联
bind_col() 将 Perl 变量绑定到SELECT语句的输出字段
begin_work() 开始新事务
commit() 将最新的一系列未提交的数据库更改写入数据库
rollback() 撤消最近一系列未提交的数据库更改
quote() 引用字符串字面值,以用作 SQL 语句中的字面值
dump_results() 获取所有行并打印
fetchrow_array() 获取下一行作为字段数组
fetchrow_arrayref() 获取下一行作为字段的引用数组
fetchrow_hashref() 获取下一行作为对哈希表的引用
fetchall_arrayref() 以数组的形式获取所有数据
finish() 完成声明并让系统释放资源
rows() 返回受影响的行数
column_info() 提供有关列的信息
table_info() 提供有关表的信息
primary_key_info() 提供有关表中主键的信息
foreign_key_info() 提供有关表中外键的信息

约定

在使用 Perl DBI 时,Perl 程序员通常使用以下变量名。 在本教程中,我们也将遵守这些约定。

变量名 描述
$dbh 数据库句柄对象
$sth 语句句柄对象
$drh 驱动程序句柄对象(在应用中很少见或使用)
$h | 上面的任何句柄类型($dbh$sth$drh
$rc 通用返回码(布尔值:true = okfalse = error
$rv 一般返回值(通常为整数)
@ary 从数据库返回的值列表,通常是一行数据
$rows 处理的行数(如果可用,否则为 -1)
$fh 文件句柄
undef NULL值由 Perl 中的未定义值表示
\%attr 引用传递给方法的属性值的哈希

MySQL Perl 教程的这一章介绍了 Perl DBI 模块和 MySQL 数据库。

使用 Perl 连接到 MySQL 数据库

原文: http://zetcode.com/db/mysqlperl/connect/

MySQL Perl 教程的这一部分将展示如何创建与数据库的数据库连接。

第一步是连接到数据库。 我们使用connect() DBI 方法建立连接。 disconnect()方法用于关闭数据库连接。

$dbh = DBI->connect($dsn, $username, $password)
    or die $DBI::errstr;
$dbh = DBI->connect($dsn, $username, $password, \%attr)
    or die $DBI::errstr;

connect()方法建立与请求的数据源的数据库连接。 如果连接成功,它将返回数据库句柄对象。 我们使用disconnect()方法终止连接。

$dsn是数据源名称。 它是一个字符串,告诉 Perl DBI 模块,应加载哪种驱动程序以及将要建立连接的数据库的位置。

dbi:DriverName:database_name
dbi:DriverName:database_name@hostname:port
dbi:DriverName:database=database_name;host=hostname;port=port

上面的字符串是 Perl DBI 中数据源名称的示例。

dbi:mysql:dbname=mydb

我们将使用此数据源名称。 dsn始终以dbi:子字符串开头。 然后我们有驱动程序名称。 在我们的情况下,驱动程序名称为mysql。 第三部分是数据库名称。 在本教程中,我们将使用mydb

$username$password是认证所需的用户名和密码。 最后一个参数是对哈希的引用,在哈希中,我们可以设置属性以更改连接的默认设置。 例如,RaiseError属性可用于强制错误引发异常,而不是返回错误代码。 HandleError属性可用于提供子程序,以防发生错误。 AutoCommit属性设置或取消设置自动提交模式。

$DBI::errstr是一个 DBI 动态属性,它返回本地数据库引擎错误消息。 如果连接失败,则会显示此消息,并且脚本将中止。

版本

在第一个代码示例中,我们将获取 MySQL 数据库的版本。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $sth = $dbh->prepare("SELECT VERSION()");
$sth->execute();

my $ver = $sth->fetch();

print @$ver;
print "\n";

$sth->finish();
$dbh->disconnect();

在上面的 Perl 脚本中,我们连接到先前创建的mydb数据库。 我们执行一条 SQL 语句,该语句返回 MySQL 数据库的版本。

use DBI;

我们使用 Perl DBI 模块连接到 MySQL 数据库。

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

在这里,我们连接到mydb数据库。 第一个参数是数据源名称。 在字符串中,我们指定数据库驱动程序和数据库名称。 第二个参数是用户名。 第三个参数是用户密码。 最后一个参数是数据库选项。 我们将RaiseError选项设置为 1。这将导致引发异常,而不是返回错误代码。

my $sth = $dbh->prepare("SELECT VERSION()");
$sth->execute();

prepare()方法准备一条 SQL 语句供以后执行。 execute()方法执行 SQL 语句。

my $ver = $sth->fetch();

我们获取数据。

print @$ver;
print "\n";

我们将检索到的数据打印到控制台。

$sth->finish();

在这里,我们指示将不再从此语句句柄中获取任何数据。

$dbh->disconnect();

我们关闭与数据库的连接。

$ ./version.pl
5.1.62-0ubuntu0.11.10.1

执行verion.pl脚本,我们得到 MySQL 数据库的版本。

插入数据

我们将创建一个Cars表并在其中插入几行。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",
    "34klq*",
    { RaiseError => 1}
) or die $DBI::errstr;

$dbh->do("DROP TABLE IF EXISTS Cars");
$dbh->do("CREATE TABLE Cars(Id INT PRIMARY KEY, Name TEXT, Price INT) ENGINE=InnoDB");
$dbh->do("INSERT INTO Cars VALUES(1,'Audi',52642)");
$dbh->do("INSERT INTO Cars VALUES(2,'Mercedes',57127)");
$dbh->do("INSERT INTO Cars VALUES(3,'Skoda',9000)");
$dbh->do("INSERT INTO Cars VALUES(4,'Volvo',29000)");
$dbh->do("INSERT INTO Cars VALUES(5,'Bentley',350000)");
$dbh->do("INSERT INTO Cars VALUES(6,'Citroen',21000)");
$dbh->do("INSERT INTO Cars VALUES(7,'Hummer',41400)");
$dbh->do("INSERT INTO Cars VALUES(8,'Volkswagen',21600)");

$dbh->disconnect();

上面的脚本创建一个Cars表,并将 8 行插入到该表中。

$dbh->do("CREATE TABLE Cars(Id INT PRIMARY KEY, Name TEXT, Price INT) ENGINE=InnoDB");

do()方法执行 SQL 语句。 它将两个方法调用prepare()execute()组合为一个调用。 do()方法用于非选择语句。

$dbh->do("INSERT INTO Cars VALUES(1,'Audi',52642)");
$dbh->do("INSERT INTO Cars VALUES(2,'Mercedes',57127)");

这两行将两辆车插入表。 请注意,默认情况下,我们处于自动提交模式,其中对表的所有更改均立即生效。

mysql> SELECT * FROM Cars;
+----+------------+--------+
| Id | Name       | Price  |
+----+------------+--------+
|  1 | Audi       |  52642 |
|  2 | Mercedes   |  57127 |
|  3 | Skoda      |   9000 |
|  4 | Volvo      |  29000 |
|  5 | Bentley    | 350000 |
|  6 | Citroen    |  21000 |
|  7 | Hummer     |  41400 |
|  8 | Volkswagen |  21600 |
+----+------------+--------+
8 rows in set (0.01 sec)

这是我们已写入Cars表的数据。

最后插入的行 ID

有时,我们需要确定最后插入的行的 ID。 在 Perl DBI 中,我们使用last_insert_id()方法进行查找。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

$dbh->do("DROP TABLE IF EXISTS Friends");
$dbh->do("CREATE TABLE Friends(Id INTEGER PRIMARY KEY AUTO_INCREMENT, Name TEXT)");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Tom')");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Rebecca')");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Jim')");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Robert')");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Julian')");

my $id = $dbh->last_insert_id("", "", "Friends", "");
print "The last Id of the inserted row is $id\n";

$dbh->disconnect();

我们创建一个新的Friends表。 Id自动递增。

$dbh->do("CREATE TABLE Friends(Id INTEGER PRIMARY KEY AUTO_INCREMENT, Name TEXT)");

这是创建Friends表的 SQL 语句。 AUTO_INCREMENT属性用于为新行生成唯一的 ID。

$dbh->do("INSERT INTO Friends(Name) VALUES ('Tom')");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Rebecca')");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Jim')");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Robert')");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Julian')");

这五个 SQL 语句将五行插入到Friends表中。

my $id = $dbh->last_insert_id("", "", "Friends", "");

使用last_insert_id()方法,我们获得最后插入的行 ID。

$ ./last_rowid.pl
The last Id of the inserted row is 5

我们看到了脚本的输出。

取得数据

在本章的最后一个示例中,我们获取一些数据。 有关数据获取的更多信息将在“查询”一章中进行讨论。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $sth = $dbh->prepare( "SELECT * FROM Cars WHERE Id=1" );  
$sth->execute();

my ($id, $name, $price) = $sth->fetchrow();
print "$id $name $price\n";

my $fields = $sth->{NUM_OF_FIELDS};
print "We have selected $fields field(s)\n";

my $rows = $sth->rows();
print "We have selected $rows row(s)\n";

$sth->finish();
$dbh->disconnect();

在示例中,我们从Cars表中获取一行。 我们还将找出我们选择了多少字段&行。

my $sth = $dbh->prepare( "SELECT * FROM Cars WHERE Id=1" );  
$sth->execute();

我们使用prepare()方法准备一条 SQL 语句。 SQL 字符串被发送到 MySQL 数据库引擎进行处理。 检查其语法和有效性。 该方法返回一个语句句柄。 然后执行 SQL 语句。 准备将数据发送到客户端程序。

my ($id, $name, $price) = $sth->fetchrow();
print "$id $name $price\n";

使用fetchrow()方法从数据库中检索数据。 该方法以 Perl 列表的形式从表中返回一行。

my $fields = $sth->{NUM_OF_FIELDS};

NUM_OF_FIELDS是一个语句句柄属性,它为我们提供了返回字段的数量。 在我们的情况下,我们返回了三个字段:IdNamePrice

my $rows = $sth->rows();

我们得到选定的行数。 我们仅从表中检索到一行。 rows()方法返回受影响的行数。 它可以用于SELECTUPDATEDELETE SQL 语句。

$ ./fetchrow.pl
1 Audi 52642
We have selected 3 field(s)
We have selected 1 row(s)

fetchrow.pl脚本的输出。

在 MySQL Perl 教程的这一章中,我们展示了如何建立与 MySQL 数据库的数据库连接。 我们已经解释了对数据库进行一些基本工作的脚本。

MySQL 中的 Perl 错误处理

原文: http://zetcode.com/db/mysqlperl/err/

在本章中,我们将展示如何处理错误。

方法名称 描述
$h->err() 从上次调用的驱动程序方法返回本机数据库引擎错误代码。
$h->errstr() 从最后一个调用的 DBI 方法返回本机数据库引擎错误消息。
$h->state() 以标准SQLSTATE五字符格式返回状态代码。

以上三种方法处理错误消息。

DBI 动态属性 描述
$DBI::err | 相当于$h->err()
$DBI::errstr | 相当于$h->errstr()
$DBI::state | 相当于$h->state()

第二张表列出了与错误处理有关的 DBI 动态属性。 这些属性的寿命很短。 应该在可能导致错误的方法之后立即使用它们。

默认错误处理

默认情况下,错误由 Perl DBI 方法返回。

#!/usr/bin/perl

use strict;
use DBI;

my $dsn = "dbi:mysql:dbname=mydb";
my $user = "user12";
my $password = "34klq*";

my $dbh = DBI->connect($dsn, $user, $password) 
    or die "Can't connect to database: $DBI::errstr";

my $sth = $dbh->prepare( 
    q{ SELECT Id, Name, Price FROM Cars }
    )
    or die "Can't prepare statement: $DBI::errstr";

my $rc = $sth->execute()
    or die "Can't execute statement: $DBI::errstr";

while (my($id, $name, $price) = $sth->fetchrow()) {
    print "$id $name $price\n";
}

# check for problems which may have terminated the fetch early
warn $DBI::errstr if $DBI::err;

$sth->finish();
$dbh->disconnect();

在第一个脚本中,我们处理返回错误代码的默认行为。

my $dbh = DBI->connect($dsn, $user, $password) 
    or die "Can't connect to database: $DBI::errstr";

我们调用connect()方法来创建数据库连接。 如果尝试失败,则该方法返回undef并设置$DBI::err$DBI::errstr属性。 die()方法在失败的情况下会打印错误消息并终止脚本。

my $sth = $dbh->prepare( 
    q{ SELECT Id, Name, Price FROM Cars }
    )
    or die "Can't prepare statement: $DBI::errstr";

我们称为prepare()语句。 如果该方法失败,则die()方法将显示错误消息并终止脚本。

my $rc = $sth->execute()
    or die "Can't execute statement: $DBI::errstr";

再次。 我们调用execute()方法并检查错误。 如果失败,该方法将返回undef

warn $DBI::errstr if $DBI::err;

我们检查可能会提早终止提取方法的问题。

引发异常

每次我们调用 DBI 方法时都要检查错误,这可能很麻烦。 如果我们有一个较大的脚本,我们很容易忘记这样做。 处理可能的错误的首选方法是引发异常。 为了引发异常,我们将RaiseError属性设置为true

#!/usr/bin/perl

use strict;
use DBI;

my $dsn = "dbi:mysql:dbname=mydb";
my $user = "user12";
my $password = "34klq*";
my %attr = ( RaiseError => 1 );

my $dbh = DBI->connect($dsn, $user, $password, \%attr) 
    or die "Can't connect to database: $DBI::errstr";

my $sth = $dbh->prepare("SELECT * FROM Cars LIMIT 5");   
$sth->execute();

while (my($id, $name, $price) = $sth->fetchrow()) {
    print "$id $name $price\n";
}

$sth->finish();
$dbh->disconnect();

在连接属性中,我们将RaiseError属性设置为 1。发生错误时,引发异常,而不是返回错误代码。

my %attr = ( RaiseError => 1 );

我们将RaiseError属性设置为 1。

my $dbh = DBI->connect($dsn, $user, $password, \%attr) 
    or die "Can't connect to database: $DBI::errstr";

connect()方法是我们检查返回代码的唯一方法。

my $sth = $dbh->prepare("SELECT * FROM Cars LIMIT 5");   
$sth->execute();

prepare()execute()方法不检查返回错误代码。 如果失败,将引发异常,Perl DBI 将调用die()方法并打印错误消息。

错误子程序

使用HandleError连接的handle属性,我们可以设置对子例程的引用,该子例程在检测到错误时被调用。 用三个参数调用该子例程:RaiseErrorPrintError将使用的错误消息字符串,正在使用的 DBI 句柄以及失败的方法返回的第一个值(通常为undef)。

如果子例程返回错误值,则将检查RaiseErrorPrintError属性,并按常规操作。

#!/usr/bin/perl

use strict;
use DBI;

my $dsn = "dbi:mysql:dbname=mydb";
my $user = "user12";
my $password = "34klq*";
my %attr = ( RaiseError => 1, AutoCommit => 0, HandleError => \&handle_error );

my $dbh = DBI->connect($dsn, $user, $password, \%attr) 
    or die "Can't connect to database: $DBI::errstr";

$dbh->do("UPDATE Cars SET Price=52000 WHERE Id=1");
$dbh->do("UPDATE Car SET Price=22000 WHERE Id=8");

$dbh->commit();

$dbh->disconnect();

sub handle_error {

    $dbh->rollback(); 

    my $error = shift;
    print "An error occurred in the script\n";
    print "Message: $error\n";
    return 1;
}

我们自己的子例程将处理该错误。

my %attr = ( RaiseError => 1, AutoCommit => 0, HandleError => \&handle_error );

当检测到错误时,HandleError属性提供对所调用的handle_error()子例程的引用。 AutoCommit已关闭,这意味着我们正在处理事务。

$dbh->do("UPDATE Car SET Price=22000 WHERE Id=8");

SQL 语句中有错误。 没有汽车表。

sub handle_error {

    $dbh->rollback(); 

    my $error = shift;
    print "An error occurred in the script\n";
    print "Message: $error\n";
    return 1;
}

这是handle_error()子例程。 我们打印错误消息。 并返回 1。如果我们改为返回 0,则会出现其他错误消息。 禁止返回与RaiseError属性相关的 1 条错误消息。

$ ./errsub.pl
An error occurred in the script
Message: DBD::mysql::db do failed: Table 'mydb.Car' doesn't exist

示例的输出。

eval的例子

根据 Perl DBI 文档,处理 DBI 错误的最可靠的方法是使用eval()方法。

#!/usr/bin/perl

use strict;
use DBI;
use DBI qw(:sql_types);

my $dsn = "dbi:mysql:dbname=mydb";
my $user = "user12";
my $password = "34klq*";
my %attr = ( RaiseError => 1, AutoCommit => 0 );

my $dbh = DBI->connect($dsn, $user, $password, \%attr) 
    or die "Can't connect to database: $DBI::errstr";

my @data = (
    [1, "Audi", 52642],
    [2, "Mercedes", 57127],
    [3, "Skoda", 9000], 
    [4, "Volvo", 29000], 
    [5, "Bentley", 350000], 
    [6, "Citroen", 21000],
    [7, "Hummer", 41400],
    [8, "Volkswagen", 21601] 
);

eval {
    $dbh->do("DROP TABLE IF EXISTS Cars");
    $dbh->do("CREATE TABLE Cars(Id INTEGER PRIMARY KEY, Name TEXT, Price INT)");
};

my $sql = qq{ INSERT INTO Cars VALUES ( ?, ?, ? ) };
my $sth = $dbh->prepare($sql);

foreach my $row (@data) {

  eval {
      $sth->bind_param(1, @$row[0], SQL_INTEGER);
      $sth->bind_param(2, @$row[1], SQL_VARCHAR);
      $sth->bind_param(3, @$row[2], SQL_INTEGER);
      $sth->execute();
      $dbh->commit();
  };

  if( $@ ) {
      warn "Database error: $DBI::errstr\n";
      $dbh->rollback(); 
  }
}

$sth->finish();
$dbh->disconnect();

上面的代码示例是处理错误的最正确方法。

my %attr = ( RaiseError => 1, AutoCommit => 0 );

我们提出异常,而不是检查返回码。 我们关闭自动提交模式,然后手动提交或回滚更改。

my $sql = qq{ INSERT INTO Cars VALUES ( ?, ?, ? ) };
my $sth = $dbh->prepare($sql);

我们使用占位符来防止错误和安全问题。

eval {
    $sth->bind_param(1, @$row[0], SQL_INTEGER);
    $sth->bind_param(2, @$row[1], SQL_VARCHAR);
    $sth->bind_param(3, @$row[2], SQL_INTEGER);
    $sth->execute();
    $dbh->commit();
};

eval()方法内部,我们放置了容易出错的代码。 该方法捕获异常,并用错误消息填充$@特殊变量。 我们使用bind_param()方法将变量绑定到占位符。

if( $@ ) {
    warn "Database error: $DBI::errstr\n";
    $dbh->rollback(); 
}

如果发生错误,我们将打印错误消息并回滚更改。

在 MySQL Perl 教程的这一部分中,我们讨论了错误处理。

使用 Perl 进行 MySQL 查询

原文: http://zetcode.com/db/mysqlperl/queries/

我们已经建立了到数据库的连接。 现在我们要修改并从数据库中获取数据。

使用SELECT语句从数据库中检索数据。 在 Perl DBI 中,首先我们使用prepare()方法准备 SQL 语句。 SQL 字符串被发送到数据库引擎,该引擎检查语句的有效性,语法,并在某些数据库中检查执行某些查询的用户权限。 如果一切正常,则对语句句柄的引用将返回到 Perl 脚本。 下一步是对execute()方法的调用。 该方法在数据库内执行查询。 此时,结果保留在数据库中。 Perl 脚本尚不包含数据。 对于非选择语句,execute()方法返回已知的受影响的行数。 在最后一步中,从数据库中获取数据。 数据被逐行提取并填充到 Perl 数据结构中。

Perl DBI 有几种从数据库表中获取数据的方法。

方法 描述
fetchrow_arrayref() 获取下一行数据并返回对数组的引用。
fetchrow_array() 获取下一行数据,并将其作为列表返回。
fetchrow_hashref() 获取下一行数据,并将其返回为对哈希的引用。
fetchall_arrayref() 提取所有数据&返回对每行有一个引用的数组的引用。
fetch() 该方法是fetchrow_arrayref()的别名。
fetchrow() 该方法是fetchrow_array()的别名。

准备并执行完 SQL 语句后,我们调用可用的访存方法之一。

方法 描述
selectrow_arrayref() prepare()execute()fetchrow_arrayref()合并为一个调用。
selectrow_hashref() Combines prepare()execute()fetchrow_hashref()进入一个通话。
selectrow_array() prepare()execute()fetchrow_array()合并为一个调用。
selectall_arrayref() prepare()execute()fetchall_arrayref()合并为一个调用。
selectall_hashref() Combines prepare()execute()fetchall_hashref()进入一个通话。
selectcol_arrayref() 合并prepare()execute()并从所有行中提取一个col到单个调用中。

在第二张表中,我们列出了工具方法,这些方法将三个方法组合为一个调用。 它们是方便的方法。

提取方法

在第一个示例中,我们将演示fetchrow_arrayref()方法的用法。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $sth = $dbh->prepare("SELECT * FROM Cars LIMIT 5");
$sth->execute();

my $row;
while ($row = $sth->fetchrow_arrayref()) {
    print "@$row[0] @$row[1] @$row[2]\n";
}

$sth->finish();
$dbh->disconnect();

在示例中,我们从Cars表中选择 5 行。 使用fetchrow_arrayref()方法检索数据。

my $sth = $dbh->prepare("SELECT * FROM Cars LIMIT 5");
$sth->execute();

这是数据检索过程的前两个阶段。 我们准备并执行SELECT语句。

my $row;
while ($row = $sth->fetchrow_arrayref()) {
    print "@$row[0] @$row[1] @$row[2]\n";
}

现在,我们正在获取数据。 fetchrow_arrayref()方法获取下一行数据,并返回对包含字段值的数组的引用。 当没有更多的行了时,我们将方法放入终止的while循环中。

$ ./fetchrow_arrayref.pl
1 Audi 52642
2 Mercedes 57127
3 Skoda 9000
4 Volvo 29000
5 Bentley 350000

示例输出。

在第二个示例中,我们将使用fetchrow_array()方法。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $sth = $dbh->prepare( "SELECT * FROM Cars LIMIT 5" );  
$sth->execute();

my @row;
while (@row = $sth->fetchrow_array()) {
    print "@row\n";
}

$sth->finish();
$dbh->disconnect();

在此脚本中,我们连接到数据库,并使用fetchrow_array()方法一张一张地获取 5 行Cars表。

my @row;
while (@row = $sth->fetchrow_array()) {
    print "@row\n";
}

fetchrow_array()方法获取下一行数据,并将其作为包含字段值的列表返回。 我们使用while循环遍历所有 5 行。

在下一个示例中,我们将按列名称获取数据。 为此,我们将使用fetchrow_hashref()方法。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $sth = $dbh->prepare( "SELECT * FROM Cars LIMIT 5" );  
$sth->execute();

my $row;
while($row = $sth->fetchrow_hashref()) {
    print "$row->{Id} $row->{Name} $row->{Price}\n";
}

$sth->finish();
$dbh->disconnect();

在示例中,数据以对 Perl 哈希的引用形式返回。

my $row;
while($row = $sth->fetchrow_hashref()) {
    print "$row->{Id} $row->{Name} $row->{Price}\n";
}

fetchrow_hashref()方法获取下一行数据,并将其返回为对包含字段名称和字段值对的哈希的引用。 使用此方法,我们可以按列名称检索值。

在本节的最后一个示例中,我们一步一步从SELECT语句中获取所有数据。 我们使用fetchall_arrayref()方法。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $sth = $dbh->prepare("SELECT * FROM Cars LIMIT 5");
$sth->execute();

my $all = $sth->fetchall_arrayref();

foreach my $row (@$all) {
    my ($id, $name, $price) = @$row;
    print "$id $name $price\n";
}

$sth->finish();
$dbh->disconnect();

该示例从Cars表中选择并打印五行。

my $all = $sth->fetchall_arrayref();

我们通过一个方法调用获取所有数据。 fetchall_arrayref()方法返回对数组的引用,该数组每行包含一个引用。

foreach my $row (@$all) {
    my ($id, $name, $price) = @$row;
    print "$id $name $price\n";
}

我们使用foreach循环遍历检索到的数据。

转储数据

Perl DBI 有一个称为dump_results()的特殊方法。 此方法被设计为用于原型设计和测试查询的便捷工具。 它使用neat_list()方法格式化和编辑字符串以供人类阅读。 不建议将其用于数据传输应用。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $sth = $dbh->prepare( "SELECT * FROM Cars LIMIT 5" );  
$sth->execute();

$sth->dump_results();      

$sth->finish();
$dbh->disconnect();

在示例中,我们将转储结果集中的所有数据。

my $sth = $dbh->prepare( "SELECT * FROM Cars LIMIT 5" );  
$sth->execute();

SQL 语句从Cars表中选择五行。 和所有三列。

$sth->dump_results();  

dump_results()从语句句柄中选择所有行并进行打印。 这是用于原型制作和测试的方法。

$ ./dump.pl 
1, 'Audi', 52642
2, 'Mercedes', 57127
3, 'Skoda', 9000
4, 'Volvo', 29000
5, 'Bentley', 350000
5 rows

这是示例的输出。

便利方法

我们将展示两个使用上述便利方法的示例。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $ary = $dbh->selectrow_arrayref("SELECT * FROM Cars WHERE Id = 5");
print join(" ", @$ary), "\n";

$dbh->disconnect();

在第一个代码示例中,我们将调用selectrow_arrayref()方法。 我们从Cars表中选择第五行。

my $ary = $dbh->selectrow_arrayref("SELECT * FROM Cars WHERE Id = 5");

selectrow_arrayref()方法将prepare()execute()fetchrow_arrayref()合并为一个调用。 它返回对语句第一行数据的引用。 请注意,我们不使用语句句柄。 我们使用$dbh数据库句柄对象。

print join(" ", @$ary), "\n";

我们将行打印到控制台。

下面的示例显示selectall_arrayref()方法。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $all = $dbh->selectall_arrayref("SELECT * FROM Cars LIMIT 5");

foreach my $row (@$all) {
    my ($id, $name, $price) = @$row;
    print "$id $name $price\n";
}

$dbh->disconnect();

我们再次从Cars表中检索 5 行。

my $all = $dbh->selectall_arrayref("SELECT * FROM Cars LIMIT 5");

selectall_arrayref()方法返回对数组的引用,其中包含对获取的每一行数据的数组的引用。 提供的 SQL 语句从Cars表中选择 5 行。 注意,我们既没有调用prepare()也没有调用execute()方法。 这是因为selectall_arrayref()方法将prepare()execute()fetchall_arrayref()组合到一个调用中。

foreach my $row (@$all) {
    my ($id, $name, $price) = @$row;
    print "$id $name $price\n";
}

我们遍历提取的数组数组并将数据打印到终端。

$ ./selectall_arrayref.pl
1 Audi 52642
2 Mercedes 57127
3 Skoda 9000
4 Volvo 29000
5 Bentley 350000

这是示例的输出。

参数化查询

现在,我们将关注参数化查询。 当使用参数化查询时,我们使用占位符,而不是直接将值写入语句。 参数化查询可提高安全性和性能。

当程序收到用户的输入时,程序员必须始终保持谨慎。 稍后,我们将值绑定到预备语句,而不是从用户输入构建字符串。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $id = 3;
my $sth = $dbh->prepare( "SELECT * FROM Cars WHERE Id = ?" );  
$sth->execute($id);

my $ret = $sth->fetch();

foreach my $row (@$ret) {
    print "$row ";
} 

print "\n";

$sth->finish();
$dbh->disconnect();

在代码示例中,我们从表中选择特定的行。 SQL 语句具有一个占位符,稍后将在代码中填充该占位符。

my $id = 3;

这可能是用户的输入。

my $sth = $dbh->prepare( "SELECT * FROM Cars WHERE Id = ?" ); 

问号?是值的占位符。 该值稍后添加。

$sth->execute($id);

execute()语句采用一个参数,该参数绑定到占位符。

$ ./parameterized.pl
3 Skoda 9000

我们已经使用参数化查询从Cars表中检索了一行。

在第二个示例中,我们将使用便利选择方法之一使用参数化查询。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $id = 2;

my @ary = $dbh->selectrow_array("SELECT * FROM Cars WHERE Id = ?", undef, $id);
print join(" ", @ary), "\n";

$dbh->disconnect();

我们需要在SELECT查询中填充一个占位符。

my @ary = $dbh->selectrow_array("SELECT * FROM Cars WHERE Id = ?", undef, $id);

selectrow_array()方法的第三个参数采用占位符的值。

在 MySQL Perl 教程的这一部分中,我们演示了如何使用各种 Perl DBI 方法从数据库中获取数据。

在 MySQL 中使用 Perl 绑定参数&列

原文: http://zetcode.com/db/mysqlperl/bind/

SQL 语句通常是动态构建的。 用户提供一些输入,并将其内置到语句中。 程序员每次处理用户的输入时都必须谨慎。 它具有一些严重的安全隐患。 动态构建 SQL 语句的推荐方法是使用参数绑定。

绑定参数可以防止 SQL 注入程序。 它会自动转义一些特殊字符并允许正确处理它们。 当我们准备语句并绑定参数时,许多数据库也会显着提高其性能。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $name = "Volkswagen"; 

my $sth = $dbh->prepare("SELECT * FROM Cars WHERE Name = ?");
$sth->execute($name);

my ($id, $name, $price) = $sth->fetchrow();
print "$id $name $price\n";

$sth->finish();
$dbh->disconnect();

该示例从Cars表中为特定的汽车名称选择一行。

my $name = "Volkswagen"; 

这是一个可能来自用户的值。 例如从 HTML 表单。

my $sth = $dbh->prepare("SELECT * FROM Cars WHERE Name = ?");

问号?是值的占位符。 它将稍后添加到脚本中。

$sth->execute($name);

execute()方法中,我们将值绑定到占位符。

以下示例与上一个示例相同; 这次我们使用bind_param()方法。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $name = "Volkswagen"; 

my $sth = $dbh->prepare("SELECT * FROM Cars WHERE Name = ?");
$sth->bind_param(1, $name);
$sth->execute();

my ($id, $name, $price) = $sth->fetchrow();
print "$id $name $price\n";

$sth->finish();
$dbh->disconnect();

使用bind_param()方法检索带有参数绑定的行。

$sth->bind_param(1, $name);

bind_param()方法采用一个值,并将其与 SQL 语句内的占位符关联。 可以有更多的占位符。 占位符从 1 开始编号。

quote参数

使用占位符并将参数绑定到它们是处理动态 SQL 语句构建的最佳方法。 有时不能使用占位符。 例如,当我们要动态选择一个表名时。 在这种情况下,我们可以连接 SQL 字符串并使用quote()quote_identifier()方法。 我们必须对变量使用这些方法,否则会引入严重的安全性错误。

quote()方法引用字符串字面值,以用作 SQL 语句中的字面值。 它转义包含在字符串中的任何特殊字符(例如引号),并添加所需类型的外部引号。 quote_identifier()方法引用在 SQL 语句中使用的标识符(表名等)。 它转义包含的任何特殊字符(例如双引号),并添加所需类型的外部引号。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $table = "Cars";
my $name = "Volkswagen";

my $sql = sprintf "SELECT * FROM %s WHERE Name = %s", 
    $dbh->quote_identifier($table), $dbh->quote($name);

my $sth = $dbh->prepare($sql);
$sth->execute();

my @row;
while (@row = $sth->fetchrow_array()) {
    print "@row\n";
}

$sth->finish();
$dbh->disconnect();

在该示例中,我们使用quote()quote_identifier()方法动态构建 SQL 语句字符串。

my $table = "Cars";
my $name = "Volkswagen";

这些是在 SQL 语句中使用的 Perl 标量。

my $sql = sprintf "SELECT * FROM %s WHERE Name = %s", 
    $dbh->quote_identifier($table), $dbh->quote($name);

我们有一个更复杂的 SQL 语句。 无法使用占位符构建此语句。 我们使用quote方法来引用提供的标量。

绑定列

使用获取方法时,我们将返回的值复制到 Perl 变量中。 通过绑定列可以简化此过程并使其更快。 Perl DBI 具有bind_col()bind_columns()方法,它们将 Perl 变量与表列相关联。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $sth = $dbh->prepare( "SELECT * FROM Cars LIMIT 4" );  
$sth->execute();

$sth->bind_columns(\my($id, $name, $price));

while ($sth->fetchrow_arrayref()) {
    print "$id $name $price\n";
}

$sth->finish();
$dbh->disconnect();

在示例中,我们将Cars表的三列绑定到$id$name$price变量。

$sth->bind_columns(\my($id, $name, $price));

我们使用bind_columns()方法将变量绑定到Cars表的列。

while ($sth->fetchrow_arrayref()) {
    print "$id $name $price\n";
}

我们遍历返回的数据并将值打印到控制台。

在 MySQL Perl 教程的这一部分中,我们讨论了绑定参数。

在 MySQL 中使用 Perl 处理图像

原文: http://zetcode.com/db/mysqlpeimg/

在 MySQL Perl 教程的这一章中,我们将使用图像文件。 请注意,有些人反对将图像放入数据库。 在这里,我们只展示如何做。 我们不讨论是否将图像保存在数据库中的技术问题。

mysql> CREATE TABLE Images(Id INT PRIMARY KEY, Data MEDIUMBLOB);

对于此示例,我们创建一个名为Images的新表。 对于图像,我们使用 MySQL MEDIUMBLOB数据类型,该数据类型存储二进制对象。

插入图像

在第一个示例中,我们将图像插入 MySQL 数据库。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

open IMAGE, "woman.jpg" or die $!;

my ($image, $buff);
while(read IMAGE, $buff, 1024) {
    $image .= $buff;
}

my $stm = $dbh->prepare("INSERT INTO Images(Id, Data) VALUES (1, ?)");
$stm->bind_param(1, $image, DBI::SQL_BLOB);
$stm->execute();

close(IMAGE);
$stm->finish();
$dbh->disconnect();

我们从当前工作目录中读取图像,并将其写入 MySQL mydb数据库的Images表中。

open IMAGE, "woman.jpg" or die $!;

我们打开一个图像。 这是称为woman.jpg的 JPG 图像。

my ($image, $buff);
while(read IMAGE, $buff, 1024) {
    $image .= $buff;
}

我们从图像文件读取二进制数据。

my $stm = $dbh->prepare("INSERT INTO Images(Id, Data) VALUES (1, ?)");
$stm->bind_param(1, $image, DBI::SQL_BLOB);
$stm->execute();

三行代码准备 SQL 语句,将图像数据绑定到该语句并执行它。

close(IMAGE);
$sth->finish();
$dbh->disconnect();

最后,我们正在释放资源。

读取图像

在本节中,我们将执行相反的操作。 我们将从数据库表中读取图像。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $stm = $dbh->prepare("SELECT Data FROM Images WHERE Id=1");
$stm->execute();
my $image = $stm->fetch();

open IMAGE, ">woman2.jpg" or die $!;
print IMAGE @$image;
close(IMAGE);

$stm->finish();
$dbh->disconnect();

我们从Images表中读取图像数据,并将其写入另一个文件woman2.jpg中。

my $sth = $dbh->prepare("SELECT Data FROM Images WHERE Id=1");
$sth->execute();
my $image = $sth->fetch();

这三行从表中选择图像数据。

open IMAGE, ">woman2.jpg" or die $!;
print IMAGE @$image;
close(IMAGE);

我们打开一个新的图像文件,并将检索到的数据写入该文件。 然后我们关闭文件。

MySQL Perl 教程的这一部分专门用于读取和写入图像。

使用 Perl 获取 MySQL 元数据

原文: http://zetcode.com/db/mysqlperl/meta/

元数据是有关数据库中数据的信息。 MySQL 中的元数据包含有关存储数据的表和列的信息。 受 SQL 语句影响的行数是元数据。 结果集中返回的行数和列数也属于元数据。

方法名称 描述
column_info() 提供有关列的信息
table_info() 提供有关表的信息
primary_key_info() 提供有关表中主键的信息
foreign_key_info() 提供有关表中外键的信息

上表列出了四种用于检索元数据的 Perl DBI 方法。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $sth = $dbh->primary_key_info(undef, "mydb", "Cars");
my @ary = $sth->fetchrow_array();

print join(" ", @ary), "\n";

$sth->finish();
$dbh->disconnect();

在第一个示例中,我们将在Cars表中找到有关主键的信息。

my $sth = $dbh->primary_key_info(undef, "main", "Cars");

primary_key_info()返回一个活动语句句柄,该句柄可用于获取有关构成表主键的列的信息。

my @ary = $sth->fetchrow_array();

从语句句柄,我们检索信息。

$ ./pk_info.pl
 mydb Cars Id 1 PRIMARY

从输出中我们可以看到Cars表中有一个主键。 主键是第一列,名为Id

接下来,我们将打印Cars表中的所有行及其列名。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my $sth = $dbh->prepare( "SELECT * FROM Cars LIMIT 8" );  
$sth->execute();

my $headers = $sth->{NAME};

my ($id, $name, $price) = @$headers;
printf  "%s %-10s %s\n", $id, $name, $price;

my $row;
while($row = $sth->fetchrow_hashref()) {
    printf "%2d %-10s %d\n", $row->{Id}, $row->{Name}, $row->{Price};
}

$sth->finish();
$dbh->disconnect();

我们将Cars表的内容打印到控制台。 现在,我们也包括列的名称。 记录与列名对齐。

my $headers = $sth->{NAME};

我们从语句对象获得列名。

my ($id, $name, $price) = @$headers;
printf "%s %-10s %s\n", $id, $name, $price;

列名将打印到控制台。 我们使用printf函数应用某些格式。

my $row;
while($row = $sth->fetchrow_hashref()) {
    printf "%2d %-10s %d\n", $row->{Id}, $row->{Name}, $row->{Price};
}

数据被检索,格式化并打印到终端。

$ ./column_names.pl
Id Name       Price
 1 Audi       52642
 2 Mercedes   57127
 3 Skoda      9000
 4 Volvo      29000
 5 Bentley    350000
 6 Citroen    21000
 7 Hummer     41400
 8 Volkswagen 21601

column_names.pl脚本的输出。

在与元数据有关的最后一个示例中,我们将列出test.db数据库中的所有表。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1 },         
) or die $DBI::errstr;

my @tables = $dbh->tables(); 

foreach my $table ( @tables ) {
     print "Table: $table\n"; 
}

$dbh->disconnect();

该代码示例将当前数据库中的所有可用表打印到终端。

my @tables = $dbh->tables();

表名使用tables()方法检索。

$ ./list_tables.pl
Table: `mydb`.`Cars`
Table: `mydb`.`Friends`
Table: `mydb`.`Images`

这些是我们系统上的表。

在 MySQL Perl 教程的这一部分中,我们使用了数据库元数据。

MySQL 简介

原文: http://zetcode.com/databases/mysqltutorial/introduction/

这是 MySQL 教程。 它涵盖 MySQL 数据库,各种 mysql 命令行工具以及数据库引擎涵盖的 SQL 语言。 它是初学者的入门教程。

MySQL 数据库

MySQL 是领先的开源数据库管理系统。 它是一个多用户,多线程的数据库管理系统。 MySQL 在网络上特别流行。 它是非常流行的 LAMP 平台的组成部分之一。 Linux,Apache,MySQL 和 PHP。 MySQL 数据库在最重要的 OS 平台上可用。 它可以在 BSD Unix,Linux,Windows 或 Mac 上运行。 维基百科,YouTube,Facebook 使用 MySQL。 这些站点每天管理数百万个查询。 MySQL 有两个版本:MySQL 服务器系统和 MySQL 嵌入式系统。 MySQL 服务器软件和客户端库具有双重许可:GPL 版本 2 和专有许可。

MySQL 的开发始于 1994 年,瑞典的 MySQL AB 公司。 Sun Microsystems 在 2008 年收购了 MySQLAB。Sun 在 2010 年被 Oracle 收购。

MySQL,PostgreSQL,Firebird,SQLite,Derby 和 HSQLDB 是最著名的开源数据库系统。

MySQL 是用 C/C++ 开发的。 除 C/C++ 外,还存在适用于 PHP,Python,Java,C# ,Eiffel,Ruby,Tcl 或 Perl 的 API。

MariaDB

MariaDB 是 MySQL 的社区开发分支,旨在在 GNU GPL 下保持免费。 它是由 MySQL 的原始开发者领导的,由于担心它被 Oracle 收购,他们分叉了它。 MariaDB 打算保持与 MySQL 的高度兼容性,以库二进制等效性和与 MySQL API 和命令的精确匹配来确保“嵌入式”替换功能。

定义

关系数据库是表中组织的数据的集合。 表之间存在关系。 这些表是正式描述的。 它们由行和列组成。 SQL(结构化查询语言)是一种数据库计算机语言,旨在管理关系数据库管理系统中的数据。 表是使用垂直列和水平行的模型组织的一组值。 列由其名称标识。 数据库系统的模式是用正式语言描述的结构。 它定义了表,字段,关系,视图,索引,过程,函数,队列,触发器和其他元素。

数据库的行代表表中的单个隐式结构化数据项。 它也称为元组或记录。 列是一组特定简单类型的数据值,该数据值对应于表的每一行。 列提供了构成行所依据的结构。 字段是单个项目,存在于一行和一列之间的交点处。 主键唯一标识表中的每个记录。 外键是两个表之间的引用约束。 外键标识一个(引用)表中的一列或一组列,该列或表引用另一(引用)表中的一列或一组列。

触发器是响应于数据库中特定表上的某些事件而自动执行的过程代码。 视图是对来自一个或多个表的数据的特定外观。 它可以按特定顺序排列数据,突出显示或隐藏某些数据。 视图由存储的查询组成,该查询可作为由查询结果集组成的虚拟表访问。 与普通表不同,视图不构成物理模式的一部分。 它是根据数据库中的数据计算或整理的动态虚拟表。

事务是针对一个或多个数据库中数据的数据库操作的基本单位。 事务中所有 SQL 语句的影响可以全部提交给数据库,也可以全部回滚。 SQL 结果集是数据库中由SELECT语句返回的一组行。 它还包含有关查询的元信息,例如列名以及每列的类型和大小。 索引是一种数据结构,可提高对数据库表的数据检索操作的速度。

使用的表

在这里,我们将列出整个教程中使用的所有表。

cars.sql

-- SQL for the Cars table

USE mydb;
CREATE TABLE IF NOT EXISTS Cars(Id INTEGER PRIMARY KEY, Name VARCHAR(50), 
Cost INTEGER);
INSERT INTO Cars VALUES(1,'Audi',52642);
INSERT INTO Cars VALUES(2,'Mercedes',57127);
INSERT INTO Cars VALUES(3,'Skoda',9000);
INSERT INTO Cars VALUES(4,'Volvo',29000);
INSERT INTO Cars VALUES(5,'Bentley',350000);
INSERT INTO Cars VALUES(6,'Citroen',21000);
INSERT INTO Cars VALUES(7,'Hummer',41400);
INSERT INTO Cars VALUES(8,'Volkswagen',21600);

这是一个Cars表。

customers_reservations.sql

-- SQL for the Customers, Reservations tables

USE mydb;

CREATE TABLE IF NOT EXISTS Customers(CustomerId INTEGER AUTO_INCREMENT 
    PRIMARY KEY, Name VARCHAR(55));
INSERT INTO Customers(Name) VALUES('Paul Novak');
INSERT INTO Customers(Name) VALUES('Terry Neils');
INSERT INTO Customers(Name) VALUES('Jack Fonda');
INSERT INTO Customers(Name) VALUES('Tom Willis');

CREATE TABLE IF NOT EXISTS Reservations(Id INTEGER AUTO_INCREMENT
    PRIMARY KEY, CustomerId INTEGER, Day DATE);
INSERT INTO Reservations(CustomerId, Day) VALUES(1, '2009-11-22');
INSERT INTO Reservations(CustomerId, Day) VALUES(2, '2009-11-28');
INSERT INTO Reservations(CustomerId, Day) VALUES(2, '2009-11-29');
INSERT INTO Reservations(CustomerId, Day) VALUES(1, '2009-11-29');
INSERT INTO Reservations(CustomerId, Day) VALUES(3, '2009-12-2');

这些是CustomersReservations表。

books.sql

-- SQL for the Books table

USE mydb;

CREATE TABLE IF NOT EXISTS Books(Id INTEGER PRIMARY KEY, 
    Title VARCHAR(100), Author VARCHAR(60));
INSERT INTO Books VALUES(1,'War and Peace','Leo Tolstoy');
INSERT INTO Books VALUES(2,'The Brothers Karamazov','Fyodor Dostoyevsky');
INSERT INTO Books VALUES(3,'Paradise Lost','John Milton');
INSERT INTO Books VALUES(4,'Crime and Punishment','Fyodor Dostoyevsky');
INSERT INTO Books VALUES(5,'Cousin Bette','Honore de Balzac');

这是一个Books表。

数据来源

这是对 MySQL 数据库系统的介绍。

Perl 的 MySQL 事务

原文: http://zetcode.com/db/mysqlperl/trans/

在本章中,我们将处理事务。 首先,我们提供一些基本定义。 然后,我们介绍 Perl 脚本,该脚本显示如何在 Perl DBI 中处理事务。 我们还将讨论自动提交模式,这对于理解事务是必不可少的。

定义

事务是针对一个或多个数据库中数据的数据库操作的基本单位。 事务中所有 SQL 语句的影响可以全部提交给数据库,也可以全部回滚。 在自动提交模式中,更改立即生效。 要处理事务,我们要么关闭自动提交模式,要么使用begin_work()方法启动事务。 事务以commit()rollback()方法结束。

MySQL 数据库具有不同类型的存储引擎。 最常见的是 MyISAM 和 InnoDB 引擎。 在数据安全性和数据库速度之间需要权衡。 MyISAM 表的处理速度更快,并且不支持事务。 另一方面,InnoDB 表可以更安全地防止数据丢失。 他们支持事务。 它们处理较慢。

默认情况下,数据库连接处于自动提交模式。 AutoCommit数据库句柄属性用于设置或读取自动提交模式。

AutoCommit打开时,对begin_work()的调用将AutoCommit关闭。 commit()rollback()方法重新打开AutoCommit。 如果关闭AutoCommit属性,然后再调用begin_work()方法,则会收到一条错误消息,提示我们已经在事务中。

例子

现在,我们将有一些可用于事务处理的脚本。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1, AutoCommit => 0 },        
) or die $DBI::errstr;

$dbh->do("DROP TABLE IF EXISTS Friends");
$dbh->do("CREATE TABLE Friends(Id INTEGER PRIMARY KEY AUTO_INCREMENT, 
    Name TEXT) ENGINE=InnoDB");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Tom')");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Rebecca')");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Jim')");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Robert')");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Julian')");

$dbh->disconnect();

我们创建一个Friends表,并尝试用数据填充它。 但是,正如我们将看到的,数据不会被提交。

{ RaiseError => 1, AutoCommit => 0 }, 

我们已经将AutoCommit参数设置为 0。更改不会自动提交。 而且没有提交语句。 因此,更改不会写入数据库。

$dbh->do("CREATE TABLE Friends(Id INTEGER PRIMARY KEY AUTO_INCREMENT, 
    Name TEXT) ENGINE=InnoDB");

这是创建Friends表的 SQL 语句。 我们已经指定了 InnoDB 引擎。 请注意,自 MySQL 5.5 起,默认引擎为 InnoDB。 在 MyISAM 表中,SQL 语句在执行后被提交。 MyISAM 表不支持事务。

$ ./noautocommit.pl

mysql> SELECT * FROM Friends;
Empty set (0.00 sec)

表已创建,但数据未插入表中。

在第二个示例中,我们将使用commit()方法将数据写入数据库。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1, AutoCommit => 0 },        
) or die $DBI::errstr;

$dbh->do("DROP TABLE IF EXISTS Friends");
$dbh->do("CREATE TABLE Friends(Id INTEGER PRIMARY KEY AUTO_INCREMENT, 
    Name TEXT) ENGINE=InnoDB");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Tom')");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Rebecca')");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Jim')");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Robert')");
$dbh->do("INSERT INTO Friends(Name) VALUES ('Julian')");

$dbh->commit();

$dbh->disconnect();

当关闭自动提交模式时,每个语句都在事务内,直到我们调用commit()方法为止。

$dbh->commit();

所有更改都将写入数据库。

mysql> SELECT * FROM Friends;
+----+---------+
| Id | Name    |
+----+---------+
|  1 | Tom     |
|  2 | Rebecca |
|  3 | Jim     |
|  4 | Robert  |
|  5 | Julian  |
+----+---------+
5 rows in set (0.00 sec)

我们使用sqlite3命令行工具验证是否已写入更改。

当事务中存在错误时,将回滚事务,并且不会将任何更改提交到数据库。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1, AutoCommit => 0},
) or die $DBI::errstr;

$dbh->do("UPDATE Friends SET Name='Thomas' WHERE Id=1");
$dbh->do("UPDATE Friend SET Name='Bob' WHERE Id=4");

$dbh->commit();

$dbh->disconnect();

在代码示例中,自动提交已关闭。 有两个语句构成一个事务。 第二个 SQL 语句中有错误。 因此,该事务将回滚。

$dbh->do("UPDATE Friend SET Name='Bob' WHERE Id=4");

表名称不正确。 数据库中没有Friend表。

$ ./rollingback.pl
DBD::mysql::db do failed: Table 'mydb.Friend' doesn't exist at ./rollingback.pl line 14.
DBD::mysql::db do failed: Table 'mydb.Friend' doesn't exist at ./rollingback.pl line 14.
Issuing rollback() due to DESTROY without explicit disconnect() of
DBD::mysql::db handle dbname=mydb at ./rollingback.pl line 14.

运行示例将显示此错误消息。 事务回滚。

mysql> SELECT * FROM Friends;
+----+---------+
| Id | Name    |
+----+---------+
|  1 | Tom     |
|  2 | Rebecca |
|  3 | Jim     |
|  4 | Robert  |
|  5 | Julian  |
+----+---------+
5 rows in set (0.00 sec)

即使第一个UPDATE语句正确,在Friends表中也没有发生任何变化。

正如我们在教程中已经提到的那样,默认模式是自动提交。 在这种模式下,我们可以使用begin_work()方法开始新的事务,并使用commit()rollback()完成它。 begin_work()方法将关闭自动提交,commit()rollback()方法将重新打开自动提交。

#!/usr/bin/perl

use strict;
use DBI;

my $dbh = DBI->connect(          
    "dbi:mysql:dbname=mydb", 
    "user12",                          
    "34klq*",                          
    { RaiseError => 1, HandleError=>\&handle_error },
) or die $DBI::errstr;

$dbh->begin_work();

$dbh->do("UPDATE Friends SET Name='Thomas' WHERE Id=1");
$dbh->do("UPDATE Friend SET Name='Bob' WHERE Id=4");

$dbh->commit();

$dbh->do("INSERT INTO Friends(Name) VALUES('Ronald')");

$dbh->disconnect();

sub handle_error {

    my $error = shift;
    print "An error occurred in the script\n";
    print "Message: $error\n";
    return 1;
}

再次,我们有一个不正确的第二条 SQL 语句。 这次,我们没有明确关闭自动提交。

{ RaiseError => 1, HandleError=>\&handle_error },  

我们将把错误处理委托给handle_error()子例程。

$dbh->begin_work();

使用begin_work()方法,我们开始一个新事务。 自动提交已关闭。

$dbh->do("UPDATE Friends SET Name='Thomas' WHERE Id=1");
$dbh->do("UPDATE Friend SET Name='Bob' WHERE Id=4");

这两个语句构成一个事务。 第二个是不正确的。

sub handle_error {

    my $error = shift;
    print "An error occurred in the script\n";
    print "Message: $error\n";
    return 1;
}

当我们遇到错误时,将调用此子例程。 我们打印一条错误消息。 请注意,该脚本不会退出。

$dbh->do("INSERT INTO Friends(Name) VALUES('Ronald')");

事务已回滚,我们没有退出脚本。 它继续。 回滚后,自动提交已重新打开。 新行已添加到Friends表中。

$ ./rollingback2.pl
An error occurred in the script
Message: DBD::mysql::db do failed: Table 'mydb.Friend' doesn't exist

我们可以从handle_error()子例程中看到我们的自定义错误消息。

mysql> SELECT * FROM Friends;
+----+---------+
| Id | Name    |
+----+---------+
|  1 | Thomas  |
|  2 | Rebecca |
|  3 | Jim     |
|  4 | Robert  |
|  5 | Julian  |
|  6 | Ronald  |
+----+---------+
6 rows in set (0.00 sec)

一个新朋友被插入了表。

在 MySQL Perl 教程的这一部分中,我们处理了事务。

MySQL C API 编程教程

原文: http://zetcode.com/db/mysqlc/

这是针对 MySQL 数据库的 C 编程教程。 它涵盖了使用 C API 进行 MySQL 编程的基础。 您还可以考虑查看有关 ZetCode 的 MySQL 教程

Tweet

关于 MySQL 数据库

MySQL 是领先的开源数据库管理系统。 它是一个多用户,多线程的数据库管理系统。 MySQL 在网络上特别流行。 它是由 Linux,Apache,MySQL 和 PHP 组成的非常流行的 LAMP 平台的一部分。 Oracle 当前拥有 MySQL。 MySQL 数据库在最重要的 OS 平台上可用。 它可以在 BSD Unix,Linux,Windows 或 Mac OS 上运行。 维基百科和 YouTube 使用 MySQL。 这些站点每天管理数百万个查询。 MySQL 有两个版本:MySQL 服务器系统和 MySQL 嵌入式系统。

$ sudo apt-get install libmysqlclient-dev

为了能够编译 C 示例,我们需要安装 MySQL C 开发库。 上一行显示了我们如何在基于 Debian 的 Linux 上做到这一点。

C99

本教程使用 C99。 对于 GNU C 编译器,我们需要添加-std = c99 选项。 对于 Windows 用户,强烈建议使用 Pelles C IDE。 (MSVC 不支持 C99。)

MYSQL *con = mysql_init(NULL);

在 C99 中,我们可以将声明与代码混合使用。 在较早的 C 程序中,我们需要将这一行分成两行。

第一个例子

我们的第一个示例将测试一个 MySQL 函数调用。

#include <my_global.h>
#include <mysql.h>

int main(int argc, char **argv)
{
  printf("MySQL client version: %s\n", mysql_get_client_info());

  exit(0);
}

mysql_get_client_info()显示 MySQL 客户端版本。

#include <my_global.h>
#include <mysql.h>

我们包括必要的头文件。 mysql.h是 MySQL 函数调用的最重要的头文件。 my_global.h包含一些全局函数声明。 除其他外,它包括标准输入/输出头文件。

printf("MySQL client version: %s\n", mysql_get_client_info());

此代码行输出 MySQL 客户端的版本。 为此,我们使用mysql_get_client_info()函数调用。

exit(0);

我们从脚本退​​出。

$ gcc version.c -o version  `mysql_config --cflags --libs`

这是我们编译代码示例的方式。

$ ./version 
MySQL client version: 5.1.67

示例输出。

建立数据库

下一个代码示例将创建一个数据库。 该代码示例可以分为以下几部分:

  • 创建连接句柄结构
  • 建立连接
  • 执行查询
  • 关闭连接
#include <my_global.h>
#include <mysql.h>

int main(int argc, char **argv)
{  
  MYSQL *con = mysql_init(NULL);

  if (con == NULL) 
  {
      fprintf(stderr, "%s\n", mysql_error(con));
      exit(1);
  }

  if (mysql_real_connect(con, "localhost", "root", "root_pswd", 
          NULL, 0, NULL, 0) == NULL) 
  {
      fprintf(stderr, "%s\n", mysql_error(con));
      mysql_close(con);
      exit(1);
  }  

  if (mysql_query(con, "CREATE DATABASE testdb")) 
  {
      fprintf(stderr, "%s\n", mysql_error(con));
      mysql_close(con);
      exit(1);
  }

  mysql_close(con);
  exit(0);
}

该代码示例连接到 MySQL 数据库系统,并创建一个名为testdb的新数据库。

MYSQL *con = mysql_init(NULL);

mysql_init()函数分配或初始化适合mysql_real_connect()函数的 MYSQL 对象。 记住这是 C99。

if (con == NULL) 
{
    fprintf(stderr, "%s\n", mysql_error(con));
    exit(1);
}

我们检查返回值。 如果mysql_init()函数失败,我们将打印错误消息并终止应用。

if (mysql_real_connect(con, "localhost", "root", "root_pswd", 
        NULL, 0, NULL, 0) == NULL) 
{
    fprintf(stderr, "%s\n", mysql_error(con));
    mysql_close(con);
    exit(1);
} 

mysql_real_connect()函数建立与数据库的连接。 我们为该函数提供连接处理器,主机名,用户名和密码参数。 其他四个参数是数据库名称,端口号,unix 套接字以及最后的客户端标志。 我们需要超级用户特权才能创建新数据库。

if (mysql_query(con, "CREATE DATABASE testdb")) 
{
    fprintf(stderr, "%s\n", mysql_error(con));
    mysql_close(con);
    exit(1);
}

mysql_query()执行 SQL 语句。 在我们的例子中,该语句创建一个新数据库。

mysql_close(con);

最后,我们关闭数据库连接。

$ gcc createdb.c -o createdb -std=c99  `mysql_config --cflags --libs`

第二个示例已经利用了 C99 标准的功能。 因此,我们需要添加-std=c99选项。

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| testdb             |
+--------------------+
3 rows in set (0.00 sec)

这是数据库已创建的证明。

创建并填充表

在创建新表之前,我们将创建一个将在本教程其余部分中使用的用户。

mysql> CREATE USER user12@localhost IDENTIFIED BY '34klq*';

我们创建了一个新用户user12

mysql> GRANT ALL ON testdb.* to user12@localhost;

在这里,我们将所有特权授予testdb数据库上的user12

下一个代码示例将创建一个表并将一些数据插入其中。

#include <my_global.h>
#include <mysql.h>

void finish_with_error(MYSQL *con)
{
  fprintf(stderr, "%s\n", mysql_error(con));
  mysql_close(con);
  exit(1);        
}

int main(int argc, char **argv)
{
  MYSQL *con = mysql_init(NULL);

  if (con == NULL) 
  {
      fprintf(stderr, "%s\n", mysql_error(con));
      exit(1);
  }  

  if (mysql_real_connect(con, "localhost", "user12", "34klq*", 
          "testdb", 0, NULL, 0) == NULL) 
  {
      finish_with_error(con);
  }    

  if (mysql_query(con, "DROP TABLE IF EXISTS Cars")) {
      finish_with_error(con);
  }

  if (mysql_query(con, "CREATE TABLE Cars(Id INT, Name TEXT, Price INT)")) {      
      finish_with_error(con);
  }

  if (mysql_query(con, "INSERT INTO Cars VALUES(1,'Audi',52642)")) {
      finish_with_error(con);
  }

  if (mysql_query(con, "INSERT INTO Cars VALUES(2,'Mercedes',57127)")) {
      finish_with_error(con);
  }

  if (mysql_query(con, "INSERT INTO Cars VALUES(3,'Skoda',9000)")) {
      finish_with_error(con);
  }

  if (mysql_query(con, "INSERT INTO Cars VALUES(4,'Volvo',29000)")) {
      finish_with_error(con);
  }

  if (mysql_query(con, "INSERT INTO Cars VALUES(5,'Bentley',350000)")) {
      finish_with_error(con);
  }

  if (mysql_query(con, "INSERT INTO Cars VALUES(6,'Citroen',21000)")) {
      finish_with_error(con);
  }

  if (mysql_query(con, "INSERT INTO Cars VALUES(7,'Hummer',41400)")) {
      finish_with_error(con);
  }

  if (mysql_query(con, "INSERT INTO Cars VALUES(8,'Volkswagen',21600)")) {
      finish_with_error(con);
  }

  mysql_close(con);
  exit(0);
}

我们在这里不使用任何新的 MySQL 函数调用。 我们使用mysql_query()函数调用来创建表并将数据插入其中。

void finish_with_error(MYSQL *con)
{
  fprintf(stderr, "%s\n", mysql_error(con));
  mysql_close(con);
  exit(1);        
}

为了避免不必要的重复,我们创建了一个自定义finish_with_error()函数。

if (mysql_real_connect(con, "localhost", "user12", "34klq*", 
        "testdb", 0, NULL, 0) == NULL) 
{
    finish_with_error(con);
}   

我们连接到testdb数据库。 用户名为user12,密码为34klq*。 第五个参数是数据库名称。

if (mysql_query(con, "CREATE TABLE Cars(Id INT, Name TEXT, Price INT)")) {      
    finish_with_error(con);
}

在这里,我们创建一个名为Cars的表。 它具有三列。

if (mysql_query(con, "INSERT INTO Cars VALUES(1,'Audi',52642)")) {
    finish_with_error(con);
}

我们在Cars表中插入一行。

mysql> USE testdb;
mysql> SHOW TABLES;
+------------------+
| Tables_in_testdb |
+------------------+
| Cars             |
+------------------+
1 row in set (0.00 sec)

我们显示数据库中的表。

mysql> SELECT * FROM Cars;
+------+------------+--------+
| Id   | Name       | Price  |
+------+------------+--------+
|    1 | Audi       |  52642 |
|    2 | Mercedes   |  57127 |
|    3 | Skoda      |   9000 |
|    4 | Volvo      |  29000 |
|    5 | Bentley    | 350000 |
|    6 | Citroen    |  21000 |
|    7 | Hummer     |  41400 |
|    8 | Volkswagen |  21600 |
+------+------------+--------+
8 rows in set (0.00 sec)

我们从表中选择所有数据。

从数据库中检索数据

在下一个示例中,我们将从表中检索数据。

我们需要执行以下步骤:

  • 建立连接
  • 执行查询
  • 获取结果集
  • 获取所有可用行
  • 释放结果集
#include <my_global.h>
#include <mysql.h>

void finish_with_error(MYSQL *con)
{
  fprintf(stderr, "%s\n", mysql_error(con));
  mysql_close(con);
  exit(1);        
}

int main(int argc, char **argv)
{      
  MYSQL *con = mysql_init(NULL);

  if (con == NULL)
  {
      fprintf(stderr, "mysql_init() failed\n");
      exit(1);
  }  

  if (mysql_real_connect(con, "localhost", "user12", "34klq*", 
          "testdb", 0, NULL, 0) == NULL) 
  {
      finish_with_error(con);
  }    

  if (mysql_query(con, "SELECT * FROM Cars")) 
  {
      finish_with_error(con);
  }

  MYSQL_RES *result = mysql_store_result(con);

  if (result == NULL) 
  {
      finish_with_error(con);
  }

  int num_fields = mysql_num_fields(result);

  MYSQL_ROW row;

  while ((row = mysql_fetch_row(result))) 
  { 
      for(int i = 0; i < num_fields; i++) 
      { 
          printf("%s ", row[i] ? row[i] : "NULL"); 
      } 
          printf("\n"); 
  }

  mysql_free_result(result);
  mysql_close(con);

  exit(0);
}

该示例打印Cars表中的所有列。

if (mysql_query(con, "SELECT * FROM Cars")) 
{
    finish_with_error(con);
}

我们执行查询,该查询将从 Cars 表中检索所有数据。

MYSQL_RES *result = mysql_store_result(con);

我们使用mysql_store_result()函数获得结果集。 MYSQL_RES是用于保存结果集的结构。

int num_fields = mysql_num_fields(result);

我们获得表中的字段(列)数。

MYSQL_ROW row;

while ((row = mysql_fetch_row(result))) 
{ 
    for(int i = 0; i < num_fields; i++) 
    { 
        printf("%s ", row[i] ? row[i] : "NULL"); 
    } 
        printf("\n"); 
}

我们获取行并将其打印到屏幕上。

mysql_free_result(result);
mysql_close(con);

我们释放资源。

$ ./retrieva_data 
1 Audi 52642 
2 Mercedes 57127 
3 Skoda 9000 
4 Volvo 29000 
5 Bentley 350000 
6 Citroen 21000 
7 Hummer 41400 
8 Volkswagen 21600 

示例输出。

最后插入的行 ID

有时,我们需要确定最后插入的行的 ID。 我们可以通过调用mysql_insert_id()函数来确定最后插入的行 ID。 仅当我们在表中定义了AUTO_INCREMENT列时,该函数才起作用。

#include <my_global.h>
#include <mysql.h>

void finish_with_error(MYSQL *con)
{
  fprintf(stderr, "%s\n", mysql_error(con));
  mysql_close(con);
  exit(1);        
}

int main(int argc, char **argv)
{

  MYSQL *con = mysql_init(NULL);

  if (con == NULL)
  {
      fprintf(stderr, "mysql_init() failed\n");
      exit(1);
  }

  if (mysql_real_connect(con, "localhost", "user12", "34klq*", 
          "testdb", 0, NULL, 0) == NULL) 
  {
      finish_with_error(con);
  }   

  if (mysql_query(con, "DROP TABLE IF EXISTS Writers"))
  {    
      finish_with_error(con);    
  }

  char *sql = "CREATE TABLE Writers(Id INT PRIMARY KEY AUTO_INCREMENT, Name TEXT)";

  if (mysql_query(con, sql))
  {    
      finish_with_error(con);    
  }

  if (mysql_query(con, "INSERT INTO Writers(Name) VALUES('Leo Tolstoy')"))
  {    
      finish_with_error(con);    
  }

  if (mysql_query(con, "INSERT INTO Writers(Name) VALUES('Jack London')"))
  {    
      finish_with_error(con);
  }

  if (mysql_query(con, "INSERT INTO Writers(Name) VALUES('Honore de Balzac')"))
  {    
      finish_with_error(con);
  }

  int id = mysql_insert_id(con);

  printf("The last inserted row id is: %d\n", id);

  mysql_close(con);
  exit(0);
}

创建一个新表。 三行插入到表中。 我们确定最后插入的行 ID。

char *sql = "CREATE TABLE Writers(Id INT PRIMARY KEY AUTO_INCREMENT, Name TEXT)";

Id列具有AUTO_INCREMENT类型。

int id = mysql_insert_id(con);

mysql_insert_id()函数返回由先前的INSERTUPDATE语句为AUTO_INCREMENT列生成的值。

$ ./last_row_id 
The last inserted row id is: 3

输出。

列标题

在下一个示例中,我们将从表及其列名称中检索数据。

#include <my_global.h>
#include <mysql.h>

void finish_with_error(MYSQL *con)
{
  fprintf(stderr, "%s\n", mysql_error(con));
  mysql_close(con);
  exit(1);        
}

int main(int argc, char **argv)
{
  MYSQL *con = mysql_init(NULL);

  if (con == NULL)
  {
      fprintf(stderr, "mysql_init() failed\n");
      exit(1);
  }  

  if (mysql_real_connect(con, "localhost", "user12", "34klq*", 
          "testdb", 0, NULL, 0) == NULL) 
  {
      finish_with_error(con);
  } 

  if (mysql_query(con, "SELECT * FROM Cars LIMIT 3"))
  {  
      finish_with_error(con);
  }

  MYSQL_RES *result = mysql_store_result(con);

  if (result == NULL) 
  {
      finish_with_error(con);
  }  

  int num_fields = mysql_num_fields(result);

  MYSQL_ROW row;
  MYSQL_FIELD *field;

  while ((row = mysql_fetch_row(result))) 
  { 
      for(int i = 0; i < num_fields; i++) 
      { 
          if (i == 0) 
          {              
             while(field = mysql_fetch_field(result)) 
             {
                printf("%s ", field->name);
             }

             printf("\n");           
          }

          printf("%s  ", row[i] ? row[i] : "NULL"); 
      } 
  }

  printf("\n");

  mysql_free_result(result);
  mysql_close(con);

  exit(0);
}

我们从Cars表中打印前三行。 我们还包括列标题。

MYSQL_FIELD *field;

MYSQL_FIELD结构包含有关字段的信息,例如字段的名称,类型和大小。 字段值不属于此结构; 它们包含在MYSQL_ROW结构中。

if (i == 0) 
{              
    while(field = mysql_fetch_field(result)) 
    {
        printf("%s ", field->name);
    }

    printf("\n");           
}

第一行包含列标题。 mysql_fetch_field()调用返回MYSQL_FIELD结构。 我们从该结构中获取列标题名称。

$ ./headers 
Id Name Price 
1  Audi  52642  
2  Mercedes  57127  
3  Skoda  9000 

这是我们程序的输出。

多个语句

可以在一个查询中执行多个 SQL 语句。 我们必须在connect方法中设置CLIENT_MULTI_STATEMENTS标志。

#include <my_global.h>
#include <mysql.h>

void finish_with_error(MYSQL *con)
{
  fprintf(stderr, "%s\n", mysql_error(con));
  mysql_close(con);
  exit(1);        
}

int main(int argc, char **argv)
{ 
  int status = 0;  

  MYSQL *con = mysql_init(NULL);  

  if (con == NULL)
  {
      fprintf(stderr, "mysql_init() failed\n");
      exit(1);
  }  

  if (mysql_real_connect(con, "localhost", "user12", "34klq*", 
          "testdb", 0, NULL, CLIENT_MULTI_STATEMENTS) == NULL) 
  {
      finish_with_error(con);
  }    

  if (mysql_query(con, "SELECT Name FROM Cars WHERE Id=2;\
      SELECT Name FROM Cars WHERE Id=3;SELECT Name FROM Cars WHERE Id=6")) 
  {
      finish_with_error(con);
  }

  do {  
      MYSQL_RES *result = mysql_store_result(con);

      if (result == NULL) 
      {
          finish_with_error(con);
      }

      MYSQL_ROW row = mysql_fetch_row(result);

      printf("%s\n", row[0]);

      mysql_free_result(result);

      status = mysql_next_result(con); 

      if (status > 0) {
          finish_with_error(con);
      }

  } while(status == 0);

  mysql_close(con);  
  exit(0);
}

在该示例中,我们在一个查询中执行了三个SELECT语句。

if (mysql_real_connect(con, "localhost", "user12", "34klq*", 
        "testdb", 0, NULL, CLIENT_MULTI_STATEMENTS) == NULL) 
{
    finish_with_error(con);
}

mysql_real_connect()方法的最后一个选项是客户端标志。 它用于启用某些功能。 CLIENT_MULTI_STATEMENTS允许执行多个语句。 默认情况下禁用此功能。

if (mysql_query(con, "SELECT Name FROM Cars WHERE Id=2;\
    SELECT Name FROM Cars WHERE Id=3;SELECT Name FROM Cars WHERE Id=6")) 
{
    finish_with_error(con);
}

该查询包含三个SELECT语句。 它们之间用分号;字符分隔。 反斜杠字符\用于将字符串分成两行。 它与多个语句无关。

do {  
...    
} while(status == 0);

该代码位于do/while语句之间。 数据检索要分多个周期进行。 我们将分别为每个SELECT语句检索数据。

status = mysql_next_result(con); 

我们期望有多个结果集。 因此,我们称为mysql_next_result()函数。 它读取下一个语句结果,并返回状态以指示是否存在更多结果。 如果执行正常并且有更多结果,该函数将返回 0。 当执行 OK 且没有更多结果时,它将返回 -1。 最后,如果发生错误,它将返回大于零的值。

if (status > 0) {
    finish_with_error(con);
}

我们检查错误。

$ ./multiple_statements 
Mercedes
Skoda
Citroen

示例输出。

将图像插入 MySQL 数据库

有些人喜欢将其图像放入数据库中,有些人则希望将其保留在文件系统中以供其应用使用。 当我们处理大量图像时,会出现技术难题。 图像是二进制数据。 MySQL 数据库具有一种特殊的数据类型来存储称为BLOB(二进制大对象)的二进制数据。

mysql> CREATE TABLE Images(Id INT PRIMARY KEY, Data MEDIUMBLOB);

对于我们的示例,我们创建一个新的Images表。 图像大小最大为 16 MB。 它由MEDIUMBLOB数据类型确定。

#include <my_global.h>
#include <mysql.h>
#include <string.h>

void finish_with_error(MYSQL *con)
{
  fprintf(stderr, "%s\n", mysql_error(con));
  mysql_close(con);
  exit(1);        
}

int main(int argc, char **argv)
{

  FILE *fp = fopen("woman.jpg", "rb");

  if (fp == NULL) 
  {
      fprintf(stderr, "cannot open image file\n");    
      exit(1);
  }

  fseek(fp, 0, SEEK_END);

  if (ferror(fp)) {

      fprintf(stderr, "fseek() failed\n");
      int r = fclose(fp);

      if (r == EOF) {
          fprintf(stderr, "cannot close file handler\n");          
      }    

      exit(1);
  }  

  int flen = ftell(fp);

  if (flen == -1) {

      perror("error occurred");
      int r = fclose(fp);

      if (r == EOF) {
          fprintf(stderr, "cannot close file handler\n");
      }

      exit(1);      
  }

  fseek(fp, 0, SEEK_SET);

  if (ferror(fp)) {

      fprintf(stderr, "fseek() failed\n");
      int r = fclose(fp);

      if (r == EOF) {
          fprintf(stderr, "cannot close file handler\n");
      }    

      exit(1);
  }

  char data[flen+1];

  int size = fread(data, 1, flen, fp);

  if (ferror(fp)) {

      fprintf(stderr, "fread() failed\n");
      int r = fclose(fp);

      if (r == EOF) {
          fprintf(stderr, "cannot close file handler\n");
      }

      exit(1);      
  }

  int r = fclose(fp);

  if (r == EOF) {
      fprintf(stderr, "cannot close file handler\n");
  }          

  MYSQL *con = mysql_init(NULL);

  if (con == NULL)
  {
      fprintf(stderr, "mysql_init() failed\n");
      exit(1);
  }  

  if (mysql_real_connect(con, "localhost", "user12", "34klq*", 
          "testdb", 0, NULL, 0) == NULL) 
  {
      finish_with_error(con);
  }   

  char chunk[2*size+1];
  mysql_real_escape_string(con, chunk, data, size);

  char *st = "INSERT INTO Images(Id, Data) VALUES(1, '%s')";
  size_t st_len = strlen(st);

  char query[st_len + 2*size+1]; 
  int len = snprintf(query, st_len + 2*size+1, st, chunk);

  if (mysql_real_query(con, query, len))
  {
      finish_with_error(con);
  }

  mysql_close(con);
  exit(0);
}

在此示例中,我们将一张图像插入Images表。

#include <string.h>

这包括strlen()函数。

FILE *fp = fopen("woman.jpg", "rb");

if (fp == NULL) 
{
    fprintf(stderr, "cannot open image file\n");    
    exit(1);
}

在这里,我们打开图像文件。 在当前工作目录中,我们应该有woman.jpg文件。

fseek(fp, 0, SEEK_END);

if (ferror(fp)) {

    fprintf(stderr, "fseek() failed\n");
    int r = fclose(fp);

    if (r == EOF) {
        fprintf(stderr, "cannot close file handler\n");          
    }    

    exit(1);
}  

我们使用fseek()函数将文件指针移到文件末尾。 我们将确定图像的大小。 如果发生错误,则设置错误指示器。 我们使用fseek()函数检查指示器。 如果发生错误,我们还将关闭打开的文件处理器。

int flen = ftell(fp);

if (flen == -1) {

    perror("error occurred");
    int r = fclose(fp);

    if (r == EOF) {
        fprintf(stderr, "cannot close file handler\n");
    }

    exit(1);      
}

对于二进制流,ftell()函数返回文件开头的字节数,例如图像文件的大小。 如果发生错误,该函数将返回 -1 并设置errnoperrro()函数将errno的值解释为错误消息,并将其打印到标准错误输出流。

char data[flen+1];

在这个数组中,我们将存储图像数据。

int size = fread(data, 1, flen, fp);

我们从文件指针读取数据并将其存储在数据数组中。 返回成功读取的元素总数。

int r = fclose(fp);

if (r == EOF) {
    fprintf(stderr, "cannot close file handler\n");
}

读取数据后,我们可以关闭文件处理器。

char chunk[2*size+1];
mysql_real_escape_string(con, chunk, data, size);

mysql_real_escape_string()函数在传递给该函数的字符串中的某些潜在危险字符之前添加转义字符反斜杠\。 这可以帮助防止 SQL 注入攻击。 新缓冲区的长度必须至少为2*size+1

char *st = "INSERT INTO Images(Id, Data) VALUES(1, '%s')";
size_t st_len = strlen(st);

在这里,我们开始构建 SQL 语句。 我们使用strlen()函数确定 SQL 字符串的大小。

char query[st_len + 2*size+1]; 
int len = snprintf(query, st_len + 2*size+1, st, chunk);

查询的时间必须足够长,以包含 SQL 字符串语句的大小和图像文件的大小。 使用snprintf()函数,将格式化的输出写入查询缓冲区。

if (mysql_real_query(con, query, len))
{
    finish_with_error(con);
};

我们使用mysql_real_query()函数执行查询。 mysql_query()不能用于包含二进制数据的语句; 我们必须改用mysql_real_query()

从 MySQL 数据库中选择图像

在前面的示例中,我们已将图像插入数据库。 在下面的示例中,我们将从数据库中选择回插入的图像。

#include <my_global.h>
#include <mysql.h>

void finish_with_error(MYSQL *con)
{
  fprintf(stderr, "%s\n", mysql_error(con));
  mysql_close(con);
  exit(1);        
}

int main(int argc, char **argv)
{
  FILE *fp = fopen("woman2.jpg", "wb");

  if (fp == NULL) 
  {
      fprintf(stderr, "cannot open image file\n");    
      exit(1);
  }

  MYSQL *con = mysql_init(NULL);

  if (con == NULL)
  {
      fprintf(stderr, "mysql_init() failed\n");
      exit(1);
  }  

  if (mysql_real_connect(con, "localhost", "user12", "34klq*", 
          "testdb", 0, NULL, 0) == NULL) 
  {
      finish_with_error(con);
  }

  if (mysql_query(con, "SELECT Data FROM Images WHERE Id=1"))
  {
      finish_with_error(con);
  }

  MYSQL_RES *result = mysql_store_result(con);

  if (result == NULL) 
  {
      finish_with_error(con);
  }  

  MYSQL_ROW row = mysql_fetch_row(result);
  unsigned long *lengths = mysql_fetch_lengths(result);

  if (lengths == NULL) {
      finish_with_error(con);
  }

  fwrite(row[0], lengths[0], 1, fp);

  if (ferror(fp)) 
  {            
      fprintf(stderr, "fwrite() failed\n");
      mysql_free_result(result);
      mysql_close(con);

      exit(1);      
  }  

  int r = fclose(fp);

  if (r == EOF) {
      fprintf(stderr, "cannot close file handler\n");
  }

  mysql_free_result(result);
  mysql_close(con);

  exit(0);
}

在此示例中,我们将从数据库中创建一个图像文件。

FILE *fp = fopen("woman2.jpg", "wb");

if (fp == NULL) 
{
    fprintf(stderr, "cannot open image file\n");    
    exit(1);
}

我们打开一个新的文件处理器进行写入。

if (mysql_query(con, "SELECT Data FROM Images WHERE Id=1"))
{
    finish_with_error(con);
}

我们使用Id 1 从Image表中选择Data列。

MYSQL_ROW row = mysql_fetch_row(result);

该行包含原始数据。

unsigned long *lengths = mysql_fetch_lengths(result);

我们得到图像的长度。

fwrite(row[0], lengths[0], 1, fp);

if (ferror(fp)) 
{            
    fprintf(stderr, "fwrite() failed\n");
    mysql_free_result(result);
    mysql_close(con);

    exit(1);      
}  

我们使用fwrite()函数调用将检索到的数据写入磁盘。 我们使用ferror()函数检查错误指示符。

int r = fclose(fp);

if (r == EOF) {
    fprintf(stderr, "cannot close file handler\n");
}

写入图像数据后,使用fclose()函数关闭文件处理器。

这是 MySQL C API 教程。 您可能也对 MySQL Python 教程MySQL Visual Basic 教程MySQL PHP 教程PostgreSQL C 教程或[ 关于 ZetCode 的 SQLite C 教程

MySQL Visual Basic 教程

原文: http://zetcode.com/db/mysqlvb/

这是针对 MySQL 数据库的 Visual Basic 教程。 它涵盖了使用 Visual Basic 进行 MySQL 编程的基础。 在本教程中,我们使用 Connector/Net 驱动程序。 该驱动程序基于 ADO.NET 规范。 这些示例是在 Ubuntu Linux 上创建和测试的。 在 ZetCode 上有类似的 MySQL C# 教程MySQL Perl 教程SQLite Visual Basic 教程

如果您需要重新了解 Visual Basic 语言,可以在 ZetCode 上找到完整的 Visual Basic 教程

关于 MySQL 数据库

MySQL 是领先的开源数据库管理系统。 它是一个多用户,多线程的数据库管理系统。 MySQL 在网络上特别流行。 它是由 Linux,Apache,MySQL 和 PHP 组成的 LAMP 平台的组成部分之一。 目前,MySQL 由 Oracle 拥有。 MySQL 数据库在最重要的 OS 平台上可用。 它可以在 BSD Unix,Linux,Windows 或 Mac OS 上运行。 维基百科和 YouTube 使用 MySQL。 这些站点每天管理数百万个查询。 MySQL 有两个版本:MySQL 服务器系统和 MySQL 嵌入式系统。

开始之前

我们需要安装几个包来执行本教程中的示例:libmysql6.1-cilmysql-servermysql-client。 我们需要从 Mono 项目安装 Visual Basic 编译器。 从包装或从来源。

libmysql6.1-cil是 CLI 的 MySQL 数据库连接器。 它用 C# 编写,可用于所有 CLI 语言。 C# ,Visual Basic,Boo 等。

$ ls /usr/lib/cli/MySql.Data-6.1/MySql.Data.dll 
/usr/lib/cli/MySql.Data-6.1/MySql.Data.dll

从技术角度来看,我们需要一个 DLL。 在我的系统(Ubuntu Lucid Lynx)上,它位于上述路径下。 我们需要知道 DLL 库的路径。 汇编我们的例子。

如果您尚未安装 MySQL,则必须安装它。

$ sudo apt-get install mysql-server

此命令将安装 MySQL 服务器和其他各种包。 在安装包时,提示我们输入 MySQL 根帐户的密码。

接下来,我们将创建一个新的数据库用户和一个新的数据库。 我们使用mysql客户端。

$ service mysql status
mysql start/running, process 1238

我们检查 MySQL 服务器是否正在运行。 如果没有,我们需要启动服务器。 在 Ubuntu Linux 上,可以使用service mysql start命令来完成。

$ mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 30
Server version: 5.0.67-0ubuntu6 (Ubuntu)

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema | 
| mysql              | 
+--------------------+
2 rows in set (0.00 sec)

我们使用 mysql 监视器客户端应用连接到服务器。 我们使用根帐户连接到数据库。 我们用SHOW DATABASES语句显示所有可用的数据库。

mysql> CREATE DATABASE testdb;
Query OK, 1 row affected (0.02 sec)

我们创建一个新的testdb数据库。 在整个教程中,我们将使用此数据库。

mysql> CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'test623';
Query OK, 0 rows affected (0.00 sec)

mysql> USE testdb;
Database changed

mysql> GRANT ALL ON testdb.* TO 'testuser'@'localhost';
Query OK, 0 rows affected (0.00 sec)

mysql> quit;
Bye

我们创建一个新的数据库用户。 我们授予该用户testdb数据库所有表的所有特权。

定义

ADO.NET是.NET 框架的重要组成部分。 该规范统一了对关系数据库,XML 文件和其他应用数据的访问。 MySQL Connector/Net是 MySQL 数据库的 ADO.NET 规范的实现。 它是用 C# 语言编写的驱动程序,可用于所有.NET 语言。

ConnectionCommandDataReaderDataSetDataProvider是.NET 数据供应器模型的核心元素。 Connection创建到特定数据源的连接。 Command对象针对数据源执行一条 SQL 语句。 DataReader从数据源读取数据流。 DataSet对象用于脱机处理大量数据。 它是一种断开连接的数据表示形式,可以保存来自各种不同来源的数据。 DataReaderDataSet都用于处理数据。 它们在不同的情况下使用。 如果只需要读取查询结果,则DataReader是更好的选择。 如果需要更广泛的数据处理,或者要将Winforms控件绑定到数据库表,则首选DataSet

MySQL 版本

如果以下程序运行正常,则我们已安装一切正常。 我们检查 MySQL 服务器的版本。

Option Strict On

Imports MySql.Data.MySqlClient

Module Example

    Sub Main()

        Dim cs As String = "Database=testdb;Data Source=localhost;" _
            & "User Id=testuser;Password=test623"

        Dim conn As New MySqlConnection(cs)

        Try
          conn.Open()
          Console.WriteLine("MySQL version : {0}", conn.ServerVersion)

        Catch ex As MySqlException
          Console.WriteLine("Error: " & ex.ToString())
        Finally
          conn.Close()
        End Try

    End Sub

End Module

我们连接到数据库并获取有关 MySQL 服务器的一些信息。

Imports MySql.Data.MySqlClient

我们导入 MySQL 数据供应器的元素。

Dim cs As String = "Database=testdb;Data Source=localhost;" _
    & "User Id=testuser;Password=test623"

这是连接字符串。 数据提供者使用它来建立与数据库的连接。 我们指定数据库名称,主机,用户名和密码。

Dim conn As New MySqlConnection(cs)

创建一个MySQLConnection对象。 该对象用于打开与数据库的连接。

conn.Open()

这条线打开连接。

Console.WriteLine("MySQL version : {0}", conn.ServerVersion)

在这里,我们使用连接对象的ServerVersion属性打印 MySQL 的版本。

Catch ex As MySqlException
  Console.WriteLine("Error: " & ex.ToString())

如果发生异常,我们将错误消息打印到控制台。

$ vbnc -r:/usr/lib/cli/MySql.Data-6.1/MySql.Data.dll connect.vb

我们汇编我们的例子。 提供了 MySQL 连接器 DLL 的路径。

$ ./connect.exe 
MySQL version : 5.1.41-3ubuntu12.6

这是我系统上程序的输出。

接下来是一个更复杂的程序。

Option Strict On

Imports MySql.Data.MySqlClient

Module Example

    Sub Main()
        Dim cs As String = "Database=testdb;Data Source=localhost;" _
            & "User Id=testuser;Password=test623"

        Dim stm As String = "SELECT VERSION()"
        Dim version As String
        Dim conn As MySqlConnection

        Try
            conn = New MySqlConnection(cs)
            conn.Open()

            Dim cmd As MySqlCommand = New MySqlCommand(stm, conn)

            version = Convert.ToString(cmd.ExecuteScalar())

            Console.WriteLine("MySQL version: {0}", version)

        Catch ex As MySqlException
            Console.WriteLine("Error: " & ex.ToString())
        Finally
            conn.Close()
        End Try

   End Sub

End Module

我们检查 MySQL 数据库的版本。 这次使用 SQL 查询。

Dim stm As String = "SELECT VERSION()"

这是 SQL SELECT语句。 它返回数据库的版本。 VERSION()是内置的 MySQL 函数。

Dim cmd As MySqlCommand = New MySqlCommand(stm, conn)

MySqlCommand是一个对象,用于在数据库上执行查询。 参数是 SQL 语句和连接对象。

version = Convert.ToString(cmd.ExecuteScalar())

有些查询仅返回标量值。 在我们的例子中,我们需要一个简单的字符串来指定数据库的版本。 在这种情况下使用ExecuteScalar()。 我们避免了使用更复杂的对象的开销。

$ ./connect2.exe 
MySQL version : 5.1.41-3ubuntu12.6

结果与前面的示例相同。

创建和填充表

接下来,我们将创建数据库表并用数据填充它们。 这些表将在本教程中使用。

DROP TABLE IF EXISTS Books, Authors;

CREATE TABLE IF NOT EXISTS Authors(Id INT PRIMARY KEY AUTO_INCREMENT, 
    Name VARCHAR(25)) ENGINE=INNODB;

INSERT INTO Authors(Id, Name) VALUES(1, 'Jack London');
INSERT INTO Authors(Id, Name) VALUES(2, 'Honore de Balzac');
INSERT INTO Authors(Id, Name) VALUES(3, 'Lion Feuchtwanger');
INSERT INTO Authors(Id, Name) VALUES(4, 'Emile Zola');
INSERT INTO Authors(Id, Name) VALUES(5, 'Truman Capote');

CREATE TABLE IF NOT EXISTS Books(Id INT PRIMARY KEY AUTO_INCREMENT, 
    AuthorId INT, Title VARCHAR(100), 
    FOREIGN KEY(AuthorId) REFERENCES Authors(Id) ON DELETE CASCADE)
    ENGINE=INNODB;

INSERT INTO Books(Id, AuthorId, Title) VALUES(1, 1, 'Call of the Wild');
INSERT INTO Books(Id, AuthorId, Title) VALUES(2, 1, 'Martin Eden');
INSERT INTO Books(Id, AuthorId, Title) VALUES(3, 2, 'Old Goriot');
INSERT INTO Books(Id, AuthorId, Title) VALUES(4, 2, 'Cousin Bette');
INSERT INTO Books(Id, AuthorId, Title) VALUES(5, 3, 'Jew Suess');
INSERT INTO Books(Id, AuthorId, Title) VALUES(6, 4, 'Nana');
INSERT INTO Books(Id, AuthorId, Title) VALUES(7, 4, 'The Belly of Paris');
INSERT INTO Books(Id, AuthorId, Title) VALUES(8, 5, 'In Cold blood');
INSERT INTO Books(Id, AuthorId, Title) VALUES(9, 5, 'Breakfast at Tiffany');

我们有一个books.sql文件。 它创建两个数据库表:AuthorsBooks。 这些表是 InnoDB 类型的。 InnoDB 数据库支持外键约束和事务。 我们将外键约束放在Books表的AuthorId列上。 我们用初始数据填充表。

mysql> source books.sql
Query OK, 0 rows affected (0.07 sec)
Query OK, 0 rows affected (0.12 sec)
Query OK, 1 row affected (0.04 sec)
...

我们使用source命令执行books.sql脚本。

在下面的示例中,我们将在Authors表中插入一个新作者。

Option Strict On

Imports MySql.Data.MySqlClient

Module Example

   Sub Main()
      Dim connString As String = "Database=testdb;Data Source=localhost;" _
          & "User Id=testuser;Password=test623"

      Dim conn As New MySqlConnection(connString)
      Dim cmd As New MySqlCommand()

      Try
        conn.Open()
        cmd.Connection = conn

        cmd.CommandText = "INSERT INTO Authors(Name) VALUES(@Name)"
        cmd.Prepare()

        cmd.Parameters.AddWithValue("@Name", "Trygve Gulbranssen")
        cmd.ExecuteNonQuery()

        conn.Close()

      Catch ex As MySqlException
          Console.WriteLine("Error: " & ex.ToString())
      End Try

   End Sub

End Module

我们将新作者添加到Authors表中。 我们使用参数化命令。

cmd.CommandText = "INSERT INTO Authors(Name) VALUES(@Name)"
cmd.Prepare()

在这里,我们创建一个预备语句。 在编写预备语句时,我们使用占位符,而不是直接将值写入语句中。 预备语句更快,并且可以防止 SQL 注入攻击。 @Name是一个占位符,稍后将填充。

cmd.Parameters.AddWithValue("@Name", "Trygve Gulbranssen")

值绑定到占位符。

cmd.ExecuteNonQuery()

执行预备语句。 当我们不希望返回任何数据时,我们使用MySQLCommand对象的ExecuteNonQuery()方法。 这是当我们创建数据库或执行INSERTUPDATEDELETE语句时。

$ ./prepared.exe 
mysql> select * from Authors;
+----+--------------------+
| Id | Name               |
+----+--------------------+
|  1 | Jack London        |
|  2 | Honore de Balzac   |
|  3 | Lion Feuchtwanger  |
|  4 | Emile Zola         |
|  5 | Truman Capote      |
|  6 | Trygve Gulbranssen |
+----+--------------------+
6 rows in set (0.00 sec)

我们在表中插入了一位新作者。

使用MySqlDataReader检索数据

MySqlDataReader是用于从数据库检索数据的对象。 它提供对查询结果的快速,仅转发和只读访问。 这是从表中检索数据的最有效方法。

Option Strict On

Imports MySql.Data.MySqlClient

Module Example

    Sub Main()

        Dim cs As String = "Database=testdb;Data Source=localhost;" _
            & "User Id=testuser;Password=test623"

        Dim conn As New MySqlConnection(cs)

        Try
            conn.Open()
            Dim stm As String = "SELECT * FROM Authors"
            Dim cmd As MySqlCommand = New MySqlCommand(stm, conn)
            Dim reader As MySqlDataReader = cmd.ExecuteReader()

            While reader.Read()
                Console.WriteLine(reader.GetInt32(0) & ": " _ 
                    & reader.GetString(1))
            End While

            reader.Close()

        Catch ex As MySqlException
          Console.WriteLine("Error: " & ex.ToString())
        Finally
          conn.Close()
        End Try

    End Sub

End Module

我们从Authors表中获取所有作者并将其打印到控制台。

Dim reader As MySqlDataReader = cmd.ExecuteReader()

要创建MySQLDataReader,我们必须调用MySqlCommand对象的ExecuteReader()方法。

While reader.Read()
    Console.WriteLine(reader.GetInt32(0) & ": " _ 
        & reader.GetString(1))
End While

Read()方法将数据读取器移至下一条记录。 如果有更多行,则返回true;否则,返回true。 否则为假。 我们可以使用数组索引符号来检索值,或者使用特定的方法来访问其本机数据类型中的列值。 后者效率更高。

reader.Close()

阅读完毕后,请始终调用Close()方法。

$ ./read.exe 
1: Jack London
2: Honore de Balzac
3: Lion Feuchtwanger
4: Emile Zola
5: Truman Capote
6: Trygve Gulbranssen

这是示例的输出。

列标题

接下来,我们将展示如何使用数据库表中的数据打印列标题。

Option Strict On

Imports MySql.Data.MySqlClient

Module Example

   Sub Main()
      Dim connString As String = "Database=testdb;Data Source=localhost;" _
          & "User Id=testuser;Password=test623"

      Dim conn As New MySqlConnection(connString)

      Try
          conn.Open()

          Dim stm As String = "SELECT Name, Title From Authors, " _
              & "Books WHERE Authors.Id=Books.AuthorId"

          Dim cmd As MySqlCommand = New MySqlCommand(stm, conn)
          Dim reader As MySqlDataReader = cmd.ExecuteReader()

          Console.WriteLine("{0} {1}", reader.GetName(0), _
              reader.GetName(1).PadLeft(18))

          While reader.Read()
              Console.WriteLine(reader.GetString(0).PadRight(18) _ 
                  & reader.GetString(1))
          End While

          reader.Close()

      Catch ex As MySqlException
         Console.WriteLine("Error: " & ex.ToString())
      Finally
         conn.Close()
      End Try

   End Sub

End Module

在此程序中,我们从Authors表中选择作者,并从Books表中选择他们的书。

Dim stm As String = "SELECT Name, Title From Authors, " _
    & "Books WHERE Authors.Id=Books.AuthorId"

这是将作者与他们的书联系在一起的 SQL 语句。

Dim reader As MySqlDataReader = cmd.ExecuteReader()

我们创建一个MySqlDataReader对象。

Console.WriteLine("{0} {1}", reader.GetName(0), _
    reader.GetName(1).PadLeft(18))

我们使用阅读器的GetName()方法获得列的名称。 PadLeft()方法返回指定长度的新字符串,其中当前字符串的开头用空格填充。 我们使用此方法正确对齐字符串。

While reader.Read()
    Console.WriteLine(reader.GetString(0).PadRight(18) _ 
        & reader.GetString(1))
End While

我们将 SQL 语句返回的数据打印到终端。

$ ./columns.exe 
Name              Title
Jack London       Call of the Wild
Jack London       Martin Eden
Honore de Balzac  Old Goriot
Honore de Balzac  Cousin Bette
Lion Feuchtwanger Jew Suess
Emile Zola        Nana
Emile Zola        The Belly of Paris
Truman Capote     In Cold blood
Truman Capote     Breakfast at Tiffany

该程序的输出。

数据集& MySqlDataAdapter

DataSet是数据库表中数据的副本以及数据之间的关系。 它在内存中创建,并在需要对数据进行大量处理或将数据表绑定到 Winforms 控件时使用。 处理完成后,更改将被写入数据源。 MySqlDataAdapterDataSet和数据源之间的中介。 它填充DataSet并解析数据源的更新。

Option Strict On

Imports System.Data
Imports MySql.Data.MySqlClient

Module Example

    Sub Main()

        Dim cs As String = "Database=testdb;Data Source=localhost;" _
            & "User Id=testuser;Password=test623"

        Dim conn As New MySqlConnection(cs)

        Dim stm As String = "SELECT * FROM Authors"

        Try
            conn.Open()

            Dim da As New MySqlDataAdapter(stm, conn)

            Dim ds As New DataSet
            da.Fill(ds, "Authors")

            Dim dt As DataTable = ds.Tables("Authors")

            dt.WriteXml("authors.xml")

            For Each row As DataRow In dt.Rows
                For Each col As DataColumn In dt.Columns
                  Console.WriteLine(row(col))
                Next
                 Console.WriteLine("".PadLeft(20, "="))
            Next

        Catch ex As MySqlException
          Console.WriteLine("Error: " & ex.ToString())
        Finally
          conn.Close()
        End Try

    End Sub

End Module 

我们从Authors表中打印作者。 这次,我们使用MySqlDataAdapterDataSet对象。

Dim da As New MySqlDataAdapter(stm, conn)

创建一个MySqlDataAdapter对象。 它以 SQL 语句和连接为参数。

Dim ds As New DataSet
da.Fill(ds, "Authors")

我们创建并填充DataSet

Dim dt As DataTable = ds.Tables("Authors")

我们得到名为Authors的表。 我们只给了DataSet一个表,但它可以包含多个表。

dt.WriteXml("authors.xml")

我们将数据写入 XML 文件。

For Each row As DataRow In dt.Rows
    For Each col As DataColumn In dt.Columns
      Console.WriteLine(row(col))
    Next
      Console.WriteLine("".PadLeft(20, "="))
Next

我们将Authors表的内容显示到终端。 为了遍历数据,我们利用了DataTable对象的行和列。

在下一个示例中,我们将表绑定到 Winforms DataGrid控件。

Option Strict On

Imports System.Windows.Forms
Imports System.Drawing
Imports System.Data
Imports MySql.Data.MySqlClient

Public Class WinVBApp
    Inherits Form

    Private dg As DataGrid
    Private da As MySqlDataAdapter
    Private ds As DataSet

    Public Sub New()

       Me.Text = "DataGrid"
       Me.Size = New Size(350, 300)

       Me.InitUI()
       Me.InitData()

       Me.CenterToScreen()

    End Sub

    Private Sub InitUI()

        dg = New DataGrid

        dg.CaptionBackColor = System.Drawing.Color.White
        dg.CaptionForeColor = System.Drawing.Color.Black
        dg.CaptionText = "Authors"

        dg.Location = New Point(8, 0)
        dg.Size = New Size(350, 300)
        dg.TabIndex = 0
        dg.Parent = Me

    End Sub

    Private Sub InitData()

        Dim cs As String = "Database=testdb;Data Source=localhost;" _
            & "User Id=testuser;Password=test623"

        Dim conn As New MySqlConnection(cs)

        Dim stm As String = "SELECT * FROM Authors"
        ds = New DataSet

        Try
            conn.Open()

            da = New MySqlDataAdapter(stm, conn)
            da.Fill(ds, "Authors")

            dg.DataSource = ds.Tables("Authors")

        Catch ex As MySqlException
          Console.WriteLine("Error: " & ex.ToString())
        Finally
          conn.Close()
        End Try

    End Sub

    Public Shared Sub Main()
        Application.Run(New WinVBApp)
    End Sub

End Class

在此示例中,我们将Authors表绑定到 Winforms DataGrid控件。

Imports System.Windows.Forms
Imports System.Drawing

这两个名称空间用于 GUI。

Me.InitUI()
Me.InitData()

InitUI()方法内部,我们构建了用户界面。 在InitData()方法中,我们连接到数据库,将数据检索到DataSet中并将其绑定到DataGrid控件。

dg = New DataGrid

DataGrid控件已创建。

Dim stm As String = "SELECT * FROM Authors"

我们将在DataGrid控件中显示Authors表中的数据。

dg.DataSource = ds.Tables("Authors")

我们将DataGrid控件的DataSource属性绑定到所选表。

vbnc -r:/usr/lib/mono/2.0/System.Windows.Forms.dll 
    -r:/usr/lib/cli/MySql.Data-6.1/MySql.Data.dll grid.vb

要编译该示例,我们必须包含两个 DLL。 Winforms 的 DLL 和 MySQL 连接器的 DLL。

DataGrid

图:DataGrid

事务支持

transaction是针对一个或多个数据库中数据的数据库操作的基本单位。 事务中所有 SQL 语句的影响可以全部提交给数据库,也可以全部回滚。

MySQL 数据库具有不同类型的存储引擎。 最常见的是 MyISAM 和 InnoDB 引擎。 MyISAM 是默认的。 在数据安全性和数据库速度之间需要权衡。 MyISAM 表的处理速度更快,并且不支持事务。 另一方面,InnoDB 表可以更安全地防止数据丢失。 他们支持事务。 它们处理较慢。

Option Strict On

Imports MySql.Data.MySqlClient

Module Example

   Sub Main()
      Dim cs As String = "Database=testdb;Data Source=localhost;" _
          & "User Id=testuser;Password=test623"

      Dim conn As New MySqlConnection(cs)
      Dim cmd As New MySqlCommand()

      Dim tr As MySqlTransaction 

      Try
        conn.Open()
        tr = conn.BeginTransaction()

        cmd.Connection = conn
        cmd.Transaction = tr

        cmd.CommandText = "UPDATE Authors SET Name = 'Leo Tolstoy' WHERE Id = 1"
        cmd.ExecuteNonQuery()
        cmd.CommandText = "UPDATE Books SET Title = 'War and Peace' WHERE Id = 1"
        cmd.ExecuteNonQuery()
        cmd.CommandText = "UPDATE Books SET Titl = 'Anna Karenina' WHERE Id = 2"
        cmd.ExecuteNonQuery()

        tr.Commit()
        conn.Close()

      Catch ex As MySqlException
          tr.Rollback()
          Console.WriteLine("Error: " & ex.ToString())
      End Try

   End Sub

End Module

在此程序中,我们想在Authors表的第一行上更改作者的姓名。 我们还必须更改与该作者相关的书籍。 一个需要进行事务的很好的例子。 如果我们更改作者但不更改作者的书,则数据已损坏。

Dim tr As MySqlTransaction 

MySqlTransaction是用于处理事务的对象。

tr = conn.BeginTransaction()

我们开始事务。

cmd.CommandText = "UPDATE Books SET Titl = 'Anna Karenina' WHERE Id = 2"
cmd.ExecuteNonQuery()

第三个 SQL 语句有一个错误。 表中没有 Titl 栏。

tr.Commit()

如果没有异常,则提交事务。

Catch ex As MySqlException
    tr.Rollback()
    Console.WriteLine("Error: " & ex.ToString())

发生异常时,事务将回滚。 没有更改提交到数据库。

$ ./transaction.exe 
Error: MySql.Data.MySqlClient.MySqlException: Unknown column 'Titl' in 'field list'
  at MySql.Data.MySqlClient.MySqlStream.ReadPacket () [0x00000] 
  at MySql.Data.MySqlClient.NativeDriver.ReadResult () [0x00000]

mysql> SELECT Name, Title From Authors, Books WHERE Authors.Id=Books.AuthorId;
+-------------------+----------------------+
| Name              | Title                |
+-------------------+----------------------+
| Jack London       | Call of the Wild     |
| Jack London       | Martin Eden          |
| Honore de Balzac  | Old Goriot           |
| Honore de Balzac  | Cousin Bette         |
| Lion Feuchtwanger | Jew Suess            |
| Emile Zola        | Nana                 |
| Emile Zola        | The Belly of Paris   |
| Truman Capote     | In Cold blood        |
| Truman Capote     | Breakfast at Tiffany |
+-------------------+----------------------+
9 rows in set (0.00 sec)

引发异常。 事务已回滚,并且未进行任何更改。

但是,如果没有事务,数据是不安全的。

Option Strict On

Imports MySql.Data.MySqlClient

Module Example

   Sub Main()
      Dim cs As String = "Database=testdb;Data Source=localhost;" _
          & "User Id=testuser;Password=test623"

      Dim conn As New MySqlConnection(cs)
      Dim cmd As New MySqlCommand()

      Try
        conn.Open()

        cmd.Connection = conn

        cmd.CommandText = "UPDATE Authors SET Name = 'Leo Tolstoy' WHERE Id = 1"
        cmd.ExecuteNonQuery()
        cmd.CommandText = "UPDATE Books SET Title = 'War and Peace' WHERE Id = 1"
        cmd.ExecuteNonQuery()
        cmd.CommandText = "UPDATE Books SET Titl = 'Anna Karenina' WHERE Id = 2"
        cmd.ExecuteNonQuery()

        conn.Close()

      Catch ex As MySqlException
          Console.WriteLine("Error: " & ex.ToString())
      End Try

   End Sub

End Module

我们有同样的例子。 这次,没有事务支持。

$ ./update.exe 
Error: MySql.Data.MySqlClient.MySqlException: Unknown column 'Titl' in 'field list'
  at MySql.Data.MySqlClient.MySqlStream.ReadPacket () [0x00000] 
  at MySql.Data.MySqlClient.NativeDriver.ReadResult () [0x00000] 

mysql> SELECT Name, Title From Authors, Books WHERE Authors.Id=Books.AuthorId;
+-------------------+----------------------+
| Name              | Title                |
+-------------------+----------------------+
| Leo Tolstoy       | War and Peace        |
| Leo Tolstoy       | Martin Eden          |
| Honore de Balzac  | Old Goriot           |
| Honore de Balzac  | Cousin Bette         |
| Lion Feuchtwanger | Jew Suess            |
| Emile Zola        | Nana                 |
| Emile Zola        | The Belly of Paris   |
| Truman Capote     | In Cold blood        |
| Truman Capote     | Breakfast at Tiffany |
+-------------------+----------------------+
9 rows in set (0.00 sec)

再次引发异常。 列夫·托尔斯泰没有写马丁·伊甸园。 数据已损坏。

Tweet

这是带有 MySQL 连接器的 MySQL Visual Basic 教程。 您可能也对 MySQL C API 教程MySQL Python 教程MySQL PHP 教程感兴趣。

MySQL PHP 教程

原文: http://zetcode.com/databases/mysqlphptutorial/

这是针对 MySQL 数据库的 PHP 编程教程。 它涵盖了使用 PHP 进行 MySQL 编程的基础。 它使用通用 mysql 模块。 这些示例是在 Ubuntu Linux 上创建和测试的。 在 ZetCode 上有类似的 MySQL C API 教程MySQL MySQL 教程MongoDB PHP 教程PostgreSQL PHP 教程

如果您需要重新了解 PHP 语言,可以在 ZetCode 上找到完整的 PHP 教程

关于 MySQL 数据库

MySQL 是领先的开源数据库管理系统。 它是一个多用户,多线程的数据库管理系统。 MySQL 在网络上特别流行。 它是非常流行的 LAMP 平台的组成部分之一。 Linux,Apache,MySQL 和 PHP。 目前,MySQL 由 Oracle 拥有。 MySQL 数据库在最重要的 OS 平台上可用。 它可以在 BSD Unix,Linux,Windows 或 Mac OS 上运行。 维基百科和 YouTube 使用 MySQL。 这些站点每天管理数百万个查询。 MySQL 有两个版本:MySQL 服务器系统和 MySQL 嵌入式系统。

开始之前

我们需要安装几个包来执行本教程中的示例:php5-cliphp5-mysqlmysql-servermysql-client

php5-cli是 PHP5 编程语言的命令行解释器。 本教程中的所有示例均在控制台上创建。 我特意跳过了 Web 界面,以使示例更简单,仅关注 PHP 和 MySQL。

如果您尚未安装 MySQL,则必须安装它。

$ sudo apt-get install mysql-server

此命令将安装 MySQL 服务器和其他各种包。 在安装包时,提示我们输入 MySQL 根帐户的密码。

接下来,我们将创建一个新的数据库用户和一个新的数据库。 我们使用mysql客户端。

$ service mysql status
mysql start/running, process 1238

我们检查 MySQL 服务器是否正在运行。 如果没有,我们需要启动服务器。 在 Ubuntu Linux 上,可以使用service mysql start命令来完成。

$ sudo service mysql start

如果我们已经从包中安装了 MySQL 数据库,则上述命令是启动 MySQL 的常用方法。

$ sudo -b /usr/local/mysql/bin/mysqld_safe

上面的命令使用 MySQL 服务器启动脚本启动 MySQL 服务器。 我们启动 MySQL 服务器的方式可能有所不同。 这取决于我们是否从源代码或包安装了 MySQL,也取决于 Linux 发行版。 有关更多信息,请查阅 MySQL 的第一步或您的 Linux 发行版信息。

接下来,我们将创建一个新的数据库用户和一个新的数据库。 我们使用mysql客户端。

$ mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 30
Server version: 5.0.67-0ubuntu6 (Ubuntu)

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema | 
| mysql              | 
+--------------------+
2 rows in set (0.00 sec)

我们使用 mysql 监视器客户端应用连接到服务器。 我们使用根帐户连接到数据库。 我们用SHOW DATABASES语句显示所有可用的数据库。

mysql> CREATE DATABASE mydb;
Query OK, 1 row affected (0.02 sec)

我们创建一个新的mydb数据库。 在整个教程中,我们将使用此数据库。

mysql> CREATE USER user12@localhost IDENTIFIED BY '34klq*';
Query OK, 0 rows affected (0.00 sec)

mysql> USE mydb;
Database changed

mysql> GRANT ALL ON mydb.* to user12@localhost;
Query OK, 0 rows affected (0.00 sec)

mysql> quit;
Bye

我们创建一个新的数据库用户。 我们授予该用户mydb数据库所有表的所有特权。

php5-mysql

为了从 PHP 语言连接到 MySQL 数据库,我们必须安装php5-mysql包。 这是 Debian / Ubuntu Linux 的包名称。 在其他衍生产品上,名称可能有所不同。 该包包含三个模块。 它们也称为扩展。

  • mysql模块
  • mysqli模块
  • pdo_mysql

通用mysql模块是 MySQL 数据库的原始 PHP API。 我们的教程涵盖了该模块。 API 是程序性的。 该模块不提供较新的 MySQL 数据库的所有最新功能。 MySQL 改进的mysqli模块是 MySQL 4.1.3 或更高版本的推荐模块。 它提供了面向对象的 API 和过程 API。 与原始的mysql模块相比,它具有一些优点和增强。

PHP 数据对象模块pdo_mysql是 PHP 应用的数据库抽象层。 如果我们编写可移植的数据库 PHP 脚本,则此模块非常有用。

第一个脚本

以下脚本是一个简单的 PHP 脚本。 如果此小脚本运行正常,则说明我们已安装了所有必需的东西。

<?php

$host = "localhost"; 
$user = "user12"; 
$pass = "34klq*"; 

$r = mysql_connect($host, $user, $pass);

if (!$r) {
    echo "Could not connect to server\n";
    trigger_error(mysql_error(), E_USER_ERROR);
} else {
    echo "Connection established\n"; 
}

echo mysql_get_server_info() . "\n"; 

mysql_close();

?>

我们连接到数据库并获取有关 MySQL 服务器的一些信息。

$host = "localhost"; 
$user = "user12"; 
$pass = "34klq*"; 

这是三个变量,分别包含主机名,用户名和密码。 连接到 MySQL 数据库时需要这些变量。

$r = mysql_connect($host, $user, $pass);

我们使用mysql_connect()函数连接到数据库。 该函数返回一个布尔值,指示是否成功创建连接。 该函数具有 3 个参数。 第一个是安装服务器的主机。 第二和第三个参数是用户名和用户密码。

if (!$r) {
    echo "Could not connect to server\n";
    trigger_error(mysql_error(), E_USER_ERROR);
} else {
    echo "Connection established\n"; 
}

现在我们检查$r变量。 如果它包含布尔值false,则表示未创建与数据库的连接。 我们调用trigger_error()函数来生成错误消息。 第一条通用消息发送给用户。 记录通过trigger_error()函数生成的更具体的错误消息。

echo mysql_get_server_info() . "\n"; 

mysql_get_server_info()返回 MySQL 服务器版本。

mysql_close();

mysql_close()函数关闭与数据库的连接。 在本例中,不需要关闭连接,因为在脚本执行结束时,非持久性打开链接会自动关闭。 但是,这是一种好的编程习惯。

$ php version.php
5.1.41-3ubuntu12.6
5.3.2-1ubuntu4.5

在我的系统上,我得到以下输出。

我们有一个类似的脚本。

<?php

$host = "localhost"; 
$user = "user12"; 
$pass = "34klq*"; 

$r = mysql_connect($host, $user, $pass);

if (!$r) {
    echo "Could not connect to server\n";
    trigger_error(mysql_error(), E_USER_ERROR);
} else {
    echo "Connection established\n"; 
}

$query = "SELECT VERSION()";

$rs = mysql_query($query);

if (!$rs) {
    echo "Could not execute query: $query\n";
    trigger_error(mysql_error(), E_USER_ERROR);
} else {
    echo "Query: $query executed\n"; 
}

$row = mysql_fetch_row($rs);

echo "Version: $row[0]\n";

mysql_close();

?>

我们检查 MySQL 数据库的版本。 这次使用 SQL 查询。

$query = "SELECT VERSION()";

这是 SQL SELECT语句。 它返回数据库的版本。 VERSION()是内置的 MySQL 函数。

$rs = mysql_query($query);

mysql_query()函数在数据库上执行 SQL 查询。 这是一个SELECT查询,因此结果是一个包含一些数据的结果集。

if (!$rs) {
    echo "Could not execute query: $query\n";
    trigger_error(mysql_error(), E_USER_ERROR);
} else {
    echo "Query: $query executed\n"; 
}

如果发生错误,我们会生成一条错误消息。 否则,我们将打印执行的 SQL 查询。

$row = mysql_fetch_row($rs);

我们从结果集中获取一行。 $row变量是一个包含数据的数组。

echo "Version: $row[0]\n";

我们从数组中打印数据。 根据查询的性质,我们知道数组只有一项,即 MySQL 版本字符串。

$ php version2.php
Connection established
Query: SELECT VERSION() executed
Version: 5.1.62-0ubuntu0.11.10.1

脚本在我们系统上的输出。

创建并填充表

接下来,我们将创建一个数据库表并用数据填充它。

<?php

$host = "localhost"; 
$user = "user12"; 
$pass = "34klq*"; 
$db = "mydb"; 

function execute_query($query) {

    $r = mysql_query($query);

    if (!$r) {
        echo "Cannot execute query: $query\n";
        trigger_error(mysql_error()); 
    } else {
        echo "Query: $query executed\n"; 
    }
}

$r = mysql_connect($host, $user, $pass);

if (!$r) {
    echo "Could not connect to server\n";
    trigger_error(mysql_error(), E_USER_ERROR);
} else {
    echo "Connection established\n"; 
}

$r2 = mysql_select_db($db);

if (!$r2) {
    echo "Cannot select database\n";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Database selected\n";
}

$query = "DROP TABLE IF EXISTS Cars"; 
execute_query($query);

$query = "CREATE TABLE Cars(Id INT PRIMARY KEY, Name TEXT, 
    Price INT) ENGINE=InnoDB";
execute_query($query);

$query = "INSERT INTO Cars VALUES(1,'Audi',52642)";
execute_query($query);

$query = "INSERT INTO Cars VALUES(2,'Mercedes',57127)";
execute_query($query);

$query = "INSERT INTO Cars VALUES(3,'Skoda',9000)";
execute_query($query);

$query = "INSERT INTO Cars VALUES(4,'Volvo',29000)";
execute_query($query);

$query = "INSERT INTO Cars VALUES(5,'Bentley',350000)";
execute_query($query);

$query = "INSERT INTO Cars VALUES(6,'Citroen',21000)";
execute_query($query);

$query = "INSERT INTO Cars VALUES(7,'Hummer',41400)";
execute_query($query);

$query = "INSERT INTO Cars VALUES(8,'Volkswagen',21600)";
execute_query($query);

mysql_close();

?>

在上面的代码示例中,我们创建具有 8 行的Cars表。

function execute_query($query) {

    $r = mysql_query($query);

    if (!$r) {
        echo "Cannot execute query: $query\n";
        trigger_error(mysql_error()); 
    } else {
        echo "Query: $query executed\n"; 
    }
}

我们创建了一个自定义execute_query()函数,该函数将为每个INSERT语句调用。

$r2 = mysql_select_db($db);

在使用数据库表之前,我们必须选择一个数据库。 使用mysql_select_db()函数选择一个数据库。

if (!$r2) {
    echo "Cannot select database\n";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Database selected\n";
}

数据库选择过程的错误处理。

$query = "DROP TABLE IF EXISTS Cars"; 
execute_query($query);

第一个查询将删除Cars表(如果已存在)。

$query = "CREATE TABLE Cars(Id INT PRIMARY KEY, Name TEXT, 
    Price INT) ENGINE=InnoDB";
execute_query($query);

这是创建Cars表的 SQL 语句。

$query = "INSERT INTO Cars VALUES(1,'Audi',52642)";
execute_query($query);

一辆汽车被插入表。

if (!$ok) {
   echo mysql_error();
   die("Cannot execute query. \n");
}

如果发生错误,我们将打印错误消息并终止脚本。

$ php create_fill.php
Connection established
Database selected
Query: DROP TABLE IF EXISTS Cars executed
Query: CREATE TABLE Cars(Id INT PRIMARY KEY, Name TEXT, 
    Price INT) ENGINE=InnoDB executed
Query: INSERT INTO Cars VALUES(1,'Audi',52642) executed
Query: INSERT INTO Cars VALUES(2,'Mercedes',57127) executed
Query: INSERT INTO Cars VALUES(3,'Skoda',9000) executed
Query: INSERT INTO Cars VALUES(4,'Volvo',29000) executed
Query: INSERT INTO Cars VALUES(5,'Bentley',350000) executed
Query: INSERT INTO Cars VALUES(6,'Citroen',21000) executed
Query: INSERT INTO Cars VALUES(7,'Hummer',41400) executed
Query: INSERT INTO Cars VALUES(8,'Volkswagen',21600) executed

执行create_fill.php脚本。

mysql> SELECT * FROM Cars;
+----+------------+--------+
| Id | Name       | Price  |
+----+------------+--------+
|  1 | Audi       |  52642 |
|  2 | Mercedes   |  57127 |
|  3 | Skoda      |   9000 |
|  4 | Volvo      |  29000 |
|  5 | Bentley    | 350000 |
|  6 | Citroen    |  21000 |
|  7 | Hummer     |  41400 |
|  8 | Volkswagen |  21600 |
+----+------------+--------+
8 rows in set (0.00 sec)

数据插入到Cars表中。

检索数据

现在我们已经将一些数据插入数据库中,我们想要取回它。

<?php

$host = "localhost"; 
$user = "user12"; 
$pass = "34klq*"; 
$db = "mydb";

$r = mysql_connect($host, $user, $pass);

if (!$r) {
    echo "Could not connect to server\n";
    trigger_error(mysql_error(), E_USER_ERROR);
} else {
    echo "Connection established\n"; 
}

$r2 = mysql_select_db($db);

if (!$r2) {
    echo "Cannot select database\n";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Database selected\n";
}

$query = "SELECT * FROM Cars LIMIT 5";

$rs = mysql_query($query);

if (!$rs) {
    echo "Could not execute query: $query";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Query: $query executed\n";
} 

while ($row = mysql_fetch_assoc($rs)) {
    echo $row['Id'] . " " . $row['Name'] . " " . $row['Price'] . "\n";
}

mysql_close();

?>

在此示例中,我们从Cars表中检索五行。

$query = "SELECT * FROM Cars LIMIT 5";

该 SQL 语句从Cars表中选择 5 行。

$rs = mysql_query($query);

我们使用mysql_query()函数执行查询并检索结果集。

if (!$rs) {
    echo "Could not execute query: $query";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Query: $query executed\n";
} 

如果查询没有成功,我们将生成一条错误消息。

while ($row = mysql_fetch_assoc($rs)) {
    echo $row['Id'] . " " . $row['Name'] . " " . $row['Price'] . "\n";
}

我们遍历结果集并将数据打印到控制台。 mysql_fetch_assoc()函数返回与提取的行相对应的字符串关联数组,如果没有更多行,则返回FALSE。 换句话说,函数调用从结果集中返回一行。 该行采用关联数组的形式。 列名称是关联数组的键。 当结果集中没有更多行时,该函数将返回FALSE,而while循环终止。

$ php query.php
Connection established
Database selected
Query: SELECT * FROM Cars LIMIT 5 executed
1 Audi 52642
2 Mercedes 57127
3 Skoda 9000
4 Volvo 29000
5 Bentley 350000

这是示例的输出。

在第二个示例中,我们将使用mysql_fetch_row()函数获取数据。

<?php

$host = "localhost"; 
$user = "user12"; 
$pass = "34klq*"; 
$db = "mydb";

$r = mysql_connect($host, $user, $pass);

if (!$r) {
    echo "Could not connect to server\n";
    trigger_error(mysql_error(), E_USER_ERROR);
} else {
    echo "Connection established\n"; 
}

$r2 = mysql_select_db($db);

if (!$r2) {
    echo "Cannot select database\n";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Database selected\n";
}

$query = "SELECT Id, Name, Price From Cars LIMIT 5";

$rs = mysql_query($query);

if (!$rs) {
    echo "Could not execute query: $query";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Query: $query executed\n";
} 

$nrows = mysql_num_rows($rs);

for ($i = 0; $i < $nrows; $i++) {
    $row = mysql_fetch_row($rs);
    echo $row[0];
    echo " ";
    echo $row[1];
    echo " ";
    echo $row[1];
    echo "\n";
}

mysql_close();

?>

我们从Cars表中获得前 5 行。

$nrows = mysql_num_rows($rs);

mysql_num_rows()函数从结果集中获取行数。

for ($i = 0; $i < $nrows; $i++) {
    $row = mysql_fetch_row($rs);
    echo $row[0];
    echo " ";
    echo $row[1];
    echo " ";
    echo $row[1];
    echo "\n";
}

我们使用for循环遍历返回的行。 mysql_fetch_row()函数从结果集中以枚举数组的形式检索行。

$ php query.php
Connection established
Query: SELECT * FROM Cars LIMIT 5 executed
1 Audi 52642
2 Mercedes 57127
3 Skoda 9000
4 Volvo 29000
5 Bentley 350000

输出。

在下面的示例中,我们显示了如何从表中检索特定行。

<?php

$host = "localhost"; 
$user = "user12"; 
$pass = "34klq*"; 
$db = "mydb";

$r = mysql_connect($host, $user, $pass);

if (!$r) {
    echo "Could not connect to server\n";
    trigger_error(mysql_error(), E_USER_ERROR);
} else {
    echo "Connection established\n"; 
}

$r2 = mysql_select_db($db);

if (!$r2) {
    echo "Cannot select database\n";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Database selected\n";
}

$name = "Volkswagen";

$query = sprintf("SELECT Id, Name, Price From Cars Where Name = '%s'", 
    mysql_real_escape_string($name));

$rs = mysql_query($query);

if (!$rs) {
    echo "Could not execute query: $query\n";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Query: $query executed\n";
} 

while ($row = mysql_fetch_object($rs)) {
    echo $row->Id;
    echo " ";
    echo $row->Name;
    echo " ";
    echo $row->Price;
    echo "\n";
}

mysql_close();

?>

开发者在处理用户输入时必须考虑安全性问题。 我们必须始终处理来自外部世界的数据。 检查数据的有效性。

$name = "Volkswagen";

在脚本中,我们检查Caras表中是否有"Volkswagen"。 此值可能来自 XML 文件或 Web 表单。 我们将展示如何检查它。

$query = sprintf("SELECT Id, Name, Price From Cars Where Name = '%s'", 
    mysql_real_escape_string($name));

我们使用sprintf()函数构建 SQL 语句。 我们使用mysql_real_escape_string()函数处理$name变量。 此函数转义字符串中的特殊字符以用于 SQL 语句。 这样可以防止 SQL 注入攻击和数据损坏。 处理完变量后,将其放入 SQL 语句字符串中。

while ($row = mysql_fetch_object($rs)) {
    echo $row->Id;
    echo " ";
    echo $row->Name;
    echo " ";
    echo $row->Price;
    echo "\n";
}

我们使用mysql_fetch_object()函数获取数据。 该函数获取结果行作为对象。 然后,我们使用对象表示法获取表列。

$ php query3.php
Connection established
Database selected
Query: SELECT Id, Name, Price From Cars Where Name = 'Volkswagen' executed
8 Volkswagen 21600

示例的输出。 我们找到了汽车,并将整行打印到控制台。

转义字符

我们将有一个小示例演示如何转义字符。 有些字符在数据库环境中被认为是不安全的。 其中之一是单引号字符。

mysql> CREATE TABLE IF NOT EXISTS Authors(Id INT PRIMARY KEY AUTO_INCREMENT, 
    ->     Name VARCHAR(25)) ENGINE=InnoDB;
Query OK, 0 rows affected (0.09 sec)

对于该示例,我们创建一个Authors表。

<?php

$host = "localhost"; 
$user = "user12"; 
$pass = "34klq*"; 
$db = "mydb"; 

$r = mysql_connect($host, $user, $pass);

if (!$r) {
    echo "Could not connect to server\n";
    trigger_error(mysql_error(), E_USER_ERROR);
} else {
    echo "Connection established\n"; 
}

$r2 = mysql_select_db($db);

if (!$r2) {
    echo "Cannot select database\n";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Database selected\n";
}

$name = "O'Neill";
$name_es = mysql_real_escape_string($name);

$query = "INSERT INTO Authors(Name) VALUES('$name_es')";
$rs = mysql_query($query);

if (!$rs) {
    echo "Could not execute query: $query\n";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Query: $query executed\n";
} 

mysql_close();

?>

我们在Authors表中插入一个新作者。 作者的名字叫奥尼尔。 该名称具有不安全的单引号字符。

$name_es = mysql_real_escape_string($name);

这就是为什么我们使用mysql_real_escape_string()函数来转义此字符。

$query = "INSERT INTO Authors(Name) VALUES('$name_es')";
$rs = mysql_query($query);

我们创建该语句并执行它。

mysql> SELECT * FROM Authors;
+----+---------+
| Id | Name    |
+----+---------+
|  1 | O'Neill |
+----+---------+
1 row in set (0.00 sec)

该名称已成功写入表中。

列标题

接下来,我们将展示如何使用数据库表中的数据打印列标题。

<?php

$host = "localhost"; 
$user = "user12"; 
$pass = "34klq*"; 
$db = "mydb"; 

$r = mysql_connect($host, $user, $pass);

if (!$r) {
    echo "Could not connect to server\n";
    trigger_error(mysql_error(), E_USER_ERROR);
} else {
    echo "Connection established\n"; 
}

$r2 = mysql_select_db($db);

if (!$r2) {
    echo "Cannot select database\n";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Database selected\n";
}

$query = "SELECT * From Cars LIMIT 8";

$rs = mysql_query($query);

if (!$rs) {
    echo "Could not execute query: $query";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Query: $query executed\n";
} 

$cname1 = mysql_fetch_field($rs, 0);
$cname2 = mysql_fetch_field($rs, 1);
$cname3 = mysql_fetch_field($rs, 2);

printf("%3s %-11s %8s\n", $cname1->name, $cname2->name, 
    $cname3->name);

while ($row = mysql_fetch_row($rs)) {
    printf("%3s %-11s %8s\n", $row[0], $row[1], $row[2]);
}

mysql_close();

?>

同样,我们将Writers表的内容打印到控制台。 现在,我们也包括列的名称。

$cname1 = mysql_fetch_field($rs, 0);
$cname2 = mysql_fetch_field($rs, 1);
$cname3 = mysql_fetch_field($rs, 2);

要获取特定的字段名称,我们利用mysql_fetch_field()函数。 该函数返回一个包含列信息的对象。

printf("%3s %-11s %8s\n", $cname1->name, $cname2->name, 
    $cname3->name);

列名称已打印并格式化。 name属性包含列名。

$ php columns.php
Connection established
Database selected
Query: SELECT * From Cars LIMIT 8 executed
 Id Name           Price
  1 Audi           52642
  2 Mercedes       57127
  3 Skoda           9000
  4 Volvo          29000
  5 Bentley       350000
  6 Citroen        21000
  7 Hummer         41400
  8 Volkswagen     21600

脚本的输出。

行和列数

以下脚本计算查询返回的字段/列和行数。

<?php

$host = "localhost"; 
$user = "user12"; 
$pass = "34klq*"; 
$db = "mydb";

$r = mysql_connect($host, $user, $pass);

if (!$r) {
    echo "Could not connect to server\n";
    trigger_error(mysql_error(), E_USER_ERROR);
} else {
    echo "Connection established\n"; 
}

$r2 = mysql_select_db($db);

if (!$r2) {
    echo "Cannot select database\n";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Database selected\n";
}

$query = "SELECT * FROM Cars WHERE Id IN (1, 2, 3)";

$rs = mysql_query($query);

if (!$rs) {
    echo "Could not execute query: $query\n";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Query: $query executed\n";
} 

echo "We have " . mysql_num_fields($rs) . " fields\n";
echo "We have " . mysql_num_rows($rs) . " rows\n";

print_r(mysql_fetch_row($rs));

mysql_close();

?>

我们从Cars表中选择三行。 我们计算查询返回的行数和列数。

$query = "SELECT * FROM Cars WHERE Id IN (1, 2, 3)";

这是要执行的查询。 它从Cars表中选择前三行。

echo "We have " . mysql_num_fields($rs) . " fields\n";

mysql_num_fields()返回查询返回的字段数。

echo "We have " . mysql_num_rows($rs) . " rows\n";

mysql_num_rows()返回查询返回的行数。

print_r(mysql_fetch_row($rs));

我们打印数组的内容。

$ php fields_rows.php
Connection established
Database selected
Query: SELECT * FROM Cars WHERE Id IN (1, 2, 3) executed
We have 3 fields
We have 3 rows
Array
(
    [0] => 1
    [1] => Audi
    [2] => 52642
)

运行脚本。

写入图像

有些人喜欢将其图像放入数据库中,有些人则希望将其保留在文件系统中以供其应用使用。 当我们处理大量图像时,会出现技术难题。 图像是二进制数据。 MySQL 数据库具有一种特殊的数据类型来存储称为BLOB(二进制大对象)的二进制数据。

mysql> CREATE TABLE Images(Id INT PRIMARY KEY AUTO_INCREMENT, Data MEDIUMBLOB);
Query OK, 0 rows affected (0.06 sec)

对于此示例,我们创建一个名为Images的新表。

<?php

$host = "localhost"; 
$user = "user12"; 
$pass = "34klq*"; 
$db = "mydb";

$r = mysql_connect($host, $user, $pass);

if (!$r) {
    echo "Could not connect to server\n";
    trigger_error(mysql_error(), E_USER_ERROR);
} else {
    echo "Connection established\n"; 
}

$r2 = mysql_select_db($db);

if (!$r2) {
    echo "Cannot select database\n";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Database selected\n";
}

$file = "woman.jpg";

$img = fopen($file, 'r');

if (!$img) {
    echo "Cannot open file for writing\n";
    trigger_error("Cannot open file for writing\n", E_USER_ERROR);
} 

$data = fread($img, filesize($file));

if (!$data) {
    echo "Cannot read image data\n";
    trigger_error("Cannot read image data\n", E_USER_ERROR);
}

$es_data = mysql_real_escape_string($data);
fclose($img);

$query = "INSERT INTO Images(Id, Data) Values(1, '$es_data')";

$rs = mysql_query($query);

if (!$rs) {
    echo "Could not execute query: $query";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Query successfully executed\n";
} 

mysql_close();

?>

在上面的脚本中,我们读取 JPG 图像并将其插入到Images表中。

$file = "woman.jpg";

这是我们从文件系统读取并写入数据库的映像名称。 它与脚本名称位于同一目录中。

$img = fopen($file, 'r');

if (!$img) {
    echo "Cannot open file for writing\n";
    trigger_error("Cannot open file for writing\n", E_USER_ERROR);
} 

$data = fread($img, filesize($file));

if (!$data) {
    echo "Cannot read image data\n";
    trigger_error("Cannot read image data\n", E_USER_ERROR);
}

我们打开并阅读图像。 fread()函数以字符串形式返回数据。

$es_data = mysql_real_escape_string($data);

我们逃避不安全的字符。

fclose($img);

我们关闭图像文件的句柄。

$query = "INSERT INTO Images(Id, Data) Values(1, '$es_data')";

$rs = mysql_query($query);

if (!$rs) {
    echo "Could not execute query: $query";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Query successfully executed\n";
} 

我们将数据插入到新创建的Images表中。

读取图像

在前面的示例中,我们已将图像插入数据库表中。 现在,我们将从表中读取图像。

<?php

$host = "localhost"; 
$user = "user12"; 
$pass = "34klq*"; 
$db = "mydb";

$r = mysql_connect($host, $user, $pass);

if (!$r) {
    echo "Could not connect to server\n";
    trigger_error(mysql_error(), E_USER_ERROR);
} else {
    echo "Connection established\n"; 
}

$r2 = mysql_select_db($db);

if (!$r2) {
    echo "Cannot select database\n";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Database selected\n";
}

$query = "SELECT Data FROM Images WHERE Id=1";

$rs = mysql_query($query);

if (!$rs) {
    echo "Could not execute query: $query";
    trigger_error(mysql_error(), E_USER_ERROR); 
} else {
    echo "Query: $query executed\n";
} 

$row = mysql_fetch_row($rs);

$file = "woman2.jpg";

$img = fopen($file, 'wb');

if (!$img) {
    echo "Cannot open file for writing\n";
    trigger_error("Cannot open file for writing\n", E_USER_ERROR);
} 

$r3 = fwrite($img, $row[0]);

if (!$r3) {
    echo "Cannot write image to file\n";
    trigger_error("Cannot write image to file\n", E_USER_ERROR);
} 

fclose($img);

mysql_close();

?>

我们从Images表中读取了一张图像。

$query = "SELECT Data FROM Images WHERE Id=1";

我们从表中选择一条记录。

$row = mysql_fetch_row($rs);

我们从结果集中获取一行。 只有一行包含图像数据。

$file = "woman2.jpg";

我们将创建一个名为woman2.jpg的新图像文件。

$img = fopen($file, 'wb');

if (!$img) {
    echo "Cannot open file for writing\n";
    trigger_error("Cannot open file for writing\n", E_USER_ERROR);
} 

我们打开一个可写的二进制文件。

$r3 = fwrite($img, $row[0]);

if (!$r3) {
    echo "Cannot write image to file\n";
    trigger_error("Cannot write image to file\n", E_USER_ERROR);
} 

我们使用fwrite()函数将数据写入文件系统。

现在我们在当前目录中应该有一个名为woman2.jpg的映像。 我们可以检查它是否与我们插入表中的图像相同。

事务支持

事务是针对一个或多个数据库中数据的数据库操作的基本单位。 事务中所有 SQL 语句的影响可以全部提交给数据库,也可以全部回滚。

MySQL 数据库具有不同类型的存储引擎。 最常见的是 MyISAM 和 InnoDB 引擎。 MyISAM 是默认的。 在数据安全性和数据库速度之间需要权衡。 MyISAM 表的处理速度更快,并且不支持事务。 另一方面,InnoDB 表可以更安全地防止数据丢失。 他们支持事务。 它们处理较慢。

<?php

mysql_connect('localhost', 'testuser', 'test623') 
    or die("cannot connect to database\n");

mysql_select_db("testdb") or die(mysql_error());

$r1 = mysql_query("UPDATE Writers SET Name = 'Leo Tolstoy' WHERE Id = 1")
    or die(mysql_error());

$r2 = mysql_query("UPDATE Writers SET Name = 'Boris Pasternak' WHERE Id = 2")
    or die(mysql_error());

$r3 = mysql_query("UPDATE Writer SET Name = 'Leonid Leonov' WHERE Id = 3")
    or die(mysql_error());

mysql_close();

?>

在此脚本中,我们尝试更新三行。 该表的存储引擎是 MyISAM。

$r1 = mysql_query("UPDATE Writers SET Name = 'Leo Tolstoy' WHERE Id = 1")
    or die(mysql_error());

$r2 = mysql_query("UPDATE Writers SET Name = 'Boris Pasternak' WHERE Id = 2")
    or die(mysql_error());

在这里,我们要更改第 1 行和第 2 行的作者姓名。

$r3 = mysql_query("UPDATE Writer SET Name = 'Leonid Leonov' WHERE Id = 3")
    or die(mysql_error());

SQL 语句中有错误。 没有Writer表。

$ php update.php
Table 'testdb.Writer' doesn't exist

mysql> SELECT * FROM Writers;
+----+-------------------+
| Id | Name              |
+----+-------------------+
|  1 | Leo Tolstoy       |
|  2 | Boris Pasternak   |
|  3 | Lion Feuchtwanger |
|  4 | Emile Zola        |
|  5 | Truman Capote     |
|  6 | O'Neill           |
+----+-------------------+
6 rows in set (0.00 sec)

运行脚本会出现错误。 但是,正如我们所看到的,前两行已经更改。

在本教程的最后一个示例中,我们将重新创建Writers表。 这次,该表将为 InnoDB 类型。 InnoDB MySQL 数据库表支持事务。

DROP TABLE Writers;

CREATE TABLE IF NOT EXISTS Writers(Id INT PRIMARY KEY AUTO_INCREMENT, 
    Name VARCHAR(25)) ENGINE=INNODB;

INSERT INTO Writers(Name) VALUES('Jack London');
INSERT INTO Writers(Name) VALUES('Honore de Balzac');
INSERT INTO Writers(Name) VALUES('Lion Feuchtwanger');
INSERT INTO Writers(Name) VALUES('Emile Zola');
INSERT INTO Writers(Name) VALUES('Truman Capote');

这是writers.sql文件。 它用于重新创建Writers表。

mysql> source writers.sql
Query OK, 0 rows affected (0.03 sec)

Query OK, 0 rows affected (0.10 sec)

Query OK, 1 row affected (0.02 sec)

Query OK, 1 row affected (0.03 sec)

Query OK, 1 row affected (0.02 sec)

Query OK, 1 row affected (0.02 sec)

Query OK, 1 row affected (0.02 sec)

我们可以使用source命令来加载和执行 SQL 脚本。

<?php

mysql_connect('localhost', 'testuser', 'test623') 
    or die("cannot connect to database\n");

mysql_select_db("testdb") or die(mysql_error());

mysql_query("SET AUTOCOMMIT=0");
mysql_query("START TRANSACTION");

$r1 = mysql_query("DELETE FROM Writers WHERE Id = 3")
    or die(mysql_error());

$r2 = mysql_query("DELETE FROM Writers WHERE Id = 4")
    or die(mysql_error());

$r3 = mysql_query("DELETE FROM Writer WHERE Id = 5")
    or die(mysql_error());

if ($r1 and $r2 and $r3) {
    mysql_query("COMMIT");
} else {
    mysql_query("ROLLBACK");
}

mysql_close();

?>

现在,我们将执行上面的脚本。 我们要从表中删除三行。 第三个 SQL 语句有一个错误。

mysql_query("START TRANSACTION");

START TRANSACTION语句开始一个新的事务。 必须使用COMMIT语句将所有更改永久化,或使用ROLLBACK语句将其忽略。

if ($r1 and $r2 and $r3) {
    mysql_query("COMMIT");
} else {
    mysql_query("ROLLBACK");
}

仅当所有三个 SQL 语句都返回True时,才提交语句。 否则,我们将它们回滚。 在我们的情况下,$r3变量为False,因此不会使语句成为永久语句,也不会从表中删除行。

$ php transaction.php
Table 'testdb.Writer' doesn't exist

mysql> SELECT * FROM Writers;
+----+-------------------+
| Id | Name              |
+----+-------------------+
|  1 | Jack London       |
|  2 | Honore de Balzac  |
|  3 | Lion Feuchtwanger |
|  4 | Emile Zola        |
|  5 | Truman Capote     |
+----+-------------------+
5 rows in set (0.00 sec)

在将更改提交到数据库之前,发生了错误。 调用ROLLBACK语句,并且没有发生删除。

Tweet

这是 MySQL PHP 教程。 您可能也对 MySQL C API 教程MySQL Python 教程MySQL Visual Basic 教程感兴趣。

MySQL Java 教程

原文: http://zetcode.com/db/mysqljava/

这是 MySQL 数据库的 Java 教程。 它涵盖了使用 JDBC 进行 Java 中 MySQL 编程的基础。 ZetCode 拥有用于 MySQL Java 的完整电子书: MySQL Java 编程电子书

Tweet

在本教程中,我们使用 MySQL Connector/J 驱动程序。 它是 MySQL 的官方 JDBC 驱动程序。 这些示例是在 Ubuntu Linux 上创建和测试的。 您可能还需要查看 Java 教程PostgreSQL Java 教程Apache Derby 教程MySQL 教程Spring JdbcTemplate ZetCode 的教程

JDBC

JDBC 是 Java 编程语言的 API,用于定义客户端如何访问数据库。 它提供了查询和更新数据库中数据的方法。 JDBC 面向关系数据库。 从技术角度来看,API 是java.sql包中的一组类。 要将 JDBC 与特定数据库一起使用,我们需要该数据库的 JDBC 驱动程序。

JDBC 是 Java 数据库编程的基石。 今天,它被认为是非常底层的,并且容易出错。 创建了诸如 MyBatis 或JdbcTemplate之类的解决方案来减轻 JDBC 编程的负担。 但是,实际上,这些解决方案仍然使用 JDBC。 JDBC 是 Java Standard Edition 平台的一部分。

JDBC 管理以下三个主要的编程活动:

  • 连接到数据库;
  • 向数据库发送查询和更新语句;
  • 检索和处理从数据库接收到的结果以响应查询。

MySQL Connector/J

为了以 Java 连接到 MySQL,MySQL 提供了 MySQL Connector/J,它是实现 JDBC API 的驱动程序。 MySQL Connector/J 是 JDBC Type 4 驱动程序。 Type 4 表示驱动程序是 MySQL 协议的纯 Java 实现,并且不依赖 MySQL 客户端库。 在本教程中,我们使用 MySQL Connector/J 5.1.41,它是 5.1 生产分支的维护版本。

连接字符串

使用连接字符串定义数据库连接。 它包含诸如数据库类型,数据库名称,服务器名称和端口号之类的信息。 它还可能包含用于配置的其他键/值对。 每个数据库都有其自己的连接字符串格式。

以下是 MySQL 连接字符串的语法:

jdbc:mysql://[host1][:port1][,[host2][:port2]]...[/[database]] 
    [?propertyName1=propertyValue1[&propertyName2=propertyValue2]...]

可以为服务器故障转移设置指定多个主机。 方括号中的项目是可选的。 如果未指定主机,则主机名默认为localhost。 如果未指定主机端口,则默认为 MySQL 服务器的默认端口号 3306。

jdbc:mysql://localhost:3306/testdb?useSSL=false

这是一个 MySQL 连接字符串的示例。 jdbc:mysql://被称为子协议,对于 MySQL 来说是恒定的。 我们在 MySQL 标准端口 3306 上连接到localhost。数据库名称为testdb。 其他键/值对在问号字符(?)之后。 useSSL=false告诉 MySQL,将没有安全连接。

关于 MySQL 数据库

MySQL 是领先的开源数据库管理系统。 它是一个多用户,多线程的数据库管理系统。 MySQL 在网络上特别流行。 它是由 Linux,Apache,MySQL 和 PHP 组成的非常流行的 LAMP 平台的一部分。 目前,MySQL 由 Oracle 拥有。 在大多数重要的 OS 平台上都可以使用 MySQL 数据库。 它可以在 BSD Unix,Linux,Windows 或 Mac OS 上运行。 维基百科和 YouTube 使用 MySQL。 这些站点每天管理数百万个查询。 MySQL 有两个版本:MySQL 服务器系统和 MySQL 嵌入式系统。

设置 MySQL

在本节中,我们将安装 MySQL 服务器,创建testdb数据库和测试用户。

$ sudo apt-get install mysql-server

此命令将安装 MySQL 服务器和其他各种包。 在安装包时,提示我们输入 MySQL 根帐户的密码。

接下来,我们将创建一个新的数据库用户和一个新的数据库。 我们使用mysql客户端。

$ sudo service mysql status
mysql start/running, process 5129

我们检查 MySQL 服务器是否正在运行。 如果没有,我们需要启动服务器。 在 Ubuntu Linux 上,可以使用sudo service mysql start命令来完成。

$ mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 43
Server version: 5.7.21-0ubuntu0.16.04.1 (Ubuntu)

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema | 
| mysql              | 
+--------------------+
2 rows in set (0.00 sec)

我们使用 mysql 监视器客户端应用连接到服务器。 我们使用根帐户连接到数据库。 我们用SHOW DATABASES语句显示所有可用的数据库。

mysql> CREATE DATABASE testdb;
Query OK, 1 row affected (0.02 sec)

我们创建一个新的testdb数据库。 在整个教程中,我们将使用此数据库。

mysql> CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'test623';
Query OK, 0 rows affected (0.00 sec)

mysql> USE testdb;
Database changed

mysql> GRANT ALL ON testdb.* TO 'testuser'@'localhost';
Query OK, 0 rows affected (0.00 sec)

mysql> quit;
Bye

我们创建一个新的数据库用户。 我们为该用户授予testdb数据库所有表的所有特权。

Maven 文件

我们使用以下 Maven 文件:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zetcode</groupId>
    <artifactId>AppName</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.45</version>
        </dependency>    
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <configuration>
                    <mainClass>com.zetcode.AppName</mainClass>
                    <cleanupDaemonThreads>false</cleanupDaemonThreads>
                </configuration>
            </plugin>
        </plugins>
    </build>             

    <name>AppName</name>
</project>

POM 文件具有 MySQL 驱动程序的依赖关系。 我们还包括用于执行 Maven Java 程序的exec-maven-plugin。 在<mainClass></mainClass>标签之间,我们提供了应用的全名。

Java MySQL 版本

如果以下程序运行正常,则我们已安装一切正常。 我们检查 MySQL 服务器的版本。

JdbcMySQLVersion.java

package com.zetcode;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JdbcMySQLVersion {

    public static void main(String[] args) {

        String url = "jdbc:mysql://localhost:3306/testdb?useSSL=false";
        String user = "testuser";
        String password = "test623";

        String query = "SELECT VERSION()";

        try (Connection con = DriverManager.getConnection(url, user, password);
            Statement st = con.createStatement();
            ResultSet rs = st.executeQuery(query)) {

            if (rs.next()) {

                System.out.println(rs.getString(1));
            }

        } catch (SQLException ex) {

            Logger lgr = Logger.getLogger(JdbcMySQLVersion.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        } 
    }
}

我们连接到数据库并获取有关 MySQL 服务器的一些信息。

String url = "jdbc:mysql://localhost:3306/testdb?useSSL=false";

这是 MySQL 数据库的连接 URL。 每个驱动程序对于 URL 都有不同的语法。 在本例中,我们提供了主机,端口和数据库名称。

try (Connection con = DriverManager.getConnection(url, user, password);
    Statement st = con.createStatement();
    ResultSet rs = st.executeQuery(query)) {

我们使用连接 URL,用户名和密码建立与数据库的连接。 通过getConnection()方法建立连接。

连接对象的createStatement()方法创建一个Statement对象,用于将 SQL 语句发送到数据库。

连接对象的executeQuery()方法执行给定的 SQL 语句,该语句返回单个ResultSet对象。 ResultSet是由特定 SQL 语句返回的数据表。

try-with-resources语法可确保最终清除资源。

if (result.next()) {

    System.out.println(result.getString(1));
}

ResultSet对象维护一个游标,该游标指向其当前数据行。 最初,光标位于第一行之前。 next()方法将光标移动到下一行。 如果没有剩余的行,则该方法返回falsegetString()方法检索指定列的值。 第一列的索引为 1。

} catch (SQLException ex) {

    Logger lgr = Logger.getLogger(JdbcMySQLVersion.class.getName());
    lgr.log(Level.SEVERE, ex.getMessage(), ex);
} 

如果发生异常,我们将记录错误消息。 对于此控制台示例,该消息显示在终端中。

$ mvn exec:java -q
5.7.21-0ubuntu0.16.04.1

我们从命令行运行程序。 Manen 的-q选项在安静模式下运行 Maven。 即我们只看到错误消息。

创建和填充表

接下来,我们将创建数据库表并用数据填充它们。 这些表将在本教程中使用。

mysql_tables.sql

USE testdb;

DROP TABLE IF EXISTS Books, Authors, Testing, Images;

CREATE TABLE Authors(Id BIGINT PRIMARY KEY AUTO_INCREMENT, Name VARCHAR(100));
CREATE TABLE Books(Id BIGINT PRIMARY KEY AUTO_INCREMENT, AuthorId BIGINT, 
    Title VARCHAR(100), FOREIGN KEY(AuthorId) REFERENCES Authors(Id) 
    ON DELETE CASCADE);
CREATE TABLE Testing(Id INT);
CREATE TABLE Images(Id INT PRIMARY KEY AUTO_INCREMENT, Data MEDIUMBLOB);

INSERT INTO Authors(Id, Name) VALUES(1, 'Jack London');
INSERT INTO Authors(Id, Name) VALUES(2, 'Honore de Balzac');
INSERT INTO Authors(Id, Name) VALUES(3, 'Lion Feuchtwanger');
INSERT INTO Authors(Id, Name) VALUES(4, 'Emile Zola');
INSERT INTO Authors(Id, Name) VALUES(5, 'Truman Capote');

INSERT INTO Books(Id, AuthorId, Title) VALUES(1, 1, 'Call of the Wild');
INSERT INTO Books(Id, AuthorId, Title) VALUES(2, 1, 'Martin Eden');
INSERT INTO Books(Id, AuthorId, Title) VALUES(3, 2, 'Old Goriot');
INSERT INTO Books(Id, AuthorId, Title) VALUES(4, 2, 'Cousin Bette');
INSERT INTO Books(Id, AuthorId, Title) VALUES(5, 3, 'Jew Suess');
INSERT INTO Books(Id, AuthorId, Title) VALUES(6, 4, 'Nana');
INSERT INTO Books(Id, AuthorId, Title) VALUES(7, 4, 'The Belly of Paris');
INSERT INTO Books(Id, AuthorId, Title) VALUES(8, 5, 'In Cold blood');
INSERT INTO Books(Id, AuthorId, Title) VALUES(9, 5, 'Breakfast at Tiffany');

SQL 命令创建四个数据库表:AuthorsBooksTestingImages。 这些表是 InnoDB 类型的。 InnoDB 数据库支持外键约束和事务。 我们将外键约束放置在Books表的AuthorId列上。 我们用初始数据填充AuthorsBooks表。

mysql> source mysql_tables.sql
Query OK, 0 rows affected (0.07 sec)
Query OK, 0 rows affected (0.12 sec)
Query OK, 1 row affected (0.04 sec)
...

我们使用source命令执行tables.sql脚本。

Java MySQL 预备语句

现在,我们将以预备语句来关注自己。 在编写预备语句时,我们使用占位符,而不是直接将值写入语句中。 预准备的语句可提高安全性和性能。

在 Java 中,PreparedStatement是代表预编译的 SQL 语句的对象。

JdbcPrepared.java

package com.zetcode;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JdbcPrepared {

    public static void main(String[] args) {

        String url = "jdbc:mysql://localhost:3306/testdb?useSSL=false";
        String user = "testuser";
        String password = "test623";

        String author = "Trygve Gulbranssen";
        String sql = "INSERT INTO Authors(Name) VALUES(?)";

        try (Connection con = DriverManager.getConnection(url, user, password);
                PreparedStatement pst = con.prepareStatement(sql)) {

            pst.setString(1, author);
            pst.executeUpdate();

            System.out.println("A new author has been inserted");

        } catch (SQLException ex) {

            Logger lgr = Logger.getLogger(JdbcPrepared.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);

        }
    }
}

我们将新作者添加到Authors表中。

try (Connection con = DriverManager.getConnection(url, user, password);
        PreparedStatement pst = con.prepareStatement(sql)) {

在这里,我们创建一个预备语句。 在编写预备语句时,我们使用占位符,而不是直接将值写入语句中。 预备语句更快,并且可以防止 SQL 注入攻击。 ?是一个占位符,稍后将被填充。

pst.setString(1, author);

值绑定到占位符。

pst.executeUpdate();

执行预备语句。 当我们不希望返回任何数据时,我们使用语句对象的executeUpdate()方法。 这是当我们创建数据库或执行INSERTUPDATEDELETE语句时。

$ mvn exec:java -q
A new author has been inserted
mysql> select * from Authors;
+----+--------------------+
| Id | Name               |
+----+--------------------+
|  1 | Jack London        |
|  2 | Honore de Balzac   |
|  3 | Lion Feuchtwanger  |
|  4 | Emile Zola         |
|  5 | Truman Capote      |
|  6 | Trygve Gulbranssen |
+----+--------------------+
6 rows in set (0.00 sec)

我们在表中插入了一位新作者。

测试 MySQL 预备和非预备的语句

对于以下两个示例,我们将使用Testing表。 我们将执行一条普通语句和一条准备语句 5000 次。 我们检查执行时间是否有差异。

JdbcNotPreparedTesting.java

package com.zetcode;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JdbcNotPreparedTesting {

    public static void main(String[] args) {

        String cs = "jdbc:mysql://localhost:3306/testdb?useSSL=false";
        String user = "testuser";
        String password = "test623";

        try (Connection con = DriverManager.getConnection(cs, user, password);
                Statement st = con.createStatement()) {

            for (int i = 1; i <= 5000; i++) {

                String sql = "INSERT INTO Testing(Id) VALUES(" + 2 * i + ")";
                st.executeUpdate(sql);
            }

        } catch (SQLException ex) {

            Logger lgr = Logger.getLogger(JdbcNotPreparedTesting.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }
}

第一个示例使用普通的Statement对象。

for (int i = 1; i <= 5000; i++) {

    String sql = "INSERT INTO Testing(Id) VALUES(" + 2 * i + ")";
    st.executeUpdate(sql);
}

我们建立查询并执行 5000 次。

$ time mvn exec:java -q

real    4m14.716s
user    0m6.820s
sys     0m0.404s

完成 5000 次插入需要 4.14 分钟。

JdbcPreparedTesting.java

package com.zetcode;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JdbcPreparedTesting {

    public static void main(String[] args) {

        String cs = "jdbc:mysql://localhost:3306/testdb?useSSL=false";
        String user = "testuser";
        String password = "test623";

        String sql = "INSERT INTO Testing(Id) VALUES(?)";

        try (Connection con = DriverManager.getConnection(cs, user, password);
                PreparedStatement pst = con.prepareStatement(sql)) {

            for (int i = 1; i <= 5000; i++) {

                pst.setInt(1, i * 2);
                pst.executeUpdate();
            }

        } catch (SQLException ex) {

            Logger lgr = Logger.getLogger(JdbcPreparedTesting.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }
}

现在,我们使用PreparedStatement执行相同的任务。

try (Connection con = DriverManager.getConnection(cs, user, password);
        PreparedStatement pst = con.prepareStatement(sql)) {

我们使用prepareStatement()方法创建预备语句。

for (int i = 1; i <= 5000; i++) {

    pst.setInt(1, i * 2);
    pst.executeUpdate();
}

我们将一个值绑定到预备语句,并在循环中执行数千次。

$ time mvn exec:java -q

real    3m53.962s
user    0m6.280s
sys     0m0.380s

现在花了 3.53 分钟完成了 5000 次插入。 我们节省了 20 秒。

Java MySQL 检索数据

接下来,我们将展示如何从数据库表中检索数据。 我们从Authors表中获取所有数据。

JdbcRetrieve.java

package com.zetcode;

import java.sql.PreparedStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JdbcRetrieve {

    public static void main(String[] args) {

        String url = "jdbc:mysql://localhost:3306/testdb?useSSL=false";
        String user = "testuser";
        String password = "test623";

        String query = "SELECT * FROM Authors";

        try (Connection con = DriverManager.getConnection(url, user, password);
                PreparedStatement pst = con.prepareStatement(query);
                ResultSet rs = pst.executeQuery()) {

            while (rs.next()) {

                System.out.print(rs.getInt(1));
                System.out.print(": ");
                System.out.println(rs.getString(2));
            }

        } catch (SQLException ex) {

            Logger lgr = Logger.getLogger(JdbcRetrieve.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }
}

我们从Authors表中获取所有作者并将其打印到控制台。

String query = "SELECT * FROM Authors";

try (Connection con = DriverManager.getConnection(url, user, password);
        PreparedStatement pst = con.prepareStatement(query);
        ResultSet rs = pst.executeQuery()) {

我们执行一个查询,该查询从Authors表中选择所有列。 我们使用executeQuery()方法。 该方法执行给定的 SQL 语句,该语句返回单个ResultSet对象。 ResultSet是 SQL 查询返回的数据表。

while (rs.next()) {

      System.out.print(rs.getInt(1));
      System.out.print(": ");
      System.out.println(rs.getString(2));
}

next()方法将光标移至下一条记录。 当结果集中没有更多行时,它将返回falsegetInt()getString()方法检索此ResultSet对象当前行中指定列的值,作为 Java 编程语言的intString

$ mvn exec:java -q
1: Jack London
2: Honore de Balzac
3: Lion Feuchtwanger
4: Emile Zola
5: Truman Capote
6: Trygve Gulbranssen

我们执行程序; 我们在控制台上印有作者的 ID 和姓名。

属性

通常的做法是将配置数据放在程序外部的单独文件中。 这样程序员可以更加灵活。 我们可以更改用户,密码或连接 URL,而无需重新编译程序。 它在需要大量测试,调试,保护数据等的动态环境中特别有用。

在 Java 中,Properties是为此经常使用的类。 该类用于轻松读取和保存键/值属性。

db.properties

db.url=jdbc:mysql://localhost:3306/testdb?useSSL=false
db.user=testuser
db.passwd=test623

我们有一个db.properties文件,其中有三个键/值对。 这些是在程序执行期间动态加载的。

JdbcProperties.java

package com.zetcode;

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JdbcProperties {

    private static Properties getConnectionData() {

        Properties props = new Properties();

        String fileName = "src/main/resources/db.properties";

        try (FileInputStream in = new FileInputStream(fileName)) {
            props.load(in);
        } catch (IOException ex) {
            Logger lgr = Logger.getLogger(JdbcProperties.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }

        return props;
    }

    public static void main(String[] args) {

        Properties props = getConnectionData();

        String url = props.getProperty("db.url");
        String user = props.getProperty("db.user");
        String passwd = props.getProperty("db.passwd");

        String query = "SELECT * FROM Authors";

        try (Connection con = DriverManager.getConnection(url, user, passwd);
                PreparedStatement pst = con.prepareStatement(query);
                ResultSet rs = pst.executeQuery()) {

            while (rs.next()) {

                System.out.print(rs.getInt(1));
                System.out.print(": ");
                System.out.println(rs.getString(2));
            }

        } catch (SQLException ex) {

            Logger lgr = Logger.getLogger(JdbcProperties.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }
}

我们连接到testdb数据库,并将Authors表的内容打印到控制台。 这次,我们从文件加载连接属性。 他们没有在程序中硬编码。

Properties props = new Properties();

String fileName = "src/main/resources/db.properties";

try (FileInputStream in = new FileInputStream(fileName)) {
    props.load(in);
} catch (IOException ex) {
    Logger lgr = Logger.getLogger(JdbcProperties.class.getName());
    lgr.log(Level.SEVERE, ex.getMessage(), ex);
}

创建Properties类。 数据是从名为db.properties的文件中加载的,其中包含我们的配置数据。

String url = props.getProperty("db.url");
String user = props.getProperty("db.user");
String passwd = props.getProperty("db.passwd");

使用getProperty()方法检索这些值。

Java MySQL 数据源

在此示例中,我们使用数据源连接到数据库。 数据源的使用可以提高应用的性能和可伸缩性。 与DriverManager相比,使用数据源具有多个优点:增强了可移植性,连接池和分布式事务。

MysqlDataSource是用于创建数据源的类。

db.properties

# mysql properties
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/testdb?useSSL=false
mysql.username=testuser
mysql.password=test623

是 MySQL 数据库的属性。

ComLineDSEx.java

package com.zetcode;

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ComLineDSEx {

    public static MysqlDataSource getMySQLDataSource() {

        Properties props = new Properties();

        String fileName = "src/main/resources/db.properties";

        try (FileInputStream fis = new FileInputStream(fileName)) {
            props.load(fis);
        } catch (IOException ex) {
            Logger lgr = Logger.getLogger(ComLineDSEx.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }

        MysqlDataSource ds = new MysqlDataSource();
        ds.setURL(props.getProperty("mysql.url"));
        ds.setUser(props.getProperty("mysql.username"));
        ds.setPassword(props.getProperty("mysql.password"));

        return ds;
    }

    public static void main(String[] args) {

        MysqlDataSource ds = getMySQLDataSource();

        String query = "SELECT VERSION()";

        try (Connection con = ds.getConnection();
                PreparedStatement pst = con.prepareStatement(query);
                ResultSet rs = pst.executeQuery()) {

            if (rs.next()) {

                String version = rs.getString(1);
                System.out.println(version);
            }
        } catch (SQLException ex) {

            Logger lgr = Logger.getLogger(ComLineDSEx.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }
}

在此示例中,我们使用数据源连接到数据库。

String fileName = "src/main/resources/db.properties";

try (FileInputStream fis = new FileInputStream(fileName)) {
    props.load(fis);
} catch (IOException ex) {
    Logger lgr = Logger.getLogger(ComLineDSEx.class.getName());
    lgr.log(Level.SEVERE, ex.getMessage(), ex);
}

db.properties文件中读取数据库属性。

MysqlDataSource ds = new MysqlDataSource();
ds.setURL(props.getProperty("mysql.url"));
ds.setUser(props.getProperty("mysql.username"));
ds.setPassword(props.getProperty("mysql.password"));

创建MysqlDataSource并设置数据源属性。

try (Connection con = ds.getConnection();
        PreparedStatement pst = con.prepareStatement(query);
        ResultSet rs = pst.executeQuery()) {

连接对象是从数据源创建的。

Java MySQL 多条语句

可以在一个查询中执行多个 SQL 语句。 必须将allowMultiQueries设置为启用 MySQL 中的多个语句。

JdbcMulStat.java

package com.zetcode;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JdbcMulStat {

    public static void main(String[] args) throws SQLException {

        String cs = "jdbc:mysql://localhost:3306/"
                + "testdb?allowMultiQueries=true&useSSL=false";
        String user = "testuser";
        String password = "test623";

        String query = "SELECT Id, Name FROM Authors WHERE Id=1;"
                + "SELECT Id, Name FROM Authors WHERE Id=2;"
                + "SELECT Id, Name FROM Authors WHERE Id=3";

        try (Connection con = DriverManager.getConnection(cs, user, password);
                PreparedStatement pst = con.prepareStatement(query);) {

            boolean isResult = pst.execute();

            do {
                try (ResultSet rs = pst.getResultSet()) {

                    while (rs.next()) {

                        System.out.print(rs.getInt(1));
                        System.out.print(": ");
                        System.out.println(rs.getString(2));
                    }

                    isResult = pst.getMoreResults();
                }

            } while (isResult);
        }
    }
}

在代码示例中,我们从Authors表中检索三行。 我们使用三个SELECT语句来获取三行。

String cs = "jdbc:mysql://localhost:3306/"
        + "testdb?allowMultiQueries=true&useSSL=false";

通过将allowMultiQueries参数设置为true,我们可以在数据库 URL 中启用多个语句查询。

String query = "SELECT Id, Name FROM Authors WHERE Id=1;"
        + "SELECT Id, Name FROM Authors WHERE Id=2;"
        + "SELECT Id, Name FROM Authors WHERE Id=3";

在这里,我们有一个包含多个语句的查询。 语句用分号分隔。

boolean isResult = pst.execute();

我们调用已预备语句对象的execute()方法。 该方法返回一个布尔值,该布尔值指示第一个结果是否为ResultSet对象。 使用getMoreResults()方法调用后续结果。

do {
    try (ResultSet rs = pst.getResultSet()) {

        while (rs.next()) {

            System.out.print(rs.getInt(1));
            System.out.print(": ");
            System.out.println(rs.getString(2));
        }

        isResult = pst.getMoreResults();
    }

} while (isResult);

结果的处理在do while循环内完成。 通过getResultSet()方法调用检索ResultSet。 为了找出是否还有其他结果,我们调用getMoreResults()方法。

$ mvn exec:java -q
1: Jack London
2: Honore de Balzac
3: Lion Feuchtwanger

这是示例的输出。 前三行是从Authors表中检索的。

Java MySQL 列标题

下面的示例显示如何使用数据库表中的数据打印列标题。 我们将列名称称为元数据。 元数据是有关数据库中核心数据的数据。

JdbcColumnHeaders.java

package com.zetcode;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JdbcColumnHeaders {

    public static void main(String[] args) {

        String cs = "jdbc:mysql://localhost:3306/testdb?useSSL=false";
        String user = "testuser";
        String password = "test623";

        String query = "SELECT Name, Title From Authors, "
                + "Books WHERE Authors.Id=Books.AuthorId";

        try (Connection con = DriverManager.getConnection(cs, user, password);
                PreparedStatement pst = con.prepareStatement(query);
                ResultSet rs = pst.executeQuery()) {

            ResultSetMetaData meta = rs.getMetaData();

            String colname1 = meta.getColumnName(1);
            String colname2 = meta.getColumnName(2);

            String header = String.format("%-21s%s", colname1, colname2);
            System.out.println(header);

            while (rs.next()) {

                String row = String.format("%-21s", rs.getString(1));
                System.out.print(row);
                System.out.println(rs.getString(2));
            }
        } catch (SQLException ex) {

            Logger lgr = Logger.getLogger(JdbcColumnHeaders.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }
}

在此程序中,我们从Authors表中选择作者,并从Books表中选择他们的书。 我们打印结果集中返回的列的名称。 输出已格式化。

String query = "SELECT Name, Title From Authors, " +
    "Books WHERE Authors.Id=Books.AuthorId";

这是将作者与他们的书联系在一起的 SQL 语句。

ResultSetMetaData meta = rs.getMetaData();

要获取列名,我们需要获取ResultSetMetaData。 它是一个对象,可用于获取有关ResultSet对象中列的类型和属性的信息。

String colname1 = meta.getColumnName(1);
String colname2 = meta.getColumnName(2);

从获得的元数据中,我们获得列名。

String header = String.format("%-21s%s", colname1, colname2);
System.out.println(header);

我们将列名称打印到控制台。

while (rs.next()) {

    String row = String.format("%-21s", rs.getString(1));
    System.out.print(row);
    System.out.println(rs.getString(2));
}

我们将数据打印到控制台。 第一列为 21 个字符,并在左侧对齐。

$ mvn exec:java -q
NAME                 Title
Jack London          Call of the Wild
Jack London          Martin Eden
Honore de Balzac     Old Goriot
Honore de Balzac     Cousin Bette
Lion Feuchtwanger    Jew Suess
Emile Zola           Nana
Emile Zola           The Belly of Paris
Truman Capote        In Cold blood
Truman Capote        Breakfast at Tiffany

这是程序的输出。

MySQL Java 自动生成的键

MySQL 的AUTO_INCREMENT属性为新行生成唯一的 ID。 以下示例说明了如何使用 JDBC 检索自动生成的键值。

JdbcAutoGenKey.java

package com.zetcode;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JdbcAutoGenKey {

    public static void main(String[] args) {

        String url = "jdbc:mysql://localhost:3306/testdb?useSSL=false";
        String user = "testuser";
        String password = "test623";

        String author = "Oscar Wilde";
        String sql = "INSERT INTO Authors(Name) VALUES(?)";

        try (Connection con = DriverManager.getConnection(url, user, password);
                PreparedStatement pst = con.prepareStatement(sql,
                        Statement.RETURN_GENERATED_KEYS)) {

            pst.setString(1, author);
            pst.executeUpdate();

            try (ResultSet rs = pst.getGeneratedKeys()) {

                if (rs.first()) {

                    System.out.printf("The ID of new author: %d", rs.getLong(1));
                }
            }

        } catch (SQLException ex) {

            Logger lgr = Logger.getLogger(JdbcAutoGenKey.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }
}

在该示例中,我们向表中添加了一个新作者,其主键由 MySQL 自动递增。 我们检索生成的 ID。

try (Connection con = DriverManager.getConnection(url, user, password);
        PreparedStatement pst = con.prepareStatement(sql,
                Statement.RETURN_GENERATED_KEYS)) {

第一步,我们必须将Statement.RETURN_GENERATED_KEYS传递给prepareStatement()方法。

try (ResultSet rs = pst.getGeneratedKeys()) {

然后,我们使用getGeneratedKeys()方法检索生成的键。

if (rs.first()) {

    System.out.printf("The ID of new author: %d", rs.getLong(1));
}

由于我们只有一个插入语句,因此我们使用first()导航到该值。

$ mvn exec:java -q
The ID of new author: 7

这是一个示例输出。

MySQL Java 编写图像

有些人喜欢将其图像放入数据库中,有些人则希望将其保留在文件系统中以供其应用使用。 当我们处理大量图像时,会出现技术难题。 图像是二进制数据。 MySQL 数据库具有一种特殊的数据类型来存储称为BLOB(二进制大对象)的二进制数据。

对于此示例,我们使用Images表。

JdbcWriteImage.java

package com.zetcode;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JdbcWriteImage {

    public static void main(String[] args) {

        String cs = "jdbc:mysql://localhost:3306/testdb?useSSL=false";
        String user = "testuser";
        String password = "test623";

        String sql = "INSERT INTO Images(Data) VALUES(?)";

        try (Connection con = DriverManager.getConnection(cs, user, password);
                PreparedStatement pst = con.prepareStatement(sql)) {

            File myFile = new File("src/main/resources/tree.png");

            try (FileInputStream fin = new FileInputStream(myFile)) {

                pst.setBinaryStream(1, fin, (int) myFile.length());
                pst.executeUpdate();

            } catch (IOException ex) {

                Logger lgr = Logger.getLogger(JdbcWriteImage.class.getName());
                lgr.log(Level.SEVERE, ex.getMessage(), ex);
            }
        } catch (SQLException ex) {

            Logger lgr = Logger.getLogger(JdbcWriteImage.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }
}

在前面的示例中,我们从当前工作目录中读取 PNG 图像,并将其插入Images表中。

String sql = "INSERT INTO Images(Data) VALUES(?)";

这是插入图像的 SQL。

File myFile = new File("src/main/resources/tree.png");

try (FileInputStream fin = new FileInputStream(myFile)) {

我们为图像文件创建一个File对象。 要从该文件读取字节,我们创建一个FileInputStream对象。

pst.setBinaryStream(1, fin, (int) myFile.length());

二进制流设置为预备语句。 setBinaryStream()方法的参数是要绑定的参数索引,输入流和流中的字节数。

pst.executeUpdate();

我们执行该语句。

MySQL Java 读取图像

在前面的示例中,我们已将图像插入数据库表中。 现在,我们将从表中读取图像。

JdbcReadImage.java

package com.zetcode;

import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JdbcReadImage {

    public static void main(String[] args) {

        String url = "jdbc:mysql://localhost:3306/testdb?useSSL=false";
        String user = "testuser";
        String password = "test623";

        String query = "SELECT Data FROM Images LIMIT 1";

        try (Connection con = DriverManager.getConnection(url, user, password);
                PreparedStatement pst = con.prepareStatement(query);
                ResultSet result = pst.executeQuery()) {

            if (result.next()) {

                String fileName = "src/main/resources/tree.png";

                try (FileOutputStream fos = new FileOutputStream(fileName)) {

                    Blob blob = result.getBlob("Data");
                    int len = (int) blob.length();

                    byte[] buf = blob.getBytes(1, len);

                    fos.write(buf, 0, len);

                } catch (IOException ex) {

                    Logger lgr = Logger.getLogger(JdbcReadImage.class.getName());
                    lgr.log(Level.SEVERE, ex.getMessage(), ex);
                }
            }
        } catch (SQLException ex) {

            Logger lgr = Logger.getLogger(JdbcReadImage.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }
}

我们从图片表中读取了一张图片。

String query = "SELECT Data FROM Images LIMIT 1";

我们从表中选择一条记录。

String fileName = "src/main/resources/tree.png";

try (FileOutputStream fos = new FileOutputStream(fileName)) {

创建FileOutputStream对象以写入文件。 它旨在写入原始字节流,例如图像数据。

Blob blob = result.getBlob("Data");

我们通过调用getBlob()方法从Data列中获取图像数据。

int len = (int) blob.length();

我们计算出斑点数据的长度。 换句话说,我们得到字节数。

byte[] buf = blob.getBytes(1, len);

getBytes()方法以字节数组的形式检索 Blob 对象的所有字节。

fos.write(buf, 0, len);

字节被写入输出流。 该映像在文件系统上创建。

事务支持

事务是针对一个或多个数据库中数据的数据库操作的基本单位。 事务中所有 SQL 语句的影响可以全部提交给数据库,也可以全部回滚。

MySQL 数据库具有不同类型的存储引擎。 最常见的是 MyISAM 和 InnoDB 引擎。 在数据安全性和数据库速度之间需要权衡。 MyISAM 表的处理速度更快,并且不支持事务。 另一方面,InnoDB 表可以更安全地防止数据丢失。 它们支持事务,并且处理速度较慢。

JdbcTransaction.java

package com.zetcode;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JdbcTransaction {

    public static void main(String[] args) {

        String url = "jdbc:mysql://localhost:3306/testdb?useSSL=false";
        String user = "testuser";
        String password = "test623";

        try (Connection con = DriverManager.getConnection(url, user, password)) {

            try (Statement st = con.createStatement()) {

                con.setAutoCommit(false);

                st.executeUpdate("UPDATE Authors SET Name = 'Leo Tolstoy'"
                        + "WHERE Id = 1");
                st.executeUpdate("UPDATE Books SET Title = 'War and Peace'"
                        + "WHERE Id = 1");
                st.executeUpdate("UPDATE Books SET Titl = 'Anna Karenina'"
                        + "WHERE Id = 2");

                con.commit();

            } catch (SQLException ex) {

                try {

                    con.rollback();
                } catch (SQLException ex1) {

                    Logger lgr = Logger.getLogger(JdbcTransaction.class.getName());
                    lgr.log(Level.WARNING, ex1.getMessage(), ex1);
                }

                Logger lgr = Logger.getLogger(JdbcTransaction.class.getName());
                lgr.log(Level.SEVERE, ex.getMessage(), ex);

            }
        } catch (SQLException ex) {
            Logger.getLogger(JdbcTransaction.class.getName()).log(
                Level.SEVERE, null, ex);
        }
    }
}

在此程序中,我们想在Authors表的第一行上更改作者的姓名。 我们还必须更改与该作者相关的书籍。 这是一个需要进行事务的很好的例子。 如果我们更改作者但不更改作者的书,则数据已损坏。

con.setAutoCommit(false);

要处理事务,我们必须将自动提交模式设置为false。 默认情况下,数据库连接处于自动提交模式。 在这种模式下,每条语句在执行后都会立即提交给数据库。 声明无法撤消。 当自动提交关闭时,我们通过调用commit()提交更改,或通过调用rollback()方法将其回滚。

st.executeUpdate("UPDATE Books SET Titl = 'Anna Karenina' "
        + "WHERE Id = 2");

第三个 SQL 语句有一个错误。 表中没有Titl栏。

con.commit();

如果没有异常,则提交事务。

try {

    con.rollback();
} catch (SQLException ex1) {

    Logger lgr = Logger.getLogger(JdbcTransaction.class.getName());
    lgr.log(Level.WARNING, ex1.getMessage(), ex1);
}

发生异常时,事务将回滚。 没有更改提交到数据库。

Feb 21, 2018 2:35:14 PM com.zetcode.JdbcTransaction main
SEVERE: Unknown column 'Titl' in 'field list'
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: 
Unknown column 'Titl' in 'field list'

该应用以异常结束。

mysql> SELECT Name, Title From Authors, Books WHERE Authors.Id=Books.AuthorId;
+-------------------+----------------------+
| Name              | Title                |
+-------------------+----------------------+
| Jack London       | Call of the Wild     |
| Jack London       | Martin Eden          |
| Honore de Balzac  | Old Goriot           |
| Honore de Balzac  | Cousin Bette         |
| Lion Feuchtwanger | Jew Suess            |
| Emile Zola        | Nana                 |
| Emile Zola        | The Belly of Paris   |
| Truman Capote     | In Cold blood        |
| Truman Capote     | Breakfast at Tiffany |
+-------------------+----------------------+
9 rows in set (0.01 sec)

事务已回滚,并且未进行任何更改。

但是,如果没有事务,数据是不安全的。

JdbcNoTransaction.java

package com.zetcode;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JdbcNoTransaction {

    public static void main(String[] args) {

        String url = "jdbc:mysql://localhost:3306/testdb?useSSL=false";
        String user = "testuser";
        String password = "test623";

        try (Connection con = DriverManager.getConnection(url, user, password);
                Statement st = con.createStatement()) {

            st.executeUpdate("UPDATE Authors SET Name = 'Leo Tolstoy'"
                    + "WHERE Id = 1");
            st.executeUpdate("UPDATE Books SET Title = 'War and Peace'"
                    + "WHERE Id = 1");
            st.executeUpdate("UPDATE Books SET Titl = 'Anna Karenina'"
                    + "WHERE Id = 2");

        } catch (SQLException ex) {

            Logger lgr = Logger.getLogger(JdbcNoTransaction.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }
}

我们有同样的例子。 这次,没有事务支持。

mysql> SELECT Name, Title From Authors, Books WHERE Authors.Id=Books.AuthorId;
+-------------------+----------------------+
| Name              | Title                |
+-------------------+----------------------+
| Leo Tolstoy       | War and Peace        |
| Leo Tolstoy       | Martin Eden          |
| Honore de Balzac  | Old Goriot           |
| Honore de Balzac  | Cousin Bette         |
| Lion Feuchtwanger | Jew Suess            |
| Emile Zola        | Nana                 |
| Emile Zola        | The Belly of Paris   |
| Truman Capote     | In Cold blood        |
| Truman Capote     | Breakfast at Tiffany |
+-------------------+----------------------+
9 rows in set (0.00 sec)

再次引发异常。 列夫·托尔斯泰没有写马丁·伊甸园; 数据已损坏。

批量更新

当我们需要使用多个语句更新数据时,可以使用批处理更新。 批处理更新可用于INSERTUPDATEDELETE语句以及CREATE TABLEDROP TABLE语句。

JdbcBatchUpdate.java

package com.zetcode;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JdbcBatchUpdate {

    public static void main(String[] args) {

        String url = "jdbc:mysql://localhost:3306/testdb?useSSL=false";
        String user = "testuser";
        String password = "test623";

        try (Connection con = DriverManager.getConnection(url, user, password)) {

            try (Statement st = con.createStatement()) {

                con.setAutoCommit(false);

                st.addBatch("DROP TABLE IF EXISTS Authors2");
                st.addBatch("CREATE TABLE Authors2(Id INT PRIMARY KEY, "
                        + "Name VARCHAR(100))");
                st.addBatch("INSERT INTO Authors2(Id, Name) "
                        + "VALUES(1, 'Jack London')");
                st.addBatch("INSERT INTO Authors2(Id, Name) "
                        + "VALUES(2, 'Honore de Balzac')");
                st.addBatch("INSERT INTO Authors2(Id, Name) "
                        + "VALUES(3, 'Lion Feuchtwanger')");
                st.addBatch("INSERT INTO Authors2(Id, Name) "
                        + "VALUES(4, 'Emile Zola')");
                st.addBatch("INSERT INTO Authors2(Id, Name) "
                        + "VALUES(5, 'Truman Capote')");
                st.addBatch("INSERT INTO Authors2(Id, Name) "
                        + "VALUES(6, 'Umberto Eco')");

                int counts[] = st.executeBatch();

                con.commit();

                System.out.printf("Committed %d updates", counts.length);

            } catch (SQLException ex) {
                try {

                    con.rollback();
                } catch (SQLException ex2) {

                    Logger lgr = Logger.getLogger(JdbcBatchUpdate.class.getName());
                    lgr.log(Level.FINEST, ex2.getMessage(), ex2);
                }

                Logger lgr = Logger.getLogger(JdbcBatchUpdate.class.getName());
                lgr.log(Level.FINEST, ex.getMessage(), ex);
            }
        } catch (SQLException ex) {
            Logger lgr = Logger.getLogger(JdbcBatchUpdate.class.getName());
            lgr.log(Level.FINEST, ex.getMessage(), ex);
        }
    }
}

这是用于批处理更新的示例程序。 我们从Authors表中删除所有数据,然后插入新数据。 我们添加了一位新作者,Umberto Eco,以查看更改。

st.addBatch("DROP TABLE IF EXISTS Authors2");
st.addBatch("CREATE TABLE Authors2(Id INT PRIMARY KEY, "
        + "Name VARCHAR(100))");
st.addBatch("INSERT INTO Authors2(Id, Name) "
        + "VALUES(1, 'Jack London')");
...

我们使用addBatch()方法向该语句添加新命令。

int counts[] = st.executeBatch();

添加所有命令后,我们调用executeBatch()进行批量更新。 该方法返回已提交更改的数组。

con.commit();

批处理更新在事务中提交。

} catch (SQLException ex) {
    try {

        con.rollback();
    } catch (SQLException ex2) {

        Logger lgr = Logger.getLogger(JdbcBatchUpdate.class.getName());
        lgr.log(Level.FINEST, ex2.getMessage(), ex2);
    }

    Logger lgr = Logger.getLogger(JdbcBatchUpdate.class.getName());
    lgr.log(Level.FINEST, ex.getMessage(), ex);
}

万一批量更新失败,我们将其称为rollback()

$ mvn exec:java -q
Committed 8 updates

mysql> SELECT * from Authors2;
+----+-------------------+
| Id | Name              |
+----+-------------------+
|  1 | Jack London       |
|  2 | Honore de Balzac  |
|  3 | Lion Feuchtwanger |
|  4 | Emile Zola        |
|  5 | Truman Capote     |
|  6 | Umberto Eco       |
+----+-------------------+
6 rows in set (0.00 sec)

我们执行BatchUpdate程序。 SELECT语句显示Authors2表已成功更新。 它有一个新作者,Umerto Eco。

将数据导出到 CSV 文件

下一个示例将数据导出到 CSV 文件。

我们需要对testuser具有适当的文件许可权; 否则,我们会收到拒绝访问错误消息。

mysql> GRANT FILE ON *.* TO 'testuser'@'localhost';

我们设置了FILE权限。

mysql> SHOW VARIABLES LIKE "secure_file_priv";
+------------------+-----------------------+
| Variable_name    | Value                 |
+------------------+-----------------------+
| secure_file_priv | /var/lib/mysql-files/ |
+------------------+-----------------------+
1 row in set (0.26 sec)

出于安全原因,MySQL 从启用--secure-file-priv选项开始,该选项仅允许处理特定目录中的文件。 该目录在secure_file_priv变量中指定。 在 Windows 上,路径类似于'C:/ProgramData/MySQL/MySQL Server 5.7/Uploads'

ExportCSV.java

package com.zetcode;

import java.sql.PreparedStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JdbcExportCSV {

    public static void main(String[] args) {

        String url = "jdbc:mysql://localhost:3306/testdb?useSSL=false";
        String user = "testuser";
        String password = "test623";

        String query = "SELECT Name, Title INTO OUTFILE "
                + "'/var/lib/mysql-files/authors_books.csv' "
                + "FIELDS TERMINATED BY ',' "
                + "FROM Authors, Books WHERE "
                + "Authors.Id=Books.AuthorId";

        try (Connection con = DriverManager.getConnection(url, user, password);
                PreparedStatement pst = con.prepareStatement(query)) {

            pst.execute();
        } catch (SQLException ex) {

            Logger lgr = Logger.getLogger(JdbcExportCSV.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }
}

我们将作者及其相应的书籍导出到/var/lib/mysql-files/authors_books.csv文件中。

String query = "SELECT Name, Title INTO OUTFILE "
        + "'/var/lib/mysql-files/authors_books.csv' "
        + "FIELDS TERMINATED BY ',' "
        + "FROM Authors, Books WHERE "
        + "Authors.Id=Books.AuthorId";

要将数据导出到文件中,我们使用SELECT INTO OUTFILE SQL 语句。

$ cat /var/lib/mysql-files/authors_books.csv
Jack London,Call of the Wild
Jack London,Martin Eden
Honore de Balzac,Old Goriot
Honore de Balzac,Cousin Bette
Lion Feuchtwanger,Jew Suess
Emile Zola,Nana
Emile Zola,The Belly of Paris
Truman Capote,In Cold blood
Truman Capote,Breakfast at Tiffany

我们验证数据。

这是 MySQL Java 教程。 您可能也对 JDBI 教程Java H2 教程PostgreSQL Java 教程MongoDB Java 教程MySQL 教程感兴趣。

posted @ 2024-10-24 18:19  绝不原创的飞龙  阅读(5)  评论(0编辑  收藏  举报