Java-029-MVC架构

Web MVC架构

MVC架构

为什么需要MVC架构

回顾一下 Web电子商务系统

Web电子商城主要逻辑模块

API

  • 处理HTTP请求, 请求校验
  • 解析和转换请求内容
  • 调用下层处理逻辑
  • 把处理结果转换成面向用户的结果
  • 构造HTTP响应和返回

数据访问

  • 数据ORM
  • 实体对象
  • 数据访问模型 (DAO)

业务逻辑

  • 用户身份校验
  • 产品信息校验
  • 创建购物车中的收藏记录
  • 产品库存管理
  • 订单生成
  • 等等等…..

那最粗暴最极端的做法是把所有这些代码逻辑都写在API里!

那很容易出现的问题就是:

  • 巨大的方法和类. 比如, 我见过15000行的类和其中5000行的方法
  • 逻辑的交叉, 可能会导致代码没法重用
  • 代码的重复和不可重用
  • 数据流的混乱

那我们最先想到的的是划分模块:

API (Controller) 模块

  • 处理HTTP请求, 请求校验
  • 解析和转换请求内容
  • 调用下层逻辑
  • 把内部处理结果转换成面向用户的结果 (View)
  • 构造HTTP响应2和返回

数据访问

  • 数据ORM
  • 实体对象
  • 数据访问模型 (DAO)

业务逻辑 (Business) 模块

  • 用户身份验证模块
  • 产品信息验证
  • 构建购物车中的收藏记录
  • 产品库存管理
  • 订单生成
  • 等等等……

其中 API 模块中, 我们看到一块很重要的部分, 就是面向用户的部分, 这部分是API和前端或者用户直接交互的数据的结构, 所以单独抽象成一个模块

交互 (View) 模块

  • 请求结构
  • 请求和内部Model的转换
  • 内部处理结构和面向用户结果的转换
  • 面向用户的结果
  • 页面, 如果HTTP页面是在后端构造 (JSP或者其他模板系统)

其中什么部分是Web项目中通用和必须的呢?

aView, Controller, Model

MVC架构

分层模型

除了分模块, 我们应该注意到在MVC架构中, 我们还可以有一个分层的结构

View —> Controller —> Business —> Model

这样单线的依赖关系, 可以让代码逻辑更加清晰, 我们可以想象成一个生产线或者流水线, 请求逻辑一层层拆解处理, 流水线上每个模块各司其职, 最后的终点是Model, 再把需要响应的内容, 从Model中提取出来, 再一层层按原来的路线往回传递.

Serverless架构

代码链接

Java-028-购物车实例

购物车数据模型

购物车数据模型

RESTful API

  • 用户 User: Create, List, Get, Update
  • 购物车 Cart: Create, List, Get, Update
  • 产品 Product: Create, List, Get, Update
  • 订单 Order: Create, List, Get, Update

数据模型

用户的订单 Order: List {product1, product2, product3, product4}

{order1 product1}

{order1 product2}

{order1 product3}

select * from orders where order_name = order1;

为每一个产品生成一个订单:

{order1 product1, 5}
{order2 product2, 1}
{order3 product3, 3}

从对象出发去思考我们的数据系统 (不要开始就想着表的结构)
对象有什么样的属性, 每个属性的语义是什么
对象与对象之间的关系
cart_item

1, user_1, product_1, 5
2, user_1, product_2, 1
3, user_1, product_3, 3
4, user_2, product_2, 1
5, user_2, product_2, 2

查看user_1的购物车里有什么?

select * from cart_item where user_id = user_1;

用户

数据对象:

User {
id: Integer primary key,
name: String not null,
password: String not null
//购物车, 购物车里的商品
}

关系数据库表 Schema:

CREATE TABLE user (
id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR(20) NOT NULL,
password VARCHAR(255) NOT NULL,
);
购物车
数据对象:

Cart {
id: Integer primary key,
user_id: Integer not null
};

购物车的记录

CartItem {
id: Integer primary key,
cart_id: Integer not null,
user_id: Integer not null,
product_id: Integer not null,
quantity: Integer not null
}

这样就违反了范式二和范式三
cart:
cart_0, user_0, 1, 2

cart_item:
cart_0, user_0, product_0, 1
cart_0, user_1, product_1, 2

购物车中的记录
数据对象:

CartItem {
id: Integer primary key,
user_id: Integer not null, //哪个用户的购物车记录
product_id: Integer not null, //对应的产品
quantity: Integer not null, //对应的数量
}
关系数据库 Schema:

CREATE TABLE cart_item (
id INTEGER PRIMARY KEY NOT NULL,
user_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL
);
产品
数据对象:

Product {
id: Integer primary key,
name: String not null,
description: String not null,
price: Integer not null
}
关系数据库 Schema:

CREATE TABLE product (
id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR(25) NOT NULL,
price INTEGER NOT NULL,
description VARCHAR(255) NOT NULL
);
订单
数据对象:

Order {
id: Integer primary key,
user_id: Integer not null,
product_id: Integer not null,
quantity: Integer not null,
status: String not null,
address: String not null
}
关系数据库 Schema:

CREATE TABLE order (
id INTEGER PRIMARY KEY NOT NULL,
user_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
status VARCHAR(25) NOT NULL,
address VARCHAR(255) NOT NULL
);

ORM 实体关系

示例:

  • 一个用户可以把多个产品的关注列表记录起来 (购物车), 一个用户可以有多个关注的产品. –> 一对多 和 多对一
  • 一个产品有一个产品的销售者, 对于产品来说, 那就 N个产品 –> 1个销售者, 对于销售者来说: 那就 1个销售者 –> 多个产品
  • 一对一: 博客系统: Post <--> PostDetail, 用户 <--> 购物车

一对一

例如: 一个购物车内的产品记录, 对应一个产品

记录 <--> 产品 ===> 是1 对 1的关系

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = “product_id”)
private Product product;
查询可以翻译成SQL语句:

一对多 和 多对一

例如: 一个用户会有多个放入购物车的记录

User:

User {
id: Integer primary key,
name: String not null,
password: String not null,
}
Cart:

Cart {
id: Integer primary key,
user_id: Integer not null,
product_id: Integer not null,
quantity: Integer not null,
}
Cart ORM:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = “user_id”)
private User user;
User ORM:

@OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
mappedBy = “user”)
private List carts;
实例:

1 团队 –> n 队员

对于团队来说: OneToMany

一个团队里面有哪些队员?

对于队员来说: ManyToOne

一个队员属于哪个团队?

注意: SQL 范式!

代码链接

Java-028-数据模型

购物车数据模型

RESTful API

  • 用户 User: Create, List, Get, Update
  • 购物车 Cart: Create, List, Get, Update
  • 产品 Product: Create, List, Get, Update
  • 订单 Order: Create, List, Get, Update

数据模型

用户

数据对象:

User {
id: Integer primary key,
name: String not null,
password: String not null,
}
关系数据库表 Schema:

CREATE TABLE user (
id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR(20) NOT NULL,
password VARCHAR(255) NOT NULL,
);
购物车中收藏的产品记录
数据对象:

CartItem {
id: Integer primary key,
user_id: Integer not null,
product_id: Integer not null,
quantity: Integer not null,
}
关系数据库 Schema:

CREATE TABLE cart_item (
id INTEGER PRIMARY KEY NOT NULL,
user_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL
);
产品
数据对象:

Product {
id: Integer primary key,
name: String not null,
description: String not null,
price: Integer not null
}
关系数据库 Schema:

CREATE TABLE product (
id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR(25) NOT NULL,
price INTEGER NOT NULL,
description VARCHAR(255) NOT NULL
);
订单
数据对象:

Order {
id: Integer primary key,
user_id: Integer not null,
product_id: Integer not null,
quantity: Integer not null,
status: String not null,
address: String not null
}
关系数据库 Schema:

CREATE TABLE order (
id INTEGER PRIMARY KEY NOT NULL,
user_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
status VARCHAR(25) NOT NULL,
address VARCHAR(255) NOT NULL
);

Java-027-ORM踩坑

报错

1
No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no propert
1
2
在所有实体类上添加注解
@JsonIgnoreProperties(value = { "hibernateLazyInitializer", "handler" })

关系循环引用

注意

  • 表名千万别和sql关键字重复
  • 表名千万别和sql关键字重复
  • 表名千万别和sql关键字重复

Java-027-ORM

ORM和Hibernate

数据建模方式

  1. 面向对象的思考方式 – 更加抽象, 逻辑性更强

    • 需要处理的实体, 他们之间的关系, 互相的操作
  2. 关系数据库 – 数据的存储结构

    • 数据库表, 表的字段 (属性), 数据记录

SQL

面向对象 SQL

JDBC代码回顾

