前言
最近这半年,学习了SSM框架,做了大大小小几个模拟项目和两个商业项目。从什么都不会,到可以做一个具有主要功能的管理系统,再到接触商业项目,感觉自己对项目流程和业务逻辑的理解以及对SSM框架的使用有了显著的进步,以下是这段时间做项目时的收获、遇到的问题、解决方案和技术点的总结。
模拟项目
在初学如何做项目的时候,首先自己先做的是一些模拟项目,例如学生管理系统、酒店管理系统、毕设管理系统等。
初学SSM框架
entity层(model层、domain层)
用来放我们的实体类,每个实体类的类名以及属性要和数据库中表名和字段要一一对应。例如user表的实体类就是User,User实体类的属性和user表的字段一一对应。
public class User {
private Integer id;
private Integer username;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getUsername() {
return username;
}
public void setUsername(Integer username) {
this.username = username;
}
}
mapper层(dao层)
文件名格式为xxxMapper,每个接口中的方法要和mybatis中xxx.xml中定义的sql一一对应
service层
文件名格式为xxxService,其实现层格式为xxxServiceImpl,Service类封装要在controller层中调用的方法,ServiceImpl类实现这些方法
controller层
文件名格式为xxxController,通过调用Service层中的接口控制业务流程,需要通过使用前台传回来的参数来调用相应的接口,并将该调用接口获得的结果按照一定的形式传回前台,完成业务逻辑控制。
mybatis映射文件
文件名格式为xxxMapper.xml或xxxMapping.xml。 定义resultMap,type为实体类对象,其中的result每个代表该实体类对象的一个属性,即数据库中表的一个字段。
<resultMap id="BaseResultMap" type="cn.zcbigdata.mybits_demo.entity.User">
<result column="id" jdbcType="INTEGER" property="id"/>
<result column="user_name" jdbcType="VARCHAR" property="userName"/>
<result column="pass_word" jdbcType="VARCHAR" property="passWord"/>
</resultMap>
定义sql语句,包括增删改查等,parameterType为传入的参数类型,resultMap或resultType为返回的结果类型,resultMap返回的是java对象,resultType返回的是指定的类型,如java.lang.Long、java.lang.String等
<select id="studentLogin" resultMap="BaseResultMap" parameterType="cn.zcbigdata.mybits_demo.entity.Student">
SELECT *
FROM student
WHERE userName = #{userName}
AND password = md5(#{password});
</select>
Layui框架
由于在做模拟项目的时候没有学习过vue,只学习过html,所以前端页面使用的是layui框架。  通过ajax,调用后台的接口。
table.render({
elem: '#test'
, url: '/student/selectStudentByTeacherid'
, toolbar: '#toolbarDemo' //开启头部工具栏,并为其绑定左侧模板
, defaultToolbar: ['filter', 'exports', 'print', { //自定义头部工具栏右侧图标。如无需自定义,去除该参数即可
title: '提示'
, layEvent: 'LAYTABLE_TIPS'
, icon: 'layui-icon-tips'
}]
, title: '用户数据表'
, cols: [
[
{type: 'checkbox', fixed: 'left'}
, {field: 'id', title: 'ID', width: 80, fixed: 'left', unresize: true, sort: true}
, {field: 'userName', title: '用户名'}
, {field: 'password', title: '密码'}
, {field: 'teacherid', title: '教师id', hide: true}
, {field: 'nickName', title: '昵称'}
, {fixed: 'right', title: '操作', toolbar: '#barDemo'}
]
]
, page: true
});
项目地址
毕设管理系统 汽车管理系统
总结
通过大大小小的几个模拟项目,了解到了整个项目的开发流程以及一些基础功能是如何实现的,为我之后做业务逻辑更复杂、指标更严格的商业项目打下了一定的基础。
XX应急平台和XX药学院
这两个项目是我接触到的商业项目,在XX应急平台这个项目中主要负责后端接口的开发,在XX药学院这个项目中负责前端页面和后端接口同步开发。
技术点
Lambok
在实体类上加上@Data和@Builder注解。 @Data:可以简化实体类,加上这个注解就相当于实体类加上了getter/setter方法。 @Builder:便于初始化对象,将类转变为建造者模式。
@Builder
@Data
public class VedioFile {
private Long id;
private Long vedioId;
private Long fileId;
private String vedioFile;
private int sort;
private LocalDateTime createTime;
}
Mybatis-Plus
代码生成
快速生成entity层、mapper层、service层、controller层和mapper映射文件。 在生成的mapper层和service层的接口中,继承mybatis-plus自带的接口。 mapper层接口继承了BaseMapper接口。 service层接口继承IService接口,IService接口封装了BaseMapper接口。 在BaseMapper接口中有许多CRUD相关的方法,可以直接调用。
【添加数据:(增)】
int insert(T entity); // 插入一条记录
注:
T 表示任意实体类型
entity 表示实体对象
【删除数据:(删)】
int deleteById(Serializable id); // 根据主键 ID 删除
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); // 根据 map 定义字段的条件删除
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper); // 根据实体类定义的 条件删除对象
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); // 进行批量删除
注:
id 表示 主键 ID
columnMap 表示表字段的 map 对象
wrapper 表示实体对象封装操作类,可以为 null。
idList 表示 主键 ID 集合(列表、数组),不能为 null 或 empty
【修改数据:(改)】
int updateById(@Param(Constants.ENTITY) T entity); // 根据 ID 修改实体对象。
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper); // 根据 updateWrapper 条件修改实体对象
注:
update 中的 entity 为 set 条件,可以为 null。
updateWrapper 表示实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
【查询数据:(查)】
T selectById(Serializable id); // 根据 主键 ID 查询数据
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); // 进行批量查询
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); // 根据表字段条件查询
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根据实体类封装对象 查询一条记录
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询记录的总条数
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询所有记录(返回 entity 集合)
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询所有记录(返回 map 集合)
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询所有记录(但只保存第一个字段的值)
<E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询所有记录(返回 entity 集合),分页
<E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询所有记录(返回 map 集合),分页
注:
queryWrapper 表示实体对象封装操作类(可以为 null)
page 表示分页查询条件
实体类注释
@TableName:用来指定数据库表名和JavaBean映射关系。 @TableId:指定该属性为数据库表中的主键。 @TableField:指定该属性为数据库表中的非主键。
@Builder
@Data
@TableName("ref_vedio_file")
public class VedioFile {
@TableId(type = IdType.AUTO)
private Long id;
IPage
private Long vedioId;
private Long fileId;
private String vedioFile;
private int sort;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}
条件构造器
可以使用queryWrapper、updateWrapper 等构造器来构造条件,返回相应的数据,用于查询相关的接口。
@Log(value = "查询列表", businessType = BusinessType.LIST)
@PreAuthorize("hasAuthority('/vedio')")
@ApiImplicitParams({@ApiImplicitParam(name = "Authorization", value = "Authorization token", required = true, dataType = "string", paramType = "header")})
@PostMapping("/list/{currentPage}/{pageSize}")
public ResultJson index(@ApiParam(name="currentPage",value="页数",required=true) @PathVariable Integer currentPage, @ApiParam(name="pageSize",value="每页数量",required=true) @PathVariable Integer pageSize, @RequestBody Vedio vedio) {
QueryWrapper<Vedio> queryWrapper = new QueryWrapper<>();
if(StringUtils.isNotBlank(vedio.getVedioIntro())) {
queryWrapper.like("vedio_intro", vedio.getVedioIntro());
}
if(StringUtils.isNotBlank(vedio.getVedioCover())) {
queryWrapper.like("vedio_cover", vedio.getVedioCover());
}
if(StringUtils.isNotBlank(vedio.getOperator())) {
queryWrapper.like("operator", vedio.getOperator());
}
if(vedio.getType()!=null){
queryWrapper.eq("type",vedio.getType());
}
if(vedio.getClassificationId()!=null){
queryWrapper.eq("type",vedio.getClassificationId());
}
IPage<Vedio> pageList = vedioService.page(new Page<>(currentPage, pageSize), queryWrapper);
return ResultJson.ok(pageList);
}
分页
使用IPage和Page来进行分页,mapper层和service层的方法加上Page参数,返回参数类型为IPage,泛型为对应的实体类。
IPage<Vedio> selectVedioList(Page<Vedio> page, Vedio vedio);
@Override
public IPage<Vedio> selectVedioList(Page<Vedio> page, Vedio vedio) {
return this.baseMapper.selectVedioList(page, vedio);
}
controller层中的方法构造Page和IPage对象,IPage对象作为返回值返回,即可实现分页。
@ApiOperation(value = "视频列表-查询视频列表,可通过分类id查询")
@PostMapping("/public/selectVedioList")
public ResultJson selectVedioList(@RequestBody Vedio vedio,
@ApiParam(name="page",value="页码",required = true)@RequestParam("page") String page,
@ApiParam(name="limit",value="每页数据量",required = true)@RequestParam("limit") String limit) {
Page<Vedio> userPage = new Page<>(Integer.valueOf(page), Integer.valueOf(limit));
IPage<Vedio> vedioList = this.vedioService.selectVedioList(userPage, vedio);
return ResultJson.ok(vedioList);
}
Swagger
通过使用Swagger,可以方便地测试接口。  
使用@ApiModelProperty标记Swagger要解析的实体类的属性。
@Builder
@Data
@TableName("ref_vedio_file")
public class VedioFile {
@ApiModelProperty(value = "序号")
@TableId(type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "视频id")
private Long vedioId;
@ApiModelProperty(value = "文件Id")
private Long fileId;
@ApiModelProperty(value = "文件名称")
private String vedioFile;
@ApiModelProperty(value = "排序字段")
private int sort;
@ApiModelProperty(value = "创建时间")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}
使用@Api注解标记在controller层中每个类上,用来表示对这个类的说明
@RestController
@Api(value = "视频相关业务控制", tags = {"前端接口-前端视频接口"})
@RequestMapping("vedioFront")
public class VedioFrontController {
}
使用@ApiOperation、@ApiImplicitParams和@ApiParam注解标记controller层中每个方法。 @ApiOperation:说明该方法的作用 @ApiImplicitParams:对该方法的参数的说明 @ApiParam:对该方法参数的说明
@ApiOperation(value = "视频详情-通过id查询视频的详情信息")
@ApiImplicitParams({@ApiImplicitParam(name = "Authorization", value = "Authorization token", required = true, dataType = "string", paramType = "header")})
@PostMapping("/private/selectVedioById/{id}")
public ResultJson selectVedioById(@ApiParam(name="id",value="视频id",required = true)@PathVariable Long id) {
Vedio vedio = vedioService.selectVedioById(id);
return ResultJson.ok(vedio);
}
association和collection
association:常用于一对一或多对一的连表查询中 collection:常用于一对多的连表查询中 property参数为返回的数据,column为传入的参数,select为调用的接口 每次调用返回这个resultMap的接口,都会将column作为参数,再调用select接口,返回property。
<resultMap id="BaseResultMapFrontVedio" type="tech.niua.admin.vedio.domain.Vedio">
<id column="id" jdbcType="BIGINT" property="id" />
<id column="vedio_intro" jdbcType="VARCHAR" property="vedioIntro" />
<id column="classification_id" jdbcType="BIGINT" property="classificationId" />
<id column="operator_id" jdbcType="BIGINT" property="operatorId" />
<id column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
<id column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<association property="description" column="classification_id" select="tech.niua.admin.vedioclassification.mapper.DicVedioClassificationMapper.selectDescriptionByDescriptionId">
</association>
<collection property="vedioFileList" column="id" select="tech.niua.admin.vedio.mapper.VedioMapper.selectVedioFileListById">
</collection>
</resultMap>
Slf4j日志框架
在类上加上@Slf4j注解,使用log.info即可输出日志
@Slf4j
class LogTest {
@Test
void testLog() {
log.info("info!");
log.error("error!");
log.debug("debug!");
log.warn("warn!");
log.trace("trace!");
}
}
遇到的问题及解决方法
Vue
在XX药学院这个项目中,前端页面的实现是通过VUE框架实现的,在此之前我还没有学习过Vue,为了完成自己负责模块,只能硬着头皮按照这个项目之前做完的页面的代码通过修改部分地方来实现自己负责的模块,在空闲时间观看Vue教学视频,学习了一段时间之后,终于学会了使用Vue完成页面设计。
角色权限控制问题
在XX药学院这个项目中,有着很多角色,包括初级学生,高级学生,专科学生,带教教师,教学负责人,主任,学习委员等。在一些业务中,要控制在不同角色账号下有不同的权限。 方法一:使用自带的权限控制功能。 方法二:在后端写一个查询角色的接口,使用VUE中disabled属性或v-if判断来控制该功能对于当前角色的使用权。
总结
XX应急平台项目是我接触到的真正意义上的前后端分离项目,接口也分为了前后台对应的接口。在刚上手的时候看到很多自己没有见过的注释也未免有些头大,但是在一个一个搜索相关用法之后也解决掉了很多难题,测试接口用的swagger也很方便,mybatis-plus也让开发更加效率。
XX药学院项目不仅表数量也很多,表名也一时间难以记住,这个项目对我来说是个很大的考验,花了一周时间才记住各个表的表名已经对应的含义。对于Vue小白的我来说,使用Vue来设计前台页面也是个比较困难的任务,但是这也恰恰使我在不经意间又掌握了Vue的使用。
想法
虽然接口不过是一个个增删改查语句,但最难的还是对业务逻辑的理解。
|