5.1.2 基本映射方式

  1. 数据映射:持久化类被映射到一个数据表。程序使用这个持久化类来创建实例、修改属性、删除实例时,系统自动会转换为对这个表进行CRUD 操作。受ORM 管理的持久化类对应一个数据表,只要程序对这个持久化类进行操作,系统就可以转换成对对应数据表的操作
  2. 数据表的映射对象:持久化类会生成很多实例,每个实例就对应数据表中的一行记录。当程序在应用中修改持久化类的某个实例时, ORM 工具将会转换成对对应数据表中特定行的操作。
  3. 数据表的(字段)映射对象的属性: 当程序修改某个持久化对象的指定属性时(持久化实例映射到数据行) , ORM 将会转换成对对应数据表中指定数据行、指定列的操作。

基于这种基本的映射方式,ORM工具可完成对象模型和关系模型之间的相互映射。在ORM框架中,持久化对象是一种中间媒介,应用程序只需操作持久化对象,ORM框架则负责将这种操作转换为底层数据库操作,从而将开发者从关系模型中释放出来,使得开发者能以面向对象的思维操作关系数据库.

10.2 实现Hibernate持久化类

本系统打算使用贫血模型定义Domain Object,系统Domain``Object类就是持久化类,这些持久化类仅仅为各属性提供必需的settergetter方法,并未包含业务逻辑方法。所有的业务逻辑方法都由业务逻辑组件提供实现

10.2.1 设计Domain Object

本系统的开发并未完全按OOAOOD的过程进行,而是采用了**传统的信息化系统开发过程,先设计系统的数据库**。因此在系统建模期间,已经得到了系统的E/R图,根据E/R图可以创建数据库的表,数据库表结构建立以后,可以根据表结构编写持久化对象。
虽然这个过程并不完全符合面向对象的设计过程,但因为数据库的建立对于企业信息应用非常重要,往往难以放弃分析系统的E/R关系图,因此E/R图的建立也是非常基础的部分。实际上E/R图也可用于辅助设计Domain Object
本系统一共有如下5个Domain Object对象。

对象 描述
AuctionUser 对应注册用户,包括用户名、密码、Email地址等信息。
Kind 对应物品种类,包括种类名、种类描述等信息。
State 对应物品的状态信息,包含状态名等信息。
Item 对应物品,包含物品名、物品描述、物品备注、物品种类、物品状态等信息。
Bid 对应竞价信息,包含竞价物品、参与竞价的用户、竞价价格等信息。

不仅如此,5个Domain Object之间的关联关系也比较多,它们之间存在着如下关联关系。

  • AuctionUser(用户)与Item(物品)之间存在两种关系:所有者关系和赢取者关系。这两种关系都是1对N的关系,即AuctionUser可以访问他所赢取的全部物品,也可以访问他所拥有的全部物品,因为AuctionUser通过Set类型的变量来分别保存他的赢取物品和所有物品。而Item里则保存AuctionUser的变量,分别是它对应的所有者和赢取者。
  • Kind(物品种类)和Item(物品)之间存在1对N的关系,
    • Kind里以Set类型属性保存该种类下的全部物品,
    • Item里以Kind类型属性保存它所在的种类。
  • State(物品状态信息)和Item(物品)之间存在1对N的关系,
    • StateSet类型属性保存该状态下的全部物品,
    • ItemState类型属性保存它所处的状态。
  • Item(物品)和Bid(竞价信息)之间存在1对N的关系,
    • ItemSet类型属性保存该物品的全部竞价,
    • BidItem类型属性保存它对应的物品。
  • AuctionUser(用户)和Bid(竞价信息)之间也存在1对N的关系,
    • AuctionUserSet类型属性保存该用户参与的全部竞价,
    • BidUser类型属性保存参与竞价的用户。

图10.8显示了5个实体之间的关联关系。
这里有一张图片

10.1.2 数据库设计

本系统的E/R图如图10.2所示。
这里有一张图片
本系统的数据库系统使用MySQL建立,包含5张数据表,分别用于存放E/R图中的5个实体。
auction_user表用于存放系统的注册用户信息,其表结构如下所示。

1
2
3
4
5
6
7
8
9
10
mysql> desc auction_user;
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| user_id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(50) | NO | UNI | NULL | |
| userpass | varchar(50) | NO | | NULL | |
| email | varchar(100) | NO | | NULL | |
+----------+--------------+------+-----+---------+----------------+
4 rows in set

