SpringBoot redis GEO 实战应用

上篇文章(Redis地理算法GEO解析和应用)我们对redis GEO算法进行解析以及相关shell命令的使用,这篇文章将带你进行实战应用。

依赖

注:jedis可以不引入,这里只是为了方便查看源码进行解析

        
        
            org.springframework.boot
            spring-boot-starter-data-redis
            2.3.0.RELEASE
        

        
        
        
            redis.clients
            jedis
            3.3.0
        

数据源配置

spring:
  redis:
    host: localhost
    port: 6379
    database: 4

编写实例

package com.gmall.user.server;

import com.gamll.user.base.BaseJunitTest;
import com.gmall.user.server.biz.UserBiz;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;

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

/**
 * @author HL.Wu
 * @date 2020/5/28 17:54
 * Copyright ©https://blog.csdn.net/qq_31150503 Copyright@2009-2020 AII Right Reserve
 */
@Slf4j
public class UserBizTest extends BaseJunitTest {
    @Autowired
    private UserBiz userBiz;

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void getCircleUser() {

        // 初始化数据
        Map locMap = new HashMap<>(); // <纬度(x),经度(y)>
        locMap.put(121.576334, 31.168569);
        locMap.put(121.587664, 31.205503);
        locMap.put(121.60586, 31.221726);
        locMap.put(121.65238, 31.199703);
        locMap.put(121.544749, 31.204989);
        locMap.put(121.506297, 31.03268);

        Map cityMap = new HashMap<>();
        cityMap.put(121.576334, "上海希奥信息科技有限公司");
        cityMap.put(121.587664, "上海火车站");
        cityMap.put(121.60586, "上海人民广场");
        cityMap.put(121.65238, "上海中医药大学");
        cityMap.put(121.544749, "上海漕河泾经开区新创业园");
        cityMap.put(121.506297, "上海竹林工业园");
        System.out.println("--------------------------------- start to redis sync save Coordinate ---------------------------------");
        // 定义坐标信息
        for (Map.Entry entry : locMap.entrySet()) {
            Point point = new Point(entry.getKey(), entry.getValue());
            redisTemplate.opsForGeo().add("user-local", point, cityMap.get(entry.getKey()));
        }

        // 设置检索范围
        Point point = new Point(121.587623,31.201719);
        Circle circle = new Circle(point, new Distance(5, Metrics.KILOMETERS));
        // 定义返回结果参数,如果不指定默认只返回content即保存的member信息
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending().limit(5);
        GeoResults> results = redisTemplate.opsForGeo().radius("user-local", circle,args);
        List>> list = results.getContent();
        for (GeoResult> l1 : list) {
            System.out.println("name : " + l1.getContent().getName() +"  distance : " + l1.getDistance() +" point :" + l1.getContent().getPoint());
            //
        }

        System.out.println("--------------------------------- end to sync save Coordinate ---------------------------------");
    }

}

SpringBoot redis GEO 实战应用_第1张图片

源码解析

1、坐标添加 add

注:支持单点、多点集合Map同时添加

  • point:元素属性x(经度),y(纬度)
