Talking is Cheap,Show Me the Code
引子
之前无论是使用实体类映射文件的方式,还是使用注解的方式,都只是对单个表进行的增删改查的操作。但在实际项目中,经常是关联表的查询,比如:最常见到的多对一,一对多,多对多等。这些查询是要如何进行呢,MyBatis要怎么进行支持的呢——这次我学习了多个表之间的关联查询。
过程
对于多个表之间的关联查询,学习主要分为三个部分——一对多,多对一,多对多
一对多
在现实中,实体类一对多的场景有很多,我们随便抽象一个:一个人和他发送的朋友圈时间的关系。(一个人可以发送多条朋友圈,但是每条朋友圈只对应一个发送人)
- 准备工作
首先我们需要创建两个实体类对应的表,不妨命名为person表和post表,建表的语句大致如下,并且插入几条数据
CREATE TABLE [person] (
[id] int NOT NULL PRIMARY KEY,
[name] varchar(255) NOT NULL,
[mobile] varchar(255) NOT NULL,
[create] datetime DEFAULT ('0000-00-00 00:00:00') NOT NULL
);
CREATE TABLE [post] (
[id] int NOT NULL PRIMARY KEY,
[pid] int NOT NULL,
[title] varchar(255) NOT NULL,
[time] datetime DEFAULT ('0000-00-00 00:00:00') NOT NULL
);
- 创建实体类
需要创建的实体类分别是Person和Post,属性与表属性一保持一致
//person实体类
public class Person {
private int id;
private String name;
private String mobile;
private String createTime;
private List<Post> posts;
//此处省略setter和getter方法
}
//post实体类
public class Post {
private int id;
private int pid;
private String title;
private String createTime;
private Person person;
//此处省略setter和getter方法
}
- 创建实体类配置文件
在Java实体对象对中,一对多可以根据List和Set来实现,两者在MyBatis中都是通过collection标签来配合来加以实现。
<?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">
<mapper namespace="com.bean.personMapper">
<!-- Person 级联朋友圈查询 方法配置 (一个人对多条朋友圈) -->
<resultMap type="Person" id="resultPersonMap">
<result property="id" column="id" />
<result property="name" column="name" />
<result property="mobile" column="mobile" />
<result property="createTime" column="create"/>
<collection property="posts" ofType="com.bean.Post" column="pid">
<result property="id" column="id" javaType="int" jdbcType="INTEGER"/>
<result property="title" column="title" javaType="string" jdbcType="VARCHAR"/>
<result property="createTime" column="time" javaType="string" jdbcType="VARCHAR"/>
</collection>
</resultMap>
<select id="getPerson" resultMap="resultPersonMap" parameterType="int">
SELECT per.*,p.*
FROM person per, post p
WHERE per.id=p.pid AND per.id=#{id}
</select>
</mapper>
从配置文件中,可以看到这次所作的查询其实只有一个——id为getPerson的一个查询,传入的参数是用户的id然后联合查询获取到结果——resultPersonMap,这个就是之前resultMap标签声明的结果集,结果集的属性(也就是实体类的属性)和数据表一一对应,而collection标签对应的是通过pid(用户id)查询出来的朋友圈记录。
- 创建项目配置文件
创建Configuration.xml文件,声明实体类映射文件和数据库连接的参数
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--声明实体类-->
<typeAliases>
<typeAlias alias="Person" type="com.bean.Person" />
<typeAlias alias="Post" type="com.bean.Post" />
</typeAliases>
<!--声明数据库连接参数-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
<property name="url" value="jdbc:sqlserver://localhost:1433;DatabaseName=ssm" />
<property name="username" value="sa" />
<property name="password" value="xiaogezzZ" />
</dataSource>
</environment>
</environments>
<!--实体类映射文件声明-->
<mappers>
<mapper resource="com/bean/Person.xml" />
</mappers>
</configuration>
- 测试类
public class Main {
private static SqlSessionFactory sqlSessionFactory;
private static Reader reader;
static {
try {
reader = Resources.getResourceAsReader("config/Configure.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (Exception e) {
e.printStackTrace();
}
}
public static SqlSessionFactory getSession() {
return sqlSessionFactory;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
SqlSession session = sqlSessionFactory.openSession();
try {
int id = 1;
Person person = session.selectOne("com.bean.personMapper.getPerson", id);
System.out.println("Name: " + person.getName());
System.out.println("Mobile: " + person.getMobile());
List<Post> posts = person.getPosts();
for(Post p : posts) {
System.out.println("----***----");
System.out.println("Title: " + p.getTitle());
System.out.println("PieceCreateTime: " + p.getCreateTime());
}
} finally {
session.close();
}
}
}
-
运行结果
运行结果 -
注意点
在实体类映射文件中,查询语句中id需要指定明确,不然会报下面的错误
id不明确
在创建表的时候需要注意,两个表尽量不要有相同名称的属性,不然在结果中出现的结果和实际结果不一致。比如,我们把post表的time属性改名和person表中的create属性相同,都为create,那么根据表属性,实体类映射文件中Collection对应的写法应该为
<collection property="posts" ofType="com.bean.Post" column="pid">
<result property="id" column="id" javaType="int" jdbcType="INTEGER"/>
<result property="title" column="title" javaType="string" jdbcType="VARCHAR"/>
<result property="createTime" column="create" javaType="string" jdbcType="VARCHAR"/>
</collection>
这样的写法按照表结构和实体类来说都是没问题的,但是一运行,结果会是这样我们发现每条朋友圈的创建时间都是一样的,而且与实际创建时间不符合——原因是,我们尽管按照表结构和实体类属性来对column赋值,但这样会让程序运行过程中与person表中的create属性相混淆,导致得到的这个值是查询的person的创建时间。
多对一
在一对多的情形中,一个人对应多条朋友圈——反过来即是多对一的情形。根据朋友圈的发送人的id,查找到对应的这个人员的信息。
- 实体类映射文件
因为是多对一的情形,一条朋友圈与人员之间的关系是关联关系,因此要修改为association标签
<?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">
<mapper namespace="com.bean.personMapper">
<!-- Post 级联朋友圈查询 方法配置 (一条(或多条)朋友圈对应一个人员) -->
<resultMap type="Post" id="resultPostsMap">
<result property="id" column="id" />
<result property="title" column="title" />
<result property="createTime" column="create"/>
<association property="person" javaType="Person">
<id property="id" column="pid"/>
<result property="name" column="name"/>
<result property="mobile" column="mobile"/>
</association>
</resultMap>
<select id="getPosts" resultMap="resultPostsMap" parameterType="int">
SELECT per.*,p.*
FROM person per, post p
WHERE per.id=p.pid AND p.id=#{id}
</select>
</mapper>
- 测试类修改
public static void main(String[] args) {
// TODO Auto-generated method stub
SqlSession session = sqlSessionFactory.openSession();
try {
int id = 1;
Post post = session.selectOne("com.bean.personMapper.getPosts", id);
System.out.println("title: "+post.getTitle());
System.out.println("PersonName: "+post.getPerson().getName());
System.out.println("PersonMobile: "+post.getPerson().getMobile());
} finally {
session.close();
}
}
多对多
MyBatis3.0 添加了association和collection标签专门用于对多个相关实体类数据进行级联查询,之前学习的一对多,多对一情形便是其中之一。但仍不支持多个相关实体类数据的级联保存和级联删除操作。因此在进行实体类多对多映射表设计时,需要专门建立一个关联对象类对相关实体类的关联关系进行描述。
考虑这样的一个情形:大学里一个学生选修多门课程,而一门课程被多个学生选修。这样学生与课程之间就构成了多对多的关系。
- 创建student表和course表,插入几条数据
create table student(
sid int not null PRIMARY key,
sname varchar(255) not null,
mobile VARCHAR(255) not null DEFAULT '');
CREATE TABLE course(
cid int not NULL PRIMARY key,
cname VARCHAR(255) not NULL);
- 创建学生课程映射表
create table student_course(
sid int not null,
cid int not null)
insert into student_course values(1,1)
insert into student_course values(1,2)
insert into student_course values(2,1)
insert into student_course values(2,3)
insert into student_course values(3,2)
insert into student_course values(3,3)
根据随意插入的数据可知,Tom选了ComputerScience和Art两门课,Jack选了ComputerScience和Java两门课,Sarah选了Art和Java两门课;反过来说,选了CompterScience的有Tom与Jack,选了Art的有Tom与Sarah,选了Java的有Jack与Sarah
- 创建实体类
Student实体类
public class Student {
private int sid;
private String sname;
private String mobile;
private List<Course> courses;
//此处省略getter和setter方法
}
Course实体类
public class Course {
private int cid;
private String cname;
private List<Student> students;
//此处省略getter和setter方法
}
StudentCourse实体类
public class StudentCourse {
private int sid;
private int cid;
//此处省略getter和setter方法
}
- 创建接口映射类
因为我们可能会不只是查询,可能还会有删除、插入等相关操作,若通过命名空间来调用相关方法不免太过繁琐,使用接口映射类,将要使用的方法声明,在使用的时候可以直接调用。
StudentMapper映射类
public interface StudentMapper {
//@Insert("insert into student(sid,name,mobile) values(#{student.getSid()}, #{student.getSName()},#{student.getMobile()})")
public void insertStudent(Student student);
//@Select("select * from student where sid = #{sid}")
public Student getStudent(int sid);
//@Select("select * from student")
public List<Student> getAllStudents();
}
CourseMapper映射类
public interface CourseMapper {
//@Insert("insert into course(cid,cname) values(#{course.getCid()},#{course.getCname()})")
public void insertCourse(Course course);
//@Select("select * from course where cid=#{cid}")
public Course getCourse(int cid);
//@Select("select * from course")
public List<Course> getAllCourses();
}
StudentCourseMapper映射类
public interface StudentCourseMapper {
//@Insert("insert into student_course(sid,cid) values(#{studentCourse.getSid()},#{studentCourse.getCid()})")
public void insertStudentCourse(StudentCourse studentCourse);
//@Select("select * from student where sid in (select sid from student_course)")
public List<Student> getStudents();
}
这里因为使用的是映射文件,所以注解都被注释了。另外,如果想要添加其他的方法,比如说是删除、更新等,都可以在对应的映射类中进行声明。
- 映射文件
Student映射
<?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">
<mapper namespace="com.maper.StudentMapper">
<parameterMap type="Student" id="parameterStudentMap">
<parameter property="sid"/>
<parameter property="sname"/>
<parameter property="mobile"/>
</parameterMap>
<insert id="insertStudent" parameterMap="parameterStudentMap">
insert into student(sid,sname,mobile)
values(#{sid},#{sname},#{mobile});
</insert>
<resultMap type="Student" id="resultStudent">
<result property="sid" column="sid"/>
<result property="sname" column="sname"/>
<collection property="courses" column="sid" select="com.maper.StudentCourseMapper.getCoursesByStudentId"/>
</resultMap>
<select id="getStudent" resultMap="resultStudent" parameterType="int">
SELECT *
FROM student
WHERE sid=#{sid}
</select>
<select id="getAllStudents" resultMap="resultStudent">
select *
from student;
</select>
</mapper>
Course映射
<?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">
<mapper namespace="com.maper.CourseMapper">
<parameterMap type="Course" id="parameterCourseMap">
<parameter property="cid"/>
<parameter property="cname"/>
</parameterMap>
<insert id="insertCourse" parameterMap="parameterCourseMap">
insert into course (cid,cname)
VALUES(#{cid},#{cname});
</insert>
<resultMap type="Course" id="resultCourseMap">
<result property="cid" column="cid" />
<result property="cname" column="cname" />
<collection property="students" column="cid" select="com.maper.StudentCourseMapper.getStudentsByCourseId" />
</resultMap>
<select id="getCourse" resultMap="resultCourseMap" parameterType="int">
SELECT *
FROM course
WHERE cid=#{cid}
</select>
<select id="getAllCourses" resultMap="resultCourseMap">
select * from course
</select>
</mapper>
StudentCourse映射
<?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">
<mapper namespace="com.maper.StudentCourseMapper">
<parameterMap type="StudentCourse" id="parameterStudentCourseMap">
<parameter property="sid"/>
<parameter property="cid"/>
</parameterMap>
<insert id="insertStudentCourse" parameterMap="parameterStudentCourseMap">
INSERT INTO student_course(sid, cid)
VALUES(#{sid},#{cid})
</insert>
<resultMap type="Student" id="resultStudentMap_2">
<result property="sid" column="sid"/>
<result property="sname" column="sname"/>
<result property="mobile" column="mobile"/>
</resultMap>
<select id="getStudentsByCourseId" resultMap="resultStudentMap_2" parameterType="int">
SELECT s.*, sc.cid
FROM student s, student_course sc
WHERE s.sid=sc.sid AND sc.cid=#{cid}
</select>
<resultMap type="Course" id="resultCourseMap_2">
<result property="cid" column="cid"/>
<result property="cname" column="cname"/>
</resultMap>
<select id="getCoursesByStudentId" resultMap="resultCourseMap_2" parameterType="int">
SELECT c.*, sc.sid
FROM course c, student_course sc
WHERE c.cid=sc.cid AND sc.sid=#{sid}
</select>
</mapper>
- 测试类
import com.bean.Course;
import com.bean.Student;
import com.bean.StudentCourse;
import com.maper.CourseMapper;
import com.maper.StudentCourseMapper;
import com.maper.StudentMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.Reader;
import java.util.List;
public class Main {
private static SqlSessionFactory sqlSessionFactory;
private static Reader reader;
static {
try {
reader = Resources.getResourceAsReader("config/Configure.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (Exception e) {
e.printStackTrace();
}
}
public static SqlSessionFactory getSession() {
return sqlSessionFactory;
}
/**
* @param args
*/
public static void main(String[] args) {
AddCourse();
AddStudent();
AddStudentCourse();
GetCourseAndStudents();
GetStudentAndCourses();
getAllCoursesAndStudents();
getAllStudentsAndCourses();
}
/*
* 获取一个学生对应的所有选修课程
*/
public static void GetStudentAndCourses() {
SqlSession session = sqlSessionFactory.openSession();
try {
int sid = 2;
StudentMapper studentMapper = session.getMapper(StudentMapper.class);
Student student = studentMapper.getStudent(sid);
System.out.println(student);
System.out.println(student.getSid());
System.out.println("Student => " + student.getSname() + "," + student.getMobile());
List<Course> courses = student.getCourses();
for (Course course : courses) {
System.out.println("\t:" + course.getCid() + "\t" + course.getCname());
}
} finally {
session.close();
}
}
/*
* 获取一门课程所有选修的学生
*/
public static void GetCourseAndStudents() {
SqlSession session = sqlSessionFactory.openSession();
try {
CourseMapper courseMaper = session.getMapper(CourseMapper.class);
Course course = courseMaper.getCourse(1);
System.out.println(course);
System.out.println("Course => " + course.getCname());
System.out.println("CourseId: " + course.getCid());
List<Student> students = course.getStudents();
for (Student student : students) {
System.out.println("\t:" + student.getSid() + "\t"
+ student.getSname());
}
} finally {
session.close();
}
}
/*
* 获取所有课程并且通过课程获取对应的选修了的学生
*/
public static void getAllCoursesAndStudents() {
SqlSession session = sqlSessionFactory.openSession();
try {
CourseMapper courseMaper = session.getMapper(CourseMapper.class);
List<Course> courses = courseMaper.getAllCourses();
for (Course course : courses) {
System.out.println("Course Id: " + course.getCid() + " Course Name: " + course.getCname());
List<Student> students = course.getStudents();
for (Student student : students) {
System.out.println("\t:" + student.getSid() + "\t"
+ student.getSname());
}
}
} finally {
session.close();
}
}
/*
* 获取所有学生并且根据学生获取对应的选修课程
*/
public static void getAllStudentsAndCourses() {
SqlSession session = sqlSessionFactory.openSession();
try {
StudentMapper studentMapper = session.getMapper(StudentMapper.class);
List<Student> students = studentMapper.getAllStudents();
for (Student student : students) {
System.out.println("Student Id: " + student.getSid() + " Student Name: " + student.getSname());
List<Course> courses = student.getCourses();
for (Course course : courses) {
System.out.println("\t:" + course.getCid() + "\t"
+ course.getCname());
}
}
} finally {
session.close();
}
}
/*
* 向选修关系表中添加数据
*/
public static void AddStudentCourse() {
StudentCourse studentCourse = new StudentCourse();
studentCourse.setCid(1);
studentCourse.setSid(2);
SqlSession session = sqlSessionFactory.openSession();
try {
StudentCourseMapper studentCourseMaper = session
.getMapper(StudentCourseMapper.class);
studentCourseMaper.insertStudentCourse(studentCourse);
session.commit();
} finally {
session.close();
}
}
/*
* 添加学生
*/
public static void AddStudent() {
SqlSession session = sqlSessionFactory.openSession();
try {
Student student = new Student();
student.setSname("Student-name-1");
student.setMobile("13888888888");
StudentMapper studentMaper = session.getMapper(StudentMapper.class);
studentMaper.insertStudent(student);
session.commit();
} finally {
session.close();
}
}
/*
* 添加课程
*/
public static void AddCourse() {
SqlSession session = sqlSessionFactory.openSession();
try {
Course course = new Course();
course.setCname("课程-1");
CourseMapper courseMapper = session.getMapper(CourseMapper.class);
courseMapper.insertCourse(course);
session.commit();
} finally {
session.close();
}
}
}
-
运行结果
可以看到结果是相对应的。
getAllStudentsAndCourses()方法运行结果
getAllCoursesAndStudents()方法运行结果