一 Mybatis高级查询
1.1 ResutlMap属性
建立对象关系映射
* resultType如果实体的属性名与表中字段名一致,将查询结果自动封装到实体类中* ResutlMap如果实体的属性名与表中字段名不一致,可以使用 ResutlMap 实现手动封装到实体类中
UserMapper.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.bigload.mapper.UserMapper">
<!-- 实现手动映射封装
resultMap
id="userResultMap" 此标签唯一标识
type="user" 封装后的实体类型
<id column="uid" property="id"></id> 表中主键字段封装
column="uid" 表中的字段名
property="id" user实体的属性名
<result column="NAME" property="username"></result>
表中普通字段封装 column="NAME"
表中的字段名 property="username" user实体的属性名
补充:如果有查询结果有 字段与属性是对应的,可以省略手动封装 【了解】 -->
<resultMap id="userResultMap" type="user">
<id column="uid" property="id"></id>
<result column="NAME" property="name"></result>
<result column="PASSWORD" property="password"></result>
</resultMap>
<select id="findAllResultMap" resultMap="userResultMap"> SELECT id AS uid,name AS NAME,password AS PASSWORD FROM user </select>
</mapper>
1.2 多条件查询(三种)
需求
根据id和username查询user表
使用 #{arg0}-#{argn} 或者 #{param1}-#{paramn} 获取参数
注意:arg的下标从0开始,param的下标从1开始
UserMapper接口
/*多条件查询*/
public List<User> findMoreUser(Integer id,String name);
UserMapper.xml
<select id="findMoreUser" resultType="user">
-- select * from user where id=#{arg0} and name=#{arg1}
select * from user where id=#{param1} and name=#{param2}
</select>
使用注解,引入 @Param() 注解获取参数
UserMapper接口
/*用注解实现多条件查询*/
public List<User> findMoreUserOfParam(@Param("id") Integer id,@Param("name") String name);
UserMapper.xml
<select id="findMoreUserOfParam" resultType="user">
select * from user where id=#{id} and name=#{name}
</select>
使用pojo对象传递参数
UserMapper接口
/*使用对象实现多条件查询*/
public List<User> findMoreUserOfUser(User user);
UserMapper.xml
<select id="findMoreUserOfUser" resultType="user" parameterType="user">
select * from user where id=#{id} and name=#{name}
</select>
测试
public void findMoreUserOfParam() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatisConf.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1=new User();
user1.setName("giraffe");
user1.setId(17);
List<User> giraffe = mapper.findMoreUserOfUser(user1);
for (User user : giraffe) {
System.out.println(user);
}
sqlSession.close();
}
1.3 模糊查询
需求
根据username模糊查询user表
通过 #{} 可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,# {}可以有效防止sql注入。
UserMapper接口
/*名字模糊查询*/
public List<User> findUserByName(String name);
UserMapper.xml
<select id="findUserByName" resultType="user" parameterType="String">
select * from user where name like '#{name}'
</select>
通过 ${} 可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换,会出现sql注入问题。
如果parameterType传输单个简单类型值, ${} 括号中只能是value。
UserMapper.xml
<select id="findUserByName" resultType="user" parameterType="String">
select * from user where name like '${value}'
</select>
- 通过 #{} 可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,# {}可以有效防止sql注入。
- #{} 可以接收简单类型值或pojo属性值。
- 如果parameterType传输单个简单类型值, #{} 括号中名称随便写。
- 通过 ${} 可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换,会出现sql注入问题。
- ${} 可以接收简单类型值或pojo属性值。
- 如果parameterType传输单个简单类型值, ${} 括号中只能是value。
补充:TextSqlNode.java 源码可以证明
二 Mybatis映射文件深入
2.1 返回主键
应用场景
我们很多时候有这种需求,向数据库插入一条记录后,希望能立即拿到这条记录在数据库中的主键值。
2.1.1 useGeneratedKeys
useGeneratedKeys="true" 声明返回主键
keyProperty="id" 把返回主键的值,封装到实体的id属性中
注意:只适用于主键自增的数据库,mysql和sqlserver支持,oracle不支持
UserMapper接口
注意返回类型为void,主键会直接赋值给User对象的id
/*返回主键到对象*/
public void saveUser(User user);
UserMapper.xml
<insert id="saveUser" parameterType="user" keyProperty="id">
insert into user (name,password)values(#{name},#{password})
</insert>
2.1.2 selectKey
selectKey 适用范围广,支持所有类型数据库keyColumn="id" 指定主键列名
keyProperty="id" 指定主键封装到实体的id属性中
resultType="int" 指定主键类型
order="AFTER" 设置在sql语句执行前(后),执行此语句
对于自增的数据库可以使用AFTER获取主键
对于不自增的数据库可以使用BEFORE来获取主键再填入之后的操作
UserMapper.xml
<insert id="saveUser" parameterType="user">
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID();
</selectKey>
insert into user (name,password)values(#{name},#{password})
</insert>
测试
public void saveUser() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatisConf.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user=new User("bigloadsosocoo", "123456");
mapper.saveUser(user);
System.out.println(user);
sqlSession.commit();
sqlSession.close();
}
2.2 动态SQL
应用场景
当我们要根据不同的条件,来执行不同的sql语句的时候,需要用到动态sql。
2.2.1 动态 SQL 之<if>
需求
根据id和name查询,但是不确定两个都有值。
UserMapper接口
/*动态sql --if*/
public List<User> findIFUser(User user);
UserMapper.xml映射
where标签相当于 where 1=1,但是如果没有条件,就不会拼接where关键字
<select id="findIFUser" parameterType="user" resultType="user">
select * from user
<where>
<if test="id != 0">
and id=#{id}
</if>
<if test="name != null">
and name=#{name}
</if>
</where>
</select>
测试
public void findIFUser() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatisConf.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user=new User();
// user.setId(17);
// user.setName("giraffe");
List<User> users = mapper.findIFUser(user);
for (User user1 : users) {
System.out.println(user1);
}
sqlSession.close();
}
}
2.2.2 动态 SQL 之<set>
需求
动态更新user表数据,如果该属性有值就更新,没有值不做处理。
UserMapper接口
/*动态sql --set*/
public void updateSETuser(User user);
UserMapper.xml映射
<update id="updateSETuser" parameterType="user">
update user
<set>
<if test="name!=null">
name=#{name}
</if>
<if test="password!=null">
password=#{password}
</if>
</set>
where id=#{id}
</update>
2.2.3 动态 SQL 之<foreach>
foreach主要是用来做数据的循环遍历
例如: select * from user where id in (1,2,3) 在这样的语句中,传入的参数部分必须依靠foreach遍历才能实现。
* <foreach>标签用于遍历集合,它的属性:
• collection:代表要遍历的集合元素
mybatis 中 foreach collection的三种用法
1. 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
2. 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
3. 如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可
• open:代表语句的开始部分
• close:代表结束部分
• item:代表遍历集合的每个元素,生成的变量名
• sperator:代表分隔符
a)集合
如果查询条件为普通类型 List集合,collection属性值为:collection 或者 list
UserMapper接口
/*动态sql --foreach*/
public List<User> findFOREACHUser(List<Integer> nums);
UserMaper.xml映射
<select id="findFOREACHUser" parameterType="list" resultType="user">
select * from user
<where>
<foreach collection="list" item="id" open="id in(" separator="," close=")">
#{id}
</foreach>
</where>
</select>
测试
public void findFOREACHUser() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatisConf.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<Integer> nums = new ArrayList<>();
nums.add(1);
nums.add(17);
nums.add(20);
List<User> foreachUser = mapper.findFOREACHUser(nums);
for (User user : foreachUser) {
System.out.println(user);
}
sqlSession.close();
}
b)数组
如果查询条件为普通类型 Array数组,collection属性值为:array
UserMapper接口
/*动态sql --foreach*/
public List<User> findFOREACHUser(Integer[] nums);
测试
@Test
public void findFOREACHUser() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatisConf.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Integer[] nums={1, 17, 21};
List<User> foreachUser = mapper.findFOREACHUser(nums);
for (User user : foreachUser) {
System.out.println(user);
}
sqlSession.close();
}
UserMaper.xml映射
<select id="findFOREACHUser" parameterType="list" resultType="user">
select * from user
<where>
<foreach collection="array" item="id" open="id in(" separator="," close=")">
#{id}
</foreach>
</where>
</select>
2.3 SQL片段
应用场景
映射文件中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的
<!--抽取的sql片段-->
<sql id="selectAllUser">
select * from user
</sql>
<select id="findFOREACHUser" parameterType="list" resultType="user">
<!--引入sql片段-->
<include refid="selectAllUser"/>
<where>
<foreach collection="array" item="id" open="id in(" separator="," close=")">
#{id}
</foreach>
</where>
</select>
三 Mybatis核心配置文件深入
3.1 plugins标签
MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据
开发步骤:
- ①导入通用PageHelper的坐标
<!-- 分页助手 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>3.7.5</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>0.9.1</version>
</dependency>
- ②在mybatis核心配置文件中配置PageHelper插件
注意放置的位置
The content of element type "configuration" must match "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)".
<plugins>
<!-- 分页助手的插件 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 指定方言 -->
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
- ③测试分页数据获取
第一个参数为第几页,第二个参数为一页几条记录
//设置分页参数
PageHelper.startPage(1,2);
获得分页相关的其他参数
//其他分页的数据
PageInfo<User> pageInfo = new PageInfo<>(allResultMap);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页显示长度:"+pageInfo.getPageSize());
System.out.println("是否第一页:"+pageInfo.isIsFirstPage());
System.out.println("是否最后一页:"+pageInfo.isIsLastPage());
3.2 知识小结
MyBatis核心配置文件常用标签:1、properties标签:该标签可以加载外部的properties文件
2、typeAliases标签:设置类型别名
3、environments标签:数据源环境配置标签
4、plugins标签:配置MyBatis的插件
四 Mybatis多表查询
4.1 案例环境准备
DROP TABLE IF EXISTS `orders`;CREATE TABLE `orders` (`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,`ordertime` VARCHAR ( 255 ) DEFAULT NULL ,`total` DOUBLE DEFAULT NULL ,`uid` INT ( 11 ) DEFAULT NULL ,PRIMARY KEY (`id`),KEY `uid` (`uid`),CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user` (`id`)) ENGINE=INNODB AUTO_INCREMENT= 4 DEFAULT CHARSET=utf8;-- ------------------------------ Records of orders-- ----------------------------INSERT INTO `orders` VALUES ( '1' , '2020-12-12' , '3000' , '1' );INSERT INTO `orders` VALUES ( '2' , '2020-12-12' , '4000' , '1' );INSERT INTO `orders` VALUES ( '3' , '2020-12-12' , '5000' , '2' );-- ------------------------------ Table structure for sys_role-- ----------------------------DROP TABLE IF EXISTS `sys_role`;CREATE TABLE `sys_role` (`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,`rolename` VARCHAR ( 255 ) DEFAULT NULL ,`roleDesc` VARCHAR ( 255 ) DEFAULT NULL ,PRIMARY KEY (`id`)) ENGINE=INNODB AUTO_INCREMENT= 3 DEFAULT CHARSET=utf8;-- ------------------------------ Records of sys_role-- ----------------------------INSERT INTO `sys_role` VALUES ( '1' , 'CTO' , 'CTO' );INSERT INTO `sys_role` VALUES ( '2' , 'CEO' , 'CEO' );-- ------------------------------ Table structure for sys_user_role-- ----------------------------DROP TABLE IF EXISTS `sys_user_role`;CREATE TABLE `sys_user_role` (`userid` INT ( 11 ) NOT NULL ,`roleid` INT ( 11 ) NOT NULL ,PRIMARY KEY (`userid`,`roleid`),KEY `roleid` (`roleid`),CONSTRAINT `sys_user_role_ibfk_1` FOREIGN KEY (`userid`) REFERENCES `sys_role`(`id`),CONSTRAINT `sys_user_role_ibfk_2` FOREIGN KEY (`roleid`) REFERENCES `user`(`id`)) ENGINE=INNODB DEFAULT CHARSET=utf8;-- ------------------------------ Records of sys_user_role-- ----------------------------INSERT INTO `sys_user_role` VALUES ( '1' , '1' );INSERT INTO `sys_user_role` VALUES ( '2' , '1' );INSERT INTO `sys_user_role` VALUES ( '1' , '2' );INSERT INTO `sys_user_role` VALUES ( '2' , '2' );
4.2 一对一(多对一)
4.2.1 介绍
一对一查询模型
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
一对一查询的需求:查询所有订单,与此同时查询出每个订单所属的用户
一对一查询语句
SELECT * FROM orders o LEFT JOIN USER u ON o.`uid`=u.`id`;
4.2.2 代码实现
Order实体
public class Order {
private Integer id;
private Date ordertime;
private double money;
// 表示当前订单属于哪个用户
private User user;
}
OrderMapper接口
public List<Order> findAllWithUser();
OrderMapper.xml映射
一对一(多对一)使用 association 标签关联property="user" 封装实体的属性名javaType="user" 封装实体的属性类型
<mapper namespace="com.bigload.mapper.OrderMapper">
<resultMap id="orderMap" type="com.bigload.domain.Order">
<id column="id" property="id"/>
<result column="ordertime" property="ordertime"/>
<result column="total" property="total"/>
<association property="user" javaType="com.bigload.domain.User">
<id column="uid" property="id"/>
<result column="name" property="name"/>
<result column="password" property="password"/>
</association>
</resultMap>
<select id="findAllWithUser" resultMap="orderMap">
select * from orders left join user on orders.uid=user.id;
</select>
</mapper>
测试
@Test
public void findAllWithUser() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatisConf.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
List<Order> allWithUser = mapper.findAllWithUser();
for (Order order : allWithUser) {
System.out.println(order);
}
sqlSession.close();
}
4.3 一对多
4.3.1 介绍
一对多查询模型
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
一对多查询的需求:查询所有用户,与此同时查询出该用户具有的订单
一对多查询语句
select *,orders.id oid from user left join orders on user.id=orders.uid;
4.3.2 代码实现
User实体
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
// 代表当前用户具备的订单列表
private List<Order> orders;
}
UserMapper接口
/*查询用户的所有订单*/
public List<User> findAllWithOrder();
UserMapper.xml映射
一对多使用 collection 标签关联property="orderList" 封装到集合的属性名
ofType="order" 封装集合的泛型类型
<resultMap id="userOrderMap" type="com.bigload.domain.User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="password" property="password"/>
<collection property="orders" ofType="com.bigload.domain.Order">
<id column="oid" property="id"/>
<result column="ordertime" property="ordertime"/>
<result column="total" property="total"/>
</collection>
</resultMap>
<select id="findAllWithOrder" resultMap="userOrderMap">
select *,orders.id oid from user left join orders on user.id=orders.uid;
</select>
测试
@Test
public void findAllWithOrder() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatisConf.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> foreachUser = mapper.findAllWithOrder();
for (User user : foreachUser) {
System.out.println(user);
}
sqlSession.close();
}
4.4 多对多
4.4.1 介绍
多对多查询的模型
用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用
多对多查询的需求:查询所有用户同时查询出该用户的所有角色
User和Role 实体
public class User {
private int id;
private String name;
private String password;
public List<Order> orders;
public List<Role> roles;
}
public class Role {
private Integer id;
private String rolename;
private String roleDesc;
}
UserMapper接口
/*查询所有用户同时查询出该用户的所有角色*/
public List<User> findAllWithRole();
UserMapper.xml映射
<resultMap id="userRoleMap" type="user">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="password" property="password"/>
<collection property="roles" ofType="com.bigload.domain.Role">
<id column="roleid" property="id"/>
<result column="rolename" property="rolename"/>
<result column="roleDesc" property="roleDesc"/>
</collection>
</resultMap>
<select id="findAllWithRole" resultMap="userRoleMap">
select * from user u left join sys_user_role sur on u.id=sur.userid left join sys_role sr on sur.roleid=sr.id
</select>
4.5 小结
MyBatis多表配置方式
* 多对一(一对一)配置:使用 <resultMap>+<association> 做配置* 一对多配置:使用 <resultMap>+<collection> 做配置* 多对多配置:使用 <resultMap>+<collection> 做配置* 多对多的配置跟一对多很相似,难度在于 SQL 语句的编写。
五 MyBatis嵌套查询
5.1 什么是嵌套查询
嵌套查询就是将原来多表查询中的联合查询语句拆成单个表的查询,再使用mybatis的语法嵌套在一 起。
5.2 一对一嵌套查询
5.2.1 介绍
需求:查询一个订单,与此同时查询出该订单所属的用户
一对一查询语句
-- 先查询订单SELECT * FROM orders;-- 再根据订单 uid 外键,查询用户SELECT * FROM `user` WHERE id = #{ 订单的 uid};
5.2.2 代码实现
public List<Order> findAllWithUser2();
<resultMap id="roleUserMap" type="com.bigload.domain.Order">
<id column="id" property="id"/>
<result column="rolename" property="rolename"/>
<result column="roleDesc" property="roleDesc"/>
<association property="user" javaType="user"
column="uid"
select="com.bigload.mapper.UserMapper.findID"/>
</resultMap>
<select id="findAllWithUser2" resultMap="roleUserMap">
select * from orders
</select>
/*根据id搜索用户*/
public User findID();
<select id="findID" parameterType="int" resultType="user" >
select * from user where id=#{id}
</select>
@Test
public void findAllWithUser2() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatisConf.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
List<Order> allWithUser = mapper.findAllWithUser2();
for (Order order : allWithUser) {
System.out.println(order);
}
sqlSession.close();
}
5.3 一对多嵌套查询
5.3.1 介绍
需求:查询所有用户,与此同时查询出该用户具有的订单
一对多查询语句
-- 先查询用户SELECT * FROM `user`;
-- 再根据用户 id 主键,查询订单列表SELECT * FROM orders where uid = #{用户id};
5.3.2 代码实现
/*嵌套查询搜索用户后查订单*/
public List<User> findAllWithOrder2();
<resultMap id="userOrder2Map" type="user">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="password" property="password"/>
<collection property="orders" ofType="com.bigload.domain.Order" column="id"
select="com.bigload.mapper.OrderMapper.findOfUserId"/>
</resultMap>
<select id="findAllWithOrder2" resultMap="userOrder2Map">
select * from user
</select>
public List<Order> findOfUserId();
<select id="findOfUserId" parameterType="int" resultType="order">
select * from orders where uid=#{id}
</select>
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> foreachUser = mapper.findAllWithOrder2();
for (User user : foreachUser) {
System.out.println(user);
}
5.4 多对多嵌套查询
5.4.1 介绍
需求:查询用户 同时查询出该用户的所有角色
多对多查询语句
-- 先查询用户SELECT * FROM `user`;
-- 再根据用户 id 主键,查询角色列表select * from sys_role r left join sys_user_role s on s.roleid=r.id where s.userid=#{userid}
5.4.2 代码实现
/*嵌套查询所有用户同时查询出该用户的所有角色*/
public List<User> findAllWithRole2();
<resultMap id="userRole2Map" type="user">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="password" property="password"/>
<collection property="roles" ofType="role" column="id" select="com.bigload.mapper.RoleMapper.findId"/>
</resultMap>
<select id="findAllWithRole2" resultMap="userRole2Map">
select * from user
</select>
public Role findId();
<select id="findId" parameterType="int" resultType="com.bigload.domain.Role">
select * from sys_role r left join sys_user_role s on s.roleid=r.id where s.userid=#{userid}
</select>
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> foreachUser = mapper.findAllWithRole2();
for (User user : foreachUser) {
System.out.println(user);
}
5.5 小结
一对一配置:使用 <resultMap> + <association> 做配置,通过 column 条件,执行 select 查询一对多配置:使用 <resultMap> + <collection> 做配置,通过 column 条件,执行 select 查询多对多配置:使用 <resultMap> + <collection> 做配置,通过 column 条件,执行 select 查询优点:简化多表查询操作缺点:执行多次 sql 语句,浪费数据库性能
六 MyBatis加载策略
问题
通过前面的学习,我们已经掌握了Mybatis中一对一,一对多,多对多关系的配置及实现,可以实现对象的关联查询。实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的订单信息。此时就是我们所说的延迟加载。
举个栗子
* 在一对多中,当我们有一个用户,它有个 100 个订单在查询用户的时候,要不要把关联的订单查出来?
在查询订单的时候,要不要把关联的用户查出来?
* 回答在查询用户时,用户下的订单应该是,什么时候用,什么时候查询。
在查询订单时,订单所属的用户信息应该是随着订单一起查询出来
延迟加载
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。
* 优点:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
* 缺点:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
* 在多表中:一对多,多对多:通常情况下采用延迟加载
一对一(多对一):通常情况下采用立即加载
* 注意:延迟加载是基于嵌套查询来实现的
在association和collection标签中都有一个fetchType属性,通过修改它的值,可以修改局部的加载策略。
fetchType="lazy" 懒加载策略fetchType="eager" 立即加载策略
<resultMap id="userRole2Map" type="user">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="password" property="password"/>
<collection property="roles" ofType="role" column="id"
select="com.bigload.mapper.RoleMapper.findId"
fetchType="lazy"/>
</resultMap>
<select id="findAllWithRole2" resultMap="userRole2Map">
select * from user
</select>
6.2.2 设置触发延迟加载的方法
大家在配置了延迟加载策略后,发现即使没有调用关联对象的任何方法,但是在你调用当前对象的 equals、clone、hashCode、toString方法时也会触发关联对象的查询。
我们可以在配置文件中使用lazyLoadTriggerMethods配置项覆盖掉上面四个方法。
在Mybatis的核心配置文件中可以使用setting标签修改全局的加载策略。
<settings>
<!--所有方法都会延迟加载-->
<setting name="lazyLoadTriggerMethods" value="toString()"/>
</settings>
注意
局部的加载策略优先级高于全局的加载策略。
七 MyBatis缓存
7.1 为什么使用缓存?
当用户频繁查询某些固定的数据时,第一次将这些数据从数据库中查询出来,保存在缓存中。当用户再次查询这些数据时,不用再通过数据库查询,而是去缓存里面查询。减少网络连接和数据库查询带来的损耗,从而提高我们的查询效率,减少高并发访问带来的系统性能问题。
一句话概括:经常查询一些不经常发生变化的数据,使用缓存来提高查询效率。
像大多数的持久化框架一样,Mybatis也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。 Mybatis中缓存分为一级缓存,二级缓存。
7.2 一级缓存
7.2.1 介绍
一级缓存是SqlSession级别的缓存,是默认开启的所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。
7.2.2 验证
idea配置mybatis打印日志
1.在src/main/resources配置文件夹中添加一个log4j.properties文件
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c:/mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=debug, stdout
2.导入依赖到pom.xml
<!-- SLFJ 可依赖到log4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
进行断点调试
@Test
public void findOne() throws Exception {
InputStream is = Resources.getResourceAsStream("mybatisConf.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User one = mapper.findOne(1);
System.out.println(one);
User two = mapper.findOne(1);
System.out.println(two);
sqlSession.close();
}
我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是Mybatis提供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询id为1的记录时,并 没有发出sql语句从数据库中查询数据,而是从一级缓存中查询。
7.2.3 分析
一级缓存是SqlSession范围的缓存,执行SqlSession的C(增加)U(更新)D(删除)操作,或者调用clearCache()、commit()、close()方法,都会清空缓存。
1. 第一次发起查询用户id为41的用户信息,先去找缓存中是否有id为41的用户信息,如果没有,从数据库查询用户信息。
2. 得到用户信息,将用户信息存储到一级缓存中。
3. 如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
4. 第二次发起查询用户id为41的用户信息,先去找缓存中是否有id为41的用户信息,缓存中有,直接从缓存中获取用户信息。
7.2.4 清除
1.在测试文件调用清除缓存
//调用sqlSession清除缓存的方法
sqlSession.clearCache();
2.在映射文件添加 flushCache="true" 每次查询时,都会清除缓存
<!-- 每次查询时,都会清除缓存 -->
< select flushCache="true"></select>
7.3 二级缓存
7.3.1 介绍
二级缓存是namspace级别(跨sqlSession)的缓存,当sqlSession.close之后会把数据放到二级缓冲中,是默认不开启的
二级缓存的开启需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。
也就是要求实现Serializable接口,配置方法很简单,只需要在映射XML文件配置 <cache/> 就可以开启二级缓存了。
7.3.2 验证
a)配置核心配置文件
因为 cacheEnabled 的取值默认就为 true ,所以这一步可以省略不配置。为 true 代表开启二级缓存;为 false 代表不开启二级缓存。
<setting name="cacheEnabled" value="true"/>
b)配置UserMapper.xml映射
<select> 标签中设置 useCache=”true” 代表当前这个 statement 要使用二级缓存。如果不使用二级缓存可以设置为 false注意:针对每次查询都需要最新的数据sql,要设置成useCache="false",禁用二级缓存。
<mapper namespace="com.bigload.mapper.UserMapper">
<!--当前映射文件开启二级缓存-->
<cache></cache>
<select id="findOne" resultType="user" parameterType="int" useCache="true">select * from user where id=#{id}</select>
</mapper>
c)修改User实体,实现Serializable
public class User implements Serializable { private int id; private String name; private String password; public List<Order> orders;
d)测试结果
@Test
public void findOne() throws Exception {
InputStream is = Resources.getResourceAsStream("mybatisConf.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User one = mapper.findOne(1);
System.out.println(one);
sqlSession.close();
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
User two = mapper1.findOne(1);
System.out.println(two);
sqlSession1.close();
}
7.3.3 分析
二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个Mapper映射(跨sqlsession但不跨mapper)的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
1. 映射语句文件中的所有 select 语句将会被缓存。2. 映射语句文件中的所有 insert 、 update 和 delete 语句会刷新缓存。
7.3.4 注意问题(脏读)
mybatis的二级缓存因为是namespace级别,所以在进行多表查询时会产生脏读问题(例在查询userMapper的时候存入了缓存,在OrderMapper添加增删改,虽然有改变的字段,UserMapper不会刷新缓存,就会造成脏读)
7.4 小结
1. mybatis 的缓存,都不需要我们手动存储和获取数据。 mybatis 自动维护的。2. mybatis 开启了二级缓存后,那么查询顺序:二级缓存 -- 》一级缓存 -- 》数据库2. 注意: mybatis 的二级缓存会存在脏读问题,需要使用第三方的缓存技术redis解决问题。
八 MyBatis注解
8.1 MyBatis常用注解
这几年来注解开发越来越流行,Mybatis也可以使用注解开发方式,这样我们就可以减少编写 Mapper映射文件了。
* @Insert:实现新增,代替了<insert></insert>
* @Delete:实现删除,代替了<delete></delete>
* @Update:实现更新,代替了<update></update>
* @Select:实现查询,代替了<select></select>
* @Result:实现结果集封装,代替了<result></result>
* @Results:可以与@Result 一起使用,封装多个结果集,代替了<resultMap></resultMap>
* @One:实现一对一结果集封装,代替了<association></association>
* @Many:实现一对多结果集封装,代替了<collection></collection>
8.2 MyBatis注解的增删改查【重点】
8.2.1 创建UserMapper接口
public interface UserMapper {
@Select("SELECT * FROM `user`")
public List<User> findaAll();
@Insert("INSERT INTO `user`(name,password) VALUES(# {name},#{password})")
public void save(User user);
@Update("UPDATE `user` SET name = #{name},password = #{password} WHERE id = #{id}")
public void update(User user);
@Delete("DELETE FROM `user` where id = #{id}")
public void delete(Integer id);
}
8.2.2 编写核心配置文件
和之前配置映射文件的方式一样
8.3 使用注解实现复杂映射开发
之前我们在映射文件中通过配置 <resultMap>、<association>、<collection> 来实现复杂关系映射
使用注解开发后,我们可以使用 @Results、@Result,@One、@Many 注解组合完成复杂关系的配置。
8.4 一对一查询
8.4.1 介绍
需求:查询一个订单,与此同时查询出该订单所属的用户
一对一查询语句
SELECT * FROM orders;SELECT * FROM `user` WHERE id = #{ 订单的 uid};
OrderMapper
@Select("select * from orders")
@Results({
@Result(column = "id",property = "id"),
@Result(column = "ordertime", property = "ordertime"),
@Result(column = "total", property = "total"),
@Result(property = "user",javaType = User.class,
column = "uid",one = @One(
select = "com.bigload.mapper.UserMapper.findAOne",
fetchType = FetchType.EAGER
))
})
public List<Order> findAllWithUserZj();
UserMapper
@Select("select * from user where id =#{id}")
public User findAOne();
8.5 一对多查询
8.5.1 介绍
需求:查询一个用户,与此同时查询出该用户具有的订单
一对多查询语句
SELECT * FROM `user`;SELECT * FROM orders where uid = #{ 用户 id};
UserMapper
注意一对多的javatype需要写List.class
/*所有用户和旗下的账单*/
@Select("select * from user")
@Results({
@Result(column = "id",property = "id",id=true),
@Result(column = "ordertime",property = "ordertime"),
@Result(column = "total",property = "total"),
@Result(property = "orders",javaType = List.class,column = "id",many = @Many(
select = "com.bigload.mapper.OrderMapper.findAllWithUserID",
fetchType = FetchType.LAZY
))
})
public List<User> findaWithOrder();
OrderMapper
@Select("select * from orders where uid=#{uid}")
public List<Order> findAllWithUserID();
测试
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> allWithUserZj = mapper.findaWithOrder();
for (User user : allWithUserZj) {
System.out.println(user);
System.out.println(user.getOrders());
}
8.6 多对多查询
8.6.1 介绍
需求:查询所有用户,同时查询出该用户的所有角色
多对多查询语句
SELECT * FROM `user`;select * from sys_role sr left join sys_user_role sur on sr.id=sur.roleid where sur.userid=#{用户id}
UserMapper
/*所有用户和他们的角色*/
@Select("select * from user")
@Results({
@Result(column = "id",property = "id",id=true),
@Result(column = "rolename",property = "rolename"),
@Result(column = "roleDesc",property = "roleDesc"),
@Result(property = "roles",javaType = List.class,column = "id",many = @Many(
select = "com.bigload.mapper.RoleMapper.findId2",fetchType = FetchType.LAZY
))
})
public List<User> findaWithRole();
RoleMapper
@Select("select * from sys_role sr left join sys_user_role sur on sr.id=sur.roleid where sur.userid=#{uid}")
public Role findId2();
8.7 基于注解的二级缓存
8.7.1 配置SqlMapConfig.xml文件开启二级缓存的支持
<settings>
<!--因为cacheEnabled的取值默认就为true,所以这一步可以省略不配置。
为true代表开启二级缓存;为false代表不开启二级缓存。
-->
<setting name="cacheEnabled" value="true"/>
</settings>
8.7.2 在Mapper接口中使用注解配置二级缓存
@CacheNamespace
public interface UserMapper {...}
8.8 注解延迟加载
不管是一对一还是一对多 ,在注解配置中都有fetchType的属性
* fetchType = FetchType.LAZY 表示懒加载* fetchType = FetchType.EAGER 表示立即加载* fetchType = FetchType.DEFAULT 表示使用全局配置
8.9 小结
* 注解开发和 xml 配置优劣分析1.注解开发和xml配置相比,从开发效率来说,注解编写更简单,效率更高。
2.从可维护性来说,注解如果要修改,必须修改源码,会导致维护成本增加。xml维护性更强。