ORM(Object Relational Mapping) ORM思想:为了解决面向对象与关系数据库存在的互不匹配的现象的技术。ORM是通过使用描述对象和数据库之间映射的元数据,将java程序中的对象自动持久化到关系数据库中。将关系数据库中表中的记录映射成对象,以对象的形式展现。把对数据库的操作转化为对对象的操作。因此 ORM 的目的是为了让开发人员以面向对象的思想来实现对数据库的操作。
目前流行的 ORM 框架:
JPA : 一种 ORM 规范,需要各大 ORM 框架实现,JavaEE 的规范,Java persistence api : Java 持久化 API 
Hibernate : 最流行的 ORM 框架,设计灵巧,性能优秀,文档丰富 
MyBatis : 本是 apache 的一个开源项目 iBatis,提供的持久层框架包括SQL、Maps和DAO,允许开发人员直接编写SQL 
 
mybatis 概述 MyBatis 是一个 SQL 的映射框架,其前身是 iBatis,也就是淘宝试用的持久层框架,org.apache.ibatis
mybatis 主要功能
 
几乎消除了所有 JDBC 代码和参数的手工设置以及结果集都可以交给 MyBatis完成 
这些只需要简单的使用 XML 或者 注释配置即可
使用步骤 
使用配置文件(主配置文件/总配置文件/全局配置文件),告知框架连接数据库四要素 
使用配置文件(SQL映射文件),告知框架执行哪些SQL 
启动框架,让框架执行具体SQL任务 
 
框架从对应的配置文件中获取数据(DOM 解析),其他的事情都已经做好了。
准备工作 导入 mybatis 导入 mybatis 核心包和 mysql 驱动包
创建数据库表 
创建domain类 
MyBatis-config.XML 又叫 主配置文件/总配置文件/全局配置文件 
主要是告知框架 连接数据库四要素,事务处理,关联mapper文件,启动框架时,只会加载主配置文件 
规范
 
起名:mybatis-config.xml 
路径,放在classpath 下面,创建一个 source folder:resources 
参考文档:mybatis 中文文档/XML 映射文件 
 
约束头 1 2 3 <!DOCTYPE configuration   PUBLIC "-//mybatis.org//DTD Config 3.0//EN"    "http://mybatis.org/dtd/mybatis-3-config.dtd" > 
约束头的作用:有网络会用网络上的 dtd约束, 如果没有网络,需要配置离线的约束,离线约束加载如下,只需将 dtd 放在 resources 即可
MyBatis-config.XML 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <?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 > 	 	<properties  resource ="db.properties" > </properties >  	 	 	<typeAliases >  		 		 		<package  name ="cn.lizhaoloveit.mybatis" />  	</typeAliases >  	 	<environments  default ="dev" >  		 		<environment  id ="dev" >  			 			<transactionManager  type ="JDBC" > </transactionManager >  			 			<dataSource  type ="POOLED" >  				<property  name ="driver"  value ="${driverClassName}" />  				<property  name ="url"  value ="${url}" />  				<property  name ="username"  value ="${username}" />  				<property  name ="password"  value ="${password}" />  			</dataSource >  		</environment >  	</environments >  	 	<mappers >  		 		<mapper  resource ="cn/lizhaoloveit/mybatis/UserMapper.xml" />  	</mappers >  </configuration > 
MyBatis 的 XML 配置文件的高级结构如下:
configuration 配置
properties 属性 
setting 设置 
typeAliases 类型命名 
typeHandlers 类型处理器 
objectFactory 对象工厂 
plugins 插件 
environments 环境
environment 环境变量
transactionManager 事务管理器 
dataSource 数据源 
 
 
 
 
databaseIdProvider chinese? 
mappers 映射器 
 
 
 
mapper映射文件 主要告知框架可以执行哪些数据库操作(写增删改查的SQL语句 )
规范
 
起名:xxxMapper.xml -> xxx 表示 domain, userMapper.xml 
路径:映射文件跟 mapper(用来做增删改查) 接口 位置一致。 
 
