【Mybatis从入门到实战教程】第四章 Mybatis 输入映射、输出映射和动态SQL详解
四、Mybatis Mapper配置文件
mapper.xml映射文件中定义了操作数据库的Sql,每个Sql是一个statement,映射文件是MyBatis的核心。
4.1 parameterType输入映射
parameterType配置输入参数的类型。
4.1.1 表结构
CREATE TABLE `users` ( `id` int(11) PRIMARY KEY AUTO_INCREMENT, `username` varchar(20), `password` varchar(50), `realname` varchar(20));INSERT INTO `users` VALUES (1, 'admin', '123456', '管理员');INSERT INTO `users` VALUES (2, 'tom', '123', '汤姆');INSERT INTO `users` VALUES (3, 'jerry', '456', '杰瑞');INSERT INTO `users` VALUES (4, 'zhangsan', '111', '张三');INSERT INTO `users` VALUES (5, 'lisi', '222', '李四');
4.1.2 实体类
package com.newcapec.entity;public class Users { private Integer id; private String username; private String password; private String realname; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getRealname() { return realname; } public void setRealname(String realname) { this.realname = realname; } @Override public String toString() { return "Users{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", realname='" + realname + '\'' + '}'; }}
4.1.3 简单类型
Java基本数据类型以及包装类,String字符串类型。
mapper接口:
package com.newcapec.mapper;import com.newcapec.entity.Users;import java.util.List;public interface UsersMapper { List selectByRealname(String realname);}
mapper文件:
select id,username,password,realname from users where realname like concat('%',#{realname},'%')
测试:
package com.newcapec;import com.newcapec.entity.Users;import com.newcapec.mapper.UsersMapper;import com.newcapec.util.MybatisUtil;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.List;public class ParameterTypeTest { @Test public void testSimpleParam() { SqlSession sqlSession = MybatisUtil.getSession(); UsersMapper usersMapper = sqlSession.getMapper(UsersMapper.class); List list = usersMapper.selectByRealname("张"); for (Users users : list) { System.out.println(users); } sqlSession.close(); }}
4.1.4 实体类或自定义类型
开发中通过实体类或pojo类型传递查询条件,查询条件是综合的查询条件,不仅包括实体类中查询条件还包括其它的查询条件,这时可以使用包装对象传递输入参数。
- 自定义类型
分页类:
package com.newcapec.entity;/ * 分页类 */public class Page { //当前页码 private Integer pageNum = 1; //每页条数 private Integer pageSize = 3; //总页数: 总记录数/每页条数,除不尽+1 private Integer pages; //总记录数 private Integer total; / * mysql * 起始偏移量:(当前页码-1)*每页条数 */ private Integer offset; / * oracle * 起始条数:(当前页码-1)*每页条数+1 * 结束条数: 当前页码*每页条数 */ private Integer start; private Integer end; public Integer getPageNum() { return pageNum; } public void setPageNum(Integer pageNum) { this.pageNum = pageNum; } public Integer getPageSize() { return pageSize; } public void setPageSize(Integer pageSize) { this.pageSize = pageSize; } public Integer getPages() { return getTotal() % getPageSize() == 0 ? getTotal() / getPageSize() : getTotal() / getPageSize() + 1; } public void setPages(Integer pages) { this.pages = pages; } public Integer getTotal() { return total; } public void setTotal(Integer total) { this.total = total; } public Integer getOffset() { return (getPageNum() - 1) * getPageSize(); } public void setOffset(Integer offset) { this.offset = offset; } public Integer getStart() { return (getPageNum() - 1) * getPageSize() + 1; } public void setStart(Integer start) { this.start = start; } public Integer getEnd() { return getPageNum() * getPageSize(); } public void setEnd(Integer end) { this.end = end; }}
复合类:UsersQuery
package com.newcapec.entity;/ * 多条件查询复合类 */public class UsersQuery { private Users users; private Page page; public Users getUsers() { return users; } public void setUsers(Users users) { this.users = users; } public Page getPage() { return page; } public void setPage(Page page) { this.page = page; }}
- mapper接口
List selectByPage(Page page);List selectByRealnameAndPage(UsersQuery usersQuery);
- mapper文件
select id,username,password,realname from users order by id limit #{offset}, #{pageSize} select id,username,password,realname from users where realname like concat('%',#{users.realname},'%') order by id limit #{page.offset}, #{page.pageSize}
- 测试
@Testpublic void testClassParam1() { SqlSession sqlSession = MybatisUtil.getSession(); UsersMapper usersMapper = sqlSession.getMapper(UsersMapper.class); Page page = new Page(); page.setPageNum(1); System.out.println("mysql起始偏移量:" + page.getOffset()); System.out.println("起始条数:" + page.getStart()); System.out.println("结束条数:" + page.getEnd()); List list = usersMapper.selectByPage(page); for (Users users : list) { System.out.println(users); } sqlSession.close();}@Testpublic void testPojoParam2() { SqlSession sqlSession = MybatisUtil.getSession(); UsersMapper usersMapper = sqlSession.getMapper(UsersMapper.class); Page page = new Page(); page.setPageNum(1); Users users = new Users(); users.setRealname("张"); UsersQuery usersQuery = new UsersQuery(); usersQuery.setPage(page); usersQuery.setUsers(users); List list = usersMapper.selectByRealnameAndPage(usersQuery); for (Users u : list) { System.out.println(u); } sqlSession.close();}
4.1.5 Map类型
mapper接口:
List selectUseMap(Map map);
mapper文件:
select id,username,password,realname from users where realname like concat('%',#{name},'%') order by id limit #{begin}, #{size}
测试:
@Testpublic void testMapParam() { SqlSession sqlSession = MybatisUtil.getSession(); UsersMapper usersMapper = sqlSession.getMapper(UsersMapper.class); Map map = new HashMap(); map.put("name", "李"); map.put("size", 5); map.put("begin", 0); List list = usersMapper.selectUseMap(map); for (Users u : list) { System.out.println(u); } sqlSession.close();}
4.1.6 多输入参数
MyBatis中允许有多个输入参数,可使用@Param注解。
这种做法类似与Map类型的输入参数,其中@Param注解的value属性值为Map的key,在映射文件中通过ognl可获取对应的value,并且parameterType可以不指定类型。
mapper接口:
Users login(@Param("uname") String username, @Param("pwd") String password);
mapper文件:
select id,username,password,realname from users where username=#{uname} and password=#{pwd}
测试:
@Testpublic void testMultiParam() { SqlSession sqlSession = MybatisUtil.getSession(); UsersMapper usersMapper = sqlSession.getMapper(UsersMapper.class); Users users = usersMapper.login("jerry", "456"); System.out.println(users); sqlSession.close();}
4.2 resultType输出映射
4.2.1 表结构
CREATE TABLE `person` ( `id` int(11) PRIMARY KEY AUTO_INCREMENT, `person_name` varchar(20), `person_age` int(4), `person_address` varchar(50));INSERT INTO `person` VALUES (1, '曹操', 40, '洛阳');INSERT INTO `person` VALUES (2, '刘备', 38, '成都');INSERT INTO `person` VALUES (3, '孙权', 29, '杭州');INSERT INTO `person` VALUES (4, '关羽', 35, '荆州');INSERT INTO `person` VALUES (5, '张飞', 32, '成都');INSERT INTO `person` VALUES (6, '曹仁', 28, '许都');
4.2.2 实体类
package com.newcapec.entity;public class Person { private Integer id; private String personName; private Integer personAge; private String personAddress; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getPersonName() { return personName; } public void setPersonName(String personName) { this.personName = personName; } public Integer getPersonAge() { return personAge; } public void setPersonAge(Integer personAge) { this.personAge = personAge; } public String getPersonAddress() { return personAddress; } public void setPersonAddress(String personAddress) { this.personAddress = personAddress; } @Override public String toString() { return "Person{" + "id=" + id + ", personName='" + personName + '\'' + ", personAge=" + personAge + ", personAddress='" + personAddress + '\'' + '}'; }}
4.2.3 简单类型
查询出来的结果集只有一行且一列,可以使用简单类型进行输出映射。
mapper接口:
package com.newcapec.mapper;public interface PersonMapper { // 查询Person的总数量 Integer selectCount();}
mapper文件:
select count(1) from person
测试:
public class ResultTypeTest { @Test public void testSimpleResult() { SqlSession sqlSession = MybatisUtil.getSession(); PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class); int total = personMapper.selectCount(); System.out.println("总记录数:" + total); sqlSession.close(); }}
4.2.4 实体类对象和列表
不管是输出的实体类是单个对象还是一个列表(list中包括实体类对象),在mapper.xml中resultType指定的类型是一样的
在原始Dao的方式中,通过selectOne和selectList方法来区分返回值为单个对象或集合列表,而在mapper代理中,则通过接口中定义的方法返回值来区分。
mapper接口:
Person selectById(Integer id);List selectAll();
mapper文件:
select id,person_name personName,person_age personAge,person_address personAddress from person where id=#{id} select id,person_name personName,person_age personAge,person_address personAddress from person
测试:
@Testpublic void testResultType1() { SqlSession sqlSession = MybatisUtil.getSession(); PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class); Person person = personMapper.selectById(1); System.out.println(person); sqlSession.close();}@Testpublic void testResultType2() { SqlSession sqlSession = MybatisUtil.getSession(); PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class); List list = personMapper.selectAll(); for (Person person : list) { System.out.println(person); } sqlSession.close();}
4.2.5 resultMap
-
resultType可以指定将查询结果映射为实体类,但需要实体类的属性名和SQL查询的列名一致方可映射成功,当然如果开启下划线转驼峰 或 Sql设置列别名,也可以自动映射。
-
如果SQL查询字段名和实体类的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系,resultMap实质上还会将查询结果映射到实体类对象中。
-
resultMap可以实现将查询结果映射为复合型的实体类,比如在查询结果映射对象中包括实体类和list实现一对一查询和一对多查询。
mapper接口:
List select();
mapper文件:
使用resultMap作为statement的输出映射类型。
select id,person_name,person_age,person_address from person
-
resultType: 自动映射
-
resultMap: 手动映射
-
id: 唯一标识,名称;
-
type: 手动映射的java类型
-
子标签
配置数据库表中的主键和实体类中属性的对应关系
-
子标签
配置数据库表中的普通字段和实体类中属性的对应关系
-
property:实体类中的成员变量名
-
column:结果集中的字段名称
-
javaType:实体类成员变量的类型,由mybaits自动识别,可不配置
-
jdbcType:表字段的类型,由mybaits自动识别,可不配置
-
typeHandler:自定义类型处理器,用的相对比较少
-
-
测试:
@Testpublic void testResultMap() { SqlSession sqlSession = MybatisUtil.getSession(); PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class); List list = personMapper.select(); for (Person person : list) { System.out.println(person); } sqlSession.close();}
4.3 动态SQL
4.3.1 什么是动态SQL
动态Sql是指MyBatis核心对Sql语句进行灵活操作,通过表达式进行判断,对Sql进行灵活拼接、组装。
比如:
我们要查询姓名中带 M 和 高于 1000的员工信息;
可能有时候我们需要不带条件查询;
可能有时候我们需要模糊查询;
可能有时候需要根据多条件查询;
动态SQL可以帮助我们解决这些问题;
通过mybatis提供的各种标签方法实现动态拼接sql。
4.3.2 if标签
判断标签,当参数符合判断条件拼接SQL语句。
实体类:
package com.newcapec.entity;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 comm; private Integer deptno; private String gender; 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 getComm() { return comm; } public void setComm(Double comm) { this.comm = comm; } public Integer getDeptno() { return deptno; } public void setDeptno(Integer deptno) { this.deptno = deptno; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public String toString() { return "Emp{" + "empno=" + empno + ", ename='" + ename + '\'' + ", job='" + job + '\'' + ", mgr=" + mgr + ", hiredate=" + hiredate + ", sal=" + sal + ", comm=" + comm + ", deptno=" + deptno + ", gender='" + gender + '\'' + '}'; }}
mapper接口:
public interface EmpMapper { List selectUseIf(Emp emp);}
mapper文件:
select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp where ename like concat('%',#{ename},'%') and sal=#{sal} and deptno=#{deptno}
测试:
/* * 动态sql测试 */public class DynamicSqlTest { @Test public void testIf() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = new Emp(); emp.setEname("S"); emp.setSal(1300.0); emp.setDeptno(20); List list = empMapper.selectUseIf(emp); for (Emp e : list) { System.out.println(e); } sqlSession.close(); }}
4.3.3 where标签
where标签,替代where关键字。
1、当where标签内所有的条件都不成立,不会拼接where关键字,只要有一个条件成立就会在SQL语句中拼接where关键字。
2、where标签会自动剔除条件头部的and或者or关键字。
mapper接口:
List selectUseWhere(Emp emp);
mapper文件:
select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp ename like concat('%',#{ename},'%') and sal=#{sal} and deptno=#{deptno}
测试:
@Testpublic void testWhere() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = new Emp(); emp.setEname("S"); emp.setSal(1300.0); emp.setDeptno(20); List list = empMapper.selectUseWhere(emp); for (Emp e : list) { System.out.println(e); } sqlSession.close();}
4.3.4 set标签
set标签,替代set关键字。
1、当set标签内所有的条件都不成立,不会拼接set关键字,只要有一个条件成立就会在SQL语句中拼接set关键字。
2、注意:如果set包含的内容为空SQL语句会出错。
3、set标签会自动剔除条件末尾的任何不相关的逗号。
mapper接口:
void updateUseSet(Emp emp);
mapper文件:
update emp ename=#{ename}, job=#{job}, mgr=#{mgr}, hiredate=#{hiredate}, sal=#{sal}, comm=#{comm}, deptno=#{deptno}, where empno=#{empno}
测试:
@Testpublic void testSet() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = new Emp(); emp.setEmpno(7938); emp.setEname("JACK"); emp.setJob("MANAGER"); emp.setMgr(7844); emp.setSal(5600.0); emp.setComm(1200.0); emp.setHiredate(new Date()); emp.setDeptno(30); empMapper.updateUseSet(emp); sqlSession.commit(); sqlSession.close();}
4.3.5 trim标签
trim标签属性解析:
-
prefix:前缀,包含内容前加上某些字符。
-
suffix:后缀,包含内容后加上某些字符。
-
prefixOverrides:剔除包含内容前的某些字符。
-
suffixOverrides:剔除包含内容后的某些字符。
mapper接口:
void insertUseTrim(Emp emp);
mapper文件:
insert into emp ename, job, mgr, hiredate, sal, comm, deptno, #{ename}, #{job}, #{mgr}, #{hiredate}, #{sal}, #{comm}, #{deptno},
测试:
@Testpublic void testTrim() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = new Emp(); emp.setEname("CHRIS"); emp.setJob("CLERK"); emp.setMgr(1); emp.setSal(3400.0); emp.setComm(800.0); emp.setHiredate(new Date()); emp.setDeptno(10); empMapper.insertUseTrim(emp); sqlSession.commit(); sqlSession.close();}
代替where标签:
select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp ename like concat('%',#{ename},'%') and sal=#{sal} and deptno=#{deptno}
代替set标签:
update emp ename=#{ename}, job=#{job}, mgr=#{mgr}, hiredate=#{hiredate}, sal=#{sal}, comm=#{comm}, deptno=#{deptno}, where empno=#{empno}
4.3.6 foreach标签
向SQL传递数组或list,MyBatis使用foreach解析。
属性解析:
-
collection: 遍历的数组或集合对象名称。
-
SQL只接收一个数组参数,这时SQL解析参数的名称MyBatis固定为array。
-
SQL只接收一个List参数,这时SQL解析参数的名称MyBatis固定为list。
-
如果是通过一个实体类或自定义类型的属性传递到SQL的数组或List集合,则参数的名称为实体类或自定义类型中的属性名。
-
-
index: 为数组的下标。
-
item: 每个遍历生成对象中。
-
open: 开始遍历时拼接的串。
-
close: 结束遍历时拼接的串。
-
separator: 遍历的两个对象中需要拼接的串。
mapper接口:
void deleteUseForeach(Integer[] ids);void insertUseForeach(List empList);
mapper文件:
delete from emp where empno in #{id} insert into emp(ename,job,mgr,hiredate,sal,comm,deptno) values (#{emp.ename},#{emp.job},#{emp.mgr},#{emp.hiredate},#{emp.sal},#{emp.comm},#{emp.deptno})
测试:
@Testpublic void testForeach() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); empMapper.deleteUseForeach(new Integer[]{1, 2, 3, 4}); sqlSession.commit(); sqlSession.close();}@Testpublic void testForeach2() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List empList = new ArrayList(); for (int i = 1; i <= 3; i++) { Emp emp = new Emp(); emp.setEname("TOM" + i); emp.setJob("CLERK" + i); emp.setMgr(1); emp.setSal(4567.0); emp.setComm(123.0); emp.setHiredate(new Date()); emp.setDeptno(10); empList.add(emp); } empMapper.insertUseForeach(empList); //sqlSession.commit(); sqlSession.close();}
4.3.7 choose标签
choose标签、when标签、otherwise标签的组合,类似于if-else-if判断。
select...
4.3.8 SQL片段
将实现的动态SQL判断代码块抽取出来,组成一个SQL片段,其它的statement中就可以引用SQL片段,方便程序员进行开发。
注意:在SQL片段中不要包括where标签。
empno,ename,job,mgr,hiredate,sal,comm,deptno ename like concat('%',#{ename},'%') and sal=#{sal} and deptno=#{deptno} select from emp