实现客户列表分页展示的功能
实现客户列表页面展示
首先,我们要将客户列表页面给展示出来,效果如下图所示。

为了实现出这个功能,我们需要在src目录下新建一个com.meimeixia.crm.controller包,并在该包下创建一个处理客户信息请求的Controller,例如CustomerController.java。
package com.meimeixia.crm.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 客户信息请求处理的Controller
* @author liayun
*
*/
@Controller
@RequestMapping("customer")
public class CustomerController {
@RequestMapping("list")
public String list(Model model) {
return "customer";
}
}然后,打开Google Chrome浏览器,并在浏览器地址栏上输入http://localhost:8080/crm_ssm/customer/list这样一个url地址进行访问,你将会看到如下图所示的效果。

发现客户列表页面不能正常显示。为什么会搞成这样呢?因为我们在配置web.xml文件时,是设置所有的请求都进入SpringMVC,也就导致了SpringMVC无法处理css、js等静态资源,所以客户列表页面就无法正常显示了。那怎么让页面正常显示呢?一共有两种解决方案,我会分别介绍每一种解决方案。先来看第一种解决方案,即在springmvc.xml文件中添加如下配置解决静态资源无法被SpringMVC处理的问题。

第二种解决方案是修改web.xml文件,不要设置让所有的请求都进入SpringMVC,只让所有以.action结尾的请求进入SpringMVC。
<!-- 核心控制器的配置 -->
<servlet>
<servlet-name>boot-crm</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 加载SpringMVC的核心配置文件 -->
<init-param>
<!-- 指定SpringMVC核心配置文件的路径。如果不指定,默认为/WEB-INF/${servlet-name}-servlet.xml -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/springmvc.xml</param-value>
</init-param>
<!-- 配置SpringMVC什么时候启动,参数必须为整数 -->
<!-- 如果为0或者大于0,则SpringMVC随着Tomcat容器启动而启动(即在Tomcat服务器启动的时候就加载SpringMVC容器) -->
<!-- 如果小于0,则在第一次请求进来的时候启动(即第一次请求进来的时候加载SpringMVC容器) -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>boot-crm</servlet-name>
<!-- 所有的请求都进入SpringMVC -->
<!-- <url-pattern>/</url-pattern> -->
<url-pattern>*.action</url-pattern>
</servlet-mapping>在这里,我选择的是第二种解决方案,因为此项目中的页面的请求都是以.action结尾的。解决了静态资源无法被SpringMVC处理的问题之后,再次使用浏览器访问http://localhost:8080/crm_ssm/customer/list.action这样一个url地址,这时你便可以看到能正常显示页面样式了。

实现条件查询初始化
在条件查询客户列表时,可以选择客户来源、所属行业以及客户级别等信息来筛选,你不仅就要问了,客户来源、所属行业以及客户级别这三个下拉列表中的数据从何而来呢?

你想一想,问题是不是可以转换成客户列表页面加载时就需要初始化查询条件下拉列表啊?要做到这一点,最终就需要从数据字典表中查询数据并将其加载到以上三个下拉列表中了。首先,我们看一下sql语句怎么写?是不是可以在数据字典表(base_dict)中根据dict_type_code来查询客户来源、所属行业以及客户级别呢?就像下面这样。

