
本文共 23709 字,大约阅读时间需要 79 分钟。
Mybatis SQL映射文件详解
mybatis除了有全局配置文件,还有映射文件,在映射文件中可以编写以下的顶级元素标签:
cache – 该命名空间的缓存配置。cache-ref – 引用其它命名空间的缓存配置。resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。parameterMap – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!要求使用行内参数映射。sql – 可被其它语句引用的可重用语句块。insert – 映射插入语句。update – 映射更新语句。delete – 映射删除语句。select – 映射查询语句。
在每个顶级元素标签中可以添加很多个属性,下面是一些下具体的配置。
一、insert、update、delete元素
属性 | 描述 |
---|---|
id | 在命名空间中,SQL语句的唯一标识符,可以被用来引用这条语句。 |
parameterType | 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。 |
parameterMap | 用于引用外部 parameterMap 的属性,目前已被废弃。推荐使用行内参数映射和 parameterType 属性。 |
flushCache | 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 |
statementType | 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
useGeneratedKeys | 这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。(仅适用于 insert 和 update) |
keyProperty | 指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset )。如果生成列不止一个,可以用逗号分隔多个属性名称。(仅适用于 insert 和 update) |
keyColumn | 设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。(仅适用于 insert 和 update) |
databaseId | 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。 |
insert into user(user_name) values(#{userName}) select max(id)+1 from user insert into user(id,user_name) values(#{id},#{userName})
二、select元素
1、select的参数传递
2、参数的取值方式
在xml文件中编写sql语句的时候有两种取值的方式,详细信息可以参考,这儿简单述说,分别是#{}和${},他们之间的区别如下:
参数预编译可以参考.
3、处理集合返回结果
EmpDao.xml
UserDao.java
package com.courage.dao;import com.courage.bean.Emp;import org.apache.ibatis.annotations.MapKey;import org.apache.ibatis.annotations.Param;import java.util.List;import java.util.Map;public interface EmpDao { public Emp findEmpByEmpno(Integer empno); public int updateEmp(Emp emp); public int deleteEmp(Integer empno); public int insertEmp(Emp emp); Emp selectEmpByNoAndName(@Param("empno") Integer empno, @Param("ename") String ename,@Param("t") String tablename); Emp selectEmpByNoAndName2(Mapmap); List selectAllEmp(); Map selectEmpByEmpReturnMap(Integer empno); @MapKey("empno") Map getAllEmpReturnMap();}
4、自定义结果集resultMap
Dog.java
package com.courage.bean;public class Dog { private Integer id; private String name; private Integer age; private String gender; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public String toString() { return "Dog{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", gender='" + gender + '\'' + '}'; }}
dog.sql
/*Navicat MySQL Data TransferSource Server : node01Source Server Version : 50729Source Host : 192.168.85.111:3306Source Database : demoTarget Server Type : MYSQLTarget Server Version : 50729File Encoding : 65001Date: 2020-03-24 23:54:22*/SET FOREIGN_KEY_CHECKS=0;-- ------------------------------ Table structure for `dog`-- ----------------------------DROP TABLE IF EXISTS `dog`;CREATE TABLE `dog` ( `id` int(11) NOT NULL AUTO_INCREMENT, `dname` varchar(255) DEFAULT NULL, `dage` int(11) DEFAULT NULL, `dgender` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;-- ------------------------------ Records of dog-- ----------------------------INSERT INTO dog VALUES ('1', '大黄', '1', '雄');INSERT INTO dog VALUES ('2', '二黄', '2', '雌');INSERT INTO dog VALUES ('3', '三黄', '3', '雄');
DogDao.java
package com.courage.dao;import com.courage.bean.Dog;public interface DogDao { public Dog selectDogById(Integer id);}
DogDao.xml
5、联合查询
emp.java
package com.courage.bean;import java.util.Date;public class Emp { private Integer empno; private String ename; private String job; private Integer mgr; private Date hiredate; private Double sal; private Double common; private Dept dept; public Emp() { } public Emp(Integer empno, String ename) { this.empno = empno; this.ename = ename; } public Emp(Integer empno, String ename, String job, Integer mgr, Date hiredate, Double sal, Double common, Dept dept) { this.empno = empno; this.ename = ename; this.job = job; this.mgr = mgr; this.hiredate = hiredate; this.sal = sal; this.common = common; this.dept = dept; } public Integer getEmpno() { return empno; } public void setEmpno(Integer empno) { this.empno = empno; } public String getEname() { return ename; } public void setEname(String ename) { this.ename = ename; } public String getJob() { return job; } public void setJob(String job) { this.job = job; } public Integer getMgr() { return mgr; } public void setMgr(Integer mgr) { this.mgr = mgr; } public Date getHiredate() { return hiredate; } public void setHiredate(Date hiredate) { this.hiredate = hiredate; } public Double getSal() { return sal; } public void setSal(Double sal) { this.sal = sal; } public Double getCommon() { return common; } public void setCommon(Double common) { this.common = common; } public Dept getDept() { return dept; } public void setDept(Dept dept) { this.dept = dept; } @Override public String toString() { return "Emp{" + "empno=" + empno + ", ename='" + ename + '\'' + ", job='" + job + '\'' + ", mgr=" + mgr + ", hiredate=" + hiredate + ", sal=" + sal + ", common=" + common + ", dept=" + dept + '}'; }}
Dept.java
package com.courage.bean;public class Dept { private Integer deptno; private String dname; private String loc; public Dept() { } public Dept(Integer deptno, String dname, String loc) { this.deptno = deptno; this.dname = dname; this.loc = loc; } public Integer getDeptno() { return deptno; } public void setDeptno(Integer deptno) { this.deptno = deptno; } public String getDname() { return dname; } public void setDname(String dname) { this.dname = dname; } public String getLoc() { return loc; } public void setLoc(String loc) { this.loc = loc; } @Override public String toString() { return "Dept{" + "deptno=" + deptno + ", dname='" + dname + '\'' + ", loc='" + loc + '\'' + '}'; }}
EmpDao.xml
Test
@Test public void test08() { // 获取数据库的会话 SqlSession sqlSession = sqlSessionFactory.openSession(); try { EmpDao mapper = sqlSession.getMapper(EmpDao.class); Emp emp = mapper.selectEmpAndDept(7369); System.out.println(emp); } catch (Exception e) { e.printStackTrace(); } finally { sqlSession.close(); } }
6、获取集合元素
Dept.java
package com.courage.bean;import java.util.List;public class Dept { private Integer deptno; private String dname; private String loc; private Listemps; public Dept() { } public Dept(Integer deptno, String dname, String loc) { this.deptno = deptno; this.dname = dname; this.loc = loc; } public Integer getDeptno() { return deptno; } public void setDeptno(Integer deptno) { this.deptno = deptno; } public String getDname() { return dname; } public void setDname(String dname) { this.dname = dname; } public String getLoc() { return loc; } public void setLoc(String loc) { this.loc = loc; } public List getEmps() { return emps; } public void setEmps(List emps) { this.emps = emps; } @Override public String toString() { return "Dept{" + "deptno=" + deptno + ", dname='" + dname + '\'' + ", loc='" + loc + '\'' + ", emps=" + emps + '}'; }}
DeptDao.java
package com.courage.dao;import com.courage.bean.Dept;import com.courage.bean.Emp;import java.util.List;public interface DeptDao { public Dept getDeptAndEmps(Integer deptno);}
DeptDao.xml
Test
@Test public void test09() { // 获取数据库的会话 SqlSession sqlSession = sqlSessionFactory.openSession(); try { DeptDao mapper = sqlSession.getMapper(DeptDao.class); Dept deptAndEmps = mapper.getDeptAndEmps(10); System.out.println(deptAndEmps); } catch (Exception e) { e.printStackTrace(); } finally { sqlSession.close(); } }
7、分步查询
在上述逻辑的查询中,是由我们自己来完成sql语句的关联查询的,那么,我们能让mybatis帮我们实现自动的关联查询吗?
关联查询的分步
DeptDao.java
package com.courage.dao;import com.courage.bean.Dept;import com.courage.bean.Emp;import java.util.List;public interface DeptDao { public Dept getDeptAndEmps(Integer deptno); public Dept getDeptAndEmpsBySimple(Integer deptno);}
EmpDao.java
package com.courage.dao;import com.courage.bean.Emp;import org.apache.ibatis.annotations.MapKey;import org.apache.ibatis.annotations.Param;import java.util.List;import java.util.Map;public interface EmpDao { Emp selectEmpAndDept(Integer empno); Emp selectEmpAndDeptBySimple(Integer empno);}
DeptDao.xml
EmpDao.xml
Test
@Test public void test08() { // 获取数据库的会话 SqlSession sqlSession = sqlSessionFactory.openSession(); try { EmpDao mapper = sqlSession.getMapper(EmpDao.class);// Emp emp = mapper.selectEmpAndDept(7369); Emp emp = mapper.selectEmpAndDeptBySimple(7369); System.out.println(emp); } catch (Exception e) { e.printStackTrace(); } finally { sqlSession.close(); } }
集合的分步查询
EmpDao.java
package com.courage.dao;import com.courage.bean.Emp;import org.apache.ibatis.annotations.MapKey;import org.apache.ibatis.annotations.Param;import java.util.List;import java.util.Map;public interface EmpDao { Emp selectEmpAndDeptBySimple(Integer empno); Emp selectEmpByStep(Integer empno);}
DeptDao.java
package com.courage.dao;import com.courage.bean.Dept;import com.courage.bean.Emp;import java.util.List;public interface DeptDao { public Dept getDeptAndEmps(Integer deptno); public Dept getDeptAndEmpsBySimple(Integer deptno); public Dept getDeptAndEmpsByStep(Integer deptno);}
EmpDao.xml
DeptDao.xml
Test
@Test public void test09() { // 获取数据库的会话 SqlSession sqlSession = sqlSessionFactory.openSession(); try { DeptDao mapper = sqlSession.getMapper(DeptDao.class);// Dept deptAndEmps = mapper.getDeptAndEmps(10); Dept deptAndEmpsByStep = mapper.getDeptAndEmpsByStep(10); System.out.println(deptAndEmpsByStep); } catch (Exception e) { e.printStackTrace(); } finally { sqlSession.close(); } }
8、延迟查询
当我们在进行表关联的时候,有可能在查询结果的时候不需要关联对象的属性值,那么此时可以通过延迟加载来实现功能。在全局配置文件中添加如下属性
mybatis-config.xml
如果设置了全局加载,但是希望在某一个sql语句查询的时候不适用延时策略,可以添加如下属性:
三、动态SQL
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
1、if
EmpDao.xml
EmpDao.java
public ListgetEmpByCondition(Emp emp);
Test.java
@Test public void test10() { SqlSession sqlSession = sqlSessionFactory.openSession(); try { EmpDao mapper = sqlSession.getMapper(EmpDao.class); Emp emp = new Emp(); emp.setEmpno(6500); emp.setEname("%E%"); emp.setSal(500.0); ListempByCondition = mapper.getEmpByCondition(emp); for (Emp emp1 : empByCondition) { System.out.println(emp1); } } catch (Exception e) { e.printStackTrace(); } finally { sqlSession.close(); } }
看起来测试是比较正常的,但是大家需要注意的是如果我们传入的参数值有缺失会怎么呢?这个时候拼接的sql语句就会变得有问题,例如不传参数或者丢失最后一个参数,那么语句中就会多一个where或者and的关键字,因此在mybatis中也给出了具体的解决方案:
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
现在看起来没有什么问题了,但是我们的条件添加到了拼接sql语句的前后,那么我们该如何处理呢?
2、foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。
3、choose
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
4、set
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。
update emp empno=#{empno}, ename = #{ename}, sal = #{sal} empno = #{empno}
四、缓存
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
当添加上该标签之后,会有如下效果:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
在进行配置的时候还会分为一级缓存和二级缓存:
一级缓存:线程级别的缓存,是本地缓存,sqlSession级别的缓存
二级缓存:全局范围的缓存,不止局限于当前会话
1、一级缓存的使用
一级缓存是sqlsession级别的缓存,默认是存在的。在下面的案例中,大家发现我发送了两个相同的请求,但是sql语句仅仅执行了一次,那么就意味着第一次查询的时候已经将结果进行了缓存。
@Test public void test01() { SqlSession sqlSession = sqlSessionFactory.openSession(); try { EmpDao mapper = sqlSession.getMapper(EmpDao.class); Listlist = mapper.selectAllEmp(); for (Emp emp : list) { System.out.println(emp); } System.out.println("--------------------------------"); List list2 = mapper.selectAllEmp(); for (Emp emp : list2) { System.out.println(emp); } } catch (Exception e) { e.printStackTrace(); } finally { sqlSession.close(); } }
在大部分的情况下一级缓存是可以的,但是有几种特殊的情况会造成一级缓存失效:
1、一级缓存是sqlSession级别的缓存,如果在应用程序中只有开启了多个sqlsession,那么会造成缓存失效
@Test public void test02(){ SqlSession sqlSession = sqlSessionFactory.openSession(); EmpDao mapper = sqlSession.getMapper(EmpDao.class); Listlist = mapper.selectAllEmp(); for (Emp emp : list) { System.out.println(emp); } System.out.println("================================"); SqlSession sqlSession2 = sqlSessionFactory.openSession(); EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class); List list2 = mapper2.selectAllEmp(); for (Emp emp : list2) { System.out.println(emp); } sqlSession.close(); sqlSession2.close(); }
2、在编写查询的sql语句的时候,一定要注意传递的参数,如果参数不一致,那么也不会缓存结果
3、如果在发送过程中发生了数据的修改,那么结果就不会缓存
@Test public void test03(){ SqlSession sqlSession = sqlSessionFactory.openSession(); EmpDao mapper = sqlSession.getMapper(EmpDao.class); Emp empByEmpno = mapper.findEmpByEmpno(1111); System.out.println(empByEmpno); System.out.println("================================"); empByEmpno.setEname("zhangsan"); int i = mapper.updateEmp(empByEmpno); System.out.println(i); System.out.println("================================"); Emp empByEmpno1 = mapper.findEmpByEmpno(1111); System.out.println(empByEmpno1); sqlSession.close(); }
4、在两次查询期间,手动去清空缓存,也会让缓存失效
@Test public void test03(){ SqlSession sqlSession = sqlSessionFactory.openSession(); EmpDao mapper = sqlSession.getMapper(EmpDao.class); Emp empByEmpno = mapper.findEmpByEmpno(1111); System.out.println(empByEmpno); System.out.println("================================"); System.out.println("手动清空缓存"); sqlSession.clearCache(); System.out.println("================================"); Emp empByEmpno1 = mapper.findEmpByEmpno(1111); System.out.println(empByEmpno1); sqlSession.close(); }
2、二级缓存
二级缓存是全局作用域缓存,默认是不开启的,需要手动进行配置。
Mybatis提供二级缓存的接口以及实现,缓存实现的时候要求实体类实现Serializable接口,二级缓存在sqlSession关闭或提交之后才会生效。
a、缓存的使用
步骤:
1、全局配置文件中添加如下配置:
2、需要在使用二级缓存的映射文件出使用
3、实体类必须要实现Serializable接口
@Test public void test04(){ SqlSession sqlSession = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); EmpDao mapper = sqlSession.getMapper(EmpDao.class); EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class); Emp empByEmpno = mapper.findEmpByEmpno(1111); System.out.println(empByEmpno); sqlSession.close(); Emp empByEmpno1 = mapper2.findEmpByEmpno(1111); System.out.println(empByEmpno1); sqlSession2.close(); }
b、缓存的属性
eviction:表示缓存回收策略,默认是LRU
LRU:最近最少使用的,移除最长时间不被使用的对象
FIFO:先进先出,按照对象进入缓存的顺序来移除
SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象
flushInternal:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
size:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出
readonly:只读,true/false
true:只读缓存,会给所有调用这返回缓存对象的相同实例,因此这些对象不能被修改。
false:读写缓存,会返回缓存对象的拷贝(序列化实现),这种方式比较安全,默认值
//可以看到会去二级缓存中查找数据,而且二级缓存跟一级缓存中不会同时存在数据,因为二级缓存中的数据是在sqlsession 关闭之后才生效的 @Test public void test05(){ SqlSession sqlSession = sqlSessionFactory.openSession(); EmpDao mapper = sqlSession.getMapper(EmpDao.class); Emp empByEmpno = mapper.findEmpByEmpno(1111); System.out.println(empByEmpno); sqlSession.close(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class); Emp empByEmpno2 = mapper2.findEmpByEmpno(1111); System.out.println(empByEmpno2); Emp empByEmpno3 = mapper2.findEmpByEmpno(1111); System.out.println(empByEmpno3); sqlSession2.close(); } // 缓存查询的顺序是先查询二级缓存再查询一级缓存 @Test public void test05(){ SqlSession sqlSession = sqlSessionFactory.openSession(); EmpDao mapper = sqlSession.getMapper(EmpDao.class); Emp empByEmpno = mapper.findEmpByEmpno(1111); System.out.println(empByEmpno); sqlSession.close(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class); Emp empByEmpno2 = mapper2.findEmpByEmpno(1111); System.out.println(empByEmpno2); Emp empByEmpno3 = mapper2.findEmpByEmpno(1111); System.out.println(empByEmpno3); Emp empByEmpno4 = mapper2.findEmpByEmpno(7369); System.out.println(empByEmpno4); Emp empByEmpno5 = mapper2.findEmpByEmpno(7369); System.out.println(empByEmpno5); sqlSession2.close(); }
3、二级缓存的作用范围:
如果设置了全局的二级缓存配置,那么在使用的时候需要注意,在每一个单独的select语句中,可以设置将查询缓存关闭,以完成特殊的设置
1、在setting中设置,是配置二级缓存开启,一级缓存默认一直开启
2、select标签的useCache属性:
在每一个select的查询中可以设置当前查询是否要使用二级缓存,只对二级缓存有效
3、sql标签的flushCache属性
增删改操作默认值为true,sql执行之后会清空一级缓存和二级缓存,而查询操作默认是false
4、sqlSession.clearCache()
只是用来清楚一级缓存
3、整合第三方缓存
在某些情况下我们也可以自定义实现缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。
1、导入对应的maven依赖
org.ehcache ehcache 3.8.1 org.mybatis.caches mybatis-ehcache 1.2.0 org.slf4j slf4j-api 2.0.0-alpha1 org.slf4j slf4j-log4j12 2.0.0-alpha1 test
2、导入ehcache配置文件
3、在mapper文件中添加自定义缓存
发表评论
最新留言
关于作者
