文章作者:jqpeng
原文链接: 记一次keepalived和VIP导致的故障

起因

nginx服务器采用的keepalived+vip实现的双活,最近由于一台服务器有问题,更换了一台nginx:

操作:

  • 停止有问题服务器keepalived和nginx
  • 新服务器部署keepalived和nginx

更换后一切正常,但是过了几个小时,出现大面积的不能访问。

keepalived 升级

检查nginx正常,重启keepalived后OK,怀疑可能是keepalived的问题,于是编译安装最新的keepalived:

curl --progress http://keepalived.org/software/keepalived-1.2.15.tar.gz | tar xz
cd keepalived-1.2.15
./configure --prefix=/usr/local/keepalived-1.2.15
make
sudo make install

升级后,一切正常,。

再出故障,最终定位

一晚过去无异常,第二天又出现部分域名不能访问,检查服务一切正常,因此怀疑是VIP导致的问题,检查之前有问题服务器的ip:

ip addr

果不其然:

2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 90:b1:1c:2a:92:e4 brd ff:ff:ff:ff:ff:ff
    inet 172.31.161.42/32 scope global eno1
       valid_lft forever preferred_lft forever
    inet 172.31.161.41/32 scope global eno1
       valid_lft forever preferred_lft forever
    inet 172.31.161.38/24 brd 172.31.161.255 scope global eno1
       valid_lft forever preferred_lft forever
    inet 172.31.161.42/0 scope global eno1::1
       valid_lft forever preferred_lft forever
3: eno2: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu

41,42 是我们设置的VIP,竟然还在这个有问题服务器的网卡上,这就导致一个机房内,有2台服务器绑定相同的vip。

解决方案, 通过ip addr delete删除绑定的vip

ip addr delete 172.31.161.42/32 dev eno1
ip addr delete 172.31.161.41/32 dev eno1
ip addr delete 172.31.161.41/0 dev eno1

顺道介绍如何给网卡绑定vip:

ip addr add 172.31.161.41/32 dev eno1

溯源与问题总结

问题的根源在于,keepalived为网卡停止后,keepalived为网卡绑定的VIP并没有移除,导致多台机器出现同样的ip。

切记: 停止keepalived,vip不会自动删除,需要手动清理


作者:Jadepeng
出处:jqpeng的技术记事本–http://www.cnblogs.com/xiaoqi
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

文章作者:jqpeng
原文链接: 网易云音乐无版权音乐补全工具

缘起

网易云音乐的不少歌曲因为版权下架了,或者变成收费的,导致无法收听,因此需要一个小工具,希望可以从其他来源补全歌曲。

如图所示,不能听的显示为灰色。

之前写的小工具XMusicDownloader(https://github.com/jadepeng/XMusicDownloader) 可以从多个来源搜索歌曲并下载,因此以这个为基础,可以很快实现需求。

查看本文之前,建议查看开源工具软件XMusicDownloader——音乐下载神器.

歌曲版权声明

  • 该工具本质是歌曲聚合搜索,API来自公开网络,非破解版,只能下载各家公开的音乐付费歌曲不能下载,例如QQ、网易等的收费歌曲不能从QQ\网易源下载,工具的原理是基于聚合实现补全
  • 工具和代码仅供技术研究使用,禁止将本工具用于商业用途,如产生法律纠纷与本人无关,如有侵权,请联系删除

工具原理

  1. 获取用户歌单,找出无版权和收费歌曲
  2. 从QQ、咪咕、百度等源搜索这些歌曲,匹配成功的可以下载
  3. 下载后可以手动上传到云盘

获取用户歌单

借助NeteaseCloudMusicApi,可以方便调用云音乐的api。

分析获取到的json,可以发现,包含noCopyrightRcmd的是没有版权的,包含fee的是收费的,我们可以将这些歌曲提取出来,变为song对象。

private static List<Song> FetchNoCopyrightSongs(JObject json)
        {
            List<Song> noCopyrightsSongs = new List<Song>();
            foreach (JObject songObj in json["songs"])
            {
                int id = 0;
               
                if (songObj["noCopyrightRcmd"].HasValues || songObj["fee"].Value<int>() == 1)
                {
                    noCopyrightsSongs.Add(NeteaseProvider.extractSong(ref id, songObj));
                }
            }

            return noCopyrightsSongs;
        }        public static Song extractSong(ref int index, JToken songItem)
        {
            var song = new Song
            {
                id = (string)songItem["id"],
                name = (string)songItem["name"],

                album = (string)songItem["al"]["name"],
                //rate = 128,
                index = index++,
                //size = (double)songItem["FileSize"],
                source = "网易",
                duration = (double)songItem["dt"] / 1000
            };

            song.singer = "";
            foreach (var ar in songItem["ar"])
            {
                song.singer += ar["name"] + " ";
            }

            if (songItem.Contains("privilege") && songItem["privilege"].HasValues)
            {
                song.rate = ((int)songItem["privilege"]["fl"]) / 1000;
                var fl = (int)songItem["privilege"]["fl"];
                if (songItem["h"] != null && fl >= 320000)
                {
                    song.size = (double)songItem["h"]["size"];
                }
                else if (songItem["m"] != null && fl >= 192000)
                {
                    song.size = (double)songItem["m"]["size"];
                }
                else if (songItem["l"] != null)
                {
                    song.size = (double)songItem["l"]["size"];
                }
            }
            else
            {
                song.rate = 128;
                song.size = 0;
            }

            return song;
        }

从其他来源获取歌曲

在之前的博文开源工具软件XMusicDownloader——音乐下载神器里,我们有一个聚合的搜索歌曲的方法:

 public List<MergedSong> SearchSongs(string keyword, int page, int pageSize)
        {
            var songs = new List<Song>();
            Providers.AsParallel().ForAll(provider =>
            {
                var currentSongs = provider.SearchSongs(keyword, page, pageSize);
                songs.AddRange(currentSongs);
            });

            // merge

            return songs.GroupBy(s => s.getMergedKey()).Select(g => new MergedSong(g.ToList())).OrderByDescending(s => s.score).ToList();
        }

类似的,匹配也是先搜索,但是要排除网易源,然后根据搜索结果去匹配。搜索的时候,可以将 “歌曲名称 + 歌手名称” 组合用来搜索。

  public MergedSong SearchSong(string singer, string songName, string exceptProvider)
        {      // search
            var songs = new List<Song>();
            Providers.AsParallel().ForAll(provider =>
            {
                try
                {
                    if (provider.Name != exceptProvider)
                    {
                        var currentSongs = provider.SearchSongs(singer + " " + songName, 1, 10);
                        songs.AddRange(currentSongs);
                    }
                }
                catch (Exception e)
                {

                }
            });

            // merge

            List<MergedSong> mergedSongs = songs.GroupBy(s => s.getMergedKey()).Select(g => new MergedSong(g.ToList())).OrderByDescending(s => s.score).ToList();         // match
            foreach (MergedSong song in mergedSongs)
            {
                if (song.singer == singer && song.name == songName)
                {
                    return song;
                }
            }

            return null;
        }

软件界面

软件界面,增加用户、密码输入

界面

搜索结果,设置为默认选中:

 List<ListViewItem> listViewItems = new List<ListViewItem>();
            mergedSongs.ForEach(item =>
            {
                ListViewItem lvi = new ListViewItem();
                lvi.Text = item.name;
                lvi.SubItems.Add(item.singer);
                lvi.SubItems.Add(item.rate + "kb");
                lvi.SubItems.Add((item.size / (1024 * 1024)).ToString("F2") + "MB");  //将文件大小装换成MB的单位
                TimeSpan ts = new TimeSpan(0, 0, (int)item.duration); //把秒数换算成分钟数
                lvi.SubItems.Add(ts.Minutes + ":" + ts.Seconds.ToString("00"));
                lvi.SubItems.Add(item.source);
                lvi.Tag = item;
                lvi.Checked = true; // 默认选中
                listViewItems.Add(lvi);
            });

搜索出来后,下载可以完全复用之前逻辑。

下载歌曲使用

下载后的歌曲,可以通过网易云音乐客户端,上传到云盘,然后批量选中,添加到我喜欢的音乐

上传

批量选中后收藏到歌单:

批量收藏

工具下载地址

欢迎关注作者微信公众号, 一起交流软件开发。

欢迎关注作者微信公众号


作者:Jadepeng
出处:jqpeng的技术记事本–http://www.cnblogs.com/xiaoqi
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

文章作者:jqpeng
原文链接: Spring Boot引入swagger-ui 后swagger-ui.html无法访问404

最近给graphserver增加swagger,记录下过程与问题解决。

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务,后端集成下Swagger,然后就可以提供一个在线文档地址给前端同学。

引入 Swagger

pom中加入相关配置:

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

增加Swagger2Config, 添加@EnableSwagger2,可以通过定义Docket bean实现自定义。

@Configuration
@EnableSwagger2
@Profile("swagger")
@ComponentScan("xxx.controller")
public class Swagger2Config {

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .enable(true)
            .select()
            .apis(RequestHandlerSelectors.basePackage("xxx.controller"))
            .paths(PathSelectors.any())
            .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
            .title("XXX Rest Server")
            .description("XXXRest接口")
            .contact(new Contact("contract", "url", "email"))
            .version("1.0")
            .build();
    }
}