sql语句既然都写出来了,接下来的事情就好办了,就是写代码了,一顿操作猛如虎,就完了!
实现dao层开发
编写pojo类
在src目录下新建一个com.meimeixia.crm.pojo包,然后在该包中根据数据字典表(base_dict)创建一个BaseDict类。
package com.meimeixia.crm.pojo;
/**
* 数据字典模型
* @author liayun
*
*/
public class BaseDict {
private String dict_id;
private String dict_type_code;
private String dict_type_name;
private String dict_item_name;
private String dict_item_code;
private Integer dict_sort;
private String dict_enable;
private String dict_memo;
public String getDict_id() {
return dict_id;
}
public void setDict_id(String dict_id) {
this.dict_id = dict_id;
}
public String getDict_type_code() {
return dict_type_code;
}
public void setDict_type_code(String dict_type_code) {
this.dict_type_code = dict_type_code;
}
public String getDict_type_name() {
return dict_type_name;
}
public void setDict_type_name(String dict_type_name) {
this.dict_type_name = dict_type_name;
}
public String getDict_item_name() {
return dict_item_name;
}
public void setDict_item_name(String dict_item_name) {
this.dict_item_name = dict_item_name;
}
public String getDict_item_code() {
return dict_item_code;
}
public void setDict_item_code(String dict_item_code) {
this.dict_item_code = dict_item_code;
}
public Integer getDict_sort() {
return dict_sort;
}
public void setDict_sort(Integer dict_sort) {
this.dict_sort = dict_sort;
}
public String getDict_enable() {
return dict_enable;
}
public void setDict_enable(String dict_enable) {
this.dict_enable = dict_enable;
}
public String getDict_memo() {
return dict_memo;
}
public void setDict_memo(String dict_memo) {
this.dict_memo = dict_memo;
}
}编写dao层接口与mapper.xml
首先,在src目录下新建一个com.meimeixia.crm.mapper包,并在该包中创建一个数据字典表持久化接口(例如BaseDictMapper.java),该接口中有一个根据字典编码(dict_type_code)查询字典列表的方法。
package com.meimeixia.crm.mapper;
import java.util.List;
import com.meimeixia.crm.pojo.BaseDict;
/**
* 数据字典表持久化接口
* @author liayun
*
*/
public interface BaseDictMapper {
/**
* 根据字典编码查询字典列表
* @param code
* @return
*/
List<BaseDict> getBaseDictByCode(String code);
}然后,在com.meimeixia.crm.mapper包下创建一个BaseDictMapper.xml文件,并在该文件中编写对应的sql查询语句。
<?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.meimeixia.crm.mapper.BaseDictMapper">
<select id="getBaseDictByCode" parameterType="string" resultType="basedict">
SELECT
`dict_id`,
`dict_type_code`,
`dict_type_name`,
`dict_item_name`,
`dict_item_code`,
`dict_sort`,
`dict_enable`,
`dict_memo`
FROM
`base_dict`
WHERE
`dict_type_code` = #{code}
</select>
</mapper>实现service层开发
首先,在src目录下新建一个com.meimeixia.crm.service包,并在该包中创建一个数据字典表业务逻辑接口(例如BaseDictService.java),该接口中同样也有一个根据字典编码(dict_type_code)查询字典列表的方法。
package com.meimeixia.crm.service;
import java.util.List;
import com.meimeixia.crm.pojo.BaseDict;
/**
* 数据字典表业务逻辑接口
* @author liayun
*
*/
public interface BaseDictService {
/**
* 根据字典编码查询字典列表
* @param code
* @return
*/
List<BaseDict> getBaseDictByCode(String code);
}然后,在src目录下新建一个com.meimeixia.crm.service.impl包,并在该包下创建以上接口的一个实现类,例如BaseDictServiceImpl.java。
package com.meimeixia.crm.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.meimeixia.crm.mapper.BaseDictMapper;
import com.meimeixia.crm.pojo.BaseDict;
import com.meimeixia.crm.service.BaseDictService;
@Service
public class BaseDictServiceImpl implements BaseDictService {
@Autowired
private BaseDictMapper baseDictMapper;
@Override
public List<BaseDict> getBaseDictByCode(String code) {
return baseDictMapper.getBaseDictByCode(code);
}
}实现表现层开发
修改之前编写的CustomerController类,修改成下面这个样子。
package com.meimeixia.crm.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.meimeixia.crm.pojo.BaseDict;
import com.meimeixia.crm.service.BaseDictService;
/**
* 客户信息请求处理的Controller
* @author liayun
*
*/
@Controller
@RequestMapping("customer")
public class CustomerController {
@Autowired
private BaseDictService dictService;
@RequestMapping("list")
public String list(Model model) {
//查询客户来源
List<BaseDict> fromType = dictService.getBaseDictByCode("002");//"002"
//查询客户行业
List<BaseDict> industryType = dictService.getBaseDictByCode("001");//"001"
//查询客户级别
List<BaseDict> levelType = dictService.getBaseDictByCode("006");//"006"
//设置数据模型并返回
model.addAttribute("fromType", fromType);
model.addAttribute("industryType", industryType);
model.addAttribute("levelType", levelType);
return "customer";
}
}至此,客户列表页面加载时就需要初始化查询条件下拉列表的这个需求就算是实现了,不妨看一下效果。打开Google Chrome浏览器,并在浏览器地址栏上输入http://localhost:8080/crm_ssm/customer/list.action这样一个url地址进行访问,此时你将会看到如下图所示的效果。

不知你有没有发现一个问题,那就是在CustomerController类的list()方法中,根据dict_type_code字典类别代码查询数据时,查询条件是写死的,有硬编码问题的嫌疑。

那么如何优化呢?可以把字典类别代码提取到配置文件中,再使用@value注解进行加载。于是,我们要在src目录下创建一个配置文件,例如crm.properties,在里面存储硬编码数据。
customer_from_type=002
customer_industry_type=001
customer_level_type=006然后,修改springmvc.xml配置文件,在它里面加载crm.properties配置文件。

温馨提示:Controller需要的配置文件信息必须添加到springmvc.xml配置文件当中。接着,在CustomerController类中使用@Value注解对成员变量进行注入,因此要将其修改成下面这个样子。

这样就解决了硬编码问题。
分页展示客户列表
实现的需求很简单,就是展示客户列表,并且可以根据查询条件过滤查询结果,并且要能实现分页。说起来简单,但是要实现分页展示客户列表这个功能,或许不如你想象中的那么简单,可以说它是该项目中最复杂的一个功能了。
在《基于SSM+MySQL+BootStrap实现CRM系统中的客户管理模块(一)——搭建开发环境》这一讲中,我们引入了一个自定义分页标签库、一个自定义标签类以及一个封装分页数据信息的包装类(也即Page类)。在Page类中有4个属性,它们分别是当前页、总记录数、每页的记录数以及List对象集合,我们可以根据这个Page类所需要的属性来一步步往下写,而且最后添加到model对象中的也是这个Page对象。
再来看一下要实现分页展示客户列表这个功能,sql查询语句该怎样写?从customer.jsp页面中的表单可知,我们需要根据四个条件来进行综合查询。

所以不难想象sql查询语句应该像下面这样写,还算是蛮复杂的,写出来并不是那么容易。