约束头 1 2 3 <!DOCTYPE mapper   PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"   "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 
标签属性(自动返回主键) SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):
cache 配置给定命名空间的缓存 
cache-ref 从其他命名空间引用缓存配置 
resultMap 最复杂,用来描述如何从数据库结果集中加载你的对象 
sql 可以重用的 SQL 块, 
insert 映射插入语句 
update 映射更新语句 
delete 映射删除语句 
select 映射查询语句 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <mapper  namespace ="cn.wolfcode.UserMapper" >          <insert  id ="save"  useGeneratedKeys ="true"  keyProperty ="id" >          inset into user(name, salary, hiredate) values(#{name}, #{salary}, #{hiredate})     </insert >  	<update  id ="update" >  		update user set name=#{name}, salary=#{salary}, hiredate=#{hiredate} where id=#{id}; 	</update >  	<delete  id ="delete" >  		delete from user where id=#{id}; 	</delete >  	<select  id ="get"  resultType ="User" >  		select * from user where id=#{id}; 	</select >  	<select  id ="gets"  resultType ="User" >  		select * from user; 	</select >  </mapper > 
建议:写 SQL 的时候,先写带有占位符 ? 的那种写法,再使用 #{} 去替换。
 
resultMap 如果列名和属性名匹配,可以直接使用 resultType,否则就要使用 resultMap了
只要是查询:就一定要有结果类型, 要么是 resultType, 要么是 resultMap
封装映射,将字段名和属性名一一对应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <select  id ="listAll"  resultMap ="basemap" >      </select > <resultMap  type ="cn.lizhaoloveit.User"  id ="basemap" >          <id / cloumn ="uid"  property ="id" />      <result  column ="uname"  property ="name" />      <result  column ="usalary"  property ="salary" />      <result  column ="uhiredate"  property ="hiredate" />  </resultMap > 
只要是查询,一定有结果类型。
 
使用 mapper 接口 
创建一个 Mapper 接口,这个接口的要求:
这个接口的全限定名===对应的 Mapper 文件的 namespace 
这个接口中的方法和 Mapper 文件的 SQL 元素一一对应:方法名字=SQL元素id,方法的参数类型=SQL元素中定义的 parameterType 类型,方法的返回类型=SQL元素中定义的resultType/resultMap 类型’ 
 
 
创建 sqlSession 
通过 sqlSession.getMapper(xxxMapper.class) 方法得到一个 Mapper 对象 
调用 Mapper 对象上的方法文成对象的查询 
 
使用 @Param 设置多个参数 如果要在 Mapper 接口上的一个方法中,添加多个参数,就一定要在每个参数前使用 @Param 标签
User login(@Param(“name”)String name, @Param(“password”)String password);
原理:Mybatis 在处理这些Mapper方法的时候,会自动的把这些参数包装成一个 Map 对象,@Param中的 value 就会作为这个 Map 的 key,对应的参数值,就会作为这个 Key 的value 
${}在mapper.xml 中的使用场景 
mapper.xml  select 中若需要作为 order by group by 取值,要用${} , (#{}会给列名 + '') 会导致排序无效。
 
外键支持 我们不能在数据库添加外键,这样会非常影响数据库读写数据的性能。所以使用代码去实现表与表之间的关联是必须的。
一对一 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <resultMap  type ="mybatis.Orders"  id ="OrdersUserResultMap" > 	 	<id  column ="id"  property ="id" />  	<result  column ="user_id"  property ="userId" />  	<result  column ="number"  property ="number" />  	<result  column ="createtime"  property ="createtime" />  	<result  column ="note"  property ="note" />  	 	 	 	<association  property ="user"  javaType ="mybatis.po.User" >  		<id  column ="user_id"  property ="id" />  		<result  column ="username"  property ="username" />  		<result  column ="sex"  property ="sex" />  		<result  column ="address"  property ="address" />  	</association >  </resultMap > 
上述配置,相当于一个 Order 类中保存这一个 User 类,使用  标签来 
多对一(多个学生与一个老师) 由 Student类来维护 Student与 Teacher的关系。
项目结构如下:
表结构:
domain 类
1 2 3 4 5 6 7 8 9 10 11 12 public  class  Student  	private  Long id; 	private  String name; 	private  String password; 	private  Teacher teacher; } public  class  Teacher  	private  Long id; 	private  String name; 	private  String sex; } 
多对一(add) 添加数据 写代码时,我一般的习惯是先从测试入手,先想好程序外面会如何使用功能,再着手实现。思考过程如下:
1 2 3 4 5 6 7 8 9 10 11 public  void  testSave ()  	 	Teacher tea = new  Teacher(null , "小A" , "女" ); 	teaDao.save(tea); 	 	Student stu1 = new  Student(null , "A" , "123456" , tea); 	Student stu2 = new  Student(null , "B" , "123456" , tea); 	 	stuDao.save(stu1); 	stuDao.save(stu2); } 
TeacherMapper.xml
1 2 3 <insert  id ="save"  useGeneratedKeys ="true"  keyProperty ="id" > 		insert into teacher_all2one (name, sex) values(#{name}, #{sex}) </insert > 
StudentMapper.xml
1 2 3 <insert  id ="save"  useGeneratedKeys ="true"  keyProperty ="id" > 		insert into student_all2one (name, password, tea_id) values(#{name}, #{password}, #{teacher.id}) </insert > 
在保存的时候,先保存 teacher 然后根据 tea 的 id 属性,保存stu1,stu2。
多对一(select) 还是老办法,先写 test
1 2 3 4 5 @Test public  void  testGet ()  	Student student = stuDao.get(1l ); 	System.out.println(student); } 
思考:从上面的测试方法中,我们需要得到一个 student 对象,且属性 teacher 要能够被赋值。
解决方法,使用 resultMap 映射,将 teacher 属性映射成一行数据,该行数据正好是 teacher 表中的一行,即查询 teacher 表中的一行数据赋值给 Student 类中的 teacher 属性。
StudentMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <resultMap  type ="Student"  id ="studentMap" >      	<association  property ="teacher"  javaType ="Teacher"  column ="tea_id"   		select ="cn.lizhaoloveit.mybatis.mapper.TeacherMapper.getById" > 	</association >  </resultMap > <select  id ="getById"  resultMap ="studentMap" > 	select * from student_all2one where id=#{id} </select > 
TeacherMapper.xml
1 2 3 <select  id ="getById"  resultType ="Teacher" > 	select * from teacher_all2one where id=#{id} </select > 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <resultMap  type ="全限定名"  id ="map" >     <id  column ="id"  property ="id" />           <result  column ="name"  property ="name" />           <association  select ="全限定名.get"  property ="dept"  column ="dept_id" />  </resultMap > 
使用了继承,是因为订单信息和关联的用户信息和前面一对一是完全一样的,不需要再写一遍,resultMap 支持继承,直接继承那个 resultMap 即可,然后加上订单明细这部分。
N+1 sql (多对一查询) 问题:多对一 n+1 问题是指以下情况,假设系统中存在两张表,学生表(多),和老师表(1),
现在假设一个页面需要浏览学生记录,同时要显示这个学生对应的老师名称,有两种查询方法。
n+1 句 sql,先查询 select * from 学生表。再根据结果记录查询 select * from 老师表 where tid = ? 
建立一条联合查询语句 select * from 学生表 a inner join 老师表 b on a.tid=b.tid 
 
在没有分页的情况下,第二种方法的效率比第一种方法高,然而在有分页的情况下,反而是 n+1 句 sql 的执行效率高。
没分页的情况下,复杂度 10000 + 10000 * 1000  第二个是 10000 * 1000
假设两张表没有索引,学生表有10000条记录,老师表为1000条记录,每个页面显示 100 条记录,第一种方法数据库的执行的时间复杂度是 10000 + 100 * 1000。
第二种方法是 10000 * 1000.
总结:分页查询和查询单个对象时,一般使用 N + 1,在全部列表查询,一般使用多表查询
1 2 3 4 5 public  class  student      private  Long id;     private  String name;     private  Teacher teacher } 
1 2 3 4 5 6 <resultMap  type ="Student"  id ="studentMap" >     <id  column ="id"  property ="id" />           <result  column ="name"  property ="name" />      <association  select ="Teacher.get"  property ="teacher"  column ="t_id" />  </resultMap > 
两个 maper,一个是 select * from student, 一个是 select * from teacher where id = tid
多表联查 1 2 3 4 5 6 public  interface  EmployeeMapper      void  save (Employee employee)           Employee get (Long id)  ;         List<Employee> list ()  ;     } } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <resultMap  type ="Employee"  id ="employeeMap" > 	 	<id  column ="id"  property ="id" />  	<result  column ="name"  property ="name" />  	<result  column ="name"  property ="name" />  	 	 	 	<association  property ="dept"  javaType ="Department" >  		<id  column ="d_id"  property ="id" />  		<result  column ="d_name"  property ="name" />  	</association >  </resultMap > <select  id ="get"  resultMap ="singleMap2" >     select e.id, e.name, d.id d_id, d.name d_name from employee e left join department d on d.dept_id=d.id where e.id=#{id} </select > 
1 SELECT  * FROM  student LEFT  JOIN  teacher ON  tid = id  WHERE  student.id = 
多对一删除 首先,删除学生的话不会有任何影响,但是删除老师就意味着这个老师教授的学生的老师字段将不存在,所以需要将这些字段的值一并删除,才符合现实逻辑。
1 2 3 4 @Test public  void  testDelete ()  	teaDao.delete(2l ); } 
要先将这个老师教授的学生与这个老师的关系删除, 
再删除这个老师 
 
