1 版本说明

  JDK:1.8

  MAVEN:3.5

  SpringBoot:2.0.4

  IDEA:旗舰版207.2

  MySQL:5.5

2 SpringDataJPA环境搭建(SpringBoot版本)

  2.1 创建一个SrpingBoot项目

    需要引入的依赖如下图所示

  2.2 配置数据库相关

    》创建一个mysql数据库testdemo

    》在testdemo中创建一个student表

/*
Navicat MySQL Data Transfer Source Server : mysql5.4
Source Server Version : 50540
Source Host : localhost:3306
Source Database : testdemo Target Server Type : MYSQL
Target Server Version : 50540
File Encoding : 65001 Date: 2018-08-13 21:13:51
*/ SET FOREIGN_KEY_CHECKS=0; -- ----------------------------
-- Table structure for `student`
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int(50) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`age` int(10) NOT NULL,
`address` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4;

student.sql

    》在springboot项目中配置数据库信息

spring:
datasource:
url: jdbc:mysql://127.0.0.1/testdemo?characterEncoding=utf-8&useSSL=false
username: root
password: 182838 jpa:
properties:
hibernate:
format_sql: true
show_sql: true

application.yml

    2.2.1 利用IDEA连接数据

      参考博文

    2.2.2 利用IDEA自动生成实体类

package cn.xiangxu.jpa_demo03.domain.domain_do;

import javax.persistence.*;

/**
* @author 王杨帅
* @create 2018-08-13 21:36
* @desc
**/
@Entity
@Table(name = "student", schema = "testdemo", catalog = "")
public class StudentDO {
private int id;
private String name;
private int age;
private String address; @Id
@Column(name = "id")
public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} @Basic
@Column(name = "name")
public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Basic
@Column(name = "age")
public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} @Basic
@Column(name = "address")
public String getAddress() {
return address;
} public void setAddress(String address) {
this.address = address;
} @Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false; StudentDO studentDO = (StudentDO) object; if (id != studentDO.id) return false;
if (age != studentDO.age) return false;
if (name != null ? !name.equals(studentDO.name) : studentDO.name != null) return false;
if (address != null ? !address.equals(studentDO.address) : studentDO.address != null) return false; return true;
} @Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + age;
result = 31 * result + (address != null ? address.hashCode() : 0);
return result;
}
}

StudentDO.java

      参考博文

  2.3 创建持久层

    技巧01:持久层接口只需要继承 JpaRepository 接口即可

package cn.xiangxu.jpa_demo03.domain.repository;

import cn.xiangxu.jpa_demo03.domain.domain_do.StudentDO;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; /**
* @author 王杨帅
* @create 2018-08-13 21:53
* @desc 学生持久层接口
**/
public interface StudentRepository extends JpaRepository<StudentDO, Integer> { }

StudentRepository.java

    技巧02:需要使用持久层实例时,只需要进行依赖注入即可

    技巧03:持久层接口上不用写   @Repository  ,因为继承了 JpaRepository 接口后就已经是一个被Spring容器管理的持久层Bean啦

    技巧04:Repository相关接口

 

  2.4 测试持久层

    技巧01:继承 JpaRepository 接口后就可以有很过方法可以使用

    技巧02:可以直接使用的原因是:拦截 + JDK动态代理

package cn.xiangxu.jpa_demo03.domain.repository;

import cn.xiangxu.jpa_demo03.domain.domain_do.StudentDO;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner; import java.util.List; import static org.junit.Assert.*; @RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class StudentRepositoryTest { @Autowired
private StudentRepository studentRepository; @Test
public void findall() {
List<StudentDO> all = studentRepository.findAll();
System.out.println(all);
} }

    技巧03:jpa支持自定义SQL【借助@Query注解实现】

3 Projections 对查询结果的扩展

  3.1 返回类型建模

    Spring JPA 对 Projections 的扩展的支持,个人觉得这是个非常好的东西,从字面意思上理解就是映射,指的是和 DB 的查询结果的字段映射关系。一般情况下,我们是返回的字段和 DB 的查询结果的字段是一一对应的,但有的时候,需要返回一些指定的字段,不需要全部返回,或者返回一些复合型的字段,还得自己写逻辑。Spring Data 正是考虑到了这一点,允许对专用返回类型进行建模,以便更有选择地将部分视图对象。(声明:来自gitChat)

    3.1.1 需求

      只需要查询学生的姓名和年龄信息,其余信息不进行查询

    3.1.2 思路

      声明一个接口,包含我们要返回的属性的方法即可

    3.1.3 编程实现

      》根据实体类中属性的get方法创建接口

        技巧01:接口中的方法就是需要获取的字段在实体类中对应的get方法

