Mybatis映射文件
输入映射
ParameterType
指定输入参数的java类型,可以使用别名或者类的全限定名。它可以接收简单类型、POJO、HashMap。
传递简单类型
参考需求:根据用户ID查询用户信息。
传递POJO对象
参考需求:添加用户。
传递POJO包装对象
开发中通过pojo传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
需求
综合查询用户信息,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息)。
定义包装对象
一般User.java类要和数据表表字段一致,最好不要在这里面添加其他字段.
所以针对要扩展的po类,我们需要创建一个扩展类,来继承它。
编写Mapper接口
通过包装类来进行复杂的用户信息综合查询
public List<UserExt> findUserList(UserQueryVO userQueryVO);
编写mapper映射文件
<!-- 通过包装类来进行复杂的用户信息综合查询 -->
<select id="findUserList" parameterType="userQueryVO" resultType="userExt">
SELECT * FROM USER WHERE sex=#{userExt.sex} AND username LIKE '%${userExt.username}%'
</select>
注意:入参的类型变为UserQueryVO、结果集的类型变为UserExt,#{}里面的参数变为UserQueryVO对象中的userExt属性的sex和username子属性。
编写测试代码
@Test
public void findUserListTest() {
// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession,获取mapper接口的动态代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//构造userQueryVO对象
UserQueryVO userQueryVO = new UserQueryVO();
// 构造UserExt对象
UserExt userExt = new UserExt();
userExt.setSex("1");
userExt.setUsername("小明");
userQueryVO.setUserExt(userExt);
// 调用mapper对象的方法
List<UserExt> list = userMapper.findUserList(userQueryVO);
System.out.println(list);
// 关闭SqlSession
sqlSession.close();
}
传递HashMap
同传递POJO对象一样,map的key相当于pojo的属性。
映射文件
<!-- 传递hashmap综合查询用户信息 -->
<select id="findUserByHashmap" parameterType="hashmap" resultType="user">
select * from user where id=#{id} and username like '%${username}%'
</select>。
测试代码
Public void testFindUserByHashmap()throws Exception{
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//构造查询条件Hashmap对象
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("id", 1);
map.put("username", "管理员");
//传递Hashmap对象查询用户列表
List<User>list = userMapper.findUserByHashmap(map);
//关闭session
session.close();
}
异常测试:传递的map中的key和sql中解析的key不一致。测试结果没有报错,只是通过key获取值为空。
输出映射
resultType
先看下原先resultType作为输出结果映射时,它的特点,如何再把列名改为别名,看看是否还能不能映射成功。
使用方法
使用resultType进行结果映射时,查询的列名和映射的pojo属性名完全一致,该列才能映射成功。
如果查询的列名和映射的pojo属性名全部不一致,则不会创建pojo对象;
如果查询的列名和映射的pojo属性名有一个一致,就会创建pojo对象。
输出简单类型
当输出结果只有一列时,可以使用ResultType指定简单类型作为输出结果类型。
需求
综合查询用户总数,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息)。
Mapper映射文件
<!-- 综合查询用户信息总数,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息) -->
<select id="findUsersCount" parameterType="UserQueryVO"
resultType="int">
SELECT count(1) FROM USER WHERE sex = #{userExt.sex} AND username LIKE '%${userExt.username}%'
</select>
Mapper接口
//综合查询用户信息总数。学习:resultType输出简单类型
public int findUsersCount(UserQueryVO vo);
测试代码
@Test
public void testFindUsersCount() {
// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession,获取mapper接口的动态代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//构造userQueryVO对象
UserQueryVO userQueryVO = new UserQueryVO();
// 构造UserExt对象
UserExt userExt = new UserExt();
userExt.setSex("1");
userExt.setUsername("小明");
userQueryVO.setUserExt(userExt);
int count = mapper.findUsersCount(userQueryVO);
System.out.println(count); // 关闭SqlSession
sqlSession.close();
}
输出POJO单个对象和列表
注意:输出单个pojo对象和pojo列表(盛放pojo对象)时,mapper映射文件中的resultType的类型是一样的,mapper接口的方法返回值不同。
Mapper映射文件
Mapper映射文件是同一个
<select id="findUsersByName" parameterType="java.lang.String" resultType="cn.java.mybatis.po.User">
SELECT * FROM USER WHERE username LIKE '%${value}%'
</select>
Mapper接口
下面看下mapper接口的不同之处
1、输出单个pojo对象
//根据用户名称来模糊查询用户信息
public User findUsersByName(String username);
2、输出pojo列表
//根据用户名称来模糊查询用户信息列表
public List<User> findUsersByName(String username);
总结:同样的mapper映射文件,返回单个对象和对象列表时,mapper接口在生成动态代理的时候,会根据返回值的类型,决定调用selectOne方法还是selectList方法。
resultMap
resultMap可以进行高级结果映射(一对一、一对多映射)。
使用方法
如果查询出来的列名和属性名不一致,通过定义一个resultMap将列名和pojo属性名之间作一个映射关系。
1、定义resultMap
2、使用resultMap作为statement的输出映射类型。
需求
把下面SQL的输出结果集进行映射
SELECT id id_,username username_,sex sex_ FROM USER WHERE id = 1
Mapper映射文件
定义resultMap:
<!-- 定义resultMap -->
<!--
[id]:定义resultMap的唯一标识
[type]:定义该resultMap最终映射的pojo对象
[id标签]:映射结果集的唯一标识列,如果是多个字段联合唯一,则定义多个id标签
[result标签]:映射结果集的普通列
[column]:SQL查询的列名,如果列有别名,则该处填写别名
[property]:pojo对象的属性名 -->
<resultMap type="user" id="userResultMap">
<id column="id_" property="id"/>
<result column="username_" property="username"/>
<result column="sex_" property="sex"/>
</resultMap>
定义statement:
<!-- 根据ID查询用户信息(学习resultMap) -->
<select id="findUserByIdResultMap" parameterType="int" resultMap="userResultMap">
SELECT id id_,username username_,sex sex_ FROM USER WHERE id = #{id}
</select>
Mapper接口定义
//根据ID查询用户信息(学习resultMap)
public User findUserByIdResultMap(int id);
定义Statement使用resultMap映射结果集时,Mapper接口定义方法的返回值类型为mapper映射文件中resultMap的type类型。
测试代码
@Test
public void findUserByIdResultMapTest() {
// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession,获取mapper接口的动态代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 调用mapper对象的方法
User user = userMapper.findUserByIdResultMap(1);
System.out.println(user);
// 关闭SqlSession
sqlSession.close();
}
动态SQL
通过Mybatis提供的各种动态标签实现动态拼接sql,使得mapper映射文件在编写SQL时更加灵活,方便。常用动态SQL标签有:if、where、foreach;
If和where
If标签:作为判断入参来使用的,如果符合条件,则把if标签体内的SQL拼接上。
注意:用if进行判断是否为空时,不仅要判断null,也要判断空字符串‘’;
Where标签:会去掉条件中的第一个and符号。
需求
用户信息综合查询列表和用户信息综合查询总数这两个statement的定义使用动态SQL。
映射文件
<!-- 综合查询用户信息,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息) -->
<select id="findUsersByQueryVO" parameterType="cn.java.mybatis.po.QueryUserVO"
resultType="User">
SELECT * FROM USER
<where>
<if test="userExt != null">
<if test="userExt.sex != null and userExt.sex != ''">
AND sex = #{userExt.sex}
</if>
<if test="userExt.username != null and userExt.username != ''">
AND username LIKE '%${userExt.username}%'
</if>
</if>
</where>
</select>
<!-- 综合查询用户信息总数,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息) -->
<select id="findUsersCount" parameterType="QueryUserVO"
resultType="int">
SELECT count(1) FROM USER
<where>
<if test="userExt != null">
<if test="userExt.sex != null and userExt.sex != ''">
AND sex = #{userExt.sex}
</if>
<if test="userExt.username != null and userExt.username != ''">
AND username LIKE '%${userExt.username}%'
</if>
</if>
</where>
</select>
Mapper接口
//通过包装类来进行复杂的用户信息综合查询
public List<UserExt> findUserList(UserQueryVO userQueryVO);
//综合查询用户总数
public int findUsersCount(UserQueryVO userQueryVO);
SQL片段
Mybatis提供了SQL片段的功能,可以提高SQL的可重用性。
定义SQL片段
使用sql标签来定义一个SQL片段:
<!-- 定义SQL片段 -->
<!--
[sql标签]:定义一个SQL片段
[id]:SQL片段的唯一标识
建议:
1、SQL片段中的内容最好是以单表来定义
2、如果是查询字段,则不要写上SELECT
3、如果是条件语句,则不要写上WHERE
-->
<sql id="select_user_where">
<if test="userExt != null">
<if test="userExt.sex != null and userExt.sex != ''">
AND sex = #{userExt.sex}
</if>
<if test="userExt.username != null and userExt.username != ''">
AND username LIKE '%${userExt.username}%'
</if>
</if>
</sql>
引用SQL片段
使用 来引用SQL片段:
<!-- 根据用户id来查询用户信息(使用SQL片段) -->
<!--
[include标签]:引用已经定义好的SQL片段
[refid]:引用的SQL片段id
-->
<select id="findUserList" parameterType="userQueryVO" resultType="userExt">
SELECT * FROM USER
<where>
<include refid="select_user_where"/>
</where>
</select>
<!-- 综合查询用户信息总数,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息) -->
<select id="findUsersCount" parameterType="QueryUserVO"
resultType="int">
SELECT count(1) FROM USER
<where>
<include refid="select_user_where"/>
</where>
</select>
Foreach
向sql传递数组或List时,mybatis使用foreach解析数组里的参数并拼接到SQL中。
传递pojo对象中的List集合
需求
在用户查询列表和查询总数的statement中增加多个id输入查询。
SQL
SELECT * FROM user WHERE id IN (1,10,16)
定义pojo中的List属性
映射文件
<!-- [foreach标签]:表示一个foreach循环 -->
<!-- [collection]:集合参数的名称,如果是直接传入集合参数,则该处的参数名称只能填写[list]。 -->
<!-- [item]:每次遍历出来的对象 -->
<!-- [open]:开始遍历时拼接的串 -->
<!-- [close]:结束遍历时拼接的串 -->
<!-- [separator]:遍历出的每个对象之间需要拼接的字符 -->
<if test="idList != null and idList.size > 0">
<foreach collection="idList" item="id" open="AND id IN (" close=")" separator=",">
#{id}
</foreach>
</if>
Mapper接口
//根据用户ID的集合查询用户列表(学习foreach标签之通过POJO对象传ID集合)
public List<UserExt> findUserList(UserQueryVO vo);
测试代码
@Test
public void testFindUserList() {
// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession,获取mapper接口的动态代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 构造QueryUserVO对象
QueryUserVO vo = new QueryUserVO();
// UserExt ext = new UserExt();
// ext.setUsername("小明");
// ext.setSex("1");
// vo.setUserExt(ext);
// 创建用户ID集合,然后设置到QueryUserVO对象中
List<Integer> idList = new ArrayList<Integer>();
idList.add(1);
idList.add(10);
idList.add(16);
vo.setIdList(idList);
// 调用mapper代理对象的方法
List<UserExt> list = mapper.findUserList(vo);
System.out.println(list);
// 关闭SqlSession
sqlSession.close();
}
需求
根据用户ID的集合查询用户列表
SQL
SELECT * FROM user WHERE id IN (1,10,16)
映射文件
<!-- 根据用户ID的集合查询用户列表(学习foreach标签之直接传ID集合) -->
<!--
[foreach标签]:表示一个foreach循环
[collection]:集合参数的名称,如果是直接传入集合参数,则该处的参数名称只能填写[list]。
[item]:定义遍历集合之后的参数名称
[open]:开始遍历之前需要拼接的SQL串
[close]:结束遍历之后需要拼接的SQL串
[separator]:遍历出的每个对象之间需要拼接的字符
-->
<select id="findUsersByIdList" parameterType="java.util.List" resultType="user">
SELECT * FROM USER
<where>
<if test="list[如果是直接传入集合参数,则该处的参数名称只能填写[list]] != null and list.size > 0">
<foreach collection="list" item="id" open="AND id IN (" close=")" separator=",">
#{id}
</foreach>
</if>
</where>
</select>
Mapper接口
//根据用户ID的集合查询用户列表(学习foreach标签之直接传ID集合)
public List<User> findUsersByIdList (List<Integer> idList);
测试代码
@Test
public void findUsersByIdListTest() {
// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession,获取mapper接口的动态代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 构造List<Integer>集合
List<Integer> idList = new ArrayList<Integer>();
idList.add(1);
idList.add(10);
idList.add(16);
// 调用mapper对象的方法
List<User> list = userMapper.findUsersByIdList (idList);
System.out.println(list);
// 关闭SqlSession
sqlSession.close();
}
mybatis与hibernate的区别及各自应用场景
Mybatis技术特点:
1、通过直接编写SQL语句,可以直接对SQL进行性能的优化;
2、学习门槛低,学习成本低。只要有SQL基础,就可以学习mybatis,而且很容易上手;
3、由于直接编写SQL语句,所以灵活多变,代码维护性更好。
4、不能支持数据库无关性,即数据库发生变更,要写多套代码进行支持,移植性不好。
5、需要编写结果映射。
Hibernate技术特点:
1、标准的orm框架,程序员不需要编写SQL语句。
2、具有良好的数据库无关性,即数据库发生变化的话,代码无需再次编写。
3、学习门槛高,需要对数据关系模型有良好的基础,而且在设置OR映射的时候,需要考虑好性能和对象模型的权衡。
4、程序员不能自主的去进行SQL性能优化。
Mybatis应用场景:
需求多变的互联网项目,例如电商项目。
Hibernate应用场景:
需求明确、业务固定的项目,例如OA项目、ERP项目等。
关联查询映射
分析数据模型
思路
1、每张表记录的数据内容 分模块对每张表记录的内容进行熟悉,相当于你学习系统需求(功能)的过程。
2、每张表重要的字段 主键、外键、非空字段
3、数据库级别表与表的关系 外键关系
4、表与表之间的业务关系 在分析表与表之间的业务关系时一定要建立 在某个业务意义基础上去分析。
数据库表之间有外键关系的业务关系
user和orders:
user---->orders:一个用户可以创建多个订单,一对多
orders--->user:一个订单只由一个用户创建,一对一
orders和orderdetail:
orders-orderdetail:一个订单可以包括 多个订单明细,因为一个订单可以购买多个商品,每个商品的购买信息在orderdetail记录,一对多关系
orderdetail--> orders:一个订单明细只能包括在一个订单中,一对一
orderdetail和itesm:
orderdetail---》itesms:一个订单明细只对应一个商品信息,一对一
items--> orderdetail:一个商品可以包括在多个订单明细 ,一对多
数据库表之间没有外键关系的业务关系’
Orders和items:
这两张表没有直接的外键关系,通过业务及数据库的间接关系分析出它们是多对多的关系。
Orders orderdetail –>items:一个订单可以有多个订单明细,一个订单明细对应一个商品,所以一个订单对应多个商品
Items-orderdetailorders:一个商品可以对应多个订单明细,一个订单明细对应一个订单,所以一个商品对应多个订单
User和items:
这两张表没有直接的外键关系,通过业务及数据库的间接关系分析出它们是多对多的关系。
Userordersorderdetailitems:一个用户有多个订单,一个订单有多个订单明细、一个订单明细对应一个商品,所以一个用户对应多个商品
Itemsorderdetailordersuser:一个商品对应多个订单明细,一个订单明细对应一个订单,一个订单对应一个用户,所以一个商品对应多个用户
一对一查询
需求
查询订单信息,关联查询创建订单的用户信息
SQL语句
确定查询的主表:订单表
确定查询的关联表:用户表
关联查询使用内链接?还是外链接?
Select
Orders.id,
Orders.user_id,
orders.number,
orders.createtime,
orders.note,
user.username,
user.address
from orders,user
where orders.user_id = user.id
resultType
复杂查询时,单表对应的po类已不能满足输出结果集的映射。
所以要根据需求建立一个扩展类来作为resultType的类型。
创建po类
//通过此类映射订单和用户查询的结果,让此类继承包括 字段较多的pojo类
public class OrdersExt extends Orders{
//添加用户属性
/*USER.username,
USER.address */
private String username;
private String address;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
编写mapper接口
创建OrdersMapper接口类,在类中添加以下内容:
// 进行订单信息查询,包括用户的名称和地址信息
public List<OrdersExt> findOrdersUser();
编写映射文件
<mapper namespace="cn.java.mybatis.mapper.OrdersMapper">
<!-- 定义查询订单表列名的SQL片段 -->
<sql id="select_orders">
Orders.id,
Orders.user_id,
orders.number,
orders.createtime,
orders.note
</sql>
<!-- 定义查询用户表列名的SQL片段 -->
<sql id="select_user">
user.username,
user.address
</sql>
<!-- 进行订单信息查询,包括用户的名称和地址信息 -->
<select id="findOrdersUser" resultType="OrdersExt">
Select
<include refid="select_orders" />
<include refid="select_user"></include>
from orders,user
where orders.user_id = user.id
</select>
</mapper>
加载映射文件
<!-- 批量加载mapper文件,需要mapper接口文件和mapper映射文件名称相同且在同一个包下 -->
<package name="cn.java.mybatis.mapper"/>
编写测试代码
@Test
public void testFindOrdersUser() {
// 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession构造usermapper的代理对象
OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
// 调用usermapper的方法
List<OrdersExt> list = ordersMapper.findOrdersUser();
System.out.println(list);
// 释放SqlSession
sqlSession.close();
}
resultMap
修改po类
在Orders类中,添加User对象
public class Orders {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
//用户信息
private User user;
编写mapper接口
// 进行订单信息查询,包括用户的名称和地址信息(resultMap)
public List<OrdersExt> findOrdersUserRstMap();
编写映射文件
<!-- 进行订单信息查询,包括用户的名称和地址信息 (ResultMap) -->
<select id="findOrdersUserRstMap" resultMap="OrdersUserRstMap">
Select
<include refid="select_orders" />
,
<include refid="select_user"></include>
from orders,user
where orders.user_id = user.id
</select>
<!-- 定义orderUserResultMap -->
<resultMap type=" cn.java.mybatis.po.Orders" id="OrdersUserRstMap">
<id column="id" property="id" />
<result column="user_id" property="userId" />
<result column="number" property="number" />
<result column="createtime" property="createtime" />
<result column="note" property="note" />
<!-- 映射一对一关联关系的用户对象-->
<!--
property:指定关联对象要映射到Orders的哪个属性上
javaType:指定关联对象所要映射的java类型
-->
<!-- id标签:指定关联对象结果集的唯一标识,很重要,不写不会报错,但是会影响性能 -->
<association property="user" javaType="cn.java.mybatis.po.User">
<id column="user_id" property="id" />
<result column="username" property="username" />
<result column="address" property="address" />
</association>
</resultMap>
编写测试代码
@Test
public void testFindOrdersUserRstMap() {
// 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession构造usermapper的代理对象
OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
// 调用usermapper的方法
List<Orders> list = ordersMapper.findOrdersUserRstMap();
//此处我们采用debug模式来跟踪代码,然后验证结果集是否正确
System.out.println(list);
// 释放SqlSession
sqlSession.close();
}
一对一小结
实现一对一查询:
resultType:使用resultType实现较为简单,如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。
如果没有查询结果的特殊要求建议使用resultType。
resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射pojo的对象属性中。
resultMap可以实现延迟加载,resultType无法实现延迟加载。
一对多查询
一对多查询和一对一查询的配置基本类似。只是如果使用resultMap的话,映射一对多关联关系要使用collection标签。
需求
查询订单信息及订单明细信息
SQL语句
确定主查询表:订单表
确定关联查询表:订单明细表
在一对一查询基础上添加订单明细表关联即可。
Select
Orders.id,
Orders.user_id,
orders.number,
orders.createtime,
orders.note,
user.username,
user.address,
orderdetail.id detail_id,
orderdetail.items_id,
orderdetail.items_num
from orders,user,orderdetail
where orders.user_id = user.id
and orders.id = orderdetail.orders_id
分析
使用resultType将上边的 查询结果映射到pojo中,订单信息将会重复。
要求:
对orders映射不能出现重复记录。
在orders.java类中添加List detailList属性。最终会将订单信息映射到orders中,订单所对应的订单明细映射到orders中的detailList属性中。
映射成的orders记录数为两条(orders信息不重复)
每个orders中的detailList属性存储了该订单所对应的订单明细集合。
修改PO类
在Orders类中添加以下属性,并提供get/set方法:
//订单明细
private List<Orderdetail> detailList;
编写mapper接口
// 查询订单信息及订单明细信息(一对多映射之使用resultMap)
public List<Orders> findOrdersAndOrderdetailRstMap();
编写映射文件
<!-- 定义OrdersAndOrderdetailRstMap -->
<!-- extends:继承已有的ResultMap,值为继承的ResultMap的唯一标示 -->
<resultMap type="Orders" id="OrdersAndOrderdetailRstMap"
extends="OrdersUserRstMap">
<!-- 映射关联关系(一对多) -->
<!-- collection标签:定义一个一对多关系
ofType:指定该集合参数所映射的类型
-->
<collection property="detailList" ofType="Orderdetail">
<id column="detail_id" property="id" />
<result column="items_id" property="itemsId" />
<result column="items_num" property="itemsNum" />
</collection>
</resultMap>
<!-- 查询订单信息,包括用户名称、用户地址,订单商品信息(嵌套结果) -->
<select id="findOrdersAndOrderdetailRstMap" resultMap="OrdersAndOrderdetailRstMap">
Select
<include refid="select_orders" />
,
<include refid="select_user"/>
,
orderdetail.id detail_id,
orderdetail.items_id,
orderdetail.items_num
from orders,user,orderdetail
where orders.user_id = user.id
and
orders.id = orderdetail.orders_id
</select>
resultMap的extends属性:可以用此属性来继承一个已有的resultmap。但是它继承的resultMap的type和它本身的type要保持一致
编写测试代码
@Test
public void testFindOrdersAndOrderdetailRstMap() {
// 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession构造usermapper的代理对象
OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
// 调用usermapper的方法
List<Orders> list = ordersMapper.findOrdersAndOrderdetailRstMap();
//此处我们采用debug模式来跟踪代码,然后验证结果集是否正确
System.out.println(list);
// 释放SqlSession
sqlSession.close();
}
一对多小结
mybatis使用resultMap的collection对关联查询的多条记录映射到一个list集合属性中。
使用resultType实现:
需要对结果集进行二次处理。将订单明细映射到orders中的orderdetails中,需要自己处理,使用双重循环遍历,去掉重复记录,将订单明细放在orderdetails中。
多对多查询
需求
查询用户信息及用户购买的商品信息,要求将关联信息映射到主pojo的pojo属性中
SQL语句
查询主表:user
查询关联表:orders、orderdetail、items
Select
Orders.id,
Orders.user_id,
orders.number,
orders.createtime,
orders.note,
user.username,
user.address,
orderdetail.id detail_id,
orderdetail.items_id,
orderdetail.items_num
items.name items_name,
items.detail items_detail
FROM
orders,
USER,
orderdetail,
items
WHERE user.`id` = orders.`user_id`
AND orders.`id` = orderdetail.`orders_id`
AND orderdetail.`items_id` = items.`id`
映射思路
将用户信息映射到user中。
在user类中添加订单列表属性List<Orders> orderslist,将用户创建的订单映射到orderslist
在Orders中添加订单明细列表属性List<Orderdetail> detailList,将订单的明细映射到detailList
在Orderdetail中添加Items属性,将订单明细所对应的商品映射到Items
修改PO类
在user类中添加List ordersList 属性
// 订单信息
private List<Orders> ordersList;
在Orders类中添加List属性
//订单明细
private List<Orderdetail> detailList;
在Orderdetail类中添加Items属性
//商品信息
private Items items;
编写mapper接口
//查询用户及用户购买商品信息(多对多映射之使用resultMap)
public List<User> findUserAndItemsRstMap();
编写映射文件
<!-- 定义UserAndItemsRstMap -->
<resultMap type="User" id="UserAndItemsRstMap">
<!-- 用户信息 -->
<!-- id:关联查询用户的唯一标示 -->
<id column="user_id" property="id" />
<result column="username" property="username" />
<result column="address" property="address" />
<!-- 订单信息 (一个用户有多个订单) -->
<collection property="ordersList" ofType="orders">
<id column="id" property="id" />
<result column="user_id" property="userId" />
<result column="number" property="number" />
<result column="createtime" property="createtime" />
<result column="note" property="note" />
<!-- 订单明细信息(一个订单有多个订单明细) -->
<collection property="detailList" ofType="orderdetail">
<id column="detail_id" property="id" />
<result column="items_id" property="itemsId" />
<result column="items_num" property="itemsNum" />
<!-- 商品信息 (一个订单明细对应一个商品) -->
<association property="items" javaType="cn.java.mybatis.po.Items">
<id column="items_id" property="id" />
<result column="items_name" property="name" />
<result column="items_detail" property="detail" />
</association>
</collection>
</collection>
</resultMap>
<!-- 查询用户及用户购买商品信息(多对多映射之使用resultMap) -->
<select id="findUserAndItemsRstMap" resultMap="UserAndItemsRstMap">
Select
<include refid="select_orders" />
,
<include refid="select_user" />
,
<include refid="select_orderdetail"></include>
,
items.name items_name,
items.detail items_detail
from
orders,user,orderdetail,items
where orders.user_id = user.id
and
orders.id = orderdetail.orders_id
and orderdetail.items_id = items.id
</select>
编写测试代码
@Test
public void testFindUserAndItemsRstMap() {
// 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession构造usermapper的代理对象
OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
// 调用usermapper的方法
List<User> list = ordersMapper.findUserAndItemsRstMap();
// 此处我们采用debug模式来跟踪代码,然后验证结果集是否正确
System.out.println(list);
// 释放SqlSession
sqlSession.close();
}
多对多查询小结
将查询用户购买的商品信息明细清单,(用户名、用户地址、购买商品名称、购买商品时间、购买商品数量)
针对上边的需求就使用resultType将查询到的记录映射到一个扩展的pojo中,很简单实现明细清单的功能。
一对多是多对多的特例,如下需求:
查询用户购买的商品信息,用户和商品的关系是多对多关系。
查询字段:用户账号、用户名称、用户性别、商品名称、商品价格(最常见)
企业开发中常见明细列表,用户购买商品明细列表,
使用resultType将上边查询列映射到pojo输出。
查询字段:用户账号、用户名称、购买商品数量、商品明细(鼠标移上显示明细)
使用resultMap将用户购买的商品明细列表映射到user对象中。
总结:
使用resultMap是针对那些对查询结果映射有特殊要求的功能,,比如特殊要求映射成list中包括 多个list。
高级映射总结
resultType:
作用:
将查询结果按照sql列名pojo属性名一致性映射到pojo中。
场合:
常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到pojo中,在前端页面遍历list(list中是pojo)即可。
resultMap:
使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。
association:
作用:
将关联查询信息映射到一个pojo对象中。
场合:
为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的pojo属性中,比如:查询订单及关联用户信息。
使用resultType无法将查询结果映射到pojo对象的pojo属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap。
collection:
作用:
将关联查询信息映射到一个list集合中。
场合:
为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样的作的目的也是方便对查询结果集进行遍历查询。
如果使用resultType无法将查询结果映射到list集合中。
延迟加载
什么是延迟加载
resultMap中的association和collection标签具有延迟加载的功能。
延迟加载的意思是说,在关联查询时,利用延迟加载,先加载主信息。需要关联信息时再去按需加载关联信息。这样会大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
设置延迟加载
Mybatis默认是不开启延迟加载功能的,我们需要手动开启。需要在SqlMapConfig.xml文件中,在标签中开启延迟加载功能.
lazyLoadingEnabled、aggressiveLazyLoading
设置项 描述 允许值 默认值
lazyLoadingEnabled 全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。 true | false true
aggressiveLazyLoading 当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。 true | false true
使用association进行延迟加载
需求
查询订单并且关联查询用户信息(对用户信息的加载要求是按需加载)
编写映射文件
需要定义两个mapper的方法对应的statement。
1、只查询订单信息
SELECT * FROM orders
在查询订单的statement中使用association去延迟加载(执行)下边的satatement(关联查询用户信息)
<!-- 定义OrdersUserLazyLoadingRstMap -->
<resultMap type="cn.java.mybatis.po.Orders" id="OrdersUserLazyLoadingRstMap">
<id column="id" property="id" />
<result column="user_id" property="userId" />
<result column="number" property="number" />
<result column="createtime" property="createtime" />
<result column="note" property="note" />
<!-- 延迟加载用户信息 -->
<!-- select:指定延迟加载需要执行的statement的id(是根据user_id查询用户信息的statement)
我们使用UserMapper.xml中的findUserById完成根据用户ID(user_id)查询用户信息
如果findUserById不在本mapper中,前边需要加namespace -->
<!-- column:主信息表中需要关联查询的列,此处是user_id -->
<association property="user" select="cn.java.mybatis.mapper.UserMapper.findUserById" column="user_id"></association>
</resultMap>
<!-- 查询订单信息,延迟加载关联查询的用户信息 -->
<select id="findOrdersUserLazyLoading" resultMap="OrdersUserLazyLoadingRstMap">
SELECT * FROM orders
</select>
2、关联查询用户信息
通过上边查询到的订单信息中user_id去关联查询用户信息
使用UserMapper.xml中的findUserById
<select id="findUserById" parameterType="int"
resultType="cn.java.mybatis.po.User">
SELECT * FROM user WHERE id = #{id}
</select>
上边先去执行findOrdersUserLazyLoading,当需要去查询用户的时候再去执行findUserById,通过resultMap的定义将延迟加载执行配置起来。
加载映射文件
<!-- 批量加载mapper文件,需要mapper接口文件和mapper映射文件名称相同且在同一个包下 -->
<package name="cn.java.mybatis.mapper"/>
编写mapper接口
// 查询订单信息,延迟加载关联查询的用户信息
public List<Orders> findOrdersUserLazyLoading();
编写测试代码
思路:
1、执行上边mapper方法(findOrdersUserLazyLoading),内部去调用cn.java.mybatis.mapper.OrdersMapper中的findOrdersUserLazyLoading只查询orders信息(单表)。
2、在程序中去遍历上一步骤查询出的List<Orders>,当我们调用Orders中的getUser方法时,开始进行延迟加载。
3、执行延迟加载,去调用UserMapper.xml中findUserbyId这个方法获取用户信息。
@Test
public void testFindOrdersUserLazyLoading() {
// 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession构造usermapper的代理对象
OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
// 调用usermapper的方法
List<Orders> list = ordersMapper.findOrdersUserLazyLoading();
for(Orders orders : list){
System.out.println(orders.getUser());
}
// 释放SqlSession
sqlSession.close();
延迟加载思考
不使用mybatis提供的association及collection中的延迟加载功能,如何实现延迟加载??
实现方法如下:
定义两个mapper方法:
1、查询订单列表
2、根据用户id查询用户信息
实现思路:
先去查询第一个mapper方法,获取订单信息列表
在程序中(service),按需去调用第二个mapper方法去查询用户信息。
总之:
使用延迟加载方法,先去查询简单的sql(最好单表,也可以关联查询),再去按需要加载关联查询的其它信息。
查询缓存
mybatis缓存分析
mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
一级缓存
原理
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
Mybatis默认支持一级缓存。
测试
@Test
public void testOneLevelCache() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
User user1 = mapper.findUserById(1);
System.out.println(user1);
// 第二次查询ID为1的用户
User user2 = mapper.findUserById(1);
System.out.println(user2);
sqlSession.close();
}
只输出一次SQL:
测试2
@Test
public void testOneLevelCache() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
User user1 = mapper.findUserById(1);
System.out.println(user1);
User user = new User();
user.setUsername("东哥1");
user.setAddress("清河宝盛西里");
//执行增删改操作,清空缓存
mapper.insertUser(user);
// 第二次查询ID为1的用户
User user2 = mapper.findUserById(1);
System.out.println(user2);
sqlSession.close();
}
中间执行了commit操作,同样的查询SQL输出两次:
应用
正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
一个service方法中包括 很多mapper方法调用。
service{
//开始执行时,开启事务,创建SqlSession对象
//第一次调用mapper的方法findUserById(1)
//第二次调用mapper的方法findUserById(1),从一级缓存中取数据
//方法结束,sqlSession关闭
}
如果是执行两次service调用查询相同 的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空。\
二级缓存
原理
下图是多个sqlSession请求UserMapper的二级缓存图解。
二级缓存是mapper级别的。
第一次调用mapper下的SQL去查询用户信息。查询到的信息会存到该mapper对应的二级缓存区域内。
第二次调用相同namespace下的mapper映射文件中相同的SQL去查询用户信息。会去对应的二级缓存内取结果。
如果调用相同namespace下的mapper映射文件中的增删改SQL,并执行了commit操作。此时会清空该namespace下的二级缓存。
开启二级缓存
Mybatis默认是没有开启二级缓存
1、在核心配置文件SqlMapConfig.xml中加入以下内容(开启二级缓存总开关):
在settings标签中添加以下内容:
<!-- 开启二级缓存总开关 -->
<setting name="cacheEnabled" value="true"/>
2、在UserMapper映射文件中,加入以下内容,开启二级缓存:
<!-- 开启本mapper下的namespace的二级缓存,默认使用的是mybatis提供的PerpetualCache -->
<cache></cache>
实现序列化
由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,所以需要给缓存的对象执行序列化。
如果该类存在父类,那么父类也要实现序列化。
测试1
@Test
public void testTwoLevelCache() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
// 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
User user1 = mapper1.findUserById(1);
System.out.println(user1);
// 关闭SqlSession1
sqlSession1.close();
// 第二次查询ID为1的用户
User user2 = mapper2.findUserById(1);
System.out.println(user2);
// 关闭SqlSession2
sqlSession2.close();
}
SQL输出结果:
Cache Hit Radio : 缓存命中率
第一次缓存中没有记录,则命中率0.0;
第二次缓存中有记录,则命中率0.5(访问两次,有一次命中)
测试2
@Test
public void testTwoLevelCache() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
// 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
User user1 = mapper1.findUserById(1);
System.out.println(user1);
// 关闭SqlSession1
sqlSession1.close();
//修改查询出来的user1对象,作为插入语句的参数
user1.setUsername("东哥1");
user1.setAddress("清河宝盛西里");
mapper3.insertUser(user1);
// 提交事务
sqlSession3.commit();
// 关闭SqlSession3
sqlSession3.close();
// 第二次查询ID为1的用户
User user2 = mapper2.findUserById(1);
System.out.println(user2);
// 关闭SqlSession2
sqlSession2.close();
}
SQL输出结果:
根据SQL分析,确实是清空了二级缓存了。
禁用二级缓存
该statement中设置userCache=false,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。
<select id="findUserById" parameterType="int"
resultType="cn.java.mybatis.po.User" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
刷新二级缓存
该statement中设置flushCache=true可以刷新当前的二级缓存,默认情况下如果是select语句,那么flushCache是false。如果是insert、update、delete语句,那么flushCache是true。
如果查询语句设置成true,那么每次查询都是去数据库查询,即意味着该查询的二级缓存失效。
如果查询语句设置成false,即使用二级缓存,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现脏读。
flushCache设置如下: