Spring Data JPA 从入门到精通~QueryByExampleExecutor的使用

 QueryByExampleExecutor 的使用

按示例查询(QBE)是一种用户友好的查询技术,具有简单的接口,它允许动态查询创建,并且不需要编写包含字段名称的查询。从 UML 图中,可以看出继承 JpaRepository 接口后,自动拥有了按“实例”进行查询的诸多方法,可见 Spring Data 的团队已经认为了 QBE 是 Spring JPA 的基本功能了,继承 QueryByExampleExecutor 和继承 JpaRepository 都会有这些基本方法,所以 QueryByExampleExecutor 位于 Spring Data Common 中,而 JpaRepository 位于 Spring Data JPA 中。

QueryByExampleExecutor 详细配置

先来看一下 QueryByExampleExecutor 的源码:

public interface QueryByExampleExecutor<T> { //根据“实例”查找一个对象。
<S extends T> S findOne(Example<S> example);
//根据“实例”查找一批对象
<S extends T> Iterable<S> findAll(Example<S> example); 
//根据“实例”查找一批对象,且排序
<S extends T> Iterable<S> findAll(Example<S> example, Sort sort);//根据“实例”查找一批对象,且排序和分页 
<S extends T> Page<S> findAll(Example<S> example, Pageable pageable);
//根据“实例”查找,返回符合条件的对象个数
<S extends T> long count(Example<S> example); 
//根据“实例”判断是否有符合条件的对象
<S extends T> boolean exists(Example<S> example); 
}

从源码上可以看出,只要了解 Example 基本上就可以掌握它的用法和 API 了。

public class Example<T> {@NonNullprivate final T probe;@NonNullprivate final ExampleMatcher matcher;
public static <T> Example<T> of(T probe) {return new Example(probe, ExampleMatcher.matching());
}
public static <T> Example<T> of(T probe, ExampleMatcher matcher) {return new Example(probe, matcher);
}
......
}

我们从源码中可以看出 Example 主要包含三部分内容。

  • Probe:这是具有填充字段的域对象的实际实体类,即查询条件的封装类(又可以理解为:查询条件参数),必填。
  • ExampleMatcher:ExampleMatcher 有关于如何匹配特定字段的匹配规则,它可以重复使用在多个示例,必填;如果不填,用默认的(又可以理解为参数的匹配规则)。
  • Example:Example 由 Probe 探针和 ExampleMatcher 组成,它用于创建查询,即组合查询参数和参数的匹配规则。

QueryByExampleExecutor 的使用案例

//创建查询条件数据对象
Customer customer = new Customer();
customer.setName("Jack");
customer.setAddress("上海");
//创建匹配器,即如何使用查询条件
ExampleMatcher matcher = ExampleMatcher.matching() //构建对象.withMatcher("name", GenericPropertyMatchers.startsWith()) //姓名采用“开始匹配”的方式查询.withIgnorePaths("focus");  //忽略属性:是否关注。因为是基本类型,需要忽略掉
//创建实例
Example<Customer> ex = Example.of(customer, matcher); 
//查询
List<Customer> ls = dao.findAll(ex);
//输出结果
for (Customer bo:ls) {System.out.println(bo.getName());
}

上面例子中,是这样创建“实例”的:

Example<Customer> ex = Example.of(customer, matcher);

可以看到,Example 对象由 customer 和 matcher 共同创建,为讲解方便,再来结合案例先来明确一些定义。

  • Probe:实体对象,在持久化框架中与 Table 对应的域对象,一个对象代表数据库表中的一条记录,如上例中 Customer 对象。在构建查询条件时,一个实体对象代表的是查询条件中的“数值”部分,如要查询姓“Jack”的客户,实体对象只能存储条件值“Jack”。
  • ExampleMatcher:匹配器,它是匹配“实体对象”的,表示了如何使用“实体对象”中的“值”进行查询,它代表的是“查询方式”,解释了如何去查的问题。例如,要查询姓“刘”的客户,即姓名以“刘”开头的客户,该对象就表示了“以某某开头的”这个查询方式,如上例中 withMatcher("name", GenericPropertyMatchers.startsWith())。
  • Example:实例对象,代表的是完整的查询条件。由实体对象(查询条件值)和匹配器(查询方式)共同创建。

