使用SpringBoot+Redis+Solr完成多对多项目案例

利用SpringBoot的简单性和易集成特点,与内存服务器Redis和搜索服务器Solr集成完成项目案例,题目要求查询菜单记录需要从Solr中检索,并且高亮显示查询关键字,菜单使用的材料需要使用Redis内存服务器缓存。

案例要求:
1、安装Redis内存服务器
2、安装Solr全文检索服务器

使用SpringBoot+Redis+Solr完成多对多项目案例_第1张图片

本项目案例使用MyBatisPlus存储数据库,可以为开发者简化部分代码的编写,自动生成部分代码,并且完全兼容MyBatis,但MyBatisPlus的缺点是需要为每个实体类提供一个Mapper接口,Service接口和Service实现类。

POM.xml配置

<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0modelVersion>
    <groupId>com.testgroupId>
    <artifactId>Proj_FtlSolrRedisartifactId>
    <packaging>warpackaging>
    <version>0.0.1-SNAPSHOTversion>
    <name>Proj_FtlSolrRedis Maven Webappname>
    <url>http://maven.apache.orgurl>

    
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>1.5.6.RELEASEversion>
        <relativePath/>
    parent>

    <dependencies>
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>3.8.1version>
            <scope>testscope>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-aopartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-freemarkerartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.datagroupId>
            <artifactId>spring-data-solrartifactId>
            <version>2.1.11.RELEASEversion>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <optional>trueoptional>
            <scope>truescope>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.1.6version>
            <exclusions>
                <exclusion>
                    <groupId>com.alibabagroupId>
                    <artifactId>jconsoleartifactId>
                exclusion>
                <exclusion>
                    <groupId>com.alibabagroupId>
                    <artifactId>toolsartifactId>
                exclusion>
            exclusions>
        dependency>
        <dependency>
            <groupId>org.apache.tomcat.embedgroupId>
            <artifactId>tomcat-embed-jasperartifactId>
        dependency>
        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>jstlartifactId>
        dependency>
        <dependency>
            <groupId>taglibsgroupId>
            <artifactId>standardartifactId>
            <version>1.1.2version>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>

        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatisplus-spring-boot-starterartifactId>
            <version>1.0.5version>
        dependency>
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plusartifactId>
            <version>2.1.8version>
        dependency>
        <dependency>
            <groupId>tk.mybatisgroupId>
            <artifactId>mapperartifactId>
            <version>3.4.5version>
        dependency>
        

        
        <dependency>
            <groupId>com.github.pagehelpergroupId>
            <artifactId>pagehelper-spring-boot-starterartifactId>
            <version>1.1.2version>
        dependency>
        <dependency>
            <groupId>commons-beanutilsgroupId>
            <artifactId>commons-beanutils-coreartifactId>
            <version>1.8.3version>
        dependency>

    dependencies>
    <build>
        <finalName>Proj_FtlSolrRedisfinalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <configuration>
                    <fork>truefork>
                configuration>
            plugin>
        plugins>
    build>
project>

使用SpringBoot+Redis+Solr完成多对多项目案例_第2张图片
使用SpringBoot+Redis+Solr完成多对多项目案例_第3张图片

application.yml配置

#SpringBoot启动端口和项目路径
server:
  port: 8080
  context-path: /

#SpringMVC中JSP视图配置
spring:
  mvc:
    view:
      prefix: /WEB-INF/jsp/
      suffix: .jsp

#  freemarker:
#    charset: UTF-8
#    template-loader-path: /WEB-INF/ftl/
#    suffix: .ftl
#    content-type: text/html
#    settings:
#      number_format: '0.##'

#数据源配置      
  datasource:
    name: w1
    url: jdbc:mysql://127.0.0.1:3306/w1?useUnicode=true&characterEncoding=utf8
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    maxActive: 20
    initialSize: 1
    maxWait: 60000
    minIdle: 1

#Http编码配置    
  http:
    encoding:
      force: true
      charset: UTF-8
      enabled: true

#Rabbitmq消息服务器配置      
  rabbitmq:
    host: 192.168.25.10
    port: 5672
    username: admin
    password: admin
    virtual-host: /

# Redis数据库索引(默认为0)
  redis:
    database: 0
    host: 192.168.25.30
    port: 6379
    password: redis
    pool.max-active: 8
    pool.max-wait: -1
    pool.max-idle: 8
    pool.min-idle: 0
    timeout: 0

