matrix 运行环境搭建好后我们就可以开发一个简单的功能了,通过一个简单功能的开发例子我们可以深入了解 matrix 的技术细节。
提示:本章提供的代码主要是为了帮助大家快速了解 matrix 的开发过程,代码本身简化了所有的注释和一些功能。示例代码与正式的生产代码存在一定差距的请不要用示例代码 copy 到正式的编码中。
| 建表
首先我们需要建立一个商品表,我们这个功能就是完成一个商品功能的增删改查功能。
建表sql
CREATE TABLE `biz_goods` (
`create_by` varchar(100) NOT NULL COMMENT '创建人',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_by` varchar(100) NOT NULL COMMENT '更新人',
`update_time` datetime NOT NULL COMMENT '更新时间',
`gd_id` varchar(32) NOT NULL COMMENT '商品表主键',
`gd_name` varchar(100) DEFAULT NULL COMMENT '商品名称',
`gd_img` varchar(500) DEFAULT NULL COMMENT '商品的照片',
`gd_remark` varchar(4000) DEFAULT NULL COMMENT '商品描述',
PRIMARY KEY (`gd_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品表';
-- 插入一条数据
INSERT INTO `biz_goods` (`create_by`, `create_time`, `update_by`, `update_time`, `gd_id`, `gd_name`, `gd_img`, `gd_remark`)
VALUES
('jyy', '2017-12-12 00:00:00', 'jyy', '2017-12-12 00:00:00', 'qewrqw1231', '衣服', 'img', '好衣服');
('jyy', '2017-12-12 00:00:00', 'jyy', '2017-12-12 00:00:00', 'qewrqw1231', '衣服', 'img', '好衣服');
小贴士:
注意表名,和字段的命名。表名由模块前缀biz + 实际表名 goods构成。字段的名称由表名的简写+字段名称构成。
| 编写BO
什么是 BO
BO(Business Object,业务对象)是一个完整的业务实体,包含属性和方法,比如业务系统中的商品,订单等都是业务实体。BO 对象需要持久化,也就是说 BO 的属性值需要保存在数据库中,所以 BO 对象需要在数据库中有一个或者多个和它对应的表。
BO 与 DTO 的区别
BO 是 DTO 的超集,数据传输对象(DTO)(Data Transfer Object),它和数据库中的字段一一对应 BO 相对于 DTO,BO 承载了更多的功能和意义。在 matrix 中两种对象都可以存在,但是一般使用 BO。
如下是我们编写的BizGoods.java 是一个DTO对象,如果有包含数据库字段以外的属性放在这个类的时候它就变成了 BO 对象了
package com.zkingsoft.bean;
import com.matrix.core.anotations.Extend;
import com.matrix.core.pojo.EntityDTO;
public class BizGoods extends EntityDTO {
@Extend
private static final long serialVersionUID = 1L;
/**
* 商品表主键
*/
private String gdId;
/**
* 商品名称
*/
private String gdName;
/**
* 商品的照片
*/
private String gdImg;
/**
* 商品描述
*/
private String gdRemark;
public String getGdId() {
return gdId;
}
public void setGdId(String gdId) {
this.gdId = gdId;
}
public String getGdName() {
return gdName;
}
public void setGdName(String gdName) {
this.gdName = gdName;
}
public String getGdImg() {
return gdImg;
}
public void setGdImg(String gdImg) {
this.gdImg = gdImg;
}
public String getGdRemark() {
return gdRemark;
}
public void setGdRemark(String gdRemark) {
this.gdRemark = gdRemark;
}
}
BO 结构说明:
BO 放在 com.zkingsoft.bean 包下,BO 类继承 EntityDTO, EntityDTO 提供了所有 BO 的
公共字段(创建时间、创建人、修改人,修改日期)和一个 tostring 方法。也就是说 EntityDTO 的
子类不需要再创建这 4 个属性和 tostring 方法。
小贴士:
细心的同学可能已经发现 serialVersionUID 字段加了一个@Extend 注解,这是因为serialVersionUID 并不是数据库中的字段,所以用这个注解来标识。标识的主要做用是用来防止数据覆盖更新的问题,这是 matrix 的数据更新策略决定的。
假如有这样的业务场景,数据库中存在一条商品数据价格为 12 元,库存为 100 件。这个时候客户 A 和客户 B 同时打开了商品信息的编辑界面,A 和 B 看到的信息都是 12 元 100 件。
-
1、 A 编辑了价格把价格改成 30 元。然后保存了数据,这个时候回数据库就变成了 30 元
100 件
-
2、 接着 B 编辑了库存把 100 改成 80 然后保存,如果不加任何处理直接更新,商品的价格
会被再次更新成 12 元从而覆盖掉 A 设置的 30 元。这就造成了数据覆盖的问题。
-
3、 其实 B 并没有改动价格他不关心价格但是确覆盖了价格
如何防止这样的数据覆盖呢?在 matrix 中我们做数据更新的时候先比较更新前和更新后的 BO 对象,只更新有被修改的值,没有被修改的值是不会更新的。这样就防止了数据覆盖的问题。
如何实现这个功能呢?我们写了一个工具类 ModelUtils,通过反射的方式获取 BO 的属
性值,然后比较新旧 BO 的属性值如果属性值有变化则是需要更新到数据库的属性。
因为 BO 对象包含了数据库中有和没有的字段,如果不加以区分我们就无法找到那些字段是要更新的那些是不要更新的。所以我们用一个注解来区分哪些字段是需要被比较哪些是不需要被比较的,这个注解就是@Extend。
| 编写 Dao
Dao 是数据库访问对象(Data Access Object),它抽象和封装了所有对持久化存储介质的访问。Dao 处理可以访问数据库,除了数据库还包括 XML,内存数据等,matrix 中主要是通过封装 mybatis 来访问数据库,如下是我们创建的一个商品 Dao 对象:
package com.zkingsoft.dao;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Param;
import com.matrix.core.pojo.PaginationVO;
import com.zkingsoft.bean.BizGoods;
public interface BizGoodsDao {
public int insert(BizGoods bizGoods);
public int updateByMap(Map<String, Object> modifyMap);
public int deleteByIds(@Param("list") List<String> list);
public int deleteByModel(@Param("record") BizGoods bizGoods);
public List<BizGoods> selectInPage(@Param("record") BizGoods bizGoods, @Param("pageVo") PaginationVO pageVo);
public int selectTotalRecord(@Param("record") BizGoods bizGoods);
public BizGoods selectById(String gdId);
}
小贴士:
dao 类,需要放在满足 com.zkingsoft.**.dao 表达式的包下才能被 mybatis 扫描到,这个是在 spring-mvc-context.xml 中配置的。
| SQLMapping 编写
文件位置:/demo-biz/src/main/resources/mybatis/BizGoodsDao.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.zkingsoft.dao.BizGoodsDao">
<resultMap type="com.zkingsoft.bean.BizGoods" id="BizGoodsMap">
<id property="gdId" column="gd_id" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="gdName" column="gd_name" />
<result property="gdImg" column="gd_img" />
<result property="gdRemark" column="gd_remark" />
</resultMap>
<!-- 插入方法 -->
<insert id="insert" parameterType="com.zkingsoft.bean.BizGoods"
useGeneratedKeys="true" keyProperty="roleId">
INSERT INTO biz_goods (
create_by,
create_time,
update_by,
update_time,
gd_id,
gd_name,
gd_img,
gd_remark
)
VALUES (
#{createBy},
now(),
#{updateBy},
now(),
#{gdId},
#{gdName},
#{gdImg},
#{gdRemark}
)
</insert>
<!-- 根据Map更新 部分更新 -->
<update id="updateByMap" parameterType="java.util.HashMap" >
UPDATE biz_goods
<set>
update_time=now(),
<if test="_parameter.containsKey('updateBy')">
update_by=#{updateBy},
</if>
<if test="_parameter.containsKey('gdName')">
gd_name = #{gdName},
</if>
<if test="_parameter.containsKey('gdImg')">
gd_img = #{gdImg},
</if>
<if test="_parameter.containsKey('gdRemark')">
gd_remark = #{gdRemark},
</if>
</set>
WHERE gd_id=#{gdId}
</update>
<!-- 批量删除 -->
<delete id="deleteByIds" parameterType="java.util.List">
delete from biz_goods where gd_id in
<foreach collection="list" index="index" item="item" open="("
separator="," close=")">
#{item}
</foreach>
</delete>
<!-- 分页查询 -->
<select id="selectInPage" resultMap="BizGoodsMap">
select
create_by,
create_time,
update_by,
update_time,
gd_id,
gd_name,
gd_img,
gd_remark
from biz_goods
<where>
<if test="record!=null">
<if test="(record.gdName!=null and record.gdName!='')">
and gd_name = #{record.gdName}
</if>
</if>
</where>
<if test="pageVo !=null"><!-- 判断pageVo对象是否为空 -->
<if test="pageVo.sort !=null and pageVo.order !=null">order by${pageVo.sort} ${pageVo.order}</if><if test="pageVo.offset >=0 and pageVo.limit >0">limit#{pageVo.offset},#{pageVo.limit}</if></if>
</select><!-- 查询总条数 --><select id="selectTotalRecord" parameterType="long" resultType="java.lang.Integer">select count(*)from biz_goodswhere 1=1<if test="record!=null"><if test="(record.gdName!=null and record.gdName!='')">and gd_name = #{record.gdName} </if></if></select><!-- 根据id查询--><select id="selectById" resultMap="BizGoodsMap">select create_by,create_time,update_by,update_time,gd_id,gd_name,gd_img,gd_remark from biz_goodswhere gd_id=#{gdId} </select> </mapper>
小贴士:
1、sql mapping 文件,需要放在满足 classpath*:mybatis/*.xml 表达式的文件里才能被 mybatis 扫描到,这个是在 spring-mvc-context.xml 中配置的。classpath*表示类路径下(包括 jar 中的)的文件。
2、注意在编写 resultMap 节点的时候 <id>标签必须放在第一行,否则会解析错误。
| Service 接口与实现类编写
Service 接口位置:/demo-biz/src/main/java/com/zkingsoft/service/BizGoodsService.java
package com.zkingsoft.service;
import com.matrix.core.web.BaseServices;
import com.zkingsoft.bean.BizGoods;
public interface BizGoodsService extends BaseServices<BizGoods>{
}
小贴士:
service 继承了 BaseServices 类,BaseServices 提供了增删改查这些通用的接口,当然继承或者不继承 BaseServices 这不是强制性的,但是继承会带来很多便利,一般的需求我们都要求继承 BaseServices 当然特殊的 service 或者复杂的 services 可以视情况而定。
Service 实现类位置:/demo-biz/src/main/java/com/zkingsoft/service/impl/BizGoodsServiceImpl.java
package com.zkingsoft.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.matrix.core.pojo.PaginationVO;
import com.zkingsoft.bean.BizGoods;
import com.zkingsoft.dao.BizGoodsDao;
import com.zkingsoft.service.BizGoodsService;
@Service
public class BizGoodsServiceImpl implements BizGoodsService {
@Autowired
BizGoodsDao goodsDao;
@Override
public int add(BizGoods obje) {
return goodsDao.insert(obje);
}
@Override
public int remove(List<String> list) {
return goodsDao.deleteByIds(list);
}
@Override
public List<BizGoods> findInPage(BizGoods obje, PaginationVO pageVo) {
return goodsDao.selectInPage(obje, pageVo);
}
@Override
public int findTotal(BizGoods obje) {
return goodsDao.selectTotalRecord(obje);
}
@Override
public BizGoods findById(String id) {
return goodsDao.selectById(id);
}
@Override
public List<BizGoods> findByModel(BizGoods obje) {
return null;
}
@Override
public int removeById(String id) {
return 0;
}
@Override
public int modifyByMap(BizGoods oldValue, BizGoods newValue) {
return 0;
}
}
小贴士:
实现类里面我们使用@Service注解把接口实现类注入到spring容器中,以便于我们在action中使用。通过@ Autowired自动注入dao层的接口进行数据增删改查操作。关于注入bean的注解推荐优先使用@ Autowired ,这样可以避免硬编码bean的名称(虽然这可能造成一些性能上的问题,但是只是在spring启动的时候多花点时间,不影响后期程序的运行,长期来看这样做是很有好处的)
| action 类编写
package com.zkingsoft.action;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.matrix.core.pojo.AjaxResult;
import com.matrix.core.pojo.PaginationVO;
import com.matrix.core.web.BaseController;
import com.zkingsoft.bean.BizGoods;
import com.zkingsoft.service.BizGoodsService;
@Controller
@RequestMapping(value = "goods")
public class BizGoodsAction extends BaseController {
@Autowired
private BizGoodsService bizGoodsService;
/**
* 列表显示
*/
@RequestMapping(value = "/showList",method=RequestMethod.POST)
public @ResponseBody AjaxResult showList(BizGoods bizGoods, PaginationVO pageVo) {
return showList(bizGoodsService, bizGoods, pageVo);
}
/**
* 删除功能
*/
@GetMapping(value = "/del")
public @ResponseBody AjaxResult del(String fnId) {
return remove(bizGoodsService, fnId);
}
}
小贴士:
action类用@Controller注解注入容器。类上的注解@RequestMapping 标注这个类下所有接口的第一层 url。请注意 action 的命名规则 XXXAction.java
删除功能我们标注了一个@GetMapping 注解来表示路径,@GetMapping 和@RequestMapping 的作用是一样的,但是@GetMapping 指定了访问这个接口的请求只能为 Get请求。还有一个@PostMapping 表示接口只接收 post 请求,@PostMapping 的作用等于@RequestMapping(value = "/showList",method=RequestMethod.POST)。
@ResponseBody AjaxResult 表示返回值对象,AjaxResult 是 matrix 的标准数据返回格式如果没有特殊要求,强制使用 AjaxResult。
| 校验接口
现在我们可以启动一下项目,然后测试一下我们的接口。这里推荐大家使用postman(https://www.getpostman.com/)来调试我们的接口而不是直接使用浏览器。因为使用postman 可以方便的添加参数和保存请求等高级功能是专用于接口测试的工具能大大提高我们的调试效率。
如下图,我们创建了一个 get(http://localhost:8330/demo-web/do/goods/showList)请求然后点击 send按钮获取到服务器返回的数据,该数据就是我们在数据库插入的数据了,到这里我们的第一个接口就开发完了。