@@ -1 +1,16 @@ | |||
# spring-boot-demo-neo4j | |||
> 此 demo 主要演示了 Spring Boot 如何集成Neo4j操作图数据库,实现一个校园人物关系网。 | |||
## 注意 | |||
作者编写本demo时,Neo4j 版本为 `3.5.0`,使用 docker 运行,下面是所有步骤: | |||
1. 下载镜像:`docker pull neo4j:3.5.0` | |||
2. 运行容器:`docker run -d -p 7474:7474 -p 7687:7687 --name neo4j-3.5.0 neo4j:3.5.0` | |||
3. 停止容器:`docker stop neo4j-3.5.0` | |||
4. 启动容器:`docker start neo4j-3.5.0` | |||
5. 浏览器 http://localhost:7474/ 访问 neo4j 管理后台,初始账号/密码 neo4j/neo4j,会要求修改初始化密码,我们修改为 neo4j/admin | |||
@@ -38,6 +38,22 @@ | |||
<artifactId>spring-boot-starter-test</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.projectlombok</groupId> | |||
<artifactId>lombok</artifactId> | |||
<optional>true</optional> | |||
</dependency> | |||
<dependency> | |||
<groupId>cn.hutool</groupId> | |||
<artifactId>hutool-all</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.google.guava</groupId> | |||
<artifactId>guava</artifactId> | |||
</dependency> | |||
</dependencies> | |||
<build> | |||
@@ -0,0 +1,24 @@ | |||
package com.xkcoding.neo4j.config; | |||
import cn.hutool.core.util.IdUtil; | |||
import org.neo4j.ogm.id.IdStrategy; | |||
/** | |||
* <p> | |||
* 自定义主键策略 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.neo4j.config | |||
* @description: 自定义主键策略 | |||
* @author: yangkai.shen | |||
* @date: Created in 2018-12-24 14:40 | |||
* @copyright: Copyright (c) 2018 | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
public class CustomIdStrategy implements IdStrategy { | |||
@Override | |||
public Object generateId(Object o) { | |||
return IdUtil.fastUUID(); | |||
} | |||
} |
@@ -0,0 +1,37 @@ | |||
package com.xkcoding.neo4j.constants; | |||
/** | |||
* <p> | |||
* 常量池 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.neo4j.constants | |||
* @description: 常量池 | |||
* @author: yangkai.shen | |||
* @date: Created in 2018-12-24 14:45 | |||
* @copyright: Copyright (c) 2018 | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
public interface NeoConsts { | |||
/** | |||
* 关系:班级拥有的学生 | |||
*/ | |||
String R_STUDENT_OF_CLASS = "R_STUDENT_OF_CLASS"; | |||
/** | |||
* 关系:班级的班主任 | |||
*/ | |||
String R_BOSS_OF_CLASS = "R_BOSS_OF_CLASS"; | |||
/** | |||
* 关系:课程的老师 | |||
*/ | |||
String R_TEACHER_OF_LESSON = "R_TEACHER_OF_LESSON"; | |||
/** | |||
* 关系:学生选的课 | |||
*/ | |||
String R_LESSON_OF_STUDENT = "R_LESSON_OF_STUDENT"; | |||
} |
@@ -0,0 +1,50 @@ | |||
package com.xkcoding.neo4j.model; | |||
import com.xkcoding.neo4j.config.CustomIdStrategy; | |||
import com.xkcoding.neo4j.constants.NeoConsts; | |||
import lombok.*; | |||
import org.neo4j.ogm.annotation.GeneratedValue; | |||
import org.neo4j.ogm.annotation.Id; | |||
import org.neo4j.ogm.annotation.NodeEntity; | |||
import org.neo4j.ogm.annotation.Relationship; | |||
/** | |||
* <p> | |||
* 班级节点 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.neo4j.model | |||
* @description: 班级节点 | |||
* @author: yangkai.shen | |||
* @date: Created in 2018-12-24 14:44 | |||
* @copyright: Copyright (c) 2018 | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Data | |||
@NoArgsConstructor | |||
@RequiredArgsConstructor(staticName = "of") | |||
@AllArgsConstructor | |||
@Builder | |||
@NodeEntity | |||
public class Class { | |||
/** | |||
* 主键 | |||
*/ | |||
@Id | |||
@GeneratedValue(strategy = CustomIdStrategy.class) | |||
private String id; | |||
/** | |||
* 班级名称 | |||
*/ | |||
@NonNull | |||
private String name; | |||
/** | |||
* 班级的班主任 | |||
*/ | |||
@Relationship(NeoConsts.R_BOSS_OF_CLASS) | |||
@NonNull | |||
private Teacher boss; | |||
} |
@@ -0,0 +1,50 @@ | |||
package com.xkcoding.neo4j.model; | |||
import com.xkcoding.neo4j.config.CustomIdStrategy; | |||
import com.xkcoding.neo4j.constants.NeoConsts; | |||
import lombok.*; | |||
import org.neo4j.ogm.annotation.GeneratedValue; | |||
import org.neo4j.ogm.annotation.Id; | |||
import org.neo4j.ogm.annotation.NodeEntity; | |||
import org.neo4j.ogm.annotation.Relationship; | |||
/** | |||
* <p> | |||
* 课程节点 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.neo4j.model | |||
* @description: 课程节点 | |||
* @author: yangkai.shen | |||
* @date: Created in 2018-12-24 14:55 | |||
* @copyright: Copyright (c) 2018 | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Data | |||
@NoArgsConstructor | |||
@RequiredArgsConstructor(staticName = "of") | |||
@AllArgsConstructor | |||
@Builder | |||
@NodeEntity | |||
public class Lesson { | |||
/** | |||
* 主键,自定义主键策略,使用UUID生成 | |||
*/ | |||
@Id | |||
@GeneratedValue(strategy = CustomIdStrategy.class) | |||
private String id; | |||
/** | |||
* 课程名称 | |||
*/ | |||
@NonNull | |||
private String name; | |||
/** | |||
* 任教老师 | |||
*/ | |||
@Relationship(NeoConsts.R_TEACHER_OF_LESSON) | |||
@NonNull | |||
private Teacher teacher; | |||
} |
@@ -0,0 +1,60 @@ | |||
package com.xkcoding.neo4j.model; | |||
import com.xkcoding.neo4j.config.CustomIdStrategy; | |||
import com.xkcoding.neo4j.constants.NeoConsts; | |||
import lombok.*; | |||
import org.neo4j.ogm.annotation.GeneratedValue; | |||
import org.neo4j.ogm.annotation.Id; | |||
import org.neo4j.ogm.annotation.NodeEntity; | |||
import org.neo4j.ogm.annotation.Relationship; | |||
import java.util.List; | |||
/** | |||
* <p> | |||
* 学生节点 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.neo4j.model | |||
* @description: 学生节点 | |||
* @author: yangkai.shen | |||
* @date: Created in 2018-12-24 14:38 | |||
* @copyright: Copyright (c) 2018 | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Data | |||
@NoArgsConstructor | |||
@RequiredArgsConstructor(staticName = "of") | |||
@AllArgsConstructor | |||
@Builder | |||
@NodeEntity | |||
public class Student { | |||
/** | |||
* 主键,自定义主键策略,使用UUID生成 | |||
*/ | |||
@Id | |||
@GeneratedValue(strategy = CustomIdStrategy.class) | |||
private String id; | |||
/** | |||
* 学生姓名 | |||
*/ | |||
@NonNull | |||
private String name; | |||
/** | |||
* 学生选的所有课程 | |||
*/ | |||
@Relationship(NeoConsts.R_LESSON_OF_STUDENT) | |||
@NonNull | |||
private List<Lesson> lessons; | |||
/** | |||
* 学生所在班级 | |||
*/ | |||
@Relationship(NeoConsts.R_STUDENT_OF_CLASS) | |||
@NonNull | |||
private Class clazz; | |||
} |
@@ -0,0 +1,41 @@ | |||
package com.xkcoding.neo4j.model; | |||
import com.xkcoding.neo4j.config.CustomIdStrategy; | |||
import lombok.*; | |||
import org.neo4j.ogm.annotation.GeneratedValue; | |||
import org.neo4j.ogm.annotation.Id; | |||
import org.neo4j.ogm.annotation.NodeEntity; | |||
/** | |||
* <p> | |||
* 教师节点 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.neo4j.model | |||
* @description: 教师节点 | |||
* @author: yangkai.shen | |||
* @date: Created in 2018-12-24 14:54 | |||
* @copyright: Copyright (c) 2018 | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Data | |||
@NoArgsConstructor | |||
@RequiredArgsConstructor(staticName = "of") | |||
@AllArgsConstructor | |||
@Builder | |||
@NodeEntity | |||
public class Teacher { | |||
/** | |||
* 主键,自定义主键策略,使用UUID生成 | |||
*/ | |||
@Id | |||
@GeneratedValue(strategy = CustomIdStrategy.class) | |||
private String id; | |||
/** | |||
* 教师姓名 | |||
*/ | |||
@NonNull | |||
private String name; | |||
} |
@@ -0,0 +1,34 @@ | |||
package com.xkcoding.neo4j.payload; | |||
import com.xkcoding.neo4j.model.Student; | |||
import lombok.Data; | |||
import org.springframework.data.neo4j.annotation.QueryResult; | |||
import java.util.List; | |||
/** | |||
* <p> | |||
* 按照课程分组的同学关系 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.neo4j.payload | |||
* @description: 按照课程分组的同学关系 | |||
* @author: yangkai.shen | |||
* @date: Created in 2018-12-24 19:18 | |||
* @copyright: Copyright (c) 2018 | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Data | |||
@QueryResult | |||
public class ClassmateInfoGroupByLesson { | |||
/** | |||
* 课程名称 | |||
*/ | |||
private String lessonName; | |||
/** | |||
* 学生信息 | |||
*/ | |||
private List<Student> students; | |||
} |
@@ -0,0 +1,29 @@ | |||
package com.xkcoding.neo4j.repository; | |||
import com.xkcoding.neo4j.model.Class; | |||
import org.springframework.data.neo4j.repository.Neo4jRepository; | |||
import java.util.Optional; | |||
/** | |||
* <p> | |||
* 班级节点Repository | |||
* </p> | |||
* | |||
* @package: com.xkcoding.neo4j.repository | |||
* @description: 班级节点Repository | |||
* @author: yangkai.shen | |||
* @date: Created in 2018-12-24 15:05 | |||
* @copyright: Copyright (c) 2018 | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
public interface ClassRepository extends Neo4jRepository<Class, String> { | |||
/** | |||
* 根据班级名称查询班级信息 | |||
* | |||
* @param name 班级名称 | |||
* @return 班级信息 | |||
*/ | |||
Optional<Class> findByName(String name); | |||
} |
@@ -0,0 +1,20 @@ | |||
package com.xkcoding.neo4j.repository; | |||
import com.xkcoding.neo4j.model.Lesson; | |||
import org.springframework.data.neo4j.repository.Neo4jRepository; | |||
/** | |||
* <p> | |||
* 课程节点Repository | |||
* </p> | |||
* | |||
* @package: com.xkcoding.neo4j.repository | |||
* @description: 课程节点Repository | |||
* @author: yangkai.shen | |||
* @date: Created in 2018-12-24 15:05 | |||
* @copyright: Copyright (c) 2018 | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
public interface LessonRepository extends Neo4jRepository<Lesson, String> { | |||
} |
@@ -0,0 +1,48 @@ | |||
package com.xkcoding.neo4j.repository; | |||
import com.xkcoding.neo4j.model.Student; | |||
import com.xkcoding.neo4j.payload.ClassmateInfoGroupByLesson; | |||
import org.springframework.data.neo4j.annotation.Depth; | |||
import org.springframework.data.neo4j.annotation.Query; | |||
import org.springframework.data.neo4j.repository.Neo4jRepository; | |||
import org.springframework.data.repository.query.Param; | |||
import java.util.List; | |||
import java.util.Optional; | |||
/** | |||
* <p> | |||
* 学生节点Repository | |||
* </p> | |||
* | |||
* @package: com.xkcoding.neo4j.repository | |||
* @description: 学生节点Repository | |||
* @author: yangkai.shen | |||
* @date: Created in 2018-12-24 15:05 | |||
* @copyright: Copyright (c) 2018 | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
public interface StudentRepository extends Neo4jRepository<Student, String> { | |||
/** | |||
* 根据名称查找学生 | |||
* | |||
* @param name 姓名 | |||
* @param depth 深度 | |||
* @return 学生信息 | |||
*/ | |||
Optional<Student> findByName(String name, @Depth int depth); | |||
/** | |||
* 根据班级查询班级人数 | |||
* | |||
* @param className 班级名称 | |||
* @return 班级人数 | |||
*/ | |||
@Query("MATCH (s:Student)-[r:R_STUDENT_OF_CLASS]->(c:Class{name:{className}}) return count(s)") | |||
Long countByClassName(@Param("className") String className); | |||
@Query("match (s:Student)-[:R_LESSON_OF_STUDENT]->(l:Lesson),(l:Lesson)<-[:R_LESSON_OF_STUDENT]-(:Student) with l.name as lessonName,collect(distinct s) as students return lessonName,students") | |||
List<ClassmateInfoGroupByLesson> findByClassmateGroupByLesson(); | |||
} |
@@ -0,0 +1,20 @@ | |||
package com.xkcoding.neo4j.repository; | |||
import com.xkcoding.neo4j.model.Teacher; | |||
import org.springframework.data.neo4j.repository.Neo4jRepository; | |||
/** | |||
* <p> | |||
* 教师节点Repository | |||
* </p> | |||
* | |||
* @package: com.xkcoding.neo4j.repository | |||
* @description: 教师节点Repository | |||
* @author: yangkai.shen | |||
* @date: Created in 2018-12-24 15:05 | |||
* @copyright: Copyright (c) 2018 | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
public interface TeacherRepository extends Neo4jRepository<Teacher, String> { | |||
} |
@@ -0,0 +1,166 @@ | |||
package com.xkcoding.neo4j.service; | |||
import cn.hutool.core.util.StrUtil; | |||
import com.google.common.collect.Lists; | |||
import com.google.common.collect.Maps; | |||
import com.xkcoding.neo4j.model.Class; | |||
import com.xkcoding.neo4j.model.Lesson; | |||
import com.xkcoding.neo4j.model.Student; | |||
import com.xkcoding.neo4j.model.Teacher; | |||
import com.xkcoding.neo4j.payload.ClassmateInfoGroupByLesson; | |||
import com.xkcoding.neo4j.repository.ClassRepository; | |||
import com.xkcoding.neo4j.repository.LessonRepository; | |||
import com.xkcoding.neo4j.repository.StudentRepository; | |||
import com.xkcoding.neo4j.repository.TeacherRepository; | |||
import org.neo4j.ogm.session.Session; | |||
import org.neo4j.ogm.session.SessionFactory; | |||
import org.neo4j.ogm.transaction.Transaction; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Service; | |||
import org.springframework.transaction.annotation.Transactional; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.stream.Collectors; | |||
/** | |||
* <p> | |||
* NeoService | |||
* </p> | |||
* | |||
* @package: com.xkcoding.neo4j.service | |||
* @description: NeoService | |||
* @author: yangkai.shen | |||
* @date: Created in 2018-12-24 15:19 | |||
* @copyright: Copyright (c) 2018 | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Service | |||
public class NeoService { | |||
@Autowired | |||
private ClassRepository classRepo; | |||
@Autowired | |||
private LessonRepository lessonRepo; | |||
@Autowired | |||
private StudentRepository studentRepo; | |||
@Autowired | |||
private TeacherRepository teacherRepo; | |||
@Autowired | |||
private SessionFactory sessionFactory; | |||
/** | |||
* 初始化数据 | |||
*/ | |||
@Transactional | |||
public void initData() { | |||
// 初始化老师 | |||
Teacher akai = Teacher.of("迈特凯"); | |||
Teacher kakaxi = Teacher.of("旗木卡卡西"); | |||
Teacher zilaiye = Teacher.of("自来也"); | |||
Teacher gangshou = Teacher.of("纲手"); | |||
Teacher dashewan = Teacher.of("大蛇丸"); | |||
teacherRepo.save(akai); | |||
teacherRepo.save(kakaxi); | |||
teacherRepo.save(zilaiye); | |||
teacherRepo.save(gangshou); | |||
teacherRepo.save(dashewan); | |||
// 初始化课程 | |||
Lesson tishu = Lesson.of("体术", akai); | |||
Lesson huanshu = Lesson.of("幻术", kakaxi); | |||
Lesson shoulijian = Lesson.of("手里剑", kakaxi); | |||
Lesson luoxuanwan = Lesson.of("螺旋丸", zilaiye); | |||
Lesson xianshu = Lesson.of("仙术", zilaiye); | |||
Lesson yiliao = Lesson.of("医疗", gangshou); | |||
Lesson zhouyin = Lesson.of("咒印", dashewan); | |||
lessonRepo.save(tishu); | |||
lessonRepo.save(huanshu); | |||
lessonRepo.save(shoulijian); | |||
lessonRepo.save(luoxuanwan); | |||
lessonRepo.save(xianshu); | |||
lessonRepo.save(yiliao); | |||
lessonRepo.save(zhouyin); | |||
// 初始化班级 | |||
Class three = Class.of("第三班", akai); | |||
Class seven = Class.of("第七班", kakaxi); | |||
classRepo.save(three); | |||
classRepo.save(seven); | |||
// 初始化学生 | |||
List<Student> threeClass = Lists.newArrayList(Student.of("漩涡鸣人", Lists.newArrayList(tishu, shoulijian, luoxuanwan, xianshu), seven), Student | |||
.of("宇智波佐助", Lists.newArrayList(huanshu, zhouyin, shoulijian), seven), Student.of("春野樱", Lists.newArrayList(tishu, yiliao, shoulijian), seven)); | |||
List<Student> sevenClass = Lists.newArrayList(Student.of("李洛克", Lists.newArrayList(tishu), three), Student.of("日向宁次", Lists | |||
.newArrayList(tishu), three), Student.of("天天", Lists.newArrayList(tishu), three)); | |||
studentRepo.saveAll(threeClass); | |||
studentRepo.saveAll(sevenClass); | |||
} | |||
/** | |||
* 删除数据 | |||
*/ | |||
@Transactional | |||
public void delete() { | |||
// 使用语句删除 | |||
Session session = sessionFactory.openSession(); | |||
Transaction transaction = session.beginTransaction(); | |||
session.query("match (n)-[r]-() delete n,r", Maps.newHashMap()); | |||
session.query("match (n)-[r]-() delete r", Maps.newHashMap()); | |||
session.query("match (n) delete n", Maps.newHashMap()); | |||
transaction.commit(); | |||
// 使用 repository 删除 | |||
studentRepo.deleteAll(); | |||
classRepo.deleteAll(); | |||
lessonRepo.deleteAll(); | |||
teacherRepo.deleteAll(); | |||
} | |||
/** | |||
* 根据学生姓名查询所选课程 | |||
* | |||
* @param studentName 学生姓名 | |||
* @param depth 深度 | |||
* @return 课程列表 | |||
*/ | |||
public List<Lesson> findLessonsFromStudent(String studentName, int depth) { | |||
List<Lesson> lessons = Lists.newArrayList(); | |||
studentRepo.findByName(studentName, depth).ifPresent(student -> lessons.addAll(student.getLessons())); | |||
return lessons; | |||
} | |||
/** | |||
* 查询全校学生数 | |||
* | |||
* @return 学生总数 | |||
*/ | |||
public Long studentCount(String className) { | |||
if (StrUtil.isBlank(className)) { | |||
return studentRepo.count(); | |||
} else { | |||
return studentRepo.countByClassName(className); | |||
} | |||
} | |||
/** | |||
* 查询同学关系,根据课程 | |||
* | |||
* @return 返回同学关系 | |||
*/ | |||
public Map<String, List<Student>> findClassmatesGroupByLesson() { | |||
List<ClassmateInfoGroupByLesson> groupByLesson = studentRepo.findByClassmateGroupByLesson(); | |||
Map<String, List<Student>> result = Maps.newHashMap(); | |||
groupByLesson.forEach(classmateInfoGroupByLesson -> result.put(classmateInfoGroupByLesson.getLessonName(), classmateInfoGroupByLesson | |||
.getStudents())); | |||
return result; | |||
} | |||
} |
@@ -0,0 +1,7 @@ | |||
spring: | |||
data: | |||
neo4j: | |||
uri: bolt://localhost | |||
username: neo4j | |||
password: admin | |||
open-in-view: false |
@@ -0,0 +1,82 @@ | |||
package com.xkcoding.neo4j; | |||
import cn.hutool.json.JSONUtil; | |||
import com.xkcoding.neo4j.model.Lesson; | |||
import com.xkcoding.neo4j.model.Student; | |||
import com.xkcoding.neo4j.service.NeoService; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.junit.Test; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.stream.Collectors; | |||
/** | |||
* <p> | |||
* 测试Neo4j | |||
* </p> | |||
* | |||
* @package: com.xkcoding.neo4j | |||
* @description: 测试Neo4j | |||
* @author: yangkai.shen | |||
* @date: Created in 2018-12-24 15:17 | |||
* @copyright: Copyright (c) 2018 | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Slf4j | |||
public class Neo4jTest extends SpringBootDemoNeo4jApplicationTests { | |||
@Autowired | |||
private NeoService neoService; | |||
/** | |||
* 测试保存 | |||
*/ | |||
@Test | |||
public void testSave() { | |||
neoService.initData(); | |||
} | |||
/** | |||
* 测试删除 | |||
*/ | |||
@Test | |||
public void testDelete() { | |||
neoService.delete(); | |||
} | |||
/** | |||
* 测试查询 鸣人 学了哪些课程 | |||
*/ | |||
@Test | |||
public void testFindLessonsByStudent() { | |||
// 深度为1,则课程的任教老师的属性为null | |||
// 深度为2,则会把课程的任教老师的属性赋值 | |||
List<Lesson> lessons = neoService.findLessonsFromStudent("漩涡鸣人", 2); | |||
lessons.forEach(lesson -> log.info("【lesson】= {}", JSONUtil.toJsonStr(lesson))); | |||
} | |||
/** | |||
* 测试查询班级人数 | |||
*/ | |||
@Test | |||
public void testCountStudent() { | |||
Long all = neoService.studentCount(null); | |||
log.info("【全校人数】= {}", all); | |||
Long seven = neoService.studentCount("第七班"); | |||
log.info("【第七班人数】= {}", seven); | |||
} | |||
/** | |||
* 测试根据课程查询同学关系 | |||
*/ | |||
@Test | |||
public void testFindClassmates() { | |||
Map<String, List<Student>> classmates = neoService.findClassmatesGroupByLesson(); | |||
classmates.forEach((k, v) -> log.info("因为一起上了【{}】这门课,成为同学关系的有:{}", k, JSONUtil.toJsonStr(v.stream() | |||
.map(Student::getName) | |||
.collect(Collectors.toList())))); | |||
} | |||
} |