#Solr配置    
  data:
    solr:
      host: http://localhost:8984/solr/new_core

#Mybatis实体类配置    
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml


#pagehelper分页插件
pagehelper:
  helperDialect: mysql
  reasonable: true


#日志配置
logging:
  file: d:/springboot.log
  level:   
    com.gufang.oa.mapper: DEBUG

使用SpringBoot+Redis+Solr完成多对多项目案例_第4张图片

菜单实体类

package com.test.bean;

import java.io.Serializable;
import java.sql.Date;

import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;

//菜单表实体类
@TableName("t_menu")
public class MenuInfo implements Serializable{
    //定义主键策略
    @TableId(type=IdType.AUTO)
    private Integer id = null;
    private String name = null;
    private Date dt = null;
    private Float price = null;
    //定义非数据库字段,默认exist=true,代表是数据库字段
    @TableField(exist=false)
    private String tid = null;
    @TableField(exist=false)
    private String tname = null;

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Date getDt() {
        return dt;
    }
    public void setDt(Date dt) {
        this.dt = dt;
    }
    public Float getPrice() {
        return price;
    }
    public void setPrice(Float price) {
        this.price = price;
    }
    public String getTid() {
        return tid;
    }
    public void setTid(String tid) {
        this.tid = tid;
    }
    public String getTname() {
        return tname;
    }
    public void setTname(String tname) {
        this.tname = tname;
    }

}

菜单分类实体类

package com.test.bean;

import java.io.Serializable;
import java.sql.Date;

import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;

//分类表实体类
@TableName("t_menutype")
public class TypeInfo implements Serializable{
    @TableId
    private Integer id = null;
    private String name = null;
    @TableField(exist=false)
    private String checked = "";
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getChecked() {
        return checked;
    }
    public void setChecked(String checked) {
        this.checked = checked;
    }   
}

中间表实体类

package com.test.bean;

import java.io.Serializable;
import java.sql.Date;

import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;

//菜单与材料关联表,实现多对多关系
//MybatisPlus使用注解定义表名
@TableName("t_m2t")
public class M2TInfo implements Serializable{
    private Integer mid = null;
    private Integer tid = null;
    public Integer getMid() {
        return mid;
    }
    public void setMid(Integer mid) {
        this.mid = mid;
    }
    public Integer getTid() {
        return tid;
    }
    public void setTid(Integer tid) {
        this.tid = tid;
    }   
}

菜单Mapper接口

package com.test.mapper;

import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.test.bean.MenuInfo;

public interface MenuMapper extends BaseMapper<MenuInfo>{

}

菜单Mapper.xml文件



<mapper namespace="com.test.mapper.MenuMapper">

mapper>

菜单服务接口

package com.test.service;

import com.baomidou.mybatisplus.service.IService;
import com.test.bean.MenuInfo;

public interface IMenuService extends IService<MenuInfo>{

}

菜单服务实现类

package com.test.service.impl;

import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.test.bean.MenuInfo;
import com.test.mapper.MenuMapper;
import com.test.service.IMenuService;

@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper,MenuInfo>
    implements IMenuService
{

}

我们发现使用MyBatisPlus设计代码非常简洁,实现和集成几个接口便可提交数据库表的基本操作,用户也可以在Mapper.xml中定义SQL,在Mapper接口中实现SQL的方法,MyBatisPlus完全兼容MyBatis。唯一的缺点就是需要为每个实体类都定义一套接口和实现类。

分类表Mapper接口

package com.test.mapper;

import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.test.bean.MenuInfo;
import com.test.bean.TypeInfo;

public interface TypeMapper extends BaseMapper{

}

分类表服务接口

package com.test.service;

import com.baomidou.mybatisplus.service.IService;
import com.test.bean.MenuInfo;
import com.test.bean.TypeInfo;

public interface ITypeService extends IService{

}

分类表的服务实现类

package com.test.service.impl;

import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.test.bean.MenuInfo;
import com.test.bean.TypeInfo;
import com.test.mapper.MenuMapper;
import com.test.mapper.TypeMapper;
import com.test.service.IMenuService;
import com.test.service.ITypeService;

@Service
public class TypeServiceImpl extends ServiceImpl
    implements ITypeService
{

}

中间表的Mapper接口

package com.test.mapper;