package cn.xiangxu.jpa_demo03.domain.domain_do;

/**
* @author 王杨帅
* @create 2018-08-13 22:16
* @desc
**/
public interface StudentBaseInfo { String getName();
Integer getAge(); }

StudentBaseInfo.java

      》修改持久层接口中的方法

        技巧01:返回值用StudentBaseInfo类型

package cn.xiangxu.jpa_demo02.repository;

import cn.xiangxu.jpa_demo02.domain.domain_do.StudentBaseInfo;
import cn.xiangxu.jpa_demo02.domain.domain_do.StudentDO;
import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; /**
* @author 王杨帅
* @create 2018-08-13 10:00
* @desc
**/
public interface StudentRepository extends JpaRepository<StudentDO, Integer> { /**
* 根据姓名和年龄
* @param name 姓名
* @param age 年龄
* @return
*/
List<StudentDO> findByNameAndAge(String name, Integer age); /**
* 根据年龄段查询【PS: 开区间】
* @param start 最小值
* @param end 最大值
* @return
*/
List<StudentDO> findByAgeBetween(Integer start, Integer end); /**
* 小于给定年龄【PS: 开区间】
* @param age 年龄
* @return
*/
List<StudentDO> findByAgeLessThan(Integer age); /**
* 小于给定年龄【PS: 闭区间】
* @param age 年龄
* @return
*/
List<StudentDO> findByAgeLessThanEqual(Integer age); List<StudentDO> findByAgeGreaterThan(Integer age); List<StudentDO> findByAgeGreaterThanEqual(Integer age); List<StudentDO> findByName(String name); List<StudentDO> findByNameEquals(String name); List<StudentDO> findByNameIs(String name); List<StudentDO> findByAgeAfter(Integer age); List<StudentDO> findByAgeBefore(Integer age); List<StudentDO> findByAgeAfterOrAgeEquals(Integer age, Integer age2); // List<StudentDO> findByAddress(String address); List<StudentBaseInfo> findByAddress(String address); }

StudentRepository.java

      》测试类

package cn.xiangxu.jpa_demo03.domain.repository;

import cn.xiangxu.jpa_demo03.domain.domain_do.StudentBaseInfo;
import cn.xiangxu.jpa_demo03.domain.domain_do.StudentDO;
import cn.xiangxu.jpa_demo03.repository.StudentRepository;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class StudentRepositoryTest { @Autowired
private StudentRepository studentRepository; @Test
public void findall() {
List<StudentDO> all = studentRepository.findAll();
System.out.println(all);
} @Test
public void findByName() {
List<StudentBaseInfo> byNameIs = studentRepository.findByNameIs("杨玉林");
System.out.println(byNameIs);
} }

      》跳坑01:通过这种方式获取到的数据是一个Map对象,如何转化成一个实体类列表呢

      》填坑01:创建一个实体类,这个实体类的字段要和创建的接口中方法对应实体类类的字段名保持一致【PS:本博文就继续使用之前的实体类】,将查询到的数据转化成流,然后利用peek这个中间操作进行转化

package cn.xiangxu.jpa_demo03.domain.repository;

import cn.xiangxu.jpa_demo03.domain.domain_do.StudentBaseInfo;
import cn.xiangxu.jpa_demo03.domain.domain_do.StudentDO;
import cn.xiangxu.jpa_demo03.repository.StudentRepository;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner; import java.util.ArrayList;
import java.util.List; @RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class StudentRepositoryTest { @Autowired
private StudentRepository studentRepository; @Test
public void findall() {
List<StudentDO> all = studentRepository.findAll();
System.out.println(all);
} @Test
public void findByName() {
List<StudentBaseInfo> byNameIs = studentRepository.findByNameIs("杨玉林");
System.out.println(byNameIs);
} @Test
public void findBYName02() {
List<StudentBaseInfo> byNameIs = studentRepository.findByNameIs("王杨帅");
List<StudentDO> studentDOList = new ArrayList<>();
byNameIs.stream()
.peek(
i -> {
StudentDO studentDO = new StudentDO();
BeanUtils.copyProperties(i, studentDO);
studentDOList.add(studentDO);
}
)
.forEach(
System.out::println
);
System.out.println(studentDOList);
} }

      》转化后的结果为

4 实体类注解

  待更新......  2018年8月14日20:21:24

5 关联查询