再来理解“实例查询”,顾名思义,就是通过一个例子来查询,要查询的是 Customer 对象,查询条件也是一个 Customer 对象,通过一个现有的客户对象作为例子,查询和这个例子相匹配的对象。

QueryByExampleExecutor 的特点及约束

  • 支持动态查询:即支持查询条件个数不固定的情况,如客户列表中有多个过滤条件,用户使用时在“地址”查询框中输入了值,就需要按地址进行过滤,如果没有输入值,就忽略这个过滤条件。对应的实现是,在构建查询条件 Customer 对象时,将 address 属性值置具体的条件值或置为 null。
  • 不支持过滤条件分组:即不支持过滤条件用 or(或)来连接,所有的过滤查件,都是简单一层的用 and(并且)连接,如 firstname = ?0 or (firstname = ?1 and lastname = ?2)。
  • 仅支持字符串的开始/包含/结束/正则表达式匹配和其他属性类型的精确匹配。查询时,对一个要进行匹配的属性(如:姓名 name),只能传入一个过滤条件值,如以 Customer 为例,要查询姓“刘”的客户,“刘”这个条件值就存储在表示条件对象的 Customer 对象的 name 属性中,针对于“姓名”的过滤也只有这么一个存储过滤值的位置,没办法同时传入两个过滤值。正是由于这个限制,有些查询是没办法支持的,例如要查询某个时间段内添加的客户,对应的属性是 addTime,需要传入“开始时间”和“结束时间”两个条件值,而这种查询方式没有存两个值的位置,所以就没办法完成这样的查询。

ExampleMatcher 源码解读

(1)源码解读

public class ExampleMatcher {
NullHandler nullHandler; 
StringMatcher defaultStringMatcher; //默认
boolean defaultIgnoreCase; //默认大小写忽略方式
PropertySpecifiers propertySpecifiers; //各属性特定查询方式
Set<String> ignoredPaths; //忽略属性列表
//Null值处理方式,通过构造方法,我们发现默认忽略NullHandler nullHandler;//字符串匹配方式,通过构造方法可以看出默认是DEFAULT(默认,效果同EXACT),EXACT(相等)StringMatcher defaultStringMatcher;//各属性特定查询方式,默认无特殊指定的。PropertySpecifiers propertySpecifiers;//忽略属性列表,默认无。Set<String> ignoredPaths;//大小写忽略方式,默认不忽略。boolean defaultIgnoreCase;@Wither(AccessLevel.PRIVATE) MatchMode mode;
//通用、内部、默认构造方法。private ExampleMatcher() {this(NullHandler.IGNORE, StringMatcher.DEFAULT, new PropertySpecifiers(), Collections.<String>emptySet(), false,MatchMode.ALL);}//Example的默认匹配方式public static ExampleMatcher matching() {return matchingAll();}
public static ExampleMatcher matchingAll() {return new ExampleMatcher().withMode(MatchMode.ALL);
}
......
}

(2)关键属性分析

  • nullHandler:Null 值处理方式,枚举类型,有两个可选值,INCLUDE(包括)、IGNORE(忽略)。
    • 标识作为条件的实体对象中,一个属性值(条件值)为 Null 时,是否参与过滤;
    • 当该选项值是 INCLUDE 时,表示仍参与过滤,会匹配数据库表中该字段值是 Null 的记录;
    • 若为 IGNORE 值,表示不参与过滤。
  • defaultStringMatcher:默认字符串匹配方式,枚举类型,有 6 个可选值,DEFAULT(默认,效果同 EXACT)、EXACT(相等)、STARTING(开始匹配)、ENDING(结束匹配)、CONTAINING(包含,模糊匹配)、REGEX(正则表达式)。
    • 该配置对所有字符串属性过滤有效,除非该属性在 propertySpecifiers 中单独定义自己的匹配方式。
  • defaultIgnoreCase:默认大小写忽略方式,布尔型,当值为 false 时,即不忽略,大小不相等。
    • 该配置对所有字符串属性过滤有效,除非该属性在 propertySpecifiers 中单独定义自己的忽略大小写方式。
  • propertySpecifiers:各属性特定查询方式,描述了各个属性单独定义的查询方式,每个查询方式中包含4个元素:属性名、字符串匹配方式、大小写忽略方式、属性转换器。
    • 如果属性未单独定义查询方式,或单独查询方式中,某个元素未定义(如字符串匹配方式),则采用 ExampleMatcher 中定义的默认值,即上面介绍的 defaultStringMatcher 和 defaultIgnoreCase 的值。
  • ignoredPaths:忽略属性列表,忽略的属性不参与查询过滤。

