四、MyBatis-映射文件
MyBatis 的真正强大在于它的语句映射,这是它的魔力所在
映射文件指导着Mybatis如何进行数据库CRUD,有着非常重要的意义
cache-命名空间的二级缓存配置
cache-ref-其他命名空间缓存配置的引用
resultMap-自定义结果集映射
sql抽取可重用语句块
insert -映射插入语句
update-映射更新语句
delete -映射删除语句
select-映射查询语句
SQL映射文件一般命名为接口名+Mapper
如EmployeeMapper、EmployeeMapperpro、EmployeeMapperpromax
下面我们来分别介绍sql映射文件中的这些元素~
4.1 参数(Parameters)传递
单个参数
可以接受基本类型,对象类型,集合类型的值。这种情况MyBatis可值接使用这个参数﹐不需要经过任何处理。
多个参数
任意多个参数,都会被MyBatis重新包装成一个Map传入。Map的key是param1 , param2 ,0,1…,值就是参数的值。
命名参数
为参数使用@Param起一个名字,MyBatis就会将这些参数封装进map中, key就是我们自己指定的名字
POJO
当这些参数属于我们业务POJO时,我们直接传递POJO
Map
我们也可以封装多个参数为map ,直接传递
4.2 参数处理
参数位置支持的属性
javaType、jdbcType、mode、numericScale、resultMap、typeHandler、jdbcTypeName
实际上通常被设置的是∶
可能为空的列名指定jdbcType
#{key}:获取参数的值,预编译到SQL中。安全。
${key}:获取参数的值,拼接到SQL中。有SQL注入问题。ORDER BY ${name}
一般选择#{key}的方式来获取参数的值
4.3 增删改查元素
Select元素来定义查询操作。
ld:唯一标识符。
用来引用这条语句,需要和接口的方法名一致
parameterType :参数类型。
可以不传,MyBatis会根据TypeHandler自动推断
resultType:返回值类型。
别名或者全类名﹐如果返回的是集合﹐定义集合中元素的类型。不能和resultMap同时使用
resultMap:自定义返回值类型
外部resultMap的命名引用。和resultType属性不能同时使用
<select id="getEmpById" resultType="emp"> select * from tbl_employee where id = #{id}</select>
insert, update 和 delete和上述查询其实查不到哪里去啦,差不多都一样的啦~
<insert id="insert" parameterType="com.caq.mybatis.bean.Employee" useGeneratedKeys="true" keyProperty="id"> insert into tbl_employee(last_name,email,gender) values(#{lastName},#{email},#{gender})</insert><update id="updateEmp"> update tbl_employee set last_name=#{lastName},email=#{email},gender=#{gender} where id = #{id}</update><delete id="deleteEmpById"> delete from tbl_employee where id = #{id}</delete>
不难,主要是我们sql语句的书写
要注意的点是,删除修改数据库中的字段,我们传的参数都是一个javabean对象
如果数我们查的数据中不只有属性还有个对象改怎么查呢?
我们可以想象如下场景:
查询Employee的同时查询员工对应的部门
场景一:
查询Employee的同时查询员工对应的部门
Employee===Department
一个员工有与之对应的部门信息;
联合查询:级联属型封装结果集
<resultMap id="MyEmpPlus" type="com.caq.mybatis.bean.Employee"> <id column="id" property="id"/> <result column="last_name" property="lastName"/> <result column="gender" property="gender"/> <result column="did" property="dept.id"/> <result column="dept_name" property="dept.departmentName"/></resultMap><select id="getEmpAndDept" resultMap="MyEmpPlus"> SELECT e.id id,e.last_name last_name,e.gender gender,e.d_id d_id,d.id did,d.dept_name dept_name FROM tbl_employee e,tbl_dept d where e.d_id = d.id and e.id = 1</select>
上述方式的确可以解决这个问题
但是你看它是不是写的太麻烦了啊,那有没有简单的方法查询呢?
association可以指定联合的javaBean对象
指定哪个属型是联合的对象
javaType指定属型对象的类型【不能省略】
使用association定义关联的单个对象的封装规则
<resultMap id="MyEmpByStep" type="com.caq.mybatis.bean.Employee"> <!-- 先按照员工id查询员工信息--> <!-- 根据查询员工信息中的d_id值去查部门查处部门信息--> <!-- 部门设置到员工中--> <id column="id" property="id"/> <result column="last_name" property="lastName"/> <result column="email" property="email"/> <result column="gender" property="gender"/> <!-- association来定义关联对象的封装规则 select表明当前属性是调用sekect指定的方法查出的结果 流程:使用select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property指定的属性--> <association property="dept"select="com.caq.mybatis.dao.DepartmentMapper.getDeptById" column="d_id"> </association> </resultMap> <select id="getEmpByIdStep" resultMap="MyEmpByStep"> select * from tbl_employee where id = #{id} </select>
select:调用目标的方法查询当前属性的值
column:将指定列的值传入目标方法
没错我们可以用association来进行查询,也就是分布查询。
先按照员工id查询员工信息
根据查询员工信息中的d_id值去查部门查处部门信息
部门设置到员工中
从这里就能看出Mybatis的灵活,处理一些很复杂的查询的时候association能帮我们解决头痛的问题
4.4 SQL
这个元素可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。 参数可以静态地(在加载的时候)确定下来,并且可以在不同的 include 元素中定义不同的参数值。
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
<select id="selectUsers" resultType="map"> select <include refid="userColumns"> <property name="alias" value="t1"/> </include>, <include refid="userColumns"> <property name="alias" value="t2"/> </include> from some_table t1 cross join some_table t2</select>
一般我们会把重复的sql字段提取出一个sql,然后不同的语句用这个sql字段的时候我们调这个提取出来的sql即可
4.5 自动映射
全局setting设置
autoMappingBehavior默认是PARTIAL,开启自动映射的功能。唯一的要求是列名和javaBean属性名一致―如果autoMappingBehavior设置为null则会取消自动映射一数据库字段命名规范,POJO属性符合驼峰命名法,如A_COLUMN→>aColumn ,我们可以开启自动驼峰命名规则映射功能,mapUnderscoreToCamelCase=true
自定义resultMap ,实现高级结果集映射。
4.6 resultMap
一些参数的介绍
constructor -类在实例化时,用来注入结果到构造方法中
id 一个ID结果;标记结果作为ID可以帮助提高整体效能
result一注入到字段或JavaBean属性的普通结果
association 一个复杂的类型关联;许多结果将包成这种类型
嵌入结果映射
结果映射自身的关联,或者参考一个
collection 复杂类型的集
嵌入结果映射 结果映射自身的集,或者参考一个
discriminator 使用结果值来决定使用哪个结果映射
case-基于某些值的结果映射
嵌入结果映射-这种情形结果也映射它本身,因此可以包含很多相同的元素,或者它可以参照个外部的结果映射。
<resultMap id="MyEmp" type="com.caq.mybatis.bean.Employee"> <!-- 指定主键列的封装规则: id定义主键列的话底层会有优化 column指定哪一列 property指定对应的javaBean属性--> <id column="id" property="id"/> <!-- result定义普通列封装规则--> <result column="last_name" property="lastName"/> <!-- 其他不指定的列会自动封装,不过我们既然自定义了那就把字段都写上--> <result column="gender" property="gender"/> <result column="email" property="email"/> </resultMap> <!-- id就是方法名--> <!-- <select id="getEmpById" resultType="emp">--> <!-- select *--> <!-- from mybatis.tbl_employee--> <!-- where id = #{id};--> <!-- </select>--> <select id="getEmpById" resultMap="MyEmp"> select * from tbl_employee where id = #{id}; </select>
4.7 鉴别器
有时候,一个数据库查询可能会返回多个不同的结果集(但总体上还是有一定的联系的)。 鉴别器(discriminator)元素就是被设计来应对这种情况的,另外也能处理其它情况,例如类的继承层次结构。 鉴别器的概念很好理解——它很像 Java 语言中的 switch 语句。
column指定判定的列名,javaType列值对应的java类型
男生,如果是男生,把last_name这一列的值赋给email,女生则查询出所在部门信息
<resultMap id="MyEmpDis" type="com.caq.mybatis.bean.Employee"> <id column="id" property="id"/> <result column="last_name" property="lastName"/> <result column="email" property="email"/> <result column="gender" property="gender"/> <!-- column指定判定的列名,javaType列值对应的java类型--> <discriminator javaType="string" column="gender"> <!-- 女生--> <case value="0" resultType="com.caq.mybatis.bean.Employee"> <association property="dept" select="com.caq.mybatis.dao.DepartmentMapper.getDeptById" column="d_id"> </association> </case> <!-- 男生,如果是男生,把last_name这一列的值赋给email--> <case value="1" resultType="com.caq.mybatis.bean.Employee"> <id column="id" property="id"/> <result column="last_name" property="lastName"/> <result column="last_name" property="email"/> <result column="gender" property="gender"/> </case> </discriminator> </resultMap> <select id="getEmpByIdStep" resultMap="MyEmpDis"> SELECT * FROM tbl_employee WHERE id = #{id}; </select>
这个很好理解的,我还是把测试结果写出来
// 根据id分布查员工信息@Testpublic void test03() throws IOException { SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); SqlSession openSession = sqlSessionFactory.openSession(); try { EmployeeMapperPlus mapper = openSession.getMapper(EmployeeMapperPlus.class); Employee empByIdStep = mapper.getEmpByIdStep(4); System.out.println(empByIdStep); System.out.println(empByIdStep.getDept()); } finally { openSession.close(); }}
我们查询id为4的员工,根据我们前面写的sql映射文件,如果它的性别是男则他的邮件地址就是它的姓名,如果他的性别是女那么就能打印出她的部门信息(查部门信息又是一个新的对象我们用的association分步查询)
显示结果分别如下:
Employee{id=4, lastName=‘jerry2’, email=‘jerry2’, gender=‘1’} null Employee{id=1, lastName=‘tom’, email=‘tom@caq.com’, gender=‘0’} Department{id=1, departmentName=‘开发’}
五、MyBatis-动态SQL
动态SQL是MyBatis强大特性之一。极大的简化我们拼装SQL的操作,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。。动态SQL元素和使用JSTL或其他类似基于XML的文本处理器相似。MyBatis采用功能强大的基于OGNL的表达式来简化操作。
if choose (when, otherwise) trim (where,set) foreach
5.1 if
使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。
比如我们有很多字段,那我们写条件的时候要在where后面写很长的判断条件,那么有了if我们就可以根据if判断条件。如果条件成立则添加不添加。
遇见特殊符号其实可以写转义字符
HTML 字符实体 (w3school.com.cn)
我们可以去这里寻找特殊符号对应的转义字符!
<select id="getEmpByConditionIf" resultType="com.caq.mybatis.bean.Employee"> select * from tbl_employee where <!-- test:判定表达式(OGNL) OGNL参照PPT或官方文档 从参数中取值进行判断 遇见特殊符号其实可以写转义字符 --> <if test="id!=null"> id=#{id} </if> <if test="lastName!=null && lastName != """> and last_name like #{lastName} </if> <if test="email!=null and email.trim() != """> and email =#{email} </if> # ognl会进行字符串与数字的转换判断 <if test="gender==0 or gender==1"> and gender=#{gender} </if> </select>
5.2 choose、when、otherwise
<select id="getEmpByConditionChoose" resultType="com.caq.mybatis.bean.Employee"> select * from tbl_employee <where> # 如果带了id就用id查,如果带了lastName就用lastName查,只会进入其中一个 <choose> <when test="id!=null"> id = #{id} </when> <when test="lastName!=null"> last_name like #{lastName} </when> <when test="email!=null"> email = #{email} </when> <otherwise> gender = 0 </otherwise> </choose> </where> </select>
下面我们看测试文件的结果
// 测试自定义字符串截取@Testpublic void test07 () throws IOException { SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); SqlSession openSession = sqlSessionFactory.openSession(); try { EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL.class); Employee employee = new Employee(null, null, null, null,null); List<Employee> list = mapper.getEmpByConditionChoose(employee); for (Employee employee1 : list) { System.out.println(employee1); } } finally { openSession.close(); }}
我们什么参数也没传入,所以她就会按照默认的来查。就是gender=0的情况
所以会出现如下结果:
Employee{id=1, lastName=‘tom’, email=‘tom@caq.com’, gender=‘0’} Employee{id=6, lastName=‘tom’, email=‘tom@qq.com’, gender=‘0’} Employee{id=7, lastName=‘Rose’, email=‘Rose@qq.com’, gender=‘0’}
5.3 trim、where、set
prefix="":前缀:trim标签体中是整个字符串拼串后的结果
prefixOverrides="":
前缀覆盖:去掉整个字符串前面多余的字符
suffix="":后缀
suffix给拼串后的整个字符串加上一个后缀
suffixOverrides=""
后缀覆盖:去掉整个字符串后面多余的字符
把我们容易多写少写的and去掉或者自动通过关键字加上,真是好用呢!
<select id="getEmpByConditionTrim" resultType="com.caq.mybatis.bean.Employee"> select * from tbl_employee <trim prefix="where" suffixOverrides="and"> <if test="id!=null"> id=#{id} </if> <if test="lastName!=null && lastName != """> last_name like #{lastName} and </if> <if test="email!=null and email.trim() != """> email =#{email} and </if> <if test="gender==0 or gender==1"> gender=#{gender} and </if> </trim> </select>
5.4 foreach
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符
你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
有个错误一定要记得,sql语句在navicat里写或者别的sql工具。
不然在idea写错个标点要排查半天搞人心态
<insert id="addEmps"> insert into tbl_employee(last_name,email,gender,d_id) values <foreach collection="emps" item="emp" separator=","> (#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id}) </foreach></insert> 1 @Test public void test08 () throws IOException { SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); SqlSession openSession = sqlSessionFactory.openSession(); try { EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL.class); ArrayList<Employee> emps = new ArrayList<>(); emps.add(new Employee(null,"tim","tim@qq.com","0",new Department(1))); emps.add(new Employee(null,"apple","apple@163.com.com","0",new Department(2))); mapper.addEmps(emps); openSession.commit(); System.out.println("Success!!!"); } finally { openSession.close(); } }
添加成功啦~~~
5.5 bind
bind元素可以从OGNL表达式中创建一个变量并将其绑定到上下文。
<!-- List<Employee> getEmpsTestInnerParameter(Employee employee);--> <select id="getEmpsTestInnerParameter" resultType="com.caq.mybatis.bean.Employee"> # 可以将OGNL表达式的值绑定到一个变量中,方便后来引用这个变量的值 <bind name="_testBind" value="'_'+lastName+'%'"/> select * from tbl_employee where last_name like #{_testBind} </select>
@Test public void test09 () throws IOException { SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); SqlSession openSession = sqlSessionFactory.openSession(); try { EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL .class); Employee employee = new Employee(); employee.setLastName("%e%"); List<Employee> list = mapper.getEmpsTestInnerParameter(employee); for (Employee employee1 : list) { System.out.println(employee1); } } finally { openSession.close(); } }
测试结果如下
Employee{id=3, lastName=‘jerry1’, email=‘jerry1@qq.com’, gender=‘1’} Employee{id=4, lastName=‘jerry2’, email=‘jerry2@qq.com’, gender=‘1’} Employee{id=5, lastName=‘jerry3’, email=‘jerry3@qq.com’, gender=‘1’} Employee{id=8, lastName=‘jerry2’, email=‘jerry2@qq.com’, gender=‘1’}