创建项目,引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zh</groupId>
<artifactId>bill-manager-tk</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!-- 通用mapper -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
spring boot 启动器
注意使用tk-mybatis 的MapperScan
@SpringBootApplication
@MapperScan("com.zh.dao")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
application.yml:springboot全局配置文件
# 连接池
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password:
url: jdbc:mysql://localhost:3306/bill-manager
thymeleaf:
cache: false #关闭thymeleaf缓存
#整合mybatis
mybatis:
type-aliases-package: com.zh.bean #别名搜索
mapper-locations: classpath:/mappers/*.xml
实体类
账单类型
@Table(name = "bill_type_")
@Data
public class BillType implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id_")
private Long id;
@Column(name = "name_")
private String name;
}
账单
@Table(name = "bill_")
@Data
public class Bill implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id_")
private Long id;
@Column(name = "title_")
private String title;
@Column(name = "bill_time_")
private Date billTime;
@Column(name = "type_id_")
private Long typeId;
@Column(name = "price_")
private Double price;
@Column(name = "explain_")
private String explain;
/**
* 类别名称
*/
@Transient
private String typeName;
/**
* 开始时间:用于查询
*/
@Transient
private Date date1;
/**
* 结束时间:用于查询
*/
@Transient
private Date date2;
}
注意 @Transient注解表示当前属性为瞬时属性,跟字段没有映射,上面实体类中date1和date2只作为查询条件存
在,跟表中的字段没有关联
dao
public interface BillMapper extends Mapper<Bill> {
List<Bill> select(Bill b);
}
public interface TypeMapper extends Mapper<BillType> {
}
查询修改的映射文件
tk mybatis内置查询比较单一固定,不灵活,不能实现动态查询,动态修改语句, 所以特定需求的查询需要写映射
文件
<?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.zh.dao.BillMapper">
<sql id="selectSql">
SELECT
b.id_ as id,
b.title_ as title,
b.bill_time_ as billTime,
b.type_id_ as typeId,
b.price_ as price,
b.explain_ as `explain`,
t.name_ as typeName
FROM
bill_ as b
left join
bill_type_ as t
on
b.type_id_ = t.id_
</sql>
<select id="select" resultType="bill">
<include refid="selectSql"/>
<where>
<if test="typeId !=null">
b.type_id_ = #{typeId}
</if>
<if test="title !=null">
and b.title_ like '%${title}%'
</if>
<if test="date1 !=null">
and b.bill_time_ >= #{date1}
</if>
<if test="date2 !=null">
and b.bill_time_ <= #{date2}
</if>
</where>
</select>
</mapper>
注意:explain字段名在mysql 中是关键词必须使用``包裹
service
@Service
public class BillService {
@Resource
private BillMapper billMapper;
public List<Bill> list(Bill b) {
return billMapper.select(b);
}
public int add(Bill b) {
return billMapper.insert(b);
}
public Bill get(Long id) {
return billMapper.selectByPrimaryKey(id);
}
public int update(Bill b) {
return billMapper.updateByPrimaryKey(b);
}
public int delete(Long id) {
return billMapper.deleteByPrimaryKey(id);
}
}
@Service
public class TypeService {
@Resource
private TypeMapper typeMapper;
public List<BillType> list() {
return typeMapper.selectAll();
}
}
测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class BillServiceTest {
@Autowired
private BillService billService;
@Test
public void list() {
Bill b = new Bill();
b.setTitle("a");
List<Bill> list = billService.list(b);
for (Bill bill : list) {
System.out.println(bill.getTitle());
}
}
}
controller
package com.zh.controller;
import com.zh.bean.Bill;
import com.zh.bean.BillType;
import com.zh.service.BillService;
import com.zh.service.TypeService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.Resource;
import java.util.List;
/**
* @Author: deemoHui
* @Description:
* @Date Created in 2020-10-19 13:05
* @Modified By:
*/
@Controller
@RequestMapping("/bill")
public class BillController {
@Resource
private TypeService typeService;
@Resource
private BillService billService;
/**
* 查询
*
* @param b
* @param model
* @return
*/
@RequestMapping("/list")
public String list(Bill b, Model model) {
List<BillType> types = typeService.list();
model.addAttribute("types", types);
List<Bill> list = billService.list(b);
model.addAttribute("list", list);
model.addAttribute("bill", b);
return "/bill/list";
}
/**
* 调到添加页面
*
* @return
*/
@RequestMapping("/toAdd")
public String toAdd(Model model) {
List<BillType> types = typeService.list();
model.addAttribute("types", types);
return "/bill/add";
}
/**
* 添加
*
* @param b
* @return
*/
@RequestMapping("/add")
public String add(Bill b) {
billService.add(b);
return "redirect:/bill/list";
}
/**
* 删除
*
* @param id
* @return
*/
@RequestMapping("/delete/{id}")
public String delete(@PathVariable("id") Long id) {
billService.delete(id);
return "redirect:/bill/list";
}
/**
* 修改
*
* @param id
* @return
*/
@RequestMapping("/toUpdate/{id}")
public String toUpdate(@PathVariable("id") Long id, Model model) {
List<BillType> types = typeService.list();
model.addAttribute("types", types);
Bill bill = billService.get(id);
model.addAttribute("bill", bill);
return "/bill/update";
}
/**
* 修改
*
* @param b
* @return
*/
@RequestMapping("/update")
public String update(Bill b) {
billService.update(b);
return "redirect:/bill/list";
}
}
页面
拷贝素材中我们用到的bootstrap.css样式和jquery和my97日历控件相关静态资源到/resources/static目录
list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>userList</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.css}"></link>
<script type="text/javascript" th:src="@{/js/My97DatePicker/WdatePicker.js}"></script>
<script type="text/javascript" src="/js/My97DatePicker/lang/zh-cn.js"></script>
<script type="text/javascript" th:src="@{/js/jquery/jquery-1.10.2.min.js}"></script>
</head>
<body class="container">
<br/>
<h1>账单列表</h1>
<br/><br/>
<div class="with:80%">
<!-- 查询数据-->
<form class="form-inline" id="qf" th:action="@{/bill/list}" method="post">
<div class="form-group">
<label for="typeId" class="control-label">类型</label>
<select name="typeId" id="typeId" class="form-control">
<option value="">全部</option>
<option th:each="t:${types}" th:value="${t.id}" th:text="${t.name}"
th:selected="(${bill.typeId} == ${t.id})"></option>
</select>
</div>
<div class="form-group">
<label for="date1" class="control-label">开始时间</label>
<input type="text" class="form-control" name="date1" id="date1" placeholder="开始时间" th:value="${bill.date1} ? ${#dates.format(bill.date1, 'yyyy-MM-dd')}"
onclick="WdatePicker()"/>
</div>
<div class="form-group">
<label for="date2" class="control-label">结束时间</label>
<input type="text" class="form-control" name="date2" id="date2" placeholder="结束时间" th:value="${bill.date2} ? ${#dates.format(bill.date2, 'yyyy-MM-dd')}"
onclick="WdatePicker()"/>
</div>
<div class="form-group">
<input type="submit" value="查询" class="btn btn-info"/>
<input type="reset" value="重置" class="btn btn-info"/>
<a href="/bill/toAdd" th:href="@{/bill/toAdd}" class="btn btn-info">添加</a>
</div>
</form>
</div>
<br/>
<div class="with:80%">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>#</th>
<th>标题</th>
<th>时间</th>
<th>金额</th>
<th>类别</th>
<th>说明</th>
<th>操作</th>
</tr>
</thead>
<!-- 数据展示-->
<tbody>
<tr th:each="b, bstatus : ${list}" th:style="${bstatus.odd} ? 'background-color:#A3C6C8'">
<th scope="row" th:text="${bill.id}">id</th>
<td th:text="${b.title}">name</td>
<td th:text="${b.billTime} ? ${#dates.format(b.billTime, 'yyyy-MM-dd')}">time</td>
<td th:text="${b.price}">price</td>
<td th:text="${b.typeName}">typeName</td>
<td th:text="${b.explain}">explain</td>
<td>
<a th:href="|/bill/delete/*{b.id}|">删除</a>
<a th:href="|/bill/toUpdate/*{b.id}|">修改</a>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
add.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>user</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.css}"></link>
<script type="text/javascript" th:src="@{/js/My97DatePicker/WdatePicker.js}"></script>
<script type="text/javascript" src="/js/My97DatePicker/lang/zh-cn.js"></script>
</head>
<body class="container">
<br/>
<h1>添加账单</h1>
<br/><br/>
<div class="with:80%">
<form class="form-horizontal" action="/bill/add" method="post">
<div class="form-group">
<label for="typeId" class="col-sm-2 control-label">类型</label>
<div class="col-sm-10">
<select name="typeId" id="typeId" class="form-control">
<option th:each="t:${types}" th:value="${t.id}" th:text="${t.name}"></option>
</select>
</div>
</div>
<div class="form-group">
<label for="title" class="col-sm-2 control-label">标题</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="title" id="title" placeholder="标题"/>
</div>
</div>
<div class="form-group">
<label for="billTime" class="col-sm-2 control-label">日期</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="billTime" id="billTime" placeholder="日期"
onclick="WdatePicker()"/>
</div>
</div>
<div class="form-group">
<label for="price" class="col-sm-2 control-label">金额</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="price" id="price" placeholder="金额"/>
</div>
</div>
<div class="form-group">
<label for="explain" class="col-sm-2 control-label">说明</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="explain" id="explain" placeholder="说明"/>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<input type="submit" value="保存" class="btn btn-info"/>
<input type="reset" value="重置" class="btn btn-info"/>
</div>
</div>
</form>
</div>
</body>
</html>
update.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>user</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.css}"></link>
<script type="text/javascript" th:src="@{/js/My97DatePicker/WdatePicker.js}"></script>
<script type="text/javascript" src="/js/My97DatePicker/lang/zh-cn.js"></script>
</head>
<body class="container">
<br/>
<h1>修改账单</h1>
<br/><br/>
<div class="with:80%">
<form class="form-horizontal" th:action="@{/bill/update}" method="post"
th:object="${bill}">
<input type="hidden" name="id" th:value="*{id}"/>
<div class="form-group">
<label for="typeId" class="col-sm-2 control-label">类型</label>
<div class="col-sm-10">
<select name="typeId" id="typeId" class="form-control">
<option th:each="t:${types}" th:value="${t.id}" th:text="${t.name}"
th:selected="${t.id} == *{typeId}"></option>
</select>
</div>
</div>
<div class="form-group">
<label for="title" class="col-sm-2 control-label">标题</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="title" id="title" th:value="*{title}" placeholder="标题"/>
</div>
</div>
<div class="form-group">
<label for="billTime" class="col-sm-2 control-label">日期</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="billTime" id="billTime"
th:value="*{billTime} ? *{#dates.format(billTime, 'yyyy-MM-dd')}" placeholder="日期"
onclick="WdatePicker()"/>
</div>
</div>
<div class="form-group">
<label for="price" class="col-sm-2 control-label">金额</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="price" id="price" th:value="*{price}" placeholder="金额"/>
</div>
</div>
<div class="form-group">
<label for="explain" class="col-sm-2 control-label">说明</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="explain" id="explain" th:value="*{explain}" placeholder="说明"/>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<input type="submit" value="保存" class="btn btn-info"/>
<input type="reset" value="重置" class="btn btn-info"/>
</div>
</div>
</form>
</div>
</body>
</html>
注意在springboot中配置日期类型转换器
package com.zh.config;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Component
public class DateConverterConfig implements Converter<String, Date> {
private static final List<String> formarts = new ArrayList<>(4);
static {
formarts.add("yyyy-MM");
formarts.add("yyyy-MM-dd");
formarts.add("yyyy-MM-dd hh:mm");
formarts.add("yyyy-MM-dd hh:mm:ss");
}
@Override
public Date convert(String source) {
String value = source.trim();
if ("".equals(value)) {
return null;
}
if (source.matches("^\\d{4}-\\d{1,2}$")) {
return parseDate(source, formarts.get(0));
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {
return parseDate(source, formarts.get(1));
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")) {
return parseDate(source, formarts.get(2));
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")) {
return parseDate(source, formarts.get(3));
} else {
throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
}
}
/**
* 格式化日期
*
* @param dateStr String 字符型日期
* @param format String 格式
* @return Date 日期
*/
public Date parseDate(String dateStr, String format) {
Date date = null;
try {
DateFormat dateFormat = new SimpleDateFormat(format);
date = dateFormat.parse(dateStr);
} catch (Exception e) {
}
return date;
}
}
删除
<a th:href="|/bill/delete/*{b.id}|">删除</a>
分页
引入pageHelper分页插件的starter,注意:必须引入pagehelper的启动器,不能直接引入pagehelper
<!-- 分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
分页方法的测试
@Test
public void findPage() {
Bill b = new Bill();
b.setTitle("a");
PageInfo<Bill> page = PageHelper.startPage(2, 10).doSelectPageInfo(() -> {
billService.list(b);
});
page.getList().forEach(bill -> {
System.out.println(bill.getId() + " " + bill.getTitle());
});
System.out.println("总行数=" + page.getTotal());
System.out.println("当前页=" + page.getPageNum());
System.out.println("每页行数=" + page.getPageSize());
System.out.println("总页数=" + page.getPages());
System.out.println("起始行数=" + page.getStartRow());
System.out.println("是第一页=" + page.isIsFirstPage());
System.out.println("是最后页=" + page.isIsLastPage());
System.out.println("还有下一页=" + page.isHasNextPage());
System.out.println("还有上一页=" + page.isHasPreviousPage());
System.out.println("页码列表" + Arrays.toString(page.getNavigatepageNums()));
}
service中的分页方法
public PageInfo<Bill> listPage(Bill b, int pageNum, int pageSize) {
return PageHelper.startPage(pageNum, pageSize).doSelectPageInfo(() -> {
billMapper.select(b);
});
}
controller中的方法
/**
* 查询
*
* @param b
* @param model
* @return
*/
@RequestMapping("/list-page")
public String listPage(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
Bill b,
Model model) {
List<BillType> types = typeService.list();
model.addAttribute("types", types);
PageInfo<Bill> pageInfo = billService.listPage(b, pageNum, pageSize);
model.addAttribute("page", pageInfo);
model.addAttribute("bill", b);
return "/bill/list-page";
}
前端分页
页面部分分页逻辑示意图
list-page.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>userList</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.css}"></link>
<script type="text/javascript" th:src="@{/js/My97DatePicker/WdatePicker.js}"></script>
<script type="text/javascript" th:src="@{/js/jquery/jquery-1.10.2.min.js}"></script>
</head>
<body class="container">
<br/>
<h1>账单列表</h1>
<br/><br/>
<div class="with:80%">
<form class="form-inline" id="qf" th:action="@{/bill/list-page}" method="post">
<input type="hidden" name="pageNum" id="pageNum" th:value="${page.pageNum }"/>
<input type="hidden" name="pageSize" id="pageSize" th:value="${page.pageSize }"/>
<div class="form-group">
<label for="typeId" class="control-label">类型</label>
<select name="typeId" id="typeId" class="form-control">
<option value="">全部</option>
<option th:each="t:${types}" th:value="${t.id}" th:text="${t.name}"
th:selected="(${bill.typeId} == ${t.id})"></option>
</select>
</div>
<div class="form-group">
<label for="date1" class="control-label">开始时间</label>
<input type="text" class="form-control" name="date1" id="date1" placeholder="开始时间" th:value="${bill.date1} ? ${#dates.format(bill.date1, 'yyyy-MM-dd')}"
onclick="WdatePicker()"/>
</div>
<div class="form-group">
<label for="date2" class="control-label">结束时间</label>
<input type="text" class="form-control" name="date2" id="date2" placeholder="结束时间" th:value="${bill.date2} ? ${#dates.format(bill.date2, 'yyyy-MM-dd')}"
onclick="WdatePicker()"/>
</div>
<div class="form-group">
<input type="submit" value="查询" class="btn btn-info"/>
<input type="reset" value="重置" class="btn btn-info"/>
<a href="/bill/toAdd" th:href="@{/bill/toAdd}" class="btn btn-info">添加</a>
</div>
</form>
</div>
<br/>
<div class="with:80%">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>#</th>
<th>标题</th>
<th>时间</th>
<th>金额</th>
<th>类别</th>
<th>说明</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="b, bstatus : ${page.list}" th:style="${bstatus.odd} ? 'background-color:#A3C6C8'">
<th scope="row" th:text="${bill.id}">id</th>
<td th:text="${b.title}">name</td>
<td th:text="${b.billTime} ? ${#dates.format(b.billTime, 'yyyy-MM-dd')}">time</td>
<td th:text="${b.price}">price</td>
<td th:text="${b.typeName}">typeName</td>
<td th:text="${b.explain}">explain</td>
<td>
<a th:href="|/bill/toUpdate/${b.id}|">修改</a>
<a th:href="|/bill/download/${b.id}|">下载</a>
</td>
</tr>
</tbody>
</table>
</div>
<ul class="pagination">
<li>
<button class="btn btn-default" id="first">第一页</button>
</li>
<li>
<button class="btn btn-default" id="prev">上一页</button>
</li>
<li th:each="p:${page.navigatepageNums}">
<button class="btn btn-default" name="pn" th:text="${p}" th:disabled="(${p} == ${page.pageNum})"></button>
</li>
<li>
<button class="btn btn-default" id="next">下一页</button>
</li>
<li>
<button class="btn btn-default" id="last">最后页</button>
</li>
</ul>
<script th:inline="javascript">
/*<![CDATA[*/
$(function () {
//初始化变量
var pageNum = [[${page.pageNum}]]; //当前页
var pageCount = [[${page.pages}]];//最后页
var hasNextPage = [[${page.hasNextPage}]];//还有下一页
var hasPreviousPage = [[${page.hasPreviousPage}]];//还有上一页
$("#next").click(function () {
$("#pageNum").val(pageNum + 1);
$("#qf").submit();
});
$("#prev").click(function () {
$("#pageNum").val(pageNum - 1);
$("#qf").submit();
});
$("#first").click(function () {
$("#pageNum").val(1);
$("#qf").submit();
});
$("#last").click(function () {
$("#pageNum").val(pageCount);
$("#qf").submit();
});
//没有上一页
if (!hasPreviousPage) {
$("#prev").prop("disabled", true);
$("#first").prop("disabled", true);
}
//没有下一页
if (!hasNextPage) {
$("#next").prop("disabled", true);
$("#last").prop("disabled", true);
}
$("button[name='pn']").click(function () {
$("#pageNum").val($(this).html());
$("#qf").submit();
});
});
/*]]>*/
</script>
</body>
</html>
几个注意点:
1、如果不引入lombok那么实体类需要get和set,否则会报错,而引入lombok则需要添加@Data注解
2、如果使用通用的mapper方法,也就是说要使用tk-mybatis,那么application的mapperScan则需要import tk.mybatis.spring.annotation.MapperScan; 引入错误的话,会在那么通用方法调用哪里报错
3、项目JDK依赖修改
项目Maven修改为本地