swagger-ui.html 404问题

项目中有web配置,因此怀疑是这些配置影响了,搜索下发现这位仁兄有类似经历:https://www.cnblogs.com/pangguoming/p/10551895.html

于是在WebMvcConfig 配置中,override addResourceHandlers

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
        registry.addResourceHandler("swagger-ui.html")
            .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
            .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

搞定收工。

延伸阅读

server端有了swagger,前端如何更优先的调用?

参见:Vue 使用typescript, 优雅的调用swagger API,笔者提供了一个开源npm库,可以为前端生成调用axios调用代码。

参考

文章作者:jqpeng
原文链接: 图数据库之TinkerPop Provider

Apache TinkerPop 提供了图数据库的抽象接口,方便第三方实现自己的图数据库以接入TinkerPop 技术栈,享受TinkerPop 的Gremlin、算法等福利。TinkerPop将这些第三方称为“Provider ”,知名的Provider包含janusGraph、neo4j、hugegraph等。

Provider包含:

  • Graph System Provider

    • Graph Database Provider
    • Graph Processor Provider
  • Graph Driver Provider

  • Graph Language Provider

  • Graph Plugin Provider

Graph Structure API(图谱数据结构)

Graph最高层的抽象数据结构包含 Graph(图), Vertex(顶点), Edge(边), VertexProperty(属性) and Property.

基于这些基础数据结构,就可以对进行基本的图谱操作。

Graph graph = TinkerGraph.open(); //1
Vertex marko = graph.addVertex(T.label, "person", T.id, 1, "name", "marko", "age", 29); //2
Vertex vadas = graph.addVertex(T.label, "person", T.id, 2, "name", "vadas", "age", 27);
Vertex lop = graph.addVertex(T.label, "software", T.id, 3, "name", "lop", "lang", "java");
Vertex josh = graph.addVertex(T.label, "person", T.id, 4, "name", "josh", "age", 32);
Vertex ripple = graph.addVertex(T.label, "software", T.id, 5, "name", "ripple", "lang", "java");
Vertex peter = graph.addVertex(T.label, "person", T.id, 6, "name", "peter", "age", 35);
marko.addEdge("knows", vadas, T.id, 7, "weight", 0.5f); //3
marko.addEdge("knows", josh, T.id, 8, "weight", 1.0f);
marko.addEdge("created", lop, T.id, 9, "weight", 0.4f);
josh.addEdge("created", ripple, T.id, 10, "weight", 1.0f);
josh.addEdge("created", lop, T.id, 11, "weight", 0.4f);
peter.addEdge("created", lop, T.id, 12, "weight", 0.2f);
  1. 创建一个基于内存存储的TinkerGraph 实例(TinkerGraph是官方实现的,基于内存的Graph)

2 .创建一个顶点

  1. 创建边

上面的代码构建了一个基本的图,下面的代码演示如何进行图谱的操作。

图谱操作

实现 Gremlin-Core

