分页成果展示:www.qihea.xyz
1.整合mybatis框架
1.数据库准备好数据:我网站中的数据时通过python爬虫,直接保存到数据库中的。图片直接保存的豆瓣的url。
2.整合mybatis依赖和mybatis分页插件:
根据此教程完成初步操作:Kotlin+IDEA+SpringBoot+MyBatis+MySQL+Gradle项目(不要选择kotlin即可)
然后添加分页插件依赖:官网https://github.com/abel533/MyBatis-Spring-Boot
//mybatis分页插件
compile group: 'com.github.pagehelper', name:'pagehelper-spring-boot-starter',version: '1.2.10'
推荐阅读:(读不读都行)
1.为什么使用mybatis
2.Mybatis配置之配置元素详述
3.mybatis使用
2.结合mybatis分页插件编写动态sql实现分页功能。
有三种分页方式:JavaWeb分页显示内容之分页查询的三种思路(数据库分页查询)。我选择的是后台分页。
1.约定好url接口(REST风格)
因为使用mybatis分页插件直接得到的是json数据,所以我直接使用接口来访问和传递数据。如果不想用接口的话,也可以使用模版等方式,不过我就不说了。
使用接口后,前端可以直接通过ajax来通过rest风格接口获取数据。
示例:
- 如果什么都不选择http://www.qihea.xyz/page?size=1&start=0&sort=0:
- {"total":4867,"list":[{"id":1292052,"name":"肖申克的救赎","score":9.6,"url":"https://img3.doubanio.com/view/photo/s_ratio_poster/public/p480747492.jpg"}],"pageNum":0,"pageSize":1,"size":1,"startRow":1,"endRow":1,"pages":4867,"prePage":0,"nextPage":1,"isFirstPage":false,"isLastPage":false,"hasPreviousPage":false,"hasNextPage":true,"navigatePages":8,"navigatepageNums":[1,2,3,4,5,6,7,8],"navigateFirstPage":1,"navigateLastPage":8}
- 比如选择:剧情分类(genres=11),地区为中国(place=中国),年代为2018(beginyear和endyear都为2018),排序为最热电影(sort=0),第一页(start=1),每页最多条目(size=10)来发送url请求。
- http://www.qihea.xyz/page?size=10&start=1&sort=0&genres=11&place=%E4%B8%AD%E5%9B%BD%E5%A4%A7%E9%99%86&beginyear=2018&endyear=2018
{"total":6,"list":[{"id":26752088,"name":"我不是药神","score":9.0,"url":"https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2519070834.jpg"},{"id":26425063,"name":"无双","score":8.2,"url":"https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2535096871.jpg"},{"id":26647117,"name":"暴裂无声","score":8.2,"url":"https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2517333671.jpg"},{"id":27110296,"name":"无名之辈","score":8.3,"url":"https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2539661066.jpg"},{"id":25716096,"name":"狗十三","score":8.5,"url":"https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2540474819.jpg"},{"id":30206517,"name":"切尔诺贝利之春","score":8.0,"url":"https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2520682504.jpg"}],"pageNum":1,"pageSize":10,"size":6,"startRow":1,"endRow":6,"pages":1,"prePage":0,"nextPage":0,"isFirstPage":true,"isLastPage":true,"hasPreviousPage":false,"hasNextPage":false,"navigatePages":8,"navigatepageNums":[1],"navigateFirstPage":1,"navigateLastPage":1}
2.使用mybatis pagehelper分页插件。
我直接给出我的流程:
1.编写controller层(根据之前设计好的接口来)
@RestController
public class CategoryController {
@Autowired
private CategoryService categoryService;
@GetMapping("/page")
public PageInfo<MovieShowBean> page(@RequestParam(value = "start",defaultValue = "1") String start,
@RequestParam(value = "size",defaultValue = "6") String size,
@RequestParam(value = "genres",required=false) String genres ,
@RequestParam(value = "sort",defaultValue = "0") String sort,
@RequestParam(value = "beginyear",required=false) String beginyear,
@RequestParam(value = "endyear",required=false) String endyear,
@RequestParam(value = "place",required=false) String place
){
Map<String,String> myMap= new HashMap<>(7);
myMap.put("start",start);
myMap.put("size",size);
myMap.put("genres",genres);
myMap.put("sort",sort);
myMap.put("beginyear",beginyear);
myMap.put("endyear",endyear);
myMap.put("place",place);
return categoryService.homePage(myMap);
}
}
2.编写service层:
在执行sql前添加插件,完成分页功能:参考【MyBatis】MyBatis分页插件PageHelper的使用
我们知道,在MySQL中,分页的sql是使用limit来做,如果我们自己写sql,那分页肯定是没有任何问题的。但是一旦model多了起来,复杂了起来,我们很自然的想到使用mybatis的逆向工程来生成相应的po和mapper,但是同时也会带来弊端,比如这里的分页问题就不好解决了。 所以还是选择使用分页插件。
在查询的sql语句执行之前,添加一行代码PageHelper.startPage(x,y);
第一个参数表示第几页,第二个参数表示每页显示的记录数。这样在执行sql后就会将记录按照语句中设置的那样进行分页。如果需要获取总记录数的话,需要PageInfo类的对象,这个对象可以获取总记录数,下面看下测试的代码。
public interface CategoryService {
PageInfo homePage(Map<String,String> map);
}
@Service
public class CategoryServiceImpl implements CategoryService {
@Resource
private MovieShowBeanMapper movieShowBeanMapper;
@Resource
private CategoryMapper categoryMapper;
@Override
public PageInfo homePage(Map<String, String> myMap) {
//设置分页参数
PageHelper.startPage(Integer.valueOf(myMap.get("start")),Integer.valueOf(myMap.get("size")));
List<MovieShowBean> list=movieShowBeanMapper.findShowMovie(myMap.get("genres"),
myMap.get("sort"),
myMap.get("beginyear"),
myMap.get("endyear"),
myMap.get("place"));
//使用对象包装返回结果
return new PageInfo(list);
}
}
3.编写dao层(或者说是mapper)
因为需要根据选择来判断是否添加限定条件,所以必须是动态sql。
因为注解方式很简单,所以我直接使用注解的方式完成操作。
参考阅读:mybatis的注解开发之三种动态sql
MyBatis框架核心之(五)注解使用resultMap及多表查询
@SelectProvider(type = MovieShowSqlProvider.class, method = "selectShow"):使用MovieShowSqlProvider.class这个类中的方法selectShow返回的sql字符串 作为查询的语句。
@Results注解:结果映射的列表, 包含了一个特别结果列如何被映射到属性或字段的详情。属性:value, id。value 属性是 Result 注解的数组。这个id的属性是结果映射的名称可以省略。
@Result注解:column为数据库字段名,porperty为实体类属性名,jdbcType为数据库字段数据类型,id为是否为主键。
public interface MovieShowBeanMapper {
@SelectProvider(type = MovieShowSqlProvider.class, method = "selectShow")
@Results(value = {
@Result( id=true, property = "id",column = "id"),
@Result(property = "name",column = "name"),
@Result(property = "score",column = "score")
})
List<MovieShowBean> findShowMovie(
@Param("genres") String genres ,
@Param("sort") String sort,
@Param("beginyear") String beginyear,
@Param("endyear") String endyear,
@Param("place") String place
);
}
4.编写mapper中用到的动态sql:MovieShowSqlProvider类
然后在这个类中进行sql语句的拼接
tempSql=new SQL().SELECT("movie_id").FROM("class_contact");
因为是分页查询,使用的是子查询的方式
public class MovieShowSqlProvider {
/*完整的sql语句
SELECT m.id,m.name,m.time from movie m WHERE id in (
SELECT movie_id from place WHERE name ="中国大陆"
and movie_id in
(SELECT movie_id from class_contact WHERE class_id=11)
) and YEAR(m.time)>1995 and YEAR(m.time)<=2018
ORDER BY(YEAR(m.time)) desc*/
public String selectShow(Map<String,Object> map){
SQL tempSql=null;
String order="order by m.comment_num";
//1.是否有分类
if(map.get("genres") != null){
tempSql=new SQL().SELECT("movie_id").FROM("class_contact");
if(map.get("place") != null){
SQL tempSql2=new SQL().SELECT("movie_id").FROM("place")
.WHERE("name=#{place}");
tempSql.WHERE("movie_id in("+tempSql2+")")
.WHERE("class_id=#{genres}");
}
else{
tempSql.WHERE("class_id=#{genres}");
}
}
else{
if(map.get("place") != null){
tempSql=new SQL().SELECT("movie_id").FROM("place")
.WHERE("name=#{place}");
}
}
//判断排序方式
SQL finalTempSql = tempSql;
return new SQL(){
{
SELECT("m.id,m.name,m.score,m.url");
FROM("movie m");
if(finalTempSql !=null)
WHERE("m.id in("+finalTempSql+")");
if(map.get("beginyear") != null)
WHERE("YEAR(m.time)>=#{beginyear}");
if(map.get("endyear") != null)
WHERE("YEAR(m.time)<=#{endyear}");
//排序
if(map.get("sort").equals("1")){
ORDER_BY("m.time desc");
}
else if(map.get("sort").equals("2")){
ORDER_BY("m.score desc");
}
else{
ORDER_BY("m.comment_num desc");
}
}
}.toString();
}
}
好啦,单元测试自己写喽。还有注意写异常处理。(我暂时还不会写)
相关阅读:
4.Springboot+gradle+Mybatis-Generator
5.简介如何使用MyBatis generator生成的Example文件
逆向工程可用可不用,对我工程来说是没必要的。
3.前端发送请求来获取数据。
直接通过点按钮击触发参数,通过参数发送get请求,参数放在里面。
不过因为不太清楚get请求,我直接使用的拼接url来发送get请求。
然后前端通过js进行数据处理,我听说前端也有数据处理插件,不过没有查询资料。感兴趣的可以查一查。
//请求json
function sendurl() {
//先构造基础部分
baseUrl="/page?size="+size+"&start="+start+"&sort="+sort;
if(genres!=""){
baseUrl+="&genres="+genres;
}
if(place!=""){
baseUrl+="&place="+place;
}
if(beginyear!=""){
baseUrl+="&beginyear="+beginyear;
}
if(endyear!=""){
baseUrl+="&endyear="+endyear;
}
$.ajax({
//访问地址
url: baseUrl,
//访问方式,一般有GET或POST两种
type: 'GET',
//返回的数据格式,这个是可选参数,jquery回默认判断返回参数的类型
dataType: 'json'
/*//发送的数据
data: {
param1: 'value1',
param2: 'value2'
},*/
})
//成功后的处理函数,res为服务器返回的数据
.done(function(data) {
//删除当前页面显示:
$(".want_remove").remove();
toastr.success('获取数据成功:ヾ(o◕∀◕)ノヾ');
//因为设置了type,返回的就是json对象
//构造电影模块
//背景变量
var myback=["bg-danger","bg-primary","bg-success","bg-info","bg-secondary"]
var lineNum=size/2;
$.each(data.list,function(idx,item) {
myscore=Math.round(item.score/2);
var myn=idx%5;
var star="";
for(i=0;i<myscore;i++){
star+="⭐";
}
if(idx<lineNum){
$("#first_row").append(
"<div class=\"flex-fill justify-content-center text-center want_remove\">\n" +
" <div class=\"card "+myback[myn]+" text-white\" style=\"width:117px;margin: auto;cursor:pointer;\" onclick=\"window.open('https://movie.douban.com/subject/"+item.id+"/','_blank');\">\n" +
" <img class=\"card-img-top\" src=\""+item.url+"\"\n" +
" alt=\"Card image\" style=\"width:100%\">\n" +
" <div style=\"font-size: 14px ;\">\n" +
" <p class=\"shenglue\">"+item.name+"</p>\n" +
" <p class=\"shenglue\" style=\"color: yellow; font-size: 13px;\">"+star+item.score+"</p>\n" +
" </div>\n" +
" </div>\n" +
" </div>"
);
}
else{
$("#second_row").append(
"<div class=\"flex-fill justify-content-center text-center want_remove\">\n" +
" <div class=\"card "+myback[myn]+" text-white\" style=\"width:117px;margin: auto;cursor:pointer;\"onclick=\"window.open('https://movie.douban.com/subject/"+item.id+"/','_blank');\">\n" +
" <img class=\"card-img-top\" src=\""+item.url+"\"\n" +
" alt=\"Card image\" style=\"width:100%\">\n" +
" <div style=\"font-size: 14px ;\">\n" +
" <p class=\"shenglue\">"+item.name+"</p>\n" +
" <p class=\"shenglue\" style=\"color: yellow; font-size: 13px;\">"+star+item.score+"</p>\n" +
" </div>\n" +
" </div>\n" +
" </div>"
);
}
})
// $(".want_remove").fadeIn("slow");
//构造分页栏
//显示基本信息
$("#myresult").text("共"+data.total+"部;"+data.pages+"页");
//删除之前的标签
$(".can_remove").remove();
//设置标签页
/*for(i=0;i<data.navigatePages;i++){*/
$.each(data.navigatepageNums,function(i,item) {
if (item==data.pageNum) {
$("#list_next").before(
"<li id=\"mylist_" + i + "\" class=\"page-item active can_remove\" onclick='clickList(this.id)'><a class=\"page-link\" href=\"javascript:void(0)\">" + item + "</a></li>"
);
}
else{
$("#list_next").before(
"<li id=\"mylist_" + i + "\" class=\"page-item can_remove\" onclick='clickList(this.id)'><a class=\"page-link\" href=\"javascript:void(0)\">" + item+ "</a></li>"
);
}
})
//如果前一页不存在
if(!data.hasPreviousPage)
$("#list_pre").addClass("disabled");
else{
$("#list_pre").removeClass("disabled");
}
//如果后一页不存在
if(!data.hasNextPage)
$("#list_next").addClass("disabled");
else{
$("#list_next").removeClass("disabled");
}
//分别是数组
console.log("success");
})
//失败后的处理函数
.fail(function() {
toastr.error('Error:肯定不是服务器炸了ヾノ≧∀≦)o死开!');
console.log("error");
})
//结束后的处理函数,不管成功失败都执行
/*.always(function(res) {
console.log("complete");
});*/
//get不能判断是否失败
/*$.get(baseUrl,function(result){
alert(result);
});*/
}
最终完成喽。