(3)字符串匹配举例

字符串匹配方式

对应JPQL的写法

Default&不忽略大小写

firstname=?1

Exact&忽略大小写

LOWER(firstname) = LOWER(?1)

Starting&忽略大小写

LOWER(firstname) like LOWER(?0)+'%'

Ending&不忽略大小写

firstname like '%'+?1

Containing不忽略大小写

firstname like '%'+?1+'%'

QueryByExampleExecutor 使用场景 & 实际的使用

使用场景

使用一组静态或动态约束来查询数据存储、频繁重构域对象,而不用担心破坏现有查询、简单的查询的使用场景,有时候还是挺方便的。

实际使用中我们需要考虑的因素

查询条件的表示,有两部分,一是条件值,二是查询方式。条件值用实体对象(如 Customer 对象)来存储,相对简单,当页面传入过滤条件值时,存入相对应的属性中,没入传入时,属性保持默认值。查询方式是用匹配器 ExampleMatcher 来表示,情况相对复杂些,需要考虑的因素有以下几个:

(1)Null 值的处理

当某个条件值为 Null时,是应当忽略这个过滤条件呢,还是应当去匹配数据库表中该字段值是 Null 的记录?

Null 值处理方式:默认值是 IGNORE(忽略),即当条件值为 Null 时,则忽略此过滤条件,一般业务也是采用这种方式就可满足。当需要查询数据库表中属性为 Null 的记录时,可将值设为 INCLUDE,这时,对于不需要参与查询的属性,都必须添加到忽略列表(ignoredPaths)中,否则会出现查不到数据的情况。

(2)基本类型的处理

如客户 Customer 对象中的年龄 age 是 int 型的,当页面不传入条件值时,它默认是0,是有值的,那是否参与查询呢?

关于基本数据类型处理方式:实体对象中,避免使用基本数据类型,采用包装器类型。如果已经采用了基本类型,而这个属性查询时不需要进行过滤,则把它添加到忽略列表(ignoredPaths)中。

(3)忽略某些属性值

一个实体对象,有许多个属性,是否每个属性都参与过滤?是否可以忽略某些属性?

ignoredPaths:虽然某些字段里面有值或者设置了其他匹配规则,只要放在 ignoredPaths 中,就会忽略此字段的,不作为过滤条件。

(4)不同的过滤方式

同样是作为 String 值,可能“姓名”希望精确匹配,“地址”希望模糊匹配,如何做到?

默认配置和特殊配置混合使用:默认创建匹配器时,字符串采用的是精确匹配、不忽略大小写,可以通过操作方法改变这种默认匹配,以满足大多数查询条件的需要,如将“字符串匹配方式”改为 CONTAINING(包含,模糊匹配),这是比较常用的情况。对于个别属性需要特定的查询方式,可以通过配置“属性特定查询方式”来满足要求,设置 propertySpecifiers 的值即可。

(5)大小写匹配

字符串匹配时,有时可能希望忽略大小写,有时则不忽略,如何做到?

defaultIgnoreCase:忽略大小的生效与否,是依赖于数据库的。例如 MySQL 数据库中,默认创建表结构时,字段是已经忽略大小写的,所以这个配置与否,都是忽略的。如果业务需要严格区分大小写,可以改变数据库表结构属性来实现。

实际使用案例说明

(1)无匹配器的情况

  • 要求:查询地址是“河南省郑州市”,且重点关注的客户。
  • 说明:使用默认匹配器就可以满足查询条件,则不需要创建匹配器。
//创建查询条件数据对象
Customer customer = new Customer();
customer.setAddress("河南省郑州市");
customer.setFocus(true);
//创建实例
Example<Customer> ex = Example.of(customer); 
//查询
List<Customer> ls = dao.findAll(ex);

(2)多种条件组合

  • 要求:根据姓名、地址、备注进行模糊查询,忽略大小写,地址要求开始匹配。
  • 说明:这是通用情况,主要演示改变默认字符串匹配方式、改变默认大小写忽略方式、属性特定查询方式配置、忽略属性列表配置。
 //创建查询条件数据对象