基本过程

  1. 创建jdbc连接器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    @Component("statement")
    public class StatementFactory implements FactoryBean<Statement> {
    // TODO: 把数据库地址放到配置文件里
    private static final String DB_PATH = "jdbc:sqlite:resources/sample.db";

    @Override
    public Statement getObject() throws Exception {
    // 创建数据库连接
    Connection connection = DriverManager.getConnection(DB_PATH);
    Statement statement = connection.createStatement();
    statement.setQueryTimeout(30);

    return statement;
    }

    @Override
    public Class<?> getObjectType() {
    return Statement.class;
    }

    @Override
    public boolean isSingleton() {
    return true;
    }
    }
    • 如何创建数据库的连接器?
    • 如何去连接数据库? 如何销毁数据库连接?
    • 如何管理数据库连接? 连接池, 连接缓存

      所以和SQL数据库打交道的程序都需要考虑这些通用问题 —> 考虑重用: 库, 框架

  2. 创建DAO (Data Access Object)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    @Component
    public class ProductDao {
    private Statement statement;

    public ProductDao(Statement statement) {
    this.statement = statement;
    }

    public Product get(int id) {
    try {
    String query = "SELECT * FROM `product` WHERE id = " + id;
    ResultSet rs = statement.executeQuery(query);

    if (rs.next()) {
    return new Product(
    rs.getInt("id"),
    rs.getString("name"),
    rs.getString("description"),
    rs.getDouble("price")
    );
    } else {
    return null;
    }
    } catch (SQLException e) {
    System.out.println("Failed to query product from DB.");
    }

    return null;
    }
    }
  3. 为每个具体的数据查询操作创建SQL语句

    1
    2
    String query = "SELECT * FROM `product` WHERE id = " + id;
    ResultSet rs = statement.executeQuery(query);
    • 准备好SQL语句
    • 调用JDBC的API传入SQL语句,设置参数
    • 解析JDBC返回的结果
    1. 代码内部的数据结构和操作 –> SQL语句
    2. SQL语句返回的结果 –> Java的对象

注意事项–ORM解决的问题是什么

  • 数据DAO和数据库连接创建的解耦

思考: 如果迁移数据库到MySQL, Java代码需要如何修改?

  1. 修改数据库的链接. URL变了, 验证方式
  2. SQL语句修改 —> 工作量就很大! —> 能不能用比SQL更抽象的数据模型去封装? —> 对象!
    SQL注入

ORM (Object Relational Mapping) 对象关系映射

顾名思义, ORM把关系数据库中的数据结构映射到面向对象的结构. ORM是一种以面向对象的方式对关系数据库进行操作的技术.

为什么需要ORM

没有ORM

使用JDBC操作关系数据库

  • 准备好SQL语句
  • 调用JDBC的API传入SQL语句,设置参数
  • 解析JDBC返回的结果
  • 这个过程中, 我们的Java代码和SQL是强耦合的, 我们需要专门处理Java里的数据结构和SQL语句的转换, 然后还需要解析SQL返回的结果.
分层 –> 插入中间层: 把SQL给封装起来 –> Driver/dialect –> “接口” (ORM) <– Java代码只依赖于

这部分隐藏着很多重复的逻辑 看到这个, 是不是有一股要重构和优化的冲动!

使用ORM的方式

  • 根据具体业务和领域, 根据面向对象, - 参考关系数据库的模型, 设计代码的数据模型. Modeling
  • 使用ORM在Java代码中声明和定义数据结构, 也就是Java的类和对象.
  • 使用ORM提供的抽象接口和方法, 声明和定义对数据的访问层. 注意: 这一层不是直接操作SQL 思考: 有什么好处?
  • ORM框架根据我们的定义 (面向对象方式), 构建相应的SQL语句和环境, 执行对数据的访问

优势

  • 数据抽象层次高, 和现实业务更接近
  • 数据访问操作的抽象层次高, ORM框架帮助隐藏了很多细节
  • 面向对象封装

劣势

  • 相对固定的数据模型
  • 不如直接使用SQL灵活
  • 性能

Spring boot + Hibernate (ORM)

Maven的依赖

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.25.2</version>
</dependency>

<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.2.11</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.zsoltfabok/sqlite-dialect -->
<dependency>
<groupId>com.zsoltfabok</groupId>
<artifactId>sqlite-dialect</artifactId>
<version>1.0</version>
</dependency>

数据库连接相关的配置

项目/src/main/resources/application.properties

1
2
3
4
5
6
7
8
server.port = 8080

# Database settings
spring.datasource.url=jdbc:sqlite:resources/sample.db
spring.datasource.username=
spring.datasource.password=
spring.datasource.driver.class=org.sqlite.JDBC
hibernate.dialect=org.hibernate.dialect.SQLiteDialect

Model的代码

如何去定义数据的对象的类 (Schema)

  • 注解:
1
2
3
4
5
6
7
8
@Entity
@Table(name = "product")
public class Product {}

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)

@Column(name = "name")
  • Dao的代码

如何去查询

1
@Repository

CrudRepository使用

CrudRepository

报错信息 Error creating bean with name ‘entityManagerFactory’

如果你遇到