kind表用于存放物品种类,其表结构如下所示:

1
2
3
4
5
6
7
8
9
mysql> desc kind;
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| kind_id | int(11) | NO | PRI | NULL | auto_increment |
| kind_name | varchar(50) | NO | | NULL | |
| kind_desc | varchar(255) | NO | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
3 rows in set

item表用于存放物品,其表结构如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mysql> desc item;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| item_id | int(11) | NO | PRI | NULL | auto_increment |
| item_name | varchar(255) | NO | | NULL | |
| item_remark | varchar(255) | YES | | NULL | |
| item_desc | varchar(255) | YES | | NULL | |
| kind_id | int(11) | NO | MUL | NULL | |
| addtime | date | NO | | NULL | |
| endtime | date | NO | | NULL | |
| init_price | double | NO | | NULL | |
| max_price | double | NO | | NULL | |
| owner_id | int(11) | NO | MUL | NULL | |
| winer_id | int(11) | YES | MUL | NULL | |
| state_id | int(11) | NO | MUL | NULL | |
+-------------+--------------+------+-----+---------+----------------+
12 rows in set

state表用于存放拍卖物品的状态,其表结构如下所示:

1
2
3
4
5
6
7
8
mysql> desc state;
+------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+----------------+
| state_id | int(11) | NO | PRI | NULL | auto_increment |
| state_name | varchar(10) | YES | | NULL | |
+------------+-------------+------+-----+---------+----------------+
2 rows in set

bid表用于存放竞价记录,其表结构如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
mysql> desc bid;
+-----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+----------------+
| bid_id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | NO | MUL | NULL | |
| item_id | int(11) | NO | MUL | NULL | |
| bid_price | double | NO | | NULL | |
| bid_date | date | NO | | NULL | |
+-----------+---------+------+-----+---------+----------------+
5 rows in set

10.1 总体说明和概要设计

本章的应用包括了前端开发+后端应用两个部分。本应用的后台结构是一个完善的轻量级Java EE架构,应用架构采用Spring MVC作为后端控制器,负责对外提供JSON响应,jQuery则负责与Spring MVC暴露的JSON接口进行交互。

10.1.1 系统的总体架构设计

该系统后台采用Java EE三层结构,分别为控制器层业务逻辑层数据服务层。其中,将业务规则、数据访问等工作放到中间层处理,客户端不直接与数据库交互,而是通过控制器与中间层建立连接,再由中间层与数据库交互。系统的数据持久化层使用MySQL数据库存放数据。
系统使用HTML页面作为表现层,jQuery通过调用Spring MVC所暴露的JSON接口与服务器端交互,当jQuery拿到服务器端响应的JSON数据后,jQuery会读取、遍历JSON数据,然后通过jQueryDOM操作将数据动态显示在页面上。

中间层分层

系统中间层采用Spring 4.3+Hibernate 5.2结构,为了更好地分离,中间层又可细分为如下几层。

  1. 控制器层:控制器层负责对外暴露JSON接口。
  2. 业务逻辑层:负责实现业务逻辑,业务逻辑组件是DAO组件的门面。
  3. **DAO层**:封装了数据的增、删、改、查等原子操作。
  4. Domain Object 层(领域对象层):通过实体/关系映射工具将领域对象映射成持久化对象,从而可以以面向对象方式操作数据库。本系统采用Hibernate作为ORM框架

Spring框架贯穿整个中间层,Spring可以管理持久化访问所需的数据源,也可以管理HibernateSessionFactory,并可以管理业务逻辑组件和DAO组件之间的依赖关系。整个系统前端综合使用了jQueryBootstrap,其中Bootstrap负责提供丰富的CSS样式以及各种界面组件,而jQuery则负责与Spring MVC暴露的JSON接口交互,向服务器端提交请求,获取服务器端响应的数据,并将服务器端响应的数据动态更新在页面上。系统的总体架构如图10.1所示。
这里有一张图片

第10章 jQuery+Bootstrap 整合开发:电子拍卖系统