Customer customer = new Customer();
customer.setName("zhang");
customer.setAddress("河南省");
customer.setRemark("BB");
//虽然有值,但是不参与过滤条件
customer.setFocus(true);
//创建匹配器,即如何使用查询条件
ExampleMatcher matcher = ExampleMatcher.matching() //构建对象.withStringMatcher(StringMatcher.CONTAINING) //改变默认字符串匹配方式:模糊查询.withIgnoreCase(true) //改变默认大小写忽略方式:忽略大小写.withMatcher("address", GenericPropertyMatchers.startsWith()) //地址采用“开始匹配”的方式查询.withIgnorePaths("focus");  //忽略属性:是否关注。因为是基本类型,需要忽略掉
//创建实例
Example<Customer> ex = Example.of(customer, matcher); 
//查询
List<Customer> ls = dao.findAll(ex);

(3)多级查询

  • 要求:查询所有潜在客户。
  • 说明:主要演示多层级属性查询。
//创建查询条件数据对象
CustomerType type = new CustomerType();
type.setCode("01"); //编号01代表潜在客户
Customer customer = new Customer();
customer.setCustomerType(type);        
//创建匹配器,即如何使用查询条件
ExampleMatcher matcher = ExampleMatcher.matching() //构建对象.withIgnorePaths("focus");  //忽略属性:是否关注。因为是基本类型,需要忽略掉                
//创建实例
Example<Customer> ex = Example.of(customer, matcher); 
//查询
List<Customer> ls = dao.findAll(ex);

(4)查询 Null 值

  • 要求:地址是 Null 的客户。
  • 说明:主要演示改变“Null 值处理方式”。
//创建查询条件数据对象
Customer customer = new Customer();
//创建匹配器,即如何使用查询条件
ExampleMatcher matcher = ExampleMatcher.matching() //构建对象//改变“Null值处理方式”:包括。.withIncludeNullValues() //忽略其他属性.withIgnorePaths("id", "name", "sex", "age", "focus", "addTime", "remark", "customerType"); 
//创建实例
Example<Customer> ex = Example.of(customer, matcher);
//查询
List<Customer> ls = dao.findAll(ex);    

(5)虽然我们工作中用的最多的还是“简单查询”(因为简单,所以…)和基于 JPA Criteria 的动态查询(可以满足所有需求,没有局限性)。

但是 QueryByExampleExecutor 还是个非常不错的两种中间场景的查询处理手段,其他人没有用,感觉是对其不熟悉,还是希望我们学习过 QueryByExampleExecutor 的开发者用起来,用熟悉了会增加开发效率。

QueryByExampleExecutor 的实现源码

(1)我们通过开发工具——Hierarchy,来看一下其接口的实现类有哪些:

(2)我们发现 JpaSpecificationExecutor 的实现类是 SimpleJpaRepository。

而 SimpleJpaRepository 也实现了 JpaSpecificationExecutor,于是就利用 Specification 的特性,创建了内部类 ExampleSpecification,通过 Exmaple 实现了一套工具类和对 Predicate 的构建,进而实现了整个 ExampleQuery 的逻辑。

如果我们自己去看源码,会发现 QueryByExampleExecutor 给我们提供了两种思路:

  • 通过 JpaSpecificationExecutor 自定义 Response 的思路。
  • 和对 JpaSpecificationExecutor 的扩展思路(在后面章节自定义 Repository 中会详细介绍)。

(3)SimpleJpaRepository 实现类中的关键源码:

public class SimpleJpaRepository<T, ID extends Serializable>implements JpaRepository<T, ID>, JpaSpecificationExecutor<T> {private final EntityManager em;
public <S extends T> S findOne(Example<S> example) {try {return getQuery(new ExampleSpecification<S>(example), example.getProbeType(), (Sort) null).getSingleResult();} catch (NoResultException e) {return null;}
}
protected <S extends T> TypedQuery<S> getQuery(Specification<S> spec, Class<S> domainClass, 
Sort sort) {CriteriaBuilder builder = em.getCriteriaBuilder();CriteriaQuery<S> query = builder.createQuery(domainClass);Root<S> root = applySpecificationToCriteria(spec, domainClass, query);query.select(root);if (sort != null) {query.orderBy(toOrders(sort, root, builder));}return applyRepositoryMethodMetadata(em.createQuery(query));
}
......
}

(4)读 SimpleJpaRepository 源码给大家的启示