StudentMapper.xml
1 2 3 4 5 6 7 <update  id ="deleteRelation" > 	update student_all2one set tea_id=null where tea_id=#{id} </update > 	 <delete  id ="delete" > 	delete from student_all2one where id=#{id} </delete > 
TeacherMapper.xml
1 2 3 <delete  id ="delete" > 	delete from teacher_all2one where id=#{id} </delete > 
TeacherDAOImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 @Override public  void  delete (Long id)  	SqlSession session = MybatisUtil.getConnection(); 	TeacherMapper teaMapper = session.getMapper(TeacherMapper.class); 	StudentMapper stuMapper = session.getMapper(StudentMapper.class); 	 	 	stuMapper.deleteRelation(id); 	teaMapper.delete(id); 	session.commit(); 	session.close(); } 
一对多(一个老师教多个学生) 由 Teacher类来维护 Teacher和 Student类之间的关系
表结构不会改变,但 domain类中的维护关系方变成 Teacher 了。
1 2 3 4 5 6 7 8 9 10 11 12 public  class  Student  	private  Long id; 	private  String name; 	private  String password; } public  class  Teacher  	private  Long id; 	private  String name; 	private  String sex; 	private  List<Student> stus = new  ArrayList<Student>(); } 
一对多(add) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private  IStudentDAO stuDao = new  StudentDAOImpl();private  ITeacherDAO teaDao = new  TeacherDAOImpl();@Test public  void  testSave ()  	 	Student stu1 = new  Student(); 	stu1.setName("小A" ); 	stu1.setPassword("123456" ); 	Student stu2 = new  Student(null , "小B" , "123456" ); 	stuDao.save(stu1, stu2); 	 	Teacher tea = new  Teacher(); 	tea.setName("A" ); 	tea.setSex("女" ); 	tea.getStus().add(stu1); 	tea.getStus().add(stu2); 	teaDao.save(tea); 	 	for  (Student stu : tea.getStus()) { 	    		stuDao.addRelation(tea.getId(), stu.getId()); 	} } 
分析:先插入多个 Student对象,然后创建 Teacher对象,最后添加依赖。由于维护关系的责任在 Teacher身上,所以 Student类不能在被添加到数据库时知道自己是哪个 Teacher的学生。只能等 Teacher分配好以后才能知道,所以最后需要维护 student表的 tea_id字段。
TeacherMapper.xml
1 2 3 <insert  id ="save"  useGeneratedKeys ="true"  keyProperty ="id" > 	insert into teacher_one2all (name, sex) values(#{name}, #{sex}) </insert > 
StudentMapper.xml
1 2 3 4 5 6 7 <insert  id ="save"  useGeneratedKeys ="true"  keyProperty ="id" > 	insert into student_one2all (name, password) values(#{name}, #{password}) </insert > <update  id ="addRelation" > 	update student_one2all set tea_id=#{teaId} where id=#{stuId} </update > 
StudentMapper.java
1 2 3 4 5 6 7 8 9 10 11 12 public  interface  StudentMapper  	void  save (Student stu)  	 	void  addRelation (@Param("teaId" ) Long teaId, @Param ("stuId" ) Long stuId) 	 	void  delete (Long id)  	void  deleteRelation (Long id)  } 
一对多select(额外 sql) test,需要做到在数据库中查询出 teacher时,就能得到对应的学生列表
1 2 3 4 5 @Test public  void  testGet ()  	Teacher teacher = teaDao.get(39l );  	System.out.println(teacher); } 
由 teacher的 id属性到 student表中查询出所有 tea_id=id的学生,需要在 StudentMapper.xml 添加一个额外的方法用于查询出 Student集合。
TeacherMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 <resultMap  type ="Teacher"  id ="teacherMap" > 	<id  column ="id"  property ="id" />  	     	<collection  property ="stus"  column ="id"   		select ="cn.lizhaoloveit.mybatis.mapper.StudentMapper.getByTeaId" > 	</collection >  </resultMap > <select  id ="getById"  resultMap ="teacherMap" > 	select * from teacher_one2all where id=#{id} </select > 
StudentMapper.xml
1 2 3 <select  id ="getByTeaId"  resultType ="Student" > 	select * from student_one2all where tea_id=#{teaId} </select > 
一对多(关联查询) 改动 DepartmentMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <resultMap  type ="Department"  id ="singleMap2" >     <id  column ="id"  property ="id" />      <result  column ="name"  property ="name" />           <collection  property ="es"  ofType ="Employee" >                   <id  column ="e_id"  property ="id" />          <result  column ="e_name"  property ="name" />      </collection >  </resultMap > <select  id ="get"  resultMap ="map" >     select d.id, d.name, e.id e_id, e.name e_name      from department d left join employee e on d.id=e.dept_id     where d.id=#{id} </select > 
一对多(delete) 
将 student表中的对应要删除的 tea_id 置空 
删除 tea_id 的 teacher表中的数据。 
 
