库存管理与分布式文件系统
在电商平台的库存管理系统设计中,将涉及商品和本地图库的管理,这里我们将使用另一种数据开发框架 MyBatis进行数据库访问方面的设计,还将实现与分布式文件系统的对接使用。
本章实例的项目工程是一个商品微服务项目goods-microservice,可以从本文提供的源代码中下载,或者在IDEA中通过Git检出:检出代码后,请获取本实例使用的分支V2.1。本项目包含以下几个模块:
goods-object:公共对象设计。
goods-restapi:库存管理微服务API应用。goods-web:库存管理PC端Web应用。
本篇文章要介绍的内容基于MyBatis的数据库开发,
数据库服务组装,
单元测试,
库存微服务接口开发,
库存管理的Web应用开发,
Web应用项目热部署设置基于MyBatis的数据库开发
有关数据库开发的整个过程是在模块 goods-restapi 中实现的,在这个模块中,我们将使用MyBatis开发框架实现数据库的访问设计。其中,有关数据源的配置及其相关监控与第6章的设计相同,不再说明。
使用经过组装的MyBatis 组件
这里我们将在使用MyBatis组件的基础上,再使用一个经过高级封装设计的MyBatis组件。使用这一组件不但能简化一些基本的查询设计,还能提升程序的性能。在项目对象模型中引入相关组件的依赖,代码如下所示:org.mybatis.spring.bootmybatis-spring-boot-starter1.3.1
tk.mybatismapper-spring-boot-starter2.0.4com.github.pagehelperKpagehelper-spring-boot-starter1.2.3
其中,有关tk.mybatis 的设计,如果读者感兴趣,可以登录GitHub官网,搜索Mybatis进行更进一步的了解。
数据对象及其表结构定义
在库存管理中,我们将主要创建一个商品对象Goods,它的定义如下所示:@Table (name = "tgoods")
@ Data
public class Goods {
//商品编号
@Id
@GeneratedValue(strategy =- GenerationType. IDENTITY)private Long id;
//商家编号
private Long merchantid;//主类编号
private Long sortsid;//子类编号
private Long subsid;//商品名称
private string name;//商品内容
private string contents;
//商品图片
private String photo;//价格
private Double price;//购买数量
private Integer buynum;//库存数量
private Integer reserve;//操作员
private String operator;//创建时间
@DateTimeFormat (pattern= "yyyy-MM-dd HH:mm : ss")private Date created;
}
在上面的代码中,主要解释下面几个内容:(1)注解@Table关联了数据库的表格t _goods。
(2)注解@Data使用了Lombok 工具,它会为类的所有属性自动生成setter/getter、equals、canEqual、hashCode和 toString 等方法。
(3)注解@Id和注解@GeneratedValue将在数据创建或编辑时为对象取得数据库ID的值。
(4)注解@DateTimeFormat使用了日期格式化,以保证在数据存取中使用正确的日期格式。
商品对象在数据库中对应的表格为t_goods,这个表结构的定义如下所示:CREATE TABLE 'tgoods'(
'id' bigint (20) NOT NULL AUTO INCREMENT,
' contents' varchar(255)COLLATE utf8 bin DEFAULT NULL,'created' timestamp NOT NULL DEFAULT CURRENT TIMESTAMP,'merchantid' bigint (20) DEFAULT NULL,
'name'varchar(255)COLLATE utf8 bin DEFAULT NULL,
'operator' varchar(255) COLLATE utf8_bin DEFAULT NULL,'photo'varchar(255)COLLATE utf8_bin DEFAULT NULL,'price'double DEFAULT NULL,
'reserve' int(11)DEFAULT NULL,'sortsid' bigint(20) DEFAULT NULL,'subsid' bigint (20) DEFAULT NULL,' buynum' int (11) DEFAULT NULL,PRIMARY KEY ('id')
}
ENGINE=InnoDB AUTO INCREMENT=8 DEFAULT CHARSET=utf8 COLLATE=utf8 bin;
在这个表结构定义中,我们设定商品编号由数据库实现自动生成,同时商品的创建时间默认使用当前时间戳自动赋值。
Mapper 与SQL定制
有了数据对象和表格之后,我们就可以进行 Mapper 设计及其相关的数据库查询定制了。针对商品管理的数据库查询操作来说,我们可以设计一个数据访问接口GoodsMapper,代码如所示:@Repository
public interface GoodsMapper extends MyMapper{
ListgetPage (@Param ( "map")Mapmap);
}
在这个接口设计中,我们只声明了一个getPage 的方法,用来取得商品的列表数据,但是有关数据的增删改查的基本操作,则没有做任何设计。这是因为在代码中我们继承MyMapper,它已经为我们实现了数据访问的基本操作。对应上面的 GoodsMapper 接口定义,我们设计一个 GoodsMapper.xml来定制相关的 SOL查询设计,代码如下所示:
SELECT g.* FROMt goods g WHERE1=1
AND LIKE CONCAT('%',#{map.name}, '8')AND g.merchantid=#{map.merchantid}AND g.sortsid=#{map.sortsid]AND g.subsid=#{map.subsid}AND g.created >=#{map.created}
从上面的SQL查询设计中,我们实现了几个动态条件的查询设计,即可以根据传输的参数,通过商品名称、商家编号和商品创建时间等条件进行查询。数据库服务组装
下面封装一个商品数据的服务类GoodsService,代码如下所示:@service
CTransactional
public class GoodsService {
CAutowired
private GoodsMapper goodsMapper;
public String insert (Goods goods){
try {
goodsMapper.insert(goods);
return goods.getId().toString();}catch(Exception e){
e.printStackTrace();return e.getMessage();
public String update(Goods goods){
try {
goodsMapper. updateByPrimaryKey (goods) ;return goods.getId() .toString();
}catch(Exception e){
e.printStackTrace();return e.getMessage();
}
}
public String delete(Long id){
try{
goodsMapper.deleteByPrimaryKey (id);return id.toString();
}catch(Exception e){
e.printStackTrace();return e.getMessage();
public Goods getById (Long id){
try {
return goodsMapper.selectByPrimaryKey(id);}catch (Exception e){
e.printStackTrace();return null;
public PageInfogetPage (GoodsQ0 goodsQ0)throws Exception t
try {
Mapmap = MapToBeanUtil.transBean2Map(goodsQo);
if(!StringUtils.isEmpty(goodsQo.getName()){
map.put( "name" , goodsQo.getName());
}
if(!StringUtils.isEmpty(goodsQo.getMerchantid())){
map.put ("merchantid",goodsQo.getMerchantid());
}
if(!StringUtils.isEmpty(goodsQo.getSortsid())){
map.put("sortsid",goodsQo.getSortsid());
if(!StringUtils.isEmpty(goodsQo.getSubsid())){
map .put("subsid",goodsQo.getSubsid());
}
//把日期转为字符串
if (!StringUtils.isEmpty(goodsQo.getCreated())){
map. put("created",
CommonUtils.formatDateTime (goodsQo.getCreated()));
}
PageHelper.startPage (goodsQo.getPage(), goodsQo.getSize());Listlist = goodsMapper.getPage (map);
return new PageInfo (list);
}catch(Exception e){
e.printStackTrace();return null;
}
}
}
这是一个完整的代码,从中我们可以看到一些数据查询的基本操作,主要是通过调用接口GoodsMapper 的父接口来实现的,这是 tk.mybatis组件提供的功能。其中,分页设计的调用和实现是我们自定义的设计。注意,在参数传输中,日期数据必须进行格式化转换。单元测试
在完成数据服务的一系列设计后,我们就可以使用GoodsService进行单元测试了。下面的代码是一个插入商品数据的测试用例:org.mybatis.spring.bootmybatis-spring-boot-starter1.3.1
tk.mybatismapper-spring-boot-starter2.0.4com.github.pagehelperKpagehelper-spring-boot-starter1.2.3
参照这个测试用例,我们还可以对商品数据服务的其他操作进行测试。有关这部分的内容,可以参考源代码的设计。库存微服务接口开发
在模块 goods-restapi完成了商品数据服务的开发之后,我们就可以在这个模块中进行微服务接口的开发了。
在主程序中支持MyBatis
由于使用了MyBatis组件,在主程序GoodsRestApiApplication中必须增加对Mapper的扫描设置,代码如下所示:@Table (name = "tgoods")
@ Data
public class Goods {
//商品编号
@Id
@GeneratedValue(strategy =- GenerationType. IDENTITY)private Long id;
//商家编号
private Long merchantid;//主类编号
private Long sortsid;//子类编号
private Long subsid;//商品名称
private string name;//商品内容
private string contents;
//商品图片
private String photo;//价格
private Double price;//购买数量
private Integer buynum;//库存数量
private Integer reserve;//操作员
private String operator;//创建时间
@DateTimeFormat (pattern= "yyyy-MM-dd HH:mm : ss")private Date created;
}
基于REST协议的控制器设计
REST 的接口开发可以由Spring MVC的控制器实现,商品接口控制器GoodsRestController的部分实现代码如下所示:@RestController
@RequestMapping( " /goods")
public class GoodsRestController {
private static Logger logger =
LoggerFactory.getLogger(GoodsRestController.class);
@Autowired
private GoodsService goodsservice;
@GetMapping (value="/{id] ")
public String fnidById(@PathVariable Long id){
Goods goods =goodsService.getById(id);
return new Gson( .toJson (goods) ;
cGetMapping()
public string findAll(Integer index,Integer size,Long merchantid,
String name, Long sortsid, Long subsid,
String created) {
try{
GoodsQo goodsQ0 =new GoodsQ0();
if(CommonUtils.isNotNul1(index)){
goodsQo.setPage (index);
}
if(CommonUtils.isNotNull(size)){
goodsQo.setSize(size);
if(CommonUtils.isNotNull (merchantid)){
goodsQo.setMerchantid(merchantid);
}
if(CommonUtils.isNotNull(name)){
goodsQo.setName (name);
}
if(CommonUtils.isNotNull(sortsid)){
goodsQo.setSortsid(sortsid);
}
if(CommonUtils.isNotNull(subsid)){
goodsQo.setSubsid (subsid);
if(CommonUtils.isNotNull(created)){
SimpleDateFormat sdf = new SimpleDateFormat ("yyvvy-MM-dd
HH:mm:ss");
goodsQo .setCreated (sdf.parse(created));
PageInfopage= goodsService.getPage(goodsQ0);
return new Gson( .toJson (page);
]catch(Exception e){
e.printStackTrace();
)
return null;
@PostMapping()
public String save(@RequestBody GoodsQo goodsQo) throws Exceptiont
Goods goods = new Goods();
BeanUtils.copyProperties(goodsQo,goods);goods.setCreated(new Date());
String response = goodsService.insert(goods);
("新增=”+ response);
return response;
}
...
}
从上面代码中可以看出,我们通过商品数据服务GoodsService可以对外提供有关商品数据访问的各种接口。需要注意的是,上面分页查询使用了GET方法,所以参数的传输不能直接使用查询对象,必须每个参数单独指定。不过,我们在进行接口调用设计时,可以使用查询对象进行转换,具体可以参考7.5节。库存管理的Web应用开发
有关库存管理的Web应用的微服务开发,主要是通过调用商品管理的微服务接口实现的,这里面的设计和实现方法与第6章中类目管理的设计和实现方法十分相似,所以不再做全面的详细说明,只针对某些不同点进行简要的介绍。
库存管理的Web应用的微服务设计是在模块goods-web中实现的,这是一个独立的微服务应用,可以单独使用一个项目工程来设计。
公共对象的依赖引用
当我们管理商品时,必将用到商品的类目设定,所以在模块 goods-web中,为了方便使用相关的查询对象,必须有其相关的依赖引用,代码如下所示:CREATE TABLE 'tgoods'(
'id' bigint (20) NOT NULL AUTO INCREMENT,
' contents' varchar(255)COLLATE utf8 bin DEFAULT NULL,'created' timestamp NOT NULL DEFAULT CURRENT TIMESTAMP,'merchantid' bigint (20) DEFAULT NULL,
'name'varchar(255)COLLATE utf8 bin DEFAULT NULL,
'operator' varchar(255) COLLATE utf8_bin DEFAULT NULL,'photo'varchar(255)COLLATE utf8_bin DEFAULT NULL,'price'double DEFAULT NULL,
'reserve' int(11)DEFAULT NULL,'sortsid' bigint(20) DEFAULT NULL,'subsid' bigint (20) DEFAULT NULL,' buynum' int (11) DEFAULT NULL,PRIMARY KEY ('id')
}
ENGINE=InnoDB AUTO INCREMENT=8 DEFAULT CHARSET=utf8 COLLATE=utf8 bin;
从上面的代码中可以看出,这里同时引用了商品和类目的查询对象依赖。其中,为了减少—些组件的重复引用,使用了排除设置。
商品分页数据调用设计
在商品应用管理中,对微服务接口的调用,同样是使用FeignClient 来设计的。这里不但涉及商品数据服务微服务接口的调用,还涉及类目数据服务微服务接口的调用。因为这里涉及查询对象的参数转换方法,所以针对微服务接口的调用和 FeignClient 的使用这部分内容,需要进一步说明。
在商品管理调用的客户端设计GoodsClient 中,分页数据查询部分的代码如下所示:@Repository
public interface GoodsMapper extends MyMapper{
ListgetPage (@Param ( "map")Mapmap);
}
在这个设计中,参数传输是按照微服务接口提供方提供的方法进行设计的,所以每个参数都必须按原接口的属性设置。
现在,我们来看看服务类GoodsRestService的设计,代码如下所示:对应上面的 GoodsMapper 接口定义,我们设计一个 GoodsMapper.xml来定制相关的 SOL查询设计,代码如下所示:
SELECT g.* FROMt goods g WHERE1=1
AND LIKE CONCAT('%',#{map.name}, '8')AND g.merchantid=#{map.merchantid}AND g.sortsid=#{map.sortsid]AND g.subsid=#{map.subsid}AND g.created >=#{map.created}
在这里,我们为调用方提供了使用查询对象GoodsQo进行参数传输的方法,只有在程序中针对GoodsClient进行调用时,才从查询对象中取出每一个需要的参数。Web应用项目热部署设置
在Web应用开发过程中,我们经常需要对操作界面进行调整,如果需要频繁地重启应用,则不仅耗时耗力,还影响开发的效率和进度。因此,下面介绍如何在 IDEA 中进行热部署设置。
首先,在 Web模块的项目对象模型中增加热部署组件引用和构建工程的插件配置,代码如下所示:@service
CTransactional
public class GoodsService {
CAutowired
private GoodsMapper goodsMapper;
public String insert (Goods goods){
try {
goodsMapper.insert(goods);
return goods.getId().toString();}catch(Exception e){
e.printStackTrace();return e.getMessage();
public String update(Goods goods){
try {
goodsMapper. updateByPrimaryKey (goods) ;return goods.getId() .toString();
}catch(Exception e){
e.printStackTrace();return e.getMessage();
}
}
public String delete(Long id){
try{
goodsMapper.deleteByPrimaryKey (id);return id.toString();
}catch(Exception e){
e.printStackTrace();return e.getMessage();
public Goods getById (Long id){
try {
return goodsMapper.selectByPrimaryKey(id);}catch (Exception e){
e.printStackTrace();return null;
public PageInfogetPage (GoodsQ0 goodsQ0)throws Exception t
try {
Mapmap = MapToBeanUtil.transBean2Map(goodsQo);
if(!StringUtils.isEmpty(goodsQo.getName()){
map.put( "name" , goodsQo.getName());
}
if(!StringUtils.isEmpty(goodsQo.getMerchantid())){
map.put ("merchantid",goodsQo.getMerchantid());
}
if(!StringUtils.isEmpty(goodsQo.getSortsid())){
map.put("sortsid",goodsQo.getSortsid());
if(!StringUtils.isEmpty(goodsQo.getSubsid())){
map .put("subsid",goodsQo.getSubsid());
}
//把日期转为字符串
if (!StringUtils.isEmpty(goodsQo.getCreated())){
map. put("created",
CommonUtils.formatDateTime (goodsQo.getCreated()));
}
PageHelper.startPage (goodsQo.getPage(), goodsQo.getSize());Listlist = goodsMapper.getPage (map);
return new PageInfo (list);
}catch(Exception e){
e.printStackTrace();return null;
}
}
}
然后,在 IDEA 中,使用组合键“Shift+Ctrl+Alt+/”打开“Maintenance”对话框,选择“Registry…选项”,如图7-1所示。
在打开的“Registry”配置中,勾选“.running”选项,如图7-2所示。对于这一步骤,只要设置过一次即可,不用对每一个项目都进行设置。
接着,在IDEA 设置窗口中,选择“Complier”选项,勾选其下面的“Build project automatically"选项,如图7-3所示。这一设置必须针对每一个项目进行设定。
这样,就可以打开浏览器的开发者工具窗口(例如 Chrome),勾选“Network”菜单下面的“Disable cache”选项,如图7-4所示。
另外,为了不让每次修改一个Class就触发应用重启,我们可以在配置文件“application.yml"中增加如下所示的配置项:spring.devtools.restart.enabled: falsespring.devtools.livereload.enabled: false
完成上面这些配置之后,当我们修改页面设计时,程序就会进行自动更新了。在修改类文件时,需要手动重启一下,这样可以避免每修改一行代码,就自动触发应用重启。