  说明:本博是利用原生的SQL进行关联查询

  5.1 需求

    现有两张数据库表:

      product -> 存放产品信息

      provider -> 存放供应商信息

      product 和 provider 的关系时多对一的关系,即:一个供应商可能和多个产品对应

  5.2 思路

    知己利用SQL的关联查询实现

  5.3 编程实现

    5.3.1 数据表

      技巧01:product中的provider_id字段和provider的id字段作为关联查询的桥梁

      技巧02:理论上说,product中的provider_id字段应该设置成外键,但是为了开发方便,此处不进行设置【PS: 开发人员知道这是外键即可】

/*
Navicat MySQL Data Transfer Source Server : mysql5.4
Source Server Version : 50540
Source Host : localhost:3306
Source Database : testdemo Target Server Type : MYSQL
Target Server Version : 50540
File Encoding : 65001 Date: 2018-08-14 20:25:59
*/ SET FOREIGN_KEY_CHECKS=0; -- ----------------------------
-- Table structure for `product`
-- ----------------------------
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_name` varchar(255) NOT NULL,
`product_price` double NOT NULL,
`provider_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4; -- ----------------------------
-- Records of product
-- ----------------------------
INSERT INTO `product` VALUES ('', '天友纯牛奶', '', '');
INSERT INTO `product` VALUES ('', '天友核桃花生奶', '', '');
INSERT INTO `product` VALUES ('', '福特福克斯', '', '');
INSERT INTO `product` VALUES ('', '福特嘉年华', '', ''); -- ----------------------------
-- Table structure for `provider`
-- ----------------------------
DROP TABLE IF EXISTS `provider`;
CREATE TABLE `provider` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`provider_name` varchar(255) NOT NULL,
`provider_phone` varchar(255) NOT NULL,
`provider_address` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4; -- ----------------------------
-- Records of provider
-- ----------------------------
INSERT INTO `provider` VALUES ('', '天友乳业', '', '重庆市大足区');
INSERT INTO `provider` VALUES ('', '长安制造', '', '重庆市渝北区');

    5.3.2 创建实体类

      技巧01:利用IDEA自动生成

      技巧02:ProductInfoDTO这个实体类是用来封装产品信息和供应商信息的

package cn.xiangxu.jpa_demo04.domain.domain_do;

import lombok.ToString;

import javax.persistence.*;

/**
* @author 王杨帅
* @create 2018-08-14 19:54
* @desc
**/
@Entity
@ToString
@Table(name = "product", schema = "testdemo", catalog = "")
public class ProductDO {
private int id;
private String productName;
private double productPrice;
private int providerId; @Id
@Column(name = "id")
public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} @Basic
@Column(name = "product_name")
public String getProductName() {
return productName;
} public void setProductName(String productName) {
this.productName = productName;
} @Basic
@Column(name = "product_price")
public double getProductPrice() {
return productPrice;
} public void setProductPrice(double productPrice) {
this.productPrice = productPrice;
} @Basic
@Column(name = "provider_id")
public int getProviderId() {
return providerId;
} public void setProviderId(int providerId) {
this.providerId = providerId;
} @Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false; ProductDO productDO = (ProductDO) object; if (id != productDO.id) return false;
if (Double.compare(productDO.productPrice, productPrice) != 0) return false;
if (providerId != productDO.providerId) return false;
if (productName != null ? !productName.equals(productDO.productName) : productDO.productName != null)
return false; return true;
} @Override
public int hashCode() {
int result;
long temp;
result = id;
result = 31 * result + (productName != null ? productName.hashCode() : 0);
temp = Double.doubleToLongBits(productPrice);
result = 31 * result + (int) (temp ^ (temp >>> 32));
result = 31 * result + providerId;
return result;
}
}

ProductDO.java

package cn.xiangxu.jpa_demo04.domain.domain_do;

import lombok.ToString;

import javax.persistence.*;

