在前面三篇文章中,我们学习了什么是MyBatis、简单实现了一个MyBatis程序、使用MyBatis实现了CRUD操作、涉及了MyBatis的一些常用配置、ResultMap简单使用、日志功能的实现和介绍、分页实现。
以下是之前文章的地址
本篇文章我们会学习注解的使用、多对一、一对多的两种实现方式。多对一和一对多是比较难以理解的,看起来可能会比较累。
在MyBatis入门文档中有这么几段话。
我想这几段话作为注解的引入十分合适
简单使用注解
使用注解的话我们就不再需要编写mapper.xml文件了
既然没有mapper.xml映射文件,那我们可以通过接口的方式来映射
<!--使用mapper接口的全限定名 实现映射-->
<mappers>
<mapper class="com.molu.mapper.UserMapper"/>
</mappers>
解决了映射问题,我们在mapper接口中写一个方法并使用注解
public interface UserMapper {
// 使用select标签的注解形式
@Select("select * from user")
List<User> getUsers();
}
可以看到,使用注解的话能省很多事情,我们只需要将映射解决,注解中sql写对就可以进行测试了。
但到这里为止,我们的实体类属性名还是没有改回来的,仍然和数据库中的字段名不一致
而只通过注解 我们没办法配置ResultMap来将二者做一个映射,那可想而知,查询出来的结果password会为null
不要紧,后面我们会使用resultMap注解来将二者串起来
测试类
@Test
public void getUsersTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}
}
无法避免的,返回的查询结果 password全部为null。
想解决这个问题也很简单,把mapper.xml写一下就可以了,这种混用的方式 在MyBatis中十分常见。
写mapper.xml时需要注意,必须将mapper.xml和mapper接口放在一个接口中且必须同名。(因为映射绑定是通过mapper接口的全限定名)就像这样:
编写mapper.xml映射文件
在 mapper标签中只写 resultMap标签和具体配置,其他的一概不管。
<?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.molu.mapper.UserMapper">
<resultMap id="UserMap" type="user">
<result column="pwd" property="password"/>
</resultMap>
</mapper>
将 resultMap id绑定到接口中的 @ResultMap注解中。
public interface UserMapper {
// 进行resultMap id绑定
@ResultMap("UserMap")
@Select({"select * from user"})
List<User> getUsers();
}
写完这些后再运行就能够解决问题了。
还算省事,当然 你也可以通过前面我说过的 在sql语句中加个 as 取别名快速解决问题。
@Select({"select id,name,pwd as password from user"})
这也就是为什么我开头,要使用MyBatis官方文档这几段话作为引入了。
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
选择何种方式来配置映射,以及认为是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。
除了混用配置文件和注解,我们也可以使用纯注解来对其进行映射
@Results
注解对应着mapper.xml中的resultMap标签
@Result
对应着mapper.xml中的result标签
public interface UserMapper {
@Results({
@Result(property = "password", column = "pwd")
})
@Select({"select * from user"})
List<User> getUsers();
}
这样写的话也没什么问题,但如果需要映射的字段和属性太多了就有一点惨不忍睹了.......
到这里注解相关就告一段落了,也没太多好讲的。如果还想了解更多注解相关可以移其他博客,比如
多对一
搭建多对一环境
创建老师表(主表)
CREATE TABLE `teacher` (
`id` int(3) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
创建学生表(从表)
CREATE TABLE `student` (
`id` int(3) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`tid` int(3) NOT NULL,
PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8
二者使用物理外键关联,也就是说 student表中的tid 是 teacher表中的id
抛出需求
现在我们想在查询学生信息的同时,还要返回 tid所对应老师的名字
如果使用sql来做,那自然是非常的简单
那如果我们要使用MyBatis该如何操作呢?
尝试实现
编写Teacher实体类
public class Teacher {
private int id;
private String name;
public Teacher() {
}
public Teacher(int id, String name) { this.id = id; this.name = name;}
public int getId() { return id;}
public void setId(int id) { this.id = id;}
public String getName() { return name;}
public void setName(String name) { this.name = name;}
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
编写Student实体类
public class Student {
private int id;
private String name;
private int tid;
private String teacherName;
public Student(int id, String name, int tid, String teacherName) {
this.id = id;
this.name = name;
this.tid = tid;
this.teacherName = teacherName;
}
//========中间省略生成的getter setter方法========
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", tid=" + tid +
", teacherName=" + teacherName +
'}';
}
}
在mapper接口编写方法
public interface StudentMapper {
List<Student> getStudent();
}
编写mapper.xml文件
<mapper namespace="com.molu.mapper.StudentMapper">
<select id="getStudent" resultType="Student">
select s.id,s.name,s.tid,t.name as teacherName
from student s
inner join teacher t
where s.tid=t.id
</select>
</mapper>
编写测试类
public class MapperTest {
@Test
public void getStudentTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.getStudent();
for (Student student : studentList) {
System.out.println(student);
}
sqlSession.close();
}
}
测试结果:
很意外的居然可以实现....... ,本来还想通过无法实现 来扩展resultMap对复杂查询的支持。
既然都写到这里了还是扩展一下吧......
按照查询嵌套处理
除了比较直接的写一个连表查询sql
我们还可以写两个查询sql,将其中一个 sql的查询结果,作为另一条 sql的查询结果进行嵌套处理。
- 首先我们将原本的实体类进行修改
public class Student {
private int id;
private String name;
// private int tid;
// 不再需要 tid属性
private Teacher teacher;
// 将原本的 teacherName 修改为 Teacher对象
- 写两条查询sql
- 将getTeacher的查询结果 和 Student实体类中的 teacher属性进行映射
- 映射后, getStudent查询结果中就会嵌套进 getTeacher的查询结果
<mapper namespace="com.molu.mapper.StudentMapper">
<select id="getStudent" resultType="Student">
select * from student
</select>
<select id="getTeacher" resultType="Teacher">
select * from teacher
</select>
</mapper>
- 使用resultMap将二者连接
<select id="getStudent" resultMap="ST_Map">
select * from student
</select>
<resultMap id="ST_Map" type="Student">
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{id}
</select>
看到这里应该有点懵,先不要着急往下看。
- 测试类(平平无奇的测试类)
@Test
public void getStudentTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.getStudent();
for (Student student : studentList) {
System.out.println(student);
}
sqlSession.close();
}
- 测试结果:
测试结果十分的让人满意,这样一来就满足了我们前面的需求,而且没有通过连表查询的方式
association
上面一系列操作,大家应该都能看得懂。让人不解的,最多只有这个 association标签了
-
首先,我们使用
resultMap
标签使实体类中的属性能够和数据库中的字段映射起来。 -
id、name自然是不需要我们对其进行映射的
-
那唯一需要映射的就只有我们的 teacher属性了
-
但这个属性它不是一个简单的字段,它是一个对象
-
在
resultMap
中,简单的属性我们使用result
或者 id进行映射 -
复杂的属性(比如对象、集合)我们使用
association
和 collection来进行映射
association如何使用
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
- 既然要做映射,那
property
和column
自然是必不可少 - 如果只是这样简单映射,那我们也使用
result
也能做到。但 teacher属性是一个对象 association
标签中提供了javaType
,来方便我们指定 需要映射属性的对象类型association
标签还提供select
,它能够让我们指定一个select标签,也就是下面这个
<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{id}
</select>
- 到这里我们的 teacher属性,就与 tid字段完成了映射,且指定了对象类型,指定了嵌套的查询结果
- 也就是说,我们的 tid会被作为
where id = #{id}
中的条件来先被执行 - 返回的查询结果会被嵌套至 getStudent中的 teacher属性
- 当我们执行 getStudent方法进行查询时,teacher会被嵌套为getTeacher的查询结果。
这一操作和我们 sql中的子查询很像,理解起来有一点困难,动手实现一下应该能更好的进行理解。
按照结果嵌套处理
除了上面这种将两条查询sql 互相嵌套,association还能够通过结果来进行嵌套处理
首先我们将 select标签体中的 sql进行一些修改,要写成一个连表查询的sql。
<select id="getStudent" resultMap="ST_Map">
SELECT s.id sid,s.name sname,t.id teacherId,t.name tname
FROM student s,teacher t
WHERE s.tid=t.id
</select>
既然不再使用上一种方式,那相对的我们也需要对 resultMap
标签进行一些修改
<resultMap id="ST_Map" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
<result property="id" column="teacherId"/>
</association>
</resultMap>
可以看到,我们在 resultMap
标签中对所有 sql中查询字段都进行了映射
而比较复杂的 teacher属性,我们仍然通过 association
对其进行处理
但这一次我们不再为其指定一个 select
标签来进行嵌套。而是在它标签体内,对 Teacher对象的属性和 sql中的teacher表字段进行映射。
都完成映射后,执行getStudent方法。
同样的,实现了我们想要的效果。以上就是association的两种使用方式
一对多
搭建一对多环境
Student实体类
public class Student {
private int id;
private String name;
private int tid;
// 以下省略 有参无参构造和getter、setter、toString方法
Teacher实体类
public class Teacher {
private int id;
private String name;
private List<Teacher> teachers;
// 同
抛出需求
我们想在查询老师的同时,将tid对应学生的数据查询出来。
使用sql语句也能很轻松的做到
一样的,我们通过MyBatis能不能实现?
尝试实现
在mapper接口中写一个查询老师的方法
public interface TeacherMapper {
List<Teacher> getTeacher();
}
同步到mapper.xml中
<mapper namespace="com.molu.mapper.TeacherMapper">
<select id="getTeacher" resultType="Teacher">
select t.id,t.name,s.tid,s.id,s.name
from teacher t,student s
where t.id=s.tid
</select>
</mapper>
测试类
@Test
public void getTeacherTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
List<Teacher> teacherList = mapper.getTeacher();
for (Teacher teacher : teacherList) {
System.out.println(teacher);
}
sqlSession.close();
}
测试结果
很明显,不能够将学生的信息查询出来,我们也不会通过一开始哪种方式,把学生的所有字段都写到老师的实体类中。
按照结果嵌套处理
在多对一中,我们也有使用过这种方式。实际上在一对多中实现也大致相似
首先我们对mapper.xml进行修改(和上文基本一样)
<mapper namespace="com.molu.mapper.TeacherMapper">
<select id="getTeacher" resultMap="TS_Map">
select t.id tid,t.name tname,s.id sid,s.name sname,s.tid stid
from teacher t,student s
where t.id=s.tid
</select>
<resultMap id="TS_Map" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="stid"/>
</collection>
</resultMap>
</mapper>
在上文我们有说复杂的属性(比如对象、集合)我们使用 association和 collection来进行映射
现在我们 Teacher中的 students属性就是一个复杂属性,是一个集合。
对象使用 association
标签,那可想而知的集合我们会使用collection
标签
Java对象我们使用JavaType
来对其进行映射,而集合我们使用ofType
将这些进行修改后就和多对一中的几乎一样了。
测试类
@Test
public void getTeacherTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
List<Teacher> teacherList = mapper.getTeacher();
for (Teacher teacher : teacherList) {
System.out.println(teacher);
}
sqlSession.close();
}
测试结果:
有了多对一的基础,实现一对多也不会有太大难度。
接着我们看看让人比较痛苦的按照查询嵌套,也就是类似子查询的方式
按照查询嵌套处理
首先对mapper.xml文件进行修改
<select id="getTeacher" resultMap="TS_Map">
select t.id,t.name,s.id sid,s.name sname
from teacher t,student s
where t.id=s.tid
</select>
<resultMap id="TS_Map" type="Teacher">
<collection property="students" column="sid" javaType="ArrayList" ofType="Student" select="getStudent"/>
</resultMap>
<select id="getStudent" resultType="Student">
select * from student where id=#{sid}
</select>
</mapper>
由于 teacher表中没有可以与 Student对象进行映射的字段我们只能使用连表查询的sql
collection
标签中我们可以使用,JavaType
和 ofType
来指定对象类型和泛型中的类型
这个写得可能有点乱,捋捋应该还是能够看得懂
以我的水平只能勉强写出这种代码,如果可以优化还望评论区简单指点一下
测试类不变
测试结果
也实现了我们上述的需求
那到这里MyBatis的简单注解使用、多对一、一对多也就告一段落了。关于MyBatis的文章在之后应该也会陆续更新,感谢你能看到这里 如果有帮到你的话 (‾◡◝)