import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.test.bean.M2TInfo;
import com.test.bean.MenuInfo;
import com.test.bean.TypeInfo;

public interface M2TMapper extends BaseMapper{

}

中间表的服务接口

package com.test.service;

import com.baomidou.mybatisplus.service.IService;
import com.test.bean.M2TInfo;
import com.test.bean.MenuInfo;
import com.test.bean.TypeInfo;

public interface IM2TService extends IService{

}

中间表的服务实现类

package com.test.service.impl;

import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.test.bean.M2TInfo;
import com.test.bean.MenuInfo;
import com.test.bean.TypeInfo;
import com.test.mapper.M2TMapper;
import com.test.mapper.MenuMapper;
import com.test.mapper.TypeMapper;
import com.test.service.IM2TService;
import com.test.service.IMenuService;
import com.test.service.ITypeService;

@Service
public class M2TServiceImpl extends ServiceImpl
    implements IM2TService
{

}

菜单Controller跳转类

package com.test.ctrl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.test.bean.M2TInfo;
import com.test.bean.MenuInfo;
import com.test.bean.TypeInfo;
import com.test.service.IM2TService;
import com.test.service.IMenuService;
import com.test.service.ITypeService;
import com.test.util.FreeMarkerUtil;
import com.test.util.PageUtil;
import com.test.util.RedisTool;
import com.test.util.ResultInfo;
import com.test.util.SolrTool;

@Controller
@CacheConfig(cacheNames="menu")
public class MenuCtrl {
    @Autowired
    private IMenuService menuServ;
    @Autowired
    private ITypeService typeServ;
    @Autowired
    private IM2TService m2tServ;
    @Autowired
    private SolrTool solr;
    @Autowired
    private RedisTool redis;

    /**
     * 通过数据库查询结果集数据,并且提供分页支持
     * @param page 当前页码
     * @param rows 每页显示记录数
     * @param name 查询的关键字
     * @return
     */
    private ModelAndView initListDataDb(Integer page,Integer rows,String name)
    {
        //如果没有定义分页参数,初始化
        if(page == null)
            page = 1;
        if(rows == null)
            rows = 4;
        if(name == null)
            name = "";
        ModelAndView mv = new ModelAndView();
        //定义MybatisPlus查询封装对象
        Wrapper wrapper = new EntityWrapper();
        //定义查询条件
        wrapper.like("name", "%"+name+"%");
        //定义分页对象
        Page p = new Page(page,rows);
        //查询分页对象
        Page pageObj = menuServ.selectPage(p,wrapper);
        List menuList = pageObj.getRecords();
        for(MenuInfo mi:menuList)
        {
            //定义MyBatisPlus查询封装对象
            Wrapper wrapper2 = new EntityWrapper();
            //定义查询条件
            wrapper2.eq("mid", mi.getId());
            //查询结果集
            List m2tLst = m2tServ.selectList(wrapper2);
            String tname = "";
            for(M2TInfo m2t:m2tLst)
            {
                TypeInfo ti = typeServ.selectById(m2t.getTid());
                tname = tname + ti.getName() + ",";
            }
            mi.setTname(tname);
        }
        //将查询结果集存放到ModelAndView对象中,以便前台页面读取
        mv.addObject("menuList", menuList);
        //定义返回的页面名称
        mv.setViewName("menu");
        //分页对应的URL
        String url = "/init?name="+name;
        //查询菜单记录总数
        Integer total = menuServ.selectCount(wrapper);
        PageUtil pu = new PageUtil(url,page,rows,total);
        //保存分页Html代码
        mv.addObject("pagediv", pu.toHtml());

        return mv;
    }

    /**
     * 从Solr中查询符合条件的数据
     * @param page 当前页码
     * @param rows 每页显示记录数
     * @param name 查询的关键字
     * @return
     */
    private ModelAndView initListDataSolr(Integer page,Integer rows,String name)
    {
        //如果没有定义分页参数,初始化
        if(page == null)
            page = 1;
        if(rows == null)
            rows = 4;
        if(name == null)
            name = "";
        String query = "*:*";//"name:"+name;
        if(name != null && !"".equals(name))
            query = "name:"+name;
        ModelAndView mv = new ModelAndView();
        //转化当前页码为开始记录数
        Integer starts = (page-1)*rows;
        //从Solr中查询符合条件的数据
        ResultInfo result = solr.queryList(MenuInfo.class, query, starts, rows, "name");      
        mv.addObject("menuList", result.getList());
        mv.setViewName("menu");

        String url = "/init?name="+name;
        Integer total = result.getTotal().intValue();
        PageUtil pu = new PageUtil(url,page,rows,total);
        mv.addObject("pagediv", pu.toHtml());

        return mv;
    }

