Mvc与三层架构
创建Spring项目
勾选web和mabais框架
配置yml文件,这里创建spring项目默认生成的是propertise文件,但是properties文件的格式看起来没有yml文件一目了然。yml文件配置数据库还有映射mapper层的xml文件以及设置日志级别,比如mapper层的日志级别是debug,而另外两层都是info。
server:
port: 8080
spring:
profiles:
active: dev
main:
allow-circular-references: true
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/elm?useSSL=true&allowPublicKeyRetrieval=true&serverTimezone=UTC
username: yourname
password: yourpass
mybatis:
#mapper配置文件
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.zlh.entity
configuration:
#开启驼峰命名
map-underscore-to-camel-case: true
logging:
level:
com:
zlh:
mapper: debug
service: info
controller: info
建立如下的包,controller控制器是SpringBoot中负责处理http请求的组件,service是负责处理业务逻辑的组件,mapper是负责数据持久化的组件,由于在SpringMVC中,mapper要么通过xml文件来实现数据持久化,要么通过注解的方式进行,所以只需要一个mapper接口即可。由于需要查询商家及其食品,所以需要用到business和entity两个实体,在SpringBoot中其实使用DTO对象作为不同数据层之间传输对象的模式更为常见,这里由于业务简单,所以所有层之间都是采用的实体本身。Result负责封装返回结果,即在软件开发中所有的返回结果都应该是统一的格式。
Result类,封装所有controller返回的结果,实现Serializable接口,让这个类能够被序列化(自动转换为json格式,http之间能够直接传输)。封装返回结果使用了大量的泛型的思想。方法是静态泛型方法,参数也是泛型参数。极大保证了灵活性。此外,响应码,响应信息,响应数据这个封装格式也是运用的最广泛的封装格式。
import lombok.Data;
import java.io.Serializable;
/**
* this class is used to unify the return result
* The Serializable interface is used to mark the class could be Serialized.
* */
@Data
public class Result<T> implements Serializable {
//the status code
private Integer code;
//the response message
private String msg;
//the response data
private T data;
/**
* The generic method can encapsulate the result into the Result class which can be auto serialized.
* */
public static <T> Result<T> success(){
Result<T> result = new Result<>();
result.code=1;
return result;
}
/**
* The generic method can encapsulate the result into the Result class which can be auto serialized.
* @param object could be any object.
* */
public static <T> Result<T> success(T object){
Result<T> result = new Result<>();
result.code = 1;
result.data = object;
return result;
}
/**
* The generic method can encapsulate the result into the Result class which can be auto serialized.
* @param msg The error message.
* */
public static <T> Result<T> error(String msg){
Result<T> result = new Result<>();
result.msg = msg;
result.code=0;
return result;
}
}//of class Result
Springboot通过启动类来启动项目,第一个注解是将这个类注册为启动类,使用这个注解快速配置Springboot项目,并且启动main方法后,项目就会自动监听配置文件指定的端口。
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement //use the annotation of the transaction.
@Slf4j
public class Application {
/**
*The entrance of the program and the
* */
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
log.info("server Started");
}
}
Controller类,由于项目是前后端分离开发的,所以我们需要解决跨域问题,否则游览器会拦截请求,这里简单的将本机的所有请求全部放行。@RestController注解将这个类标记为SpringMVC控制器的同时指定所有的方法返回值(全部通过序列化转为json格式)直接写入http请求体。@RequestMapping是指定这个控制器中所有方法的前缀名。在SpringBoot中,所有的bean都是通过注入的方式来实例化的,只需要将需要实例化的类或者接口使用@Bean注解或者@Service等注解标记即可。由于查询操作分为根据id查询和根据名称进行查询,而根据名称模糊匹配有可能出现多个商家匹配的情况,所以这里的返回类型是用的泛型。
package com.zlh.controller;
import com.zlh.pojo.entity.Business;
import com.zlh.result.Result;
import com.zlh.service.BusinessService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@CrossOrigin(origins = {"http://localhost:8082", "null"})//allow all request from localhost.
@RestController//write the returned object into the http response
@RequestMapping("/business")//map the web request,here is public url of all method of this controller.
@Slf4j//the log of this class
public class businessController {
//use this annotation,your class must use the Bean annotation or your implementation class of the
//interface must use the used the corresponding annotation.
@Autowired//auto-injection
private BusinessService businessService;
/**
*
*/
@PostMapping("/query/{param}")
public Result<?> query(@PathVariable String param){
log.info("查询商家信息,查询参数为:{}",param);
Business business = new Business();
List<Business> businessesList = new ArrayList<>();
try{
Integer.parseInt(param);
business=businessService.queryById(param);
return Result.success(business);
}catch (Exception e){
businessesList=businessService.queryByName(param);
for (Business b:businessesList) System.out.println(b);
return Result.success(businessesList);
}
}//of query
/**
*
* */
@PostMapping("/insert")
public Result insert(@RequestBody Business business){
log.info("注册商家信息,注册参数为:{}",business);
businessService.insert(business);
return Result.success();
}//of insert
}//of class businessController
由于没有设计相应的dto和功能都是简单的查找和插入,这里的业务层设计比较简单,直接注入mapper接口然后调用相关方法即可。
import com.zlh.mapper.BusinessMapper;
import com.zlh.pojo.entity.Business;
import com.zlh.service.BusinessService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BusinessServiceImpl implements BusinessService {
//inject businessMapper
@Autowired
private BusinessMapper businessMapper;
@Override
public void insert(Business business) {
business.setBusinessImg("图片正在加载中");
businessMapper.insert(business);
}//of insert
@Override
public Business queryById(String param) {
return businessMapper.queryById(param);
}//of queryById
@Override
public List<Business> queryByName(String param) {
return businessMapper.queryByName(param);
}//of queryByName
}//of class BusinessServiceImpl
Mapper层
Mapper层负责实现数据持久化,简单的sql语句直接使用相应的注解,复杂语句使用xml文件。这里全部采用的是xml文件(并不是所有语句都很复杂,只是适应写配置文件)。
import com.zlh.pojo.entity.Business;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface BusinessMapper {
List<Business> queryByName(String name);
Business queryById(String id);
void insert(Business business);
}
<?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.zlh.mapper.BusinessMapper">
<resultMap id="BusinessWithFood" type="com.zlh.pojo.entity.Business">
<id property="businessId" column="businessId"/>
<result property="businessName" column="businessName"/>
<result property="businessAddress" column="businessAddress"/>
<result property="businessExplain" column="businessExplain"/>
<result property="businessImg" column="businessImg"/>
<result property="orderTypeId" column="orderTypeId"/>
<result property="starPrice" column="starPrice"/>
<result property="remarks" column="remarks"/>
<collection property="foods" column="businessId" foreignColumn="businessId" ofType="com.zlh.pojo.entity.Food"
select="getFoodByBusinessId"/>
</resultMap>
<select id="queryById" resultMap="BusinessWithFood">
SELECT * FROM elm.business WHERE businessId = #{id}
</select>
<select id="getFoodByBusinessId" resultType="com.zlh.pojo.entity.Food" parameterType="long">
select * FROM elm.food WHERE businessId=#{businessId}
</select>
<select id="queryByName" resultMap="BusinessWithFood">
select * from elm.business where businessName like CONCAT('%',#{name},'%')
</select>
<insert id="insert" parameterType="com.zlh.pojo.entity.Business" useGeneratedKeys="true" keyProperty="businessId">
insert into elm.business (businessName, businessAddress, businessExplain,businessImg, orderTypeId, starPrice, deliveryPrice)
VALUES (#{businessName}, #{businessAddress}, #{businessExplain},#{businessImg},#{orderTypeId},#{starPrice}, #{deliveryPrice})
</insert>
</mapper>
查询操作由于要返回商家和食品两个实体的内容。我开始想到的是sql语句里面的连接,但三个连接查询出来的结果全都有问题,后面想到了上课学的association,但是assocaition适应于一对一的关系,这里明显是一对多的关系(一个菜品对应一个店铺,一个店铺对应多个菜品,数据比较极端),所以查资料找到了collection来处理这种一对多的关系。首先要在Business里面加上一个food的列表,泛型指定为Food类型,Food类就对应food这张表。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Food {
private long foodId;
private String foodName;
private String foodExplain;
private String foodImg;
private float foodPrice;
private long businessId;
private String remarks;
}
再写一个resultMap,里面对应的是Business这个实体的属性,而collection与food这个列表对应,指定food的外键还有查询food的方法,这个select方法在执行这个resultMap时会自动加载执行。但是在这个方法中要指定resultMap。
<?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.zlh.mapper.BusinessMapper">
<resultMap id="BusinessWithFood" type="com.zlh.pojo.entity.Business">
<id property="businessId" column="businessId"/>
<result property="businessName" column="businessName"/>
<result property="businessAddress" column="businessAddress"/>
<result property="businessExplain" column="businessExplain"/>
<result property="businessImg" column="businessImg"/>
<result property="orderTypeId" column="orderTypeId"/>
<result property="starPrice" column="starPrice"/>
<result property="remarks" column="remarks"/>
<collection property="foods" column="businessId" foreignColumn="businessId" ofType="com.zlh.pojo.entity.Food"
select="getFoodByBusinessId"/>
</resultMap>
<select id="queryById" resultMap="BusinessWithFood">
SELECT * FROM elm.business WHERE businessId = #{id}
</select>
<select id="getFoodByBusinessId" resultType="com.zlh.pojo.entity.Food" parameterType="long">
select * FROM elm.food WHERE businessId=#{businessId}
</select>
<select id="queryByName" resultMap="BusinessWithFood">
select * from elm.business where businessName like CONCAT('%',#{name},'%')
</select>
<insert id="insert" parameterType="com.zlh.pojo.entity.Business" useGeneratedKeys="true" keyProperty="businessId">
insert into elm.business (businessName, businessAddress, businessExplain,businessImg, orderTypeId, starPrice, deliveryPrice)
VALUES (#{businessName}, #{businessAddress}, #{businessExplain},#{businessImg},#{orderTypeId},#{starPrice}, #{deliveryPrice})
</insert>
</mapper>
结果图
根据id查询
根据名称模糊查询
插入
htmnl页面,有些功能为了考试修改了一部分,可能会有bug。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>商家查询与添加</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<h2>商家查询</h2>
<input type="text" id="searchInput" placeholder="输入商家ID或名称">
<button onclick="queryMerchant()">查询</button>
<div id="result"></div>
<h2>商家添加</h2>
<form id="addForm">
<input type="text" name="businessName" placeholder="">
<input type="text" name="businessAddress" placeholder="商家地址">
<input type="text" name="businessExplain" placeholder="商家描述">
<input type="text" name="orderTypeId" placeholder="点餐分类">
<button type="button" onclick="addMerchant()">添加</button>
</form>
<script>
function queryMerchant() {
var param = $('#searchInput').val();
$.ajax({
url: 'http://localhost:8080/business/query/' + encodeURIComponent(param),
type: 'POST',
success: function(data) {
var book = Array.isArray(data.data) ? data.data : [data.data]; //将所有商家的信息统一转为数组形式来渲染
if(book[0]==null){
$('#result').html('没有找到对应的商家信息');
}else{
var resultDiv = $('#result');
resultDiv.empty(); // 清空原来的表格
$.each(book, function(index, book) {
// 商家信息表格
var businessTable = $('<table class="business-table"></table>');
var businessHeader = $('<thead><tr><th>ID</th><th>名称</th><th>价格</th><th>作者</th><th>出版社Id</th></tr></thead>');
var businessBody = $('<tbody><tr><td>' + book.id + '</td><td>' + book.bookName + '</td><td>' +
book.price +'</td><td>' + book.author +'</td><td>' + book.press_id +'</td></tr></tbody>');
businessTable.append(businessHeader).append(businessBody);
resultDiv.append(businessTable);
});
// 如果所有商家都没有foods,则显示提示信息
if (resultDiv.find('.foods-table tbody tr').length === 0) {
}
}
},
error: function(jqXHR, textStatus, errorThrown) {
// 处理请求失败的情况
$('#result').html('<p>请求失败: ' + textStatus + ', ' + errorThrown + '</p>');
}
});
}
function addMerchant() {
var formData = $('#addForm').serializeArray(); // 序列化表单数据并转换为数组
var jsonData = {};
for (var i = 0; i < formData.length; i++) {
jsonData[formData[i].name] = formData[i].value;
}
$.ajax({
url: 'http://localhost:8080/business/insert',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(jsonData),
success: function(data) {
alert('商家添加成功!');
},
error: function(xhr, status, error) {
console.error("添加失败: " + error);
}
});
}
</script>
</body>
</html>