1
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not get constructor for org.hibernate.persister.entity.SingleTableEntityPersister
  • 解决办法:
  • pom.xml 加上依赖
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.2.Final</version>
</dependency>

代码链接

Java-026-sql范式

关系数据库范式

数据库表设计

需求 –> API, 数据库 (数据库的表) –> 实现功能代码和操作数据!

  • 应该有什么表? 用户表, 商品表, 订单表, 购物车表 –> RESTful API的资源所对应, Session表
  • 每个表应该有什么的字段! 范式!
  • 定义对数据库的SQL操作, 查询, 更新, 插入, 删除
  • 建立Index索引, 对什么样的数据建立索引, 建立什么样的索引!

过度优化的边界: 是否真正理解了业务需求, 了解了瓶颈!

参考示例

用户表 user:

id name type password content
0 Alex normal 123456 Here is the first
1 Alice normal 123456 Here is thhe first user
2 Alex vip password Here is the second Alex

订单表 order:

id user_id product_id quantity timestamp
0 0 15 5 203231232
1 2 15 1 324324234
2 2 1 3 213213219

第一范式 1NF

每一个字段都是不可分的数据项, 即字段的原子性.

要求
  • 有主键, 且主键不能为空, 主键不能重复
  • 字段必须不能再分, 字段必须是原子的
示例

用户表和订单表都包含主键 – id.

然后每一个字段都只包含单一不可分的数据内容项, 比如说, name就是只有name的意思, 不能够拆分.

一个常见的错误例子是, 比如用户表, 有一个项叫做信息项, 然后把相关的电话, 地址信息都放到这个信息项里, 这样就破坏了字段的原子性.

id name type password content info
0 Alex normal 123456 Here is the first {“phone”:”13311122211”,”city”:”北京”,”email”:”xx@xx.com“}
坑:
没办法严格控制查询的粒度, 比如说, 如果只需要用户电话的时候, 就必须把所有信息内容查询出来, 然后再提取电话, 这样就造成了查询冗余, 影响性能. 同时也没办法通过使用电话这个信息, 在其他表里面查询相关信息, 例如电话的位置信息.
  • 查 phone 要查询整个 info
  • 查 email 要查询整个 info
  • 查 city 要查询整个 info
  • 而且造成了性能瓶颈
  • 还有单独更新 phone/email/city 时候数据更新的问题,很容易造成数据被无意间更改
正确做法: 设计表的时候, 尽量每一个信息都用单独的字段, 比如电话字段, 地址信息字段

而如果 单独 phone/email/city 成为一列

查询仅需要

1
2
3
select phone from user;   
select email from user;
select phone from city;

第二范式 2NF

在第一范式得基础上, 且每一个非主属性完全函数依赖于码.

要求
  • 满足第一范例
  • 表中得每一个非主键属性, 必须完全依赖于本表码, 码可以是主键, 或者多个字段组成的主键
示例

在订单表中, 一个订单由用户, 产品和订单时间确定. 那这三个属性就可以称之为码: (用户, 产品, 订单时间).

id user_id product_id quantity timestamp
0 0 15 5 20001000

这样, 如果相同用户, 相同产品, 相同订单时间就应该能够确定查询到其订单中产品数量, 而不允许有相同的用户, 相同产品, 相同订单时间, 但是订单数量不一样的数据产生.

user_id product_id quantity timestamp
0 15 5 20001000
0 15 9 20001000
同一用户,同一时间,同一商品 不该存在 quantity 两种状态

如果添加其他的内容, 比如订单状态, 这个订单状态也应该完全依赖于码: (用户, 产品, 订单时间)

第三范式 3NF

满足第二范式的基础上, 除了主键外没有冗余数据

要求
  • 数据没有冗余
示例

在用户表中, 主键是id, 然后包含用户名name字段, 这样在订单表中就不应该重复存储用户名, 只需要存储用户id (主键), 需要用到用户名等其他信息时候, 通过用户id (主键) 在用户表中查询.

反例

用户表

1
id	name	type	password	content

订单表 里有用户名

1
id	user_id user_name	product_id	quantity	timestamp

其他表里也有用户名 user_name

这样就导致了冗余

当你进行更新 user_name 的时候呢?

你要同时维护多张表。而且更新 user_name 要对每个出现的表检查是否更新

  • 这样 问题出现的时候就会呈现指数型增长。
  • 因为你不确定~ 那张表里有这个 user_name
  • 要cover所有情况

正确做法就是 不要用 user_name

而应该使用 user_id

  • 因为 主键是无法更改的
  • 你用的时候连表就行了
目的

消除冗余数据: 一个数据只出现在同一地方, 同样保证了, 我们在维护一个数据时候, 只需要维护好一个地方的数据就好了! 不用去思考到底还有没有其他地方需要同时维护.