    //初始化URL
    @RequestMapping("/init")
    public ModelAndView init(Integer page,Integer rows,String name)
    {
        return initListDataSolr(page,rows,name);
    }

    //显示菜单页面,传人菜单ID
    @RequestMapping("/addmenu")
    public ModelAndView addmenu(Integer mid)
    {
        MenuInfo mi = new MenuInfo();
        if(mid != null)
        {
            mi = menuServ.selectById(mid);
        }
        ModelAndView mv = new ModelAndView();
        //定义MyBatisPlus查询封装对象
        Wrapper wrapper = new EntityWrapper();
        //查询分类列表
        List typeList = typeServ.selectList(wrapper);
        if(mid != null)
        {
            //定义MyBatisPlus查询封装对象
            Wrapper wrapper2 = new EntityWrapper();
            //定义查询条件
            wrapper2.eq("mid", mid);
            List m2tLst = m2tServ.selectList(wrapper2);
            //循环赋值分类选中状态,对应前台CheckBox选中状态,可以简化前台代码处理
            for(TypeInfo ti:typeList)
            {
                for(M2TInfo m2t:m2tLst)
                {
                    if(ti.getId() == m2t.getTid())
                        ti.setChecked("checked");
                }
            }
        }
        //将查询结果集存放到ModelAndView对象中,以便前台页面读取
        mv.addObject("menu", mi);
        mv.addObject("typeList", typeList);
        //返回页面名称
        mv.setViewName("addmenu");
        return mv;
    }

    //定义菜单保存方法,从前台接收参数封装为MenuInfo对象
    @RequestMapping("/savemenu")
    public ModelAndView savemenu(HttpServletRequest req,MenuInfo mi)
    {
        //首先从包装对象中取出分类ID,中间以逗号分隔的字符串,如果执行保存操作后,此属性的值清空
        String tid = mi.getTid();
        //调用MyBatisPlus的保存与更新方法
        menuServ.insertOrUpdate(mi);
        //根据菜单ID,删除中间表数据
        Map delMap = new HashMap();
        delMap.put("mid", mi.getId());
        m2tServ.deleteByMap(delMap);

        String tname = "";
        if(tid != null)
        {
            //分割菜单分类ID
            String[] dim = tid.split(",");
            for(String tid2:dim)
            {
                //创建中间表对象
                M2TInfo m2t = new M2TInfo();
                m2t.setMid(mi.getId());
                m2t.setTid(Integer.parseInt(tid2));
                m2tServ.insert(m2t);
                //记录分类名称,中间以逗号分隔,最后为菜单对象赋值,前台页面方可显示分类名称
                TypeInfo ti = typeServ.selectById(tid2);
                tname = tname + ti.getName() + ",";
            }
        }
        //定义数据对象,一定需要定义ID键
        Map m = new HashMap();
        m.put("id",mi.getId());
        m.put("name",mi.getName());
        m.put("dt",mi.getDt());
        m.put("price",mi.getPrice());
        m.put("tname",tname);

        //生成静态页面
        FreeMarkerUtil.generateHtml(req.getServletContext(), "html", "menu.html", m);
        //数据添加到Solr
        solr.addMap(m);

        return initListDataSolr(1,4,null);
    }

    /**
     * 根据前台选中的菜单ID,删除菜单数据与中间表数据
     * @param ids
     * @return
     */
    @ResponseBody
    @RequestMapping("/deletemenu")
    public boolean deletemenu(Integer[] ids)
    {
        if(ids != null)
        {
            for(Integer id:ids)
            {
                menuServ.deleteById(id);
                Map delMap = new HashMap();
                delMap.put("mid", id);
                m2tServ.deleteByMap(delMap);

                solr.delete(id+"");
            }
        }
        return true;
    }
}

根据模板生成静态页面的工具类FreeMarkerUtil

package com.test.util;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletContext;

import freemarker.cache.FileTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.cache.URLTemplateLoader;
import freemarker.cache.WebappTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;

public class FreeMarkerUtil {

