图形数据库 Neo4j 开发实战
https://www.ibm.com/developerworks/cn/java/j-lo-neo4j/
Neo4j 是一个高性能的 NoSQL 图形数据库。Neo4j 使用图(graph)相关的概念来描述数据模型,把数据保存为图中的节点以及节点之间的关系。很多应用中数据之间的关系,可以很直接地使用图中节点和关系的概念来建模。对于这样的应用,使用 Neo4j 来存储数据会非常的自然,要优于使用关系数据库。本文对 Neo4j 进行了深入的介绍,并结合具体的实例来进行详细的说明,可以让您对 Neo4j 有深入的了解,从而可以在应用开发中恰当地选择 Neo4j 来作为存储方式。
4 评论
在 IBM Bluemix 云平台上开发并部署您的下一个应用。
Neo4j 简介
数据存储一般是应用开发中不可或缺的组成部分。应用运行中产生的和所需要的数据被以特定的格式持久化下来。应用开发中很常见的一项任务是在应用本身的领域对象模型与数据存储格式之间进行相互转换。如果数据存储格式与领域对象模型之间比较相似,那么进行转换所需的映射关系更加自然,实现起来也更加容易。对于一个特定的应用来说,其领域对象模型由应用本身的特征来决定,一般采用最自然和最直观的方式来进行建模。所以恰当的选择数据存储格式就显得很重要。目前最常见的数据存储格式是关系数据库。关系数据库通过实体 - 关系模型(E-R 模型)来进行建模,即以表和表之间的关系来建模。在实际开发中可以使用的关系数据库的实现非常多,包括开源的和商用的。关系数据库适合用来存储数据条目的类型同构的表格型数据。如果领域对象模型中不同对象之间的关系比较复杂,则需要使用繁琐的对象关系映射技术(Object-Relationship Mapping,ORM)来进行转换。
对于很多应用来说,其领域对象模型并不适合于转换成关系数据库形式来存储。这也是非关系型数据库(NoSQL)得以流行的原因。NoSQL 数据库的种类很多,包括键值对数据库、面向文档数据库和图形数据库等。本文中介绍的 Neo4j 是最重要的图形数据库。Neo4j 使用数据结构中图(graph)的概念来进行建模。Neo4j 中两个最基本的概念是节点和边。节点表示实体,边则表示实体之间的关系。节点和边都可以有自己的属性。不同实体通过各种不同的关系关联起来,形成复杂的对象图。Neo4j 同时提供了在对象图上进行查找和遍历的功能。
对于很多应用来说,其中的领域对象模型本身就是一个图结构。对于这样的应用,使用 Neo4j 这样的图形数据库进行存储是最适合的,因为在进行模型转换时的代价最小。以基于社交网络的应用为例,用户作为应用中的实体,通过不同的关系关联在一起,如亲人关系、朋友关系和同事关系等。不同的关系有不同的属性。比如同事关系所包含的属性包括所在公司的名称、开始的时间和结束的时间等。对于这样的应用,使用 Neo4j 来进行数据存储的话,不仅实现起来简单,后期的维护成本也比较低。
Neo4j 使用“图”这种最通用的数据结构来对数据进行建模使得 Neo4j 的数据模型在表达能力上非常强。链表、树和散列表等数据结构都可以抽象成用图来表示。Neo4j 同时具有一般数据库的基本特性,包括事务支持、高可用性和高性能等。Neo4j 已经在很多生产环境中得到了应用。流行的云应用开发平台 Heroku 也提供了 Neo4j 作为可选的扩展。
在简要介绍完 Neo4j 之后,下面介绍 Neo4j 的基本用法。
Neo4j 基本使用
在使用 Neo4j 之前,需要首先了解 Neo4j 中的基本概念。
节点和关系
Neo4j 中最基本的概念是节点(node)和关系(relationship)。节点表示实体,由 org.neo4j.graphdb.Node
接口来表示。在两个节点之间,可以有不同的关系。关系由 org.neo4j.graphdb.Relationship
接口来表示。每个关系由起始节点、终止节点和类型等三个要素组成。起始节点和终止节点的存在,说明了关系是有方向,类似于有向图中的边。不过在某些情况,关系的方向可能并没有意义,会在处理时被忽略。所有的关系都是有类型的,用来区分节点之间意义不同的关系。在创建关系时,需要指定其类型。关系的类型由 org.neo4j.graphdb.RelationshipType
接口来表示。节点和关系都可以有自己的属性。每个属性是一个简单的名值对。属性的名称是 String
类型的,而属性的值则只能是基本类型、String
类型以及基本类型和 String
类型的数组。一个节点或关系可以包含任意多个属性。对属性进行操作的方法声明在接口 org.neo4j.graphdb.PropertyContainer
中。Node
和 Relationship
接口都继承自 PropertyContainer
接口。PropertyContainer 接口中
常用的方法包括获取和设置属性值的 getProperty 和 setProperty。
下面通过具体的示例来说明节点和关系的使用。
该示例是一个简单的歌曲信息管理程序,用来记录歌手、歌曲和专辑等相关信息。在这个程序中,实体包括歌手、歌曲和专辑,关系则包括歌手与专辑之间的发布关系,以及专辑与歌曲之间的包含关系。清单 1 给出了使用 Neo4j 对程序中的实体和关系进行操作的示例。
清单 1. 节点和关系的使用示例
private static enum RelationshipTypes implements RelationshipType {
PUBLISH, CONTAIN
} public void useNodeAndRelationship() {
GraphDatabaseService db = new EmbeddedGraphDatabase("music");
Transaction tx = db.beginTx();
try {
Node node1 = db.createNode();
node1.setProperty("name", "歌手 1");
Node node2 = db.createNode();
node2.setProperty("name", "专辑 1");
node1.createRelationshipTo(node2, RelationshipTypes.PUBLISH);
Node node3 = db.createNode();
node3.setProperty("name", "歌曲 1");
node2.createRelationshipTo(node3, RelationshipTypes.CONTAIN);
tx.success();
} finally {
tx.finish();
}
}
在 清单 1 中,首先定义了两种关系类型。定义关系类型的一般做法是创建一个实现了 RelationshipType 接口的枚举类型。RelationshipTypes 中的 PUBLISH 和 CONTAIN 分别表示发布和包含关系。在 Java 程序中可以通过嵌入的方式来启动 Neo4j 数据库,只需要创建 org.neo4j.kernel.EmbeddedGraphDatabase 类的对象,并指定数据库文件的存储目录即可。在使用 Neo4j 数据库时,进行修改的操作一般需要包含在一个事务中来进行处理。通过 GraphDatabaseService 接口的 createNode 方法可以创建新的节点。Node 接口的 createRelationshipTo 方法可以在当前节点和另外一个节点之间创建关系。
另外一个与节点和关系相关的概念是路径。路径有一个起始节点,接着的是若干个成对的关系和节点对象。路径是在对象图上进行查询或遍历的结果。Neo4j 中使用 org.neo4j.graphdb.Path 接口来表示路径。Path 接口提供了对其中包含的节点和关系进行处理的一些操作,包括 startNode 和 endNode 方法来获取起始和结束节点,以及 nodes 和 relationships 方法来获取遍历所有节点和关系的 Iterable 接口的实现。关于图上的查询和遍历,在下面小节中会进行具体的介绍。
使用索引
当 Neo4j 数据库中包含的节点比较多时,要快速查找满足条件的节点会比较困难。Neo4j 提供了对节点进行索引的能力,可以根据索引值快速地找到相应的节点。清单 2 给出了索引的基本用法。
清单 2. 索引的使用示例
public void useIndex() {
GraphDatabaseService db = new EmbeddedGraphDatabase("music");
Index<Node> index = db.index().forNodes("nodes");
Transaction tx = db.beginTx();
try {
Node node1 = db.createNode();
String name = "歌手 1";
node1.setProperty("name", name);
index.add(node1, "name", name);
node1.setProperty("gender", "男");
tx.success();
} finally {
tx.finish();
}
Object result = index.get("name", "歌手 1").getSingle()
.getProperty("gender");
System.out.println(result); // 输出为“男”
}
在 清单 2 中,通过 GraphDatabaseService 接口的 index 方法可以得到管理索引的 org.neo4j.graphdb.index.IndexManager 接口的实现对象。Neo4j 支持对节点和关系进行索引。通过 IndexManager 接口的 forNodes 和 forRelationships 方法可以分别得到节点和关系上的索引。索引通过 org.neo4j.graphdb.index.Index 接口来表示,其中的 add 方法用来把节点或关系添加到索引中,get 方法用来根据给定值在索引中进行查找。
图的遍历
在图上进行的最实用的操作是图的遍历。通过遍历操作,可以获取到与图中节点之间的关系相关的信息。Neo4j 支持非常复杂的图的遍历操作。在进行遍历之前,需要对遍历的方式进行描述。遍历的方式的描述信息由下列几个要素组成。
- 遍历的路径:通常用关系的类型和方向来表示。
- 遍历的顺序:常见的遍历顺序有深度优先和广度优先两种。
- 遍历的唯一性:可以指定在整个遍历中是否允许经过重复的节点、关系或路径。
- 遍历过程的决策器:用来在遍历过程中判断是否继续进行遍历,以及选择遍历过程的返回结果。
- 起始节点:遍历过程的起点。
Neo4j 中遍历方式的描述信息由 org.neo4j.graphdb.traversal.TraversalDescription 接口来表示。通过 TraversalDescription 接口的方法可以描述上面介绍的遍历过程的不同要素。类 org.neo4j.kernel.Traversal 提供了一系列的工厂方法用来创建不同的 TraversalDescription 接口的实现。清单 3 中给出了进行遍历的示例。
清单 3. 遍历操作的示例
TraversalDescription td = Traversal.description()
.relationships(RelationshipTypes.PUBLISH)
.relationships(RelationshipTypes.CONTAIN)
.depthFirst()
.evaluator(Evaluators.pruneWhereLastRelationshipTypeIs(RelationshipTypes.CONTAIN));
Node node = index.get("name", "歌手 1").getSingle();
Traverser traverser = td.traverse(node);
for (Path path : traverser) {
System.out.println(path.endNode().getProperty("name"));
}
在 清单 3 中,首先通过 Traversal 类的 description 方法创建了一个默认的遍历描述对象。通过 TraversalDescription 接口的 relationships 方法可以设置遍历时允许经过的关系的类型,而 depthFirst 方法用来设置使用深度优先的遍历方式。比较复杂的是表示遍历过程的决策器的 evaluator 方法。该方法的参数是 org.neo4j.graphdb.traversal.Evaluator 接口的实现对象。Evalulator 接口只有一个方法 evaluate。evaluate 方法的参数是 Path 接口的实现对象,表示当前的遍历路径,而 evaluate 方法的返回值是枚举类型 org.neo4j.graphdb.traversal.Evaluation,表示不同的处理策略。处理策略由两个方面组成:第一个方面为是否包含当前节点,第二个方面为是否继续进行遍历。Evalulator 接口的实现者需要根据遍历时的当前路径,做出相应的决策,返回适当的 Evaluation 类型的值。类 org.neo4j.graphdb.traversal.Evaluators 提供了一些实用的方法来创建常用的 Evalulator 接口的实现对象。清单 3 中使用了 Evaluators 类的 pruneWhereLastRelationshipTypeIs 方法。该方法返回的 Evalulator 接口的实现对象会根据遍历路径的最后一个关系的类型来进行判断,如果关系类型满足给定的条件,则不再继续进行遍历。
清单 3 中的遍历操作的作用是查找一个歌手所发行的所有歌曲。遍历过程从表示歌手的节点开始,沿着 RelationshipTypes.PUBLISH 和 RelationshipTypes.CONTAIN 这两种类型的关系,按照深度优先的方式进行遍历。如果当前遍历路径的最后一个关系是 RelationshipTypes.CONTAIN 类型,则说明路径的最后一个节点包含的是歌曲信息,可以终止当前的遍历过程。通过 TraversalDescription 接口的 traverse 方法可以从给定的节点开始遍历。遍历的结果由 org.neo4j.graphdb.traversal.Traverser 接口来表示,可以从该接口中得到包含在结果中的所有路径。结果中的路径的终止节点就是表示歌曲的实体。
Neo4j 实战开发
在介绍了 Neo4j 的基本使用方式之后,下面通过具体的案例来说明 Neo4j 的使用。作为一个数据库,Neo4j 可以很容易地被使用在 Web 应用开发中,就如同通常使用的 MySQL、SQL Server 和 DB2 等关系数据库一样。不同之处在于如何对应用中的数据进行建模,以适应后台存储方式的需求。同样的领域模型,既可以映射为关系数据库中的 E-R 模型,也可以映射为图形数据库中的图模型。对于某些应用来说,映射为图模型更为自然,因为领域模型中对象之间的各种关系会形成复杂的图结构。
本节中使用的示例是一个简单的微博应用。在微博应用中,主要有两种实体,即用户和消息。用户之间可以互相关注,形成一个图结构。用户发布不同的微博消息。表示微博消息的实体也是图中的一部分。从这个角度来说,使用 Neo4j 这样的图形数据库,可以更好地描述该应用的领域模型。
如同使用关系数据库一样,在使用 Neo4j 时,既可以使用 Neo4j 自身的 API,也可以使用第三方框架。Spring 框架中的 Spring Data 项目提供了对 Neo4j 的良好支持,可以在应用开发中来使用。Spring Data 项目把 Neo4j 数据库中的 CRUD 操作、使用索引和进行图的遍历等操作进行了封装,提供了更加抽象易用的 API,并通过使用注解来减少开发人员所要编写的代码量。示例的代码都是通过 Spring Data 来使用 Neo4j 数据库的。下面通过具体的步骤来介绍如何使用 Spring Data 和 Neo4j 数据库。
开发环境
使用 Neo4j 进行开发时的开发环境的配置比较简单。只需要根据 参考资源中给出的地址,下载 Neo4j 本身的 jar 包以及所依赖的 jar 包,并加到 Java 程序的 CLASSPATH 中就可以了。不过推荐使用 Maven 或 Gradle 这样的工具来进行 Neo4j 相关依赖的管理。
定义数据存储模型
前面已经提到了应用中有两种实体,即用户和消息。这两种实体需要定义为对象图中的节点。清单 1 中给出的创建实体的方式并不直观,而且并没有专门的类来表示实体,后期维护成本比较高。Spring Data 支持在一般的 Java 类上添加注解的方式来声明 Neo4j 中的节点。只需要在 Java 类上添加 org.springframework.data.neo4j.annotation.NodeEntity 注解即可,如 清单 4 所示。
清单 4. 使用 NodeEntity 注解声明节点类
@NodeEntity
public class User {
@GraphId Long id; @Indexed
String loginName;
String displayName;
String email;
}
如 清单 4 所示,User 类用来表示用户,作为图中的节点。User 类中的域自动成为节点的属性。注解 org.springframework.data.neo4j.annotation.GraphId 表明该属性作为实体的标识符,只能使用 Long 类型。注解 org.springframework.data.neo4j.annotation.Indexed 表明为属性添加索引。
节点之间的关系同样用注解的方式来声明,如 清单 5 所示。
清单 5. 使用 RelationshipEntity 注解声明关系类
@RelationshipEntity(type = "FOLLOW")
public class Follow {
@StartNode
User follower;
@EndNode
User followed; Date followingDate = new Date();
}
在 清单 5 中,RelationshipEntity 注解的属性 type 表示关系的类型,StartNode 和 EndNode 注解则分别表示关系的起始节点和终止节点。
在表示实体的类中也可以添加对关联的节点的引用,如 清单 6 中给出的 User 类中的其他域。
清单 6. User 类中对关联节点的引用
@RelatedTo(type = "FOLLOW", direction = Direction.INCOMING)
@Fetch Set<User> followers = new HashSet<User>(); @RelatedTo(type = "FOLLOW", direction = Direction.OUTGOING)
@Fetch Set<User> followed = new HashSet<User>(); @RelatedToVia(type = "PUBLISH")
Set<Publish> messages = new HashSet<Publish>();
如 清单 6 所示,注解 RelatedTo 表示与当前节点通过某种关系关联的节点。因为关系是有向的,可以通过 RelatedTo 的 direction 属性来声明关系的方向。对当前用户节点来说,如果 FOLLOW 关系的终止节点是当前节点,则说明关系的起始节点对应的用户是当前节点对应的用户的粉丝,用“direction = Direction.INCOMING”来表示。因此 followers 域表示的是当前用户的粉丝的集合,而 followed 域表示的是当前用户所关注的用户的集合。注解 RelatedToVia 和 RelatedTo 的作用类似,只不过 RelatedToVia 不关心关系的方向,只关心类型。因此 messages 域包含的是当前用户所发布的消息的集合。
数据操作
在定义了数据存储模型之后,需要创建相应的类来对数据进行操作。数据操作的对象是数据模型中的节点和关系类的实例,所涉及的操作包括常见的 CRUD,即创建、读取、更新和删除,还包括通过索引进行的查找和图上的遍历操作等。由于这些操作的实现方式都比较类似,Spring Data 对这些操作进行了封装,提供了简单的使用接口。Spring Data 所提供的数据操作核心接口是 org.springframework.data.neo4j.repository.GraphRepository。GraphRepository 接口继承自三个提供不同功能的接口:org.springframework.data.neo4j.repository.CRUDRepository 接口提供 save、delete、findOne 和 findAll 等方法,用来进行基本的 CRUD 操作;org.springframework.data.neo4j.repository.IndexRepository 则提供了 findByPropertyValue、findAllByPropertyValue 和 findAllByQuery 等方法,用来根据索引来查找;org.springframework.data.neo4j.repository.TraversalRepository 则提供了 findAllByTraversal 方法,用来根据 TraversalDescription 接口的描述来进行遍历操作。
Spring Data 为 GraphRepository 接口提供了默认的实现。在大多数情况下,只需要声明一个接口继承自 GraphRepository 接口即可,Spring Data 会在运行时创建相应的实现类的对象。对表示用户的节点类 User 进行操作的接口 UserRepository 如 清单 7 所示。
清单 7. 操作 User 类的 UserRepository 接口
public interface UserRepository extends GraphRepository<User> {
}
如 清单 7 所示,UserRepository 接口继承自 GraphRepository 接口,并通过泛型声明要操作的是 User 类。对节点类的操作比较简单,而对于关系类的操作就相对复杂一些。清单 8 中给出了对发布关系进行操作的接口 PublishRepository 的实现。
清单 8. 操作 Publish 类的 PublishRepository 接口
public interface PublishRepository extends GraphRepository<Publish> {
@Query("start user1=node({0}) " +
" match user1-[:FOLLOW]->user2-[r2:PUBLISH]->followedMessage" +
" return r2")
List<Publish> getFollowingUserMessages(User user); @Query("start user=node({0}) match user-[r:PUBLISH]->message return r")
List<Publish> getOwnMessages(User user);
}
在 清单 8 中,getFollowingUserMessages 方法用来获取某个用户关注的所有其他用户所发布的消息。该方法的实现是通过图上的遍历操作来完成的。Spring Data 提供了一种简单的查询语言来描述遍历操作。通过在方法上添加 org.springframework.data.neo4j.annotation.Query 注解来声明所使用的遍历方式即可。以 getFollowingUserMessages 方法的遍历声明为例,“node({0})”表示当前节点,“start user1=node({0})”表示从当前节点开始进行遍历,并用 user1 表示当前节点。“match”用来表示遍历时选中的节点应该满足的条件。条件“user1-[:FOLLOW]->user2-[r2:PUBLISH]->followedMessage”中,先通过类型为 FOLLOW 的关系找到 user1 所关注的用户,以 user2 来表示;再通过类型为 PUBLISH 的关系,查找 user2 所发布的消息。“return”用来返回遍历的结果,r2 表示的是类型为 PUBLISH 的关系,与 getFollowingUserMessages 方法的返回值类型 List<Publish> 相对应。
在应用中的使用
在定义了数据操作的接口之后,就可以在应用的服务层代码中使用这些接口。清单 9 中给出了用户发布新微博时的操作方法。
清单 9. 用户发布新微博的方法
@Autowired
UserRepository userRepository; @Transactional
public void publish(User user, String content) {
Message message = new Message(content);
messageRepository.save(message);
user.publish(message);
userRepository.save(user);
}
如 清单 9 所示,publish 方法用来给用户 user 发布内容为 content 的微博。域 userRepository 是 UserRepository 接口的引用,由 Spring IoC 容器在运行时自动注入依赖,该接口的具体实现由 Spring Data 提供。在 publish 方法中,首先创建一个 Message 实体类的对象,表示消息节点;再通过 save 方法把该节点保存到数据库中。User 类的 publish 方法的实现如 清单 10 所示,其逻辑是创建一个 Publish 类的实例表示发布关系,并建立用户和消息实体之间的关系。最后再更新 user 对象即可。
清单 10. User 类的 publish 方法
@RelatedToVia(type = "PUBLISH")
Set<Publish> messages = new HashSet<Publish>(); public Publish publish(Message message) {
Publish publish = new Publish(this, message);
this.messages.add(publish);
return publish;
}
在创建了相关的服务层类之后,就可以从服务层中暴露出相关的使用 JSON 的 REST 服务,然后在 REST 服务的基础上创建应用的前端展示界面。界面的实现部分与 Neo4j 并无关系,在这里不再赘述。整个程序基于 Spring 框架来开发。Spring Data 为 Neo4j 提供了独立的配置文件名称空间,可以方便在 Spring 配置文件中对 Neo4j 进行配置。清单 11 给出了与 Neo4j 相关的 Spring 配置文件。
清单 11. Neo4j 的 Spring 配置文件
<neo4j:config storeDirectory="data/neo-mblog.db" />
<neo4j:repositories base-package="com.chengfu.neomblog.repository" />
在 清单 11 中,config 元素用来设置 Neo4j 数据库的数据保存目录,repositories 元素用来声明操作 Neo4j 中的节点和关系类的 GraphRepository 接口的子接口的包名。Spring Data 会负责在运行时扫描该 Java 包,并为其中包含的接口创建出对应的实现对象。
示例应用的完整代码存放在 GitHub 上,见 参考资源。
使用 Neo4j 原生 API
如果不使用 Spring Data 提供的 Neo4j 支持,而使用 Neo4j 的原生 API,也是一样可以进行开发。只不过由于 Neo4j 的原生 API 的抽象层次较低,使用起来不是很方便。下面以示例应用中用户发布微博的场景来展示原生 API 的基本用法,见 清单 12。
清单 12. 使用 Neo4j 原生 API
public void publish(String username, String message) {
GraphDatabaseService db = new EmbeddedGraphDatabase("mblog");
Index<Node> index = db.index().forNodes("nodes");
Node ueserNode = index.get("user-loginName", username).getSingle();
if (ueserNode != null){
Transaction tx = db.beginTx();
try {
Node messageNode = db.createNode();
messageNode.setProperty("message", message);
ueserNode.createRelationshipTo(messageNode, RelationshipTypes.PUBLISH);
tx.success();
} finally {
tx.finish();
}
}
}
从 清单 12 中可以看出,原生 API 的基本用法是先通过 Neo4j 数据库的索引找到需要操作的表示用户的节点,然后再创建出表示微博消息的节点,最后在两个节点之间建立关系。这些步骤都使用 Neo4j 的基本 API 来完成。
与 清单 10 中使用 Spring Data 的功能相同的方法进行比较,可以发现使用原生 API 的代码要复杂不少,而使用 Spring Data 的则简洁很多。因此,在实际开发中推荐使用 Spring Data。
小结
关系数据库在很长一段时间都是大多数应用采用的数据存储方式的首要选择。随着技术的发展,越来越多的 NoSQL 数据库开始流行起来。对于应用开发人员来说,不应该总是盲目使用关系数据库,而是要根据应用本身的特点,选用最合适的存储方式。Neo4j 数据库以“图”作为数据之间关系的描述方式,非常适合于使用在数据本身就以图结构来组织的应用中。本文对 Neo4j 数据库的使用做了详细的介绍,可以帮助开发人员了解和使用 Neo4j 数据库。
图形数据库 Neo4j 开发实战的更多相关文章
- 图形数据库 Neo4j 开发实战【转载】
简介: Neo4j 是一个高性能的 NoSQL 图形数据库.Neo4j 使用图(graph)相关的概念来描述数据模型,把数据保存为图中的节点以及节点之间的关系.很多应用中数据之间的关系,可以很直接地使 ...
- 图形数据库Neo4J简介
最近我在用图形数据库来完成对一个初创项目的支持.在使用过程中觉得这种图形数据库实际上挺有意思的.因此在这里给大家做一个简单的介绍. NoSQL数据库相信大家都听说过.它们常常可以用来处理传统的关系型数 ...
- chrome拓展开发实战:页面脚本的拦截注入
原文请访问个人博客:chrome拓展开发实战:页面脚本的拦截注入 目前公司产品的无线站点已经实现了业务平台组件化,所有业务组件的转场都是通过路由来完成,而各个模块是通过requirejs进行统一管理, ...
- 《Android NFC 开发实战详解 》简介+源码+样章+勘误ING
<Android NFC 开发实战详解>简介+源码+样章+勘误ING SkySeraph Mar. 14th 2014 Email:skyseraph00@163.com 更多精彩请直接 ...
- CSS高效开发实战:CSS 3、LESS、SASS、Bootstrap、Foundation --读书笔记(1)设定背景图
技术的新发展,除计算机可以接入互联网之外,平板电脑.智能手机.智能电视等其他设备均可访问互联网.在多设备时代,构建多屏体验也不是听说的那么难. 但是这也增加了学习CSS的难度?不知道如何上手,只懂一点 ...
- 《Node.js开发实战详解》学习笔记
<Node.js开发实战详解>学习笔记 ——持续更新中 一.NodeJS设计模式 1 . 单例模式 顾名思义,单例就是保证一个类只有一个实例,实现的方法是,先判断实例是否存在,如果存在则直 ...
- 第九篇 :微信公众平台开发实战Java版之如何实现自定义分享内容
第一部分:微信JS-SDK介绍 微信JS-SDK是微信公众平台面向网页开发者提供的基于微信内的网页开发工具包. 通过使用微信JS-SDK,网页开发者可借助微信高效地使用拍照.选图.语音.位置等手机系统 ...
- 第八篇 :微信公众平台开发实战Java版之如何网页授权获取用户基本信息
第一部分:微信授权获取基本信息的介绍 我们首先来看看官方的文档怎么说: 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑. 关于网页授权回调域 ...
- 第七篇 :微信公众平台开发实战Java版之如何获取微信用户基本信息
在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的.对于不同公众号,同一用户的openid不同). 公众号可通过本接口来根据O ...
随机推荐
- VMware 使用本机代理上网
灰机使用方法 VMware 安装方法 首先解决主机的配置 1.查询本机 IP 地址,使用 ipconfig /all 2.更改小灰机的设置 3.虚拟机设置 4.Ubuntu 设置
- 一、java概述
一.概述 java不仅仅是一门编程语言,还是一个由一系列计算机软件和规范形成的技术体系. 提供了完整的开发和跨平台部署的支持环境.用途广泛. 结构严谨.面向对象.摆脱硬件平台的束缚. ...
- HDU 4790 Just Random (2013成都J题)
Just Random Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total ...
- android图片的缓存--节约内存提高程序效率
如今android应用占内存一个比一个大,android程序的质量亟待提高. 这里简单说说网络图片的缓存,我这边就简单的说说思路 1:网络图片,无疑须要去下载图片,我们不须要每次都去下载. 维护一张表 ...
- AngularJS路由系列(6)-- UI-Router的嵌套State
本系列探寻AngularJS的路由机制,在WebStorm下开发.本篇主要涉及UI-Route的嵌套State. 假设一个主视图上有两个部分视图,部分视图1和部分视图2,主视图对应着一个state,两 ...
- 在Brackets中使用Emmet
当在Brackets中安装上Emmet插件后,就可以使用Emmet的语法来加速前端编写. 有关html ● 子关系> div>ul>li ● 相邻+ div+p+bq ● 上一级^ ...
- AutoMapper在MVC中的运用06-一次性定义映射、复杂类型属性映射
本篇AutoMapper使用场景: ※ 当源和目标具有同名的复杂类型属性.集合类型属性,这2种属性对应的类间也需建立映射 ※ 一次性定义好源和目标的所有映射 ※ 一次性定义好源和目标的所有映射,目标中 ...
- 缩放到被选择的部分: ICommand Cmd = new ControlsZoomToSelectedCommandClass();
AddItem("esriControls.ControlsZoomToSelectedCommand"); //ICommand Cmd = new ControlsZoomTo ...
- clientX, clientY,offsetX, offsetY,screenX, screenY, x, y
clientX, clientY是鼠标当前相对于网页的位置,当鼠标位于页面左上角时clientX=0, clientY=0: offsetX, offsetY是鼠标当前相对于网页中的某一区域的位置,当 ...
- python测试开发django-36.一对一(OneToOneField)关系查询
前言 前面一篇在xadmin后台一个页面显示2个关联表(OneToOneField)的字段,使用inlines内联显示.本篇继续学习一对一(OneToOneField)关系的查询. 上一篇list_d ...