/**
* @author 王杨帅
* @create 2018-08-14 19:54
* @desc
**/
@Entity
@ToString
@Table(name = "provider", schema = "testdemo", catalog = "")
public class ProviderDO {
private int id;
private String providerName;
private String providerPhone;
private String providerAddress; @Id
@Column(name = "id")
public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} @Basic
@Column(name = "provider_name")
public String getProviderName() {
return providerName;
} public void setProviderName(String providerName) {
this.providerName = providerName;
} @Basic
@Column(name = "provider_phone")
public String getProviderPhone() {
return providerPhone;
} public void setProviderPhone(String providerPhone) {
this.providerPhone = providerPhone;
} @Basic
@Column(name = "provider_address")
public String getProviderAddress() {
return providerAddress;
} public void setProviderAddress(String providerAddress) {
this.providerAddress = providerAddress;
} @Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false; ProviderDO that = (ProviderDO) object; if (id != that.id) return false;
if (providerName != null ? !providerName.equals(that.providerName) : that.providerName != null) return false;
if (providerPhone != null ? !providerPhone.equals(that.providerPhone) : that.providerPhone != null)
return false;
if (providerAddress != null ? !providerAddress.equals(that.providerAddress) : that.providerAddress != null)
return false; return true;
} @Override
public int hashCode() {
int result = id;
result = 31 * result + (providerName != null ? providerName.hashCode() : 0);
result = 31 * result + (providerPhone != null ? providerPhone.hashCode() : 0);
result = 31 * result + (providerAddress != null ? providerAddress.hashCode() : 0);
return result;
}
}

ProviderDO.java

package cn.xiangxu.jpa_demo04.domain.domain_do;

import lombok.Builder;
import lombok.Data; /**
* @author 王杨帅
* @create 2018-08-14 20:03
* @desc
**/
@Data
@Builder public class ProductInfoDTO { private int id;
private String productName;
private double productPrice;
private int providerId;
private String providerName;
private String providerPhone;
private String providerAddress; }

ProductInfoDTO.java

    5.3.3 创建持久层接口

      技巧01:这里使用@Query进行原生的SQL查询,所以直接在ProductDAO中就可以查询出产品和供应商的信息

package cn.xiangxu.jpa_demo04.repository;

import cn.xiangxu.jpa_demo04.domain.domain_do.ProductDO;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import java.util.List;
import java.util.Map; /**
* @author 王杨帅
* @create 2018-08-14 19:55
* @desc
**/
public interface ProductDAO extends JpaRepository<ProductDO, Integer> { @Query(
nativeQuery = true,
value = "SELECT product_name, product_price, provider_name, provider_phone, provider_address\n" +
"FROM product\n" +
"JOIN provider \n" +
"ON product.provider_id = provider.id\n" +
"WHERE product.provider_id = ?1"
)
List<Map<String, Object>> findTest(Integer providerId); }

ProductDAO.java

    5.3.4 测试

      技巧01:多表关联查询时获取到数据是Map类型的,需要进行一层转化;本博文用的是阿里巴巴的 fastjson 进行转化

        <dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.1.46</version>
</dependency>

      坑01:fastjson 转化时 map 的 key 要和 实体类的属性 保持一致,不一致时只有通过本办法实现了

package cn.xiangxu.jpa_demo04.repository;

import cn.xiangxu.jpa_demo04.JpaDemo04ApplicationTests;
import cn.xiangxu.jpa_demo04.domain.domain_do.DoctorInfoDetail;
import cn.xiangxu.jpa_demo04.domain.domain_do.ProductDO;
import cn.xiangxu.jpa_demo04.domain.domain_do.ProductInfoDTO;
import com.alibaba.fastjson.JSONObject;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import java.util.ArrayList;
import java.util.List;
import java.util.Map; @Component
public class ProductDaoTest extends JpaDemo04ApplicationTests { @Autowired
private ProductDAO productDAO; @Test
public void test01() {
List<ProductDO> all = productDAO.findAll();
System.out.println(all);
} @Test
public void test02() {
List<Map<String, Object>> test = productDAO.findTest(2);
System.out.println(test); List<ProductInfoDTO> productInfoDTOS = new ArrayList<>(); for (Integer i = 0; i < test.size(); i++) {
Map<String, Object> info = test.get(i);
// ProductInfoDTO productInfoDTO = JSONObject.parseObject(JSONObject.toJSONString(test.get(i)), ProductInfoDTO.class);
ProductInfoDTO productInfoDTO = ProductInfoDTO
.builder()
.productName((String)info.get("product_name"))
.productPrice((Double)info.get("product_price"))
.providerName((String)info.get("provider_name"))
.providerPhone((String)info.get("provider_phone"))
.providerAddress((String)info.get("provider_address"))
.build();
System.out.println(productInfoDTO); productInfoDTOS.add(productInfoDTO);
} System.out.println(productInfoDTOS); } }