/*
	 * (non-Javadoc)
	 * @see org.springframework.data.redis.core.GeoOperations#add(java.lang.Object, org.springframework.data.geo.Point, java.lang.Object)
	 */
	@Override
	public Long add(K key, Point point, M member) {

		byte[] rawKey = rawKey(key);
		byte[] rawMember = rawValue(member);

		return execute(connection -> connection.geoAdd(rawKey, point, rawMember), true);
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.redis.core.GeoOperations#add(java.lang.Object, org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation)
	 */
	@Override
	public Long add(K key, GeoLocation location) {
		return add(key, location.getPoint(), location.getName());
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.redis.core.GeoOperations#add(java.lang.Object, java.util.Map)
	 */
	@Override
	public Long add(K key, Map memberCoordinateMap) {

		byte[] rawKey = rawKey(key);
		Map rawMemberCoordinateMap = new HashMap<>();

		for (M member : memberCoordinateMap.keySet()) {
			byte[] rawMember = rawValue(member);
			rawMemberCoordinateMap.put(rawMember, memberCoordinateMap.get(member));
		}

		return execute(connection -> connection.geoAdd(rawKey, rawMemberCoordinateMap), true);
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.redis.core.GeoOperations#add(java.lang.Object, java.lang.Iterable)
	 */
	@Override
	public Long add(K key, Iterable> locations) {

		Map memberCoordinateMap = new LinkedHashMap<>();
		for (GeoLocation location : locations) {
			memberCoordinateMap.put(location.getName(), location.getPoint());
		}

		return add(key, memberCoordinateMap);
	}

2、删除坐标remove

注:根据元素名称进行删除,支持多个同时删除

	/**
	 * Remove the {@literal member}s.
	 *
	 * @param key must not be {@literal null}.
	 * @param members must not be {@literal null}.
	 * @return Number of elements removed. {@literal null} when used in pipeline / transaction.
	 * @since 2.0
	 */
	@Nullable
	Long remove(K key, M... members);

3、查询周边radius

注:支持多种条件检索周边

Circle对象属性:

  • Point:需要检索的中心点坐标;
  • Distance:value为检索半径范围;Metric枚举类型为检索范围单位
KILOMETERS(6378.137, "km"), MILES(3963.191, "mi"), NEUTRAL(1, "");

GeoRadiusCommandArgs对象方法:

注:作用指定返回数据结果参数

  • includeCoordinates:返回结果包含坐标信息

  • includeDistance:返回结果包含具中心坐标距离信息

  • sortAscending:按照距离升序排序

  • sortDescending:按照距离降序排序

  • limit:返回结果数量限制

/**
		 * Create new {@link GeoRadiusCommandArgs}.
		 *
		 * @return never {@literal null}.
		 */
		public static GeoRadiusCommandArgs newGeoRadiusArgs() {
			return new GeoRadiusCommandArgs();
		}

		/**
		 * Sets the {@link Flag#WITHCOORD} flag to also return the longitude, latitude coordinates of the matching items.
		 *
		 * @return
		 */
		public GeoRadiusCommandArgs includeCoordinates() {

			flags.add(Flag.WITHCOORD);
			return this;
		}

		/**
		 * Sets the {@link Flag#WITHDIST} flag to also return the distance of the returned items from the specified center.
		 *
		 * @return never {@literal null}.
		 */
		public GeoRadiusCommandArgs includeDistance() {

			flags.add(Flag.WITHDIST);
			return this;
		}

		/**
		 * Sort returned items from the nearest to the furthest, relative to the center.
		 *
		 * @return never {@literal null}.
		 */
		public GeoRadiusCommandArgs sortAscending() {

			sortDirection = Direction.ASC;
			return this;
		}

		/**
		 * Sort returned items from the furthest to the nearest, relative to the center.
		 *
		 * @return never {@literal null}.
		 */
		public GeoRadiusCommandArgs sortDescending() {

			sortDirection = Direction.DESC;
			return this;
		}

		/**
		 * Limit the results to the first N matching items.
		 *
		 * @param count
		 * @return never {@literal null}.
		 */
		public GeoRadiusCommandArgs limit(long count) {

			Assert.isTrue(count > 0, "Count has to positive value.");
			limit = count;
			return this;
		}

radius源码解析:

/**
	 * Get the {@literal member}s within the boundaries of a given {@link Circle}.
	 *
	 * @param key must not be {@literal null}.
	 * @param within must not be {@literal null}.
	 * @return never {@literal null} unless used in pipeline / transaction.
	 * @since 2.0
	 * @see Redis Documentation: GEORADIUS
	 */
	@Nullable
	GeoResults> radius(K key, Circle within);


	/**
	 * Get the {@literal member}s within the boundaries of a given {@link Circle} applying {@link GeoRadiusCommandArgs}.
	 *
	 * @param key must not be {@literal null}.
	 * @param within must not be {@literal null}.
	 * @param args must not be {@literal null}.
	 * @return never {@literal null} unless used in pipeline / transaction.
	 * @since 2.0
	 * @see Redis Documentation: GEORADIUS
	 */
	@Nullable
	GeoResults> radius(K key, Circle within, GeoRadiusCommandArgs args);

	
	/**
	 * Get the {@literal member}s within the circle defined by the {@literal members} coordinates and given
	 * {@literal radius}.
	 *
	 * @param key must not be {@literal null}.
	 * @param member must not be {@literal null}.
	 * @param radius
	 * @return never {@literal null} unless used in pipeline / transaction.
	 * @since 2.0
	 * @see Redis Documentation: GEORADIUSBYMEMBER
	 */
	@Nullable
	GeoResults> radius(K key, M member, double radius);


	/**
	 * Get the {@literal member}s within the circle defined by the {@literal members} coordinates and given
	 * {@literal radius} applying {@link Metric}.
	 *
	 * @param key must not be {@literal null}.
	 * @param member must not be {@literal null}.
	 * @param distance must not be {@literal null}.
	 * @return never {@literal null} unless used in pipeline / transaction.
	 * @since 2.0
	 * @see Redis Documentation: GEORADIUSBYMEMBER
	 */
	@Nullable
	GeoResults> radius(K key, M member, Distance distance);


	/**
	 * Get the {@literal member}s within the circle defined by the {@literal members} coordinates and given
	 * {@literal radius} applying {@link Metric} and {@link GeoRadiusCommandArgs}.
	 *
	 * @param key must not be {@literal null}.
	 * @param member must not be {@literal null}.
	 * @param distance must not be {@literal null}.
	 * @param args must not be {@literal null}.
	 * @return never {@literal null} unless used in pipeline / transaction.
	 * @since 2.0
	 * @see Redis Documentation: GEORADIUSBYMEMBER
	 */
	@Nullable
	GeoResults> radius(K key, M member, Distance distance, GeoRadiusCommandArgs args);

异常现象

Caused by: io.lettuce.core.RedisCommandExecutionException: ERR unknown command 'GEOADD'


org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: ERR unknown command 'GEOADD'

	at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:54)
	at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:52)
	at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)
	at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)
	at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42)
	at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:273)
	at org.springframework.data.redis.connection.lettuce.LettuceGeoCommands.convertLettuceAccessException(LettuceGeoCommands.java:424)
	at org.springframework.data.redis.connection.lettuce.LettuceGeoCommands.geoAdd(LettuceGeoCommands.java:78)
	at org.springframework.data.redis.connection.DefaultedRedisConnection.geoAdd(DefaultedRedisConnection.java:1187)
	at org.springframework.data.redis.connection.DefaultStringRedisConnection.geoAdd(DefaultStringRedisConnection.java:2908)
	at org.springframework.data.redis.core.DefaultGeoOperations.lambda$add$0(DefaultGeoOperations.java:60)
	at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:228)
	at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:188)
	at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:96)
	at org.springframework.data.redis.core.DefaultGeoOperations.add(DefaultGeoOperations.java:60)
	at com.gmall.user.server.UserBizTest.getCircleUser(UserBizTest.java:52)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
	at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
	at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
	at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: io.lettuce.core.RedisCommandExecutionException: ERR unknown command 'GEOADD'
	at io.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:135)
	at io.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:108)
	at io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:120)
	at io.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:111)
	at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:654)
	at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:614)
	at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:565)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:748)

解决方案:

更换redis版本,因为redis GEO是从>=3.2版本之后开始支持

最后

源码地址:https://gitee.com/mackjie/gmall-service 

项目中gmall-batch -->test-->com.gmall.user.server.UserBizTest#getCircleUser()

这个技能 今天你学到了吗?^_^

 

 

你可能感兴趣的:(SpringBoot,SpringBoot,redis,SpringBoot+geo,redis+geo)