    /**
     * 此方法根据模板和数据在指定的目录生成静态页面,数据对象Map必须存在键值id,它对应的值是生成静态页面的文件名
     * @param servletContext  Servlet上下文对象,可以通过request获得
     * @param staticHtmlPath  生成静态网页的目录,它位于webapp目录下,通过浏览器地址可以访问到
     * @param template  freemarker模板名称,它必须位于webapp/ftl目录下
     * @param map  数据模型
     */
    public static void generateHtml(ServletContext servletContext,
            String staticHtmlPath,String template,Map map)
    {
        try
        {
            //声明配置对象
            Configuration conf = new Configuration(Configuration.VERSION_2_3_23);
            conf.setEncoding(Locale.getDefault(), "UTF-8");
            //声明模板加载器
            WebappTemplateLoader wtl = new WebappTemplateLoader(servletContext, "/ftl");
            //绑定到配置对象
            conf.setTemplateLoader(wtl);
            Template tmplt = conf.getTemplate(template);
            //根据模板与数据模型生成静态网页
            String path = servletContext.getRealPath("/"+staticHtmlPath);
            String file = path+"/"+map.get("id")+".html";
            FileOutputStream fos = new FileOutputStream(file);
            OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
            BufferedWriter bw = new BufferedWriter(osw);

            tmplt.process(map, bw);

            fos.close();
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
}

分页工具类

package com.test.util;

/**
* 处理分页的工具类
 * 每翻页都请求后台加载分页数据
 */
public class PageUtil {
    private Integer page = 1;//默认显示第一页
    private Integer rows = 4;//每页显示记录数
    private Integer total = null;//总行数
    private String url = null;//点击页码跳转url

    /**
     * 分页工具类构造方法
     * @param url  分页对应的URL,可以包含查询参数
     * @param page 当前页码
     * @param rows 每页显示的记录数
     * @param total 数据库表总行数
     */

    public PageUtil(String url,Integer page,Integer rows,Integer total)
    {
        this.url = url;
        this.page = page;
        this.rows = rows;
        this.total = total;
    }

    /**
    * 生成静态HTML分页代码片段,通过EL语言加载到JSP页面中
     */
    public String toHtml()
    {
        StringBuffer sb = new StringBuffer();
        //计算总页数
        int pages = 0;
        if(total % rows == 0)
            pages = total / rows;
        else
            pages = (total / rows) + 1;
        sb.append("
\r\n"); String firstUrl = null; if(url.indexOf("?")>0) firstUrl = url + "&page=1&rows="+rows; else firstUrl = url + "?page=1&rows="+rows; sb.append("首页\r\n"); String backUrl = null; if(url.indexOf("?")>0) backUrl = url + "&page="+(page==1?1:(page-1))+"&rows="+rows; else backUrl = url + "?page="+(page==1?1:(page-1))+"&rows="+rows; sb.append("上一页\r\n"); for(int i=1;i<=pages;i++) { String pageUrl = null; if(url.indexOf("?")>0) pageUrl = url + "&page="+i+"&rows="+rows; else pageUrl = url + "?page="+i+"&rows="+rows; if(i == page) sb.append(""+i+"\r\n"); else sb.append(""+i+"\r\n"); } String nextUrl = null; if(url.indexOf("?")>0) nextUrl = url + "&page="+(page==pages?pages:(page+1))+"&rows="+rows; else nextUrl = url + "?page="+(page==pages?pages:(page+1))+"&rows="+rows; sb.append("下一页\r\n"); String lastUrl = null; if(url.indexOf("?")>0) lastUrl = url + "&page="+pages+"&rows="+rows; else lastUrl = url + "?page="+pages+"&rows="+rows; sb.append("尾页\r\n"); sb.append("    第"+page+"/"+pages+"页\r\n"); sb.append("    共"+total+"条记录\r\n"); sb.append("
\r\n"
); return sb.toString(); } }

Redis的工具类提供两两个,一个适应代码缓存对象,一个适应使用注解缓存对象

使用代码缓存对象

package com.test.util;

import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisAccessor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;

@Service
public class RedisTool {
    //注入Redis模板对象
    @Autowired
    //@Qualifier("redisTemplate") 
    private RedisTemplate redis;

    //保存对象类型到Redis内存服务器
    public void saveObject(String key,Object obj)
    {
        redis.opsForValue().set(key,obj);
    }

    //根据Key获取Redis内存对象
    public Object getObject(String key)
    {
        return redis.opsForValue().get(key);
    }

    //保存Map对象到Redis内存服务器
    public void saveMap(String key,Map map)
    {
        redis.opsForHash().putAll(key,map);
    }

    //根据Key获取Redis Map对象
    public Map getMap(String key)
    {
        return redis.opsForHash().entries(key);
    }

    //保持List对象到Redis内存服务器
    public void saveList(String key,List list)
    {
        redis.opsForList().leftPushAll(key, list);
    }

    //根据Key获取Redis List对象
    public List getList(String key)
    {
        return redis.opsForList().range(key, 0, -1);
    }
}

使用注解缓存对象

package com.test.util;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * 通过注解使用Redis内存服务器
 */
@Configuration
@EnableCaching
public class RedisUtil extends CachingConfigurerSupport{

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.timeout}")
    private int timeout;

    /**
     * 生成Redis的键值,根据类名+方法名+参数值的形式
     * @return
     */
    @Bean(name = "keyGenerator")
    public KeyGenerator keyGenerator() {
        return new KeyGenerator(){
            @Override
            public Object generate(Object target, java.lang.reflect.Method method, 
                    Object... params) {
                StringBuffer sb = new StringBuffer();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for(Object obj:params){
                    if(obj != null)
                    {
                        sb.append(obj.toString());
                    }
                }
                return sb.toString();
            }
        };
    }

    /**
     * 生成CacheManager对象
     * @param redisTemplate
     * @return
     */
    @Bean 
    public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        cacheManager.setDefaultExpiration(10000);
        return cacheManager;
    }

