2023年3月

记一次接口调用字段映射失败问题排查

在写接口的时候遇到一个很神奇的问题,编写一个post接口,在使用包装类接收body的时候发现有个字段映射不上。代码如下

@RestController
public class TestController {
    @PostMapping("test")
    public TestDto test(@RequestBody TestDto testDto) {
        return testDto;
    }
}
..........
@Getter
@Setter
public class TestDto {
    private String sName;
    private String value;
}

TestDto中的value可以正常获取值,但是sName却没值。
根据我多年的开发经验,推测应该是字段名的问题,大家都知道springboot接口反序列化是用的jackson,而jackson又是调用的getter和setter实现的序列化和反序列化,这样咱们大概就有一个方向。直接看一下class文件,看看@getter和@setter注解生成的方法长什么样

看起来感觉没什么问题。
那就只能debug看了,为了方便调试,手动编写getter和setter方法(复制过来),然后在setSName方法上打上断点,结果根本没停,说明根本就没有调用该方法。那我们再试试在setValue方法上打上断点,这次走到了

看一下方法调用,发现前几个都是invoke,没啥用,往前看几个,发现deserializeAndSet方法,没看到什么有用的信息

再往前看一个,看看
deserializeFromObject
方法里面,发现这么一个判断

看一下_beanProperties

发现这里就是保存类字段的信息地方,但是,嗯?为什么是sname,难怪我们的sName无法映射上。
那我们接下来就找找看,这个_beanProperties到底是怎么来的

在_beanProperties上打上断点,调一下接口,嗯?怎么没停?难不成这个是系统启动的时候就加载好的,重启一下试试。还是没有!再调一下接口试试,这次终于是停了

我们再来看看这properties是哪来的

继续深入

终于看到了我们熟悉的内容,这不就是我们要找的东西吗,发现又是从
props
来的,再回头

继续在_properties上打上断点,注意一定要把Field access勾选上,不然没办法监听到参数的使用

重启服务,调用接口,然后进入addProperty方法

然后继续反推,发现propDefs

阅读方法后发现数据来源于beanDesc.findProperties(),深入得到

继续在这个新的_properties上打上断点,重启服务,跳过几个无用断点,调试接口

深入方法,发现collectAll方法

看到_addFields和_addMethods方法,感觉终于要接近真相了!
接下来我们就看看collectAll方法到底做了什么

可以看到,在_addFields之后,props里面已经存放了TestDto的两个字段,这个时候sName的key还是对的

进入_addMethods方法,这段代码的逻辑是遍历类里的所有方法,如果方法的入参是0个就尝试从add getter方法,如果方法入参是1个则尝试add setter方法。
进入_addGetterMethod方法,发现如下逻辑,如果方法上没有添加json注解就会尝试从方法名称中解析字段名

进入findNameForRegularGetter方法,发现最终是调用的legacyManglePropertyName解析字段名

进入该方法,终于真相大白,重点看下面这段逻辑

这个方法中和方法名一起传进来的还有getter字符的offset用于去除get前缀,然后得到方法中的属性名,首先将属性名的第一个字符变为小写,如果本来就是小写的话直接返回属性名,如getvalue->value。
如果不是则继续遍历剩余字符,将每一个遇到的大写字符都转化为小写,直到遇到第一个小写的字符,然后返回属性名。如getVAlue->value,
getSName->sname,getURL->url。本来_addMethods方法是为了给每个field添加对应的getter和setter方法的,但是现在由于从getter方法中解析的名称和真实的field不一致,就导致会新增一个该名称的字段,我们的sname就是这么来的

这个时候props里面实际上有三个字段,而sName里面是没有getter,setter方法的,我们的getSName方法被sname字段拿去了。然后在经过_removeUnWantedProperties(props)方法之后,没有getter和setter方法的字段就被移除了!

结论

jackson反序列化的逻辑是,先找到类的成员变量field,然后从getter setter方法中反推属性名,为field和方法添加映射,而jackson反推属性名的逻辑是方法中去掉get的部分后跟着的连续大写字符都转换为小写字符。

写在最后

实际上这也和lombok生成getter方法的逻辑有关,如果我用idea自动生成getter方法的话,这个逻辑和jackson是一致的

我经常在手中拿着一个内存条手链,以彰显我是计算机深入挖掘专家,它就是一个象征,类似摸金符,有它代表你有资格可以探墓了。

同事找到我说:“我们有一台服务器,内存资源持续高位运行,经常浮动在80%左右,系统重启后会好一些,不过持续不了太久。”

服务器是云服务器:8G内存,发生问题时,大约5GB内存消失了,但合计所有进程的内存工作集也不到3G,消失的内存何在?

好吧,进入远程登录看看实际情况吧

在任务管理器与Process Explorer中将所有进程工作集排序并汇总后,得到的结果就是我们没有任何程序使用这么大的内存

不及格的程序员-八神

不及格的程序员-八神

发生这种奇怪诡异的事情在Windows已经见怪不怪了,因为【任务管理器】不是理想的内存探察器,有些系统内存资源它是感知不到的,你不能指望它来计算系统正在使用的内存,除非你想掉入无止尽的黑洞时间,到头来一场空,浪沸大量人生。

有经验的Windows运维专家到这里就会知道,如果上面没有你想要的结果,那么这背后可能有不可告人的秘密在发生,来自下面几个原因之一:

1 Sql Server 内存缓冲池,SqlServer如果客户请求量巨大,它会吞掉你所有可用物理内存,直到系统到崩溃的边缘。

2 Hyper-V虚似机 Dynamic Memory 占用内存

3 某些系统驱动程序内存泄漏(如:某个版本的Ati catalyst control center,或是某个品牌的网卡驱动程序)

4 第三方大型服务器程序:Logitech Media Server

我们使用正确的工具软件是探察这些问题的先决条件,要有好的罗盘,口决,驴踢子

到微软网站下载RAMMap, VMMap ,借用这两个软件我发现Sql Server偷偷占用大量的内存,下图只是例子:如果你看到了满屏黄色条状private Data,恭喜。

进入数据库的内存设定页面,将最大内存调整到较小的数值,系统内存的压力立杆见影,马上就下降了,由此断定此问题正是SQLServer触发的。

如何设置这个最大服务器内存是个技术活,因为系统上还有许多程序需要大量内存,除非你这台服务器就SqlServer独占资源,你可以将90%的内存给它用。

需要注意:


设置最大服务器内存,不代表SqlServer申请内存资源不会超过这个数值,它只是缓存数据池的内存大小,SqlServer还有许多占用内存的组件是不包含在内的
有时你会怀疑我明明设置了最大服务器内存,为什么在资源管理下看到SQLServer申请的内存已经超过此值,你没看错,SqlServer是内存大户,内存池只是它需要用的一部分,多数数据库都有一个成名秘技就是减少磁盘IO,代价就是更多的内存。

经过上面分析,告之同事要么升级内存购买更多,要么忍着,并将最大服务内存适当调低些。

找几个倒霉的客户程序,在代码里多加几个Sleep,当它们觉得慢的受不了时,你告之需要升级系统数据库了,原因你们的数据大到服务器快装不下了。。。

补充内容:

问题来自另一台服务器, 是某客户独占的,只有一个IIS站点,一个SqlServer实例,该客户每日上传的数据百万左右,数据库时常报错如下:

资源池‘ default’ 没有足够的系统内存来运行此查询

运维同事之后将数据库的最大服务器内存调整到一个小的值,SqlServer默认会消耗到它所能找到的所有系统内存,所以每台装有SQLServer的服务器在安装后会应设定一个合适的数值,来配合系统的运行。但是在设置之后却不知道这些内存占用并不在【任务管理器】中显示,所以引来了它的疑惑,并找到了我。

好吧,我承认,这不是第一次找我了,就在前些日子这个服务器的数据库经常崩溃,经常出现如下错误

这种错误大家也许经常会在网络上看到,引起这个故障的原因可能是多样的,在我这里也不例外,2个星期内我们一直围绕在数据文件格式问题打转,可问题一直在持续,客户经常打电话到客服那报怨,花了的钱并不值当,称我们服务质量简直垃圾一样,天天烦到它们了。
直到我冥思苦想后终于找到罪魁祸首,直指Windows Remote DeskTop漏洞。

终其原因是系统受到多次攻击,导致系统BOSD死亡蓝屏,由于是云服务器,系统自动重启,SQLSever数据库不能正常启动。

为什么会有这样的攻击呢,因为黑客通过此手段可以得到计算机的控制权,达到做任何事情,无利不起早。你们(黑客们)都给我低调点儿

上面的错误提示让我受到启发:
Sqlserver的日志和系统日志中的其它信息可能提供了更详细信息。。。。

系统日志中提到 重启证据,当时开始怀疑机房最近供电有些不足,哈哈,怎么人不都是这样的吗?

后来又看到这个报错,也许能洗清我对机房电力系统的污蔑

系统蓝屏后给出Windows系统内核转储文件,真是太棒了,拿到转储用WinDBG简单看一下就知道是受到BlueKeep攻击了,下面是
Dump的 栈回溯

不及格的程序员-八神

红框之处正是远程桌面的驱动程序调用所在最后死在了蓝屏里,升级最新Windows系统补丁,故障消失,至此故事完结!

概述

引述维基百科的介绍:

Perlin噪声(Perlin noise,又称为柏林噪声)指由Ken Perlin发明的自然噪声生成算法,具有在函数上的连续性,并可在多次调用时给出一致的数值。 在电子游戏领域中可以透过使用Perlin噪声生成具连续性的地形;或是在艺术领域中使用Perlin噪声生成图样。

维基百科的介绍相当的官方,其实可以理解为一个随机函数,不过有以下两个特点:

  • 连续的输入得到的输出更加平滑(对连续的输入有一定权重采样)
  • 相同的输入必定得到相同的输出(有的随机函数有状态(时间种子),这里更像是Hash函数)

它适用于希望给定连续的输入,能够给出相对连续的随机输出。(例如,模拟自然地形生成:想象地形不能前一步是高山,脚下是深谷,后一步又是高山这种连续剧烈的变化)

随机函数噪声:
image

柏林噪声:
image

原理

对于有经验的同学来说,一提到“平滑”,直觉上就会想到插值、平滑函数等。没错,柏林噪声其实就是使用插值、平滑函数,有时会在此基础上使用倍频,波形叠加(傅里叶变换)等方法对波形调整。

image
先把复杂问题简单化,考虑一个一维的柏林噪声生成:

上面提到了插值,插值首先要有值:静态生成一组随机数,在一个坐标系中每单位距离散落一个随机数。不妨令:rands是这个随机数数组,上图中y1 = rands[0], y2 = rands[1], ...,x2 - x1 = delta_x = 上述的单位距离,建立一个坐标系。
对于散落在[0, rands.Len - 1]区间的某个值n来说([rands.Len-1, rands.Len]区间对应的x的点规定不能取到,因为下面计算会推到rands[n + 1]),假设n对应上图P点则有:

Noise(P) = Y1 + (Y2 - Y1) * F((xp - x1)/(delta_x))
理解下这个公式:

  • Y1指红色的那个函数表达式(N),Y2指黄色的(N + 1)
  • Noise(P)类型插值函数: Lerp = yn + (yn+1 - yn) * t, t 取值 [0, 1],在这里:
    • yn = Y1
    • yn+1 = Y2
    • t = F((xp - x1)/(delta_x))

这里的F是指平滑函数,上述(t)可知F在[0,1]的输出也必须在[0,1]区间内,通常F(x) = 6 * x^5 - 15 * x^4 - 10 * x^3,顾名思义就是对输入进行平滑,函数图像如下:
image

带入数据来算:
Noise(p) = Y1(xp) + (Y2(xp) - Y1(xp)) * F((xp - x1)/(delta_x))
就不展开了

再来思考下它的实现原理:

  • 随机:对于Noise(p)来说它的值取决于y1和y2两个随机数
  • 平滑: Noise(p)取值是通过前后插值得到的,其插值参数t也经过平滑处理

其思路可以拓展到2维、3维,以2维举例:
image
p落在abcd组成的2维网格中,其实可以视为3次1维的计算:分别计算pab、pcd所在1维直线(ab、cd)的结果,在此基础上计算pad、pcd所在的线上p点的结果。这个计算会在下面的代码实现中更加具象化体现出来。(注意有一点计算是不一样的,一维中y = kx + b计算两个点之间的影响在2维空间不适用,点会受到2个维度的影响,具体看下面实现中的示例)

经典实现

static int p[512] = { 
    151,160,137,91,90,15,					
    131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,	
    190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
    88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
    77,146,158,231,83,666666,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
    102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
    135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
    5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
    223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
    129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
    251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
    49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
    138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,

    151,160,137,91,90,15,					
    131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,	
    190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
    88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
    77,146,158,231,83,666666,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
    102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
    135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
    5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
    223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
    129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
    251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
    49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
    138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
};


static float3 grads[12] = {
    {1,1,0},
    {-1,1,0},
    {1,-1,0},
    {-1,-1,0},

    {1,0,1},
    {-1,0,1},
    {1,0,-1},
    {-1,0,-1},

    {0,1,1},
    {0,-1,1},
    {0,1,-1},
    {0,-1,-1}
};

float grad(int hash, float x, float y, float z)
{
    float3 v3 = float3(x,y,z);
    hash = hash & 0xb;
    return dot(grads[hash],v3);
}

int inc(int num) {
    num++;
    return num;
}

float fade(float t) {
    return t * t * t * (t * (t * 6 - 15) + 10);         
}

float perlin(float x,float y,float z)
{
    int xi = (int)x & 255;
    int yi = (int)y & 255;
    int zi = (int)z & 255;

    float xf = x - xi;
    float yf = y - yi;
    float zf = z - zi;

    float u = fade(xf);
    float v = fade(yf);
    float w = fade(zf);

    int aaa, aba, aab, abb, baa, bba, bab, bbb;
    aaa = p[p[p[    xi ]+    yi ]+    zi ];
    aba = p[p[p[    xi ]+inc(yi)]+    zi ];
    aab = p[p[p[    xi ]+    yi ]+inc(zi)];
    abb = p[p[p[    xi ]+inc(yi)]+inc(zi)];
    baa = p[p[p[inc(xi)]+    yi ]+    zi ];
    bba = p[p[p[inc(xi)]+inc(yi)]+    zi ];
    bab = p[p[p[inc(xi)]+    yi ]+inc(zi)];
    bbb = p[p[p[inc(xi)]+inc(yi)]+inc(zi)];

    float x1, x2, y1, y2;
    x1 = lerp(    grad (aaa, xf  , yf  , zf),           
        grad (baa, xf-1, yf  , zf),             
        u);                                     
    x2 = lerp(    grad (aba, xf  , yf-1, zf),           
    grad (bba, xf-1, yf-1, zf),             
        u);
    y1 = lerp(x1, x2, v);

    x1 = lerp(    grad (aab, xf  , yf  , zf-1),
        grad (bab, xf-1, yf  , zf-1),
        u);
    x2 = lerp(    grad (abb, xf  , yf-1, zf-1),
        grad (bbb, xf-1, yf-1, zf-1),
        u);
    y2 = lerp (x1, x2, v);

    return lerp (y1, y2, w); 
}

image

这段代码是3维的perlin函数,控制参数也可以实现1维、2维计算,从perlin函数看起:

  • 静态的p[512]数组散落随机数数组每256个分为一块,共两块(为了方便计算)。aaa = p[p[p[ xi ]+ yi ]+ zi ] 类似的其实就是进行一次哈希计算,打乱顺序结果尽可能随机,类似于一维中的每隔单位距离散落随机数。
  • grads数组和grad函数就是确定这个p点分别受这8个顶点影响的程度,在计算上体现就是进行内积(投影),注意这里的类比于一维的计算是有差别的:这里提到所谓的“梯度”,在一维计算里梯度就是指y = kx + 1中的k也就是斜率,而在三维空间中,梯度受3个维度的影响,在这里进行了简化从预设的12个向量中选取(至于为什么见参考链接:柏林噪声作者论文)。
  • 接着就是进行lerp插值,对各个顶点方向上的计算结果进行平滑。

一个其他非典型实现示例

float rand(float2 p){
    return frac(sin(dot(p ,float2(12.9898,78.233))) * 43758.5453);
}

float noise(float2 x)
{
    float2 i = floor(x);
    float2 f = frac(x);

    float a = rand(i);
    float b = rand(i + float2(1.0, 0.0));
    float c = rand(i + float2(0.0, 1.0));
    float d = rand(i + float2(1.0, 1.0));
    float2 u = f * f * f * (f * (f * 6 - 15) + 10);

    float x1 = lerp(a,b,u.x);
    float x2 = lerp(c,d,u.x);
    return lerp(x1,x2,u.y);
}

可以看到这种实现和上文中的思路是一样的,只是hash函数和计算各个方向上的影响计算进行了简化。

波形调整

可以看出柏林函数的输出具有“波”的特点,那么自然可以所有对于波的操作。

  • 进行类似正弦波调幅、调频、调相,还可以上下偏移
    image
    (f(x)=Asin(ωx+φ) + b 这里 A = 0.5, w = 2, φ = 1, b = 0.5)

  • 波的叠加
    image
    傅里叶变换说一个波可以由为n个波叠加而成,叠加结果如图所示。

波形的调整在实际应用中作用很大,如:

  • 模拟生成地图中某个区域的地质运动剧烈,地形起伏很大,可以对波形调幅把振幅调大。
  • 如果想让生成的波形更加连续,可以先调频(倍频)然后叠加

参考链接

OrientDB API v1.0.0(OrientDB 3.x)

Gitee

OrientDB介绍

OrientDB是一个开源的NoSQL数据库管理系统,同时也是一款高性能的图数据库,支持ACID事务以及原子操作。

官网

图数据库介绍

图数据库是以点、边为基础存储单元,以高效存储、查询图数据为设计原理的数据管理系统。

图概念对于图数据库的理解至关重要。图是一组点和边的集合,“点”表示实体,“边”表示实体间的关系。在图数据库中,数据间的关系和数据本身同样重要 ,它们被作为数据的一部分存储起来。这样的架构使图[数据库]能够快速响应复杂关联查询,因为实体间的关系已经提前存储到了数据库中。图数据库可以直观地可视化关系,是[存储]、查询、分析高度[互联数据]的最优办法。

图数据库属于非关系型数据库(NoSQL)。图数据库对数据的存储、查询以及数据结构都和关系型数据库有很大的不同。图数据结构直接存储了节点之间的依赖关系,而关系型数据库和其他类型的非关系型数据库则以非直接的方式来表示数据之间的关系。图数据库把数据间的关联作为数据的一部分进行存储,关联上可添加标签、方向以及属性,而其他数据库针对关系的查询必须在运行时进行具体化操作,这也是图数据库在关系查询上相比其他类型数据库有巨大性能优势的原因。

最简单的说,图数据库对于关系型数据库中的join操作具有十分高的效率,每个节点存储了所有边的引用。

下面我们来看一个示列,加入圆代表一个学校教学楼,圆角矩形代表教学楼下面的教室,三角形代表教室的每一张桌椅,当我们需要获取一个楼宇下面的所有桌椅,在关系型数据库如MySQL中,就需要把教学楼表、教室表以及桌椅表join起来查询,虽然有索引,但当量上去后依然是一个昂贵的操作。而在图数据库如OrientDB中,就可以直接从教学楼出发通过广度优先或者深度优先直接找到桌椅,而且相比关系型数据库具有极高的效率。

1680147172117

OrientDB API

OrientDB是一款高性能的文档、图数据库,在关系查找、遍历方面有很大的速度优势,特别是处理传统关系型数据库中的join操作,图数据库具有无法比拟的优点。虽然OrientDB官方提供了Java的SDK,但是还是有一定的学习成本,需要手撸操作脚本,本仓库对OrientDB的Java SDK进行了二次封装,以更加自然的语言操作OrientDB,降低学习成本,使得项目能更快的集成OrientDB。

一、特性

更简单的API
:话不多说上例子感受,假如我们要保存一个
People
对象到图数据库,先看看原生的SDK例子:

public static void main(String[] args) {
    	//使用原生JDK保存一个“人”顶点到图数据库
        People people1 = new People("张三", "男", 18);
        try (ODatabaseSession session = OrientSessionFactory
                .getInstance()
                .getSession()) {
            //在图数据库创建Class:People
            if (session.getClass("People") == null) {
                session.createVertexClass("People");
            }
            OVertex vertex = session.newInstance();
            vertex.setProperty("name", people1.getName());
            vertex.setProperty("age", people1.getAge());
            vertex.save();
        }
}

原生的SDK将顶点封装成了
Overtex
对象,首先需要先获取会话
ODatabaseSession
,并且创建对应的顶点类,然后将实体相关的属性需要调用
setProperty
方法存入进去,并且保存,还要要留意关闭会话,对于属性多、数量多的实体简直是灾难,下面我们来看看使用OrientDB API:

public static void main(String[] args) {
    	//创建实体对象
        People people2 = new People("李四", "男", 21);
    	//将实体对象包装成ResourceNode对象,其提供了对顶点的操作,对边的操作在ResourceRelation里
    	ResourceNode<People> li = new GraphResourceNode<>(people2);
    	//直接调用save方法进行保存
    	li.save();
}

@Getter
@Setter
@ToString
//实现Vertex语义接口,表明该实体是一个图数据库的顶点对象
public class People implements Vertex {

    private String name;

    private String sex;

    private Integer age;

    public People(String name, String sex, Integer age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    public People() {
    }
}

1680157536700

如上图,通过上面的语句就将实体对象
People1
存入了OrientDB。

更优雅的查询
:原生SDK的查询难免会跟OrientDB的
SQL
语句或者
Match
语句打交道,而且国内的中文文档很少和相关博客也很少,学习成本进一步加大,因此OrientDB API对常用查询操作进行了封装,做到完全透明化,下面我们来看一个示列:使用原生SDK进行查询:

 public static void main(String[] args) {
        try (ODatabaseSession session = OrientSessionFactory
                .getInstance()
                .getSession()) {
            OResultSet resultSet = session.query("select * from People where name = ?", "李四");
            People people=new People();
            while(resultSet.hasNext()){
                OResult result= resultSet.next();
                people.setName(result.getProperty("name"));
                people.setAge(result.getProperty("age"));
            }
            resultSet.close();
        }
 }

原生JDK使用起来跟JDBC差不多,体验差,下面看看OrientDB API查询:

public static void main(String[] args) {
    	//创建资源图对象,其提供了很多对图的直接操作。使用OrientDB存储库(后续可以拓展Neo4j等存储库)
        ResourceGraph graph = new ResourceGraph(new OrientDBRepository());
    	//调用extractNode方法取出指定节点
        ResourceNode<People> peopleResourceNode = graph.extractNode(QueryParamsBuilder
                .newInstance()
                .addParams("name", "李四")
                .getParams());
    	//获取节点对应的属性实体
        People people = peopleResourceNode.getSource();
}

更人性化的遍历
: 单一的查询肯定不能满足实际的需要,OrientDB提供了图的遍历,支持更复杂的查询,通过遍历我们能找到与任意一个节点有某种关系的其他节点,使用原生SDK如下:

public static void main(String[] args) {
        try (ODatabaseSession session = OrientSessionFactory
                .getInstance()
                .getSession()) {
            //从顶点#74:0出发,深度优先遍历7层以内的同学,并且进行分页
            OResultSet resultSet = session.query("select * from (traverse * from #74:0 MAXDEPTH 7 STRATEGY DEPTH_FIRST) where (@class = \"Classmate\") skip 0 limit 10");
            List<ClassMates> classMates=new ArrayList<>();
            while(resultSet.hasNext()){
                ClassMates classMate=new ClassMates(null);
                OResult result= resultSet.next();
                classMate.setDate(result.getProperty("date"));
                //...
                classMates.add(classMate);
            }
            resultSet.close();
        }
}

使用OrientDB API

public static void main(String[] args) {
    	  //创建资源图对象,其提供了很多对图的直接操作。使用OrientDB存储库(后续可以拓展Neo4j等存储库)
           ResourceGraph graph = new ResourceGraph(new OrientDBRepository());
    	   //直接调用traverse方法,参数分别是,出发节点、深度、遍历策略、分页配置、目标实体类型
           PagedResult<ResourceNode<? extends Vertex>> result = graph.traverse(graph.extractNode("#74:0"),
                QueryParamsBuilder
                .newInstance()
                .getParams(), 7, TraverseStrategy.DEPTH_FIRST, new PageConfig(1, 10, true), ClassMates.class);
        
        result
                .getSources()
                .forEach(item -> System.out.println(item.getSource()));
}

无需语句,透明化,更人性化。

二、详细用法

图的元素分为顶点和边两类,在使用图数据库时我们一定要想好以前在关系型数据库中存的数据如何在建立模型,如介绍中的教学楼-教室-桌椅模型。创建好模型后就需要定义顶点实体类,如特性中的
People
类,以及关系类顶点实体类需要实现Vertex接口,关系类需要实现Edge接口。

图数据库的相关配置在
orientdb-config.yml
中下面是一个配置示列:

orientDB:
  #  图数据库IP地址
  url: "localhost"
  #  图数据库库名
  database: "testDB"
  #  访问用户名
  username: "root"
  #  访问密码
  password: "0000"
  #  连接池配置
  pool:
    min: 5
    max: 30
    acquireTimeout: 30

2.1 创建顶点

public static void main(String[] args) {
    	//创建实体对象
        People people2 = new People("李四", "男", 21);
    	//将实体对象包装成ResourceNode对象,其提供了对顶点的操作,对边的操作在ResourceRelation里
    	ResourceNode<People> li = new GraphResourceNode<>(people2);
    	//直接调用save方法进行保存
    	li.save();
}

2.2 创建边

public static void main(String[] args) {
    //创建实体对象
    People people1 = new People("张三", "男", 18);
    People people2 = new People("李四", "男", 21);
    //创建关系对象
    FriendOf friendOf = new FriendOf();
    //封装成ResourceNode对象
    ResourceNode<People> zhang = new GraphResourceNode<>(people1);
    ResourceNode<People> li = new GraphResourceNode<>(people2);
    //封装成ResourceRelation对象
    ResourceRelation<FriendOf> friend = new GraphResourceRelation<>(friendOf);
    //直接调用ResourceRelation的link方法创建有向边,如果顶点还未创建会先创建顶点
    friend.link(zhang, li);
    //或者调用linkUndirected创建无向边,即会创建两条互相指向的边
    friend.linkUndirected(zhang,li)
}

2.3 分页查询

public static void main(String[] args){
    //通过指定PageCofig来实现分页查询,构造函数参数列表分别代表:页号、页大小、是否查询总数
    PagedResult<ResourceNode<? extends Vertex>> pagedResult = graph.extractNodes(QueryParamsBuilder
                .newInstance()
                .addParams("sex", "男")
                .getParams(), new PageConfig(1, 5, true));
        System.out.println("分页结果:"+pagedResult.getSources());
        System.out.println("总条数:"+pagedResult.getTotal());
}

2.4 关系查找

public static void main(String[] args){
    //创建资源图对象,其提供了很多对图的直接操作。使用OrientDB存储库(后续可以拓展Neo4j等存储库)
    ResourceGraph graph = new ResourceGraph(new OrientDBRepository());
    //查找张三的朋友的朋友
     graph
         //获取关系匹配器
          .getGraphResourceNodeMatcher()
         //张三作为起始节点
          .asStart(zhang)
         //查找张三的朋友
          .findUndirected(FriendOf.class)
         //查找张三的朋友的朋友
          .findUndirected(FriendOf.class)
         //将结果收集成People对象
          .collect(People.class)
         //遍历打印
          .forEach(item -> System.out.println(item.getSource()));
}

2.5 最短路径搜索

public static void main(String[] args){
    	//查询张到李的最短路径,参数列表分别是:起始顶点、终止顶点、关系方向,无向边任意方向都可、路径最大深度、路径要经过的边类型
        List<ResourceNode<? extends Vertex>> find4 = graph.shortestPath(zhang, li, RelationDirection.OUT, 3, FriendOf.class);
        find4.forEach(item -> System.out.println(item.getSource()));
}

2.6 属性修改

public static void main(String[] args){
    //修改张的年龄为15岁
    zhang.update(QueryParamsBuilder
                .newInstance()
                .addParams("age", 15)
                .getParams())
}

2.7 元素删除

public static void main(String[] args){
    //移除张
    zhang.remove();
}

2.8 关系重定向

关系重定向意思是将原来的A->B的关系变更成A->C。

public static void main(String[] args){
    ResourceRelation<FriendOf> friend = graph.extractRelation(QueryParamsBuilder
                .newInstance()
                .addParams("date", new Date())
                .getParams());
    //参数列表分别是:要重定向的关系,重定向到的目标
    graph.relink(friend, li);
}

2.9 批量创建关系和顶点

public static void main(String[] args){
    //构建参数key是连接的关系,value是目标顶点
    Map<FriendOf, ResourceNode<People>> toMap = new HashMap<>();
    toMap.put(new FriendOf(), li);
    toMap.put(new FriendOf(), wang);
    toMap.put(new FriendOf(), huang);
    toMap.put(new FriendOf(), hu);
    //调用linksUndirected方法,返回所有的关系
    List<ResourceRelation<? extends Edge>> relations = zhang.linksUndirected(toMap);
    relations.forEach(item -> System.out.println(item.getInEdgeId() + "," + item.getOutEdgeId()));
}

其他更多API正在更新中......