1. 前言
这几天老师带领我们做一个标准的Java web项目,提供了前端的源码,是一次很好的练习机会,老师带领做的是基于自己引入包的SSM框架,而自己是用springboot管理开发的。做的过程中也学到了很多新的知识,总结一下,希望以后温故而知新。
2. 前端
2.1 头文件
引入thymeleaf模板需要添加
<html lang="en" xmlns:th="http://www.thymeleaf.org">
因为是老师给的前端静态界面,IDEA中仍然无法提示出th:xxx。后发现原因是最上面的标签影响了
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!--上面这个即使加了thymeleaf也会无法提示,所以改成下面这个-->
<!DOCTYPE html>
还有一个就是相对/绝对路径的问题
开头为/的路径为绝对路径,引入css/js/…最好使用绝对路径,这样在跳转之后不会找不到上述文件
<link href="/css/main.css" rel="stylesheet" type="text/css" media="all" />
<script src="/js/jquery-1.4.2.min.js" type="text/javascript"></script>
文件的路径存放位置为:
2.2 js/ajax
自己只是懂一丢丢前端的知识,但开发中用到了几个js/ajax记录一下
2.2.1 submit()
由于提交按钮不在form表单中,所以需要一个简单的js来进行提交
<script type="text/javascript">
function submit(){
document.getElementById("institutionCreat").submit();
}
</script>
2.2.2 ajax
需求是这样的:进入界面会显示所有的机构列表,选中某一机构后,会显示旗下的所有科室列表(如同选中省后显示所有的市)
<script type="text/javascript">
var orgId = null;
$("select[id=selectO]").change(function(){
orgName = $(this).val();
$.ajax({
type:"POST",
async:true, //异步
dataType:"json",
url: "/part01/content/member-section.html", //url地址
data:{"orgName":orgName},
success : function (data) { //请求成功时的返回函数
// var list = JSON.stringify(data);
for (var i = 0; i < data.length; i++) {
var option = document.createElement("option");
$(option).val(data[i]);
$(option).text(data[i]);
$('#dss').append(option);
}
}
});
});
</script>
其中第一个select[id=selectO]的意思为找到对应的select,selectO是其id
第二个dss是第二个下拉列表的id
需要注意的是,这个ajax需要放在页面最下方,原因是:页面进行加载,从上到下,如果先加载到ajax,则找不到对应的select(这个还未加载)所以失效。
/**
* 根据机构名称得到旗下的科室列表
* @param orgName
* @param model
* @return
*/
@RequestMapping("/part01/content/member-section.html")
@ResponseBody
public List<String> StuffRelationSectionView(@RequestParam(required=true,name = "orgName") String orgName,
Model model) {
Integer orgId = organizationService.getIdByOrgName(orgName);
List<String> divNameList = sectionService.getSectionDivNameByOrgId(orgId);
List<String> allOrgName = organizationService.getAllOrgName();
model.addAttribute("allOrgName",allOrgName);
model.addAttribute("divNameList",divNameList);
return divNameList;
// return "/part01/content/member-relation.html";
}
用@ResponseBody标记为返回json,直接返回json数据
2.2 date格式
一般来说,采用th:text标记的date日期都是外国格式,对我们来说很奇怪
<td align="center" th:text="${orgInformation.standAloneDate}"></td>
<td align="center" th:text="${#calendars.format(orgInformation.standAloneDate,'yyyy-MM-dd HH:mm:ss')}"></td>
很明显的差距,修改方法也很简单,其实不止#calendars,还有其他的一些封装好的类,具体我也没用过。
2.3 th:if
需要注意的是,如果数据是字符串类型的,判断是否相等需要带引号。尤其注意有时需要用varchar(1)来保存一个魔法值,比如1->博士等等,此时看似是数字,其实是字符串,判断就可能有误。正确事例如下:
<td th:if="${leader.title=='1'}">初级(医(技)师)</td>
2.4 input多选
2.4.1 radio
对于多选项radio,只需将每个radio的name改成需要传的值即可
<td align="left" colspan="4" >
<input type="radio" name="exeType" id="radio5" value="2" />行政部门
<input type="radio" name="exeType" id="radio6" value="1" />卫生监督机构
<input type="radio" name="exeType" id="radio7" value="3" />协作单位
</td>
2.4.1 select
select 将select的name属性改为需要传的值即可
<select name="orgLevel" id="select">
<!--<option value="">请选择</option>-->
<option value="1">未定级</option>
<option value="2">副科级以下</option>
<option value="3">副科级</option>
<option value="4">科级</option>
<option value="5">副处级</option>
<option value="6">处级</option>
<option value="7">副厅局级</option>
<option value="8">厅局级</option>
</select>
2.5 数据回显
数据回显的意思是,当我们对一个表进行修改时,页面要提前先展示原值,方便修改。
后端逻辑就是传回id,查询出整个表数据,填充到前端界面
2.5.1 大多数
简单的一部分就是直接使用th:value填充
2.5.2 radio
<td align="left" colspan="4" >
<input type="radio" name="exeType" id="radio5" value="2" th:field="*{organization.exeType}"/>行政部门
<input type="radio" name="exeType" id="radio6" value="1" th:field="*{organization.exeType}"/>卫生监督机构
<input type="radio" name="exeType" id="radio7" value="3" th:field="*{organization.exeType}"/>协作单位
</td>
使用th:field
2.5.3 option
<select name="edu" id="select">
<option value="1" th:selected="${stuff.edu=='1'}">博士</option>
<option value="2" th:selected="${stuff.edu=='2'}">硕士</option>
<option value="3" th:selected="${stuff.edu=='3'}">本科</option>
<option value="4" th:selected="${stuff.edu=='4'}">大专</option>
<option value="5" th:selected="${stuff.edu=='5'}">中专</option>
<option value="6" th:selected="${stuff.edu=='6'}">高中</option>
<option value="7" th:selected="${stuff.edu=='7'}">初中</option>
<option value="8" th:selected="${stuff.edu=='8'}">无学历</option>
<option value="9" th:selected="${stuff.edu=='9'}">不详</option>
</select>
2.6 隐藏的input
需求如下:我们要对一个表进行修改,需要传递id,此时需要隐藏一个input,存储隐藏的信息
<input type="hidden" th:value="${organization.id}" name="id">
3. 后端
3.1 mapper层
我用的是注解版mybatis,sql直接写在了注解上。
3.1.1 脚本
mybatis有很多有用的脚本,在xml版中,通常用<>来写,在注解版中,写法如下
代码一
@Select("<script> " +
"SELECT orgName,equ3Count " +
"FROM t_d0_orgattached " +
"where 1=1 " +
"<if test='orgName!=null'> and orgName = #{orgName}</if>" +
"</script>")
List<Orgattached> getEquipment(Orgattached orgattached);
代码二
@Select("<script>" +
"SELECT orgName," +
"COUNT(CASE WHEN gender='1' THEN 1 END) manNumber, " +
"COUNT(CASE WHEN gender='2' THEN 1 END) womenNumber, " +
"COUNT(CASE WHEN edu='1' THEN 1 END) doctorNumber ," +
"COUNT(CASE WHEN edu='2' THEN 1 END) masterNumber ," +
"COUNT(CASE WHEN edu='6' THEN 1 WHEN edu='7' THEN 1 WHEN edu='8' THEN 1 WHEN edu='9' THEN 1 END) lowerMiddleNumber ," +
"COUNT(CASE WHEN ROUND(DATEDIFF(CURDATE(),birthdate)/365.2422)>=0 AND ROUND(DATEDIFF(CURDATE(),birthdate)/365.2422)<=25 THEN 1 END) numberLower25," +
"COUNT(CASE WHEN ROUND(DATEDIFF(CURDATE(),birthdate)/365.2422)>=26 AND ROUND(DATEDIFF(CURDATE(),birthdate)/365.2422)<=30 THEN 1 END) number26To30," +
"COUNT(CASE WHEN jobLevel='1' THEN 1 END) jobLevel1 ," +
"COUNT(CASE WHEN jobLevel='2' THEN 1 END) jobLevel2 ," +
"COUNT(CASE WHEN title='14' THEN 1 END) title1 ," +
"COUNT(CASE WHEN title='1' THEN 1 WHEN title='2' THEN 1 WHEN title='3' THEN 1 WHEN title='4' THEN 1 WHEN title='5' THEN 1 END) title4 ," +
"COUNT(CASE WHEN title='15' THEN 1 END) title5 " +
"FROM ( " +
"SELECT s.*,o.orgName " +
"FROM t_d0_stuff s LEFT JOIN t_d0_organization o " +
"ON s.orgId=o.id " +
") stuffPlus " +
"where 1=1 " +
"<if test='orgName!=null'> and orgName = #{orgName}</if>" +
"GROUP BY orgId" +
"</script>")
List<StuffComposition> getStuffComposition(StuffComposition stuffComposition);
代码一是简单的一个例子,代码二是一个复杂的例子(意思不是例子二是例子一的加强版)
在字符串中添加<script>来注明使用脚本。
同时,上述代码还解决了多条件查询的问题。
3.1.1.1 注意1
如果只传入一个值,如上述代码,必须由对象来传入,这点很奇怪,首先是一个值,如果是两个值就没有影响。原因暂时不知道,用对象传会正常执行。
3.1.1.2 注意2
对于代码一,我这里为了保证后续条件都可以格式正确,所以先写了一个 ,1==1,然后用if标签加and xxx,这样格式可以正确,同时官网也提供了一种<where>标签可以自动识别之类的,
详情参考:http://www.mybatis.org/mybatis-3/zh/dynamic-sql.html
3.1.1.3 注意3
由于对应xml语言,所以需要对<>进行处理
> 对应 >
< 对应 <
3.1.1.4 注意4
代码二提供了对表进行统计的sql语句,包括:统计男女个数,统计年龄(这里数据库只给了生日,需要函数运算)
3.1.2 返回自增ID
需求:增加一个机构,同时需要增加一个机构详情,机构详情里有机构的自增id,所以需要insert机构的时候返回自增的ID
@Insert("insert into " +
"t_d0_organization " +
"(orgNo, orgCode, orgName, exeType, areaCode, linkAdd, listingDate) " +
"values" +
"(#{orgNo},#{orgCode},#{orgName},#{exeType},#{areaCode},#{linkAdd},#{listingDate})")
@Options(useGeneratedKeys = true, keyProperty = "id")
Integer insertOneOrganization(Organization organization);
解决方案如上,添加@Options(useGeneratedKeys = true, keyProperty = “id”),其中此处的id即为数据库中机构的ID列名。
注意,返回的值并不在return中,而是自动绑定到了传入的对象中
3.2 service层
我的service层并没有太多的逻辑,都是直接返回mapper层的值,只有一个有逻辑。就是上面对应那个统计的函数
需求:统计一些数据后,比如,级别1 x人,级别2 y人…还需要统计共多少人。
此时可以在service层直接计算上述统计出来数据的和。
3.3 controller层
controller层没有使用什么特别的用法
使用 Model model
model.addAttribute(“allOrgName”,allOrgName);
可以将信息放入request域中
3.4 VO
显然,我们有时需要对数据打包,而PO对象可能不适合,此时我们可以添加一个VO对象来存数据。
例如上面提到的对数据进行统计,需要一个新的VO来存数据
3.5 配置文件
#关闭缓存便于调试
spring.thymeleaf.cache=false
#添加日志详情
spring.http.log-request-details=true
#对mapper层进行更高级别的日志,如果是debug则会展示进行了什么sql,查询了什么结果...
logging.level.cn.edu.hrbeu.group15.mapper=debug
4 分页
我这里采用的分页是pagehelper,是一个包,另外的解决方案是对sql进行修改手动limit
4.1 pom.xml中添加依赖
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
4.2 配置文件
# Pagehelper #分页插件
pagehelper.helper-dialect=mysql
pagehelper.reasonable=true
pagehelper.support-methods-arguments=true
pagehelper.params=count=countSql
4.3 后端controller
@RequestMapping("/part01/content/institution-list.html")
public String InstitutionList(@RequestParam(required=true,defaultValue="1") Integer page,
Model model) {
//开启分页,10就是条数
PageHelper.startPage(page,10);
//正常查询
List<Organization> all = organizationService.getAll();
//对结果进行包装
PageInfo<Organization> organizationPageInfo = new PageInfo<>(all);
//将正常结果与包装结果均放入request域中
model.addAttribute("organizationPageInfo",organizationPageInfo);
model.addAttribute("all",all);
return "part01/content/institution-list.html";
}
4.4 前端 html
显示数据是正常显示
<td align="center" th:text="${organization.orgCode}">0123456-01</td>
页面展示
<li><a th:href="@{'/part01/content/institution-list.html?page='+${organizationPageInfo.firstPage}}">首页</a></li>
<li><a th:href="@{'/part01/content/institution-list.html?page='+${organizationPageInfo.prePage}}">上页</a></li>
<li><a href="/part01/content/institution-list.html?page=1">1</a></li>
<li><a href="/part01/content/institution-list.html?page=2">2</a></li>
<li><a th:href="@{'/part01/content/institution-list.html?page='+${organizationPageInfo.nextPage}}">下页</a></li>
<li><a th:href="@{'/part01/content/institution-list.html?page='+${organizationPageInfo.lastPage}}">尾页</a></li>
其实与正常数据显示其实大同小异
那些数据属性都在PageInfo这个类中,点进去就可以看到
public class PageInfo<T> extends PageSerializable<T> {
private int pageNum;
private int pageSize;
private int size;
private int startRow;
private int endRow;
private int pages;
private int prePage;
private int nextPage;
private boolean isFirstPage;
private boolean isLastPage;
private boolean hasPreviousPage;
private boolean hasNextPage;
private int navigatePages;
private int[] navigatepageNums;
private int navigateFirstPage;
private int navigateLastPage;
就是上述属性。