    /**
     * 生成Redis内存管理模板,注解@Primary代表此对象是同类型对象的主对象,优先被使用
     * @param factory
     * @return
     */
    @Primary
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory){
        RedisTemplate template = new RedisTemplate();
        template.setKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(factory);
        setSerializer(template);
        template.afterPropertiesSet();
        return template;
    }

    /**
     * 序列化Redis的键和值对象
     * @param template
     */
    private void setSerializer(RedisTemplate template){
        @SuppressWarnings({ "rawtypes", "unchecked" })
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = 
            new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
     }
}

Solr数据管理工具类

package com.test.util;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * Solr管理的工具类,提供添加数据到Solr,删除Solr数据和查询Solr数据
 */
@Service
public class SolrTool {
    @Autowired
    private SolrClient client;

    /**
     * 根据记录ID删除Solr中数据
     * @param id
     */
    public void delete(String id)
    {

        try
        {
            client.deleteById(id.toString());
            UpdateResponse up = client.commit();
            System.out.println(up);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }

    /**
     * 删除Solr中全部数据
     */
    public void deleteAll()
    {
        try
        {
            SolrQuery q = new SolrQuery();
            q.set("q", "*:*");
            q.set("fl", "id");
            QueryResponse resp = client.query(q);
            SolrDocumentList lst = resp.getResults();
            for(SolrDocument doc:lst)
            {
                String id = (String)doc.getFieldValue("id");
                client.deleteById(id);

            }
            client.commit();
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }

    /**
     * 更新Solr中的数据,数据模型Map中必须包含id
     * @param map
     */
    public void update(Map map)
    {
        try
        {
            SolrInputDocument doc = new SolrInputDocument();
            doc.addField("id", map.get("id"));
            for(Object o:map.keySet())
            {
                String key = (String)o;
                if(!"id".equals(key))
                {
                    Map m = new HashMap();
                    m.put("set", map.get(key));
                    doc.addField(key, m);
                }
            }
            UpdateResponse resp = client.add(doc);
            client.commit();
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }

    /**
     * 添加数据对象Map到Solr
     * 如果Solr中没有Field域,自动创建这些Field
     * @param map
     */
    public void addMap(Map map)
    {
        try
        {
            SolrInputDocument doc = new SolrInputDocument();
            Set keys = map.keySet();
            for(String k:keys)
            {
                doc.addField(k, map.get(k));
            }

            client.add(doc);
            UpdateResponse up = client.commit();
            System.out.println(up);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }

    /**
     * 查询Solr中符合条件的数据并高亮显示
     * @param clz 查询的Solr记录转化为对象的类型
     * @param query 查询条件,以field:value的形式,可以包含and,or
     * @param starts 开始记录数
     * @param rows 查询的记录数
     * @param flCol 高亮显示的字段列表,以逗号分隔
     * @param  对象类型
     * @return
     */
    public  ResultInfo queryList(Class clz,String query,
            Integer starts,Integer rows,String flCol)
    {
        try
        {
            SolrQuery q = new SolrQuery();
            q.set("q",query);//定义查询条件,使用属性名:属性值的形式,查询全部使用*
            q.set("fl", "*");//定义返回属性列表,返回全部以*代替
            // 分页显示功能
            q.setStart(starts);
            q.setRows(rows);
            q.setHighlight(true);
            q.setHighlightSimplePre(""); 
            q.setHighlightSimplePost(""); 
            q.setParam("hl.fl", flCol); 
            QueryResponse response = client.query(q);
            String hl = response.getHighlighting().toString();
            Map>> hlMap = response.getHighlighting();
            SolrDocumentList list = response.getResults();
            System.out.println("total = "+list.getNumFound());
            List lst = getBeans(clz, list,flCol,hlMap);

            ResultInfo result = new ResultInfo();
            result.setList(lst);
            result.setTotal(list.getNumFound());
            return result;
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将查询的SolrDocument转换为对象
     * @param clazz 查询的Solr记录转化为对象的类型
     * @param solrDocumentList 记录列表
     * @param flCol 高亮显示的字段列表,以逗号分隔
     * @param hlMap 高亮显示字符串,需要解析
     * @param  对象类型
     * @return
     * @throws Exception
     */
    public final  List getBeans(Class clazz, 
            SolrDocumentList solrDocumentList,String flCol,
            Map>> hlMap) throws Exception{
        List list = new ArrayList();
        T t = null;
        for (SolrDocument solrDocument : solrDocumentList)
        {
            //反射出实例
            t = clazz.newInstance();
            //获取所有属性
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields)
            {
                Object val = solrDocument.get(field.getName());
                if(val != null)
                {
                    String val2 = val.toString();
                    if(val2.startsWith("[") && val2.endsWith("]"))
                        val2 = val2.substring(1,val2.length()-1);
                    if(field.getType() == java.sql.Date.class)
                    {
                        java.util.Date dt = new java.util.Date(val2);
                        val2 = new java.sql.Date(dt.getTime()).toString();
                    }
                    if(field.getType() == java.sql.Timestamp.class)
                    {
                        java.util.Date dt = new java.util.Date(val2);
                        val2 = new java.sql.Timestamp(dt.getTime()).toString();
                    }
                    if(flCol.indexOf(field.getName())>=0)
                    {
                        List lstHigh= hlMap.get(solrDocument.get("id")).get(field.getName());
                        if(lstHigh!=null && lstHigh.size()>0)
                            val2=lstHigh.get(0);
                    }
                    BeanUtils.setProperty(t, field.getName(), val2);
                }
            }
            list.add(t);
        }
        return list;

    }

    public final  T getBean(Class clazz, 
            SolrDocument solrDocument) throws Exception {
        //反射出实例
        T t = clazz.newInstance();
        //获取所有属性
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // 如果注解为默认的 采用此属性的name来从solr中获取值
            BeanUtils.setProperty(t, field.getName(), 
                    solrDocument.get(field.getName()));

        }
        return t;
    }

    public static void main(String[] args)
    {
        String name = "七匹狼短袖T恤";
        name = name.replaceAll("短袖", "短袖");
        System.out.println(name);
    }

}
package com.test.util;

import java.util.List;

/**
 * 查询Solr返回的对象,对象类型为T的集合,还包含Solr中符合条件记录总数
 * @param 
 */
public class ResultInfo {
    private List list = null;
    private Long total = null;

    public List getList() {
        return list;
    }
    public void setList(List list) {
        this.list = list;
    }
    public Long getTotal() {
        return total;
    }
    public void setTotal(Long total) {
        this.total = total;
    }   
}

SpringBoot启动类

package com.test.util;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@MapperScan("com.test.mapper")
@ComponentScan("com.test.ctrl,com.test.service.impl,com.test.util")
public class Starter {

    public static void main(String[] args) {
        SpringApplication.run(Starter.class, args);
    }

}

菜单页面

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>   

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" href="/js/base.css">
<script type="text/javascript" src="/js/jquery.min.js">script>
<title>菜单列表title>
<script>
    function checkall()
    {
        $('input[name="chkid"]').each(function(){
            if(this.checked)
                this.checked = false;
            else
                this.checked = true;
        });
    }
    function doadd()
    {
        window.open('/addmenu','_self');
    }
    function doedit(id)
    {
        window.open('/addmenu?mid='+id,'_self');
    }
    function dodeletes()
    {
        var ids = '';
        $('input[name="chkid"]:checked').each(function(){
            ids = ids + this.value + ',';
        });
        $.ajax({
            url:'/deletemenu?ids='+ids,
            success:function(data){
                window.open('/init','_self');
            }
        })
    }
    function dodelete(id)
    {
        $.ajax({
            url:'/deletemenu?ids='+id,
            success:function(data){
                window.open('/init','_self');
            }
        })
    }
    function doquery()
    {
        var q = $('#query').val();
        window.open('/init?name='+q,'_self');
    }
    function dohtml(id)
    {
        var url = '/html/'+id+'.html';
        window.open(url,'_blank');
    }
script>
head>
<body>
    <div>
        <table>
            <tr>
                <lable>名称查询:lable>
                <input id="query" name="query" type="text" value="${query}"/>
                <input type="button" onclick="doquery()" value="查询"/>
                <input type="button" onclick="doadd()" value="新增"/>
                <input type="button" onclick="dodeletes()" value="批量删除"/>
            tr>
        table>
        <table>
            <tr>
                <th width="50"><input type="checkbox" id="chkall" name="chkall" onclick="checkall()"/>全选th>
                <th>编号th>
                <th>名称th>
                <th>菜系th>
                <th>价格th>
                <th>上架时间th>
                <th>操作th>
            tr>
            <c:forEach items="${menuList}" var="m">
                <tr>
                    <td><input type="checkbox" id="chkid" name="chkid" value="${m.id}"/>td>
                    <td>${m.id}td>
                    <td>${m.name}td>
                    <td>${m.tname}td>
                    <td>${m.price}td>
                    <td>${m.dt}td>
                    <td>
                        <input type="button" value="编辑" onclick="doedit(${m.id})"/>
                        <input type="button" value="删除" onclick="dodelete(${m.id})"/>
                        <input type="button" value="查看" onclick="dohtml(${m.id})"/>
                    td>
                tr>
            c:forEach>
        table>

        ${pagediv}
    div>
body>
html>

添加菜单页面

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>   

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" href="/js/base.css">
<script type="text/javascript" src="/js/jquery.min.js">script>
<title>菜单列表title>
<script>
    function dosave()
    {
        addfrm.submit();
    }
    function doback()
    {
        window.open('/init','_self');
    }
script>
head>
<body>
    <div>
        <form name="addfrm" action="/savemenu" method="post">
            <table>
                <input type="hidden" id="id" name="id" value="${menu.id}"/>
                <tr>
                    <td>名称td>
                    <td><input type="text" id="name" name="name" value="${menu.name}" }/>td>
                tr>
                <tr>
                    <td>价格td>
                    <td><input type="text" id="price" name="price" value="${menu.price}" />td>
                tr>
                <tr>
                    <td>上架时间td>
                    <td><input type="text" id="dt" name="dt" value="${menu.dt}" />td>
                tr>
                <tr>
                    <td>菜系td>
                    <td>
                        <c:forEach items="${typeList}" var="t">
                            <input type="checkbox" id="tid" name="tid" value="${t.id}" ${t.checked }>${t.name}
                        c:forEach>
                    td>
                tr>
            table>
            <table>
                <tr>
                    <input type="button" onclick="dosave()" value="保存"/>
                    <input type="button" value="返回" onclick="doback()"/>
                tr>
            table>
        form>
    div>
body>
html>

菜单模板
模板必须位于webapp/ftl目录下


<html>
<head>
<meta charset="UTF-8">
<title>菜单title>
head>
<body>
    <h3>菜名:${name}h3>
    <h3>价格:${price}h3>
    <h3>菜系:${tname}h3>
    <h3>上架时间:${dt}h3>
body>
html>

使用SpringBoot+Redis+Solr完成多对多项目案例_第5张图片

测试效果
使用SpringBoot+Redis+Solr完成多对多项目案例_第6张图片

代码下载
https://github.com/qixiangchen/Proj_FtlSolrRedis.git

你可能感兴趣的:(SpringBoot)