: 例如在用户表中有用户名, 在订单表中也有一份用户名, 则我们更改用户表用户名的时候, 就得同时更新订单表里的用户名!

在代码和数据的世界里, 重复是万恶之源

在代码和数据的世界里, 重复是万恶之源

在代码和数据的世界里, 重复是万恶之源

总结

范式主要是我们在设计数据库结构和字段时候需要遵循的一些规则, 用来避免出现的一些坑.

在设计或者Review数据库表的过程中, 需要对照范式来看是否出现反范式的情况.

但是既然不是硬语法规定, 那就是在实际应用中, 在需求, 性能或者特例的考量下, 可以根据实际情况进行妥协, 使用一些反范式的设计, 但是对这些特例需要有特别的关注.

一个原则永远记在心中: 重复是万恶之源, 在关系数据库里, 数据冗余是万恶之源

参考资料

代码链接

Java-025-sql进阶

进阶—SQL语言

SQL 语言

SQL 全称 Structured Query Language, 主要用来查询和更新数据库的一个语言.

SQL 也就是关系数据库的一个标准化交互接口, 而关系数据库中的数据是结构化, 主要的一个操作就是从数据库中查询我们需要的内容.

结构化数据

数据表

关系数据库的结构主题是数据表, 数据库可以说是数据表的合集. 然后数据表中就包含具体的数据记录. 一个数据表会有不同的字段.

  • 数据表
  • 字段 / 主键
  • 记录

主键是每一条数据记录的唯一标志, 同一张数据表中, 所有记录的主键都是不同的!

具体实例就是用户数据库, 我们可以设置用户 id 作为主键, 然后就可以通过用户 id 来唯一确定一个用户. 其他的字段, 用来表示用户的属性, 比如用户的名字, 用户的性别, 用户密码之类属性, 从语义上说, 不同用户是可以有相同的名字, 性别或密码.

用户表 user:

id name type password content
0 Alex normal 123456 Here is the first
1 Alice normal 123456 Here is thhe first user
2 Alex vip password Here is the second Alex

数据库是表的合集, 然后不同表之间可能有相同的字段 (一般是其中一个表的主键), 也就是说, 这些表通过这些相同的字段给联系在一起了, 也就是所谓的关系. 我们查询过程中, 也是通过这些键从不同的数据表中查询到相关的数据.

订单表 order:

id user_id product_id quantity timestamp
0 0 15 5 203231232
1 2 15 1 324324234
2 2 1 3 213213219

上面两个示例表, 可以通过用户id在用户表中查询一个用户的信息, 然后再在订单表中查询这个用户的订单.

SQL数据类型
请参考: SQL数据类型

数据约束
请参考: SQL约束

常用查询(更新, 管理)语句

创建数据库
1
2
CREATE DATABASE shop;
USE shop; // 使用shop这个数据库, 之后在这个数据库下执行语句
创建数据表

用户表 user:

1
2
3
4
5
6
7
CREATE TABLE `user` (
id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR(20) UNIQUE NOT NULL,
type VARCHAR(25) NOT NULL,
password VARCHAR(25) NOT NULL,
content VARCHAR(255)
);

订单表 order:

1
2
3
4
5
6
7
8
CREATE TABLE `order` (
id INTEGER PRIMARY KEY NOT NULL,
user_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
time DATETIME NOT NULL,
status VARCHAR(20) NOT NULL
);
插入数据

插入数据到user表

1
2
3
4
5
6
7
8
INSERT INTO `user` (name, type, password, content)
VALUES ('alex', 'ADMIN', 'password', '管理员账号, 属于shiyang');

INSERT INTO `user` (name, type, password, content)
VALUES ('alic', 'NORMAL', 'password', '普通账号');

INSERT INTO `user` (name, type, password, content)
VALUES ('bob', 'NORMAL', 'password', '普通账号Bob');

插入数据到order表

1
2
3
4
5
INSERT INTO `order` (user_id, product_id, quantity, time, status)
VALUES (1, 1, 5, CURRENT_TIMESTAMP, 'PENDING');

INSERT INTO `order` (user_id, product_id, quantity, time, status)
VALUES (2, 1, 1, CURRENT_TIMESTAMP, 'PENDING');
查询

基本查询语法

1
SELECT 通配符,字段或函数 FROM 表 WHERE 条件 group by 字段

实例

查询所有用户信息:

1
SELECT * FROM `user`;

通过用户id查询用户名和密码:

1
2
SELECT name FROM `user` WHERE id = 1;
SELECT name, password FROM `user` WHERE id = 1;

查询特定数量范围内的订单:

1
SELECT * FROM `order` WHERE quantity between 1 and 10;

根据用户类型分组查询用户:

1
SELECT * FROM `user` GROUP BY type;
更新数据
1
2
UPDATE `user` SET password = '123456'  WHERE id = 1;
UPDATE `user` SET content = '这是更新之后的内容', password = '09876' WHERE id = 1;
Join 查询

SQL 的 Join 子句用于结合两个或多个数据库中表的记录. JOIN 是一种通过共同值来结合两个表中字段的手段. 也就是所谓的关系.

比如: 我们需要查询一个用户的用户名简介及其所有订单商品的id和数量, 这种查询应该怎么做呢?

主要难点: 需要查询的内容来自两个表

Cross Join

Cross join把两个表的所有行进行叉乘拼接 (XY). 即表A的每一行会拼接表B的的所有行 (n行), 然后生成n行数据. 假设表A有x行, 表B有y行, 则输输出总行数为xy行.

1
2
3
SELECT * FROM `user` CROSS JOIN `order`;

SELECT * FROM `user` CROSS JOIN `order` WHERE `user`.id = 1 AND `order`.user_id = 1;

Inner Join

Inner Join和Cross Join相似, 只是可以接收一个条件, 然后把满足这个条件的行组合再一起.

比如: 我们需要查询用户的信息, 简介及其所有订单商品的id和数量, 这种查询应该怎么做呢?

1
SELECT * FROM `user` INNER JOIN `order` ON `user`.id =  `order`.user_id;

Outter Join

JAVA执行SQL