当我们学习一个 API 的时候最好顺带读一下源码,分析一下它的实现结构,这样你会有意外发现:

  • 学习源码的编程思想;
  • 学习源码的实现方式,我自己如何写,这样可以很快的提高我们自己的编程水平。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/496357.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

谷歌大脑提出对智能体进行「正向-反向」强化学习训练,加速训练过程

原文来源&#xff1a;arXiv作者&#xff1a;Ashley D. Edwards、Laura Downs、James C. Davidson「雷克世界」编译&#xff1a;嗯~是阿童木呀、KABUDA、EVA在强化学习问题中&#xff0c;关于任务目标的制定&#xff0c;往往需要开发人员花费很多的精力&#xff0c;在本文中&…

Linux二进制实用工具Binutils工具集解析()

From&#xff1a;http://blog.csdn.net/zqixiao_09/article/details/50783007 GNU Binutils&#xff1a;http://www.gnu.org/software/binutils/ GNU Binutils详解&#xff1a;http://www.crifan.com/files/doc/docbook/binutils_intro/release/html/binutils_intro.html 交叉…

Spring Data JPA 从入门到精通~JpaSpecificationExecutor的使用方法

JpaSpecificationExecutor 源码和 API 我们也可以通过 idea 工具详细看其用法和实现类&#xff0c;JpaSpecificationExecutor 是 Repository 要继承的接口&#xff0c;而 SimpleJpaRepository 是其默认实现。而通过源码来看其提供的 API 比较简单、明了&#xff0c;有如下几个方…

谷歌人工智能野心:从“下围棋”开始走向商用赚钱

来源&#xff1a;腾讯科技作为人工智能研发的领先企业&#xff0c;谷歌已经开始从技术研发走向了产品商用&#xff0c;创造营收成为一个新目标。谷歌在人工智能领域进行研发的时间比较长&#xff0c;陆续收购了优秀的专业公司。鉴于此&#xff0c;谷歌获得了先发优势&#xff0…

Linux异步之信号(signal)机制分析

From&#xff1a;http://www.cnblogs.com/hoys/archive/2012/08/19/2646377.html From&#xff1a;http://kenby.iteye.com/blog/1173862 Linux下的信号详解及捕捉信号&#xff1a;http://www.jb51.net/article/90695.htm linux信号详解&#xff1a;http://blog.csdn.net/f…

DeepMind集成AI智能体架构「MERLIN」:基于目标导向智能体中的无监督预测记忆

来源&#xff1a;arXiv摘要&#xff1a;在自然界中&#xff0c;动物往往会执行目标导向的行为&#xff0c;尽管它们的传感器的范围有限。作者&#xff1a;Greg Wayne、 Chia-Chun Hung、David Amos、Mehdi Mirza、Arun Ahuja、Agnieszka Grabska-Barwinska、Jack Rae、Piotr Mi…

手机反编译java源码,Android反编译(一)之反编译JAVA源码

Android反编译(一)之反编译JAVA源码[目录]1、工具2、反编译步骤步骤1&#xff1a;把apk文件后缀名改为.zip步骤2&#xff1a;解压zip包得到classes.dex步骤3&#xff1a;将Dex反编译为Jar包(工具&#xff1a;dex2jar)命令: CMD>dex2jar.bat classes.dex步骤4&#xff1a;用j…

一文详解计算机视觉的广泛应用:网络压缩、视觉问答、可视化、风格迁移等

作者 | 张皓&#xff08;南京大学&#xff09;来源&#xff1a;人工智能头条丨公众号引言深度学习目前已成为发展最快、最令人兴奋的机器学习领域之一&#xff0c;许多卓有建树的论文已经发表&#xff0c;而且已有很多高质量的开源深度学习框架可供使用。然而&#xff0c;论文通…

Linux-进程、进程组、作业、会话、控制终端详解

From&#xff1a;http://www.cnblogs.com/JohnABC/p/4079669.html Linux进程优先级的处理--Linux进程的管理与调度(二十二)&#xff1a;http://blog.csdn.net/gatieme/article/details/51719208 进程 、进程组、会话、控制终端之间的关系&#xff1a;http://blog.csdn.net/y…

浅谈项目开发现状(一)