1 2 3 4 @Test public  void  testDelete ()  	teaDao.delete(42l ); } 
TeacherDAOImpl.java
1 2 3 4 5 6 7 8 9 10 11 @Override public  void  delete (Long id)  	SqlSession session = MybatisUtil.getConnection(); 	TeacherMapper teaMapper = session.getMapper(TeacherMapper.class); 	StudentMapper stuMapper = session.getMapper(StudentMapper.class); 	 	stuMapper.deleteRelation(id); 	teaMapper.delete(id); 	session.commit(); 	session.close(); } 
StudentMapper.xml
1 2 3 <update  id ="deleteRelation" > 	update student_one2all set tea_id=null where tea_id=#{teaId} </update > 
TeacherMapper.xml
1 2 3 <delete  id ="delete" > 	delete from teacher_one2all where id=#{id} </delete > 
多对多映射 表结构:
学生表:
老师表:
关系表:
多对多(save) 1 2 3 4 5 6 7 8 9 10 11 12 13 public  class  Student  	private  Long id; 	private  String name; 	private  String password; 	private  List<Teacher> teachers = new  ArrayList<Teacher>(); } public  class  Teacher  	private  Long id; 	private  String name; 	private  String sex; 	private  List<Student> stus = new  ArrayList<Student>(); } 
test.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Student s1 = new  Student(); s1.setName("乔峰" ); s1.setPassword("123456" ); Student s2 = new  Student(); s2.setName("张无忌" ); s2.setPassword("123456" ); Teacher t1 = new  Teacher(); t1.setName("dafei" ); t1.setSex("女" ); Teacher t2 = new  Teacher(); t2.setName("will" ); t2.setSex("女" );      s1.getTeachers().add(t1); s1.getTeachers().add(t2); s2.getTeachers().add(t1); s2.getTeachers().add(t2);      t1.getStus().add(s1); t1.getStus().add(s2); t2.getStus().add(s1); t2.getStus().add(s2); 
创建 Student 和 Teacher对象,然后维护对象之间的关系,然后就要保存对象到数据库,
1 2 3 4 5 6 7 8 9 10 11 12 stuDao.save(s1, s2);  teaDao.save(t1, t2);  for  (Teacher t : s1.getTeachers()) {	stuDao.addRelation(t.getId(), s1.getId()); }      for  (Teacher t : s2.getTeachers()) {	stuDao.addRelation(t.getId(), s2.getId()); } 
操作 1和 2只是让数据进入到了表里,并没有对其关系做关联。因此需要额外表 student_teacher,保存 student 和 teacher表的关系。
StudentMapper.xml
1 2 3 4 5 6 7 <insert  id ="save"  useGeneratedKeys ="true"  keyProperty ="id" > 	insert into student_all2all (name, password) values(#{name}, #{password}) </insert > <insert  id ="addRelation" > 	insert into student_teacher (stu_id, tea_id) values(#{stuId}, #{teaId}) </insert > 
TeacherMapper.xml
1 2 3 4 5 6 <insert  id ="save"  useGeneratedKeys ="true"  keyProperty ="id" > 	insert into teacher_all2all (name, sex) values(#{name}, #{sex}) </insert > <insert  id ="addRelation" > 	insert into student_teacher (stu_id, tea_id) values(#{stuId}, #{teaId}) </insert > 
多对多(get 额外sql) 1 2 3 4 5 @Test public  void  testGet ()  	System.out.println(stuDao.get(9l )); 	System.out.println(teaDao.get(10l )); } 
查询时,希望得到的 Student对象中 teachers可以被直接赋值。此时需要通过 resultMap 
StudentMapper.xml
1 2 3 4 5 6 7 8 9 10 <resultMap  type ="Student"  id ="studentMap" > 	<id  column ="id"  property ="id" />  	<collection  property ="teachers"  column ="id"   		select ="cn.lizhaoloveit.mybatis.mapper.TeacherMapper.getByStuId" > 	</collection >  </resultMap > <select  id ="getById"  resultMap ="studentMap" > 	select * from student_all2all where id=#{stuId} </select > 
Teacher.xml
1 2 3 4 5 6 <select  id ="getByStuId"  resultType ="Teacher" > 	select *  	from teacher_all2all t left join student_teacher st on t.id=st.tea_id  	where stu_id=#{stuId} </select > 
查询 Student,需要在关联表中通过 stu_id,查出所有与之匹配的 tea_id,就可以找到该学生被哪些老师教授了。
同理,想要得到 Teacher对象。需要根据 tea_id,在关联表中找出所有匹配的 stu_id
TeacherMapper.xml
1 2 3 4 5 6 7 8 9 10 <resultMap  type ="Teacher"  id ="teacherMap" > 	<id  column ="id"  property ="id" />  	<collection  property ="stus"  column ="id"   		select ="cn.lizhaoloveit.mybatis.mapper.StudentMapper.getByTeaId" > 	</collection >  </resultMap > <select  id ="getById"  resultMap ="teacherMap" > 	select * from teacher_all2all where id=#{id} </select > 
StudentMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 <select  id ="getByTeaId"  resultType ="Student" > 	select *  	from student_all2all s left join student_teacher st on s.id = st.stu_id    	where tea_id=#{teaId} </select > <select  id ="getByTeaId"  resultType ="Student" > 	select *  	from student_all2all  	where id in 	(select stu_id from student_teacher where tea_id=#{teaId}) </select > 
多对多(get 关联查询) 1 2 3 SELECT  s.id, s.name, t.id t_id, t.name t_name FROM  student s LEFT  JOIN  student_teacher st ON  s.id = st.stu_idLEFT  JOIN  teacher ON  t.id = st.tea_id WHERE  s.id = 
1 2 3 4 5 6 7 8 <resultMap  type ="Student"  id ="StudentMap" >     <id  column ="id"  property ="id" />      <result  column ="name"  property ="name" />      <collection  property ="ts"  ofType ="Teacher" >          <id  column ="t_id"  property ="id" />          <result  column ="t_name"  property ="name" />      </collection >  </resultMap > 
1 2 3 4 5 6 7 SqlSession session = MyBatisUtil.getSqlSession(); StudentMapper studentMapper = session.getMapper(StudentMapper.class); Student student = studentMapper.get(1L ); session.commit(); session.close(); 
多对多(delete) 在删除其中一个表的一个数据时,需要先删除其与其他表的关联。即先删除中间表中存在该字段的行。再删除该数据
以删除 student 表中一个数据为例,(teacher 同理)
1 2 3 4 5 @Test public  void  testDelete ()  	teaDao.delete(9l ); 	stuDao.delete(10l ); } 
StudentDAOImpl.java
1 2 3 4 5 6 7 8 9 @Override public  void  delete (Long stuId)  	SqlSession session = MybatisUtil.getConnection(); 	StudentMapper mapper = session.getMapper(StudentMapper.class); 	mapper.deleteRelation(stuId); 	mapper.delete(stuId); 	session.commit(); 	session.close(); } 
StudentMapper.xml
1 2 3 4 5 6 7 <delete  id ="delete" > 	delete from student_all2all where id=#{stuId} </delete > 	 <update  id ="deleteRelation" > 	delete from student_teacher where stu_id=#{stuId} </update > 
延迟加载问题 查询订单并且关联查询用户信息时,如果先查询订单信息即可满足要求,当我们需要查询用户信息时再查询用户信息,把对用户信息的按需查询叫做延迟加载,
延迟加载就是,先从单表查询,需要时再从关联表取查询,单标查询要比关联表查询速度快。
使用 association、collection 实现一对一及一对多映射,association 和 collection 还具备延迟加载功能,查询订单并且关联查询用户,查询用户使用延迟加载。
延迟加载要查询两次,第二次是按需查询,之前一对一关联查询的时候,只需要查一次,把订单和用户信息都查出来了,所以只要一个 mapper。但是延迟加载查两次,所以要有两个 mapper。
1 2 3 4 5 6 7 8 <select  id ="findOrdersUserLazyLoading"  resultMap ="OrdersUserLazyLoadingResultMap" > 	SELECT * FROM orders </select > <select  id ="findUserById"  parameterType ="int"  resultType ="user" > 	select * from user where id = #{id} </select > 
延迟加载的配置:<settings>,可以通过这个标签来配置一下延迟加载。
 
