具体内容
映射文件指导着MyBatis如何进行数据库的增删改查,所以具有非常重要的意义,映射文件中包含的标签:
1. 一级标签
< mapper namespace=“对应接口的全类名”></ mapper>,表示我们的这个SQL映射文件式对应哪个接口的。也意味着一个接口就存在一个sql映射文件。
2. 二级标签
- cache:配置二级缓存的标签
- cache-ref:应用其他命名空间的二级缓存配置
- resultMap:自定义映射结果集
- sql:抽取可重用的sql片段
- insert:新增操作
- delete:删除操作
- select:查询操作
- parameterMap:已废弃的参数映射
3. 三级标签:
- where
- if
- foreach
- set
-
trim
…
增删改查
- 在Dao接口中,定义增删改查方法。
//新增用户
public void insertEmp(Emp emp);
//删除用户
public void deleteEmp(Integer empId);
//修改用户
public void updateEmp(Emp emp);
//查找用户
public List<Emp> selectEmp();
- 编写sql映射文件
<!--
public void insertEmp(Emp emp);
如果传入的参数为实体类的对象,那么在sql中,必须使用#{实体类JavaBean风格的属性名} 进行参数绑定
-->
<insert id = "insertEmp">
insert into emp(emp_name,emp_mail,emp_gender,dept_id) values(#{empName},#{empMail},#{empGender},#{deptId})
</insert>
<!--
public void deleteEmp(Integer empId)
-->
<delete id="deleteEmp">
delete from emp where emp_id = #{empId}
</delete>
<!--
public void updateEmp(Emp emp);
-->
<update id="updateEmp">
update emp set emp_name = #{empName},emp_mail=#{empMail} where emp_id = #{empId}
</update>
<!--
public List<Emp> selectEmp();
-->
<select id="selectEmp" resultType="com.nhkj.entity.Emp">
select * from emp
</select >
如何知道增删改是否成功?
实际上MyBatis已经为我们封装好了Integer,int,Long,long,Boolean,boolean类型参数,如果我们接口中的增删改方法需要获取这些的参数,我们只需要修改增删改的方法,将返回值设置为对应类型的返回值即可,而且sql映射文件不必做任何修改。Integer,int,Long,long返回值类型返回的是影响数据的笔数,Boolean,boolean表示影响数据笔数为0的情况下返回false,否则返回true。
public int insertEmp(Emp emp);
public Long deleteEmp(Integer empId);
public Integer updateEmp(Emp emp);
测试类
@Test
public void testEmpUpdate() throws Exception {
InputStream input = Resources.getResourceAsStream("mybatis-conf.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(input);
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao empDao = sqlSession.getMapper(EmpDao.class);
Emp emp = new Emp();
emp.setEmpId(90);
emp.setEmpName("张益达");
emp.setEmpMail("[email protected]");
Integer cont = empDao.updateEmp(emp); //调用接口中修改方法,并且回去相应的返回值
System.out.println(cont + "=========================");// 结果 1=========================
sqlSession.commit();
sqlSession.close();
}
获取自增主键的值
Mysql是支持主键自增的,我们在开发中,有这样的一个需求,传递一个没有主键的实体类对象进行新增操作,新增完成之后,需要这个实体类封装上主键值。
Emp -- > insert
Java程序中对象
新增之前
empe_id emp_name emp_mail emp_gender dept_id
null 张三 [email protected] 1 1
新增之后
empe_id emp_name emp_mail emp_gender dept_id
10 张三 [email protected] 1 1
- Mysql数据库的相应操作
<!--
public void insertEmp(Emp emp);
如果传入的参数为实体类的对象,那么在sql中,必须使用#{实体类JavaBean风格的属性名} 进行参数绑定
获取自增主键的值:
keyColumn : 数据表中的主键列名
keyProperty : 对应主键实体类的属性名
useGeneratedKeys : 表示是否通过自增主键获取主键值
- true 是
- false 否
-->
<insert id = "insertEmp" keyColumn="emp_id" keyProperty="empId" useGeneratedKeys="true">
insert into emp(emp_name,emp_mail,emp_gender,dept_id) values(#{empName},#{empMail},#{empGender},#{deptId})
</insert>
//第二种方式:
<insert id = "insertEmp">
<!-- 配置保存时获取插入的 id -->
<selectKey keyColumn="emp_id" keyProperty="empId" resultType="int">
select last_insert_id();
</selectKey>
insert into emp(emp_name,emp_mail,emp_gender,dept_id) values(#{empName},#{empMail},#{empGender},#{deptId})
</insert>
- Oracle中使用序列来完成自增主键值的获取
Oracle中没有主键自增,而是通过序列完成。
create table emp_721(
emp_id number(11) primary key,
emp_name varchar2(20) not null,
emp_mail varchar2(30) not null,
emp_gender number(1) not null,
dept_id number(10) not null
);
select * from emp_721;
insert into emp_721 (emp_id,emp_name,emp_mail,emp_gender,dept_id)
values(1,'张大炮','[email protected]',1,1);
-- 以下sql报错,那么如何实现主键自增呢
insert into emp_721 (emp_name,emp_mail,emp_gender,dept_id)
values('张益达','[email protected]',1,1);
-- oracle数据库中要实现主键自增,我们必须使用序列
create sequence seq_2020721;
-- sequence查询
select seq_2020721.nextval from dual; -- 查询下一个值
select seq_2020721.currval from dual; -- 查询序列当前的使用的值
-- 所以开发中,Oracle数据库表数据新增,对NUmber的主键来说,都使用序列进行自增
insert into emp_721 (emp_id,emp_name,emp_mail,emp_gender,dept_id)
values(seq_2020721.nextval,'张大炮','[email protected]',1,1);
方式①:BEFORE版:先查询后新增数据
<!--
Oracle环境下获取非自增主键的值
public void insertEmp(Emp emp);
-->
<insert id="insertEmp" databaseId="oracle">
<!--
selectKey : 表示查询一个值,作为新增数据的主键
keyColumn : 数据表中的主键列名
keyProperty : 对应主键实体类的属性名
order:表示是在新增之前执行查询还是新增之后执行查询
BEFORE : 表示新增之前
AFTER : 表示新增之后
-->
<selectKey keyColumn="emp_id" keyProperty="empId" resultType="int" order="BEFORE">
select seq_2020721.nextval from dual
</selectKey>
insert into emp_721(emp_id,emp_name,emp_mail,emp_gender,dept_id) values(#{empId},#{empName},#{empMail},#{empGender},#{deptId})
</insert>
方式②:AFTER版:先新增数据,后查询
<!--
Oracle环境下获取非自增主键的值
public void insertEmp(Emp emp);
-->
<insert id="insertEmp" databaseId="oracle">
<selectKey keyColumn="emp_id" keyProperty="empId" resultType="int" order="AFTER">
select seq_2020721.currval from dual
</selectKey>
insert into emp_721(emp_id,emp_name,emp_mail,emp_gender,dept_id) values(seq_2020721.nextval,#{empName},#{empMail},#{empGender},#{deptId})
</insert>
参数的处理
在正常的开发中,参数可能是单个参数,也可能是多个参数,也可能是实体类的对象,这些是较常见的,还有一些特殊的,比如参数是map键值对,是list,除了map和list之外还有其他的多个参数。
1. 单个参数(Integer empId):
单个参数MyBatis不会做任何特殊处理,在SQL映射文件中使用任意的参数名称都可以取得该参数,如:#{abc},但一般都要有语义#{empId}。
2. 多个参数(String empName,Integer pageCurrent,Integer pageSIze):
首先我们测试一下多个参数,看情况。
<!--
public List<Emp> selectEmpByEmpNameLike(String empName,Integer startSize,Integer pageSize);
-->
<select id="selectEmpByEmpNameLike" resultType="com.wanbangee.entities.Emp">
select * from emp where emp_name like #{empName} limit #{startSize},#{pageSize}
</select>
以上程序运行错误提示:Available parameters are [0, 1, 2, param3, param1, param2],这是因为在传递多个参数的情况下,MyBatis会将参数自动的封装成Map键值对,而且每一个参数都有两个键和两个值,这两个键不同,但是值相同,以上程序最终封装的键值对如下:
Key | Value |
---|---|
第一个参数的值 | |
param1 | 第一个参数的值 |
1 | 第二个参数的值 |
param2 | 第二个参数的值 |
2 | 第三个参数的值 |
param3 | 第三个参数的值 |
那么在我们的SQl映射文件中,#{key},在执行的时候就会传入相应的键对应的值。如下:
<!--
public List<Emp> selectEmpByEmpNameLike(String empName,Integer startSize,Integer pageSize);
-->
<select id="selectEmpByEmpNameLike" resultType="com.wanbangee.entities.Emp">
select * from emp where emp_name like #{param1} limit #{1},#{param3}
</select>
当然我们也可以给参数指定键(key),在接口方法的入参前使用@Param注解,给参数指定封装的Map的key,如下:
<!--
public List<Emp> selectEmpByEmpNameLike(@Param("empName")String empName,@Param("startSize")Integer startSize,@Param("pageSize")Integer pageSize);
-->
<select id="selectEmpByEmpNameLike" resultType="com.wanbangee.entities.Emp">
select * from emp where emp_name like #{empName} limit #{startSize},#{pageSize}
</select>
现在注解后可以在SQL映射文件中,使用指定注解的key获得参数的值,那么默认的param1…或者0,1…还能用吗?
答:Param1,param2…可以正常使用,0,1,2…不能使用了。
3. 传递的参数是一个实体类对象
会将参数封装成一个Map键值对,键对应的是实体类对象的属性名,值对应的是该实体类属性的值。比如,传递对象为Emp,则封装的Map兼职对为:
Key | Value |
---|---|
empName | empName属性值 |
empMail | empMail的属性值 |
empId | empId的属性值 |
empGender | empGender的属性值 |
deptId | deptId属性值 |
<insert id = "insertEmp">
insert into emp(emp_name,emp_mail,emp_gender,dept_id) values(#{empName},#{empMail},#{empGender},#{deptId})
</insert>
4. Map作为参数(Map<String,Object>传入参数为Map的情况下,直接在sql映射文件中使用#{键} 就可以传递相应的值
5. List【Set,Array】作为参数(List< String >)
一个list参数是很好处理的,集合会被封装成键值对,键为list和collection,值为List集合的值,所以在sql映射文件中想要取得集合的某个位置的具体的值:#{list[索引位置]},#{collection[索引位置]},同理Set集合或者Array数据处理相似。
<!--
public Emp selectEmpByID2(List<Integer> ids); 去主键集合中的第一个元素作为查询条件
-->
<select id="selectEmpByID2" resultType="com.wanbangee.entities.Emp">
select * from emp where emp_id = #{collection[2]} 或者 #{list[2]}
</select>
<!--
public Emp selectEmpByID2(Integer[] ids); 去主键集合中的第一个元素作为查询条件
-->
<select id="selectEmpByID2" resultType="com.wanbangee.entities.Emp">
select * from emp where emp_id = #{array[2]}
</select>
6. Map,List,和其他参数(Map<String,Object>,List,String empName,Integer pageCurrent,Integer pageSIze):
会将参数封装成Map键值对,而且是符合②所述多个参数的封装规则。
<!--
public List<Emp> selectEmpByEmpNameLike(String empName,Integer startSize,Integer pageSize,List<Integer> ids);
-->
<select id="selectEmpByEmpNameLike" resultType="com.wanbangee.entities.Emp">
select * from emp where emp_name like #{param1} and emp_id = #{param4[2]} limit #{1},#{2}
</select>
#{}与${}的区别
- #{}表示一个占位符号
通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换,
#{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类
型值,#{}括号中可以是 value 或其它名称。

- ${}表示拼接 sql 串
通过${}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, 可 以 接 收 简 单 类 型 值 或 p o j o 属 性 值 , 如 果 p a r a m e t e r T y p e 传 输 单 个 简 单 类 型 值 , {}可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值, 可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,{}括号中只能是 value。
查询返回的结果(resultType)
查询记录返回List
如果查询的结果集为多笔数据【每一笔数据都对应实体类的一个对象】,那么我们可以使用List接收,那么每一条数据都会被分装为实体类的一个对象,而List中就是存放了查询结果集的多个对象。
public List<Emp> selectEmp();
<select id="selectEmp" resultType="com.wanbangee.entities.Emp" databaseId="mysql">
select * from emp
</select>
注意一点:requestType设置为实体类的全类名,而不是List全类名。
查询记录返回Map
在查询结果集映射为多个实体列对象情况下,不能使用Map接收,只有查询结果集为单行的情况下,才能使用Map接收。单笔数据的情况下,使用Map接收,Map的键为查询的数据列明,值为对应列明的查询结果。
<!-- public Map<String,Object> selectEmp(); -->
<!-- map是MyBatis中定义好的Map别名-->
<select id="selectEmp" resultType="map">
select * from emp where emp_id = 1
</select>
Key | Value |
---|---|
empName | empName属性值 |
empMail | empMail的属性值 |
empId | empId的属性值 |
empGender | empGender的属性值 |
deptId | deptId属性值 |
查询结果集是单行单列
比如我们查询数据笔数,那么就是单行单列的值。这种情况下,可以直接将结果集定义为查询结果集的类型。
<!--
public Integer selectEmpCount();
-->
<select id="selectEmpCount" resultType="int">
select count(*) from emp
</select>
单行单列也可以使用Map接收:
<!--
public Map<String,Object> selectEmpCount();
-->
<select id="selectEmpCount" resultType="map">
select count(*) from emp
</select>
查询记录返回resultMap
在属性名和列名不同的情况下,我们解决映射关系的方式有两种:
-
自动驼峰,但是在某些情况下,自动驼峰解决不了
如,列名:empno, 属性名:empId
-
查询的sql语句中,给列名取一个别名,别名可以映射上实体类的属性
除了以上的两种解决方案,还有另外一种解决方案,叫做自定义结果集映射,这个时候必须使用resultMap标签来进行自定义。
<!-- 自定义封装结果集映射
type : 封装后结果集的类型全类名或者别名
id : 表示自定义封装结果的id,唯一的
id 标签: 表示自定义主键封装映射规则
result 标签:表示自定义非主键封装映射规则
- column:查询结果集的列名
- property : 列明对应的属性名
在select标签,如果要使用自定义封装结果集,必须使用resultMap属性声明,而且resultMap和resultType 不能同时存在
-->
<resultMap type="com.wanbangee.entities.Emp" id="myEmp">
<id column="empno" property="empId"/>
<result column="ename" property="empName"/>
</resultMap>
<!--
public Emp selectEmpById(Integer id);
-->
<select id="selectEmpById" resultMap="myEmp">
select empno ,ename from emp where empno = #{id}
</select>
级联属性的封装
现在我们在Emp实体类中引用Dept,在查询Emp的同时,要求将对应引用的Dept属性也能够封装上,按照传统的写法如下:
<!--
public Emp selectEmpById(Integer id);
-->
<select id="selectEmpById" resultType="com.wanbangee.entities.Emp" databaseId="mysql">
select a.emp_id,a.emp_name,a.emp_mail,a.emp_gender,a.dept_id,b.dept_id `dept.deptId`, b.dept_name `dept.deptName` from emp a
left join dept b on a.dept_id = b.dept_id where emp_id = #{id}
</select>
发现程序运行正常了,实际上我们还有一种写法,叫做使用resultMap自定义封装结果集:
<resultMap type="com.wanbangee.entities.Emp" id="myEmp">
<id column="emp_id" property="empId"/>
<result column = "emp_name" property="empName"/>
<result column = "emp_mail" property="empMail"/>
<result column = "emp_gender" property="empGender"/>
<result column = "dept_id" property="deptId"/>
<result column = "dept_id" property="dept.deptId"/>
<result column = "dept_name" property="dept.deptName"/>
</resultMap>
<!--
public Emp selectEmpById(Integer id);
-->
<select id="selectEmpById" resultMap="myEmp" databaseId="mysql">
select a.emp_id,a.emp_name,a.emp_mail,a.emp_gender,a.dept_id,b.dept_name from emp a
left join dept b on a.dept_id = b.dept_id where emp_id = #{id}
</select>
association关联映射
Association是MyBatis中提供的一个多对一的关联映射的一个标签,使用在resultMap标签中,表示多对一的关系,在多的一端引用一的一端,比如雇员和部门的关系,每个雇员都存在于一个部门。现在查询雇员的同时要关联对应的部门,雇员对应的部门只有一个,这个时候我们就可以使用association进行关联映射:
<!-- 自定义封装结果集映射
type : 封装后结果集的类型全类名或者别名
id : 表示自定义封装结果的id,唯一的
id 标签: 表示自定义主键封装映射规则
result 标签:表示自定义非主键封装映射规则
- column:查询结果集的列名
- property : 列明对应的属性名
在select标签,如果要使用自定义封装结果集,必须使用resultMap属性声明,而且resultMap和resultType 不能同时存在
association : 使用之后,可以将引用属性进行分装,比如Emp中封装dept属性
-->
<resultMap type="com.wanbangee.entities.Emp" id="myEmp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="emp_mail" property="empMail"/>
<result column="emp_gender" property="empGender"/>
<result column="dept_id" property="deptId"/>
<association property="dept" javaType="com.wanbangee.entities.Dept">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
</association>
</resultMap>
<!--
public Emp selectEmpById(Integer id);
-->
<select id="selectEmpById" resultMap="myEmp" databaseId="mysql">
select a.emp_id,a.emp_name,a.emp_mail,a.emp_gender,a.dept_id,b.dept_name from emp a
left join dept b on a.dept_id = b.dept_id where emp_id = #{id}
</select>
以上的查询虽然结果正确,但是在数据量特别大的时候,效率很低,因为关联查询一定存在笛卡尔乘积现象。所以后期开发中,几乎不会使用这种封装形式。而是使用分布查询。
association分步查询
Association是支持进行分布查询的,第一步先查询雇员信息,第二步查询对应 部门信息。
<!-- 自定义封装结果集映射
association : 分步查询
property : 设置需要封装的属性
select : 调用其他的查询方法
column : 查询所需要传递的参数
-->
<resultMap type="com.wanbangee.entities.Emp" id="myEmp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="emp_mail" property="empMail"/>
<result column="emp_gender" property="empGender"/>
<result column="dept_id" property="deptId"/>
<association property="dept" select="com.wanbangee.dao.DeptDaoPlus.selectDeptByDeptId" column="dept_id">
</association>
</resultMap>
<!--
public Emp selectEmpById(Integer id);
-->
<select id="selectEmpById" resultMap="myEmp" databaseId="mysql">
select a.emp_id,a.emp_name,a.emp_mail,a.emp_gender,a.dept_id from emp a
where a.emp_id = #{id}
</select>
在对应DeptDao和DeptDao.xml中写selectDeptByDeptId()方法和配置
/**
* 根据部门id查询部门信息
* @param deptId
* @return
*/
public Dept selectDeptByDeptId(Integer deptId);
<resultMap type="dept" id="deptMap">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
</resultMap>
<!-- 根据Id查询部门信息 -->
<select id="selectDeptByDeptId" resultMap="deptMap">
select * from dept where dept_id = #{deptId}
</select>
association分步查询,延迟加载
延迟加载策略可以大大的提升数据库的查询性能,比如我们在查询Emp对象的时候,不应该将Dept查询出来,而是要在Emp对象需要使用Dept的时候,再进行查询部门,这种查询策略叫做延迟加载策略,又称按需加载,又称懒加载。默认情况下,MyBatis提供的分布查询策略就是即时加载,所以我们要通过配置开启延迟加载策略。
①:改变MyBatis运行时的行为,表示要开启延迟加载
<!-- 全局配置文件 开启延迟加载策略 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
添加了上述配置之后,我们分布查询就是延迟加载策略了,那么在开启了延迟加载策略之后,某些配置需要即时加载,有怎么配置呢?我们可以在association标签中配置fetchType属性:
<!-- 自定义封装结果集映射
type : 封装后结果集的类型全类名或者别名
id : 表示自定义封装结果的id,唯一的
id 标签: 表示自定义主键封装映射规则
result 标签:表示自定义非主键封装映射规则
- column:查询结果集的列名
- property : 列明对应的属性名
在select标签,如果要使用自定义封装结果集,必须使用resultMap属性声明,而且resultMap和resultType 不能同时存在
association : 使用之后,可以将引用属性进行分装,比如Emp中封装dept属性
association : 分步查询
property : 设置需要封装的属性
select : 调用其他的查询方法
column : 查询所需要传递的参数
fetchType : 设置提取策略
- lazy : 默认为延迟加载
- eager : 即时加载
-->
<resultMap type="com.wanbangee.entities.Emp" id="myEmp">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="emp_mail" property="empMail"/>
<result column="emp_gender" property="empGender"/>
<result column="dept_id" property="deptId"/>
<association property="dept" select="com.wanbangee.dao.DeptDaoPlus.selectDeptByDeptId" column="dept_id" fetchType="eager">
</association>
</resultMap>
collection分布查询及延迟加载
<!--
public Dept selectDeptByDeptId(Integer deptId);
-->
<resultMap type="com.wanbangee.entities.Dept" id="myDept">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
<collection property="emps" select="com.wanbangee.dao.EmpDaoPlus.selectEmpByDeptId" column="dept_id" javaType="java.util.List" >
</collection>
</resultMap>
<select id="selectDeptByDeptId" resultMap="myDept">
select * from dept where dept_id = #{deptId}
</select>
相应是编写empDao中的查询
/**
* 根据部门id查询员工的信息
* @param EmpId
* @return
*/
public Emp selectEmpByDeptId(Integer deptId);
//配置
!-- 根据部门Id查询员工信息 -->
<select id="selectEmpByDeptId" resultType="com.nhkj.entity.Emp">
select * from emp where dept_id = #{deptId}
</select>
分布查询传递多列值的情况
不管是association还是collection,上面的程序只传递了一列值作为分布查询的查询条件参数,在开发中,还存在另外一种情况,分布查询值传递两个参数,也就意味着传递两列值作为分布查询的查询条件参数
<resultMap type="com.wanbangee.entities.Dept" id="myDept">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
<!--
在传递多列值的情况下使用{abc=dept_id,bcd=dept_name},根据我们参数规则,
多个参数在MyBatis中会封装了Map键值对,此时Map封装的效果如下:
key ======================value
abc dept_id数据列的值
bcd dept_name数据列的值
-->
<collection property="emps" select="com.wanbangee.dao.EmpDaoPlus.selectEmpByDeptId"
column="{abc=dept_id,bcd=dept_name}" javaType="java.util.List" >
</collection>
</resultMap>