MyBatis联表查询

article/2025/8/19 16:38:24

数据库表结构

CREATE TABLE `teacher` (`id` int(11) NOT NULL AUTO_INCREMENT,`tname` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;CREATE TABLE `student` (`id` int(11) NOT NULL AUTO_INCREMENT,`Sname` varchar(255) DEFAULT NULL,`sex` varchar(255) DEFAULT NULL,`age` int(11) DEFAULT NULL,`t_id` int(11) DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
  • student 表:存储学生信息,包含字段id(主键)、Sname(姓名)、sex(性别)、age(年龄)、t_id(关联教师 ID)
  • teacher 表:存储教师信息,包含字段id(主键)、tname(教师姓名)

一 创建新实体类(最常用)

MyBatis 映射配置

<select id="findStudentTeacher" resultType="com.qcby.entity.StudentTeacher">select student.*,teacher.Tname from student LEFT JOIN teacher on student.t_id=teacher.id ;
</select>

实体类设计

public class StudentTeacher {private Integer id;       // 学生IDprivate String Sname;     // 学生姓名private String sex;       // 学生性别private String age;       // 学生年龄(注意:数据库为int,此处应为Integer更合适)private Integer t_id;     // 关联教师IDprivate String Tname;     // 教师姓名// getters/setters/toString
}
  • 字段映射:StudentTeacher 类的属性与查询结果字段一一对应
  • 类型问题age字段在数据库中为int,但实体类中定义为String,可能导致类型不匹配(需注意)

测试方法

@Test
public void findStudentTeacher(){List<StudentTeacher> studentTeachers = mapper.findStudentTeacher();for(StudentTeacher studentTeacher : studentTeachers){System.out.println(studentTeacher.toString());}
}
  • 功能:调用 Mapper 接口方法执行查询,并打印所有学生 - 教师关联信息
  • 输出
    StudentTeacher{id=1, Sname='张三', sex='男', age='18', t_id=1, Tname='张老师'}
    StudentTeacher{id=2, Sname='李四', sex='女', age='18', t_id=1, Tname='张老师'}
    StudentTeacher{id=5, Sname='小红', sex='女', age='20', t_id=2, Tname='李老师'}
    // ...其他学生记录
  • 查询类型:左外连接(LEFT JOIN),确保即使学生没有关联教师(t_id 为 NULL)也会被查询出来
  • 查询字段student.*(所有学生字段) + teacher.Tname(教师姓名)
  • 关联条件student.t_id = teacher.id,通过学生表的外键关联教师表主键

二 关联映射

1. SQL 查询与关联逻辑

<select id="findStudentTeacher1" resultMap="StudentTeacher">select student.*, teacher.Tname from student LEFT JOIN teacher on student.t_id = teacher.id;
</select>
  • 查询类型:左外连接(LEFT JOIN),确保即使学生没有关联教师(t_id为 NULL)也会被查询出来。
  • 返回字段
    • student.*:学生表的所有字段(idSnamesexaget_id)。
    • teacher.Tname:教师姓名(需注意表别名一致性,原表中字段名为tname,查询中使用了Tname作为别名)。

2. ResultMap 配置

<resultMap id="StudentTeacher" type="com.qcby.entity.student"><id property="id" column="id"/><result property="Sname" column="Sname"/><result property="sex" column="sex"/><result property="age" column="age"/><result property="t_id" column="t_id"/><association property="teacher" javaType="com.qcby.entity.Teacher"><result property="Tname" column="Tname"/></association>
</resultMap>
  • 核心配置
    • type:主实体类为com.qcby.entity.student(注意类名首字母应大写,规范为Student)。
    • association:处理一对一关联,将查询结果中的教师信息映射到student类的teacher属性中。
      • property="teacher"student类中定义的Teacher类型属性。
      • javaType:指定teacher属性的类型为com.qcby.entity.Teacher
      • 内部的<result>标签:将查询结果的Tname列映射到Teacher类的Tname属性(需确保Teacher类中存在该属性)。

3. 实体类结构

// Student类
public class Student {private Integer id;private String Sname;private String sex;private Integer age;  // 注意:原代码中age为String,建议改为Integer与数据库类型匹配private Integer t_id;private Teacher teacher;  // 关联的教师对象// getters/setters
}// Teacher类
public class Teacher {private Integer id;      // 虽然查询未返回teacher.id,但建议保留private String Tname;    // 与查询结果中的Tname列映射// getters/setters
}

4. 执行结果与映射规则

@Test
public void findStudentTeacher1() {List<Student> studentTeachers = mapper.findStudentTeacher1();for (Student student : studentTeachers) {System.out.println(student);// 若学生有关联教师,可通过 student.getTeacher().getTname() 访问教师姓名}
}

数据流向

  1. SQL 查询返回学生和教师的组合结果集。
  2. MyBatis 根据resultMap配置:
    • 将学生字段(idSnamesexaget_id)映射到Student对象。
    • 将教师字段(Tname)映射到Student对象的teacher属性(类型为Teacher)。
  3. 若学生无关联教师(t_id为 NULL):
    • teacher属性会被初始化为null,而非空的Teacher对象。

5. 潜在问题与优化建议

  1. 字段映射问题

    • teacher.id缺失:查询中未返回teacher.id,但Teacher类通常应有该属性。建议在 SQL 中增加teacher.id as teacher_id,并在resultMap中添加<id property="id" column="teacher_id"/>
    • 大小写不一致:若数据库字段为tname,而查询中使用Tname作为别名,需确保一致性。
  2. 类型匹配

    • 数据库中ageint,实体类中建议使用Integer而非String
  3. 优化 ResultMap

<resultMap id="StudentTeacher" type="com.qcby.entity.Student"><id property="id" column="id"/><result property="sname" column="Sname"/> <!-- 建议属性名小写开头 --><result property="sex" column="sex"/><result property="age" column="age"/><result property="tId" column="t_id"/>    <!-- 驼峰命名更规范 --><association property="teacher" javaType="com.qcby.entity.Teacher"><id property="id" column="teacher_id"/> <!-- 增加教师ID映射 --><result property="name" column="Tname"/> <!-- 建议属性名改为name --></association>
</resultMap><!-- 对应的SQL调整 -->
<select id="findStudentTeacher1" resultMap="StudentTeacher">SELECT s.id, s.Sname, s.sex, s.age, s.t_id,t.id AS teacher_id, t.tname AS Tname  <!-- 显式指定别名 -->FROM student sLEFT JOIN teacher t ON s.t_id = t.id;
</select>

6. 输出

通过这种关联映射,MyBatis 将关系型数据转换为对象嵌套结构,方便在 Java 代码中直接访问关联对象(如student.getTeacher().getName())。

 ResultMap

  • 定义ResultMap是 MyBatis 中最强大的元素,用于定义 SQL 查询结果与 Java 对象属性之间的映射关系。
  • 适用场景
    • 表字段名与对象属性名不一致。
    • 处理复杂的关联查询(一对一、一对多、多对多)。
    • 自定义类型转换。

 基础标签

  • <resultMap>:根标签,定义映射规则。
    • id:唯一标识,供<select>引用。
    • type:映射的目标 Java 类。
  • <id>:主键映射(提高性能)。
  • <result>:普通字段映射。
  • <association>:一对一关联映射。
  • <collection>:一对多关联映射。

三多表查询;分步实现

  <!--多表查询;分步实现--><!--select *from student--><!--将第一个查询出来的t_1d字段的值写入到第二个sql当中--><!--select *from teacher where id = #{t_id}-->
<select id="findStudentTeacher2" resultMap="StudentTeacher2">SELECT * FROM  student</select><resultMap id="StudentTeacher2" type="com.qcby.entity.student"><id property="id" column="id"/><result property="Sname" column="Sname"/><result property="sex" column="sex"/><result property="age" column="age"/><result property="t_id" column="t_id"/><!--   column:传值的作用     select:sql方法调用--><association property="teacher" javaType="com.qcby.entity.Teacher" column="t_id" select="getTeacher" /></resultMap><select id="getTeacher"  resultType="com.qcby.entity.Teacher">SELECT * FROM teacher where id=#{t_id}</select>

主查询:findStudentTeacher2

<select id="findStudentTeacher2" resultMap="StudentTeacher2">SELECT * FROM student
</select>
  1. 功能:查询所有学生记录
  2. 结果映射:使用StudentTeacher2这个 resultMap 处理结果
2. 结果映射配置:StudentTeacher2
<resultMap id="StudentTeacher2" type="com.qcby.entity.student"><id property="id" column="id"/><result property="Sname" column="Sname"/><!-- 其他基本字段映射 --><association property="teacher" javaType="com.qcby.entity.Teacher" column="t_id" select="getTeacher" />
</resultMap>
  • 关键点<association>标签定义了学生与教师的关联关系
    • property="teacher":将查询结果映射到 Student 类的 teacher 属性
    • javaType="com.qcby.entity.Teacher":指定关联对象的类型
    • column="t_id":将主查询中的 t_id 字段值作为参数传递给子查询
    • select="getTeacher":指定子查询的方法名
3. 子查询:getTeacher
<select id="getTeacher" resultType="com.qcby.entity.Teacher">SELECT * FROM teacher where id=#{t_id}
</select>
  • 功能:根据教师 ID 查询教师信息
  • 参数来源:来自主查询中传递的t_id
  • 结果处理:直接映射到 Teacher 类(自动匹配属性名和字段名)
    @Testpublic void findStudentTeacher2(){List<student> studentTeachers = mapper.findStudentTeacher2();for (student studentTeacher:studentTeachers){System.out.println(studentTeacher.toString());}}
  • 执行流程
    1. 调用findStudentTeacher2()方法查询所有学生
    2. 对于每个学生记录,MyBatis 会自动触发getTeacher查询其关联教师
    3. 将教师对象设置到 Student 的 teacher 属性中
    4. 最终返回一个包含完整教师信息的学生列表

四一对多(一个老师对多个学生) 

<!--查询老师的学生--><select id="FindTeacherStudents" resultMap="TeacherStudents" >select teacher.*,student.* from student RIGHT JOIN teacher on student.t_id=teacher.id ;</select><resultMap id="TeacherStudents" type="com.qcby.entity.Teacher"><id property="id" column="id"/><result property="Tname" column="Tanme"/><!--ofType泛型里的类型--><collection property="students" ofType="com.qcby.entity.student"><id property="id" column="id"/><result property="Sname" column="Sname"/><result property="sex" column="sex"/><result property="age" column="age"/><result property="t_id" column="t_id"/></collection></resultMap>
@Testpublic  void FindTeacherStudents(){
List<Teacher> teachers=mapper.FindTeacherStudents();
for(Teacher teacher:teachers){System.out.println(teacher.toString());
}}

分步解析

1. SQL 查询部分
<select id="FindTeacherStudents" resultMap="TeacherStudents">select teacher.*, student.* from student RIGHT JOIN teacher on student.t_id = teacher.id;
</select>
  • 功能:通过右外连接查询所有老师及其关联的学生
  • 表关系student.t_id(学生表的外键)关联teacher.id(老师表主键)
  • 结果集:返回的每条记录包含一个老师和一个学生的组合(若老师无学生,学生字段为 NULL)
2. 结果映射配置
<resultMap id="TeacherStudents" type="com.qcby.entity.Teacher"><id property="id" column="id"/><result property="Tname" column="Tanme"/> <!-- 注意:可能存在字段名拼写错误 --><collection property="students" ofType="com.qcby.entity.student"><id property="id" column="id"/><result property="Sname" column="Sname"/><result property="sex" column="sex"/><result property="age" column="age"/><result property="t_id" column="t_id"/></collection>
</resultMap>
  • 主映射:将结果映射到Teacher
  • 字段映射
    • id → Teacher.id
    • Tname → Teacher.Tname(注意:SQL 列名可能应为tname
  • 集合映射
    • property="students":将学生列表映射到Teacher.students属性
    • ofType="student":指定集合元素类型为Student
    • 内部的<result>标签定义学生对象的字段映射
3. Java 测试代码
@Test
public void FindTeacherStudents() {List<Teacher> teachers = mapper.FindTeacherStudents();for(Teacher teacher : teachers) {System.out.println(teacher.toString());}
}
  • 执行流程
    1. 调用FindTeacherStudents()方法执行 SQL 查询
    2. MyBatis 根据resultMap配置将结果集转换为Teacher对象列表
    3. 每个Teacher对象的students属性包含其关联的学生列表
    4. 打印每个老师及其学生信息

关键技术点

1. 右外连接(RIGHT JOIN)
  • 作用:确保即使老师没有学生,也会出现在结果集中(学生字段为 NULL)
  • 替代方案:若只需查询有学生的老师,可使用INNER JOIN
2. 集合映射(<collection>
  • 核心机制:MyBatis 通过相同的teacher.id将多条记录合并到同一个Teacher对象
  • 与分步查询的区别
    • 此方案通过单条 SQL 查询所有数据(适合数据量小的场景)
    • 分步查询会触发多次 SQL(适合延迟加载或大数据量场景)
3. 字段名冲突处理
  • 当两个表存在相同字段名(如id)时,MyBatis 会自动处理:
    • 主对象(Teacher)优先使用前面的列(teacher.id
    • 嵌套对象(Student)使用后面的列(student.id

懒加载 

MyBatis 的懒加载(延迟加载)是一种优化机制,允许在真正需要关联对象时才进行数据库查询,而不是在主对象加载时立即加载所有关联数据。这种策略可以显著提升性能,尤其是在处理复杂对象关系时。

核心原理

MyBatis 通过动态代理实现懒加载:

  1. 当主对象(如User)被加载时,其关联对象(如Order)会被替换为一个代理对象。
  2. 代理对象在首次被访问时(例如调用user.getOrders()),才会触发实际的 SQL 查询,加载关联数据。

配置步骤

1. 全局配置(mybatis-config.xml

启用懒加载并设置 aggressiveLazyLoading 为 false(避免触发所有 getter):

<settings><setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="false"/>
</settings>
2. 映射文件配置

在关联查询中使用 <association>(一对一)或 <collection>(一对多)标签,并设置 fetchType="lazy"

<resultMap id="userMap" type="User"><id property="id" column="id"/><collection property="orders" ofType="Order"column="id"select="selectOrdersByUserId"fetchType="lazy"/> <!-- 显式指定懒加载 -->
</resultMap>
3. Java 接口映射(注解方式)
public interface UserMapper {@Results({@Result(property = "orders", column = "id", many = @Many(select = "selectOrdersByUserId", fetchType = FetchType.LAZY))})@Select("SELECT * FROM users WHERE id = #{id}")User selectUserById(int id);
}

注意事项

  1. 会话生命周期
    懒加载的代理对象需要在 SqlSession 关闭前 被访问,否则会抛出 SessionAlreadyClosedException。可通过 OpenSessionInView 模式解决(如 Spring 的 OpenSessionInViewInterceptor)。

  2. 序列化问题
    懒加载的代理对象无法直接序列化(如返回 JSON),需确保在序列化前已加载所有数据。可通过 @JsonIgnore 排除未加载的属性,或使用 Hibernate.initialize() 强制初始化。

  3. 嵌套查询性能
    懒加载可能导致 N+1 查询问题(主查询 1 次,关联查询 N 次),需结合 BatchExecutor 或二级缓存优化。

示例代码

// 主对象
public class User {private Integer id;private String name;private List<Order> orders; // 延迟加载的关联对象// getters/setters
}// 测试懒加载
try (SqlSession session = sqlSessionFactory.openSession()) {UserMapper mapper = session.getMapper(UserMapper.class);User user = mapper.selectUserById(1); // 此时 orders 是代理对象// 首次访问 orders 时触发实际查询System.out.println(user.getOrders().size()); 
}

禁用懒加载的场景

  • 关联数据一定会被使用,提前加载更高效。
  • 需要在会话关闭后访问关联对象。
  • 使用二级缓存时,懒加载可能失效。

通过合理配置懒加载,MyBatis 可以在保证数据完整性的同时,大幅提升系统性能。


http://www.hkcw.cn/article/WckJSNksPj.shtml

相关文章

技术分享 | Oracle SQL优化案例一则

本文为墨天轮数据库管理服务团队第70期技术分享&#xff0c;内容原创&#xff0c;作者为技术顾问马奕璇&#xff0c;如需转载请联系小墨&#xff08;VX&#xff1a;modb666&#xff09;并注明来源。 一、问题概述 开发人员反映有条跑批语句在测试环境执行了很久都没结束&…

在力扣刷题中触摸算法的温度

在代码的世界里&#xff0c;每一道力扣题目都是一扇通往未知的门。当我推开这些门&#xff0c;与内置求和函数、二进制位运算、辗转相减思想以及链表结构相遇时&#xff0c;才真正触摸到算法的温度 —— 那是一种理性与智慧交织的炽热&#xff0c;也是思维不断淬炼的滚烫。​ 最…

LangFuse:开源LLM工程平台的革新实践

文章目录 一 架构设计与技术栈二 增强型监控能力三 提示词工程支持&#xff08;新增&#xff09;四 性能优化实践五 LangFuse部署&#xff08;docker&#xff09;和代码集成5.1 LangFuse平台部署5.2 LangFuse代码集成和检测体验 一 架构设计与技术栈 LangFuse采用模块化架构设…

信创采购热潮下的隐忧:单一技术路线的市场垄断之困

在国家信息技术应用创新&#xff08;信创&#xff09;战略的强力推动下&#xff0c;信创产业迎来了前所未有的发展机遇。 然而&#xff0c;随着采购规模的快速增长&#xff0c;单一技术路线中标现象逐渐凸显&#xff0c;引发了行业内外的广泛关注。本文将从现状、成因与影响三个…

美国还有36个州仍允许未成年人或童婚婚姻

美国还有36个州仍允许未成年人婚姻当地时间5月28日,美国俄勒冈州的州长蒂娜科特克签署了一项法令,禁止俄勒冈州未满18岁的未成年人结婚。然而,在这则新闻背后却隐藏着一个令美国乃至世界很多国家的网民都相当吃惊的魔幻情况……先介绍下俄勒冈的情况。根据当地媒体报道,俄勒…

国产榴莲6月中下旬批量上市 甜蜜来袭

对于美食爱好者而言,今年似乎又是一个“甜蜜”的年份。从年初开始,车厘子、蓝莓等曾经的高价水果价格纷纷大幅下降。在北京一家生鲜超市,一进门最显眼的位置上摆放着来自泰国的金枕榴莲。与榴莲相比,山竹的价格近年来相对稳定。这家超市里,一盒4A规格的山竹一共6颗,售价1…

国家要发财政补贴?假的 虚假信息需警惕

近日,有四川网民反映收到关于《2025年国家财政部补贴》的声明。该声明称,根据国家财政部和人力资源社会保障部发布的通知,将发放薪资补贴、社保补贴、医保补贴、住房补贴、交通补贴、岗位补贴等,并要求申领认证。5月29日,四川财政部门相关工作人员表示,这则消息是假的,其…

实战指南:步进电机规格书参数解析——以28BYJ48为例(不聊原理,只讲应用)

前言:为什么写这篇? 网上讲解步进电机原理的文章铺天盖地,但当你拿到一份电机规格书时,面对诸如“牵出频率≥1000Hz”,“自定位转矩≥300gfcm”等参数,是否仍感到一头雾水?本文以常见的28BYJ48减速步进电机规格书为例,跳过原理,直击参数的实际意义与应用陷阱,助你快速…

男子酒驾冲卡撞伤交警 肇事者已被刑拘

5月27日晚,交警在陕西西安莲湖区文景南路与农兴路十字路口附近设卡执勤时,一名男子驾车冲卡,撞毁护栏并撞伤一名交警。该男子涉嫌酒驾,已被刑拘。事发后,该男子弃车逃离现场,但很快被执勤交警抓获。目击者称,听到撞击声后,一辆由北向南行驶的黑色商务车冲过道路中间的护…

有多少业主,想着赶走自己的物业公司

‌有相当一部分业主希望赶走自己的物业公司‌。许多业主对物业公司的服务感到不满,主要原因包括物业公司服务不到位、乱收费、侵占业主收入等。例如,一些物业公司被指责拿钱不干活,设备损坏拖延维修,额外收费项目模糊不清,甚至侵占广告收入等‌。此外,部分业主认为物业公…

90后作家刘楚昕获奖感言刷屏 挚爱遗言催人泪下

日前,90后作家刘楚昕创作的小说《泥潭》荣获第二届漓江文学奖虚构类奖。在颁奖现场上,作家余华公布了这个好消息。而获奖者刘楚昕的感言因格外催泪动人在朋友圈里刷了屏。2017年,刘楚昕在武汉大学读博期间遇到了他的初恋女友。当时,他正朝着自己的文学梦马不停蹄地赶路。“…

时隔多日 金正恩露面再次喜笑颜开!

时隔多日金正恩露面再次喜笑颜开。据央视新闻报道,朝鲜人民军大联合部队炮兵部队29日进行了火炮射击比赛。朝鲜劳动党总书记、国务委员长金正恩观摩火炮射击比赛。在火炮射击比赛中,各前线大联合部队首长直接进行火力指挥。金正恩说,参赛炮兵部队展现了炮兵武装力量的实战能…

【Linux篇】叩响新世界的大门:线程

概念角度&#xff1a; 感性理解线程&#xff1a; 进程&#xff1a;内核数据结构数据和代码 线程&#xff1a;进程内部的一个执行分支 进程也是被cpu调度&#xff0c;所以进程还有一个执行流的概念 内核与资源角度理解&#xff1a; 进程&#xff1a; 承担分配系统资源的…

夫妻领证时发现互不知姓名 闪婚变闪离引发争议

你听说过这样的事吗?一对男女去民政局领结婚证,却因为男方不知道女方名字,女方也不知道男方名字而失败。这对来自襄州区古驿镇的年轻人尽管领证失败,但仍然坚持“闪婚”。他们在一起住了一年,但从未同房,最终这段短暂的婚姻走到了尽头。然而,高达十多万元的彩礼给两家人…

男子酒驾冲卡撞伤交警被刑拘 肇事司机已被控制

5月27日晚,交警在陕西西安莲湖区文景南路与农兴路十字路口附近设卡执勤时,一名男子驾车冲卡,冲毁护栏并撞伤一名交警。次日下午,该男子因涉嫌酒驾被刑拘。事发后,肇事司机弃车逃离现场,但很快被执勤交警抓获。据事发地商户描述,听到撞击声后,一辆由北向南行驶的黑色商务…

RISCV——内核及汇编

RISCV——内核及汇编 小狼http://blog.csdn.net/xiaolangyangyang 1、寄存器组&#xff08;ABI&#xff09; 2、异常及中断 XV6 trap&#xff08;二&#xff09;RISCV中断异常处理/定时器中断 mie&#xff1a;中断开关mip&#xff1a;中断状态mstatus.mie&#xff1a;全局中断…

安装Arch Linux(实体机、干货)

一、环境&#xff1a; 本地PC机&#xff1a;ASUS 目标&#xff1a;安装原生Arch &#xff08;备注&#xff1a;本文按照主流的UEFIGPTD方式&#xff0c;不涉及BIOSMBR&#xff09; Arch相关网站 官方网站:https://archlinux.org/官方wiki:https://wiki.archlinux.org/title/I…

电机控制选 STM32 还是 DSP?技术选型背后的现实博弈

现在搞电机控制&#xff0c;圈里人都门儿清 —— 主流方案早就被 STM32 这些 Cortex-M 单片机给拿捏了。可要是撞上系统里的老甲方&#xff0c;技术认知还停留在诺基亚砸核桃的年代&#xff0c;非揪着 DSP 不放&#xff0c;咱也只能赔笑脸&#xff1a;“您老说的对&#xff0c;…

秋招Day12 - 计算机网络 - 基础

说一下计算机网络体系结构 OSI七层模型&#xff0c;TCP/IP四层模型和五层体系结构 说说OSI七层模型&#xff1f; 应用层&#xff1a;最靠近用户的层&#xff0c;用于处理特定应用程序的细节&#xff0c;提供了应用程序和网络服务之间的接口。表示层&#xff1a;确保从一个系…

端午假期铁路运输今日开启 长三角迎来首波客流小高峰

2025年5月29日,G7583次高铁列车上,乘务员与旅客互动开展迎端午活动。5月30日,长三角铁路启动端午小长假运输,预计发送旅客342万人次。同日,铁路上海站预计发送旅客52.0万人次,车站增开安徽、徐州、沪杭等方向旅客列车52列。午后起,上海、杭州、南京、合肥、徐州、宁波、…