在运行,还是报这个错:
java.sql.SQLException: Parameter index out of range (1 > number of parameters, which is 0).
看来还有问题,emmm,错误信息说,我输入了一个参数,但实际上只有0个。
知道了,应该是方法没调对。
这是部分update方法的重载,竟然没有第二个参数是Map的。
我大意了啊,没有闪!
原来,具名的写法得用另一个对象,叫做namedParameterJdbcTemplate
引入:
@Autowired NamedParameterJdbcTemplate namedParameterJdbcTemplate;
然后修改saveUser的方法,保存的地方是这样的
int update = namedParameterJdbcTemplate.update("INSERT INTO `vipmgr`.`user` (`user_name`, `create_time`, `header_pic`, `ip_addr`, `is_delete`, `is_logined`, `is_vip`, `last_login_time`, `nick_name`, `password`, `role_id`, `amt`, `last_sign_date`) " + "VALUES (:userName, :createTime, NULL, :ipAddr, '0', '0', '0', NULL, :nickName, :password, '1', 0, NULL)",params);
这下就对了,可是前台又报错了。
eval有点问题,为什么呢?
这是因为,我们用ajax请求,已经设置了dataType是json,就不需要eval了。
去掉这一行代码即可。
再保存,就可以了。
咦?中文乱码。。。
解决方案:数据库连接时加上?characterEncoding=utf-8,问题解决。
把alert代码换成这个
$.messager.show({ title : '提示', msg : "保存成功!", timeout : 3000, showType : 'slide' });
查询与分页
后台简单实现(不分页)
@RequestMapping("queryUsers") @ResponseBody public Map<String, Object> queryUsers(HttpServletRequest request){ //设置返回信息 Map<String, Object> resultMap = new HashMap(); resultMap.put("rows",jdbcTemplate.queryForList("select * from user")); return resultMap; }
返回参数需要有一个rows,这边就全部查出来再返回。
data-grid代码
EasyUI的数据表格是(datagrid)以表格格式显示数据,并为选择、排序、分组和编辑数据提供了丰富的支持。
数据网格(datagrid)的设计目的是为了减少开发时间,且不要求开发人员具备指定的知识。它是轻量级的,但是功能丰富。它的特性包括单元格合并,多列页眉,冻结列和页脚,等等。
下面是用户列表的代码:
<div style="padding:5px;background:#fafafa;width:100%;border:1px solid #ccc;height:100%;"> <table id="dg" title="用户列表" class="easyui-datagrid" fitColumns="true" pagination="true" rownumbers="true" url="user/queryUsers" > <thead> <tr> <th field="cb" checkbox="true" align="center"></th> <th field="user_name" width="50" align="center">用户名</th> <th field="nick_name" width="100" align="center">昵称</th> <th field="role_id" width="60" align="center">角色编号</th> <th field="create_time" width="200" align="center">创建时间</th> <th field="is_vip" width="200" align="center">是否VIP</th> <th field="last_login_time" width="200" align="center">上次登录时间</th> <th field="is_delete" width="200" align="center">用户状态</th> </tr> </thead> </table> </div>
启动项目,刷新页面就会访问后台的接口
easyUI本地化
这时候你会发现,为啥下面的分页模块是英文的?
这是因为我们还没有做本地化,方法如下
找到原来的easyUI下载包,里面有个local,复制到static
引入中文模块:
<script src="locale/easyui-lang-zh_CN.js"></script>
就变成这样啦:
后台分页原理
按照easyUI的套路,如果你要做分页,就得告诉他当前的列表数据,还有总条数。
至于当前查询的是第几页page,还有每页多少条,在发送查询接口的时候,easyui就会帮我们自动带上。
验证:
@RequestMapping("queryUsers") @ResponseBody public Map<String, Object> queryUsers(HttpServletRequest request){ //拼装请求参数 Map<String, Object> params = handleParamToMap(request); System.out.println(params); //设置返回信息 Map<String, Object> resultMap = new HashMap(); resultMap.put("rows",jdbcTemplate.queryForList("select * from user")); return resultMap; }
{page=1, rows=10}
所以,我们要做的就是根据page和rows,获取总条数和当前查询的数据。
总条数
总条数简单,一句sql解决了。
Integer total = jdbcTemplate.queryForObject("select count(1) from user", Integer.class);
MySQL分页
limit分页公式:curPage是当前第几页;pageSize是一页多少条记录。
代码就写成了这样:
@RequestMapping("queryUsers") @ResponseBody public Map<String, Object> queryUsers(HttpServletRequest request){ //拼装请求参数 Map<String, Object> params = handleParamToMap(request); int page = Integer.parseInt(params.get("page").toString()) ; int pageSize = Integer.parseInt(params.get("rows").toString()) ; //设置返回信息 Map<String, Object> resultMap = new HashMap(); //拿到总条数 Integer total = jdbcTemplate.queryForObject("select count(1) from user", Integer.class); List rows = jdbcTemplate.queryForList("select * from user limit ?,?",(page-1)*pageSize,pageSize); resultMap.put("total",total); resultMap.put("rows",rows); return resultMap; }
查询按钮控制列表刷新
之前的查询按钮方法为:
$('#search').click(function(){ $('#ff').form('submit', { url:'loadUsers', //提交前可以额外添加参数 onSubmit: function(param){ //这边只是模拟一下 param.search = true; } }); });
当时只是测试一下form的表单提交,实际上不应该这么做。
单独写一个查询方法:
function search(){ $('#dg').datagrid('load',{ userName:$('#ff').get(0).userName.value, nickName:$('#ff').get(0).nickName.value, isVip:$('#ff').get(0).isVip.value, }); }
然后查询按钮就调用这个方法即可。
1. $('#search').click(function(){ 2. search(); 3. });
请求报文如下:
条件查询(重难点)
欢迎来到本项目最难的点:条件查询,先给出代码:
@RequestMapping("queryUsers") @ResponseBody public Map<String, Object> queryUsers(HttpServletRequest request){ //拼装请求参数 Map<String, Object> params = handleParamToMap(request); int page = Integer.parseInt(params.get("page").toString()) ; int pageSize = Integer.parseInt(params.get("rows").toString()) ; //拼接where条件 StringBuffer sb = new StringBuffer(" where 1=1 "); List<Object> injectors = new ArrayList<>(); if(!StrUtil.isEmptyIfStr(params.get("userName"))){ sb.append("and user_name like ?"); injectors.add("%"+params.get("userName")+"%"); } if(!StrUtil.isEmptyIfStr(params.get("nickName"))){ sb.append("and nick_name like ?"); injectors.add("%"+params.get("nickName")+"%"); } if(!StrUtil.isEmptyIfStr(params.get("isVip"))){ sb.append("and is_vip = ?"); injectors.add(params.get("isVip").toString()); } //设置返回信息 Map<String, Object> resultMap = new HashMap(); //拿到总条数 Integer total = jdbcTemplate.queryForObject("select count(1) from user" + sb, injectors.toArray(), Integer.class); injectors.add((page-1)*pageSize); injectors.add(pageSize); List rows = jdbcTemplate.queryForList("select * from user" + sb +" limit ?,?", injectors.toArray()); resultMap.put("total",total); resultMap.put("rows",rows); return resultMap; }
思路是根据查询的参数是否为空,来拼接对应的where条件。这边用到了一个List,这是为了防止sql注入而采取的必要措施。
这个方法非常重要,请务必好好消化一下。
新增用户后立刻触发刷新
字典翻译
相信你也发现了,就是有些字段显示是数字,这一点其实是不科学的。比如,是否vip,显示一个0,用户怎么知道你这个0是什么意思?解决办法就是用字典翻译。
字典翻译有前台翻译,也有后台翻译,一般我们会单独维护一张字典表,然后做后台翻译的。相关的知识点我们在下一章做讲解。
字典(重要)
这是一款非常经典的教程,我直接挪用了企业里面真实项目的编程技巧。
VipMgrApplication是我们的启动类,加一个容器启动完成的listener
public class VipMgrApplication implements ApplicationListener<ApplicationReadyEvent> { }
实现onApplicationEvent方法
/** * 容器启动完成事件 * @param applicationReadyEvent */ @Override public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { ConfigurableApplicationContext applicationContext = applicationReadyEvent.getApplicationContext(); JdbcTemplate jdbcTemplate = (JdbcTemplate) applicationContext.getBean("jdbcTemplate"); //System.out.println(jdbcTemplate); //加载所有启动状态的字典数据 List<Map<String, Object>> dicts = jdbcTemplate.queryForList("select * from t_dict where is_on = 1"); dictMap = new HashMap<>(); //为了转换效率,给字典列表加索引 for (int i = 0; i < dicts.size(); i++) { dictMap.put(dicts.get(i).get("dict_code").toString() + dicts.get(i).get("dict_no").toString(),dicts.get(i)); } System.out.println("数据字典加载完毕!" + dictMap); }
dictMap是我设计的一个静态Map变量
public static Map<String, Map<String, Object>> dictMap = null;
这玩意会在SpringBoot容器启动完毕后就加载。
t_dict表
CREATE TABLE `t_dict` ( `id` int(11) NOT NULL AUTO_INCREMENT, `dict_code` varchar(20) DEFAULT NULL COMMENT '字典类型', `dict_no` varchar(5) DEFAULT NULL, `dict_value` varchar(30) DEFAULT NULL, `is_on` varchar(1) DEFAULT '1' COMMENT '是否启用', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
模拟数据
INSERT INTO `vipmgr`.`t_dict` (`id`, `dict_code`, `dict_no`, `dict_value`, `is_on`) VALUES ('1', 'isVip', '0', '普通会员', '1'); INSERT INTO `vipmgr`.`t_dict` (`id`, `dict_code`, `dict_no`, `dict_value`, `is_on`) VALUES ('2', 'isVip', '1', 'VIP会员', '1'); INSERT INTO `vipmgr`.`t_dict` (`id`, `dict_code`, `dict_no`, `dict_value`, `is_on`) VALUES ('3', 'isDelete', '0', '未删除', '1'); INSERT INTO `vipmgr`.`t_dict` (`id`, `dict_code`, `dict_no`, `dict_value`, `is_on`) VALUES ('4', 'isDelete', '1', '已删除', '1'); INSERT INTO `vipmgr`.`t_dict` (`id`, `dict_code`, `dict_no`, `dict_value`, `is_on`) VALUES ('5', 'role', '1', '管理员', '1'); INSERT INTO `vipmgr`.`t_dict` (`id`, `dict_code`, `dict_no`, `dict_value`, `is_on`) VALUES ('6', 'role', '2', '注册会员', '1'); INSERT INTO `vipmgr`.`t_dict` (`id`, `dict_code`, `dict_no`, `dict_value`, `is_on`) VALUES ('7', 'role', '3', 'VIP会员', '1');
启动项目看字典数据
启动项目,看下控制台的打印:
数据字典加载完毕!{isDelete0={id=3, dict_code=isDelete, dict_no=0, dict_value=未删除, is_on=1}, role1={id=5, dict_code=role, dict_no=1, dict_value=管理员, is_on=1}, isVip1={id=2, dict_code=isVip, dict_no=1, dict_value=VIP会员, is_on=1}, role2={id=6, dict_code=role, dict_no=2, dict_value=注册会员, is_on=1}, isVip0={id=1, dict_code=isVip, dict_no=0, dict_value=普通会员, is_on=1}, isDelete1={id=4, dict_code=isDelete, dict_no=1, dict_value=已删除, is_on=1}, role3={id=7, dict_code=role, dict_no=3, dict_value=VIP会员, is_on=1}}
这样做的好处就是,在容器启动后就加载所有的数据字典,放到Map里面。到时候别的地方如果有字典转换的需求,就非常快速。
用户数据查出来后,用lamda表达式进行数据翻译:
//数据字典转换 rows.forEach((map) -> { //是否VIP翻译 map.put("is_vip",VipMgrApplication.dictMap.get("isVip" + map.get("is_vip")).get("dict_value")); //角色名称翻译 map.put("roleName",VipMgrApplication.dictMap.get("role" + map.get("role_id")).get("dict_value")); //用户状态翻译 map.put("is_delete",VipMgrApplication.dictMap.get("isDelete" + map.get("is_delete")).get("dict_value")); });
角色编号
角色ID可能还会用到,所以保留,我们增加了roleName返回,于是乎:
<div style="padding:5px;background:#fafafa;width:100%;border:1px solid #ccc;height:100%;"> <table id="dg" title="用户列表" class="easyui-datagrid" fitColumns="true" pagination="true" rownumbers="true" url="user/queryUsers" > <thead> <tr> <th field="cb" checkbox="true" align="center"></th> <th field="user_name" width="150" align="center">用户名</th> <th field="nick_name" width="100" align="center">昵称</th> <th field="role_id" width="60" align="center" hidden>角色编号</th> <th field="roleName" width="60" align="center">角色</th> <th field="create_time" width="200" align="center">创建时间</th> <th field="is_vip" width="200" align="center">是否VIP</th> <th field="last_login_time" width="200" align="center">上次登录时间</th> <th field="is_delete" width="200" align="center">用户状态</th> </tr> </thead> </table> </div>
用户名扩展到150,然后隐藏角色编号。
easyUI给我们提供了很多套皮肤,挑一个自己喜欢的吧,毕竟默认的皮肤太丑了。
皮肤库文件在这
换一个css就行
<link rel="stylesheet" type="text/css" href="themes/material/easyui.css">
我换成了这种风格,你也可以换成自己喜欢的。
我们希望不同的字典值有一个默认的颜色,这样显得层次感强一点。
添加color字段
/** * 容器启动完成事件 * @param applicationReadyEvent */ @Override public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { ConfigurableApplicationContext applicationContext = applicationReadyEvent.getApplicationContext(); JdbcTemplate jdbcTemplate = (JdbcTemplate) applicationContext.getBean("jdbcTemplate"); //System.out.println(jdbcTemplate); //加载所有启动状态的字典数据 List<Map<String, Object>> dicts = jdbcTemplate.queryForList("select * from t_dict where is_on = 1"); dictMap = new HashMap<>(); //为了转换效率,给字典列表加索引 for (int i = 0; i < dicts.size(); i++) { //如果有颜色,则自动添加font标签 if(!StrUtil.isEmptyIfStr(dicts.get(i).get("color"))){ dicts.get(i).put("dict_value",String.format("<font color='%s'>%s</font>",dicts.get(i).get("color"),dicts.get(i).get("dict_value"))); } dictMap.put(dicts.get(i).get("dict_code").toString() + dicts.get(i).get("dict_no").toString(),dicts.get(i)); } System.out.println("数据字典加载完毕!" + dictMap); }
如果color不为空,就重新渲染dict_value
我故意修改了一些数据,得到这样的效果