本章主要记录MyBatis中的多表映射:一对多、多对一和多对多,涉及到的知识点有:
1. resultMap
:当数据库方法返回的是复合数据类型(如list等),通常使用resultMap
而非resultType
。
2. collection
:在resultMap
中用来配置集合类型的数据结构,用来实现表间一对多映射。
3. association
:在resultMap
中用来配置关联关系的数据结构,用来实现表间多对一以及一对一映射。
1.表间一对多关联映射
实例场景:person(人员表)
与experience(工作经历表)
是一对多关系,即:一个人有多条工作经验,一条工作经验只能属于一个人。
实例目的:通过一个人员的id,查询出此人的人员信息及所有的工作经历信息。
1.1.SQL语句
drop table person;
create table `person`(
`person_id` int(5) unsigned not null comment '人员id',
`name` varchar(10) not null comment '姓名',
`age` int(3) not null comment '年龄',
primary key(person_id)
)engine=InnoDB comment='人员' auto_increment=1 default charset=utf8;
insert into person values(1,'张三',25);
insert into person values(2,'李四',16);
select * from person;
drop table experience;
create table `experience`(
`experience_id` int(5) unsigned not null comment '工作经验id',
`person_id` int(5) unsigned not null comment '人员id',
`company` varchar(32) not null comment '公司',
`position` varchar(16) not null comment '职位',
primary key(experience_id)
)engine=InnoDB comment='工作经验' auto_increment=1 default charset=utf8;
insert into experience values(1,1,'北京天大地大科技有限公司','开发工程师');
insert into experience values(2,1,'南通考古研究院','考古专家');
insert into experience values(3,1,'海南旅游集团','CEO');
select * from experience;
1.2.目录结构
src
\---main
\---java
| \---pers
| \---hanchao
| \---himybatis
| \---one2many
| \---Person.java
| \---Experience.java
| \---IPersonDAO.java
| \---PersonApp.java
\---webapp
\---mybatis-mappers
\---Person.xml
\---log4j.properties
\---mybatis-config.xml
1.3.MyBatis配置mybatis-config.xml
关于mybatis-config.xml
的完整配置,可以参考:
MyBatis代码实例系列-04:MyBatis单张表简单实现增删改查 + log4j + 手动事务控制
<!--一对多实例,通过collection标签进行关联-->
<typeAlias type="pers.hanchao.himybatis.one2many.Person" alias="Person"/>
<typeAlias type="pers.hanchao.himybatis.one2many.Experience" alias="Experience"/>
<!--一对多实例-->
<mapper resource="mybatis-mappers/Person.xml"/>
1.4.实体类+IDAO+XML
1.4.1.实体类
Person.java
package pers.hanchao.himybatis.one2many;
import java.util.List;
/**
* <p>人员信息</p>
* @author hanchao 2018/1/27 14:55
**/
public class Person {
/** 人员id */
private Integer id;
/** 姓名 */
private String name;
/** 年龄 */
private Integer age;
/** 教育背景 */
private List<Experience> experienceList;
//constructor,toString,setter,getter
}
Experience.java
package pers.hanchao.himybatis.one2many;
/**
* <p>工作经验</p>
* @author hanchao 2018/1/27 14:58
**/
public class Experience {
/** 工作经验id */
private Integer id;
/** 公司名称 */
private String company;
/** 职位 */
private String position;
//constructor,toString,setter,getter
}
说明:
- 一对多实现的关键之一:通过将对象列表作为属性,如private List experienceList;
1.4.2.IDAO
在当前场景中,人员是业务处理的对象,所以Person
为操作对象,建立IDAO
,即IPersonDAO
。
package pers.hanchao.himybatis.one2many;
/**
* <p>人员-工作经历操作DAO层接口</p>
* @author hanchao 2018/1/27 15:50
**/
public interface IPersonDAO {
/** 根据id查询人员信息及工作经验信息 */
Person queryPersonById(Integer id);
}
1.4.3.XML:Problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--MyBatis的分配置文件,分别对应每个实体,用来配置SQL操作及SQL语句-->
<!--namespace,定义这个映射的命名域,这里指向Dao层接口-->
<mapper namespace="pers.hanchao.himybatis.one2many.IPersonDAO">
<!--注意这里是resultMap-->
<select id="queryPersonById" parameterType="Integer" resultMap="personExperienceMap">
SELECT psn.*,exp.* FROM `person` psn LEFT JOIN `experience` exp ON psn.person_id = exp.person_id WHERE psn.person_id = #{id}
</select>
<!--注意上面的返回对象resultMap指向这个id;Person是数据模型,用全路径名是为了防止冲突 -->
<resultMap id="personExperienceMap" type="pers.hanchao.himybatis.one2many.Person">
<!--property是对象里的字段名,column是数据库中的字段名-->
<result property="id" column="person_id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<!--ofType是集合元素的类型,column是指外键字段-->
<collection property="experienceList" ofType="pers.hanchao.himybatis.one2many.Experience" column="person_id">
<!--id表示主键-->
<result property="id" column="experience_id"/>
<result property="company" column="company"/>
<result property="position" column="position"/>
</collection>
</resultMap>
</mapper>
注意:
queryPersonById
查询的返回值不是简单的Person
类型,而是组合类型personExperienceMap
。- personExperienceMap类型中由collection属性配置集合类型的元素,以实现1对多的目的。
result
标签的property
表示的是Java实体中的字段,column
表示的是数据库表中的字段。collection
标签的column
字段表示外键字段。
1.5.result
2018-01-28 15:52:48 INFO PersonApp:48 - Person{id=1, name='张三', age=25}
2018-01-28 15:52:48 INFO PersonApp:76 - Experience{id=1, company='北京天大地大科技有限公司', position='开发工程师'}
2018-01-28 15:52:48 INFO PersonApp:76 - Experience{id=2, company='南通考古研究院', position='考古专家'}
2018-01-28 15:52:48 INFO PersonApp:76 - Experience{id=3, company='海南旅游集团', position='CEO'}
2018-01-28 15:52:48 INFO PersonApp:54 - Person{id=2, name='李四', age=16}
2018-01-28 15:52:48 INFO PersonApp:73 - 此人无工作经验
2.表间多对一关联映射
实例场景:experience(工作经历表)
与person(人员表)
与是多对一关系,即:一条工作经验只能属于一个人,一个人有多条工作经验。
实例目的:通过一个工作经验的id,查询出此工作经历信息及所属的人员信息。
2.1.SQL语句
drop table person;
create table `person`(
`person_id` int(5) unsigned not null comment '人员id',
`name` varchar(10) not null comment '姓名',
`age` int(3) not null comment '年龄',
primary key(person_id)
)engine=InnoDB comment='人员' auto_increment=1 default charset=utf8;
insert into person values(1,'张三',25);
insert into person values(2,'李四',16);
select * from person;
drop table experience;
create table `experience`(
`experience_id` int(5) unsigned not null comment '工作经验id',
`person_id` int(5) unsigned not null comment '人员id',
`company` varchar(32) not null comment '公司',
`position` varchar(16) not null comment '职位',
primary key(experience_id)
)engine=InnoDB comment='工作经验' auto_increment=1 default charset=utf8;
insert into experience values(1,1,'北京天大地大科技有限公司','开发工程师');
insert into experience values(2,1,'南通考古研究院','考古专家');
insert into experience values(3,1,'海南旅游集团','CEO');
select * from experience;
2.2.目录结构
src
\---main
\---java
| \---pers
| \---hanchao
| \---himybatis
| \---many2one
| \---Experience.java
| \---Person.java
| \---IExperienceDAO.java
| \---ExperienceApp.java
\---webapp
\---mybatis-mappers
\---Experience.xml
\---log4j.properties
\---mybatis-config.xml
2.3.MyBatis配置mybatis-config.xml
关于mybatis-config.xml
的完整配置,可以参考:
MyBatis代码实例系列-04:MyBatis单张表简单实现增删改查 + log4j + 手动事务控制
<!--多对一实例-->
<!--many2one包里的Person和Experience没有在这里配置,应为在Experience.xml里直接通过全类名进行的调用,而非别名-->
<!--多对一实例-->
<mapper resource="mybatis-mappers/Experience.xml"/>
2.4.实体类+IDAO+XML
2.4.1.实体类
Experience.java
package pers.hanchao.himybatis.many2one;
/**
* <p>工作经验</p>
* @author hanchao 2018/1/27 16:07
**/
public class Experience {
/** 工作经验id */
private Integer id;
/** 公司名称 */
private String company;
/** 职位 */
private String position;
/** 所属人员 */
private Person person;
//constructor,toString,setter,getter
}
Person.java
package pers.hanchao.himybatis.many2one;
/**
* <p>人员信息</p>
* @author hanchao 2018/1/27 16:06
**/
public class Person {
/** 人员id */
private Integer id;
/** 姓名 */
private String name;
/** 年龄 */
private Integer age;
//constructor,toString,setter,getter
}
说明:
1. 多对一实现的关键之一:通过将对象作为属性,如private Person person;
2.4.2.IDAO
在当前场景中,工作经历是业务处理的对象,所以Experience
为操作对象,建立IDAO
,即IExperienceDAO
。
package pers.hanchao.himybatis.many2one;
/**
* <p>工作经历-人员DAO层接口</p>
* @author hanchao 2018/1/27 16:02
**/
public interface IExperienceDAO {
/** 根据id查询工作经验及所属人员信息 */
Experience queryExperienceById(Integer id);
}
2.4.3.XML:Experience.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--MyBatis的分配置文件,分别对应每个实体,用来配置SQL操作及SQL语句-->
<!--namespace,定义这个映射的命名域,这里指向Dao层接口-->
<mapper namespace="pers.hanchao.himybatis.many2one.IExperienceDAO">
<select id="queryExperienceById" parameterType="Integer" resultMap="experiencePersonMap">
SELECT exp.*,psn.* FROM experience exp LEFT JOIN person psn ON exp.person_id = psn.person_id WHERE exp.experience_id = #{id}
</select>
<resultMap id="experiencePersonMap" type="pers.hanchao.himybatis.many2one.Experience">
<result property="id" column="experience_id"/>
<result property="company" column="company"/>
<result property="position" column="position"/>
<association property="person" column="user_id" javaType="pers.hanchao.himybatis.many2one.Person">
<result property="id" column="person_id"/>
<!--字段的property和column一致,可以不配置-->
<!--<result property="name" column="name"/>-->
<!--<result property="age" column="age"/>-->
</association>
</resultMap>
</mapper>
注意:
queryExperienceById
查询的返回值不是简单的Experience
类型,而是组合类型experiencePersonMap
。- experiencePersonMap类型中由association属性配置关联关系的实体,以实现多对一的目的。
result
标签的property
表示的是Java实体中的字段,column
表示的是数据库表中的字段。- 字段的property和column一致,可以不配置。
association
标签的column
字段表示外键字段。
2.5.result
2018-01-28 15:53:47 INFO ExperienceApp:38 - Experience{id=2, company='南通考古研究院', position='考古专家'}
2018-01-28 15:53:47 INFO ExperienceApp:39 - Person{id=1, name='张三', age=25}
3.表间多对多关联映射
实例场景:student(学生表)
与course(选修)
与是多对多关系他们通过中间表student_course
相关联,即:一个学生可以选修多门选修课,每门选修课可以被多个学生选修。
实例目的:
- 通过一个学生的id,查询出此学生的学生信息及所选选修课信息。
- 通过一个选修课的id,查询出此选修课的信息及所有选修本课的学生信息。
- 添加一个新的学生信息。
- 添加一门新的选修课。
- 新来的学生选修了新开设的选修课。
3.1.SQL语句
drop table student;
create table `student`(
`student_id` int(4) unsigned not null comment '学生id',
`name` varchar(10) not null comment '姓名',
`number` varchar(3) not null comment '学号',
primary key(student_id)
)engine=InnoDB comment='学生' auto_increment=1 default charset=utf8;
insert into student values(1,'张三','003');
insert into student values(2,'李四','004');
insert into student values(3,'王五','005');
select * from student;
drop table course;
create table `course`(
`course_id` int(5) unsigned not null comment '选修课id',
`name` varchar(10) not null comment '课程名',
`score` int(1) unsigned not null comment '学分',
primary key(course_id)
)engine=InnoDB comment='选修课' default charset=utf8;
insert into course values(1,'Java开发',2);
insert into course values(2,'国家地理',1);
insert into course values(3,'十级英语',2);
insert into course values(4,'高等数学',3);
insert into course values(5,'国语',1);
select * from course;
drop table student_course;
create table `student_course`(
`student_id` int(4) unsigned not null comment '学生id',
`course_id` int(4) unsigned not null comment '选修课id',
primary key(student_id,course_id)
)engine = InnoDB comment='学生选修课中间表' charset=utf8;
insert into student_course values(1,1);
insert into student_course values(1,3);
insert into student_course values(1,5);
insert into student_course values(2,2);
insert into student_course values(2,4);
insert into student_course values(3,1);
insert into student_course values(3,2);
insert into student_course values(3,3);
insert into student_course values(3,4);
insert into student_course values(3,5);
select * from student_course;
注意:多对多最好是使用这种单表+中间表+单表的数据结构
3.2.目录结构
src
\---main
\---java
| \---pers
| \---hanchao
| \---himybatis
| \---many2many
| \---Student.java
| \---Course.java
| \---StudentCourse.java
| \---IStudentDAO.java
| \---ICourseDAO.java
| \---IStudentCourseDAO.java
| \---Many2manyApp.java
\---webapp
\---mybatis-mappers
\---Student.xml
\---Course.xml
\---StudentCourse.xml
\---log4j.properties
\---mybatis-config.xml
3.3.MyBatis配置mybatis-config.xml
关于mybatis-config.xml
的完整配置,可以参考:
MyBatis代码实例系列-04:MyBatis单张表简单实现增删改查 + log4j + 手动事务控制
<!--多对多实例-->
<typeAlias type="pers.hanchao.himybatis.many2many.Student" alias="Student"/>
<typeAlias type="pers.hanchao.himybatis.many2many.Course" alias="Course"/>
<typeAlias type="pers.hanchao.himybatis.many2many.StudentCourse" alias="StudentCourse"/>
<!--多对多实例-->
<mapper resource="mybatis-mappers/Student.xml"/>
<mapper resource="mybatis-mappers/Course.xml"/>
<mapper resource="mybatis-mappers/StudentCourse.xml"/>
3.4.实体类+IDAO+XML
3.4.1.实体类
单表Student.java
package pers.hanchao.himybatis.many2many;
import java.util.List;
/**
* <p>学生信息表</p>
* @author hanchao 2018/1/27 16:54
**/
public class Student {
/** 学生id */
private Integer id;
/** 姓名 */
private String name;
/** 学号 */
private String number;
/** 所有选修课 */
private List<Course> courseList;
//constructor,toString,setter,getter
}
单表Course.java
package pers.hanchao.himybatis.many2many;
import java.util.List;
/**
* <p>选课表</p>
* @author hanchao 2018/1/27 16:55
**/
public class Course {
/** 选课id */
private Integer id;
/** 课程名称 */
private String name;
/** 学分 */
private Integer score;
/** 选择本课程的学生 */
private List<Student> studentList;
//constructor,toString,setter,getter
}
中间表StudentCourse.java
package pers.hanchao.himybatis.many2many;
/**
* <p>学生选修课中间表</p>
* @author hanchao 2018/1/27 16:59
**/
public class StudentCourse {
private Integer studentId;
private Integer courseId;
//constructor,toString,setter,getter
}
注意:
- 对单表Student和Course,都需要以对方的对象列表作为属性如private List courseList;和private List studentList;
3.4.2.IDAO
在当前场景中,学生和课程可以作为业务对象。所以分别以Student
和Course
为操作对象,建立IDAO
,即IStudentDAO
和ICourseDAO
。
IStudentDAO.java
package pers.hanchao.himybatis.many2many;
/**
* <p>学生DAO</p>
* @author hanchao 2018/1/27 17:06
**/
public interface IStudentDAO {
/** 新增一个学生 */
void insertStudent(Student student);
/** 根据id查询一个学生的信息 */
Student queryStudentById(Integer id);
}
ICourseDAO.java
package pers.hanchao.himybatis.many2many;
/**
* <p>选修课DAO</p>
* @author hanchao 2018/1/27 17:03
**/
public interface ICourseDAO {
/** 新增一门课程 */
void insertCourse(Course course);
/** 根据id查询一门课程 */
Course queryCourseById(Integer id);
}
IStudentCourseDAO.java
package pers.hanchao.himybatis.many2many;
/**
* <p>学生选课DAO</p>
* @author hanchao 2018/1/27 17:08
**/
public interface IStudentCourseDAO {
/** 插入一条学生选课信息 */
void insertStudentCourse(StudentCourse studentCourse);
/** */
Student queryStudentByCourseId(Integer id);
/** */
Course queryCourseByStudentId(Integer id);
}
3.4.3.XML
Student.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--MyBatis的分配置文件,分别对应每个实体,用来配置SQL操作及SQL语句-->
<!--namespace,定义这个映射的命名域,这里指向Dao层接口-->
<mapper namespace="pers.hanchao.himybatis.many2many.IStudentDAO">
<!--新增学生信息-->
<insert id="insertStudent" parameterType="Student">
INSERT INTO `student`(student_id,name,number) VALUES (#{id},#{name},#{number})
</insert>
<!--查询学生信息-->
<select id="queryStudentById" parameterType="Integer" resultMap="studentCourseMap">
SELECT * FROM `student` WHERE student_id = #{id}
</select>
<resultMap id="studentCourseMap" type="Student">
<result property="id" column="student_id"/>
<!--property与column相同的没必要配置-->
<!--<result property="name" column="name"/>-->
<!--<result property="number" column="number"/>-->
<!--这里是1对多关系,这里的Column是在queryCourseByStudentId里的查询列-->
<collection property="courseList" column="student_id"
select="pers.hanchao.himybatis.many2many.IStudentCourseDAO.queryCourseByStudentId"/>
</resultMap>
</mapper>
Course.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--MyBatis的分配置文件,分别对应每个实体,用来配置SQL操作及SQL语句-->
<!--namespace,定义这个映射的命名域,这里指向Dao层接口-->
<mapper namespace="pers.hanchao.himybatis.many2many.ICourseDAO">
<!--插入新的选修课-->
<insert id="insertCourse" parameterType="Course">
INSERT INTO `course`(course_id,name,score) VALUES (#{id},#{name},#{score})
</insert>
<!--根据id查询选修课新-->
<select id="queryCourseById" parameterType="Integer" resultMap="courseStudentMap">
SELECT * FROM `course` WHERE course_id = #{id}
</select>
<resultMap id="courseStudentMap" type="Course">
<!--property:Java中的字段,column:表中的字段-->
<result property="id" column="course_id"/>
<!--property与column相同的没必要配置-->
<!--<result property="name" column="name"/>-->
<!--<result property="score" column="score"/>-->
<!--这里是1对多关系,这里的Column是在queryStudentByCourseId里的查询列-->
<collection property="studentList" column="course_id"
select="pers.hanchao.himybatis.many2many.IStudentCourseDAO.queryStudentByCourseId"/>
</resultMap>
</mapper>
StudentCourse.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--MyBatis的分配置文件,分别对应每个实体,用来配置SQL操作及SQL语句-->
<!--namespace,定义这个映射的命名域,这里指向Dao层接口-->
<mapper namespace="pers.hanchao.himybatis.many2many.IStudentCourseDAO">
<!--新增一条选修记录-->
<insert id="insertStudentCourse" parameterType="StudentCourse">
INSERT INTO `student_course`(student_id,course_id) VALUES (#{studentId},#{courseId})
</insert>
<!--根据学生id,查询这个学生所有的选修课信息-->
<select id="queryCourseByStudentId" resultMap="courseByStudentIdMap" parameterType="Integer">
SELECT cos.*,sc.course_id
FROM `course` cos,`student_course` sc
WHERE cos.course_id = sc.course_id
AND sc.student_id = #{student_id}
</select>
<resultMap id="courseByStudentIdMap" type="Course">
<result property="id" column="course_id"/>
<!--property与column相同的没必要配置-->
<!--<result property="name" column="name"/>-->
<!--<result property="score" column="score"/>-->
</resultMap>
<!--根据选修课id,查询选修此选修课的所有学生信息-->
<select id="queryStudentByCourseId" resultMap="studentByCourseMap" parameterType="Integer">
SELECT std.*,sc.student_id
FROM `student` std,`student_course` sc
WHERE std.student_id = sc.student_id
AND sc.course_id = #{course_id}
</select>
<resultMap id="studentByCourseMap" type="Student">
<result property="id" column="student_id"/>
<!--property与column相同的没必要配置-->
<!--<result property="name" column="name"/>-->
<!--<result property="number" column="number"/>-->
</resultMap>
</mapper>
说明:
Student
和Course
的一对多关系,实际上是通过中间表student_course
转化成了两个1对多关系,即一个student
对应多个student_course
以及一个Course
对应多个student_course
。所以,多对多映射实际上是两个一对多映射的组合。- 一对多查询的返回值不是简单的实体类型,而是组合类型,如
studentCourseMap
、courseStudentMap
等。 - resultMap类型中由collection属性配置集合类型的元素,以实现1对多的目的。
result
标签的property
表示的是Java实体中的字段,column
表示的是数据库表中的字段。- 字段的
property
和column
一致,可以不配置。 collection
标签的column
字段表示外键字段。collection
标签的select
字段表示的是映射到中间表中的方法。
3.5.result
2018-01-28 15:54:11 INFO Many2manyApp:40 - =========查询学生id=1的所有选修课
2018-01-28 15:54:11 INFO Many2manyApp:43 - Student{id=1, name='张三', number='003'}
2018-01-28 15:54:11 INFO Many2manyApp:109 - Course{id=1, name='Java开发', score=2}
2018-01-28 15:54:11 INFO Many2manyApp:109 - Course{id=3, name='十级英语', score=2}
2018-01-28 15:54:11 INFO Many2manyApp:109 - Course{id=5, name='国语', score=1}
2018-01-28 15:54:11 INFO Many2manyApp:48 - =========查询选修课id=1的所有学生
2018-01-28 15:54:11 INFO Many2manyApp:51 - Course{id=1, name='Java开发', score=2}
2018-01-28 15:54:11 INFO Many2manyApp:109 - Student{id=1, name='张三', number='003'}
2018-01-28 15:54:11 INFO Many2manyApp:109 - Student{id=3, name='王五', number='005'}
2018-01-28 15:54:11 INFO Many2manyApp:56 - =========新增一个学生:陈六
2018-01-28 15:54:11 INFO Many2manyApp:59 - Student{id=4, name='陈六', number='006'}
2018-01-28 15:54:11 INFO Many2manyApp:63 - =========新增一门课程:玄学
2018-01-28 15:54:11 INFO Many2manyApp:66 - Course{id=6, name='玄学', score=3}
2018-01-28 15:54:11 INFO Many2manyApp:70 - =========新学生 陈六 选修了新选修课 玄学
2018-01-28 15:54:11 INFO Many2manyApp:74 - StudentCourse{studentId=4, courseId=6}
2018-01-28 15:54:11 INFO Many2manyApp:78 - =========查询陈六选修的课程
2018-01-28 15:54:11 INFO Many2manyApp:80 - Student{id=4, name='陈六', number='006'}
2018-01-28 15:54:11 INFO Many2manyApp:109 - Course{id=6, name='玄学', score=3}
2018-01-28 15:54:11 INFO Many2manyApp:85 - =========查询选修玄学的所有学生
2018-01-28 15:54:11 INFO Many2manyApp:87 - Course{id=6, name='玄学', score=3}
2018-01-28 15:54:11 INFO Many2manyApp:109 - Student{id=4, name='陈六', number='006'}