SpringBoot24 SpringDataJPA环境搭建、实体类注解、关联查询的更多相关文章

  1. spring+hibernate实体类注解详解(非原创) + cascade属性取值

    @Entity //继承策略.另一个类继承本类,那么本类里的属性应用到另一个类中 @Inheritance(strategy = InheritanceType.JOINED ) @Table(nam ...

  2. 实体类注解错误:Could not determine type for: java.util.List

    今天配置实体类注解时,出现以下错误: Caused by: org.hibernate.MappingException: Could not determine type for: java.uti ...

  3. JPA实体类注解、springboot测试类、lombok的使用

    前提准备: 搭建一个springboot项目,详情请参见其它博客:点击前往 1 引入相关依赖 web.mysql.jpa.lombok <?xml version="1.0" ...

  4. Hibernate实体类注解

    常用的hibernate annotation标签如下: @Entity --注释声明该类为持久类.将一个Javabean类声明为一 个实体的数据库表映射类,最好实现序列化.此时,默认情况下,所有的类 ...

  5. HIbernate实体类注解配置

    一.类级别注解 1.@Entity(name="EntityName") 必须 name为可选,对应数据库中一的个表 2.@Table(name="",cata ...

  6. Hibernate自动生成实体类注解(转)

    常用的hibernate annotation标签如下: @Entity --注释声明该类为持久类.将一个Javabean类声明为一 个实体的数据库表映射类,最好实现序列化.此时,默认情况下,所有的类 ...

  7. Hibernate实体类注解解释

    Hibernate注解1.@Entity(name="EntityName")必须,name为可选,对应数据库中一的个表2.@Table(name="",cat ...

  8. spring+hibernate 实体类注解问题

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.Ann ...

  9. IntelliJ IDEA 2017版 spring-boot2.0.2 搭建 JPA springboot DataSource JPA环境搭建,JPA注解@ManyToOne使用详情;JPA外键设置

    一.数据库原型 数据库模型如图所示,而现在需要根据数据库模型,建立对应的实体类,这在项目重构老数据库,采用新的框架重构上应该是比较常见的. 数据库脚本如下: CREATE TABLE `bomsub` ...

随机推荐

  1. I.MX6 Android 设备节点权限

    /********************************************************************************** * I.MX6 Android ...

  2. 深入学习Heritrix---解析Frontier(链接工厂)(转)

    深入学习Heritrix---解析Frontier(链接工厂) Frontier是Heritrix最核心的组成部分之一,也是最复杂的组成部分.它主要功能是为处理链接的线程提供URL,并负责链接处理完成 ...

  3. HihoCoder 1044 01-string 贪心

    1144 : 01串 时间限制:7000ms 单点时限:1000ms 内存限制:256MB 描述 给定两个整数n和m,求是否存在恰好包含n个0和m个1的01串S,使得S中不存在子串"001& ...

  4. IOS Quartz 2D 学习(1)

    IOS提供两种创建图形的途径: 1.OpenGL. 2.Quartz.Core Animation.UIKit图形支持. UIKit的图形系统 1.视图绘画周期: DrawRect方法,在任何时候,当 ...

  5. CentOS系统内核、操作系统位数以及系统参数查看

    2016-07-29 一.系统内核的查看方法 1.uname -a 显示详细的内核信息 Linux localhost.localdomain 3.10.0-229.el7.x86_64 #1 SMP ...

  6. 关于sea.js的笔记

    首先,引入sea.js:(注意要直接写在Script标签里,不要写在jquery的页面加载事件里) seajs.config({ base: "./" //seajs的基础路径(组 ...

  7. MinGW安装与环境变量配置和Sublime Text 2搭建C++编译环境

    MinGW安装与环境变量配置 从http://sourceforge.net/projects/mingw/下载MinGW,安装到D:\MinGW.工具集选择安装(之后还可以进行安装卸载):至少需要安 ...

  8. Object-C 多线程中锁的使用-NSLock

    在多线程的编程环境中,锁的使用必不可少! 于是,今天来总结一下为共享资源加锁的操作方法.   一.使用synchronized方式   //线程1 dispatch_async(dispatch_ge ...

  9. Annotation之一:Java Annotation基本功能介绍

    一.元数据的作用 如果要对于元数据的作用进行分类,目前还没有明确的定义,不过我们可以根据它所起的作用,大致可分为三类: 编写文档:通过代码里标识的元数据生成文档.这是最常见的,也是java 最早提供的 ...

  10. 杂项-WiFi:JotSpot

    ylbtech-杂项-WiFi:JotSpot JotSpot公司成立于2003年,由曾经创建了搜索引擎Excite的Joe Kraus一手创建,曾获得了来自Mayfield和RedPoint 两家风 ...