`
CREATE TABLE product (
id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR(20) NOT NULL,
description VARCHAR(25) NOT NULL,
price DOUBLE NOT NULL
);
INSERT INTO product (name, description, price)
VALUES (‘milk’, ‘1 milk 1 day, healthy’, 2.50);

INSERT INTO product (name, description, price)
VALUES (‘coke’, ‘1 coke 1 day, unhealthy’, 1.50);

Java-024-数据库和jdbc

数据库和JDBC

关于数据库的What和Why

之前我们在计算机程序模型中提到过, 在计算机世界中需要处理的几个核心问题:

  • 存储
  • 计算
  • 交互

具体到存储这一块, 之前我们了解过

  • 存储设备 – 内存和硬盘, 以及对具体硬盘磁片的存储单元, 块和内存的存储单元
  • 文件系统 – 在操作系统层面上对存储的抽象, 比如说目录和文件.
  • 应用程序 – 针对不同的应用场景, 可能数据内容的形式和模型会有不同的需求. 比如说, Web程序可能很多文本形式的数据, 多媒体数据等等

数据库则是提供针对不同类型的应用程序 (Web前端程序, Web后端程序, 终端应用等等)的通用数据存储需求的存储需求的高级抽象

本质上, 我们谈存储和数据, 就是在谈状态. 比如:

  • 账号信息
  • 产品信息
  • 订单信息等等

存储, 不管哪个层面上, 都是为了在特定的时限内保存这些实体的状态.

然后很重要的一类计算就是对这些实体的状态的转换和维护, 以及比如:

  • 更新账号密码
  • 更新产品的库存
  • 创建订单

从各种各样的应用程序的实例中, 提炼抽象出的一些通用的数据存储需求, 一起实现到一套系统中, 就产生了数据库:

  • 数据存储
  • 数据结构和组织
  • 数据索引 (如何找到数据)
  • 数据的更新和查询
  • 数据的备份和冗余 (防止丢失和保持可用)
  • 数据一致性, 事务
  • 数据管理
  • 等等

市面上各种不同的数据库, 就是针对不同的场景和不同的需求, 对以上的问题提出针对性的解决方案, 并且有不同的侧重点

数据库类型

按照上面的逻辑, 数据库的特性和应用需求是多维度的. 所以我们在考虑数据库的分类的时候, 也得从不同维度来考虑!

从数据结构和组织

关系数据库 / SQL

MySQL, Oracle, DB2, PostgreSQL, SQLite

对象 / Document / 列-Based (NoSQL)

MongoDB, DynamoDB 等等

Key-Value
leveldb

时间序列
比如金融量化交易系统的金融数据, 时间维度是很重要的!

从数据存储

终端应用数据库

在应用本地存储数据的数据库, 比如说Web前端, Android/ios应用的本地数据库.

  • SQLite

内存数据库

在后端系统中, 可能需要一些访问更快但是不需要持久性的数据, 就可能需要在内存中的数据库. 但是可以获得更快的读取更新速度

  • Redis
  • Memcached

比如:

  • Web程序中的用户登录状态, 就可以存储在内存里, 如果Web后端程序重启之后, 内存的数据库会丢失, 但是只需要用户重新登录就好了, 但是可以获得更快的读取和更新速度
  • 爬虫中的已经访问过的URLs

持久化数据库

MySQL, Oracle, MongoDB可能会更关注持久性存储, 冗余和备份, 保证数据的安全(Security和Safety)等等.

分布式数据库

数据规模扩大之后, 可能需要在多台机器 (分布式) 上存储, 同时提供保证大量数据的存储能力, 数据冗余, 高吞吐的能力

  • HBase
  • Cassandra

云数据库

云上的完整解决方案, 按需付费, 可伸缩性, 更专业稳定的运维能力

  • DynamoDB
  • 腾讯云数据库
  • 阿里云数据库

区块链

区块链本质上是一个可以完全分布到整个互联网的数据库!

关系数据库

关系型数据库是对一类数据库设计经验的总结和设计方法的抽象,以期得到一个通用的数据库的解决方法

关系数据组织数据的基本结构:

  • 数据库
  • 数据库表
  • 数据库字段
  • 数据库记录

SQL是一个对SQL数据库的查询和操作的通用语言

JDBC

Java程序连接SQL数据库,操作SQL数据库和执行SQL语句的库.

  • 程序接口
  • 数据库驱动程序

sqlite-jdbc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class JdbcSample {
public static void main(String[] args)
{
Connection connection = null;
try {
// 创建数据库连接
connection = DriverManager.getConnection("jdbc:sqlite:resources/sample.db");
Statement statement = connection.createStatement();
statement.setQueryTimeout(30); // set timeout to 30 sec.

statement.executeUpdate("drop table if exists person");
statement.executeUpdate("create table person (id integer, name string)");
statement.executeUpdate("insert into person values(1, 'leo')");
statement.executeUpdate("insert into person values(2, 'yui')");
ResultSet rs = statement.executeQuery("select * from person");
while(rs.next()) {
// read the result set
System.out.println("name = " + rs.getString("name"));
System.out.println("id = " + rs.getInt("id"));
}
} catch(SQLException e) {
// if the error message is "out of memory",
// it probably means no database file is found
System.err.println(e.getMessage());
} finally {
try {
if(connection != null)
connection.close();
} catch(SQLException e) {
// connection close failed.
System.err.println(e);
}
}
}
}

参考资料

数据库学习

数据库选择

项目

代码链接

Java-022-控制反转和依赖注入

控制反转和依赖注入

控制反转

控制反转(Inversion of Control)是一种是面向对象编程中的一种设计原则,用来减低计算机代码之间的耦合度. 比如:

A依赖于B, 一种写法是在类A里面使用 new 创建一个类B的对象, 这就是一般所说的A控制B的创建和销毁

而控制反转, 则是让A移交出这个创建权给A的外部, 由外部(相对于A)控制B的创建和销毁. 这样就实现了B的创建和生命周期逻辑和A的解耦.

因为A类里的代码需要去new一个B的对象, 就需要调用B的构造方法: 更新了B的构造方法 –> A中调用的B的构造方法!
X –> A, B, C –> D

在X里创建D的对象, 然后再传给A, B, C

和封装实现细节的对比

依赖注入

通过控制反转, 我们把B的控制权转移到A的外部, 但是A仍然依赖于B, 需要使用B的对象去完成一些事情. 那问题来了:

我们如何把在外部创建的B的对象交给A呢?

  • 在A的构造方法里把B作为一个参数传给A
  • 使用setter方法把B传给A

这个其实就是所谓的依赖注入! 依赖注入其实就是控制反转的一个实现, 差不多也是同一回事.

组件和依赖注入

回忆之前讲Java程序的基本结构: 抽象出一些类和对象, 然后处理对象之间的交互.

然后在具体的场景中, 我们会有一些常用的对象来处理一些常见的逻辑结构, 比如说特定本地数据的访问, 数据库的访问, 配置文件访问, 认证, 这种我们会称之为组件或者模块, 在Spring里称之为Bean.

同一个组件通常可能会在程序中很多地方需要使用, 比如说 UserController 可能会使用到 UserDao 这个组件, ManageControler 也会使用到 UserDao 这个组件. 那最简单的方式是, 我们每次使用的时候都创建一个组件 UserDao 的实例, 即使这个组件是可以重用的! (这里违反了什么原则?)

但是有没有其他更好的方式呢? 就是统一管理组件的创建, 然后在需要使用的地方把这个组件的实例注入.

Spring的组件管理和依赖注入

常用的两种注入方式:

  • 组件直接标记注入
  • 工厂模式注入

区别

直接注入

如果需要被注入的组件, 是我们能够控制的, 那我们就可以使用直接标记注入, 简洁优雅.

工厂模式注入

如果需要注入的组件, 我们需要更复杂的控制创建过程, 比如说动态根据输入内容创建组件, 那就使用工厂模式注入.

或者需要注入的组件是一个外部库的类, 那我们没法给它加上@Component这样的标记, 那我们就必须使用工厂模式注入.

再或者, 我们希望控制组件的数量, 如果是只需要一个组件的实例, 则使用直接注入或者工厂模式都可以. 但是如果需要使用多个实例(比如每次使用都创建一个新的实例), 那就使用工厂模式!

代码链接

Java-021-单元测试

单元测试

@(Programming_Training)

What – 单元测试是什么

单元

我们在构建程序的时候,

都是把程序分成一个个小的模块和组件

这些模块在java里面按照层次可以对应的是:

  • 方法

意思就是测试的粒度, 一般在类和方法这样的粒度上做测试, 也就是说, 一个单元可以是一个类的一个方法

测试

顾名思义, 为了Double check我们实现的代码功能符合我们的预期

Why

Double check

类和方法的实现没有bug, 符合我们在设计时候的预期和要求

功能稳定的保证

类和方法的实现, 在代码的进化和发展过程, 会需要进行反复的修改和重构, 当我们更改代码的时候, 单元测试能够保证修改后功能不会受影响, 从而影响下层依赖这个类和方法的代码

良好的单元测试是重构的基础, 感受一下

对设计的帮助

当你在写单元测试的时候, 势必会把从一个个小的单元(或模块)来考虑程序, 也就是模块化思维, 因为如果不这样思考, 根本没办法写单元测试. 这样测试时候, 强迫自己去考虑代码的模块化

也就是江湖上流传的, 编写可测试的代码

扩展阅读
代码文档

好的测试也可以是一个很好的文档, 刚开始学习一个系统或者程序时候, 可以从其单元测试开始, 了解如何使用其代码, 输入是什么, 输出是什么, 预期的运行结果是什么

How – 如何做单元测试

到此为止, 我们大概知道单元测试的目标是什么 — 试着调用实现的类和方法, 然后确定其正确性

比如, 我们可以在main方法里写测试, 然后开发时候直接跑一下.

但是这样效率太低了, 而且当测试量大的时候, 需要结构化的组织测试代码.

Junit

https://github.com/junit-team/junit4/wiki/Getting-started

通过Java的标记, 组织测试代码, 组织成测试集. 然后每次编译之后, 运行所有的测试集.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Calculator {
public int evaluate(String expression) {
int sum = 0;
for (String summand: expression.split("\\+"))
sum += Integer.valueOf(summand);
return sum;
}
}

import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class CalculatorTest {
@Test
public void evaluatesExpression() {
Calculator calculator = new Calculator();
int sum = calculator.evaluate("1+2+3");
assertEquals(6, sum);
}
}

EasyMock

http://easymock.org

当我们在写测试的时候, 我们会经常遇到一个问题就是, 我们会有很多依赖的模块, 或者依赖的内容, 比如一些我们使用的库, 查询的数据库, 采集的网络数据

如果测试中都去获取这些依赖的内容, 就会影响测试代码的可靠性和性能.

而且我们依赖的模块和内容, 维护其正确性的责任不在我们! 我们测试应该更关注我们自己代码的逻辑和实现的正确性!

比如在爬虫中, 我们采集的网页内容, 就是依赖, 如果我们测试时候, 我们都必须去爬一下内容, 那效率就特别低! 我们可以在本地存储一份用于测试的网页内容

比如数据库查询, 我们不可能在测试代码中真正去操作数据库(炒鸡危险的行为!), 所以我们需要模拟(Mock)一个假的数据对象

EasyMock就是用来解决这个问题的! Mock需要的依赖, 一般EasyMock是配合JUnit一起使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

public class JsonNewsTestableReaderTest extends EasyMockSupport {

private TextNewsTestableReader textNewsTestableReader;
private BufferedReader newsReader;

@Before
public void setup() {
newsReader = createStrictMock(BufferedReader.class);
textNewsTestableReader = new TextNewsTestableReader(newsReader);
}

@Test
public void shouldReadNews() throws Exception {
expect(newsReader.readLine()).andReturn("title");
expect(newsReader.readLine()).andReturn("\n");
expect(newsReader.readLine()).andReturn("content");

replayAll();
News news = textNewsTestableReader.readNews();
verifyAll();

assertEquals("title", news.getTitle());
assertEquals("content", news.getContent());
}
}

maven项目引入单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 <dependencies>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<!-- mock -->
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>4.0.2</version>
<scope>test</scope>
</dependency>
</dependencies>

代码链接