1 2 3 4 5 6 7 8 <settings >          <setting  name ="lazyLoadingEnabled"  value ="true" />           <setting  name ="aggressiveLazyLoading"  value ="false" />       	 <setting  name ="lazyLoadTriggerMethods"  value ="equals,clone,hashCode" />  </settings > 
1 2 3 4 5 6 7 public  interface  UserMapperOrders  	 	 	 	 	public  List<Orders> findOrdersUserLazyLoading ()  throws  Exception } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public  void  testFindOrdersUserLazyLoading ()  throws  Exception 	SqlSession sqlSession = sqlSessionFactory.openSession(); 	UserMapperOrders userMapperOrders = sqlSession.getMapper(UserMapperOrders.class); 	 	List<Orders> list = userMapperOrders.findOrdersUserLazyLoading(); 	 	 	for (Orders orders : list) { 		 		User user = orders.getUser(); 		System.out.println(user); 	} } 
使用了延迟加载,将关联查询分成了两次单表查询,第二次查询用户的时候,没有发送 sql 就拿到了数据,其实这是 mybatis 中的一级缓存。
延迟加载原理 深入一些可以发现 orders.getUser() 方法中并没有相关的 sql 执行,为什么在加载完 list 订单表时,没有执行 sql ,而在 orders.getUser() 调用时,发送了查询 user 表的 sql。这是为什么?
其实,Mybatis 使用了动态代理技术,在 Mybatis 内部会生成一个动态代理类 Orders$xxxx类,该类继承 Orders,并会拦截所有 public 方法。
当调用 Orders 的 getUser(),调用的是代理类的同名方法,代理类会引用一个实现了 InvocationHandler 接口的切面增强类,会调接口的 invoke 方法,将方法名和方法参数传入 invoke 中,然后增强类引用 Orders 真实类型,并调用 Orders 的方法并传入参数。在 Orders 方法调用前后实现增强,详情见动态代理原理 
Orders$xxxx类,会有一个参数判断 User 是否已经加载,boolean userLoaded = false,在调用 getUser() 时,
1 2 3 4 5 6 7 8 9 public  User getUser ()      if  (!userLoaded) {                           userLoaded = true ;              }      } 
Mybatis 缓存 缓存本质就是 Map,缓存是存在内存中的,可以在查询的时候减少数据库的访问次数,加快查询速度。
一级缓存 一级缓存的工作原理:
第一次发起查询用户id为1的用户信息,先去缓存中找是否有id为1的用户信息,如果没有,从数据库查询用户信息,得到用户信息,将用户信息存储到一级缓存中。
如果中间 sqlSession 去执行 commit 操作(插入、更新、删除),则会清空 SqlSession 中的一级缓存,这样做的目的是为了让缓存中存储的是最新的版本,避免脏读。
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test public  void  testCache1 ()  throws  Exception 	SqlSession sqlSession = sqlSessionFactory.openSession(); 	UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 	 	 	 	User user1 = userMapper.findUserById(1 ); 	System.out.println(user1); 	 	 	 	user1.setUsername("测试用户22" ); 	userMapper.updateUser(user1); 	 	sqlSession.commit(); 	 	 	User user2 = userMapper.findUserById(1 ); 	System.out.println(user2); 	sqlSession.close(); } 
一级缓存生命周期==session 的生命周期,在多个 session 中是无法尽心该数据共享。 
在一次会话中,如果需要查询多次相同 id  的对象,此时后面几次的查询都会从缓存中获取,减少了访问数据库的次数,一级缓存的作用有限,只提高了一点点性能。
二级缓存 
二级缓存生命周期== sessionFactory 的生命周期 
二级缓存可以在不同的 session 之间进行数据的共享 
二级缓存默认是关闭的,需要手动的去开启 <cache/> 
不是所有对象都适合放到二级缓存中,只有读远远大于写的对象才适合放到二级缓存中 
只要对象发生 DML 操作,MyBatis 中的二级缓存都会给清除 
手动开启二级缓存,对应的 mapper.xml 文件添加一行:
<cache/> 同时,需要缓存的对象实现序列化接口 
 
 
当内存中的对象已经达到设置的存储最大值,超出的对象如果需要也缓存起来,支持把缓存对象序列化到硬盘中,要获取的时候再反序列化回来 
1 2 3 public  class  USer  implements  Serializable  } 
UserMapper.xml
1 2 3 <mapper  namespace ="User.Mapper" >     <cache  type ="org.mybatis.caches.ehcache.EhcacheCache" />   </mapper > 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public  void  testGet ()  throws  Exception     SqlSession session = MyBatisUtil.openSession();     UserMapper mapper = session.getMapper(UserMapper.class);          User user = mapper.get(1l );          session.commit();     session.close();          session = MyBatisUtil.openSession();     mapper = session.getMapper(UserMapper.class);          user = mapper.get(1l );     session.commit();     session.close(); } 
二级缓存的高效使用 需求: 开启二级缓存后,get(1l); insert(4l); get(1l) —> 总共发送3条 SQL
查询所有记录的查询语句,如果每次都先去二级缓存中获取,经常获取不到,然后又要去数据库中查询,效率低,查询所有不使用二级缓存:
useCache="false" 
 