本章要点

  • 传统Java EE应用的系统设计
  • 分析、提取系统的Domain Object
  • 映射Hibernate的持久化对象
  • 基于Hibernate 5实现DAO组件
  • Spring容器中部署DAO组件
  • 实现业务逻辑组件
  • 部署业务逻辑组件
  • 使用声明式事务机制为业务逻辑方法增加事务控制
  • 利用Spring邮件抽象层发送竞价确认邮件
  • 利用Spring任务调度处理拍卖到期的物品
  • 使用Spring MVC暴露前端JSON接口
  • 前端控制器的异常处理方式
  • 使用jQuery异步装载页面片段
  • 使用Bootstrap构建前端界面
  • 使用jQuery发送异步请求
  • 使用jQuery动态更新HTML页面

本章介绍的系统是一个前端开发+后端整合的系统,本系统前端综合使用了jQuery+Bootstrap,后端则整合使用了Spring MVCSpringHibernate这些框架。
该系统是一个模拟的电子拍卖系统。注册用户可以在这里发布拍卖物品,参与竞价。非注册用户可以浏览拍卖物品,浏览流拍物品。如果到了物品的拍卖期限,系统提供后台线程判断物品是流拍了,还是被最高竞价者赢取。注册用户参与竞价后,系统会发送邮件通知竞价用户。Spring的任务调度负责启动后台线程来修改物品状态;Spring的邮件抽象层负责发送竞价通知邮件。

本系统使用Hibernate作为持久层的ORM框架,使用Spring管理业务层组件和持久层组件。Spring MVC作为前端MVC控制器,用于对外暴露JSON接口供前端界面调用,权限控制也在Spring MVC层完成。本应用的界面使用Bootstrap的样式和组件实现;使用jQuery作为异步交互的引擎,负责与前端和后端的交互,并通过jQuery封装的方法来操作DOM页面。

5.1 ORM和Hibeniate

目前流行的编程语言,如JavaC#等,它们都是面向对象的编程语言,而目前主流的数据库产品,例如OracleDB2等,依然是关系数据库。编程语言和底层数据库的发展不协调,催生出了ORM框架。**ORM框架可作为面向对象编程语言和数据库之间的桥梁**。

5.1.1 对象/关系数据库映射(ORM)

什么是ORM

ORM的全称是Object/Relation Mapping,即对象/关系数据库映射。是一个可以把关系数据库包装成面向对象的模型的工具

ORM框架的基本特征

ORM框架的基本特征:完成面向对象的编程语言到关系数据库的映射可把ORM框架当成应用程序和数据库的桥梁

ORM的作用

ORM工具的唯一作用就是**:把对持久化对象的保存、删除、修改等操作,转换成对数据库的操作。以便程序员可以以面向对象的方式操作持久化对象**,而ORM框架则负责转换成对应的SQL(结构化查询语言)操作。

第5章 Hibernate的基本用法

本章要点

  • ORM的基本知识
  • ORMHibernate的关系
  • Hibernate的基本映射思想
  • Hibernate入门知识
  • 使用 Eclipse开发 Hibernate应用
  • Hibernate的体系和核心API
  • Hibernate的配置文件
  • 持久化类的基本要求
  • 持久化对象的状态
  • Hibernate的基本映射
  • 数据库对象映射
  • ListSetMap等集合属性映射
  • 组件属性映射
  • 集合元素为复合类型的映射
  • 复合主键映射
  • 使用传统XML映射文件管理映射信息

Hibernate是轻量级Java EE应用的持久层解决方案, Hibernate不仅管理Java类到数据库表的映射(包括Java数据类型到SQL数据类型的映射),还提供数据査询和获取数据的方法,可以大幅度缩短处理数据持久化的时间。
目前的主流数据库依然是关系数据库,而Java语言则是面向对象的编程语言,当把二者结合在起使用时相当麻烦,而Hibernate则减少了这个问题的困扰,它完成对象模型和基于SQL的关系模型的映射关系,使得应用开发者可以完全采用面向对象的方式来开发应用程序。
Hibernate较之另一个持久层框架MyBatis, Hibernate更具有面向对象的特征;受Hibernate的影响,Java EE5规范抛弃了传统的Entity EJB,改为使用JPA作为持久层解决方案。而JPA实体完全可以当成Hibernate PO(Persistent Object,持久化对象)使用,由此可见Hibernate的影响深远。 Hibernate倡导低侵入式的设计,完全采用普通的Java对象(POJO)编程,不要求PO继承Hibernate的某个超类或实现Hibernate的某个接口
Hibernate充当了面向对象的程序设计语言和关系数据库之间的桥梁,** Hibernate允许程序开发者采用面向对象的方式来操作关系数据库**。因为有了Hibernate的支持,使得Java EE应用的OOA(面向对象分析)、OOD(面向对象设计)和OOP(面向对象编程)三个过程一脉相承,成为一个整体。

8.12 本章小结

本章主要介绍了Bootstrap内置的各种插件,Bootstrap基本为每种插件都提供了两种使用方式:使用data-*属性和JS代码。需要同时掌握这两种使用Bootstrap内置组件的方式。本章详细介绍了对话框插件的用法、下拉菜单的用法、滚动监听插件的功能和用法、标签页插件的用法、胶囊式标签页的用法、工具提示插件的用法、弹出框插件的功能和用法、警告框插件的用法、按钮插件的用法、折叠插件的用法和利用折叠插件实现手风琴效果、轮播图插件的功能和用法。

8.11.4 轮播图事件

Bootstrap为轮播图提供了如下两个事件。

事件 描述
slide.bs.carousel 当轮播图开始切换图片时触发该事件。
slid.bs.carousel 当轮播图切换图片完成时触发该事件,也就是CSS过渡动画执行完成时触发该事件。

程序示例

例如我们在前面的carousel-data.html页面的后面添加如下JS脚本。

1
2
3
4
5
6
7
8
<script type="text/javascript">
// 轮播图事件
$('#myCarousel').on('slide.bs.carousel', function () {
console.log("轮播图的图片开始切换");
}).on('slid.bs.carousel', function () {
console.log("轮播图的图片切换完成");
})
</script>

这段代码为轮播图的两个事件都绑定了事件处理函数,因此无论图片开始切换,还是图片切换完成都会触发相应的事件。

完整代码

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<!DOCTYPE html>
<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title> 轮播图 </title>
<link rel="stylesheet" href="../bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="../bootstrap/css/bootstrap-theme.min.css">
<script type="text/javascript" src="../jquery-3.1.1.js"></script>
<script type="text/javascript" src="../bootstrap/js/bootstrap.min.js"></script>
</head>

<body>
<div class="container">
<!-- 轮播图容器 -->
<div id="myCarousel" class="carousel" data-ride="carousel" data-interval="1000" data-pause='hover'>
<!-- 轮播图显示器 -->
<ul class="carousel-indicators">
<li class="active" data-slide-to="0" data-target="#myCarousel"></li>
<li data-slide-to="1" data-target="#myCarousel"></li>
<li data-slide-to="2" data-target="#myCarousel"></li>
<li data-slide-to="3" data-target="#myCarousel"></li>
</ul>
<!-- 轮播图主体内容 -->
<div class="carousel-inner" role="listbox">
<!-- 每个class='item'的div元素代表一个轮播项 -->
<div class="item active">
<img src="images/lijiang.jpg" alt="漓江">
<!-- 图片说明 -->
<div class="carousel-caption">
<h4>漓江</h4>
<div>漓江风光有山青、水秀、洞奇、石美“四胜”之誉。从桂林至阳朔的83公里漓江河段,集中了桂林山水的精华,令人有“舟行碧波上,人在画中游”之感。</div>
</div>
</div>
<div class="item">
<img src="images/shuangta.jpg" alt="双塔">
<!-- 图片说明 -->
<div class="carousel-caption">
<h4>金银双塔</h4>
<div>金银双塔白天和夜晚晚会呈现出截然不同的美景,白天让人觉得庄严、肃穆,而当夜幕降临,在灯光的映照下,则给人以亲切温馨的感觉。</div>
</div>
</div>
<div class="item">
<img src="images/qiao.jpg" alt="桥">
</div>
<div class="item">
<img src="images/xiangbi.jpg" alt="象鼻山">
</div>
</div>
<!-- 轮播图的前、后控制按钮 -->
<a class="left carousel-control" role="button" data-slide="prev" href="#myCarousel">
<span class="glyphicon glyphicon-chevron-left"></span>
</a>
<a class="right carousel-control" role="button" data-slide="next" href="#myCarousel">
<span class="glyphicon glyphicon-chevron-right"></span>
</a>
</div>
</div>
<script type="text/javascript">
// 轮播图事件
$('#myCarousel').on('slide.bs.carousel', function () {
console.log("轮播图的图片开始切换");
}).on('slid.bs.carousel', function () {
console.log("轮播图的图片切换完成");
})
</script>
</body>

</html>

8.11.3 通过JS触发轮播图

Bootstrap同样支持使用JS来触发轮播图。Bootstrap为轮播图提供了一个carousel()方法,该方法有如下几个用法。

  1. 不传入参数:默认情况下,轮播插件下载完成后会自动解析data-*属性激活轮播图。如果在轮播图容器上没有指定data-ride属性,则需要调用该方法。
  2. 传入JS对象:JS对象支持intervalpausewrapkeyboard这些选项,这些选项与应用于轮播图容器上的data-*属性的意义相同。
  3. 传入字符串参数或数字参数。对于传入字符串参数的情形,Bootstrap可支持传入如下参数。
    • 'cycle':让轮播图从左到右开始轮播。
    • 'pause':让轮播图停止。
    • 整数:让轮播图跳转到特定的图片。
    • 'prev':让轮播图跳转到上一张图片。
    • 'next':让轮播图跳转到下一张图片。

程序示例

下面代码示范了通过JS代码来实现动态轮播图。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<!DOCTYPE html>
<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title> 轮播图 </title>
<link rel="stylesheet" href="../bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="../bootstrap/css/bootstrap-theme.min.css">
<script type="text/javascript" src="../jquery-3.1.1.js"></script>
<script type="text/javascript" src="../bootstrap/js/bootstrap.min.js"></script>
</head>

<body>
<div class="container">

<button class="btn btn-primary" onclick="$('#myCarousel').carousel('cycle');">
开启自动播放</button>
<button class="btn btn-danger" onclick="$('#myCarousel').carousel('pause');">
停止自动播放</button>
<!-- 轮播图容器 -->
<div id="myCarousel" class="carousel">
<!-- 轮播图显示器 -->
<ul class="carousel-indicators">
<li class="active" onclick="$('#myCarousel').carousel(0);"></li>
<li onclick="$('#myCarousel').carousel(1);"></li>
<li onclick="$('#myCarousel').carousel(2);"></li>
<li onclick="$('#myCarousel').carousel(3);"></li>
</ul>
<!-- 轮播图主体内容 -->
<div class="carousel-inner" role="listbox">
<!-- 每个class='item'的div元素代表一个轮播项 -->
<div class="item active">
<img src="images/lijiang.jpg" alt="漓江">
<!-- 图片说明 -->
<div class="carousel-caption">
<h4>漓江</h4>
<div>漓江风光有山青、水秀、洞奇、石美“四胜”之誉。从桂林至阳朔的83公里漓江河段,集中了桂林山水的精华,令人有“舟行碧波上,人在画中游”之感。</div>
</div>
</div>
<div class="item">
<img src="images/shuangta.jpg" alt="双塔">
<!-- 图片说明 -->
<div class="carousel-caption">
<h4>金银双塔</h4>
<div>金银双塔白天和夜晚晚会呈现出截然不同的美景,白天让人觉得庄严、肃穆,而当夜幕降临,在灯光的映照下,则给人以亲切温馨的感觉。</div>
</div>
</div>
<div class="item">
<img src="images/qiao.jpg" alt="桥">
</div>
<div class="item">
<img src="images/xiangbi.jpg" alt="象鼻山">
</div>
</div>
<!-- 轮播图的前、后控制按钮 -->
<a class="left carousel-control" role="button" onclick="$('#myCarousel').carousel('prev');">
<span class="glyphicon glyphicon-chevron-left"></span>
</a>
<a class="right carousel-control" role="button" onclick="$('#myCarousel').carousel('next');">
<span class="glyphicon glyphicon-chevron-right"></span>
</a>
</div>
</div>
</body>

</html>

这里没有为轮播图设置data-*属性来激活轮播图,因此需要通过JS代码激活轮播图。通过JS代码来激活轮播图主要通过为carousel()方法传入不同参数来实现:

  • 调用carousel('cycle')可以控制轮播图从左到右开始轮播。
  • 调用carousel('pause')可以控制轮播图停止轮播。
  • 调用carousel(0)可以控制轮播图跳转到第一张图片。
  • 调用carousel('prev')可以控制轮播图跳转到上一张图片。
  • 调用carousel('next')控制轮播图跳转到下一张图片。