sql查询语句既然都写出来了,接下来的事情就好办了,就是写代码了,一顿操作猛如虎,就完了!
实现dao层开发
编写pojo类
在com.meimeixia.crm.pojo包下根据客户表(customer)创建一个客户实体类。
package com.meimeixia.crm.pojo;
import java.util.Date;
/**
* 客户信息数据模型
* @author liayun
*
*/
public class Customer {
private Long cust_id;
private String cust_name;
private Long cust_user_id;
private Long cust_create_id;
private String cust_source;
private String cust_industry;
private String cust_level;
private String cust_linkman;
private String cust_phone;
private String cust_mobile;
private String cust_zipcode;
private String cust_address;
private Date cust_createtime;
public Long getCust_id() {
return cust_id;
}
public void setCust_id(Long cust_id) {
this.cust_id = cust_id;
}
public String getCust_name() {
return cust_name;
}
public void setCust_name(String cust_name) {
this.cust_name = cust_name;
}
public Long getCust_user_id() {
return cust_user_id;
}
public void setCust_user_id(Long cust_user_id) {
this.cust_user_id = cust_user_id;
}
public Long getCust_create_id() {
return cust_create_id;
}
public void setCust_create_id(Long cust_create_id) {
this.cust_create_id = cust_create_id;
}
public String getCust_source() {
return cust_source;
}
public void setCust_source(String cust_source) {
this.cust_source = cust_source;
}
public String getCust_industry() {
return cust_industry;
}
public void setCust_industry(String cust_industry) {
this.cust_industry = cust_industry;
}
public String getCust_level() {
return cust_level;
}
public void setCust_level(String cust_level) {
this.cust_level = cust_level;
}
public String getCust_linkman() {
return cust_linkman;
}
public void setCust_linkman(String cust_linkman) {
this.cust_linkman = cust_linkman;
}
public String getCust_phone() {
return cust_phone;
}
public void setCust_phone(String cust_phone) {
this.cust_phone = cust_phone;
}
public String getCust_mobile() {
return cust_mobile;
}
public void setCust_mobile(String cust_mobile) {
this.cust_mobile = cust_mobile;
}
public String getCust_zipcode() {
return cust_zipcode;
}
public void setCust_zipcode(String cust_zipcode) {
this.cust_zipcode = cust_zipcode;
}
public String getCust_address() {
return cust_address;
}
public void setCust_address(String cust_address) {
this.cust_address = cust_address;
}
public Date getCust_createtime() {
return cust_createtime;
}
public void setCust_createtime(Date cust_createtime) {
this.cust_createtime = cust_createtime;
}
}前台发起请求,在后台需要接收请求过来的查询条件数据,从customer.jsp页面中的表单可知,前台是根据四个条件来进行综合查询的,所以我们还要创建一个pojo类来接收这四个查询条件数据。
于是,我们还应在com.meimeixia.crm.pojo包中创建一个如下的pojo类,在其里面不仅要包含查询条件数据,还要包含与分页相关的数据。
package com.meimeixia.crm.pojo;
public class QueryVo {
private String custName;
private String custSource;
private String custIndustry;
private String custLevel;
// 当前页码数
private Integer page = 1;
// 数据库从哪一条数据开始查
private Integer start;
// 每页显示数据条数
private Integer rows = 10;
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public String getCustIndustry() {
return custIndustry;
}
public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public Integer getPage() {
return page;
}
public void setPage(Integer page) {
this.page = page;
}
public Integer getStart() {
return start;
}
public void setStart(Integer start) {
this.start = start;
}
public Integer getRows() {
return rows;
}
public void setRows(Integer rows) {
this.rows = rows;
}
}编写dao层接口与mapper.xml
首先,在com.meimeixia.crm.mapper包中创建一个客户信息持久化接口(例如CustomerMapper.java),该接口中应该要声明两个方法,一个是根据查询条件分页查询客户列表,一个是根据查询条件查询总记录数。
package com.meimeixia.crm.mapper;
import java.util.List;
import com.meimeixia.crm.pojo.Customer;
import com.meimeixia.crm.pojo.QueryVo;
/**
* 客户信息持久化接口
* @author liayun
*
*/
public interface CustomerMapper {
/**
* 根据查询条件分页查询客户列表
* @param vo
* @return
*/
List<Customer> getCustomerByQueryVo(QueryVo vo);
/**
* 根据查询条件查询总记录数
* @param vo
* @return
*/
Integer getCountByQueryVo(QueryVo vo);
}然后,在com.meimeixia.crm.mapper包下创建一个CustomerMapper.xml文件,并在该文件中编写对应的sql查询语句。
<?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.meimeixia.crm.mapper.CustomerMapper">
<sql id="customer_where">
<where>
<if test="custName != null and custName != ''">
AND c.`cust_name` LIKE '%${custName}%'
</if>
<if test="custSource != null and custSource != ''">
AND c.`cust_source` = #{custSource}
</if>
<if test="custIndustry != null and custIndustry != ''">
AND c.`cust_industry` = #{custIndustry}
</if>
<if test="custLevel != null and custLevel != ''">
AND c.`cust_level` = #{custLevel}
</if>
</where>
</sql>
<select id="getCustomerByQueryVo" parameterType="queryvo" resultType="customer">
SELECT
c.`cust_id`,
c.`cust_name`,
c.`cust_user_id`,
c.`cust_create_id`,
s.`dict_item_name` `cust_source`,
i.`dict_item_name` `cust_industry`,
l.`dict_item_name` `cust_level`,
c.`cust_linkman`,
c.`cust_phone`,
c.`cust_mobile`,
c.`cust_zipcode`,
c.`cust_address`,
c.`cust_createtime`
FROM `customer` c
LEFT JOIN `base_dict` s ON c.`cust_source` = s.`dict_id`
LEFT JOIN `base_dict` i ON c.`cust_industry` = i.`dict_id`
LEFT JOIN `base_dict` l ON c.`cust_level` = l.`dict_id`
<include refid="customer_where"></include>
LIMIT #{start}, #{rows}
</select>
<select id="getCountByQueryVo" parameterType="queryvo" resultType="int">
SELECT count(1)
FROM `customer` c
LEFT JOIN `base_dict` s ON c.`cust_source` = s.`dict_id`
LEFT JOIN `base_dict` i ON c.`cust_industry` = i.`dict_id`
LEFT JOIN `base_dict` l ON c.`cust_level` = l.`dict_id`
<include refid="customer_where"></include>
</select>
</mapper>注意:因为前台传递进来的查询条件可能有也可能没有,所以要进行判断!
实现service层开发
首先,在com.meimeixia.crm.service包中创建一个客户信息业务逻辑接口(例如CustomerService.java),该接口中有一个根据查询条件分页查询客户列表的方法。
package com.meimeixia.crm.service;
import com.meimeixia.crm.pojo.Customer;
import com.meimeixia.crm.pojo.QueryVo;
import com.meimeixia.crm.utils.Page;
/**
* 客户信息业务逻辑接口
* @author liayun
*
*/
public interface CustomerService {
/**
* 根据查询条件分页查询客户列表
* @param vo
* @return 包装的分页数据
*/
Page<Customer> getCustomerByQueryVo(QueryVo vo);
}然后,在com.meimeixia.crm.service.impl包下创建以上接口的一个实现类,例如CustomerServiceImpl.java。
package com.meimeixia.crm.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.meimeixia.crm.mapper.CustomerMapper;
import com.meimeixia.crm.pojo.Customer;
import com.meimeixia.crm.pojo.QueryVo;
import com.meimeixia.crm.service.CustomerService;
import com.meimeixia.crm.utils.Page;
@Service
public class CustomerServiceImpl implements CustomerService {
@Autowired
private CustomerMapper customerMapper;
@Override
public Page<Customer> getCustomerByQueryVo(QueryVo vo) {
//计算分页查询从哪条记录开始
vo.setStart((vo.getPage() - 1) * vo.getRows());
//查询总记录数
Integer total = customerMapper.getCountByQueryVo(vo);
//查询每页的记录(数据)列表
List<Customer> list = customerMapper.getCustomerByQueryVo(vo);
//包装一个分页数据模型
Page<Customer> page = new Page<Customer>(total, vo.getPage(), vo.getRows(), list);
return page;
}
}实现表现层开发
再次修改CustomerController类,即接收前台提交过来的请求参数,然后调用业务层提供的服务将查询出来的Page对象保存到域中。
package com.meimeixia.crm.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.meimeixia.crm.pojo.BaseDict;
import com.meimeixia.crm.pojo.Customer;
import com.meimeixia.crm.pojo.QueryVo;
import com.meimeixia.crm.service.BaseDictService;
import com.meimeixia.crm.service.CustomerService;
import com.meimeixia.crm.utils.Page;
/**
* 客户信息请求处理的Controller
* @author liayun
*
*/
@Controller
@RequestMapping("customer")
public class CustomerController {
@Autowired
private BaseDictService dictService;
@Autowired
private CustomerService customerService;
@Value("${customer_from_type}")
private String customer_from_type;
@Value("${customer_industry_type}")
private String customer_industry_type;
@Value("${customer_level_type}")
private String customer_level_type;
@RequestMapping("list")
public String list(Model model, QueryVo vo) {
//查询客户来源
List<BaseDict> fromType = dictService.getBaseDictByCode(customer_from_type);//"002"
//查询客户行业
List<BaseDict> industryType = dictService.getBaseDictByCode(customer_industry_type);//"001"
//查询客户级别
List<BaseDict> levelType = dictService.getBaseDictByCode(customer_level_type);//"006"
//设置数据模型并返回
model.addAttribute("fromType", fromType);
model.addAttribute("industryType", industryType);
model.addAttribute("levelType", levelType);
//根据查询条件分页查询用户列表
Page<Customer> page = customerService.getCustomerByQueryVo(vo);
//设置分页数据返回
model.addAttribute("page", page);
//返回查询条件
model.addAttribute("vo", vo);
return "customer";
}
}有一点需要大家注意,当用户在前台根据查询条件来筛选客户信息时,筛选完之后,查询条件一定要回显。如何回显查询条件呢?很简单,在CustomerController类的list()方法中添加如下一句代码。

然后,修改一下customer.jsp页面中的表单,让其能够回显出查询条件,如下图所示。

最后,出来的效果就是下面这样子的。

