一个标准的Graph Provider需要实现OLTP 和OLAP两类接口,官方推荐学习TinkerGraph(in-memory OLTP and OLAP in tinkergraph-gremlin),以及 Neo4jGraph (OLTP w/ transactions in neo4j-gremlin) ,还有
Neo4jGraph (OLTP w/ transactions in neo4j-gremlin) ,还有 HadoopGraph (OLAP in hadoop-gremlin) 。

  1. 在线事务处理 Graph Systems (OLTP)
1.  数据结构 API: `Graph`, `Element`, `Vertex`, `Edge`, `Property` and `Transaction` (if transactions are supported).

2.  处理API : `TraversalStrategy` instances for optimizing Gremlin traversals to the provider’s graph system (i.e. `TinkerGraphStepStrategy`).
  1. 在线分析 图系统 (OLAP)

    1. Everything required of OLTP is required of OLAP (but not vice versa).
    2. GraphComputer API: GraphComputerMessengerMemory.

OLTP 实现

需要实现structure包下的interface,包含GraphVertexEdgePropertyTransaction等等。

  • Graph实现时,需要命名为XXXGraph (举例: TinkerGraph, Neo4jGraph, HadoopGraph, etc.).
    • 需要兼容GraphFactory ,也就是提供一个静态的 Graph open(Configuration) 方法。

OLAP 实现

需要实现:

  1. GraphComputer: 图计算器,提供隔离环境,执行VertexProgram,和MapReduce任务.
  2. Memory: A global blackboard for ANDing, ORing, INCRing, and SETing values for specified keys.
  3. Messenger: The system that collects and distributes messages being propagated by vertices executing the VertexProgram application.
  4. MapReduce.MapEmitter: The system that collects key/value pairs being emitted by the MapReduce applications map-phase.
  5. MapReduce.ReduceEmitter: The system that collects key/value pairs being emitted by the MapReduce applications combine- and reduce-phases.

作者:Jadepeng
出处:jqpeng的技术记事本–http://www.cnblogs.com/xiaoqi
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

文章作者:jqpeng
原文链接: 图数据库HugeGraph源码解读 (1) —— 入门介绍

HugeGraph介绍

以下引自官方文档:

HugeGraph是一款易用、高效、通用的开源图数据库系统(Graph Database,GitHub项目地址), 实现了Apache TinkerPop3框架及完全兼容Gremlin查询语言, 具备完善的工具链组件,助力用户轻松构建基于图数据库之上的应用和产品。HugeGraph支持百亿以上的顶点和边快速导入,并提供毫秒级的关联关系查询能力(OLTP), 并可与Hadoop、Spark等大数据平台集成以进行离线分析(OLAP)。

HugeGraph典型应用场景包括深度关系探索、关联分析、路径搜索、特征抽取、数据聚类、社区检测、 知识图谱等,适用业务领域有如网络安全、电信诈骗、金融风控、广告推荐、社交网络和智能机器人等。

划重点:

  • 基于TinkerPop3框架,兼容Gremlin查询语言
  • OLTP(开源) 与 OLAP(商业版)
  • 常用图应用支持—— 路径搜索、推荐等

架构介绍

架构图

HugeGraph包括三个层次的功能,分别是存储层、计算层和用户接口层。 HugeGraph支持OLTP和OLAP两种图计算类型

HugeGraph架构

组件

HugeGraph的主要功能分为HugeCore、ApiServer、HugeGraph-Client、HugeGraph-Loader和HugeGraph-Studio等组件构成,各组件之间的通信关系如下图所示。

组件

其中核心组件:

  • HugeCore :HugeGraph的核心模块,TinkerPop的接口主要在该模块中实现。
  • ApiServer :提供RESTFul Api接口,对外提供Graph Api、Schema Api和Gremlin Api等接口服务。
  • HugeGraph-Client:基于Java客户端驱动程序

生态组件:

  • HugeGraph-Loader:数据导入模块。HugeGraph-Loader可以扫描并分析现有数据,自动生成Graph Schema创建语言,通过批量方式快速导入数据。
  • HugeGraph-Studio:基于Web的可视化IDE环境。以Notebook方式记录Gremlin查询,可视化展示Graph的关联关系。HugeGraph-Studio也是本系统推荐的工具。

HugeGraph-Studio 看起来已经被抛弃了,研发团队正开发一个名为’hugegraph-hubble’ 的新项目:

hugegraph-hubble is a graph management and analysis platform that provides features: graph data load, schema management, graph relationship analysis and graphical display.

根据官方的说明,hubble定义为图谱管理和分析平台,提供图谱数据加载、schema管理、图分析和可视化展示,目前正在研发中,预计2020年9月份会发布首个版本。

设计理念

常见的图数据表示模型有两种:

  • RDF(Resource Description Framework)模型: 学术界的选择,通过sparql来进行查询,jenagStore等等
  • 属性图(Property Graph)模型,工业界的选择,neo4jjanusgraph都是这种方案。

RDF是W3C标准,而Property Graph是工业标准,受到广大图数据库厂商的广泛支持。HugeGraph采用Property Graph,遵循工业标准。

HugeGraph存储概念模型详见下图:

HugeGraph概念模型

主要包含几个部分:

  • Vertex(顶点),对应一个实体(Entity)
  • Vertex Label(顶点的类型),对应一个概念(Concept)
  • 属性(图里的name、age),PropertyKey
  • Edge边(图里的lives),对应RDF里的Relation

可扩展性

HugeGraph提供了丰富的插件扩展机制,包含几个维度的扩展项:

  • 后端存储
  • 序列化器
  • 自定义配置项
  • 分词器

插件实现机制

  1. HugeGraph提供插件接口HugeGraphPlugin,通过Java SPI机制支持插件化
  2. HugeGraph提供了4个扩展项注册函数:registerOptions()registerBackend()registerSerializer()registerAnalyzer()
  3. 插件实现者实现相应的Options、Backend、Serializer或Analyzer的接口
  4. 插件实现者实现HugeGraphPlugin接口的register()方法,在该方法中注册上述第3点所列的具体实现类,并打成jar包
  5. 插件使用者将jar包放在HugeGraph Server安装目录的plugins目录下,修改相关配置项为插件自定义值,重启即可生效

从案例深入源码

想要深入的理解一个系统的源码,先从具体的应用入手。先查看example代码:

https://github.com/hugegraph/hugegraph/blob/master/hugegraph-example/src/main/java/com/baidu/hugegraph/example/Example1.java

 public static void main(String[] args) throws Exception {
        LOG.info("Example1 start!");

        HugeGraph graph = ExampleUtil.loadGraph();

        Example1.showFeatures(graph);

        Example1.loadSchema(graph);
        Example1.loadData(graph);
        Example1.testQuery(graph);
        Example1.testRemove(graph);
        Example1.testVariables(graph);
        Example1.testLeftIndexProcess(graph);

        Example1.thread(graph);

        graph.close();

        HugeFactory.shutdown(30L);
    }

1. loadGraph

要使用hugegraph,需要先初始化一个HugeGraph对象,而LoadGraph 正是做这个的。

loadGraph

public static HugeGraph loadGraph(boolean needClear, boolean needProfile) {
        if (needProfile) {
            profile();
        }

        registerPlugins();

        String conf = "hugegraph.properties";
        try {
            String path = ExampleUtil.class.getClassLoader()
                                     .getResource(conf).getPath();
            File file = new File(path);
            if (file.exists() && file.isFile()) {
                conf = path;
            }
        } catch (Exception ignored) {
        }

        HugeGraph graph = HugeFactory.open(conf);

        if (needClear) {
            graph.clearBackend();
        }
        graph.initBackend();

        return graph;
    }
1.1 registerPlugins

其中 registerPlugins 注册插件,注意上面介绍的扩展机制。hugegraph所有的后端存储都需要通过插件注册。

 public static void registerPlugins() {
        if (registered) {
            return;
        }
        registered = true;

        RegisterUtil.registerCassandra();
        RegisterUtil.registerScyllaDB();
        RegisterUtil.registerHBase();
        RegisterUtil.registerRocksDB();
        RegisterUtil.registerMysql();
        RegisterUtil.registerPalo();
    }

注册主要是register配置、序列化器和backend,比如下面是mysql的。

public static void registerMysql() {
        // Register config
        OptionSpace.register("mysql",
                "com.baidu.hugegraph.backend.store.mysql.MysqlOptions");
        // Register serializer
        SerializerFactory.register("mysql",
                "com.baidu.hugegraph.backend.store.mysql.MysqlSerializer");
        // Register backend
        BackendProviderFactory.register("mysql",
                "com.baidu.hugegraph.backend.store.mysql.MysqlStoreProvider");
    }
1.2 HugeFactory.open

HugeFactory 是Hugraph的工厂类,支持传入Configuraion配置信息,构建一个HugeGraph实例,注意这里为了线程安全,签名采用synchronized

 public static synchronized HugeGraph open(Configuration config) {
        HugeConfig conf = config instanceof HugeConfig ?
                          (HugeConfig) config : new HugeConfig(config);
        String name = conf.get(CoreOptions.STORE);
        checkGraphName(name, "graph config(like hugegraph.properties)");
        name = name.toLowerCase();
        HugeGraph graph = graphs.get(name);
        if (graph == null || graph.closed()) {
            graph = new StandardHugeGraph(conf);
            graphs.put(name, graph);
        } else {
            String backend = conf.get(CoreOptions.BACKEND);
            E.checkState(backend.equalsIgnoreCase(graph.backend()),
                         "Graph name '%s' has been used by backend '%s'",
                         name, graph.backend());
        }
        return graph;
    }

这里顺带提下配置文件,通过代码看到,默认是读取hugegraph.properties.

1.3 HugeGraph 对象

HugeGraph是一个interface,继承gremlin的Graph接口,定义了图谱的Schema定义、数据存储、查询等API方法。从上面1.2可以看到,默认的实现是StandardHugeGraph

public interface HugeGraph extends Graph {

    public HugeGraph hugegraph();

    public SchemaManager schema();

    public Id getNextId(HugeType type);

    public void addPropertyKey(PropertyKey key);
    public void removePropertyKey(Id key);
    public Collection<PropertyKey> propertyKeys();
    public PropertyKey propertyKey(String key);
    public PropertyKey propertyKey(Id key);
    public boolean existsPropertyKey(String key);

...
   
1.4 graph.clearBackend 与initBackend

clearBackend将后端数据清理,initBackend初始化基本的数据结构。

2. loadSchema

该方法,用来定义schema:

public static void loadSchema(final HugeGraph graph) {

        SchemaManager schema = graph.schema();

        // Schema changes will be commit directly into the back-end
        LOG.info("===============  propertyKey  ================");
        schema.propertyKey("id").asInt().create();
        schema.propertyKey("name").asText().create();
        schema.propertyKey("gender").asText().create();
        schema.propertyKey("instructions").asText().create();
        schema.propertyKey("category").asText().create();
        schema.propertyKey("year").asInt().create();
        schema.propertyKey("time").asText().create();
        schema.propertyKey("timestamp").asDate().create();
        schema.propertyKey("ISBN").asText().create();
        schema.propertyKey("calories").asInt().create();
        schema.propertyKey("amount").asText().create();
        schema.propertyKey("stars").asInt().create();
        schema.propertyKey("age").asInt().valueSingle().create();
        schema.propertyKey("comment").asText().valueSet().create();
        schema.propertyKey("contribution").asText().valueSet().create();
        schema.propertyKey("nickname").asText().valueList().create();
        schema.propertyKey("lived").asText().create();
        schema.propertyKey("country").asText().valueSet().create();
        schema.propertyKey("city").asText().create();
        schema.propertyKey("sensor_id").asUUID().create();
        schema.propertyKey("versions").asInt().valueList().create();

        LOG.info("===============  vertexLabel  ================");

        schema.vertexLabel("person")
              .properties("name", "age", "city")
              .primaryKeys("name")
              .create();
        schema.vertexLabel("author")
              .properties("id", "name", "age", "lived")
              .primaryKeys("id").create();
        schema.vertexLabel("language").properties("name", "versions")
              .primaryKeys("name").create();
        schema.vertexLabel("recipe").properties("name", "instructions")
              .primaryKeys("name").create();
        schema.vertexLabel("book").properties("name")
              .primaryKeys("name").create();
        schema.vertexLabel("reviewer").properties("name", "timestamp")
              .primaryKeys("name").create();

        // vertex label must have the properties that specified in primary key
        schema.vertexLabel("FridgeSensor").properties("city")
              .primaryKeys("city").create();

        LOG.info("===============  vertexLabel & index  ================");
        schema.indexLabel("personByCity")
              .onV("person").secondary().by("city").create();
        schema.indexLabel("personByAge")
              .onV("person").range().by("age").create();

        schema.indexLabel("authorByLived")
              .onV("author").search().by("lived").create();

        // schemaManager.getVertexLabel("author").index("byName").secondary().by("name").add();
        // schemaManager.getVertexLabel("recipe").index("byRecipe").materialized().by("name").add();
        // schemaManager.getVertexLabel("meal").index("byMeal").materialized().by("name").add();
        // schemaManager.getVertexLabel("ingredient").index("byIngredient").materialized().by("name").add();
        // schemaManager.getVertexLabel("reviewer").index("byReviewer").materialized().by("name").add();

        LOG.info("===============  edgeLabel  ================");

        schema.edgeLabel("authored").singleTime()
              .sourceLabel("author").targetLabel("book")
              .properties("contribution", "comment")
              .nullableKeys("comment")
              .create();

        schema.edgeLabel("write").multiTimes().properties("time")
              .sourceLabel("author").targetLabel("book")
              .sortKeys("time")
              .create();

        schema.edgeLabel("look").multiTimes().properties("timestamp")
              .sourceLabel("person").targetLabel("book")
              .sortKeys("timestamp")
              .create();

        schema.edgeLabel("created").singleTime()
              .sourceLabel("author").targetLabel("language")
              .create();

        schema.edgeLabel("rated")
              .sourceLabel("reviewer").targetLabel("recipe")
              .create();
    }

划重点:

  • SchemaManager schema = graph.schema() 获取SchemaManager
  • schema.propertyKey(NAME).asXXType().create() 创建属性
  • schema.vertexLabel(“person”) // 定义概念
    .properties(“name”, “age”, “city”) // 定义概念的属性
    .primaryKeys(“name”) // 定义primary Keys,primary Key组合后可以唯一确定一个实体
    .create();
  • schema.indexLabel(“personByCity”).onV(“person”).secondary().by(“city”).create(); 定义索引
  • schema.edgeLabel(“authored”).singleTime()
    .sourceLabel(“author”).targetLabel(“book”)
    .properties(“contribution”, “comment”)
    .nullableKeys(“comment”)
    .create(); // 定义关系

3. loadData

创建实体,注意格式,K-V成对出现:

graph.addVertex(T.label, "book", "name", "java-3");

创建关系,Vertex的addEdge方法:

    Vertex james = tx.addVertex(T.label, "author", "id", 1,
                                "name", "James Gosling",  "age", 62,
                                "lived", "San Francisco Bay Area");

    Vertex java = tx.addVertex(T.label, "language", "name", "java",
                               "versions", Arrays.asList(6, 7, 8));
    Vertex book1 = tx.addVertex(T.label, "book", "name", "java-1");
    Vertex book2 = tx.addVertex(T.label, "book", "name", "java-2");
    Vertex book3 = tx.addVertex(T.label, "book", "name", "java-3");

    james.addEdge("created", java);
    james.addEdge("authored", book1,
                  "contribution", "1990-1-1",
                  "comment", "it's a good book",
                  "comment", "it's a good book",
                  "comment", "it's a good book too");
    james.addEdge("authored", book2, "contribution", "2017-4-28");

    james.addEdge("write", book2, "time", "2017-4-28");
    james.addEdge("write", book3, "time", "2016-1-1");
    james.addEdge("write", book3, "time", "2017-4-28");    

添加后,需要commit

4. testQuery 测试查询

查询主要通过GraphTraversal, 可以通过graph.traversal()获得:

public static void testQuery(final HugeGraph graph) {
        // query all
        GraphTraversal<Vertex, Vertex> vertices = graph.traversal().V();
        int size = vertices.toList().size();
        assert size == 12;
        System.out.println(">>>> query all vertices: size=" + size);

        // query by label
        vertices = graph.traversal().V().hasLabel("person");
        size = vertices.toList().size();
        assert size == 5;
        System.out.println(">>>> query all persons: size=" + size);

        // query vertex by primary-values
        vertices = graph.traversal().V().hasLabel("author").has("id", 1);
        List<Vertex> vertexList = vertices.toList();
        assert vertexList.size() == 1;
        System.out.println(">>>> query vertices by primary-values: " +
                           vertexList);

        VertexLabel author = graph.schema().getVertexLabel("author");
        String authorId = String.format("%s:%s", author.id().asString(), "11");

        // query vertex by id and query out edges
        vertices = graph.traversal().V(authorId);
        GraphTraversal<Vertex, Edge> edgesOfVertex = vertices.outE("created");
        List<Edge> edgeList = edgesOfVertex.toList();
        assert edgeList.size() == 1;
        System.out.println(">>>> query edges of vertex: " + edgeList);

        vertices = graph.traversal().V(authorId);
        vertexList = vertices.out("created").toList();
        assert vertexList.size() == 1;
        System.out.println(">>>> query vertices of vertex: " + vertexList);

        // query edge by sort-values
        vertices = graph.traversal().V(authorId);
        edgesOfVertex = vertices.outE("write").has("time", "2017-4-28");
        edgeList = edgesOfVertex.toList();
        assert edgeList.size() == 2;
        System.out.println(">>>> query edges of vertex by sort-values: " +
                           edgeList);

        // query vertex by condition (filter by property name)
        ConditionQuery q = new ConditionQuery(HugeType.VERTEX);
        PropertyKey age = graph.propertyKey("age");
        q.key(HugeKeys.PROPERTIES, age.id());
        if (graph.backendStoreFeatures()
                 .supportsQueryWithContainsKey()) {
            Iterator<Vertex> iter = graph.vertices(q);
            assert iter.hasNext();
            System.out.println(">>>> queryVertices(age): " + iter.hasNext());
            while (iter.hasNext()) {
                System.out.println(">>>> queryVertices(age): " + iter.next());
            }
        }

        // query all edges
        GraphTraversal<Edge, Edge> edges = graph.traversal().E().limit(2);
        size = edges.toList().size();
        assert size == 2;
        System.out.println(">>>> query all edges with limit 2: size=" + size);

        // query edge by id
        EdgeLabel authored = graph.edgeLabel("authored");
        VertexLabel book = graph.schema().getVertexLabel("book");
        String book1Id = String.format("%s:%s", book.id().asString(), "java-1");
        String book2Id = String.format("%s:%s", book.id().asString(), "java-2");

        String edgeId = String.format("S%s>%s>%s>S%s",
                                      authorId, authored.id(), "", book2Id);
        edges = graph.traversal().E(edgeId);
        edgeList = edges.toList();
        assert edgeList.size() == 1;
        System.out.println(">>>> query edge by id: " + edgeList);

        Edge edge = edgeList.get(0);
        edges = graph.traversal().E(edge.id());
        edgeList = edges.toList();
        assert edgeList.size() == 1;
        System.out.println(">>>> query edge by id: " + edgeList);

        // query edge by condition
        q = new ConditionQuery(HugeType.EDGE);
        q.eq(HugeKeys.OWNER_VERTEX, IdGenerator.of(authorId));
        q.eq(HugeKeys.DIRECTION, Directions.OUT);
        q.eq(HugeKeys.LABEL, authored.id());
        q.eq(HugeKeys.SORT_VALUES, "");
        q.eq(HugeKeys.OTHER_VERTEX, IdGenerator.of(book1Id));

        Iterator<Edge> edges2 = graph.edges(q);
        assert edges2.hasNext();
        System.out.println(">>>> queryEdges(id-condition): " +
                           edges2.hasNext());
        while (edges2.hasNext()) {
            System.out.println(">>>> queryEdges(id-condition): " +
                               edges2.next());
        }

        // NOTE: query edge by has-key just supported by Cassandra
        if (graph.backendStoreFeatures().supportsQueryWithContainsKey()) {
            PropertyKey contribution = graph.propertyKey("contribution");
            q.key(HugeKeys.PROPERTIES, contribution.id());
            Iterator<Edge> edges3 = graph.edges(q);
            assert edges3.hasNext();
            System.out.println(">>>> queryEdges(contribution): " +
                               edges3.hasNext());
            while (edges3.hasNext()) {
                System.out.println(">>>> queryEdges(contribution): " +
                                   edges3.next());
            }
        }

        // query by vertex label
        vertices = graph.traversal().V().hasLabel("book");
        size = vertices.toList().size();
        assert size == 5;
        System.out.println(">>>> query all books: size=" + size);

        // query by vertex label and key-name
        vertices = graph.traversal().V().hasLabel("person").has("age");
        size = vertices.toList().size();
        assert size == 5;
        System.out.println(">>>> query all persons with age: size=" + size);

        // query by vertex props
        vertices = graph.traversal().V().hasLabel("person")
                        .has("city", "Taipei");
        vertexList = vertices.toList();
        assert vertexList.size() == 1;
        System.out.println(">>>> query all persons in Taipei: " + vertexList);

        vertices = graph.traversal().V().hasLabel("person").has("age", 19);
        vertexList = vertices.toList();
        assert vertexList.size() == 1;
        System.out.println(">>>> query all persons age==19: " + vertexList);

        vertices = graph.traversal().V().hasLabel("person")
                        .has("age", P.lt(19));
        vertexList = vertices.toList();
        assert vertexList.size() == 1;
        assert vertexList.get(0).property("age").value().equals(3);
        System.out.println(">>>> query all persons age<19: " + vertexList);

        String addr = "Bay Area";
        vertices = graph.traversal().V().hasLabel("author")
                        .has("lived", Text.contains(addr));
        vertexList = vertices.toList();
        assert vertexList.size() == 1;
        System.out.println(String.format(">>>> query all authors lived %s: %s",
                           addr, vertexList));
    }

划重点

查询指定label的实体:
 vertices = graph.traversal().V().hasLabel("person");
 size = vertices.toList().size();
根据primary-values查询实体:
 vertices = graph.traversal().V().hasLabel("author").has("id", 1);
        List<Vertex> vertexList = vertices.toList();
查询edge:

查询所有edge:

GraphTraversal<Edge, Edge> edges = graph.traversal().E().limit(2);

根据ID查询edge:

    EdgeLabel authored = graph.edgeLabel("authored");
    VertexLabel book = graph.schema().getVertexLabel("book");
    String book1Id = String.format("%s:%s", book.id().asString(), "java-1");
    String book2Id = String.format("%s:%s", book.id().asString(), "java-2");

    String edgeId = String.format("S%s>%s>%s>S%s",
                                  authorId, authored.id(), "", book2Id);
    edges = graph.traversal().E(edgeId);

注意,edge的id由几个字段拼接起来的: “S%s>%s>%s>S%s”,authorId, authored.id(), “”, book2Id)

根据条件查询edge:

 q = new ConditionQuery(HugeType.EDGE);
        q.eq(HugeKeys.OWNER_VERTEX, IdGenerator.of(authorId));
        q.eq(HugeKeys.DIRECTION, Directions.OUT);
        q.eq(HugeKeys.LABEL, authored.id());
        q.eq(HugeKeys.SORT_VALUES, "");
        q.eq(HugeKeys.OTHER_VERTEX, IdGenerator.of(book1Id));

        Iterator<Edge> edges2 = graph.edges(q);
        assert edges2.hasNext();
        System.out.println(">>>> queryEdges(id-condition): " +
                           edges2.hasNext());
        while (edges2.hasNext()) {
            System.out.println(">>>> queryEdges(id-condition): " +
                               edges2.next());
        }

可以指定DIRECTION,

5. 删除

删除Vetex,调用vetex自带的remove方法

       // remove vertex (and its edges)
        List<Vertex> vertices = graph.traversal().V().hasLabel("person")
                                     .has("age", 19).toList();
        assert vertices.size() == 1;
        Vertex james = vertices.get(0);
        Vertex book6 = graph.addVertex(T.label, "book", "name", "java-6");
        james.addEdge("look", book6, "timestamp", "2017-5-2 12:00:08.0");
        james.addEdge("look", book6, "timestamp", "2017-5-3 12:00:08.0");
        graph.tx().commit();
        assert graph.traversal().V(book6.id()).bothE().hasNext();
        System.out.println(">>>> removing vertex: " + james);
        james.remove();
        graph.tx().commit();
        assert !graph.traversal().V(james.id()).hasNext();
        assert !graph.traversal().V(book6.id()).bothE().hasNext();

    

删除关系,也类似:

    // remove edge
        VertexLabel author = graph.schema().getVertexLabel("author");
        String authorId = String.format("%s:%s", author.id().asString(), "11");
        EdgeLabel authored = graph.edgeLabel("authored");
        VertexLabel book = graph.schema().getVertexLabel("book");
        String book2Id = String.format("%s:%s", book.id().asString(), "java-2");

        String edgeId = String.format("S%s>%s>%s>S%s",
                                      authorId, authored.id(), "", book2Id);

        List <Edge> edges = graph.traversal().E(edgeId).toList();
        assert edges.size() == 1;
        Edge edge = edges.get(0);
        System.out.println(">>>> removing edge: " + edge);
        edge.remove();
        graph.tx().commit();
        assert !graph.traversal().E(edgeId).hasNext();

小结

本文初步介绍了hugegraph设计理念、基本使用等。


作者:Jadepeng
出处:jqpeng的技术记事本–http://www.cnblogs.com/xiaoqi
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

文章作者:jqpeng
原文链接: 使用科大讯飞TTS自定义彩虹屁语音包

rainbow-fart-tts

彩虹屁插件红了后,你是否想生成定义语音包呢?本文给出一个解决方案,使用科大讯飞的TTS生成彩虹屁语音包,你可以完全自定义文本,自定义发音人哦!

开源地址: https://github.com/jadepeng/rainbow-fart-tts

修改文本

打开manifest.json, 修改text,可以按需增加keyword和对应text

 {
      "keywords": [
        "if",
        "else"
      ],
      "text": [
        "你就是因为想太多如果,所以才交不到女朋友吧?",
        " 别试了,我的可爱不需要用 if 来判断!",
        " 人生没有那么多如果,有没有比编程更容易呢?"
      ]
 }

获取开发者账号

https://www.xfyun.cn/ 注册账号,创建应用,然后开通语音合成,可以开通免费包,好使的话可以购买套餐哦。

讯飞tts免费包

然后到控制面板,查看appid等信息:

appid

然后打开VoicePackageMakerApp,将对应的信息填入:

public class VoicePackageMakerApp {
    private static final String hostUrl = "https://tts-api.xfyun.cn/v2/tts";

    // 到控制台-语音合成页面获取
    private static final String APPID = "";

    // 到控制台-语音合成页面获取
    private static final String API_SECRET = "";

    //到控制台-语音合成页面获取
    private static final String API_KEY = "";

选取发音人

讯飞开放平台的在线语音合成有很多发音人,可以到https://www.xfyun.cn/services/online_tts 查看:

发音人

选择自己心仪的,然后到控制面板开通权限:

开通发音人

比如我选择的讯飞玲姐姐(志林姐姐),发音人是x_xiaoling,修改代码:

public class VoicePackageMakerApp {


    // 默认发音人
    private static final String DEFAULT_VCN = "x_xiaoling";

生成和使用语音包

上面步骤做完后,直接运行VoicePackageMakerApp即可,然后在voicePackages目录下会生成x_xiaoling文件夹,里面是合成的语音包,可以给各个版本的彩虹屁插件使用。

使用语音包

本文开源地址: https://github.com/jadepeng/rainbow-fart-tts

IDE版本的语音包请参见:https://github.com/jadepeng/idea-rainbow-fart


作者:Jadepeng
出处:jqpeng的技术记事本–http://www.cnblogs.com/xiaoqi
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

文章作者:jqpeng
原文链接: webpack升级到4.x 不完全指南

最近在团队推行ts,顺便将webpack做了升级,升级到最新的4.X版本,下面记录一些迁移指南。

VueLoader

VueLoaderPlugin,显示的引用:

const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
  module: {
    rules: [
      // ... other rules
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
    // make sure to include the plugin!
    new VueLoaderPlugin()
  ]
}

less问题

less需要单独的配置规则,注意一定要加上vue-style-loader,否则样式不生效

{
  module: {
    rules: [
      // ... other rules
      {
        test: /\.less$/,
        use: [
          'vue-style-loader',
          'css-loader',
          'less-loader'
        ]
      }
    ]
  }
}

ts 支持

加上ts-loader

 {
            test: /\.tsx?$/,
            loader: 'ts-loader',
            exclude: /node_modules/,
            options: {
                appendTsSuffixTo: [/\.vue$/],
            }
        },

UglifyJsPlugin

UglifyJsPlugin 配置位置发生了变化,放到optimization里,如果不想开启minimize,可以配置minimize:false,开启优化的话,可以配置minimizer:

// webpack.optimize.UglifyJsPlugin
    optimization: {
        // minimize: false,
        minimizer: [
            new UglifyJsPlugin({
                cache: true,
                parallel: true,
                uglifyOptions: {
                    compress: false
                },
                sourceMap: false
            })
        ]
    }

CopyWebpackPlugin

配置发生了变化:

        // copy custom static assets
        new CopyWebpackPlugin({
            patterns: [{
                from: path.resolve(__dirname, '../static'),
                to: config.dev.assetsSubDirectory
            }]
        })

ExtractTextPlugin

之前:

 new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css'),
      allChunks: true,
    }),

contenthash已不能使用,换成hash即可

    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].[hash].css'),
      allChunks: true,
    }),

文章作者:jqpeng
原文链接: npm 更新package.json中依赖包版本

NPM可以使用npm-check-updates库更新版本

1、安装:
cnpm install -g npm-check-updates

2、使用:

  ncu --timeout=10000000 -u

指定–timeout参数防止超时

  1. 更新全部到最新版本:
    cnpm install

为了防止版本冲突,可以先讲node_modules删掉

文章作者:jqpeng
原文链接: Vue 使用typescript, 优雅的调用swagger API

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务,后端集成下Swagger,然后就可以提供一个在线文档地址给前端同学。

swagger

前端如何优雅的调用呢?

入门版

根据文档,用axios自动来调用

// 应用管理相关接口
import axios from '../interceptors.js'

