美文网首页solrspringboot
spring boot和solr整合

spring boot和solr整合

作者: 非典型_程序员 | 来源:发表于2018-12-22 19:33 被阅读0次

    上周简单的学习了solr的数据导入以及IK分词器的安装,今天学习一下solr的一些基本操作,以及和spring boot的整合。在开始项目之前简单的学习一下solr的一些查询的操作

    一、admin页面简单查询

    先在本地启动solr,然后进入solr admin管理页面。在这之前我修改了数据库的数据,所以需要重新导入新的数据。选择自定义的core,点击"Query",入下图所示:

    图-1.png
    简单介绍一下几个常用查询参数:
    "q":the quert string,即查询条件,":"表示查询所有内容,支持多个查询条件。
    "fq":fiter query,即过滤查询,使用Filter Query可以充分利用FilterQuery Cache,提高检索性能。个人觉得可能更多时候用来做区间查询。
    "sort":sort field or function asc|desc,即根据指定field进行排序。
    "start,rows"主要用来做分页,这个应该都比较熟悉。
    "fl":field list,就是查询结果显示field,多个field用逗号分割。
    "df" default search field,默认查询field。
    Raw Query Parameters:就是所有的查询条件,只是查询参数是在URL上而已。
    "wt": the wite type,即查询结果的显示形式,支持多种个数,json、xml、python、ruby、csv等,最常用的可能还是json或者xml吧。
    上面这个几个都是比较差用的,查询条件,其他的还有一些,就不再介绍了。
    用一个简单的需求来测试一下,查询条件:性别为male且地址为FR,年龄在30岁以下,结果按照年龄逆序排列,每页只展示5条记录,,输出格式为json(默认):
    图-2.png
    因为数据比较少,只有3条记录,所以分页没起到效果,注意一点的是:solr查询的AND、OR、TO这些必须要大写,自己用小写发现没起作用,查询条件的格式一定邀注意,不然就会出错,另外查询条件也是支持通配符的("*"号)。这些常用的查询还是比较简单的,和sql也比较接近,这里就不过多介绍了,建议自己动手练习一下。

    二、spring boot整合solr

    spring boot有关于solr的支持官方文档, Spring Data JPA同样也支持solr,自己有兴趣可以尝试一下,其实本质上是一样的。我这次使用的是Spring Data Solr。
    打开idea,创建一个spring boot项目,添加相关的依赖,pom文件如下:

    <?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.ypc</groupId>
        <artifactId>solrdemo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>solrdemo</name>
        <description>solr demo test</description>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.1.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-solr</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.2</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>org.postgresql</groupId>
                <artifactId>postgresql</artifactId>
                <version>42.2.5</version>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>2.8.5</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    

    接下来是项目的配置文件,打开application.properties

    server.port=9090
    # 数据库配置
    spring.datasource.url=jdbc:postgresql://localhost:5432/springboot?useSSL=false&characterEncoding=utf8
    spring.datasource.driver-class-name=org.postgresql.Driver
    spring.datasource.username=postgres
    spring.datasource.password=123456
    
    # mybatis 配置
    mybatis.type-aliases-package=com.ypc.solrdemo.entity
    mybatis.mapper-locations=classpath:mapper/*.xml
    
    # solr配置
    spring.data.solr.host=http://localhost:8983/solr/custom_core
    
    spring.thymeleaf.prefix=classpath:/templates/
    spring.thymeleaf.suffix=.html
    

    配置solr host的时候注意一定加上自己的core的名称,不然会报500,错误,因为一个solr可以有多个core,服务器是没办法判断你要使用那个core,所以必须显示指定core的名称。
    接下来是相关的controller service 以及mybatis的mapper和mapper.xml代码,因为比较简单这里就不再贴相关代码了。
    我这里主要是通过solr client,简单的做了一些CRUD操作:

    1、单个查询:

    根据 id查询,先查询solr,如果solr没用查询到再查询数据库,并将结果添加到solr;

    2、查询所有

    也是先通过solr去查询,如果查询不到就去数据库查询,然后将查询结果添加到solr;

    3、单个添加

    先更新到数据库,如果异常,则回滚,这里使用@Transactional注解,然后将添加到solr,并提交,如果异常则回滚solr。

    4、根据id删除

    和单个添加一样,如果数据库事务失败则回滚;成功,则删除solr里面的信息,solr失败同样回滚。

    5、页面搜索

    通过一个搜索框,根据用户搜索条件,查询相关信息,并根据年龄排序,一个非常简单的html页面。


    一、根据Id查询

    根据id查询方法有几种,先说一种最简单或者说最笨的方法,service代码如下:

    public User queryById(int id) {
            // 先通过solr查询,查询不到查数据库
            SolrQuery query = new SolrQuery();
            query.setQuery("id:" + id);
            User user = null;
            try {
                QueryResponse response = solrClient.query(query);
                SolrDocumentList documentList = response.getResults();
                if (!documentList.isEmpty()) {
                    for (SolrDocument document:documentList) {
                        user = new User();
                        user.setId(id);
                        user.setAddress((String) document.get("address"));
                        user.setMobile((String) document.get("mobile"));
                        user.setUserName((String) document.get("userName"));
                        user.setAge((Integer) document.get("age"));
                        user.setDescription((String) document.get("description"));
                        LOGGER.info(">>>> query user from solr success <<<<");
                    }
                } else {
                    // 从数据库查询
                    user = userMapper.queryById(id);
                    if (user != null) {
                        solrClient.addBean(user,1000);
                    }
                    LOGGER.info(">>>> query user from database <<<<");
                }
    
            } catch (SolrServerException e) {
                LOGGER.error(e.getMessage(),e);
            } catch (IOException e) {
                LOGGER.error(e.getMessage(),e);
            }
            return user;
        }
    

    这里query.setQuery("id:" + id);其实等同于query.set("q","id:" + id);也就是说使用solrClient查询的时候设置的参数名称和在admin管理页面的查询条件名称是一样的。当然也可以直接使用solrClient.getById(String.valueOf(id))这个方法,我为了更直观的了解查询时设置查询参数,因此使用了query.setQuery("id:" + id)方法。
    查询返回的结果是一个QueryResponse就是整个的查询结果,包括header和结果两部分,如下图:


    图-3.png

    响应头包括的内容有查询状态、查询时间、参数;而结果里面是查询到的数量,查询起始数量,以及最终的查询结果(这个结果是一个document list)。
    先根据id查询solr,获取到SolrDocumentList,然后遍历(虽然只有一个document),然后转换成我们需要的实体对象。但是如果字段多的话在实体类User和SolrDocument之间转换非常的不方便,所以我们需要修改下项目的User类

    @SolrDocument(solrCoreName = "custom_core")
    public class User {
    
        @Id
        @Field
        private int id;
        @Field
        private String userName;
        @Field
        private String sex;
        @Field
        private String address;
        @Field
        private String description;
        @Field
        private int age;
        @Field
        private String mobile;
        @Field
        private String period;
        // 方法省略
    }
    

    可以在实体类上添加上@SolrDocument(solrCoreName = "custom_core")这个注解,表明这个实体类可以转换成SolrDocument对象,此外一定不要忘了指定core的名称。如果实体类属性和solr的field对应不上,可以使用@Field(value="field名称")注解,实现实体类和solr field之间的对应关系。
    这样在像solr添加User对象的时候就不需要手动将其转换为SolrDocument了,这点在添加用户时候我们再看。现在的情况是我们查询出来的是SolrDocument,而我们返回的User对象,当然从功能上讲直接返回SolrDocument好像也是可行的。但是从逻辑来讲,返回的应该就是User对象。我自己看了一下好像不能直接转换,当然通过反射自己写一个工具类方法也是可行的。我使用的是通过json,其实solr查询返回结果的形式,默认就是json,我只需要将SolrDocument显示的转成json,然后在转成User对象就可以了,代码如下:

    @Override
        public User queryById(int id) {
            User user = null;
            try {
                SolrDocument solrDocument = solrClient.getById(String.valueOf(id));
                Gson gson = new Gson();
                // 方法1
                String solrString = gson.toJson(solrDocument);
                user = gson.fromJson(solrString,User.class);
                // 方法2
                Map<String,Object> map = solrDocument.getFieldValueMap();
                user = gson.fromJson(map.toString(),User.class);
                
                if (null == user) {
                    user = userMapper.queryById(id);
                    solrClient.addBean(user,1000);
                }
    
            } catch (SolrServerException e) {
                LOGGER.error(e.getMessage(),e);
            } catch (IOException e) {
                LOGGER.error(e.getMessage(),e);
            }
            return user;
        }
    

    上面的方法中代码明显就简单了不少,而且两种方法都是可行的,只是方法1先将SolrDocument转成json串,然后再将json传转成对应的User对象;方法2值获取SolrDocument的key vlaue值,然后转成string后再转成User对象。两种方法都是可行的,但是我感觉方法2邀好一点,中间省去了对象转json串这个步骤,当然用其他方法也是可行的。

    二、查询所有

    这个其实和单个查询没有上面区别,只是查询条件设置成":"就可以了,代码如下:

    @Override
        public List<User> queryAll() {
            List<User> list = null;
            SolrQuery query = new SolrQuery();
            query.setQuery("*:*");
            query.setStart(0);
            query.setRows(20);
            try {
                QueryResponse response = solrClient.query(query);
                SolrDocumentList documentList = response.getResults();
                if (!documentList.isEmpty()) {
                    Gson gson = new Gson();
                    String listString = gson.toJson(documentList);
                    list = gson.fromJson(listString, new TypeToken<List<User>>() {}.getType());
    //              list = convertToModel(documentList);
                    LOGGER.info(">>>> query user from solr success <<<<");
                } else {
                    list = userMapper.queryAll();
                    solrClient.addBeans(list);
                    LOGGER.info(">>>> query user from database <<<<");
                }
    
            } catch (SolrServerException e) {
                LOGGER.error(e.getMessage(),e);
            } catch (IOException e) {
                LOGGER.error(e.getMessage(),e);
            }
            return list;
        }
    

    查询所有信息,查出来结果是一个SolrDocumentList,可以遍历出每个SolrDocument,并转成User对象,也可以直接转成json串,然后直接转成List<User>。另外是我设置的是值查询前20个(实际没有20个),本来是想做一个分页查询的,但是前端没处理好,所以就没用分页做查询了。

    三、单个添加

    因为我没用写专门的form表单,所以就直接REST Client工具提交了用户信息了,提交数据格式是json:controller和service如下:

        @ResponseBody
        @PostMapping("/insert")
        public Map<String, Object> insertUser(@RequestBody User user) {
            Map<String,Object> result = userService.insertAndUpdate(user);
            return result;
        }
        // service
        @Override
        @Transactional(rollbackFor = Exception.class)
        public Map<String, Object> insertAndUpdate(User user) {
            Map<String,Object> result = new HashMap<>();
            result.put("success",false);
            // 返回结果表示受影响的数据条数,而不是id值
            int insert = userMapper.insertUser(user);
            if (insert != 1) {
                throw new RuntimeException(" >>>> insert user to database failed,the return value should be 1,but result is:" + insert + " <<<<");
            }
            // 插入或者更新solr数据
            try {
                UpdateResponse response = solrClient.addBean(user,1000);
                int staus = response.getStatus();
                if (staus != 0) {
                    LOGGER.error(">>>> update solr document failed <<<<");
                    solrClient.rollback();
                    result.put("message","insert user to solr failed");
                    return result;
                }
            } catch (SolrServerException e) {
                LOGGER.error(e.getMessage(),e);
                result.put("message",e.getMessage());
                return result;
            } catch (IOException e) {
                LOGGER.error(e.getMessage(),e);
                result.put("message",e.getMessage());
                return result;
            }
    
            result.put("message","insert user to solr success");
            result.put("success",true);
            return result;
        }
    
    

    solr的添加和更新是一个操作,添加则添加新数据,更新覆盖原有数据,其他都是一样的,我这里使用了@Transactional注解,当我插入数据库的数据记录不等于1时,自己抛出一个运行时异常(当然自己定义一个专门的统一异常更好些),数据回滚。在更新solr的数据时,solrClient.addBean(user,1000);即在1秒钟内自动提交事务,然后根据状态值判断是否成功,如果不成功则回滚,但是这一点自己有点疑问,就是如果在1秒内提交事务失败,是否需要显式回滚??这个还需要自己再查下相关的资料。个人感觉应该是不需要的,但是为了保险起见,显示的回滚了,如果有了解的朋友希望能指点一下。其实这里自己还遇到一个问题,就是数据库插入数据后,插入的对象如何返回id的问题,因为数据库设置的是id自增主键,而返回结果是影响的行数,即插入的数据记录数,那么如何获取新插入的数据id呢?开始自己想的是查询最后一次插入的id值,即用:SELECT currval('user_id_seq');类似于mysql的SELECT LAST_INSERT_ID()。但是自己还想获取受影响的记录数以判断插入是否成功,以便回滚。后来网上找了些资料,修改mapper.xml文件如下:

    <insert id="insertUser" parameterType="com.ypc.solrdemo.entity.User" useGeneratedKeys="true" keyProperty="id">
            insert into t_user (username,age,mobile,address,sex,description)
            values (#{userName},#{age},#{mobile},#{address},#{sex},#{description})
    </insert>
    

    这样插入数据成功后,对应的id值会直接映射到对象中,mybatis官方文档。useGeneratedKeys属性只对插入和修改有效,这告诉MyBatis使用JDBC getGeneratedKeys方法来检索数据库内部生成的主键值(像MySQL或pgsql自动增量字段),默认值为false;keyProperty用来标识MyBatis将设置getGeneratedKeys返回的键值映射到哪个属性上。

    四、根据id删除

    和根据id插入基本一样,代码如下:

        @Override
        @Transactional(rollbackFor = Exception.class)
        public Map<String, Object> deleteUserById(int id) {
            Map<String,Object> result = new HashMap<>();
            result.put("success",false);
            // 先删除数据库,再更新solr
            int delete = userMapper.deleteById(id);
            if (delete != 1) {
                throw new RuntimeException(">>>> delete user failed ,user id=" + id + " <<<<");
            }
    
            try {
                UpdateResponse response = solrClient.deleteById(String.valueOf(id),1000);
                int status = response.getStatus();
                if (status != 0) {
                    LOGGER.error(">>>> delete user from solr failed ,user id=" + id + " <<<<");
                    solrClient.rollback();
                    result.put("message","delete user to solr failed");
                    return result;
                }
            } catch (SolrServerException e) {
                LOGGER.error(e.getMessage(),e);
                result.put("message",e.getMessage());
                return result;
            } catch (IOException e) {
                LOGGER.error(e.getMessage(),e);
                result.put("message",e.getMessage());
                return result;
            }
    
            result.put("success",true);
            result.put("message","delete user success");
            return result;
        }
    
    五、搜索框搜索

    自己写了一个简单的html页面,作为搜索框使用,用户输入条件,后台根据条件查询,并且自己设置了一个简单的根据年龄排序的功能,比较low,但是功能上可以使用,点击年龄会进行顺序或者逆序排序。


    图-4.png

    搜索功能,也就是根据多个字段进行OR查询,这里其实可以设置查询根据年龄顺序或是逆序,但是为了后面排序的功能,这里就省略了,代码如下:

        @Override
        public List<User> queryByCondition(String de) {
            List<User> list = null;
            // 关键字模糊查询
            SolrQuery query = new SolrQuery();
            String nameLike = "userName:*" + de +  "*";
            String desLike = " OR description:*" + de+  "*";
            String sexLike = " OR sex:*" + de +  "*";
            String addLike = " OR address:*" + de +  "*";
            query.set("q",nameLike + desLike + sexLike + addLike);
    
            query.setStart(0);
            query.setRows(20);
            try {
                QueryResponse response = solrClient.query(query);
                SolrDocumentList documentList = response.getResults();
                if (!documentList.isEmpty()) {
                    Gson gson = new Gson();
                    String listString = gson.toJson(documentList);
                    list = gson.fromJson(listString, new TypeToken<List<User>>() {}.getType());
                } else {
                    LOGGER.info(">>>> no result returned by the filter query word: " + de + " <<<<");
                }
    
            } catch (SolrServerException e) {
                LOGGER.error(e.getMessage(),e);
            } catch (IOException e) {
                LOGGER.error(e.getMessage(),e);
            }
            return list;
        }
    

    自己简单测试一下,结果如下,可见年龄顺序既不是顺序也不是逆序:

    图-5.png
    而根据年龄排序,只需要在上面代码添加排序的规则就可以了,另外就是ajax传递参数时将是顺序还是逆序这个标识位传递给后台即可,这里就不再贴代码了。
    好了,以上就是这次spring boot整合solr的一个很简单的demo,其实单机的solr操作并不复杂,只需要掌握常用的一些操作即可。今天主要学习就是通过admin管理页面熟悉常用的查询操作和spring boot整合后solrClient的使用,此外就是实体类和SolrDocument之间转换的问题。当然自己的demo很简单,如果是复杂的场景就又不太一样了。
    这次的代码已经更新到我的github,如果文章或代码中有什么问题,欢迎指正!!

    相关文章

      网友评论

        本文标题:spring boot和solr整合

        本文链接:https://www.haomeiwen.com/subject/qxeikqtx.html