送水公司后台管理系统
1送水历史列表
点击“送水历史管理”,显示“送水历史”列表。送水历史列表查询工作涉及到三张表联合查询,我们使用MyBatis-plus映射文件(HistoryMapper.xml)代替MyBatis-Plus提供的内置查询方法。
1.1 编写YML文件
配置Mapper映射文件的路径和包别名
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
# 配置Mapper.xml文件的路径,此时所有Mapper.xml映射文件都定义在resources/mapper路径下
mapper-locations: classpath:/mapper/*Mapper.xml
# 配置指定包中(com.shouyi.entities)不包括包名的简单类名作为包括包名的别名
type-aliases-package: com.shouyi.entities
1.2 编写History实体类
package com.shouyi.entities;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
/**
* TODO 送水历史管理实体类
* @author caojie
* @version 1.0
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_history")
public class History {
/**
* 送水历史ID
*/
private Integer hid;
/**
* 关联送水工
*/
private Worker worker;
/**
* 关联客户
*/
private Customer customer;
/**
* 送水时间
*/
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date sendWaterTime;
/**
* 送水数量
*/
private Integer sendWaterCount;
}
1.3 编写Mapper接口
package com.shouyi.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.shouyi.entities.History;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* TODO: 送水历史相关的映射器
* @author caojie
* @version 1.0
*/
@Repository
public interface HistoryMapper extends BaseMapper<History> {
/**
* 自定义查询方法来代替MyBaits-Plus内置的查询方法,查询所有的送水历史信息。
* @return 送水历史列表
*/
List<History> listHistory();
}
1.4 编写Mapper映射文件
我们使用自定义查询方法来代替MyBaits-Plus
内置的查询方法,所以要编写HistoryMapper
接口对应的映射文件HistoryMapper.xml
。该映射文件在resources/mapper
路径下定义
<?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.shouyi.mapper.HistoryMapper">
<!-- 我们使用三张表进行联合查询,所以查询的送水历史列表返回一个结果集,将三张表查询的结果封装到resultMap结果集中。resultMap类似于JDBC的ResultSet对象
resultMap有两个核心属性:
1) id表示resultMap在整个配置文件中的唯一性
2) type表示返回值的全限定类名,我们在yml配置文件中配置了type-aliases-package: com.shouyi.entities。所以type属性值可以返回类型别名(省略了包名)
-->
<resultMap id="selectHistoryMap" type="History">
<id property="hid" column="hid" />
<result column="send_water_time" property="sendWaterTime" />
<result column="send_water_count" property="sendWaterCount"/>
<!-- 送水历史关联送水工-->
<association property="worker">
<id column="wid" property="wid"></id>
<result column="worker_name" property="workerName"></result>
</association>
<!-- 送水历史关联客户-->
<association property="customer">
<id column="cid" property="cid"></id>
<result column="cust_name" property="custName"></result>
</association>
</resultMap>
<!--
select节点表示一个查询方法
id属性必须跟HistoryMapper接口的方法名一致
-->
<select id="listHistory" resultMap="selectHistoryMap">
SELECT h.hid,w.worker_name,h.send_water_time,c.cust_name,h.send_water_count
FROM tb_history h, tb_worker w, tb_customer c
WHERE h.worker_id = w.wid and h.cust_id = c.cid
</select>
</mapper>
1.5 编写Service接口
package com.shouyi.service;
import com.shouyi.entities.History;
import java.util.List;
/**
* TODO 送水历史管理业务逻辑接口
* @author caojie
* @version 1.0
*/
public interface HistoryService {
/**
查询所有送水历史信息
*/
List<History> listHistory() ;
}
1.6 编写Service接口实现类
package com.shouyi.service.impl;
import com.shouyi.entities.History;
import com.shouyi.mapper.HistoryMapper;
import com.shouyi.service.HistoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* TODO:送水历史管理接口实现类
* @author caojie
* @version 1.0
*/
@Service
public class HistoryServiceImpl implements HistoryService {
@Autowired
private HistoryMapper historyMapper;
/**
查询所有送水历史信息
*/
@Override
public List<History> listHistory() {
return historyMapper.listHistory();
}
}
1.7 编写Controller控制器
package com.shouyi.controller;
import com.shouyi.entities.History;
import com.shouyi.service.HistoryService;
import lombok.extern.slf4j.Slf4j;
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 java.util.List;
/**
* TODO
*
* @author caojie
* @version 1.0
* @date 2021/10/25 20:20
*/
@RequestMapping("/history")
@Controller
@Slf4j
public class HistoryController {
@Autowired
private HistoryService historyService;
/**
点击“送水历史管理”,调用HistoryService对象查询所有送水历史信息,然后将信息渲染到前端。最后返回“送水历史”页面
*/
@RequestMapping("/listHis")
public String listHistory(Model model) {
List<History> hisList = historyService.listHistory();
log.info("history list size = "+hisList.size());
model.addAttribute("hisList",hisList);
return "historyList";
}
}
1.8 编写送水历史管理列表页面
在resources/water
路径下创建客户管理列表页面historyList.html
,显示送水历史信息。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title> XXX送水公司后台管理系统</title>
<!--Bootstrap固定框架-->
<link rel='stylesheet' th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap.css}">
<link rel='stylesheet' th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap-theme.css}">
<!--图标库-->
<link rel='stylesheet' th:href='@{/css/material-design-iconic-font.min.css}'>
<!--核心样式-->
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
<div id="viewport">
<!-- Sidebar -->
<div id="sidebar" th:replace="waterMainMenu::sidebar">
</div>
<!-- Content -->
<div id="content">
<nav class="navbar navbar-default" th:replace="waterMainMenu::navbar">
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<table class="table table-hover table-striped">
<thead>
<tr>
<td>送水历史编号</td>
<td>送水工名称</td>
<td>客户名称</td>
<td>送水时间</td>
<td>送水数量</td>
</tr>
</thead>
<tbody>
<tr th:each="his : ${hisList}">
<td th:text="${his.hid}"></td>
<td th:text="${his.worker.workerName}"></td>
<td th:text="${his.customer.custName}"></td>
<td th:text="${#dates.format(his.sendWaterTime,'yyyy-MM-dd')}"></td>
<td th:text="${his.sendWaterCount}"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
1.9 编写主页面
修改主页面waterMainMenu.html
,在“送水历史管理”菜单里面定义超链接
<li>
<a th:href="@{/history/listHis}">
<i class="zmdi zmdi-widgets"></i> 送水历史管理
</a>
</li>
2 添加送水历史信息
添加页面选择对应的“送水工”和“客户”,输入”送水时间“和“送水数量”
2.1编写Mapper接口
由于History涉及到三张表,我们无法使用Mapper
内置的添加方法。必须在HistoryMapper
接口里面定义saveHistory
方法来添加“送水历史”信息。
/**
* 添加送水历史
* @param history 送水历史信息
* @return 受影响行数,大于0添加成功,否则添加失败
*/
int saveHistory(History history);
2.2 编写Mapper映射文件
在HistoryMapper.xml
映射文件中定义insert
节点,添加“送水历史”信息。
<insert id="saveHistory" >#{}
insert into tb_history(worker_id,cust_id,send_water_time,send_water_count)
values(#{worker.wid},#{customer.cid},#{sendWaterTime},#{sendWaterCount})
</insert>
2.3 编写Service接口
/**
* 添加送水历史
* @param history 表单采集的送水历史信息
* @return 手影响行数,大于0添加成功,否则添加失败
*/
int saveHistory(History history);
2.4 编写接口实现类
/**
* 添加送水历史
*
* @param history 表单采集的送水历史信息
* @return 受影响行数,大于0添加成功,否则添加失败
*/
@Override
public int saveHistory(History history) {
return historyMapper.saveHistory(history);
}
2.5 编写Controller
/**
送水历史需要用到客户信息,自动装配CustomerService对象
*/
@Autowired
private CustomerService customerService;
/**
送水历史需要用到送水工信息,自动装配WorkerService对象
*/
@Autowired
private WorkerService workerService;
/**
点击“添加”按钮执行预修改操作
步骤:
1 查询客户列表和送水工列表
2 将客户列表和送水工列表渲染到前端页面
3 返回送水历史页面
*/
@RequestMapping("/preSaveHis")
public String preSaveHistory(Model model) {
// 查询客户列表和送水工列表,渲染到前端
List<Customer> custList = customerService.listCustomer();
List<Worker> workerList = workerService.listWorker();
model.addAttribute("custList",custList);
model.addAttribute("workerList",workerList);
return "historySave";
}
/**
点击“提交”按钮,将送水历史信息持久化到数据库,并重新查询送水历史列表,显示新增加的数据
*/
@RequestMapping(value="/saveHis",method = RequestMethod.POST)
public String saveHistory(Integer workerId,Integer custId,History history) {
log.info("save History workerId = "+workerId);
log.info("save History customerId = "+custId);
log.info("History history = "+history);
// 将送水工编号和客户编号注入到History对象
Worker worker = new Worker();
worker.setWid(workerId);
Customer customer = new Customer();
customer.setCid(custId);
history.setCustomer(customer);
history.setWorker(worker);
// 将History对象持久化到数据库
int rows =historyService.saveHistory(history);
log.info("save History rows = "+rows);
return "redirect:/history/listHis";
}
2.6 编写送水历史列表页面
在送水历史列表页面新增加一个“添加”按钮,单击“添加”按钮跳转到“添加送水历史”页面
<div class="col-md-12">
<div class="col-md-4">
<a class="btn btn-primary"
th:href="@{/history/preSaveHis}">添加</a>
</div>
</div>
2.7 编写添加送水历史页面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title> XXX送水公司后台管理系统</title>
<!--Bootstrap固定框架-->
<link rel='stylesheet' th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap.css}">
<link rel='stylesheet' th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap-theme.css}">
<!--图标库-->
<link rel='stylesheet' th:href='@{/css/material-design-iconic-font.min.css}'>
<!--核心样式-->
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
<div id="viewport">
<!-- Sidebar -->
<div id="sidebar" th:replace="waterMainMenu::sidebar">
</div>
<!-- Content -->
<div id="content">
<nav class="navbar navbar-default" th:replace="waterMainMenu::navbar">
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<form class="form-horizontal" method="post" th:action="@{/history/saveHis}" >
<div class="form-group">
<label class="col-sm-2 control-label">送水工名称</label>
<div class="col-sm-5">
<select name="workerId" class="form-control">
<option th:each="worker : ${workerList}"
th:value="${worker.wid}" th:text="${worker.workerName}">
</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">客户名称</label>
<div class="col-sm-5">
<select name="custId" class="form-control">
<option th:each="cust : ${custList}"
th:value="${cust.cid}"
th:text="${cust.custName}">
</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">送水时间</label>
<div class="col-sm-5">
<input type="date" class="form-control"
name="sendWaterTime" placeholder="送水时间">
</div>
</div>
<div class="form-group" >
<label class="col-sm-2 control-label">送水数量</label>
<div class="col-sm-5">
<input type="text" class="form-control"
name="sendWaterCount" placeholder="送水数量">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-5">
<input type="submit" class="btn btn-primary" name="submit" value="提交">
<input type="reset" class="btn btn-warning" name="reset" value="取消">
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
3 修改送水历史信息
修改"送水历史"流程如下:
3.1 编写Mapper接口
3.2 编写Mppaer映射文件
3.3 编写Service接口
3.4 编写Service接口实现类
3.5 编写Controller控制器
3.6 编写送水历史列表页面
在页面上新增加一个“修改”按钮
3.7 编写修改送水历史页面
1 计算工资
老板为工人发工资,需要统计工资。统计结果如下:
SELECT w.worker_name , w.worker_salary , w.worker_money,
-- 如果送水数量为null,默认为0
ifnull(sum(h.send_water_count),0) as send_water_count,
-- 如果实发工资为null,默认为基本工资
ifnull(sum(h.send_water_count * w.worker_money)+w.worker_salary , w.worker_salary) as final_salary
FROM tb_worker w left join tb_history h on w.wid = h.worker_id
GROUP BY w.wid
ORDER BY final_salary desc
1.1 定义计算工资Salary实体类
package com.minzu.entities;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* TODO: 计算工资相关的实体类
* @author caojie
* @version 1.0
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Salary {
/**
* 送水工名称
*/
private String workerName;
/**
* 送水工底薪
*/
private Integer workerSalary;
/**
* 送水工提成
*/
private Double workerMoney;
/**
* 送水数量
*/
private Integer sendWaterCount;
/**
* 实发工资
*/
private Double finalSalary;
}
1.2 定义SalaryMapper接口
package com.minzu.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.minzu.entities.Customer;
import com.minzu.entities.Salary;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* TODO: 计算工资映射器
* @author caojie
* @version 1.0
*/
@Repository
public interface SalaryMapper extends BaseMapper<Customer> {
/**
* 计算所有送水工的工资
* @return 工资列表
*/
List<Salary> calcSalary();
}
1.3 定义映射文件
定义SalaryMapper
接口对应的映射器SalaryMapper.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">
<mapper namespace="com.minzu.mapper.SalaryMapper">
<select id="calcSalary" resultType="Salary">
SELECT w.worker_name , w.worker_salary , w.worker_money,
ifnull(sum(h.send_water_count),0) as send_water_count,
ifnull(sum(h.send_water_count * w.worker_money)+w.worker_salary , w.worker_salary) as final_salary
FROM tb_worker w left join tb_history h on w.wid = h.worker_id
GROUP BY w.wid
ORDER BY final_salary desc
</select>
</mapper>
1.4 定义SalaryService接口
package com.minzu.service;
import com.minzu.entities.Salary;
import java.util.List;
/**
* TODO:计算工资相关的业务逻辑
* @author caojie
* @version 1.0
*/
public interface SalaryService {
/**
* 计算所有送水工的工资
* @return 工资列表
*/
List<Salary> calcSalary();
}
1.5 定义SalaryService实现类
package com.minzu.service.impl;
import com.minzu.entities.Salary;
import com.minzu.mapper.SalaryMapper;
import com.minzu.service.SalaryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* TODO
* @author caojie
* @version 1.0
*/
@Service
public class SalaryServiceImpl implements SalaryService {
@Autowired
private SalaryMapper salaryMapper;
/**
* 计算所有送水工的工资
*
* @return 工资列表
*/
@Override
public List<Salary> calcSalary() {
return salaryMapper.calcSalary();
}
}
1.6 定义Controller控制器
package com.minzu.controller;
import com.minzu.entities.Salary;
import com.minzu.service.SalaryService;
import lombok.extern.slf4j.Slf4j;
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 java.util.List;
/**
* TODO:处理计算工资对应的请求
* @author caojie
* @version 1.0
*/
@RequestMapping("/salary")
@Controller
@Slf4j
public class SalaryController {
@Autowired
private SalaryService salaryService;
/**
* 点击"计算工资"显示所有送水工的工资
* @param model
* @return
*/
@RequestMapping("/calcSalary")
public String calcWorkerSalary(Model model) {
List<Salary> salaryList = salaryService.calcSalary();
log.info("worker salary list size = "+salaryList.size());
model.addAttribute("salaryList",salaryList);
return "salaryList";
}
}
1.7 定义计算工资列表页面
定义计算工资列表页面salaryList.html
,显示所有送水工的工资
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title> XXX送水公司后台管理系统</title>
<!--Bootstrap固定框架-->
<link rel='stylesheet' th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap.css}">
<link rel='stylesheet' th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap-theme.css}">
<!--图标库-->
<link rel='stylesheet' th:href='@{/css/material-design-iconic-font.min.css}'>
<!--核心样式-->
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
<div id="viewport">
<!-- Sidebar
客户列表页面使用th:replace属性替换成主菜单的侧边栏,让代码能够复用
th:replace="waterMainMenu::sidebar"
waterMainMenu表示主菜单页面的文件名称
sidebar表示主菜单页面的片段名称
-->
<div id="sidebar" th:replace="waterMainMenu::sidebar">
</div>
<!-- Content -->
<div id="content">
<!--
th:replace="waterMainMenu::navbar"表示将nav标签里面所有的内容替换为主页面的navbar片段
-->
<nav class="navbar navbar-default" th:replace="waterMainMenu::navbar">
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<table class="table table-hover table-striped">
<thead>
<tr>
<td>送水工姓名</td>
<td>基本工资</td>
<td>每桶提成</td>
<td>送水数量</td>
<td>实发工资</td>
</tr>
</thead>
<tbody>
<tr th:each="salary : ${salaryList}">
<td th:text="${salary.workerName}"></td>
<td th:text="${salary.workerSalary}"></td>
<td th:text="${salary.workerMoney}"></td>
<td th:text="${salary.sendWaterCount}"></td>
<td th:text="${salary.finalSalary}"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
1.8 修改主界面
在主界面waterMainMenu.html
增加“计算工资”对应的超链接
<li>
<a th:href="@{/salary/calcSalary}">
<i class="zmdi zmdi-widgets"></i> 计算工资
</a>
</li>
2 根计算某一段时间的工人工资
2.1 编写SalaryMapper接口
在SalaryMapper
接口里面定义根据条件计算工资的方法。
注意:Mapper接口如果有多个方法必须定义@Param注解,否则无法传入Mapper映射文件
/**
* 根据搜索条件计算某一段时间的工资
* @param startDate 开始时间
* @param endDate 结束时间
* @return 工资列表
*/
List<Salary> calcSalaryByCondition(@Param("startDate") String startDate,
@Param("endDate") String endDate);
2.2 编写Mapper映射文件
<select id="calcSalaryByCondition" parameterType="string" resultType="Salary">
SELECT w.worker_name , w.worker_salary , w.worker_money,
ifnull(sum(h.send_water_count),0) as send_water_count,
ifnull(sum(h.send_water_count * w.worker_money)+w.worker_salary , w.worker_salary) as final_salary
FROM tb_worker w left join tb_history h on w.wid = h.worker_id
where h.send_water_time between #{startDate} and #{endDate}
GROUP BY w.wid
ORDER BY final_salary desc
</select>
2.3 编写SalaryService接口
/**
* 根据搜索条件计算某一段时间的工资
* @param startDate 开始时间
* @param endDate 结束时间
* @return 工资列表
*/
List<Salary> calcSalaryByCondition(String startDate,String endDate);
2.4 编写SalaryService实现类
/**
* 根据搜索条件计算某一段时间的工资
*
* @param startDate 开始时间
* @param endDate 结束时间
* @return 工资列表
*/
@Override
public List<Salary> calcSalaryByCondition(String startDate, String endDate) {
// 条件成立:表示“结束时间”为空,系统当前时间为结束时间,然后再转换为字符串
if (StrUtil.isEmpty(endDate)) {
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
endDate = sdf.format(date);
}
return salaryMapper.calcSalaryByCondition(startDate,endDate);
}
2.5 编写Controller控制器
@RequestMapping("/searchCalcSalary")
public String searchCalcWorkerSalary(String startDate, String endDate, Model model) {
List<Salary> salaryList = salaryService.calcSalaryByCondition(startDate, endDate);
model.addAttribute("salaryList",salaryList);
return "salaryList";
}
2.6 编写计算工资列表页面
在salaryList.html
页面中新增加搜索表单
<div class="col-md-12">
<div class="col-md-8">
<form class="form-inline" th:action="@{/salary/searchCalcSalary}">
<input type="date" class="form-control" th:name="startDate" placeholder="请输入开始时间" required/>
<input type="date" class="form-control" th:name="endDate" placeholder="请输入结束时间"/>
<input type="submit" class="btn btn-primary" name="search" value="搜索"/>
</form>
</div>
</div>
3 修复BUG
如图所示:查询2020-01-01 ~ 2020-01-31时间段送过水的送水工工资,在这个时间段没有为客户送过水的送水工工资却没有显示。这个时间段没有为为客户送过水的送水工应该显示基本工资
正确的查询结果应该是:
3.1 Mapper接口
/**
* 查询没有为客户送水的送水工信息
* @return 送水工列表
*/
List<Worker> queryNotSendWaterWorker() ;
3.2 Mapper映射器配置文件
<!-- 查询没有为客户送水的送水工信息-->
<select id="queryNotSendWaterWorker" resultType="Worker">
SELECT w.worker_name, w.worker_salary, w.worker_money
FROM tb_worker w left join tb_history h on w.wid = h.worker_id
where h.worker_id is null
</select>
3.3 SalaryService接口实现类
修改SalaryService
接口实现类的calcSalaryByCondition
方法。计算没有送水的送水工的工资
/**
* 根据搜索条件计算某一段时间的工资
*
* @param startDate 开始时间
* @param endDate 结束时间
* @return 工资列表
*/
@Override
public List<Salary> calcSalaryByCondition(String startDate, String endDate) {
// 条件成立:表示“结束时间”为空,系统当前时间为结束时间,然后再转换为字符串
if (StrUtil.isEmpty(endDate)) {
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
endDate = sdf.format(date);
}
List<Salary> salaryList = salaryMapper.calcSalaryByCondition(startDate, endDate);
// 查询没有为客户送过水的送水工信息
List<Worker> workerList = salaryMapper.queryNotSendWaterWorker();
// 遍历每个没有为客户送过水的送水工信息,将它们注入到Salary对象。最后把Salary对象添加到salaryList集合
workerList.forEach(worker ->{
Salary salary = new Salary();
salary.setWorkerName(worker.getWorkerName());
salary.setWorkerSalary(worker.getWorkerSalary());
salary.setWorkerMoney(worker.getWorkerMoney());
// 没有送水的送水工实发工资就是他的底薪
salary.setFinalSalary(Double.valueOf(worker.getWorkerSalary()));
// 没有送水的送水工送水数量为0
salary.setSendWaterCount(0);
salaryList.add(salary);
});
// salaryList集合对象可能有重复的数据,将重复数据去掉
Set set = new LinkedHashSet<>(salaryList);
salaryList.clear();
salaryList.addAll(set);
return salaryList;
}
1修复BUG
1.1 没有计算时间段外的送水工工资
输入“起始时间”和“结束时间”,然后点击“搜索”。可以计算出在这个时间段的每个送水工的工资,还可以计算没有送水的送水工工资。
“小刘”没有在1月份为客户送水但是在其它时间段为客户送过水。下面的列表没有计算到小刘的工资。
如何解决上述BUG? 使用union对多个select语句进行联合查询:查询没有为客户送过水的送水工列表 “联合” 没有在该时间段为客户送水的送水工列表。例如:
SELECT distinct w.worker_name,w.worker_salary,w.worker_money
FROM tb_worker w left join tb_history h on w.wid = h.worker_id
WHERE not h.send_water_time between #{startDate} and #{endDate}
union
SELECT w.worker_name,w.worker_salary,w.worker_money
FROM tb_worker w left join tb_history h on w.wid = h.worker_id
where h.worker_id is null
Java代码也要做如下修改:
/**
* 根据条件计算某一段时间的送水工工资
*
* @param startDate 开始时间
* @param endDate 结束时间
* @return 工资列表
*/
@Override
public List<Salary> listCalcSalaryByCondition(String startDate, String endDate) {
// 条件成立:表示输入的结束时间为Null,将系统当前时间作为结束时间
if(StrUtil.isEmpty(endDate)){
long currentTime = System.currentTimeMillis();
Date dt = new Date(currentTime);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
endDate = sdf.format(dt);
}
// salaryList 在某个时间段已经为客户送过水的送水工信息
List<Salary> salaryList = salaryMapper.listCalcSalaryByCondition(startDate, endDate);
// sendWorkerList 在某个时间段已经为客户送过水的送水工名称列表
List<String> sendWorkerList = salaryList.stream()
.map(Salary::getWorkerName).collect(Collectors.toList());
// 没有为客户送过水的送水工信息
List<Worker> workerList = salaryMapper.queryNonSendWaterWorker(startDate,endDate);
// 将没有送水的送水工信息合并到salaryList
// 遍历workerList,将worker对象的数据注入到Salary对象中,让后添加到salaryList集合
workerList.forEach(worker->{
// 条件成立:表示没有送水的送水工在salaryList集合中不存在,将其添加到集合中
if(!sendWorkerList.contains(worker.getWorkerName())){
Salary sa = new Salary();
sa.setWorkerName(worker.getWorkerName());
sa.setWorkerSalary(worker.getWorkerSalary());
sa.setWorkerMoney(worker.getWorkerMoney());
// 没有送水的送水工默认送水数量为0
sa.setSendWaterCount(0);
// 没有送水的送水工默认实发工资为基本工资
sa.setFinalSalary(Double.valueOf(worker.getWorkerSalary()));
salaryList.add(sa);
}
});
// salaryList集合可能有重复的数据,去掉重复的数据
Collections.sort(salaryList,(o1,o2)->{
if(o1.getFinalSalary() > o2.getFinalSalary()){
return -1;
}else if(o1.getFinalSalary() == o2.getFinalSalary()) {
return 0;
} else {
return 1;
}
});
return salaryList;
}
1.2 没有对送水工工资进行排序
如何解决:按照送水工的工资进行比较,降序排序
Collections.sort(salaryList,(o1,o2)->{
if(o1.getFinalSalary() > o2.getFinalSalary()){
return -1;
}else if(o1.getFinalSalary() == o2.getFinalSalary()) {
return 0;
} else {
return 1;
}
});
2 统计送水工送水数量
2.1 需求分析
原始需求:统计每个送水工为那些客户总共送了多少桶水
送水工名称 | 客户列表 | 送水数量 |
---|---|---|
小刘 | 老刘,老张 | 761 |
小李 | 李老,老陈 | 66 |
小张 | 老张 | 60 |
小唐 | - | 0 |
需求分析:
- 首先确定表:
tb_worker
,tb_history
,tb_customer
- 然后确定列:
tb_worker
表的worker_name
,tb_customer
表的cust_name
,tb_history
表的send_water_count
- 确定哪些列是原始列,哪些列是需要计算的:
tb_worker
表的worker_name
列是原始列。使用group_concat
函数对tb_customer
表的cust_name
列需要对每个客户进行拼接,然后汇总。使用聚合函数sum
对tb_history
表的send_water_count
列进行累加求和。 - 确定要分组的列:对
tb_worker
表的worker_name
列进行分组 - 对“送水数量”进行降序排序
SELECT w.worker_name ,
ifnull(GROUP_CONCAT(distinct c.cust_name),'-') as cust_details,
ifnull(sum(h.send_water_count),0) as send_water_count
FROM tb_worker w left join tb_history h on w.wid = h.worker_id
left join tb_customer c on h.cust_id = c.cid
GROUP BY w.worker_name
ORDER BY send_water_count desc
2.2 操作步骤
2.2.1 编写实体类WaterDetails
package com.shouyi.entities;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* TODO: 详细送水信息实体类
* @author caojie
* @version 1.0
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class WaterDetails {
/**
* 送水工名称
*/
private String workerName;
/**
* 客户详细信息
*/
private String custDetails;
/**
* 送水数量
*/
private Integer sendWaterCount;
}
2.2.2 编写WaterDetailsMapper接口
package com.shouyi.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.shouyi.entities.WaterDetails;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* TODO 送水详细信息映射器
* @author caojie
* @version 1.0
*/
@Repository
public interface WaterDetailsMapper extends BaseMapper<WaterDetails> {
/**
* 查询每个送水工送水的详细信息
* @return 送水详细信息列表
*/
List<WaterDetails> querySendWaterDetails();
}
2.2.3 编写Mapper对应的映射文件
<?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.shouyi.mapper.WaterDetailsMapper">
<select id="querySendWaterDetails" resultType="WaterDetails">
SELECT w.worker_name ,
ifnull(GROUP_CONCAT(distinct c.cust_name),'-') as cust_details,
ifnull(sum(h.send_water_count),0) as send_water_count
FROM tb_worker w left join tb_history h on w.wid = h.worker_id
left join tb_customer c on h.cust_id = c.cid
GROUP BY w.worker_name
ORDER BY send_water_count desc
</select>
</mapper>
2.2.4 编写WaterDetailsService接口
package com.shouyi.service;
import com.shouyi.entities.WaterDetails;
import java.util.List;
/**
* TODO: 送水详细信息业务逻辑接口
* @author caojie
* @version 1.0
*/
public interface WaterDetailsService {
/**
* 查询每个送水工送水的详细信息
* @return 送水详细信息列表
*/
List<WaterDetails> querySendWaterDetails();
}
2.2.5 编写接口实现类
package com.shouyi.service.impl;
import com.shouyi.entities.WaterDetails;
import com.shouyi.mapper.WaterDetailsMapper;
import com.shouyi.service.WaterDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* TODO
* @author caojie
* @version 1.0
*/
@Service
public class WaterDetailsServiceImpl implements WaterDetailsService {
@Autowired
private WaterDetailsMapper waterDetailsMapper;
/**
* 查询每个送水工送水的详细信息
* @return 送水详细信息列表
*/
@Override
public List<WaterDetails> querySendWaterDetails() {
return waterDetailsMapper.querySendWaterDetails();
}
}
2.2.6 编写Controller控制器
package com.shouyi.controller;
import com.shouyi.entities.WaterDetails;
import com.shouyi.service.WaterDetailsService;
import lombok.extern.slf4j.Slf4j;
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 java.util.List;
/**
* TODO: 统计送水信息的控制器
* @author caojie
* @version 1.0
*/
@RequestMapping("/stat")
@Controller
@Slf4j
public class WaterDetailsController {
@Autowired
private WaterDetailsService waterDetailsService;
/**
* 统计送水详细信息
* @return “送水详细信息”页面
*/
@RequestMapping("/statWaterDetails")
public String statWaterDetails(Model model) {
// 送水详细信息列表
List<WaterDetails> waterList = waterDetailsService.querySendWaterDetails();
model.addAttribute("waterList",waterList);
return "waterDetailsList";
}
}
2.2.7 编写前端页面
新建前端页面waterDetailsList.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title> XXX送水公司后台管理系统</title>
<!--Bootstrap固定框架-->
<link rel='stylesheet' th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap.css}">
<link rel='stylesheet' th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap-theme.css}">
<!--图标库-->
<link rel='stylesheet' th:href='@{/css/material-design-iconic-font.min.css}'>
<!--核心样式-->
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
<div id="viewport">
<!-- Sidebar -->
<div id="sidebar" th:replace="waterMainMenu::sidebar">
</div>
<!-- Content -->
<div id="content">
<nav class="navbar navbar-default" th:replace="waterMainMenu::navbar">
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<table class="table table-hover table-striped">
<thead>
<tr>
<td>送水工名称</td>
<td>客户列表</td>
<td>送水数量</td>
</tr>
</thead>
<tbody>
<tr th:each="water : ${waterList}">
<td th:text="${water.workerName}"></td>
<td th:text="${water.custDetails}"></td>
<td th:text="${water.sendWaterCount}"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
2.2.8 修改主页面
<li>
<a th:href="@{/stat/statWaterDetails}">
<i class="zmdi zmdi-widgets"></i> 统计送水数量
</a>
</li>
3调整工资
进入“送水工列表”页面,点击“+”将工人工资增加100。然后弹出提示框“调整工资成功”。完成该功能前端需要使用jQuery
框架。
3.1 导入jQuery框架
<!--引入jQuery库-->
<script th:src="@{/webjars/jquery/3.3.1/jquery.js}"></script>
3.2 编写送水工页面
“送水工底薪”旁边添加+
和-
<!-- <td th:text="${worker.workerSalary}"></td>-->
<td>
<span id="add" th:class="${worker.wid}" >+</span>
<span id="salary" th:text="${worker.workerSalary}"></span>
<span id="sub" th:class="${worker.wid}">-</span>
</td>
3.3 为span标签设置样式
#add,#sub{
font-weight: bold;
font-size:20px;
color:red;
}
3.4 编写调整工资的前端代码
<script>
$(function(){
// 为“+”绑定“单击事件”
$("span[id=add]").click(function(){
// 获取送水工ID
let workerid =$(this).attr("class");
// 选择+下一个元素,获取送水工工资
let workerSalary = $(this).next().text();
// 计算调整之后的新工资
workerSalary = parseInt(workerSalary);
workerSalary+=100;
// 将新工资设置到页面
$(this).next().text(workerSalary);
// 使用ajax技术向后端发起调整工资的异步请求
$.ajax({
// 请求的URL
url:'/worker/adjustSalary',
// 提交给后端服务器的数据
data:{
wid:workerid,
workerSalary:workerSalary
},
// 请求的方式为“POST”
method:"POST",
// 请求成功后的回调函数
success:function(data,status) {
if(data =="OK") {
alert("调整工资成功");
} else {
alert("调整工资失败");
}
}
})
})
})
</script>
3.5 编写WorkerMapper接口
在WorkerMapper
接口新增加一个方法
/**
* 调整送水工工资,在原有基础上增加100
* @param wid 送水工ID
* @param workerSalary 送水工工资
* @return 受影响行数。大于0调整工资成功,否则调整工资失败
*/
int adjustSalary(@Param("wid")Integer wid,@Param("workerSalary")Integer workerSalary);
3.6 编写WorkerMapper映射文件
创建WorkerMapper.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">
<mapper namespace="com.shouyi.mapper.WorkerMapper">
<update id="adjustSalary" parameterType="int">
update tb_worker
set worker_salary = #{workerSalary}
where wid = #{wid}
</update>
</mapper>
3.7 编写Service接口
在WorkerService
接口新增加一个调整工资的方法
/**
* 调整工资
* @param wid 送水工ID
* @param workerSalary 送水工工资
* @return 受影响行数。大于0调整工资成功,否则调整工资失败
*/
int adjustSalary(Integer wid,Integer workerSalary);
3.8编写接口实现类
在WorkerServiceImpl
接口实现类覆写WorkerService
接口的adjustSalary
方法
/**
* 调整工资
*
* @param wid 送水工ID
* @param workerSalary 送水工工资
* @return 受影响行数。大于0调整工资成功,否则调整工资失败
*/
@Override
public int adjustSalary(Integer wid, Integer workerSalary) {
return workerMapper.adjustSalary(wid,workerSalary);
}
3.9 编写Controller控制器
在WorkerController
新增一个方法,专门用来处理"调增工资"的请求
/**
处理"调增工资"的请求
@ResponseBody表示将Java对象转换为json格式的数据渲染到前端页面
*/
@RequestMapping(value="/adjustSalary",method = RequestMethod.POST)
@ResponseBody
public String adjustWorkerSalary(Integer wid,Integer workerSalary) {
log.info("adjustWorkerSalary wid = "+wid);
log.info("adjustWorkerSalary workerSalary = "+workerSalary);
int rows = workerService.adjustSalary(wid, workerSalary);
log.info("adjustWorkerSalary rows = "+rows );
if (rows > 0) {
return "OK";
} else {
return "Fail";
}
}
1 批量删除历史信息
效果如下
批量删除用到的技术“批处理”。一次数据库连接执行多次delete语句
1.1 前端页面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title> XXX送水公司后台管理系统</title>
<!--Bootstrap固定框架-->
<link rel='stylesheet' th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap.css}">
<link rel='stylesheet' th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap-theme.css}">
<!--图标库-->
<link rel='stylesheet' th:href='@{/css/material-design-iconic-font.min.css}'>
<!--核心样式-->
<link rel="stylesheet" th:href="@{/css/style.css}">
<script th:src="@{/webjars/jquery/3.3.1/jquery.js}"></script>
<script>
$(function() {
// 为“selectAll”复选框绑定单击事件,一旦触发改时间,将下面的复选框“全选”or"全不选"
$("#selectAll").click(function(){
// $("input[name=chkHistory]")表示选择“历史列表”所有的复选框,如果状态是“未选中”,将其
// 设置为“选中”,如果状态是“已选择”,将其设置为“未选中”
$("input[name=chkHistory]").prop("checked",$(this).prop("checked"))
})
/**
* 为“批量删除”按钮绑定“单击”事件,将所有选中“的复选框”对应的“送水历史信息”批量删除
* 步骤:
* 1 获取所有“选中”的复选框
* 2 如果一个都没选中,给出提示信息“至少选择一项”,结束操作。
* 3 获取每个“选中”复选框的value值,进行拼接。将它作为参数传递给后端
* 4 使用异步“Ajax”技术向后端发起请求进行“批量删除”
* */
$("#batchDelete").click(function () {
// 获取所有“已选中”的复选框
let checkedList = $("input[name=chkHistory]:checked");
if (checkedList.length == 0) {
alert("请至少选择一项");
return;
}
// 存储所有已选中复选框的value属性值
let ids ="";
// 遍历每一个已选中复选框
$(checkedList).each(function () {
// 获取复选框的value属性值
let hid = $(this).val();
ids += ","+hid;
})
// 使用
$.ajax({
url:'/history/batchDelete',
data:{
idList:ids
},
method:'POST',
success:function(data,status){
if (data == "OK") {
alert("批量删除成功");
window.location.href='/history/listHis';
} else {
alert("批量删除失败");
}
}
})
})
})
</script>
</head>
<body>
<div id="viewport">
<!-- Sidebar -->
<div id="sidebar" th:replace="waterMainMenu::sidebar">
</div>
<!-- Content -->
<div id="content">
<nav class="navbar navbar-default" th:replace="waterMainMenu::navbar">
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="col-md-4">
<a class="btn btn-primary"
th:href="@{/history/preSaveHis}">添加</a>
</div>
<div class="col-md-4">
<a class="btn btn-danger" id="batchDelete"
href="#">批量删除</a>
</div>
</div>
<div class="col-md-12">
<table class="table table-hover table-striped">
<thead>
<tr>
<td>
<input type="checkbox" id="selectAll" value="selectAll"/>全选/全不选
</td>
<td>送水历史编号</td>
<td>送水工名称</td>
<td>客户名称</td>
<td>送水时间</td>
<td>送水数量</td>
<td>操作</td>
</tr>
</thead>
<tbody>
<tr th:each="his : ${hisList}">
<td>
<input type="checkbox" name="chkHistory" th:value="${his.hid}"/>
</td>
<td th:text="${his.hid}"></td>
<td th:text="${his.worker.workerName}"></td>
<td th:text="${his.customer.custName}"></td>
<td th:text="${#dates.format(his.sendWaterTime,'yyyy-MM-dd')}"></td>
<td th:text="${his.sendWaterCount}"></td>
<td>
<a class="glyphicon glyphicon-edit"
th:href="@{'/history/preUpdateHis/'+${his.hid}}"></a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
2.2Mapper接口
/**
* 批量删除历史信息
* @param ids 要删除的历史id。例如:1,2,3,5,6,7
* @return 大于0批量删除成功,否则批量删除失败
*/
int batchDeleteHistory(@Param("ids") List<Integer> ids);
2.3 Mapper映射文件
<delete id="batchDeleteHistory" parameterType="list">
delete from history where hid in
<!--
collection里面的值必须是一个集合类型
注意:idList必须跟Mapper接口的@Param("idList")一致
item里面的值表示集合的元素
separator表示分隔符,多个id值使用半角逗号隔开
-->
<foreach collection="ids" item="hid" open="(" close=")" separator=",">
#{hid}
</foreach>
</delete>
2.4Service接口
/**
* 批量删除
* @param idList 送水历史编号
* @return 大于0删除成功,否则删除失败
*/
int batchDeleteHistory(String idList);
2.5Service实现类
/**
* 批量删除
* 步骤:
* 1 字符串id转换为List集合
* 2 调用Mapper的批量删除方法
* @param ids 送水历史编号。例如:1,2,3,5,6,7
* @return 大于0删除成功,否则删除失败
*/
public int batchDeleteHistory(String idList) {
List<Integer> ids = new ArrayList<>();
// 使用逗号作为分割器,切割字符串。然后转换为数组
String[] split = StrUtil.split(idList, ",");
for (String id : split) {
if (StrUtil.isNotEmpty(id)) {
ids.add(Integer.parseInt(id));
}
}
return historyMapper.batchDeleteHistory(ids);
}
2.6Controller控制器
/**
处理批量删除
*/
@RequestMapping(value = "/batchDelete",method = RequestMethod.POST)
@ResponseBody
public String batchDeleteHistory(String idList) {
log.info("batchDeleteHistory idList = "+idList);
int rows = historyService.batchDeleteHistory(idList);
if(rows > 0) {
return "OK";
} else {
return "Fail";
}
}
2SpringBoot事务
此前,我们主要通过XML配置Spring来托管事务。在SpringBoot则非常简单,只需在业务层添加事务注解(@Transactional )即可快速开启事务。接下来我们使用事务管理批量删除
2.1 Service实现类
/**
* 批量删除
* 步骤:
* 1 字符串id转换为List集合
* 2 调用Mapper的批量删除方法
* @param ids 送水历史编号。例如:1,2,3,5,6,7
* @return 大于0删除成功,否则删除失败
rollbackFor:触发回滚的异常。此时发生Exception异常或者它的子类就会触发事务回滚。
*/
@Transactional(rollbackFor = {Exception.class,Error.class})
@Override
public int batchDeleteHistory(String ids) {
// 第一个,替换为null
ids =ids.replaceFirst(",","");
String[] split = StrUtil.split(ids, ",");
List<Integer> idList = new ArrayList<>();
for (String id : split) {
idList.add(Integer.parseInt(id));
}
int rows = historyMapper.deleteBatchHistory(idList);
// 模拟执行失败,出现异常
System.out.println(1/0);
return rows;
}
2.2 Controller 控制器
Service业务逻辑层发生异常,Controller在catch块回滚事务
@RequestMapping(value="/batchDelete",method = RequestMethod.POST)
@ResponseBody
public String batchDeleteHistory(String idList)
if(log.isInfoEnabled()) {
log.info("deleteBatchHistory ids = "+ ids);
}
try {
int rows = historyService.deleteBatchHistory(ids);
if(rows > 0) {
return "OK";
} else {
return "fail";
}
} catch (Exception e){
log.error("删除失败...回滚事务..",e);
return "fail";
}
}
2.3 事务的传播行为
事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。org.springframework.transaction.annotation.Propagation
枚举类中定义了7个表示传播行为的枚举值:
public enum Propagation {
// 默认的事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
REQUIRED(0),
// 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
SUPPORTS(1),
// 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
MANDATORY(2),
// 创建一个新的事务,如果当前存在事务,则把当前事务挂起
REQUIRES_NEW(3),
// 以非事务方式运行,如果当前存在事务,则把当前事务挂起
NOT_SUPPORTED(4),
// 以非事务方式运行,如果当前存在事务,则抛出异常
NEVER(5),
// 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED
NESTED(6);
}
ollbackFor:触发回滚的异常。此时发生Exception异常或者它的子类就会触发事务回滚。
*/
@Transactional(rollbackFor = {Exception.class,Error.class})
@Override
public int batchDeleteHistory(String ids) {
// 第一个,替换为null
ids =ids.replaceFirst(“,”,“”);
String[] split = StrUtil.split(ids, “,”);
List idList = new ArrayList<>();
for (String id : split) {
idList.add(Integer.parseInt(id));
}
int rows = historyMapper.deleteBatchHistory(idList);
// 模拟执行失败,出现异常
System.out.println(1/0);
return rows;
}
#### 2.2 Controller 控制器
Service业务逻辑层发生异常,Controller在catch块回滚事务
```java
@RequestMapping(value="/batchDelete",method = RequestMethod.POST)
@ResponseBody
public String batchDeleteHistory(String idList)
if(log.isInfoEnabled()) {
log.info("deleteBatchHistory ids = "+ ids);
}
try {
int rows = historyService.deleteBatchHistory(ids);
if(rows > 0) {
return "OK";
} else {
return "fail";
}
} catch (Exception e){
log.error("删除失败...回滚事务..",e);
return "fail";
}
}
2.3 事务的传播行为
事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。org.springframework.transaction.annotation.Propagation
枚举类中定义了7个表示传播行为的枚举值:
public enum Propagation {
// 默认的事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
REQUIRED(0),
// 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
SUPPORTS(1),
// 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
MANDATORY(2),
// 创建一个新的事务,如果当前存在事务,则把当前事务挂起
REQUIRES_NEW(3),
// 以非事务方式运行,如果当前存在事务,则把当前事务挂起
NOT_SUPPORTED(4),
// 以非事务方式运行,如果当前存在事务,则抛出异常
NEVER(5),
// 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED
NESTED(6);
}