insert 不会对 get(1L) 造成影响,所以 insert 只有在查询所有的时候才会有影响,出现脏读,可以让 insert 不刷新缓存
 
 
get(1L); insert(4l); get(1L) 就会只发送2条 SQL。第二次 get(1L) 是从二级缓存中获取的。
EhCashe 二级缓存 
mybatis-ehcache-1.0.2.jar 
ehcache-core-2.6.5.jar 
 
导包 mybatis和ehcache包装的jar
1 <dependency > 
    <groupId > org.mybatis.caches</groupId > 
    <artifactId > mybatis-ehcache</artifactId > 
    <version > 1.1.0</version > 
</dependency > 
配置文件 名称固定 ehcache.xml
maxElementsInMemory : 该缓存池在内存中最大的缓存对象个数eternal : 是否永久有效,如果设置为 true, 内存中对象永不过期timeToIdleSeconds : 缓存对象最大空闲时间,单位: 秒;timeToLiveSeconds : 缓存对象最大生存时间,单位: 秒;overflowToDisk : 当内存中对象超过最大值,是否临时保存到磁盘maxElementsOnDisk : 能保存到磁盘上最大对象数量diskExpiryThreadIntervalSeconds : 磁盘失效线程运行时间间隔,默认是120秒memoryStoreEvictionPolicy : 当达到 maxElementsInMemory 限制时, Ehcache 将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用),可以设置为 FIFO(先进先出)或者 LFU(较少使用)
 
如果我们Mapper想单独拥有一些特性,需要在mapper.xml中单独配置
1 2 3 4 5 6 7 8 9 10 <cache  type ="org.mybatis.caches.ehcache.EhcacheCache" >     <property  name ="timeToIdleSeconds"  value ="12000" />      <property  name ="timeToLiveSeconds"  value ="3600" />           <property  name ="maxEntriesLocalHeap"  value ="1000" />           <property  name ="maxEntriesLocalDisk"  value ="10000000" />      <property  name ="memoryStoreEvictionPolicy"  value ="LRU" />  </cache > 
mybatis局限性 mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:
对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。
启动框架 目的:拿到数据库连接对象,操作增删改查
步骤:
 
加载主配置文件 
获取 SqlSesstionFactory 对象(相当于连接池) 
通过 factory 获取 SqlSesstion 
 
插入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public  void  testInsert ()  throws  Exception      	  InputStream in = Resources.getResourceAsStream("mybatis-config.xml" );      	  SqlSessionFactory factory = new  SqlSessionFactoryBuilder().build(in);          SqlSession session = factory.openSession();               User u = new  User(null , "谢谢" , new  BigDecimal("1000" ), new  Date());     System.out.println(u);     session.insert("cn.lizhaoloveit.mybatis.UserMapper.insert" , u);          session.commit();          session.close();     System.out.println(u); } 
Thread.currentThread().getContextClassLoader() 和 Class.getClassLoader() 的区别
 
前者是比较安全的用法,如果要在代码中动态加载 jar、资源文件的时候,首先应该使用 Thread.currentThread().getContextClassLoader(),因为如果你是用class.getClassLoader(),可能会导致和当前线程所运行的类加载器不一致,Java是天然的多线程,Java 只要类加载器不一致,加载出的类即使源文件一样,也会有不同的字节码对象。 
Class.getClassLoader() 一般用在 getResource,因为你想获取某个资源文件的时候,这个资源文件的位置是相对固定的。 
优化,1. 配置文件只需加载一次,整个应用只需要一个 SessionFactory 对象,也就是连接池对象
 
