该系列博客主要记录笔者的开发过程,参考B站系列视频:【SpringBoot项目实战完整版】SpringBoot+MyBatis+MySQL电脑商城项目实战_哔哩哔哩_bilibili
所用的一些版本信息:
IDEA开发、JDK1.8版本以上、maven3.61版本以上,springboot,DataGrip管理数据库
省市区列表页面如下:
0. 前期准备
1.0 构建省市区的表
构建的语句可以在github上找到:
store_java_springboot/t_dict_district.sql at main · LYJAntelope/store_java_springboot · GitHub
DROP TABLE IF EXISTS t_dict_district;
CREATE TABLE t_dict_district (
id int(11) NOT NULL AUTO_INCREMENT,
parent varchar(6) DEFAULT NULL, # 表示父区域代码号 省的父代码号为+86
code varchar(6) DEFAULT NULL,
name varchar(16) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.0 构架实体类
新建:store\src\main\java\com\cy\store\entity\District.java
package com.cy.store.entity;
import java.io.Serializable;
/** 省/市/区数据的实体类 */
public class District implements Serializable {
private Integer id;
private String parent;
private String code;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getParent() {
return parent;
}
public void setParent(String parent) {
this.parent = parent;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof District)) return false;
District district = (District) o;
if (getId() != null ? !getId().equals(district.getId()) : district.getId() != null) return false;
if (getParent() != null ? !getParent().equals(district.getParent()) : district.getParent() != null)
return false;
if (getCode() != null ? !getCode().equals(district.getCode()) : district.getCode() != null) return false;
return getName() != null ? getName().equals(district.getName()) : district.getName() == null;
}
@Override
public int hashCode() {
int result = getId() != null ? getId().hashCode() : 0;
result = 31 * result + (getParent() != null ? getParent().hashCode() : 0);
result = 31 * result + (getCode() != null ? getCode().hashCode() : 0);
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
return result;
}
@Override
public String toString() {
return "District{" +
"id=" + id +
", parent='" + parent + '\'' +
", code='" + code + '\'' +
", name='" + name + '\'' +
'}';
}
}
1 持久层
1.1 规划SQL语句
1.根据父区域去查询列表值,省的代号是+86
1.2 抽象方法
新建:store\src\main\java\com\cy\store\mapper\UserMapper.java
package com.cy.store.mapper;
import com.cy.store.entity.District;
import java.util.List;
/** 省市区列表接口 */
public interface DistrictMapper {
/**
* 根据父代号进行查询
* @param parent
* @return
*/
List<District> findByParent(Integer parent);
}
1.3 SQL映射
新建:store\src\main\resources\mapper\DistrictMapper.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">
<!--namespace需要映射到java中的接口,指定路径-->
<mapper namespace="com.cy.store.mapper.DistrictMapper">
<select id="countByUid" resultType="com.cy.store.entity.District">
SELECT * FROM t_dict_district WHERE parent = #{parent} ORDER BY code
</select>
</mapper>
1.4 单元测试
新建:store\src\test\java\com\cy\store\mapper\DistrictMapperTests.java
package com.cy.store.mapper;
import com.cy.store.entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.util.Date;
//@SpringBootTest:该注解表示标注当前类是个测试类,测试类不会随着项目一块打包发送
@SpringBootTest
//@RunWith:表示启动这个单元测试类(单元测试类时不能够运行的),需传递一个参数,必须是SpringRunner的实例类型
@RunWith(SpringRunner.class)
public class DistrictMapperTests {
//接口不能创建bean对象,但是mybatis创建了动态代理实现类帮我们进行解决
@Resource
private DistrictMapper districtMapper;
/**
* 单元测试方法(可以独立运行,不启动整个项目):
* 1.必须被@Test修饰
* 2.返回值类型必须是void
* 3.方法的参数列表不能指定任何类型
* 4.方法的访问修饰符必须是public
*/
@Test
public void findByParent(){
System.out.println(districtMapper.findByParent(110100));
}
}
2 业务层
2.1 规划异常
无异常
2.2 接口与抽象方法
新建:tore\src\main\java\com\cy\store\service\IDistrictService.java
package com.cy.store.service;
import com.cy.store.entity.District;
import java.util.List;
/** 地址下拉列表接口 */
public interface IDistrictService {
/**
* 根据父代号查询区域信息
* @param parent
* @return
*/
List<District> getByParent(String parent);
}
2.3 实现抽象方法
新建:store\src\main\java\com\cy\store\service\impl\DistrictServiceImpl.java
package com.cy.store.service.impl;
import com.cy.store.entity.District;
import com.cy.store.mapper.DistrictMapper;
import com.cy.store.service.IDistrictService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class DistrictServiceImpl implements IDistrictService {
@Resource
private DistrictMapper districtMapper;
@Override
public List<District> getByParent(String parent) {
List<District> list = districtMapper.findByParent(parent);
//在网络传输时为了避免无效的传递,可以将无效数据设置为null,目的:节省流量,提升效率
for(District d:list){
d.setParent(null);
d.setId(null);
}
return list;
}
}
2.4 单元测试
新建:store\src\test\java\com\cy\store\service\DistrictServiceImplTests.java
package com.cy.store.mapper;
import com.cy.store.entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.util.Date;
//@SpringBootTest:该注解表示标注当前类是个测试类,测试类不会随着项目一块打包发送
@SpringBootTest
//@RunWith:表示启动这个单元测试类(单元测试类时不能够运行的),需传递一个参数,必须是SpringRunner的实例类型
@RunWith(SpringRunner.class)
public class DistrictMapperTests {
//接口不能创建bean对象,但是mybatis创建了动态代理实现类帮我们进行解决
@Resource
private DistrictMapper districtMapper;
/**
* 单元测试方法(可以独立运行,不启动整个项目):
* 1.必须被@Test修饰
* 2.返回值类型必须是void
* 3.方法的参数列表不能指定任何类型
* 4.方法的访问修饰符必须是public
*/
@Test
public void findByParent(){
System.out.println(districtMapper.findByParent("86"));
}
}
3 控制层
3.1 处理异常
无异常处理
3.2 设计请求
url:/districts
请求方式:Get
String: parent
JsonResult<List<District>>
3.3 处理请求
新建:store\src\main\java\com\cy\store\controller\DistrictController.java
package com.cy.store.controller;
import com.cy.store.entity.District;
import com.cy.store.service.IDistrictService;
import com.cy.store.util.JsonResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
//@Controller
@RestController //该注解等于@Controller + 方法上的@ResponseBody
@RequestMapping("districts")
public class DistrictController extends BaseController{
@Resource
private IDistrictService districtService;
@RequestMapping("/")
public JsonResult<List<District>> getByParent(String parent){
List<District> data = districtService.getByParent(parent);
return new JsonResult<>(OK,data);
}
}
3.4 测试
http://localhost:8081/districts?parent=86
4 前端页面
4.1 注释原本的js代码
原本的地区数据在一份js代码中:
查看store\src\main\resources\static\web\addAddress.html
注释这两行代码
4.2 获取省市区的名称(完成的开发过程)
由于前端的表单提交的是地址的code,我们需要在address存储的时候对address进行补全,所以这里需要从持久层到控制层完成的开发一个查询过程。
4.2.1 持久层
在store\src\main\java\com\cy\store\mapper\DistrictMapper.java中添加:
/**
* 根据地址code查name
* @param code
* @return
*/
String findNameByCode(String code);
在store\src\main\resources\mapper\DistrictMapper.xml中添加:
<select id="findNameByCode" resultType="java.lang.String">
SELECT name FROM t_dict_district WHERE code = #{code}
</select>
4.2.1 业务层
store\src\main\java\com\cy\store\service\IDistrictService.java 中添加
/**
* 根据code查询name
* @param code
* @return
*/
String getNameByCode(String code);
在store\src\main\java\com\cy\store\service\impl\DistrictServiceImpl.java中添加
@Override
public String getNameByCode(String code) {
return districtMapper.findNameByCode(code);
}
同时,在地址Address的业务层(store\src\main\java\com\cy\store\service\impl\AddressServiceImpl.java)需要添加相应接口,因为我们需要在Address中进行地址name的补全:
package com.cy.store.service.impl;
import com.cy.store.entity.Address;
import com.cy.store.mapper.AddressMapper;
import com.cy.store.service.IAddressService;
import com.cy.store.service.IDistrictService;
import com.cy.store.service.ex.AddressCountLimitException;
import com.cy.store.service.ex.InsertException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Date;
/** 收获地址的实现类 */
@Service
public class AddressServiceImpl implements IAddressService {
@Resource
private AddressMapper addressMapper;
@Resource
private IDistrictService districtService;
// 收获地址最大数量
@Value("${user.address.max-count:20}")
private Integer maxCount;
@Override
public void addNewAddress(Integer uid, String username, Address address) {
//查看收获地址是否达到上限
Integer count = addressMapper.countByUid(uid);
if(count >= maxCount){
throw new AddressCountLimitException("收获地址超出上限");
}
//补全字段
address.setUid(uid);
address.setIsDefault(count==0?1:0);
//补全四项日志
address.setCreatedTime(new Date());
address.setCreatedUser(username);
address.setModifiedTime(new Date());
address.setModifiedUser(username);
//补全地址名称
address.setProvinceName(districtService.getNameByCode(address.getProvinceCode()));
address.setCityName(districtService.getNameByCode(address.getCityCode()));
address.setAreaName(districtService.getNameByCode(address.getAreaCode()));
//插入
Integer rows = addressMapper.insert(address);
if(rows!=1){
throw new InsertException("插入时异常");
}
}
}
4.3 将后端返回的数据在前端展示:
在store\src\main\resources\static\web\addAddress.html中添加:
let defaultOption = '<option value="0">----- 请选择 -----</option>';
$(document).ready(function() {
showProvinceList();
$("#city-list").append(defaultOption);
$("#area-list").append(defaultOption);
});
$("#province-list").change(function() {
showCityList();
});
$("#city-list").change(function() {
showAreaList();
});
function showProvinceList() {
$("#province-list").append(defaultOption);
$.ajax({
url: "/districts",
type: "GET",
data: "parent=86",
dataType: "JSON",
success: function(json) {
if (json.state == 200) {
let list = json.data;
console.log("count=" + list.length);
for (let i = 0; i < list.length; i++) {
console.log(list[i].name);
let option = '<option value="' + list[i].code + '">' + list[i].name + '</option>';
$("#province-list").append(option);
}
}
}
});
}
function showCityList() {
let parent = $("#province-list").val();
$("#city-list").empty();
$("#area-list").empty();
$("#city-list").append(defaultOption);
$("#area-list").append(defaultOption);
if (parent == 0) {
return;
}
$.ajax({
url: "/districts",
type: "GET",
data: "parent=" + parent,
dataType: "JSON",
success: function(json) {
if (json.state == 200) {
let list = json.data;
console.log("count=" + list.length);
for (let i = 0; i < list.length; i++) {
console.log(list[i].name);
let option = '<option value="' + list[i].code + '">' + list[i].name + '</option>';
$("#city-list").append(option);
}
}
}
});
}
function showAreaList() {
let parent = $("#city-list").val();
$("#area-list").empty();
$("#area-list").append(defaultOption);
if (parent == 0) {
return;
}
$.ajax({
url: "/districts",
type: "GET",
data: "parent=" + parent,
dataType: "JSON",
success: function(json) {
if (json.state == 200) {
let list = json.data;
console.log("count=" + list.length);
for (let i = 0; i < list.length; i++) {
console.log(list[i].name);
let option = '<option value="' + list[i].code + '">' + list[i].name + '</option>';
$("#area-list").append(option);
}
}
}
});
}