// 获取应用列表
export const getList = (data) => {
  return axios({
    url: '/app/list?sort=createdDate,desc',
    method: 'get',
    params: data
  })
}

这里的问题是,有多少个接口,你就要编写多少个函数,且数据结构需要查看文档获取。

进阶版本

使用typescript,编写API,通过Type定义数据结构,进行约束。

问题: 还是需要手写

优雅版本

swagger 其实是一个json-schema描述文档,我们可以基于此,自动生成。

很早之前,写过一个插件 generator-swagger-2-t, 简单的实现了将swagger生成typescript api。

今天,笔者对这个做了升级,方便支持后端返回的泛型数据结构。

安装

需要同时安装 Yeoman 和 -swagger-2-ts

npm install -g generator-swagger-2-ts

然后cd到你的工作目录,执行:

yo swagger-2-ts

按提示

也可以通过命令行直接传递参数

 yo swagger-2-ts --swaggerUrl=http://localhost:8080/swagger-ui.html --className=API --type=typescript --outputFile=api.ts
  • swaggerUrl: swagger ui url swaggerui地址
  • className: API class name 类名
  • type: typescript or javascipt
  • outputFile: api 文件保存路径

生成代码demo:

export type AccountUserInfo = {
  disableTime?: string
  isDisable?: number
  lastLoginIp?: string
  lastLoginPlace?: string
  lastLoginTime?: string
  openId?: string
}


export type BasePayloadResponse = {
  data?: object
  desc?: string
  retcode?: string

}

/**
 * User Account Controller
 * @class UserAccountAPI
 */
export class UserAccountAPI {
/**
  * changeUserState
  * @method
  * @name UserAccountAPI#changeUserState
  
  * @param  accountUserInfo - accountUserInfo 
  
  * @param $domain API域名,没有指定则使用构造函数指定的
  */
  changeUserState(parameters: {
    'accountUserInfo': AccountUserInfo,
    $queryParameters?: any,
    $domain?: string
  }): Promise<AxiosResponse<BasePayloadResponse>> {

    let config: AxiosRequestConfig = {
      baseURL: parameters.$domain || this.$defaultDomain,
      url: '/userAccount/changeUserState',
      method: 'PUT'
    }

    config.headers = {}
    config.params = {}

    config.headers[ 'Accept' ] = '*/*'
    config.headers[ 'Content-Type' ] = 'application/json'

    config.data = parameters.accountUserInfo
    return axios.request(config)
  }

  _UserAccountAPI: UserAccountAPI = null;

  /**
  * 获取 User Account Controller API
  * return @class UserAccountAPI
  */
  getUserAccountAPI(): UserAccountAPI {
    if (!this._UserAccountAPI) {
      this._UserAccountAPI = new UserAccountAPI(this.$defaultDomain)
    }
    return this._UserAccountAPI
  }
}


/**
 * 管理系统接口描述
 * @class API
 */
export class API {
  /**
   *  API构造函数
   * @param domain API域名
   */
  constructor(domain?: string) {
    this.$defaultDomain = domain || 'http://localhost:8080'
  }
}

使用

import { API } from './http/api/manageApi'
// in main.ts
let api = new API("/api/")
api.getUserAccountAPI().changeUserState({
  isDisable: 1
  openId: 'open id'
})

Vue中最佳实践

main.ts 全局定义

import { API } from './http/api/manageApi'

Vue.prototype.$manageApi = new API('/api/')

增加.d.ts

增加types文件,方便使用智能提示

import { API } from '@/http/api/manageApi'
import { MarkAPI } from '@/http/api/mark-center-api'
declare module "vue/types/vue" {
  interface Vue {
    $manageApi: API
    $markApi: MarkAPI
  }
}

实际使用

现在可以在vue里直接调用了。

vscode调用

this.$manageApi
      .getUserAccountAPI().changeUserState({isDisable: 1, openId: 'open id'})

开源地址

https://github.com/jadepeng/generator-swagger-2-ts

欢迎star!


作者:Jadepeng
出处:jqpeng的技术记事本–http://www.cnblogs.com/xiaoqi
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

文章作者:jqpeng
原文链接: Spring Data Mongodb 乐观锁

Spring Data 针对mongodb提供了乐观锁实现:

The @Version annotation provides syntax similar to that of JPA in the context of MongoDB and makes sure updates are only applied to documents with a matching version. Therefore, the actual value of the version property is added to the update query in such a way that the update does not have any effect if another operation altered the document in the meantime. In that case, an OptimisticLockingFailureException is thrown. The following example shows these features:

提供@Version注解,用来标识版本,保存、删除等操作会验证version,不一致会抛出OptimisticLockingFailureException

来看一个例子:

@Document
class Person {

  @Id String id;
  String firstname;
  String lastname;
  @Version Long version;
}

Person daenerys = template.insert(new Person("Daenerys"));                            (1)

Person tmp = template.findOne(query(where("id").is(daenerys.getId())), Person.class); (2)

daenerys.setLastname("Targaryen");
template.save(daenerys);                                                              (3)

template.save(tmp); // throws OptimisticLockingFailureException                       (4)
  1. 最初插入一个person daenerysversion0
  2. 加载刚插入的数据,tmpversion还是0
  3. 更新version = 0daenerys,更新lastname,save后version变为1
  4. 现在来更新,会抛出OptimisticLockingFailureException, 提示操作失败。

注意:

Important Optimistic Locking requires to set the WriteConcern to ACKNOWLEDGED. Otherwise OptimisticLockingFailureException can be silently swallowed.
Note As of Version 2.2 MongoOperations also includes the @Version property when removing an entity from the database. To remove a Document without version check use MongoOperations#remove(Query,…​) instead of MongoOperations#remove(Object).
Note As of Version 2.2 repositories check for the outcome of acknowledged deletes when removing versioned entities. An OptimisticLockingFailureException is raised if a versioned entity cannot be deleted through CrudRepository.delete(Object). In such case, the version was changed or the object was deleted in the meantime. Use CrudRepository.deleteById(ID) to bypass optimistic locking functionality and delete objects regardless of their version.

作者:Jadepeng
出处:jqpeng的技术记事本–http://www.cnblogs.com/xiaoqi
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。