在现在的软件开发中&#xff0c;一些大的软件公司有充分的资金&#xff0c;所以他的公司人员组织架构能组成&#xff1a;需求分析团队&#xff08;为了更好的了解用户的完整需求&#xff09;--->研发团队&#xff08;通过计算机语言来实现用户需求&#xff09;&#xff0c;方…

波士顿咨询:2018最具创新力企业50强

来源&#xff1a;前瞻网在波士顿咨询公司评选的2018年最具创新力公司中&#xff0c;有11家公司——其中包括前10名中的7家——都是“数字原住民”&#xff0c;按定义也就是“数字创新者”。榜单上大多数公司已经将数字技术建立在他们的创新计划中。这一趋势在各个行业都很普遍&…

Linux 线程

Linux 的多线程编程的高效开发经验&#xff1a;https://www.ibm.com/developerworks/cn/linux/l-cn-mthreadps/ linux线程的实现&#xff1a;http://www.cnblogs.com/zhaoyl/p/3620204.html 线程概念经典解析&#xff1a;http://blog.chinaunix.net/uid-29613952-id-421477…

Spring Data JPA 从入门到精通~JpaSpecificationExecutor实现原理

JpaSpecificationExecutor 实现原理 我们还是先通过开发工具&#xff0c;把关键的类添加到Diagram上面进行分析&#xff0c;如图&#xff1a; 我们通过上图可以看一下&#xff0c;前面介绍的几个类之间的关联关系。 SimpleJpaRepository 实现类中的关键源码如下&#xff1a; …

微信发布首份《移动支付时代的无人零售报告》

来源&#xff1a;爱范儿 作者&#xff1a;Panda3 月 30 日&#xff0c;微信支付行业运营总监白振杰在 2018 智慧无人零售大会上发布了《移动支付时代的无人零售行业报告》&#xff0c;报告首次结合中国百货商业协会权威调研和微信支付的数据分析能力&#xff0c;揭示了移动支…

盘点《头号玩家》里的 VR 技术,现在就能造个 Oasis 出来

来源&#xff1a;沉浸感丨公众号 作者&#xff1a; 刘芳平由史蒂文斯皮尔伯格导演的科幻电影《头号玩家》&#xff08;Ready One Player&#xff09;于 3 月 30 日在中国大陆上映。首个周末便收获广泛好评&#xff0c;登上豆瓣电影本周口碑榜第一名&#xff0c;截止 31 日晚积…

find()matlab,Matlab 之 find()函数

当我第一次用matlab语言编写一个工程项目时&#xff0c;发现自己编写的脚本里循环特别多&#xff0c;导致编程效率很低&#xff0c;这让我特别苦恼。有一次导师让我阅读他编写的一个Matlab脚本&#xff0c;并按照新要求对其进行更改。我发现脚本里多次用到find()函数&#xff0…

一图看懂阿里云IoT战略

来源&#xff1a;云栖社区从万物互联到万物智联&#xff0c;离不开“无处不在的计算”。阿里云IoT在云边端部署了可协同的计算&#xff0c;基于物联网使能平台联合开发者、芯片模组厂商、行业合作伙伴等在城市、生活、汽车、制造四大领域已完成深度布局&#xff0c;即一朵云、两…

matlab机器人模型仿真,一知半解|MATLAB机器人建模与仿真控制(1)

各位机器人技术爱好者们&#xff0c;大家好&#xff01;很荣幸受古月老师邀请&#xff0c;成为古月居的签约作者。希望在这里可以同大家相互交流与学习。始终觉得自己能力不足&#xff0c;但终于还是硬着头皮开始了在古月居的第一篇博客。由于我是机械专业出身&#xff0c;在接…

第四期《Summer Tree》 已经整理完毕

虽然已经整理完毕&#xff0c;但是却没有空间上传&#xff0c;由于太大了上传不了.下次再补充回来 如果地址下载不到&#xff0c;告诉我。呵呵 我上传在微软共享空间里面 在这里可以下载在这里转载于:https://www.cnblogs.com/hero82748274/archive/2009/06/06/1497771.html

研究报告:城市大脑的起源、现状与未来趋势

报告撰写人&#xff1a;刘锋 《互联网进化论》作者&#xff0c;计算机博士2009年1月&#xff0c;IBM公司首席执行官彭明盛首次提出“智慧地球”&#xff0c;建议政府投资新一代的智慧型基础设施。此后智慧城市建设在世界范围内展开&#xff0c;在中国有上百个地区提出建设“智慧…