## 一、动态SQL #### 1、if+where标签 当表中有多个字段可以查询时,有很多时候都会选择不同字段组合在一起进行查询,若一条一条写会很麻烦,所以可以使用MyBaits提供的动态SQL语句来完成SQL语句的拼接。 ``标签可以判断查询语句后是否有约束条件来自动处理查询的`WHERE`部分,在``标签下,有``标签可以通过`test`属性中的条件语句来判断是否满足添加约束条件,``标签可以自动处理是否在语句连接部分是否保留`and`语句,所以每条查询约束条件前都要加`and`。 ##### 多条件动态查询 在`EmpMapper.java`接口中添加 ```java // 使用动态SQL:if+where标签 List listEmpByCondition(Emp emp); ``` 在`EmpMapper.xml`映射文件中添加 ```xml select * from emp and emp.ename like concat('%',#{ename},'%') and emp.job like concat('%', #{job}, '%') and emp.mgr=#{mgr} and emp.hiredate=#{hiredate} and emp.sal=#{sal} and emp.comm=#{comm} and emp.DEPTno=#{DEPTno} order by emp.EMPno; ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = new Emp(); emp.setJob("MAN"); emp.setDEPTno(30); List list = empMapper.listEmpByCondition(emp); for (Emp e : list) { System.out.println(e); } sqlSession.close(); ``` #### 2、choose标签 ``标签的作用也是进行条件判断,类似于``,但不同的是他相当于`switch-case-default`语句,在``标签下可以有多个``标签,其属性`test`来写条件语句,但只会执行第一个满足条件的``标签,若所有语句都不满足,可以通过``标签来添加约束条件。 ##### 单条件动态查询 在`EmpMapper.java`接口中添加 ```java // 使用动态SQL:Choose标签 List listEmpWithChooseByCondition(Emp emp); ``` 在`EmpMapper.xml`映射文件中添加 ```xml select * from emp and emp.ename like concat('%',#{ename},'%') and emp.job like concat('%', #{job}, '%') and emp.mgr=#{mgr} and emp.hiredate=#{hiredate} and emp.sal=#{sal} and emp.comm=#{comm} and emp.DEPTno=#{DEPTno} order by emp.EMPno; ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = new Emp(); emp.setJob("MAN"); // 此时只会添加job的约束条件 emp.setDEPTno(30); List list = empMapper.listEmpWithChooseByCondition(emp); for (Emp e : list) { System.out.println(e); } sqlSession.close(); ``` #### 3、trim标签 可以在SQL语句中添加或删除特定的前缀`prefix`或后缀`suffix`,以及去除SQL语句前后不必要的关键字或字符,如多余的"AND"、"OR"或逗号。`prefixOverrides`: 去除SQL语句前面的指定关键字或字符,`suffixOverrides`: 去除SQL语句后面的指定关键字或字符。 ##### 多条件动态保存 在`EmpMapper.java`接口中添加 ```java // 使用动态SQL:trim标签 int saveEmp(Emp emp); ``` 在`EmpMapper.xml`映射文件中添加 **注意**:`if`标签前加的字段表示必填字段,不经过条件判断也会默认添加。 ```xml insert into emp ename,hiredate,DEPTno, job, mgr, sal, comm, #{ename},#{hiredate},#{DEPTno}, #{job}, #{mgr}, #{sal}, #{comm}, ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = new Emp(); emp.setEname("熊大"); emp.setHiredate(Util.getNewDate()); emp.setDEPTno(20); emp.setJob("ENGINEER"); System.out.println(empMapper.saveEmp(emp)); // 增删改手动提交 sqlSession.commit(); sqlSession.close(); ``` #### 4、set标签 主要用于`update`更新操作,当一次性需要更新一条字段或多条字段时,通过``标签来实现哪些字段被更新。 ##### 多条件动态更新 在`EmpMapper.java`接口中添加 ```java // 使用动态SQL:set标签 int updateEmpById(Emp emp); ``` 在`EmpMapper.xml`映射文件中添加 ```xml update emp emp.ename=#{ename}, emp.job=#{job}, emp.mgr=#{mgr}, emp.hiredate=#{hiredate}, emp.sal=#{sal}, emp.comm=#{comm}, emp.DEPTno=#{DEPTno}, where emp.EMPno = #{EMPno}; ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = new Emp(); emp.setEMPno(8004); emp.setSal(3000.0); System.out.println(empMapper.updateEmpById(emp)); // 增删改手动提交 sqlSession.commit(); sqlSession.close(); ``` **注意**:更新的数据不能全为`null`否则会出现SQL语句错误异常。 #### 5、foreach标签 ``标签可以在SQL语句中遍历一个数组或集合,主要用于`in`后的拼接,进行批量操作。 ##### 通过EMPno批量删除 - 使用数组 在`EmpMapper.java`接口中添加 ```java // 使用动态SQL:foreach标签 - 数组 int deleteEmpById(Integer[] ids); ``` 在`EmpMapper.xml`映射文件中添加 ```xml delete from emp where emp.EMPno in #{id} ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); System.out.println(empMapper.deleteEmpById(new Integer[]{8004, 8005})); // 增删改手动提交 sqlSession.commit(); sqlSession.close(); ``` ##### 通过EMPno批量删除 - 使用集合 在`EmpMapper.java`接口中添加 ```java // 使用动态SQL:foreach标签 - 集合 int deleteEmpById(List empList); ``` 在`EmpMapper.xml`映射文件中添加 ```xml delete from emp where emp.EMPno in #{emp.EMPno} ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List list = new ArrayList<>(); Emp emp1 = new Emp(); Emp emp2 = new Emp(); emp1.setEMPno(8000); emp2.setEMPno(8002); list.add(emp1); list.add(emp2); System.out.println(empMapper.deleteEmpById(list)); // 增删改手动提交 sqlSession.commit(); sqlSession.close(); ``` ## 二、ResultMap的使用 当数据库表中字段名与其对应的持久化对象的属性名不一致时,需要通过写``来实现一一对应,在``中 `type`属性表示其对应的持久化对象,``标签为表主键标签,``标签为表其他字段标签,标签中`property`属性表示持久化对象的属性名,`column`属性表示字段名称。 在`EmpMapper.java`接口中添加 ```java // 使用ResultMap全查询 List listEmpAll(); ``` 在`EmpMapper.xml`映射文件中添加 ```xml select emp.EMPno EMPno_0, emp.ename ename_0, emp.job job_0, emp.mgr mgr_0, emp.hiredate hiredate_0, emp.sal sal_0, emp.comm comm_0, emp.DEPTno DEPTno_0 from emp ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List list = empMapper.listEmpAll(); for (Emp e : list) { System.out.println(e); } sqlSession.close(); ``` ## 三、关联查询 在MySQL数据库中表的关系有一对一、多对一、一对多、多对多。 #### 1、多表连接形式 ##### 多对一关联查询 创建dept表的实体对象类 在`po`持久层对象包下新建`Dept.java`类 ```java package com.doooog.mybatis.po; 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 + '\'' + '}'; } } ``` 因为`emp`与`dept`时多对一的关系,多个`emp`记录对应一个`dept`记录,所以在`emp.java`类中添加新的属性,并创建`Getter&Setter`,修改全参`构造`方法,修改`toString()`方法 ```java private Dept dept; public Dept getDept() { return dept; } public void setDept(Dept dept) { this.dept = dept; } public Emp(Integer EMPno, String ename, String job, Integer mgr, String hiredate, Double sal, Double comm, Integer DEPTno) { this.EMPno = EMPno; this.ename = ename; this.job = job; this.mgr = mgr; this.hiredate = hiredate; this.sal = sal; this.comm = comm; this.DEPTno = DEPTno; } @Override public String toString() { return "Emp{" + "EMPno=" + EMPno + ", ename='" + ename + '\'' + ", job='" + job + '\'' + ", mgr=" + mgr + ", hiredate='" + hiredate + '\'' + ", sal=" + sal + ", comm=" + comm + ", DEPTno=" + DEPTno + (dept!=null ? ", dept=" + dept : "") + '}'; } ``` 在`EmpMapper.java`接口中添加 ```java // 多对一关联查询 Emp getEmpById(Integer EMPno); ``` 在`EmpMapper.xml`映射文件中添加 ```xml select e.*,d.DEPTno dDEPTno,d.dname dname,d.loc loc from emp e left join dept d on e.DEPTno=d.DEPTno where e.EMPno=#{empno} ``` **注意**:在这里因为`Emp`的属性`dept`是一个`Dept`对象,所以如果只在``标签下填写`resultType=Emp`的话将无法映射,因为字段名无法与对象名进行一一对应,所以要通过**二、ResultMap的使用**来建立一个``来写映射关系,其中``标签专门用来处理单个对象属性,其`javaType`属性须填写对象属性的对象类型,同样的在此标签下``映射主键,``映射其他字段,其中`property`属性需填写对象属性的属性名,`column`属性填写``标签下SQL语句里给表字段起的`别名`。 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = empMapper.getEmpById(8003); System.out.println(emp); Dept dept = emp.getDept(); System.out.println(dept); sqlSession.close(); ``` ##### 一对多的关联查询 修改`Dept.java`类,因为是一对多,所以添加一个集合属性来存放部门中所有的`Emp`,创建`Getter&Setter`方法,修改全参`构造`方法,修改`toString()`方法 ```java private List empList; public Dept(Integer DEPTno, String dname, String loc, List empList) { this.DEPTno = DEPTno; this.dname = dname; this.loc = loc; this.empList = empList; } public List getEmpList() { return empList; } public void setEmpList(List empList) { this.empList = empList; } @Override public String toString() { return "Dept{" + "DEPTno=" + DEPTno + ", dname='" + dname + '\'' + ", loc='" + loc + '\'' + (empList != null && !empList.isEmpty() ? ", empList=" + empList : "") + '}'; } ``` 在`Mapper`包下创建`DeptMapper.java`接口 ```java package com.doooog.mybatis.mapper; import com.doooog.mybatis.po.Dept; public interface DeptMapper { // 一对多关联查询 Dept getDeptById(Integer DEPTno); } ``` 在`Mapper`资源目录下创建`DeptMapper.xml`映射文件 ```xml select d.*, e.EMPno EMPno, e.ename ename, e.job job, e.mgr mgr, e.hiredate hiredate, e.sal sal, e.comm comm, e.DEPTno eDEPTno from dept d left join emp e on d.DEPTno = e.DEPTno where d.DEPTno=#{DEPTno} ``` **注意**:在这里因为`DEPT`的属性`empList`是一个`Emp`对象集合,所以如果只在``标签下填写`resultType=Dept`的话将无法映射,因为字段名无法与对象名进行一一对应,所以要通过**二、ResultMap的使用**来建立一个``来写映射关系,其中与之不同的是需要用到``标签,专门用来处理`多个对象集合`属性,其`ofType`属性须填写对象属性的集合范型,同样的在此标签下``映射主键,``映射其他字段,其中`property`属性需填写对象属性的属性名,`column`属性填写``标签下SQL语句里给表字段起的`别名`。 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); Dept dept = deptMapper.getDeptById(10); System.out.println(dept); for (Emp e : dept.getEmpList()) { System.out.println(e); } sqlSeesion.close(); ``` ##### 双向一对多关联查询 **`emp`表的全查询** 在`EmpMapper.java`接口中添加 ```java // 双向一对多关联查询 List getEmpAll(); ``` 在`EmpMapper.xml`映射文件中添加 ```xml select e.*, d.DEPTno dDEPTno, d.dname dname, d.loc loc from emp e left join dept d on e.DEPTno = d.DEPTno ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List list = empMapper.getEmpAll(); for (Emp e : list){ System.out.println(e); System.out.println(e.getDept()); } sqlSession.close(); ``` **`dept`表的全查询** 在`DeptMapper.java`接口中添加 ```java // 双向一对多关联查询 List getDeptAll(); ``` 在`DeptMapper.xml`映射文件中添加 ```xml select d.*, e.EMPno, e.ename, e.job, e.mgr, e.hiredate, e.sal, e.comm, e.DEPTno eDEPTno from dept d left join emp e on d.DEPTno = e.DEPTno ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); List list = deptMapper.getDeptAll(); for (Dept d : list){ System.out.println(d); for (Emp e : d.getEmpList()){ System.out.println(e); } } sqlSession.close(); ``` #### 2、单独查询形式 ##### 多对一关联查询 在`EmpMapper.java`接口中添加 ```java // 多对一单独查询形式 Emp getEmpById(Integer EMPno); ``` 在`DeptMapper.xml`映射文件中添加单独为单独查询形式准备的`dept查询` ```xml select * from dept where dept.DEPTno = #{DEPTno} ``` 在`EmpMapper.xml`映射文件中添加 ```xml select * from emp where emp.EMPno = #{empno} ``` **注意**:因为不是多表联合查询语句所以不需要详细的去写``标签下的对应关系,而是添加`select`属性,值为上一步在`DeptMapper.xml`中添加的专为单独查询而写的SQL查询`id`,需要把路径写全,`column`属性是作为参数传入嵌套的查询里。 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = empMapper.getEmpById(8003); System.out.println(emp); Dept dept = emp.getDept(); System.out.println(dept); sqlSession.close(); ``` ##### 一对多关联查询 在`DeptMapper.java`接口中添加 ```java // 多对一单独查询形式 Dept getDeptById(Integer DEPTno); ``` 在`EmpMapper.xml`映射文件中添加单独为单独查询形式准备的`emp查询` ```xml select * from emp where emp.DEPTno = #{DEPTno} ``` 在`DeptMapper.xml`映射文件中添加 ```xml select * from dept where dept.DEPTno = #{DEPTno} ``` **注意**:因为不是多表联合查询语句所以不需要详细的去写``标签下的对应关系,而是添加`select`属性,值为上一步在`EmpMapper.xml`中添加的专为单独查询而写的SQL查询`id`,需要把路径写全,`column`属性是作为参数传入嵌套的查询里。 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); Dept dept = deptMapper.getDeptById(20); System.out.println(dept); for (Emp e : dept.getEmpList()){ System.out.println(e); } sqlSession.close(); ``` ## 四、关联查询延迟加载 执行查询时关联的查询不会立即加载,只有当使用到关联查询里的数据时才会加载关联查询,延迟加载只能用于`单独查询形式`的查询语句。 若要开启延迟加载,需要在MyBatis配置文件`sqlMapConfig.xml`里添加``标签,添加位置要在``标签之后,在``标签之前。 ```xml ``` ## 五、MyBatis缓存机制 MyBatis 的缓存机制是提升数据库查询性能的核心设计,它通过减少重复查询的数据库访问次数来优化性能,分为 **一级缓存** 和 **二级缓存**。 #### 1、一级缓存 **生命周期**:与单个 `SqlSession` 绑定(数据库会话级),默认开启。 **行为规则**: 在同一个`SqlSession`中,多次调用 相同查询方法(参数一致、Statement ID相同)会直接取缓存结果。 执行 `commit()`、`rollback()`、`close()` 或 `增删改`操作 时,缓存会被清空。 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); // 第一次查询,访问数据库 Dept dept1 = deptMapper.getDeptById(20); // 第二次查询(参数相同),直接取缓存 Dept dept2 = deptMapper.getDeptById(20); // dept1 == dept2,结果为 true(同一个对象引用) System.out.println(dept1==dept2); sqlSession.commit(); // 清空缓存(或执行增删改操作) // 第三次查询,需重新访问数据库 Dept dept3 = deptMapper.getDeptById(20); // dept1 == dept3,结果为 false System.out.println(dept1==dept3); sqlSession.close(); ``` **注意**: 若两次查询参数不同(如不同 ID),或 SQL 语句不同(如不同的 Statement ID),则不命中缓存。 可通过``关闭一级缓存(使每次查询都重新执行 SQL)。 #### 2、二级缓存 **生命周期**:与 `Mapper` 绑定(命名空间级),需手动开启。 **行为规则**: 跨 `SqlSession` 共享缓存(相同 `Mapper` 的查询结果可被多个会话访问)。 事务提交(`commit()`)或会话关闭(`close()`)后,查询结果才会存入二级缓存。 ##### 开启二级缓存 在`sqlMapConfig.xml`中添加 ```xml ``` 并在每个`*Mapper.xml`映射文件中``标签第一行加``标签,表示该`Mapper`需要进行缓存 在每个持久层对象的类中需要实现`Serializable`接口,使其对象可以序列化。 ```java SqlSession sqlSession1 = Util.getSqlSessionFactory().openSession(); DeptMapper deptMapper1 = sqlSession.getMapper(DeptMapper.class); Dept dept1 = deptMapper1.getDeptById(20); sqlSession1.close(); // 关闭后,结果存入二级缓存 SqlSession sqlSession2 = Util.getSqlSessionFactory().openSession(); DeptMapper deptMapper2 = sqlSession.getMapper(DeptMapper.class); // 命中二级缓存(无需查询数据库)需要查看日志是否执行查询语句来印证 Dept dept2 = deptMapper2.getDeptById(20); sqlSession2.close(); ``` **注意**: 更新操作(增删改)会清空 对应 `Mapper` 的二级缓存(若配置了 ``)。 二级缓存不能保证数据的时效性,若数据频繁更新,请勿使用二级缓存。 ## 六、使用注解实现MyBatis映射 MyBatis提供了四种注解`@Select`、`@Updata`、`@Insert`、`@Delete`来在接口中实现增删改查,在使用注解进行映射时,不需要写`*Mapper.xml`的映射文件,直接在`*Mapper.java`中进行书写SQL语句即可。 #### 1、CRUD操作 ##### 通过主键查询单个Emp 在`EmpMapper.java`接口中添加 ```java // 注解CRUD操作:查询员工信息 @Select("select * from emp where emp.EMPno=#{EMPno}") Emp getEmpById(Integer EMPno); ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = empMapper.getEmpById(8003); System.out.println(emp); sqlSession.close(); ``` ##### emp表全查询 在`EmpMapper.java`接口中添加 ```java // 注解CRUD操作:查询员工表 @Select("select * from emp ORDER BY emp.EMPno") List listEmpAll(); ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List list = empMapper.listEmpAll(); for (Emp e : list) { System.out.println(e); } sqlSession.close(); ``` ##### 通过主键修改emp姓名 在`EmpMapper.java`接口中添加 ```java // 注解CRUD操作:更新员工姓名 @Update("update emp set emp.ename=#{ename} where EMPno=#{EMPno}") int updateEmpNameById(Emp emp); ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp1 = new Emp(); emp1.setEMPno(8003); emp1.setEname("刘六"); System.out.println(empMapper.updateEmpNameById(emp1)); sqlSession.commit(); sqlSession.close(); ``` #### 2、多对一操作 在`DeptMapper.java`接口中添加Dept延迟查询方法 ```java // 注解多对一延迟查询Dept @Select("select * from dept where DEPTno=#{DEPTno}") Dept getDeptByIdLazy(Integer DEPTno); ``` 在`EmpMapper.java`接口中添加查询方法 ```java // 注解一对多操作 @Select("select * from emp where emp.EMPno=#{EMPno}") @Results({ @Result(id = true, property = "EMPno", column = "EMPno"), @Result(property = "ename", column = "ename"), @Result(property = "job", column = "job"), @Result(property = "mgr", column = "mgr"), @Result(property = "hiredate", column = "hiredate"), @Result(property = "sal", column = "sal"), @Result(property = "comm", column = "comm"), @Result(property = "DEPTno", column = "DEPTno"), @Result(property = "dept", column = "DEPTno", javaType = Dept.class, one = @One(select = "com.doooog.mybatis.mapper.DeptMapper.getDeptByIdLazy")) }) Emp getEmpById(Integer EMPno); ``` 在进行多对一、一对多操作时,只需添加`@Results`、`@Result`注解即可,写法类似于在映射文件中的``、``标签,若该字段为主键则添加`id = true`,在多对一时须添加`one=@one()`,`select`属性中填上一步写的方法名的全路径。 在测试类内调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = empMapper.getEmpById(8003); System.out.println(emp); sqlSession.close(); ``` #### 3、一对多操作 在`EmpMapper.java`接口中添加Emp外键延迟查询方法 ```java // 注解一对多延迟查询Emp @Select("select * from emp where emp.DEPTno=#{DEPTno}") List getEmpByDEPTnoLazy(Integer EMPno); ``` 在`DeptMapper.java`接口中添加查询方法 ```java // 注解一对多操作 @Select("select * from dept where DEPTno=#{DEPTno}") @Results({ @Result(id = true, property = "DEPTno", column = "DEPTno"), @Result(property = "dname", column = "dname"), @Result(property = "loc", column = "loc"), @Result(property = "empList", column = "DEPTno", javaType = List.class, many = @Many(select = "com.doooog.mybatis.mapper.EmpMapper.getEmpByDEPTnoLazy")) }) Dept getDeptById(Integer DEPTno); ``` 在进行一对多时,映射数组或集合属性的注解内的`javaType`要填写数组或集合的`.class`,并且使用`many=@Many()`来实现数组或集合的创建,`select`属性中填上一步写的方法名的全路径。 在测试类内调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); Dept dept = deptMapper.getDeptById(20); System.out.println(dept); for (Emp e : dept.getEmpList()) { System.out.println(e); } sqlSession.close(); ``` #### 4、使用注解进行动态SQL ##### 使用`@SelectProvider` 创建`com.doooog.mybatis.sqlprovider`包,用来存放`*SqlProvider.java`类 在此包下创建`EmpSqlProvider.java`类 ```java package com.doooog.mybatis.sqlprovider; import com.doooog.mybatis.po.Emp; import org.apache.ibatis.jdbc.SQL; public class EmpSqlProvider { public String listEmpByConditionProvider(Emp emp){ return new SQL(){{ SELECT("*"); FROM("emp"); if (emp.getEMPno()!=null) WHERE("emp.EMPno = #{EMPno}"); if (emp.getEname()!=null && !emp.getEname().isEmpty()) WHERE("emp.ename like concat('%',#{ename},'%')"); if (emp.getJob()!=null && !emp.getJob().isEmpty()) WHERE("emp.job like concat('%',#{job},'%')"); if (emp.getMgr()!=null) WHERE("emp.mgr = #{mgr}"); if (emp.getSal()!=null) WHERE("emp.sal = #{sal}"); if (emp.getComm()!=null) WHERE("emp.comm = #{comm}"); if (emp.getHiredate()!=null && !emp.getHiredate().isEmpty()) WHERE("emp.hiredate = #{hiredate}"); if (emp.getDEPTno()!=null) WHERE("emp.DEPTno = #{DEPTno}"); }}.toString(); } } ``` 使用 SQL 构建器(MyBatis 内置类)动态拼接 SQL。 通过条件判断(如 if 语句)动态生成 SET 或 WHERE 子句。 **注意**:方法参数与 Mapper 接口方法参数类型一致(如示例中的 Emp emp)。 在`EmpMapper.java`接口中添加 ```java // 使用注解进行动态SQL查询 - SqlProvider @SelectProvider(type = DeptSqlProvider.class,method = "listDeptByConditionProvider") @Results({ @Result(id = true,property = "DEPTno",column = "DEPTno"), @Result(property = "dname",column = "dname"), @Result(property = "loc",column = "loc"), @Result(property = "empList",column = "DEPTno",javaType = List.class,many = @Many(select = "com.doooog.mybatis.mapper.EmpMapper.getEmpByDEPTnoLazy")) }) List listDeptByCondition(Dept dept); ``` 使用`@SelectProvider`注解创建Select查询,`type`属性为`*SqlProvider.class`,`method`属性为`*SqlProvider`类中所用到的`方法名` 若有联合查询的需求,可以使用`@Results`、`@Result`注解进行设计ResultMap,使用方法同上面的第2、3条。 在测试类内调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); Emp emp = new Emp(); emp.setEname("S"); emp.setJob("S"); List list = empMapper.listEmpByCondition(emp); for (Emp e : list) { System.out.println(e); } sqlSession.close() ``` Loading... ## 一、动态SQL #### 1、if+where标签 当表中有多个字段可以查询时,有很多时候都会选择不同字段组合在一起进行查询,若一条一条写会很麻烦,所以可以使用MyBaits提供的动态SQL语句来完成SQL语句的拼接。 `<where>`标签可以判断查询语句后是否有约束条件来自动处理查询的`WHERE`部分,在`<where>`标签下,有`<if>`标签可以通过`test`属性中的条件语句来判断是否满足添加约束条件,`<if>`标签可以自动处理是否在语句连接部分是否保留`and`语句,所以每条查询约束条件前都要加`and`。 ##### 多条件动态查询 在`EmpMapper.java`接口中添加 ```java // 使用动态SQL:if+where标签 List<Emp> listEmpByCondition(Emp emp); ``` 在`EmpMapper.xml`映射文件中添加 ```xml <select id="listEmpByCondition" parameterType="Emp" resultType="Emp"> select * from emp <where> <if test="ename!=null and ename!=''"> and emp.ename like concat('%',#{ename},'%') </if> <if test="job!=null and job!=''"> and emp.job like concat('%', #{job}, '%') </if> <if test="mgr!=null"> and emp.mgr=#{mgr} </if> <if test="hiredate!=null and hiredate!=''"> and emp.hiredate=#{hiredate} </if> <if test="sal!=null"> and emp.sal=#{sal} </if> <if test="comm!=null"> and emp.comm=#{comm} </if> <if test="DEPTno!=null"> and emp.DEPTno=#{DEPTno} </if> </where> order by emp.EMPno; </select> ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = new Emp(); emp.setJob("MAN"); emp.setDEPTno(30); List<Emp> list = empMapper.listEmpByCondition(emp); for (Emp e : list) { System.out.println(e); } sqlSession.close(); ``` #### 2、choose标签 `<choose>`标签的作用也是进行条件判断,类似于`<if>`,但不同的是他相当于`switch-case-default`语句,在`<choose>`标签下可以有多个`<when>`标签,其属性`test`来写条件语句,但只会执行第一个满足条件的`<when>`标签,若所有语句都不满足,可以通过`<otherwise>`标签来添加约束条件。 ##### 单条件动态查询 在`EmpMapper.java`接口中添加 ```java // 使用动态SQL:Choose标签 List<Emp> listEmpWithChooseByCondition(Emp emp); ``` 在`EmpMapper.xml`映射文件中添加 ```xml <select id="listEmpWithChooseByCondition" parameterType="Emp" resultType="Emp"> select * from emp <where> <choose> <when test="ename!=null and ename!=''"> and emp.ename like concat('%',#{ename},'%') </when> <when test="job!=null and job!=''"> and emp.job like concat('%', #{job}, '%') </when> <when test="mgr!=null"> and emp.mgr=#{mgr} </when> <when test="hiredate!=null and hiredate!=''"> and emp.hiredate=#{hiredate} </when> <when test="sal!=null"> and emp.sal=#{sal} </when> <when test="comm!=null"> and emp.comm=#{comm} </when> <when test="DEPTno!=null"> and emp.DEPTno=#{DEPTno} </when> <otherwise> <!-- default --> </otherwise> </choose> </where> order by emp.EMPno; </select> ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = new Emp(); emp.setJob("MAN"); // 此时只会添加job的约束条件 emp.setDEPTno(30); List<Emp> list = empMapper.listEmpWithChooseByCondition(emp); for (Emp e : list) { System.out.println(e); } sqlSession.close(); ``` #### 3、trim标签 可以在SQL语句中添加或删除特定的前缀`prefix`或后缀`suffix`,以及去除SQL语句前后不必要的关键字或字符,如多余的"AND"、"OR"或逗号。`prefixOverrides`: 去除SQL语句前面的指定关键字或字符,`suffixOverrides`: 去除SQL语句后面的指定关键字或字符。 ##### 多条件动态保存 在`EmpMapper.java`接口中添加 ```java // 使用动态SQL:trim标签 int saveEmp(Emp emp); ``` 在`EmpMapper.xml`映射文件中添加 **注意**:`if`标签前加的字段表示必填字段,不经过条件判断也会默认添加。 ```xml <insert id="saveEmp" parameterType="Emp" useGeneratedKeys="true" keyProperty="EMPno"> insert into emp <trim prefix="(" suffix=")" suffixOverrides=","> ename,hiredate,DEPTno, <if test="job!=null and job!=''"> job, </if> <if test="mgr!=null"> mgr, </if> <if test="sal!=null"> sal, </if> <if test="comm!=null"> comm, </if> </trim> <trim prefix="values(" suffix=")" suffixOverrides=","> #{ename},#{hiredate},#{DEPTno}, <if test="job!=null and job!=''"> #{job}, </if> <if test="mgr!=null"> #{mgr}, </if> <if test="sal!=null"> #{sal}, </if> <if test="comm!=null"> #{comm}, </if> </trim> </insert> ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = new Emp(); emp.setEname("熊大"); emp.setHiredate(Util.getNewDate()); emp.setDEPTno(20); emp.setJob("ENGINEER"); System.out.println(empMapper.saveEmp(emp)); // 增删改手动提交 sqlSession.commit(); sqlSession.close(); ``` #### 4、set标签 主要用于`update`更新操作,当一次性需要更新一条字段或多条字段时,通过`<if>`标签来实现哪些字段被更新。 ##### 多条件动态更新 在`EmpMapper.java`接口中添加 ```java // 使用动态SQL:set标签 int updateEmpById(Emp emp); ``` 在`EmpMapper.xml`映射文件中添加 ```xml <update id="updateEmpById" parameterType="Emp"> update emp <set> <if test="ename!=null and ename!=''"> emp.ename=#{ename}, </if> <if test="job!=null and job!=''"> emp.job=#{job}, </if> <if test="mgr!=null"> emp.mgr=#{mgr}, </if> <if test="hiredate!=null and hiredate!=''"> emp.hiredate=#{hiredate}, </if> <if test="sal!=null"> emp.sal=#{sal}, </if> <if test="comm!=null"> emp.comm=#{comm}, </if> <if test="DEPTno!=null"> emp.DEPTno=#{DEPTno}, </if> </set> where emp.EMPno = #{EMPno}; </update> ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = new Emp(); emp.setEMPno(8004); emp.setSal(3000.0); System.out.println(empMapper.updateEmpById(emp)); // 增删改手动提交 sqlSession.commit(); sqlSession.close(); ``` **注意**:更新的数据不能全为`null`否则会出现SQL语句错误异常。 #### 5、foreach标签 `<foreach>`标签可以在SQL语句中遍历一个数组或集合,主要用于`in`后的拼接,进行批量操作。 ##### 通过EMPno批量删除 - 使用数组 在`EmpMapper.java`接口中添加 ```java // 使用动态SQL:foreach标签 - 数组 int deleteEmpById(Integer[] ids); ``` 在`EmpMapper.xml`映射文件中添加 ```xml <delete id="deleteEmpById" parameterType="int"> delete from emp where emp.EMPno in <foreach collection="array" item="id" open="(" close=")" separator=","> #{id} </foreach> </delete> ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); System.out.println(empMapper.deleteEmpById(new Integer[]{8004, 8005})); // 增删改手动提交 sqlSession.commit(); sqlSession.close(); ``` ##### 通过EMPno批量删除 - 使用集合 在`EmpMapper.java`接口中添加 ```java // 使用动态SQL:foreach标签 - 集合 int deleteEmpById(List<Emp> empList); ``` 在`EmpMapper.xml`映射文件中添加 ```xml <delete id="deleteEmpById" parameterType="Emp"> delete from emp where emp.EMPno in <foreach collection="list" item="emp" open="(" close=")" separator=","> #{emp.EMPno} </foreach> </delete> ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List<Emp> list = new ArrayList<>(); Emp emp1 = new Emp(); Emp emp2 = new Emp(); emp1.setEMPno(8000); emp2.setEMPno(8002); list.add(emp1); list.add(emp2); System.out.println(empMapper.deleteEmpById(list)); // 增删改手动提交 sqlSession.commit(); sqlSession.close(); ``` ## 二、ResultMap的使用 当数据库表中字段名与其对应的持久化对象的属性名不一致时,需要通过写`<resultMap>`来实现一一对应,在`<resultMap>`中 `type`属性表示其对应的持久化对象,`<id>`标签为表主键标签,`<result>`标签为表其他字段标签,标签中`property`属性表示持久化对象的属性名,`column`属性表示字段名称。 在`EmpMapper.java`接口中添加 ```java // 使用ResultMap全查询 List<Emp> listEmpAll(); ``` 在`EmpMapper.xml`映射文件中添加 ```xml <resultMap id="empResultMap" type="Emp"> <id property="EMPno" column="EMPno_0"/> <result property="ename" column="ename_0"/> <result property="job" column="job_0"/> <result property="mgr" column="mgr_0"/> <result property="hiredate" column="hiredate_0"/> <result property="sal" column="sal_0"/> <result property="comm" column="comm_0"/> <result property="DEPTno" column="DEPTno_0"/> </resultMap> <!-- 这里为了演示将字段取了别名来模拟字段名与属性名不一致的情况 --> <select id="listEmpAll" resultMap="empResultMap"> select emp.EMPno EMPno_0, emp.ename ename_0, emp.job job_0, emp.mgr mgr_0, emp.hiredate hiredate_0, emp.sal sal_0, emp.comm comm_0, emp.DEPTno DEPTno_0 from emp </select> ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List<Emp> list = empMapper.listEmpAll(); for (Emp e : list) { System.out.println(e); } sqlSession.close(); ``` ## 三、关联查询 在MySQL数据库中表的关系有一对一、多对一、一对多、多对多。 #### 1、多表连接形式 ##### 多对一关联查询 创建dept表的实体对象类 在`po`持久层对象包下新建`Dept.java`类 ```java package com.doooog.mybatis.po; 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 + '\'' + '}'; } } ``` 因为`emp`与`dept`时多对一的关系,多个`emp`记录对应一个`dept`记录,所以在`emp.java`类中添加新的属性,并创建`Getter&Setter`,修改全参`构造`方法,修改`toString()`方法 ```java private Dept dept; public Dept getDept() { return dept; } public void setDept(Dept dept) { this.dept = dept; } public Emp(Integer EMPno, String ename, String job, Integer mgr, String hiredate, Double sal, Double comm, Integer DEPTno) { this.EMPno = EMPno; this.ename = ename; this.job = job; this.mgr = mgr; this.hiredate = hiredate; this.sal = sal; this.comm = comm; this.DEPTno = DEPTno; } @Override public String toString() { return "Emp{" + "EMPno=" + EMPno + ", ename='" + ename + '\'' + ", job='" + job + '\'' + ", mgr=" + mgr + ", hiredate='" + hiredate + '\'' + ", sal=" + sal + ", comm=" + comm + ", DEPTno=" + DEPTno + (dept!=null ? ", dept=" + dept : "") + '}'; } ``` 在`EmpMapper.java`接口中添加 ```java // 多对一关联查询 Emp getEmpById(Integer EMPno); ``` 在`EmpMapper.xml`映射文件中添加 ```xml <resultMap id="empResultMap" type="Emp"> <id property="EMPno" column="EMPno"/> <result property="ename" column="ename"/> <result property="job" column="job"/> <result property="mgr" column="mgr"/> <result property="hiredate" column="hiredate"/> <result property="sal" column="sal"/> <result property="comm" column="comm"/> <result property="DEPTno" column="DEPTno"/> <association property="dept" javaType="Dept"> <id property="DEPTno" column="dDEPTno"/> <result property="dname" column="dname"/> <result property="loc" column="loc"/> </association> </resultMap> <select id="getEmpById" parameterType="int" resultMap="empResultMap"> select e.*,d.DEPTno dDEPTno,d.dname dname,d.loc loc from emp e left join dept d on e.DEPTno=d.DEPTno where e.EMPno=#{empno} </select> ``` **注意**:在这里因为`Emp`的属性`dept`是一个`Dept`对象,所以如果只在`<select>`标签下填写`resultType=Emp`的话将无法映射,因为字段名无法与对象名进行一一对应,所以要通过**二、ResultMap的使用**来建立一个`<resultMap>`来写映射关系,其中`<association>`标签专门用来处理单个对象属性,其`javaType`属性须填写对象属性的对象类型,同样的在此标签下`<id>`映射主键,`<result>`映射其他字段,其中`property`属性需填写对象属性的属性名,`column`属性填写`<select>`标签下SQL语句里给表字段起的`别名`。 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = empMapper.getEmpById(8003); System.out.println(emp); Dept dept = emp.getDept(); System.out.println(dept); sqlSession.close(); ``` ##### 一对多的关联查询 修改`Dept.java`类,因为是一对多,所以添加一个集合属性来存放部门中所有的`Emp`,创建`Getter&Setter`方法,修改全参`构造`方法,修改`toString()`方法 ```java private List<Emp> empList; public Dept(Integer DEPTno, String dname, String loc, List<Emp> empList) { this.DEPTno = DEPTno; this.dname = dname; this.loc = loc; this.empList = empList; } public List<Emp> getEmpList() { return empList; } public void setEmpList(List<Emp> empList) { this.empList = empList; } @Override public String toString() { return "Dept{" + "DEPTno=" + DEPTno + ", dname='" + dname + '\'' + ", loc='" + loc + '\'' + (empList != null && !empList.isEmpty() ? ", empList=" + empList : "") + '}'; } ``` 在`Mapper`包下创建`DeptMapper.java`接口 ```java package com.doooog.mybatis.mapper; import com.doooog.mybatis.po.Dept; public interface DeptMapper { // 一对多关联查询 Dept getDeptById(Integer DEPTno); } ``` 在`Mapper`资源目录下创建`DeptMapper.xml`映射文件 ```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"> <mapper namespace="com.doooog.mybatis.mapper.DeptMapper"> <resultMap id="deptResultMap" type="Dept"> <id property="DEPTno" column="DEPTno"/> <result property="dname" column="dname"/> <result property="loc" column="loc"/> <collection property="empList" ofType="Emp"> <id property="EMPno" column="EMPno"/> <result property="ename" column="ename"/> <result property="job" column="job"/> <result property="mgr" column="mgr"/> <result property="hiredate" column="hiredate"/> <result property="sal" column="sal"/> <result property="comm" column="comm"/> <result property="DEPTno" column="eDEPTno"/> </collection> </resultMap> <select id="getDeptById" parameterType="int" resultMap="deptResultMap"> select d.*, e.EMPno EMPno, e.ename ename, e.job job, e.mgr mgr, e.hiredate hiredate, e.sal sal, e.comm comm, e.DEPTno eDEPTno from dept d left join emp e on d.DEPTno = e.DEPTno where d.DEPTno=#{DEPTno} </select> </mapper> ``` **注意**:在这里因为`DEPT`的属性`empList`是一个`Emp`对象集合,所以如果只在`<select>`标签下填写`resultType=Dept`的话将无法映射,因为字段名无法与对象名进行一一对应,所以要通过**二、ResultMap的使用**来建立一个`<resultMap>`来写映射关系,其中与之不同的是需要用到`<collection>`标签,专门用来处理`多个对象集合`属性,其`ofType`属性须填写对象属性的集合范型,同样的在此标签下`<id>`映射主键,`<result>`映射其他字段,其中`property`属性需填写对象属性的属性名,`column`属性填写`<select>`标签下SQL语句里给表字段起的`别名`。 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); Dept dept = deptMapper.getDeptById(10); System.out.println(dept); for (Emp e : dept.getEmpList()) { System.out.println(e); } sqlSeesion.close(); ``` ##### 双向一对多关联查询 **`emp`表的全查询** 在`EmpMapper.java`接口中添加 ```java // 双向一对多关联查询 List<Emp> getEmpAll(); ``` 在`EmpMapper.xml`映射文件中添加 ```xml <resultMap id="empResultMap" type="Emp"> <id property="EMPno" column="EMPno"/> <result property="ename" column="ename"/> <result property="job" column="job"/> <result property="mgr" column="mgr"/> <result property="hiredate" column="hiredate"/> <result property="sal" column="sal"/> <result property="comm" column="comm"/> <result property="DEPTno" column="DEPTno"/> <association property="dept" javaType="Dept"> <id property="DEPTno" column="dDEPTno"/> <result property="dname" column="dname"/> <result property="loc" column="loc"/> </association> </resultMap> <select id="getEmpAll" resultMap="empResultMap"> select e.*, d.DEPTno dDEPTno, d.dname dname, d.loc loc from emp e left join dept d on e.DEPTno = d.DEPTno </select> ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List<Emp> list = empMapper.getEmpAll(); for (Emp e : list){ System.out.println(e); System.out.println(e.getDept()); } sqlSession.close(); ``` **`dept`表的全查询** 在`DeptMapper.java`接口中添加 ```java // 双向一对多关联查询 List<Dept> getDeptAll(); ``` 在`DeptMapper.xml`映射文件中添加 ```xml <resultMap id="deptResultMap" type="Dept"> <id property="DEPTno" column="DEPTno"/> <result property="dname" column="dname"/> <result property="loc" column="loc"/> <collection property="empList" ofType="Emp"> <id property="EMPno" column="EMPno"/> <result property="ename" column="ename"/> <result property="job" column="job"/> <result property="mgr" column="mgr"/> <result property="hiredate" column="hiredate"/> <result property="sal" column="sal"/> <result property="comm" column="comm"/> <result property="DEPTno" column="eDEPTno"/> </collection> </resultMap> <select id="getDeptAll" resultMap="deptResultMap"> select d.*, e.EMPno, e.ename, e.job, e.mgr, e.hiredate, e.sal, e.comm, e.DEPTno eDEPTno from dept d left join emp e on d.DEPTno = e.DEPTno </select> ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); List<Dept> list = deptMapper.getDeptAll(); for (Dept d : list){ System.out.println(d); for (Emp e : d.getEmpList()){ System.out.println(e); } } sqlSession.close(); ``` #### 2、单独查询形式 ##### 多对一关联查询 在`EmpMapper.java`接口中添加 ```java // 多对一单独查询形式 Emp getEmpById(Integer EMPno); ``` 在`DeptMapper.xml`映射文件中添加单独为单独查询形式准备的`dept查询` ```xml <select id="getDeptByIdLazy" parameterType="int" resultType="Dept"> select * from dept where dept.DEPTno = #{DEPTno} </select> ``` 在`EmpMapper.xml`映射文件中添加 ```xml <resultMap id="empResultMap" type="Emp"> <id property="EMPno" column="EMPno"/> <result property="ename" column="ename"/> <result property="job" column="job"/> <result property="mgr" column="mgr"/> <result property="hiredate" column="hiredate"/> <result property="sal" column="sal"/> <result property="comm" column="comm"/> <result property="DEPTno" column="DEPTno"/> <association property="dept" javaType="Dept" select="com.doooog.mybatis.mapper.DeptMapper.getDeptByIdLazy" column="DEPTno"/> </resultMap> <select id="getEmpById" parameterType="int" resultMap="empResultMap"> select * from emp where emp.EMPno = #{empno} </select> ``` **注意**:因为不是多表联合查询语句所以不需要详细的去写`<association>`标签下的对应关系,而是添加`select`属性,值为上一步在`DeptMapper.xml`中添加的专为单独查询而写的SQL查询`id`,需要把路径写全,`column`属性是作为参数传入嵌套的查询里。 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = empMapper.getEmpById(8003); System.out.println(emp); Dept dept = emp.getDept(); System.out.println(dept); sqlSession.close(); ``` ##### 一对多关联查询 在`DeptMapper.java`接口中添加 ```java // 多对一单独查询形式 Dept getDeptById(Integer DEPTno); ``` 在`EmpMapper.xml`映射文件中添加单独为单独查询形式准备的`emp查询` ```xml <select id="listEmpByDEPTnoLazy" parameterType="int" resultType="Emp"> select * from emp where emp.DEPTno = #{DEPTno} </select> ``` 在`DeptMapper.xml`映射文件中添加 ```xml <resultMap id="deptResultMap" type="Dept"> <id property="DEPTno" column="DEPTno"/> <result property="dname" column="dname"/> <result property="loc" column="loc"/> <collection property="empList" ofType="Emp" select="com.doooog.mybatis.mapper.EmpMapper.listEmpByDEPTnoLazy" column="DEPTno"/> </resultMap> <select id="getDeptById" parameterType="int" resultMap="deptResultMap"> select * from dept where dept.DEPTno = #{DEPTno} </select> ``` **注意**:因为不是多表联合查询语句所以不需要详细的去写`<collection>`标签下的对应关系,而是添加`select`属性,值为上一步在`EmpMapper.xml`中添加的专为单独查询而写的SQL查询`id`,需要把路径写全,`column`属性是作为参数传入嵌套的查询里。 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); Dept dept = deptMapper.getDeptById(20); System.out.println(dept); for (Emp e : dept.getEmpList()){ System.out.println(e); } sqlSession.close(); ``` ## 四、关联查询延迟加载 执行查询时关联的查询不会立即加载,只有当使用到关联查询里的数据时才会加载关联查询,延迟加载只能用于`单独查询形式`的查询语句。 若要开启延迟加载,需要在MyBatis配置文件`sqlMapConfig.xml`里添加`<settings>`标签,添加位置要在`<properties>`标签之后,在`<typeAliases>`标签之前。 ```xml <settings> <!-- 如果aggressiveLazyLoading为true,即使lazyLoadingEnable为true也没有效果 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 全局延迟加载开关 默认为false --> <setting name="aggressiveLazyLoading" value="false"/> <!-- 延迟加载整个对象 默认为true,false:表示对象的每个属性都延迟加载 --> </settings> ``` ## 五、MyBatis缓存机制 MyBatis 的缓存机制是提升数据库查询性能的核心设计,它通过减少重复查询的数据库访问次数来优化性能,分为 **一级缓存** 和 **二级缓存**。 #### 1、一级缓存 **生命周期**:与单个 `SqlSession` 绑定(数据库会话级),默认开启。 **行为规则**: 在同一个`SqlSession`中,多次调用 相同查询方法(参数一致、Statement ID相同)会直接取缓存结果。 执行 `commit()`、`rollback()`、`close()` 或 `增删改`操作 时,缓存会被清空。 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); // 第一次查询,访问数据库 Dept dept1 = deptMapper.getDeptById(20); // 第二次查询(参数相同),直接取缓存 Dept dept2 = deptMapper.getDeptById(20); // dept1 == dept2,结果为 true(同一个对象引用) System.out.println(dept1==dept2); sqlSession.commit(); // 清空缓存(或执行增删改操作) // 第三次查询,需重新访问数据库 Dept dept3 = deptMapper.getDeptById(20); // dept1 == dept3,结果为 false System.out.println(dept1==dept3); sqlSession.close(); ``` **注意**: 若两次查询参数不同(如不同 ID),或 SQL 语句不同(如不同的 Statement ID),则不命中缓存。 可通过`<setting name="localCacheScope" value="STATEMENT"/>`关闭一级缓存(使每次查询都重新执行 SQL)。 #### 2、二级缓存 **生命周期**:与 `Mapper` 绑定(命名空间级),需手动开启。 **行为规则**: 跨 `SqlSession` 共享缓存(相同 `Mapper` 的查询结果可被多个会话访问)。 事务提交(`commit()`)或会话关闭(`close()`)后,查询结果才会存入二级缓存。 ##### 开启二级缓存 在`sqlMapConfig.xml`中添加 ```xml <settings> <setting name="cacheEnabled" value="true"/> </settings> ``` 并在每个`*Mapper.xml`映射文件中`<mapper>`标签第一行加`<cache/>`标签,表示该`Mapper`需要进行缓存 在每个持久层对象的类中需要实现`Serializable`接口,使其对象可以序列化。 ```java SqlSession sqlSession1 = Util.getSqlSessionFactory().openSession(); DeptMapper deptMapper1 = sqlSession.getMapper(DeptMapper.class); Dept dept1 = deptMapper1.getDeptById(20); sqlSession1.close(); // 关闭后,结果存入二级缓存 SqlSession sqlSession2 = Util.getSqlSessionFactory().openSession(); DeptMapper deptMapper2 = sqlSession.getMapper(DeptMapper.class); // 命中二级缓存(无需查询数据库)需要查看日志是否执行查询语句来印证 Dept dept2 = deptMapper2.getDeptById(20); sqlSession2.close(); ``` **注意**: 更新操作(增删改)会清空 对应 `Mapper` 的二级缓存(若配置了 `<cache/>`)。 二级缓存不能保证数据的时效性,若数据频繁更新,请勿使用二级缓存。 ## 六、使用注解实现MyBatis映射 MyBatis提供了四种注解`@Select`、`@Updata`、`@Insert`、`@Delete`来在接口中实现增删改查,在使用注解进行映射时,不需要写`*Mapper.xml`的映射文件,直接在`*Mapper.java`中进行书写SQL语句即可。 #### 1、CRUD操作 ##### 通过主键查询单个Emp 在`EmpMapper.java`接口中添加 ```java // 注解CRUD操作:查询员工信息 @Select("select * from emp where emp.EMPno=#{EMPno}") Emp getEmpById(Integer EMPno); ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = empMapper.getEmpById(8003); System.out.println(emp); sqlSession.close(); ``` ##### emp表全查询 在`EmpMapper.java`接口中添加 ```java // 注解CRUD操作:查询员工表 @Select("select * from emp ORDER BY emp.EMPno") List<Emp> listEmpAll(); ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List<Emp> list = empMapper.listEmpAll(); for (Emp e : list) { System.out.println(e); } sqlSession.close(); ``` ##### 通过主键修改emp姓名 在`EmpMapper.java`接口中添加 ```java // 注解CRUD操作:更新员工姓名 @Update("update emp set emp.ename=#{ename} where EMPno=#{EMPno}") int updateEmpNameById(Emp emp); ``` 在测试类中调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp1 = new Emp(); emp1.setEMPno(8003); emp1.setEname("刘六"); System.out.println(empMapper.updateEmpNameById(emp1)); sqlSession.commit(); sqlSession.close(); ``` #### 2、多对一操作 在`DeptMapper.java`接口中添加Dept延迟查询方法 ```java // 注解多对一延迟查询Dept @Select("select * from dept where DEPTno=#{DEPTno}") Dept getDeptByIdLazy(Integer DEPTno); ``` 在`EmpMapper.java`接口中添加查询方法 ```java // 注解一对多操作 @Select("select * from emp where emp.EMPno=#{EMPno}") @Results({ @Result(id = true, property = "EMPno", column = "EMPno"), @Result(property = "ename", column = "ename"), @Result(property = "job", column = "job"), @Result(property = "mgr", column = "mgr"), @Result(property = "hiredate", column = "hiredate"), @Result(property = "sal", column = "sal"), @Result(property = "comm", column = "comm"), @Result(property = "DEPTno", column = "DEPTno"), @Result(property = "dept", column = "DEPTno", javaType = Dept.class, one = @One(select = "com.doooog.mybatis.mapper.DeptMapper.getDeptByIdLazy")) }) Emp getEmpById(Integer EMPno); ``` 在进行多对一、一对多操作时,只需添加`@Results`、`@Result`注解即可,写法类似于在映射文件中的`<resultMap>`、`<result>`标签,若该字段为主键则添加`id = true`,在多对一时须添加`one=@one()`,`select`属性中填上一步写的方法名的全路径。 在测试类内调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = empMapper.getEmpById(8003); System.out.println(emp); sqlSession.close(); ``` #### 3、一对多操作 在`EmpMapper.java`接口中添加Emp外键延迟查询方法 ```java // 注解一对多延迟查询Emp @Select("select * from emp where emp.DEPTno=#{DEPTno}") List<Emp> getEmpByDEPTnoLazy(Integer EMPno); ``` 在`DeptMapper.java`接口中添加查询方法 ```java // 注解一对多操作 @Select("select * from dept where DEPTno=#{DEPTno}") @Results({ @Result(id = true, property = "DEPTno", column = "DEPTno"), @Result(property = "dname", column = "dname"), @Result(property = "loc", column = "loc"), @Result(property = "empList", column = "DEPTno", javaType = List.class, many = @Many(select = "com.doooog.mybatis.mapper.EmpMapper.getEmpByDEPTnoLazy")) }) Dept getDeptById(Integer DEPTno); ``` 在进行一对多时,映射数组或集合属性的注解内的`javaType`要填写数组或集合的`.class`,并且使用`many=@Many()`来实现数组或集合的创建,`select`属性中填上一步写的方法名的全路径。 在测试类内调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); Dept dept = deptMapper.getDeptById(20); System.out.println(dept); for (Emp e : dept.getEmpList()) { System.out.println(e); } sqlSession.close(); ``` #### 4、使用注解进行动态SQL ##### 使用`@SelectProvider` 创建`com.doooog.mybatis.sqlprovider`包,用来存放`*SqlProvider.java`类 在此包下创建`EmpSqlProvider.java`类 ```java package com.doooog.mybatis.sqlprovider; import com.doooog.mybatis.po.Emp; import org.apache.ibatis.jdbc.SQL; public class EmpSqlProvider { public String listEmpByConditionProvider(Emp emp){ return new SQL(){{ SELECT("*"); FROM("emp"); if (emp.getEMPno()!=null) WHERE("emp.EMPno = #{EMPno}"); if (emp.getEname()!=null && !emp.getEname().isEmpty()) WHERE("emp.ename like concat('%',#{ename},'%')"); if (emp.getJob()!=null && !emp.getJob().isEmpty()) WHERE("emp.job like concat('%',#{job},'%')"); if (emp.getMgr()!=null) WHERE("emp.mgr = #{mgr}"); if (emp.getSal()!=null) WHERE("emp.sal = #{sal}"); if (emp.getComm()!=null) WHERE("emp.comm = #{comm}"); if (emp.getHiredate()!=null && !emp.getHiredate().isEmpty()) WHERE("emp.hiredate = #{hiredate}"); if (emp.getDEPTno()!=null) WHERE("emp.DEPTno = #{DEPTno}"); }}.toString(); } } ``` 使用 SQL 构建器(MyBatis 内置类)动态拼接 SQL。 通过条件判断(如 if 语句)动态生成 SET 或 WHERE 子句。 **注意**:方法参数与 Mapper 接口方法参数类型一致(如示例中的 Emp emp)。 在`EmpMapper.java`接口中添加 ```java // 使用注解进行动态SQL查询 - SqlProvider @SelectProvider(type = DeptSqlProvider.class,method = "listDeptByConditionProvider") @Results({ @Result(id = true,property = "DEPTno",column = "DEPTno"), @Result(property = "dname",column = "dname"), @Result(property = "loc",column = "loc"), @Result(property = "empList",column = "DEPTno",javaType = List.class,many = @Many(select = "com.doooog.mybatis.mapper.EmpMapper.getEmpByDEPTnoLazy")) }) List<Dept> listDeptByCondition(Dept dept); ``` 使用`@SelectProvider`注解创建Select查询,`type`属性为`*SqlProvider.class`,`method`属性为`*SqlProvider`类中所用到的`方法名` 若有联合查询的需求,可以使用`@Results`、`@Result`注解进行设计ResultMap,使用方法同上面的第2、3条。 在测试类内调用即可 ```java SqlSession sqlSession = Util.getSqlSessionFactory().openSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); Emp emp = new Emp(); emp.setEname("S"); emp.setJob("S"); List<Emp> list = empMapper.listEmpByCondition(emp); for (Emp e : list) { System.out.println(e); } sqlSession.close() ``` 最后修改:2025 年 10 月 22 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 👆请简单粗暴地爱我。