JPA 联合主键 Repository主键查询

持久化实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
@Table(name = "stClubUser")
public class ClubUser implements Serializable {

	@EmbeddedId
	private ClubUserPK id;
}

嵌入式主键实体类:

 

@Embeddable
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class ClubUserPK implements Serializable {
	/**
	 * 俱乐部
	 */
	@ManyToOne
	private Club club;

	/**
	 * 用户
	 */
	@ManyToOne
	private User user;
}

Repository:

@Repository
public interface ClubUserRepository extends JpaRepository<ClubUser, ClubUserPK>, JpaSpecificationExecutor<ClubUser> {

}

9403693155

在config里Converter方法

    @Bean
    public Converter<String, Date> DateConvert() {
        return new Converter<String, Date>() {
            @Override
            public Date convert(String source) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                Date date = null;
                try {
                    String dateStr = sdf.format(Long.parseLong(source));
                    date = sdf.parse(dateStr);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return date;
            }
        };
    }

idea debug模式下运行慢

最近开发时发现debug运行启动要80s,在run运行下只要12秒,相差巨大。

 åŽŸå› :在方法中加了断点 

 è§£å†³æ–¹æ³•:去掉方法中的断点。如果未知断点位置,可由下图方法找到。

 

QQ图片20180531094740.png

JPQL 表名、参数通用 SpEL 写法

关于表名:

方法一:

直接写表名

方法二:通过SpEL

SELECT SUM(star) FROM #{#entityName}

#{#entityName}为固定写法.

关于参数:

方法一:用(?+序号)方式

@Query("select * from Task t where t.task_name = ?1")
Task findByTaskName(String taskName);

方法二:通过@Param绑定参数

@Query("select t from Task t where t.taskName = :taskName and t.createTime = :createTime")
Task findByTaskName(@Param("taskName")String taskName,@Param("createTime") Date createTime);

 

linux 定时任务

查看进程:

ps -ef|grep cron

服务:

service crond start    /启动服务
service crond stop     /关闭服务
service crond restart  /重启服务
service crond reload   /重新载入配置
service crond status   /查看服务状态

日志查询:

tailf /var/log/cron

编辑定时任务:

crontab -uroot -e

参数命令:

-u user:用来设定某个用户的crontab服务,例如,“-u ixdba”表示设定ixdba用户的crontab服务,此参数一般有root用户来运行。

file:file是命令文件的名字,表示将file做为crontab的任务列表文件并载入crontab。如果在命令行中没有指定这个文件,crontab命令将接受标准输入(键盘)上键入的命令,并将它们载入crontab。

-e:编辑某个用户的crontab文件内容。如果不指定用户,则表示编辑当前用户的crontab文件。

-l:显示某个用户的crontab文件内容,如果不指定用户,则表示显示当前用户的crontab文件内容。

-r:从/var/spool/cron目录中删除某个用户的crontab文件,如果不指定用户,则默认删除当前用户的crontab文件。

-i:在删除用户的crontab文件时给确认提示。

说明:

打开crontab

vim /etc/crontab


常用方法:

# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed

其中:

minute: 表示分钟,可以是从0到59之间的任何整数。

hour:表示小时,可以是从0到23之间的任何整数。

day:表示日期,可以是从1到31之间的任何整数。

month:表示月份,可以是从1到12之间的任何整数。

week:表示星期几,可以是从0到7之间的任何整数,这里的0或7代表星期日。

command:要执行的命令,可以是系统命令,也可以是自己编写的脚本文件。

9105638575

public class IGeohashUtil {
    private static final String BASE32="0123456789bcdefghjkmnpqrstuvwxyz";
    private static final int TOP = 0;
    private static final int RIGHT = 1;
    private static final int BOTTOM = 2;
    private static final int LEFT = 3;

    private static final int EVEN = 0;
    private static final int ODD = 1;

    private static String[][] NEIGHBORS;
    private static String[][] BORDERS;

    static {
        NEIGHBORS = new String[2][4];
        BORDERS = new String[2][4];

        BORDERS[ODD][TOP] = "bcfguvyz";
        BORDERS[ODD][RIGHT] = "prxz";
        BORDERS[ODD][BOTTOM] = "0145hjnp";
        BORDERS[ODD][LEFT] = "028b";

        BORDERS[EVEN][TOP] = BORDERS[ODD][RIGHT];
        BORDERS[EVEN][RIGHT] = BORDERS[ODD][TOP];
        BORDERS[EVEN][BOTTOM] = BORDERS[ODD][LEFT];
        BORDERS[EVEN][LEFT] = BORDERS[ODD][BOTTOM];

        NEIGHBORS[ODD][TOP] = "238967debc01fg45kmstqrwxuvhjyznp";
        NEIGHBORS[ODD][RIGHT] = "14365h7k9dcfesgujnmqp0r2twvyx8zb";
        NEIGHBORS[ODD][BOTTOM] = "bc01fg45238967deuvhjyznpkmstqrwx";
        NEIGHBORS[ODD][LEFT] = "p0r21436x8zb9dcf5h7kjnmqesgutwvy";

        NEIGHBORS[EVEN][TOP] = NEIGHBORS[ODD][RIGHT];
        NEIGHBORS[EVEN][RIGHT] = NEIGHBORS[ODD][TOP];
        NEIGHBORS[EVEN][BOTTOM] = NEIGHBORS[ODD][LEFT];
        NEIGHBORS[EVEN][LEFT] = NEIGHBORS[ODD][BOTTOM];
    }

    /**
     * 求与当前geohash相邻的8个格子的geohash值。
     *
     * @param geohash
     * @param suffix  数据库查询中前缀匹配使用的通配符
     * @return string数组,周围格子的geohash值
     */
    public static String[] expand(String geohash, String suffix) {

        String left = calculate(geohash, LEFT);
        String right = calculate(geohash, RIGHT);
        String top = calculate(geohash, TOP);
        String bottom = calculate(geohash, BOTTOM);

        String topLeft = calculate(top, LEFT);
        String topRight = calculate(top, RIGHT);
        String bottomLeft = calculate(bottom, LEFT);
        String bottomRight = calculate(bottom, RIGHT);

        return new String[]{topLeft + suffix, top + suffix, topRight + suffix, left + suffix, geohash + suffix, right + suffix, bottomLeft + suffix, bottom + suffix, bottomRight + suffix};
    }

    /**
     * 递归计算当前区域特定方向的geohash值
     *
     * @param geohash
     * @param direction 偏移方向
     * @return 周围区域的geohash值,超出边界则返回空字符串""
     */
    private static String calculate(String geohash, int direction) {
        if ("".equals(geohash))      /如果递归到第一个字符仍然处于边界,则不存在这一方向的相邻格子
            return "";
        int length = geohash.length();
        char lastChar = geohash.charAt(length - 1);
        int charType = (geohash.length() % 2) == 1 ? ODD : EVEN;  /最后一位是奇数还是偶数
        String base = geohash.substring(0, length - 1);
        if (BORDERS[charType][direction].indexOf(lastChar) != -1) { /判断对后一位是否处在边界
            base = calculate(base, direction);
        }
        if (!"".equals(base)) {
            return base + NEIGHBORS[charType][direction].charAt(BASE32.indexOf(lastChar));
        } else {
            return "";
        }
    }
}

2104464202

转载于:/blog.csdn.net/ghsau/article/details/50591932

简介

现在几乎所有的O2O应用中都会存在“按范围搜素、离我最近、显示距离”等等基于位置的交互,那这样的功能是怎么实现的呢?本文提供的实现方式,适用于所有数据库。

实现

为了方便下面说明,先给出一个初始表结构,我使用的是MySQL:

CREATE TABLE `customer` (    `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键',    `name` VARCHAR(5) NOT NULL COMMENT '名称',    `lon` DOUBLE(9,6) NOT NULL COMMENT '经度',    `lat` DOUBLE(8,6) NOT NULL COMMENT '纬度',    PRIMARY KEY (`id`)
)
COMMENT='商户表'CHARSET=utf8mb4
ENGINE=InnoDB
;

实现过程主要分为四步:
1. 搜索
在数据库中搜索出接近指定范围内的商户,如:搜索出1公里范围内的。
2. 过滤
搜索出来的结果可能会存在超过1公里的,需要再次过滤。如果对精度没有严格要求,可以跳过。
3. 排序
距离由近到远排序。如果不需要,可以跳过。
4. 分页
如果需要2、3步,才需要对分页特殊处理。如果不需要,可以在第1步直接SQL分页。

第1步数据库完成,后3步应用程序完成。

step1 搜索

搜索可以用下面两种方式来实现。

区间查找

customer表中使用两个字段存储了经度和纬度,如果提前计算出经纬度的范围,然后在这两个字段上加上索引,那搜索性能会很不错。
那怎么计算出经纬度的范围呢?已知条件是移动设备所在的经纬度,还有满足业务要求的半径,这很像初中的一道平面几何题:给定圆心坐标和半径,求该圆外切正方形四个顶点的坐标。而我们面对的是一个球体,可以使用5078574115来计算。

<dependency>
    <groupId>com.spatial4j</groupId>
    <artifactId>spatial4j</artifactId>
    <version>0.5</version>
</dependency>
/ 移动设备经纬度
double lon = 116.312528, lat = 39.983733;/ 千米
int radius = 1;SpatialContext geo = SpatialContext.GEO;Rectangle rectangle = geo.getDistCalc().calcBoxByDistFromPt(
        geo.makePoint(lon, lat), radius * DistanceUtils.KM_TO_DEG, geo, null);
System.out.println(rectangle.getMinX() + "-" + rectangle.getMaxX());/ 经度范围
System.out.println(rectangle.getMinY() + "-" + rectangle.getMaxY());/ 纬度范围

计算出经纬度范围之后,SQL是这样:

SELECT id, nameFROM customerWHERE (lon BETWEEN ? AND ?) AND (lat BETWEEN ? AND ?);

需要给lon、lat两个字段建立联合索引:

INDEX `idx_lon_lat` (`lon`, `lat`)

geohash

geohash的原理不讲了,详细可以看这篇文章,讲的很详细。geohash算法能把二维的经纬度编码成一维的字符串,它的特点是越相近的经纬度编码后越相似,所以可以通过前缀like的方式去匹配周围的商户。
customer表要增加一个字段,来存储每个商户的geohash编码,并且建立索引。

CREATE TABLE `customer` (    `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键',    `name` VARCHAR(5) NOT NULL COMMENT '名称' COLLATE 'latin1_swedish_ci',    `lon` DOUBLE(9,6) NOT NULL COMMENT '经度',    `lat` DOUBLE(8,6) NOT NULL COMMENT '纬度',    `geo_code` CHAR(12) NOT NULL COMMENT 'geohash编码',    PRIMARY KEY (`id`),
    INDEX `idx_geo_code` (`geo_code`)
)
COMMENT='商户表'CHARSET=utf8mb4
ENGINE=InnoDB
;

在新增或修改一个商户的时候,维护好geo_code,那geo_code怎么计算呢?spatial4j也提供了一个工具类GeohashUtils.encodeLatLon(lat, lon),默认精度是12位。这个存储做好后,就可以通过geo_code去搜索了。拿到移动设备的经纬度,计算geo_code,这时可以指定精度计算,那指定多长呢?我们需要一个geo_code长度和距离的对照表:

geohash length width height
1 5,009.4km 4,992.6km
2 1,252.3km 624.1km
3 156.5km 156km
4 39.1km 19.5km
5 4.9km 4.9km
6 1.2km 609.4m
7 152.9m 152.4m
8 38.2m 19m
9 4.8m 4.8m
10 1.2m 59.5cm
11 14.9cm 14.9cm
12 3.7cm 1.9cm

/en.wikipedia.org/wiki/Geohash#Cell_Dimensions

假设我们的需求是1公里范围内的商户,geo_code的长度设置为5就可以了,GeohashUtils.encodeLatLon(lat, lon, 5)。计算出移动设备经纬度的geo_code之后,SQL是这样:

SELECT id, nameFROM customerWHERE geo_code LIKE CONCAT(?, '%');

这样会比区间查找快很多,并且得益于geo_code的相似性,可以对热点区域做缓存。但这样使用geohash还存在一个问题,geohash最终是在地图上铺上了一个网格,每一个网格代表一个geohash值,当传入的坐标接近当前网格的边界时,用上面的搜索方式就会丢失它附近的数据。比如下图中,在绿点的位置搜索不到白家大院,绿点和白家大院在划分的时候就分到了两个格子中。
这里写图片描述
解决这个问题思路也比较简单,我们查询时,除了使用绿点的geohash编码进行匹配外,还使用周围8个网格的geohash编码,这样可以避免这个问题。那怎么计算出周围8个网格的geohash呢,可以使用geohash-java来解决。

<dependency>
    <groupId>ch.hsr</groupId>
    <artifactId>geohash</artifactId>
    <version>1.3.0</version>
</dependency>
/ 移动设备经纬度
double lon = 116.312528, lat = 39.983733;
GeoHash geoHash = GeoHash.withCharacterPrecision(lat, lon, 10);/ 当前
System.out.println(geoHash.toBase32());/ N, NE, E, SE, S, SW, W, NW
GeoHash[] adjacent = geoHash.getAdjacent();
for (GeoHash hash : adjacent) {
    System.out.println(hash.toBase32());
}

最终我们的sql变成了这样:

SELECT id, nameFROM customerWHERE geo_code LIKE CONCAT(?, '%')OR geo_code LIKE CONCAT(?, '%')OR geo_code LIKE CONCAT(?, '%')OR geo_code LIKE CONCAT(?, '%')OR geo_code LIKE CONCAT(?, '%')OR geo_code LIKE CONCAT(?, '%')OR geo_code LIKE CONCAT(?, '%')OR geo_code LIKE CONCAT(?, '%')OR geo_code LIKE CONCAT(?, '%');

原来的1次查询变成了9次查询,性能肯定会下降,这里可以优化下。还用上面的需求场景,搜索1公里范围内的商户,从上面的表格知道,geo_code长度为5时,网格宽高是4.9KM,用9个geo_code查询时,范围太大了,所以可以将geo_code长度设置为6,即缩小了查询范围,也满足了需求。还可以继续优化,在存储geo_code时,只计算到6位,这样就可以将sql变成这样:

SELECT id, nameFROM customerWHERE geo_code IN (?, ?, ?, ?, ?, ?, ?, ?, ?);

这样将前缀匹配换成了直接匹配,速度会提升很多。

step2 过滤

上面两种搜索方式,都不是精确搜索,只是尽量缩小搜索范围,提升响应速度。所以需要在应用程序中做过滤,把距离大于1公里的商户过滤掉。计算距离同样使用spatial4j。

/ 移动设备经纬度double lon1 = 116.3125333347639, lat1 = 39.98355521792821;/ 商户经纬度double lon2 = 116.312528, lat2 = 39.983733;

SpatialContext geo = SpatialContext.GEO;
double distance = geo.calcDistance(geo.makePoint(lon1, lat1), geo.makePoint(lon2, lat2)) * DistanceUtils.DEG_TO_KM;
System.out.println(distance);

过滤代码就不写了,遍历一遍搜索结果即可。

step3 排序

同样,排序也需要在应用程序中处理。排序基于上面的过滤结果做就可以了Collections.sort(list, comparator)。

step4 分页

如果需要2、3步,只能在内存中分页,做法也很简单,可以参考这篇文章。

总结

全文的重点都在于搜索如何实现,更好的利用数据库的索引,两种搜索方式以百万数据量为分割线,第一种适用于百万以下,第二种适用于百万以上,未经过严格验证。可能有人会有疑问,过滤和排序都在应用层做,内存占用会不会很严重?这是个潜在问题,但大多数情况下不会。看我们大部分的应用场景,都是单一种类POI(Point Of Interest)的搜索,如酒店、美食、KTV、电影院等等,这种数据密度是很小,1公里内的酒店,能有多少家,50家都算多的,所以最终要看具体业务数据密度。本文没有分析原理,只讲了具体实现,有关分析的文章可以看参考链接。

 

kitchen bob

maven 打包需要依赖父类pom 出现 Failed to execute goal on project *: Could not resolve dependencies for project

解决办法:

在父类目录下 mvn install