优化后代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private  static  SqlSessionFactory factory = null ;private  final  static  String USERMAPPER_PATH = "cn.lizhaoloveit.mybatis.UserMapper" ;static  {	try  { 		factory = new  SqlSessionFactoryBuilder() 				.build(Resources.getResourceAsStream("mybatis-config.xml" )); 	} catch  (Exception e) { 		e.printStackTrace(); 	} } 	 @Test public  void  testInsert ()  throws  Exception 	 	SqlSession session = factory.openSession(); 	 	User u = new  User(null , "路驾驭" , new  BigDecimal("100" ), new  Date()); 	session.insert(USERMAPPER_PATH + ".insert" , u); 	 	session.commit(); 	session.close(); } 
修改 1 2 3 4 5 6 7 public  void  testUpdate ()  throws  Exception 	SqlSession sesstion = factory.openSession(); 	User u = new  User(9l , "路驾驭" , new  BigDecimal("100" ), new  Date()); 	sesstion.update(USERMAPPER_PATH + ".update" , u); 	sesstion.commit(); 	sesstion.close(); } 
删除 1 2 3 4 5 6 public  void  testDelete ()  throws  Exception 	SqlSession session = factory.openSession(); 	session.delete(USERMAPPER_PATH + ".delete" , 3l ); 	session.commit(); 	session.close(); } 
查询 1 2 3 4 5 6 public  void  testGet ()  throws  Exception 	SqlSession session = factory.openSession(); 	User u = session.selectOne(USERMAPPER_PATH + ".get" , 2l ); 	session.close(); 	System.out.println(u); } 
1 2 3 4 5 public  void  testGets ()  throws  Exception 	SqlSession session = factory.openSession(); 	List<User> list = session.selectList(USERMAPPER_PATH + ".gets" ); 	session.close(); } 
MyBatis 操作流程 
通过主配置文件来启动框架,目的主要是获取 SqlSession 对象 
使用 session 对象执行 SQL 命令。
找到指定的 SQL 命令:namespace(cn.lizhaoloveit.mybatis.UserMapper.insert) + . + id 
传递执行该 SQL 需要的参数 
 
 
如果执行 DML 操作,不要忘记提交事务。 
关闭源 
 
OGNL 表达式 #{} :mybatis 会将该语法进行翻译,将#{}直接使用 ? 来替换,相当于是一个 SQL 模板,创建一个预编译语句对象,给占位符设置值
#{} 就是一个 OGNL 表达式
OGNL (Object-Graph Navigation Language) 对象图形导航语言,可以存取对象的属性和调用对象的方法,遍历整个对象的结构图。
如何取值 
#{} 可以直接取某个位置的属性#{key} #{任意} 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class  Employee      String name;     int  age;     Dept dept; } class  Dept      String name; } 
什么时候往 session 中放的数据
 
什么时候取?
 
使用日志 日志框架可以把日志的输出和代码分离,方便的定义日志的输出环境,控制台,文件,数据库,方便的定义日志的输出格式和输出级别。
ERROR > WARN > INFO > TRACE
导入日志框架包:
log4j-1.2.17.jar slf4j-api-1.7.25.jar slf4j-log4j12-1.7.25.jar—> stf4 规范
配置日志文件监控 MyBatis 的运行
log4j.properties; 日志文件在 classpath
1 2 3 4 5 6 7 8 log4j.rootLogger =ERROR, stdout log4j.logger.cn.lizhaoloveit =TRACE log4j.appender.stdout =org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout =org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern =%5p [%t] - %m%n 
动态 SQL 如果你有 JDBC 或者其他相似框架的使用经验,就明白条件地串联 SQL 字符串在一起有多痛苦,确保不能忘了空格或者在列表的最后省略逗号。动态 SQL 可以彻底处理这种痛苦
通常使用动态 SQL 不可能是独立的一部分,MyBatis 使用一种强大的 动态 SQL 语言可以被用在任意映射的 SQL 语句中。
if 
choose(when, otherwise) 
trim(where, set) 
(foreach) 
 
xml 转义字符 
字符实体 
字符 
意义 
 
 
<< 
小于 
 
>> 
大于 
 
&& 
和 
 
'‘ 
单引号 
 
"“ 
双引号 
 
示例 查询类如下:
1 2 3 4 5 6 7 public  class  QueryStudent  	private  String name; 	private  int  minAge; 	private  int  maxAge; 	private  QueryByPage qbp; 	private  int [] ages; } 
mapper.xml 内要做模糊匹配和范围查询,三种方式和第一个 select 标签内要查询同一个内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <select  id ="queryForName"  resultType ="Student" > 	select * from student  		where name like concat('%', concat(#{name}, '%'))  			and age < #{maxAge} and age > #{minAge}  				limit #{qbp.beginIndex}, #{qbp.pageSize} </select > 	 <select  id ="queryForName"  resultType ="Student" > 	select * from student 	<where >  		<if  test ="name != null" >  			name like concat('%', concat(#{name}, '%')) 		</if >  		<if  test ="maxAge != null" >  			and age < #{maxAge} 		</if >  		<if  test ="minAge != null" >  			and age > #{minAge} 		</if >  	</where >  		limit #{qbp.beginIndex}, #{qbp.pageSize} </select > 	 <select  id ="queryForName"  resultType ="Student" > 	select * from student 	<where >  		<if  test ="name != null" >  			name like concat('%', concat(#{name}, '%')) 		</if >  		<if  test ="ages != null" >  			and age in 			<foreach  collection ="ages"  item ="age"  index ="index"    			open ="("  separator =","  close =")" > 				#{age} 			</foreach >  		</if >  	</where >  		limit #{qbp.beginIndex}, #{qbp.pageSize} </select > 
if 1 2 3 4 5 6 7 8 <select  id ="findActiveBlogWithTitleLike"        parameterType ="Blog"  resultType ="Blog" >   SELECT * FROM BLOG    WHERE state = ‘ACTIVE’    <if  test ="title != null" >   --如果title != null if 标签--      AND title like #{title}   </if >  </select > 
如果没有 title state=’ACTIVE’ 的 blog 将会被返回,如果传递了 title,就会查找相近的 title 的 blog。
如果我们想可选的搜索 title 和 auther ,加入另一个简单的条件即可
1 2 3 4 5 6 7 8 9 10 <select  id ="findActiveBlogLike"        parameterType ="Blog"  resultType ="Blog" >   SELECT * FROM BLOG WHERE state = ‘ACTIVE’    <if  test ="title != null" >      AND title like #{title}   </if >    <if  test ="author != null and author.name != null" >      AND author_name like #{author.name}   </if >  </select > 
choose, when, otherwise 有时候,不想应用所有的条件,很多情况下只选择一种,和 Java 中的 switch 语句相似,MyBatis 提供 choose 元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <select  id ="findActiveBlogLike"        parameterType ="Blog"  resultType ="Blog" >   SELECT * FROM BLOG WHERE state = ‘ACTIVE’   <choose >      <when  test ="title != null" >        AND title like #{title}     </when >      <when  test ="author != null and author.name != null" >        AND author_name like #{author.name}     </when >      <otherwise >        AND featured = 1     </otherwise >    </choose >  </select > 
trim, where, set 如果 Where 后面的语句都是条件语句。就可以会出现问题,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <select  id ="findActiveBlogLike"        parameterType ="Blog"  resultType ="Blog" >   SELECT * FROM BLOG    WHERE    <if  test ="state != null" >      state = #{state}   </if >     <if  test ="title != null" >      AND title like #{title}   </if >    <if  test ="author != null and author.name != null" >      AND author_name like #{author.name}   </if >  </select > 
如果这些条件都没有匹配上,这条 SQL 结束时就会成这样:
1 2 SELECT  * FROM  BLOG WHERE 
如果仅仅第二条语句匹配:
1 2 3 SELECT  * FROM  BLOG WHERE  AND  title like  ‘someTitle’
where 标签就是用来解决这个问题,如果以 “AND” “OR” 开头的内容,就会跳过不插入 “AND” 或者 “OR”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <select  id ="findActiveBlogLike"        parameterType ="Blog"  resultType ="Blog" >   SELECT * FROM BLOG    <where >       <if  test ="state != null" >           state = #{state}     </if >       <if  test ="title != null" >          AND title like #{title}     </if >      <if  test ="author != null and author.name != null" >          AND author_name like #{author.name}     </if >    </where >  </select > 
如果 where 标签没有做出你想要的,可以使用 trim 标签自定义,比如:
1 2 3 <trim  prefix ="WHERE"  prefixOverrides ="AND |OR " >   ...  </trim > 
和 where 类似,set 在更新时,也可能没有要更新的列字段。此时就会变成
1 update Author set where id = ... 
1 2 3 4 5 6 7 8 9 10 11 <update  id ="updateAuthorIfNecessary"         parameterType ="domain.blog.Author" >   update Author     <set >        <if  test ="username != null" > username=#{username},</if >        <if  test ="password != null" > password=#{password},</if >        <if  test ="email != null" > email=#{email},</if >        <if  test ="bio != null" > bio=#{bio}</if >      </set >    where id=#{id} </update > 
foreach 如果你需要动态在范围查找,构建一个集合,你可以传递一个 List 对象或者数组作为参数,MyBatis 会自动将它包装在一个 Map 中,名称作为键,List对象会以 “list” 作为键,数组会以 “array” 作为键。
1 2 3 4 5 6 7 8 9 <select  id ="selectPostIn"  resultType ="domain.blog.Post" >   SELECT *   FROM POST P   WHERE ID in   <foreach  item ="item"  index ="index"  collection ="list"         open ="("  separator =","  close =")" >         #{item}   </foreach >  </select > 
如果User有属性List ids。入参是User对象,那么这个collection = “ids”Ids ids;其中 Ids 是个对象,Ids 有个属性 List id;
 
批量插入和批量删除 
bind –
效果:可以把一段字符串拼接成一个属性”name”值 中
1 2 3 4 5 <select  id ="selectBlogsLike"  parameterType ="Blog"  resultType ="Blog" >   <bind  name ="pattern"  value ="'%' + _parameter.getTitle() + '%'"  />    SELECT * FROM BLOG   WHERE title LIKE #{pattern} </select > 
Multi-db vendor support 支持不同的数据库一起查询
1 2 3 4 5 6 7 8 9 10 11 <insert  id ="insert"  parameterType ="org.myapp.domain.User" >   <selectKey  keyProperty ="id"  resultType ="int"  order ="BEFORE" >      <if  test ="_databaseId == 'oracle'" >        select seq_users.nextval from dual     </if >      <if  test ="_databaseId == 'db2'" >        select nextval for seq_users from sysibm.sysdummy1"     </if >    </selectKey >    insert into users values (#{id}, #{name}) </insert > 
MyBatis 3.2 开始,支持可插拔的脚本语言。
 
1 2 3 <select  id ="selectBlog"  lang ="raw" >   SELECT * FROM BLOG </select > 
1 2 3 4 5 6 7 8 9 10 11 public  interface  Mapper    @Lang (RawLanguageDriver.class)   @Select ("SELECT * FROM BLOG" )   List<Blog> selectBlog ()  ; } public  interface  LanguageDriver    ParameterHandler createParameterHandler (MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql)  ;   SqlSource createSqlSource (Configuration configuration, XNode script, Class<?> parameterType)  ;   SqlSource createSqlSource (Configuration configuration, String script, Class<?> parameterType)  ; }