Mybatis2

一、SQL深入-动态sql【重点】

​ 我们在前边的学习过程中,使用的SQL语句都非常简单。而在实际业务开发中,我们的SQL语句通常是动态拼接而成的,比如:条件搜索功能的SQL语句。

​ 在Mybatis中,SQL语句是写在映射配置的XML文件中的。Mybatis提供了一些XML的标签,用来实现动态SQL的拼接。

常用的标签有:

  • <if></if>:用来进行判断,相当于Java里的if判断
  • <where></where>:通常和if配合,用来代替SQL语句中的where 1=1
  • <foreach></foreach>:用来遍历一个集合,把集合里的内容拼接到SQL语句中。例如拼接:in (value1, value2, ...)
  • <sql></sql>:用于定义sql片段,达到重复使用的目的

准备环境

​ 我们以对user表的操作为例,演示这些标签的用法。先准备Mybatis的环境如下

  1. 创建java项目,导入jar包

  2. 创建JavaBean实体类:User

  3. 创建映射器接口UserDao

  4. 创建映射文件UserDao.xml

  5. 创建Mybatis的核心配置文件,配置好类型别名和映射器

  6. 准备log4j日志配置文件

  7. 准备好单元测试类

    public class MybatisSqlTest {
        private InputStream is;
        private SqlSession session;
        private UserDao dao;
    
        @Before
        public void init() throws IOException {
            is = Resources.getResourceAsStream("SqlMapConfig.xml");
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory factory = builder.build(is);
            session = factory.openSession();
            dao = session.getMapper(UserDao.class);
        }
    
        @After
        public void destory() throws IOException {
            session.close();
            is.close();
        }
    }
    

<if>标签:

<if test="判断条件,使用OGNL表达式进行判断">
	SQL语句内容, 如果判断为true,这里的SQL语句就会进行拼接
</if>

需求描述

  • 根据用户的名称和性别搜索用户信息。

  • 把搜索条件放到User对象里,传递给SQL语句

需求实现

1) 在映射器UserDao中增加方法
	//根据user搜索用户信息
    List<User> search(User user);
2) 在映射文件UserDao.xml中配置
  • 在if标签的test属性中,直接写OGNL表达式,从parameterType中取值进行判断,不需要加#{}或者${}
    <select id="search" parameterType="user" resultType="user">
        select * from user where 1=1
        <if test="username != null and username.length() > 0">
            and username like #{username}
        </if>
        <if test="sex != null and username.length() > 0">
            and sex = #{sex}
        </if>
    </select>
3) 在单元测试类中编写测试代码
    @Test
    public void testSearch(){
        User user = new User();
        user.setUsername("%王%");
        user.setSex("男");
        
        List<User> users = dao.search(user);
        for (User u : users) {
            System.out.println(u);
        }
    }

<where>标签

在刚刚的练习的SQL语句中,我们写了where 1=1。如果不写的话,SQL语句会出现语法错误。Mybatis提供了一种代替where 1=1的技术:<where></where>标签。

  1. <where>标签代替了where 1=1

  2. <where>标签内拼接的SQL没有变化,每个if的SQL中都有and

  3. <where>标签会自动处理掉第一条件里前边的and,以保证SQL语法正确

需求描述

​ 使用==<where></where>标签代替where 1=1==

需求实现

  • 只需要修改一下配置文件:
    <select id="search" parameterType="user" resultType="user">
        select * from user
        <where>
            <if test="username != null and username.length() > 0">
                and username like #{username}
            </if>
            <if test="sex != null and username.length() > 0">
                and sex = #{sex}
            </if>
        </where>
    </select>
  • 再次运行测试代码,查看结果仍然正常

小结

<foreach>标签

​ foreach标签,通常用于循环遍历一个集合,把集合的内容拼接到SQL语句中。例如,我们要根据多个id查询用户信息,SQL语句:

select * from user where id = 1 or id = 2 or id = 3;
select * from user where id in (1, 2, 3);

​ 假如我们传参了id的集合,那么在映射文件中,如何遍历集合拼接SQL语句呢?可以使用foreach标签实现。

<!--
foreach标签:
	属性:
		collection:被循环遍历的对象,使用OGNL表达式获取,注意不要加#{}
		open:循环之前,拼接的SQL语句的开始部分
		item:定义变量名,代表被循环遍历中每个元素,生成的变量名
		separator:分隔符
		close:循环之后,拼接SQL语句的结束部分
	标签体:
		使用#{OGNL}表达式,获取到被循环遍历对象中的每个元素
-->
<foreach collection="" open="id in(" item="id" separator="," close=")">
    #{id}
</foreach>
id in(41,42,44,45)

需求描述

QueryVO中有一个属性ids, 是id值的集合。根据QueryVO中的ids查询用户列表。

​ QueryVO类如下:

public class QueryVO {
    
    private Integer[] ids;

    public Integer[] getIds() {
        return ids;
    }

    public void setIds(Integer[] ids) {
        this.ids = ids;
    }
}

需求实现

1) 在映射器接口UserDao中增加方法
List<User> findByIds(QueryVO vo);
2) 在映射文件UserDao.xml中添加statement
<!--在核心配置文件中已经使用package配置了类型别名-->
<select id="findByIds" resultType="user" parameterType="queryvo">
    select * from user 
    <where>
        <foreach collection="ids" open="and id in (" item="id" separator="," close=")">
            #{id}
        </foreach>
    </where>
</select>
3) 在单元测试类中编写测试代码
    @Test
    public void testFindUserByIdsQueryVO(){
        QueryVO vo = new QueryVO();
        vo.setIds(new Integer[]{41, 42});

        List<User> userList = dao.findByIds(vo);
        for (User user : userList) {
            System.out.println(user);
        }
    }

小结

<foreach collection="被循环遍历的集合" item="每一个元素的变量名" separator="分隔符" open="前缀" close="后缀">
   #{每一个元素的变量名}
</foreach>

拼接的结果是:前缀 + 用指定分隔符拼接的字符串 + 后缀

<sql>标签

在映射文件中,我们发现有很多SQL片段是重复的。Mybatis提供了一个<sql>标签,把重复的SQL片段抽取出来,可以重复使用。

定义SQL片段:

<sql id="唯一标识">sql语句片段</sql>

引用SQL片段:

<include refid="sql片段的id"></include>

扩展:

​ 如果想要引入其它映射文件中的sql片段,那么<include>标签的refid的值,需要在sql片段的id前指定namespace。例如:

<include refid="com.itheima.dao.RoleDao.selectRole"></include>

​ 表示引入了namespace为com.itheima.dao.RoleDao的映射文件中id为selectRole的sql片段

需求描述

在查询用户的SQL中,需要重复编写:select * from user。把这部分SQL提取成SQL片段以重复使用

需求实现

1) 在映射文件UserDao.xml中定义SQL片段
    <sql id="selectUser">
        select * from user
    </sql>
2) 在映射文件UserDao.xml中使用SQL片段
<select id="findByIds" resultType="user" parameterType="queryvo">
    <!-- refid属性:要引用的sql片段的id -->
    <include refid="selectUser"></include> 
    <where>
        <foreach collection="ids" open="and id in (" item="id" separator="," close=")">
            #{id}
        </foreach>
    </where>
</select>

小结

<select id="xxx" parameterType="QueryVO" resultType="user">
    <include refid="yyy"/>
    <where>
    	<!-- 循环集合ids -->
        <if test="ids != null and ids.size() > 0">
            <foreach collection="ids" item="id" separator="," open="and id in(" close=")">
        		#{id}    
            </foreach>
        </if>
        
        <!-- user里的username和sex -->
        <if test="user != null">
        	<if test="user.username != null and user.username.length()>0">
            	and username like #{user.username}
            </if>
            <if test="user.sex != null and user.sex.length() > 0">
            	and sex = #{user.sex}
            </if>
        </if>
    </where>
</select>

<sql id="yyy">select * from user</sql>

二、Mybatis的缓存

我们以 根据主键查询User为例,演示缓存的效果

注意:User类不要重写toString()方法,我们需要打印User对象的地址

public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    //get/set方法......
}

一级缓存

什么是一级缓存

什么是一级缓存?

  • 一级缓存,也叫本地缓存(localCache),是SqlSession对象提供的缓存,默认是开启的
  • 执行一次查询之后,查询的结果会被缓存到SqlSession中。
  • 再次查询同样的数据,Mybatis会优先从缓存中查找;如果找到了,就不再查询数据库

什么情况下一级缓存会失效

  • sqlSession执行了修改、添加、删除时
  • sqlSession提交了事务commit()或回滚rollback()
  • 当手动清理缓存sqlSession.clearCache()
  • 当SqlSession对象关闭sqlSession.close()

拓展:一级缓存能否关闭?可以,但通常不需要

  • 在全局配置文件中添加设置项 localCacheScope的值为 STATEMENT即可

一级缓存效果演示

/**
 * Mybatis缓存效果演示
 */
public class MybatisCacheTest {
    private InputStream is;
    private SqlSession session;
    private UserDao dao;
    /**
     * 测试 Mybatis的一级缓存:
     * SQL语句执行了一次、输出user1和user2的地址是相同的
     * 说明Mybatis使用了缓存
     */
    @Test
    public void testLevel1Cache(){
        User user1 = dao.findUserById(41);
        System.out.println(user1);

        User user2 = dao.findUserById(41);
        System.out.println(user2);
    }

    /**
     * 测试  清除Mybatis的一级缓存
     * 两次打印的User地址不同,执行了两次SQL语句
     * SqlSession的修改、添加、删除、commit()、clearCache()、close()都会清除一级缓存
     */
    @Test
    public void testClearLevel1Cache(){
        User user1 = dao.findUserById(41);
        session.clearCache();
        User user2 = dao.findUserById(41);
        
        System.out.println(user1 == user2);//false
    }

    @Before
    public void init() throws IOException {
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        session = factory.openSession();
        dao = session.getMapper(UserDao.class);
    }

    @After
    public void destory() throws IOException {
        session.close();
        is.close();
    }
}

一级缓存的源码分析

  • SqlSession接口:面向用户的接口,提供了让用户调用的方法:selectList, selectOne, insert, update, delete…。 实现类 DefaultSqlSession
  • Executor接口:每次操作数据库,本质都是由这个Executor来执行的。每个SqlSession里都有自己的一个Executor对象
  • Cache接口:缓存接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k22nVOVr-1610904937778)(img/image-20200813203742345.png)]

二级缓存

什么是二级缓存

什么是二级缓存?

  • 是映射器级别的缓存,同一映射器Mapper共享缓存

  • SqlSession查询了一条数据后,会把数据存储到SqlSession的一级缓存中

  • 当这个SqlSession关闭或提交时,Mybatis可以把一级缓存中的数据,转存到二级缓存中

  • 当有新的SqlSession对象要获取 数据时,优先从二级缓存中查找

什么情况下二级缓存会失效

  • 默认情况下,执行增删改之后,一级缓存和二级缓存都会清空
  • 默认情况下,查询数据,不会清除缓存

注意事项:

  • 如果要使用二级缓存,要求JavaBean实现序列化接口Serializable
  • 二级缓存需要手动开启

二级缓存效果演示

1) 修改全局配置文件,开启全局的二级缓存开关
<settings>
    <!-- 增加此配置项,启动二级缓存(默认值就是true,但是仍然建议配置上) -->
    <setting name="cacheEnabled" value="true"/>
</settings>
2) 修改映射文件UserDao.xml,让映射器支持二级缓存
<mapper namespace="com.itheima.dao.UserDao">
    <!-- 把cache标签加到映射文件 mapper标签里 -->
    <cache/>
    ......
</mapper>
3) 修改映射文件UserDao中的findById,让此方法(statement)支持二级缓存
<!-- 如果statement的标签上,设置有的useCache="true",表示此方法要使用二级缓存 -->
<select id="findById" parameterType="int" resultType="user" useCache="true">
    select * from user where id = #{id}
</select>
4) 修改JavaBean:User

注意:如果要使用二级缓存,那么==JavaBean需要实现Serializable接口==

public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    //get/set......
}
5) 编写测试代码
/**
 * Mybatis二级缓存效果演示
 */
public class MybatisLevel2CacheTest {
    private InputStream is;
    private SqlSessionFactory factory;

    /**
     * 测试二级缓存。
     * 测试结果:
     *      虽然输出的user1和user2地址不同,但是SQL语句只执行了一次,说明第二次用了缓存。
     *      Mybatis的二级缓存,保存的不是JavaBean对象,而是散列的数据。
     *      当要获取缓存时,把这些数据重新组装成一个JavaBean对象,所以地址不同
     */
    @Test
    public void testLevel2Cache(){
        SqlSession session1 = factory.openSession();
        UserDao dao1 = session1.getMapper(UserDao.class);
        User user1 = dao1.findUserById(41);
        session1.close();

        SqlSession session2 = factory.openSession();
        UserDao dao2 = session2.getMapper(UserDao.class);
        User user2 = dao2.findUserById(41);
        session2.close();
        
        System.out.println(user1==user2);
    }

    @Before
    public void init() throws IOException {
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        factory = builder.build(is);
    }

    @After
    public void destory() throws IOException {
        is.close();
    }
}

二级缓存原理

在这里插入图片描述

三、多表关联查询【重点】

  • 多表关系:

    • 一对一:一对一通常会合并成一张表,但是:
      • 如果一张表太大,把常用字段放在一张表里,不常用字段放在另外一张表
    • 一对多:用户和订单, 部门和员工
      • 使用外键维护数据的完整性和一致性
    • 多对多:学生和课程,订单和商品,老师和学生
      • 通过中间关系表,来维护多对多的关系
  • 多表查询语法

    • 内连接:查询表之间必定有关联的数据,无关数据是会被剔除的
  # 显式内连接
  select * from table1 inner join table2 on 表关联条件
  # 隐式内连接
  select * from table1, table2 where 表关联条件
  • 外连接:查询一张表的全部数据,及另一张表的关联的数据
# 左外连接:查左表的全部数据,及右表的关联数据
select * from 左表 left join 右表 on 表关联条件
# 右外连接:查右表的全部数据,及左表的关联数据
select * from 左表 right join 右表 on 表关联条件
  • 子查询:查询嵌套的技巧
# 子查询是一个值
#    查询帐号1所属的用户
select * from user where id = (select uid from account where id = 1)
# 子查询是一个集合
#    查询帐号余额大于1000的用户
select * from user where id in(select uid from account where money > 1000)
# 子查询是一张虚拟表。拿虚拟表和其它表关联查询
#    查询帐号余额大于1000的用户和帐号信息
select * from user u right join (select * from account where money > 1000) t on u.id = t.uid

select * from user u right join account a on u.id = a.uid where a.money > 1000

准备工作

  1. 创建java项目,导入依赖

  2. 分别创建好user表和account表的实体类:User和Account

  3. 在dao中创建映射器接口AccountDao和UserDao

  4. 创建映射器的配置文件AccountDao.xml和UserDao.xml

  5. 创建Mybatis核心配置文件,配置好类型别名和映射器

  6. 准备日志配置文件log4j.properties

  7. 编写好单元测试类

public class MybatisMultipleTest {
    private InputStream is;
    private SqlSession session;
    private AccountDao accountDao;
    private UserDao userDao;

    @Before
    public void init() throws IOException {
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(is);
        session = factory.openSession();
        accountDao = session.getMapper(AccountDao.class);
        userDao = session.getMapper(UserDao.class);
    }

    @After
    public void destory() throws IOException {
        session.close();
        is.close();
    }
}

一对一(多对一)关联查询

  • 查询所有帐户表信息,及其关联的用户信息

方案一:类继承方式(不推荐)

创建新的JavaBean用于封装查询结果,定义与所有字段对应的属性,可以使用继承的方式来减少代码量。

例如:

  1. 创建UserAccount类,定义user表对应的属性,然后继承Account类

  2. 创建UserAccount类,定义account表对应的属性,然后继承User类

1) 创建JavaBean:UserAccount
public class UserAccount extends Account {
    private String username;
    private String address;

    //get/set...
    //toString...
}
2) 在映射器AccountDao中增加方法
//查询所有帐号,及其关联的用户信息--类继承的方式
List<UserAccount> queryAllAccounts1();
3) 在映射文件AccountDao.xml中增加配置
  • 注意:SQL语句查询结果集中,不能有重名列。如果有,给列起别名保证没有重名列
<select id="queryAllAccounts1" resultType="userAccount">
    select a.*, u.username, u.address from account a left join user u on a.uid = u.id
</select>
4) 编写测试代码
@Test
public void testQueryAllAccounts(){
    List<UserAccount> userAccounts = accountDao.queryAllAccounts1();
    for (UserAccount userAccount : userAccounts) {
        System.out.println(userAccount);
    }
}

方案二:类引用方式(推荐)

JavaBean中要有 关联JavaBean的引用。例如:

  • 在Account中增加一个属性user,指向User对象。
    • 把查询结果集中,帐号信息封装到Account中
    • 把查询结果集中,用户信息封装到Account的User中
1) 修改JavaBean:Account类
  • 注意:Account中要有User的引用
public class Account {
    private Integer id;
    private Integer uid;
    private Double money;
    
    private User user;

    //get/set...
    //toString...
}
2) 在映射器AccountDao中增加方法
//查询所有帐号,及其关联的用户信息-类引用方式
List<Account> queryAllAccounts2();
3) 在映射文件AccountDao.xml中增加配置
  • 注意:SQL语句查询结果集中,不能有重名列。如果有,给列起别名保证没有重名列
<select id="queryAllAccounts2" resultMap="AccountUserMap">
    SELECT a.id aid, a.uid uid, a.money money, u.* FROM account a LEFT JOIN USER u ON a.uid = u.id
</select>

<resultMap id="AccountUserMap" type="account">
    <id property="id" column="aid"/>
    <result property="uid" column="uid"/>
    <result property="money" column="money"/>
    <!-- 
	association:用于把结果集中某些列的数据,封装到JavaBean中关联的一个对象上。用于一对一情形
		property:把数据封装到哪个属性关联的对象上
		javaType:关联的对象是什么类型的。是com.itheima.domain.User,这里使用了别名
	-->
    <association property="user" javaType="user">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="birthday" column="birthday"/>
        <result property="sex" column="sex"/>
        <result property="address" column="address"/>
    </association>
</resultMap>
4) 编写测试代码
@Test
public void testQueryAllAccounts2(){
    List<Account> accounts = accountDao.queryAllAccounts2();
    for (Account account : accounts) {
        System.out.println(account);
    }
}

方案三:查询嵌套方式(推荐)

mybatis的嵌套查询与嵌套结果查询的不同
在这里插入图片描述

我们可以把多表关联查询,拆分成两步:

  1. 先查询帐号的信息,封装到Account里
  2. 再查询每个帐号关联的用户信息,封装到Account内的User对象里(我们配置好,Mybatis自动调用)
1) 修改JavaBean:Account类
  • Account类里要有一个成员变量:User(同方案二的JavaBean)
2) 在映射器AccountDao中增加方法
List<Account> queryAllAccounts3();
3) 在映射文件AccountDao.xml中增加配置
    <select id="queryAllAccounts3" resultMap="accountMap">
        select * from account
    </select>
    <resultMap id="accountMap" type="Account">
        <id property="id" column="id"/>
        <result property="uid" column="uid"/>
        <result property="money" column="money"/>

        <!--
			根据用户的id查询用户对象
			调用方法com.itheima.dao.UserDao.findById,把uid字段值传递过去,得到对应User对象
		-->
        <association property="user" javaType="User"
                     select="com.itheima.dao.UserDao.findById"
                     column="uid"/>
    </resultMap>
4) 在映射器UserDao中增加方法
User findById(Integer id);
5) 在映射文件UserDao.xml中增加配置
<select id="findById" resultType="User">
    select * from user where id = #{uid}
</select>
6) 编写测试代码
    @Test
    public void testOne() throws IOException {
        List<Account> accounts = accountDao.queryAllAccounts3();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }

小结

对一关联查询注意事项:

  1. JavaBean:Account关联一个User,所以Account里应该有User类型的成员变量
  2. sql语句:多表查询语句,注意不能有重名列,可以起别名保证所有列名不重复
  3. 结果集要使用resultMap进行手动映射

使用多表查询语句,手动配置映射关系

<select id="queryAllAccounts" resultMap="accountMap">
    select a.id aid, a.uid, a.money, u.* from account a left join user u on u.id = a.uid
</select>
<resultMap id="accountMap" type="Account">
    <id property="id" column="aid"/>
    <result property="uid" column="uid"/>
    <result property="money" column="money"/>
    <!--<result property="user.username" column="username"/>-->

    <!--
        association标签:用于把数据封装到关联的一个JavaBean对象里
            property:属性名。哪个属性是关联的JavaBean
            javaType:关联的JavaBean是什么类型的,可以写全限定类名或别名
        -->
    <association property="user" javaType="User">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="birthday" column="birthday"/>
        <result property="sex" column="sex"/>
        <result property="address" column="address"/>
    </association>
</resultMap>

把多表查询拆分成多步查询

  1. 查询所有帐号。把帐号的数据封装到Account对象里
  2. Account里需要的那个User:调用另外一个方法,查询当前帐号关联的那个User对象
<select id="queryAllAccounts2" resultMap="accountMap2">
        select * from account
    </select>
    <resultMap id="accountMap2" type="Account">
        <id property="id" column="id"/>
        <result property="uid" column="uid"/>
        <result property="money" column="money"/>
		
        <!-- 调用另外一个方法,得到关联的User对象。被调用的方法,必须提供好 -->
        <association property="user" javaType="User" select="com.itheima.dao.UserDao.findById" column="uid"/>
    </resultMap>
  • 被调用的方法
public interface UserDao {
    User findById(Integer id);
}
<select id="findById" resultType="User">
    select * from user where id = #{id}
</select>

一对多(多对多)关联查询

  • 查询所有用户(user)信息,以及每个用户拥有的所有帐号(account)信息

类引用方式

  • JavaBean中要有关联JavaBean的集合
  • 例如:在User中增加一个属性accounts,类型是List<Account>
    • 把查询结果集里,用户的信息封装到User中
    • 把查询结果集里,帐号的信息封装到User的accounts中
1) 修改JavaBean:User类
  • User类中要有List<Account>,用于保存用户拥有的帐号信息集合
public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    
    /**增加属性:account的集合*/
    private List<Account> accounts;

    //get/set...
    //toString...
}
2) 在映射器UserDao中增加方法
List<User> queryAllUsers();
3) 在映射文件UserDao.xml中增加配置
  • 注意:SQL语句查询结果集中,不能有重名列。如果有,给列起别名保证没有重名列
<select id="queryAllUsers" resultMap="userAccountsMap">
    SELECT a.id aid, a.uid uid, a.money money, u.* FROM USER u LEFT JOIN account a 
    ON u.id = a.uid
</select>

<resultMap id="userAccountsMap" type="user">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="birthday" column="birthday"/>
    <result property="sex" column="sex"/>
    <result property="address" column="address"/>

    <!--
       collection:用于封装JavaBean中某一属性关联的集合,用于一对多情形
       property:封装哪个属性关联的集合
       ofType:集合中的数据类型是什么。这里是com.itheima.domain.Account,使用了别名
    -->
    <collection property="accounts" ofType="account">
        <id property="id" column="aid"/>
        <result property="uid" column="uid"/>
        <result property="money" column="money"/>
    </collection>
</resultMap>
4) 编写测试代码
@Test
public void testQueryAllUsers(){
    List<User> users = userDao.queryAllUsers();
    for (User user : users) {
        System.out.println(user);
    }
}

查询嵌套方式

1) 修改JavaBean:User类
  • 同类引用方式里的JavaBean相同,略
2) 在映射器UserDao中增加方法
List<User> queryAllUser2();
3) 在映射文件UserDao.xml中增加配置
<select id="queryAllUser2" resultMap="userMap">
    select * from user
</select>
<resultMap id="userMap" type="User">
    <id property="id" column="id"/>
    <result property="userName" column="user_name"/>
    <result property="birthday" column="birthday"/>
    <result property="sex" column="sex"/>
    <result property="address" column="address"/>

    <collection property="accounts" ofType="Account"
                select="com.itheima.dao.AccountDao.findByUid" column="id"/>
</resultMap>
4) 在映射器AccountDao中增加方法
List<Account> findByUid(Integer uid);
5) 在映射文件AccountDao.xml中增加配置
<select id="findByUid" resultType="Account">
    select * from account where uid = #{uid}
</select>
6) 编写测试代码
    @Test
    public void testMany() throws IOException {
        UserDao userDao = session.getMapper(UserDao.class);
        List<User> userList = userDao.queryAllUser2();
        for (User user : userList) {
            System.out.println(user);
    }

小结

练习-多对多关联查询

  • 现有用户表(user)和角色表(role),是多对多关系。有中间关系表user_role

  • 练习:

    1. 查询所有用户,及关联的角色集合

    2. 查询所有角色,及关联的用户集合

准备工作

  1. 创建Maven的java项目,配置好坐标,引入Mybatis的依赖(略)

  2. 创建用户信息和角色信息的实体类

    注意:User中要有Role的集合; Role中要有User的集合

    public class User {
        private Integer id;
        private String username;
        private Date birthday;
        private String sex;
        private String address;
    
        private List<Role> roles;
    
        //get/set方法......
        //toString方法......
    }
    
    
    public class Role {
        private Integer id;
        private String roleName;
        private String roleDesc;
    
        private List<User> users;
    
        //get/set方法......
        //toString方法......
    }
    
    
  3. 创建映射器接口UserDao和RoleDao(准备好备用,暂时不需要加方法)

  4. 创建映射文件UserDao.xml和RoleDao.xml

  5. 创建Mybatis的核心配置文件,配置好别名和映射器

  6. 准备单元测试类(准备好备用)

    public class MybatisMany2ManyTest {
        private InputStream is;
        private SqlSession session;
        private UserDao userDao;
        private RoleDao roleDao;
        
        @Before
        public void init() throws IOException {
            is = Resources.getResourceAsStream("SqlMapConfig.xml");
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory factory = builder.build(is);
            session = factory.openSession();
            userDao = session.getMapper(UserDao.class);
            roleDao = session.getMapper(RoleDao.class);
        }
    
        @After
        public void destory() throws IOException {
            session.close();
            is.close();
        }
    }
    
    

练习1:查询所有用户,及关联的角色集合

1) 在映射器接口UserDao中增加方法
//查询所有用户信息,及其关联的角色集合
List<User> queryAllUsers();

2) 在映射文件UserDao.xml中增加statement
<select id="queryAllUsers" resultMap="userRolesMap">
    SELECT u.*, r.id rid, r.role_name roleName, r.role_desc roleDesc
    FROM USER u LEFT JOIN user_role ur ON u.id = ur.uid
    LEFT JOIN role r ON ur.rid = r.id
</select>

<resultMap id="userRolesMap" type="user">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="address" column="address"/>
    <result property="birthday" column="birthday"/>
    <result property="sex" column="sex"/>

    <!--
   collection:用于封装JavaBean里关联的角色集合
  -->
    <collection property="roles" ofType="role">
        <id property="id" column="rid"/>
        <result property="roleName" column="roleName"/>
        <result property="roleDesc" column="roleDesc"/>
    </collection>
</resultMap>

3) 在单元测试类中编写测试代码
/**
     * 查询所有用户信息,及其关联的角色信息集合
     */
@Test
public void testQueryAllUsers(){
    List<User> users = userDao.queryAllUsers();
    for (User user : users) {
        System.out.println(user);
    }
}

练习2:查询所有角色,及关联的用户集合

1) 在映射器接口RoleDao中增加方法
//查询所有角色信息,及其关联的用户集合
List<Role> queryAllRoles();

2) 在映射文件RoleDao.xml中增加statement
<select id="queryAllRoles" resultMap="roleUsersMap">
    SELECT r.id rid, r.role_name roleName, r.role_desc roleDesc, u.*
    FROM role r LEFT JOIN user_role ur ON r.id = ur.rid
    LEFT JOIN USER u ON ur.uid = u.id
</select>
<resultMap id="roleUsersMap" type="role">
    <id property="id" column="rid"/>
    <result property="roleName" column="roleName"/>
    <result property="roleDesc" column="roleDesc"/>

    <collection property="users" ofType="user">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="birthday" column="birthday"/>
        <result property="sex" column="sex"/>
        <result property="address" column="address"/>
    </collection>
</resultMap>

3) 在单元测试类中编写测试代码
/**
 * 查询所有角色信息,及其关联的用户信息集合
 */
@Test
public void testQueryAllRoles(){
    List<Role> roles = roleDao.queryAllRoles();
    for (Role role : roles) {
        System.out.println(role);
    }
}

小结

  • 只要是多表关联,就需要修改JavaBean
    • 如果是关联一个,Account里要有一个User
    • 如果是关联多个,User里要有一个List<Account>
  • sql语句:查询结果集一定不能有重名列,通过起别名保证所有列不重复
  • 在映射文件里,使用resultMap手动映射
<select id="" resultMap="yyy">
	xxxsqlxxxx
</select>
<resultMap id="yyy" type="">
	
    <!-- 如果关联一个 -->
    <association property="" javaType="">
    </association>
    
    <association property="" javaType="" select="" column/>
    
    <!-- 如果关联多个 -->
    <collection property="" ofType="">
    </collection>
    
    <collection property="" ofType="" select="" column/>
</resultMap>

四、Mybatis的延迟加载(在查询嵌套的基础上)

在这里插入图片描述

在这里插入图片描述

多表关联查询时,比如查询用户信息,及其关联的帐号信息,在查询用户时就直接把帐号信息也一并查询出来了。但是在实际开发中,并不是每次都需要立即使用帐号信息,这时候,就可以使用延迟加载策略了

什么是延迟加载

立即加载

  • 不管数据是否需要使用,只要调用了方法,就立即发起查询。
  • 比如:查询帐号,得到关联的用户;查询用户,得到关联的帐号

延迟加载

  • 延迟加载,也叫按需加载,或者叫==懒加载==。
    • 只有当真正使用到数据的时候,才发起查询。不使用不发起查询
    • 比如:查询用户信息,不使用accounts的时候,不查询帐号的数据;只有当使用了用户的accounts,Mybatis再发起查询帐号的信息
  • 好处:先从单表查询,需要使用关联数据时,才进行关联数据的查询
    • 单表查询语句简单,查询速度比多表关联查询快
    • 内存占用小
  • 坏处:当需要使用数据时才会执行SQL。这样大批量的SQL执行的情况下,会造成查询等待时间比较长

延迟加载的使用场景

  • 一对一(多对一),通常不使用延迟加载(建议)。比如:查询帐号,关联加载用户信息
  • 一对多(多对多),通常使用延迟加载(建议)。比如:查询用户,关联加载帐号信息

延迟加载的实现

无论是对一,还是对多,如果想要实现延迟加载,只要在查询嵌套的基础上,再多一步:

  • 在全局配置文件中,开启懒加载

对一的延迟加载(association)

查询帐号信息,及其关联的用户信息。使用懒加载的方式实现。

步骤:

  1. 使用查询嵌套的方式,查询帐号及关联的用户信息

  2. 在核心配置文件中,开启懒加载

1. 用查询嵌套方式,查询帐号及关联的用户
1) 在映射器AccountDao中增加方法
List<Account> queryAllAccounts();

2) 在映射文件AccountDao.xml中增加配置
<select id="queryAllAccounts" resultMap="accountLazyUser">
    select * from account
</select>
<resultMap id="accountLazyUser" type="account">
    <id property="id" column="id"/>
    <result property="uid" column="uid"/>
    <result property="money" column="money"/>
    <!--
        association标签:用于封装关联的JavaBean对象   (查询账号关联的用户信息)
            select:调用哪个statement,懒加载 得到关联的JavaBean对象
            column:调用statement时,需要传递的参数值,从哪个字段中取出
        -->
    <association property="user" javaType="user"
                 column="uid" select="com.itheima.dao.UserDao.findById"/>
</resultMap>

3) 在映射器UserDao中增加方法
User findById(Integer id);

4) 在映射文件UserDao.xml中增加配置
<select id="findById" parameterType="int" resultType="user">
    select * from user where id = #{id}
</select>

2. 在核心配置文件中开启懒加载(启动延迟加载,禁用积极加载)
<!-- 把settings标签放到typeAliases之前 -->
<settings>
    <!-- 启动延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 禁用积极加载:使用按需加载 -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

3. 编写测试代码
/**
 * 测试一对一实现懒加载:查询帐号,及其关联的一个用户。
 */
@Test
public void testQueryAllAccounts(){
    List<Account> accounts = accountDao.queryAllAccounts();
    for (Account account : accounts) {
        System.out.println(account.getId()+", " + account.getUid() + ", " +account.getMoney());
        //执行下面这行代码,才会发起查询user的SQL语句
        System.out.println(account.getUser());
    }
}

对多的延迟加载(collection)

查询用户信息,及其关联的帐号信息集合。使用延迟加载实现。

实现步骤:

  1. 使用查询嵌套的方式,查询用户信息collection,及关联的帐号集合

  2. 在核心配置文件中,开启懒加载

1. 用查询嵌套方式,查询用户及关联的帐号
1) 在映射器UserDao中增加方法
List<User> queryAllUsers();

2) 在映射文件UserDao.xml中增加statement
<select id="queryAllUsers" resultMap="userLazyAccounts">
    select * from user
</select>
<resultMap id="userLazyAccounts" type="user">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="birthday" column="birthday"/>
    <result property="sex" column="sex"/>
    <result property="address" column="address"/>

    <collection property="accounts" ofType="account" 
                column="id" select="com.itheima.dao.AccountDao.findByUid"/>
</resultMap>

3) 在映射器AccountDao中增加方法
List<Account> findByUid(Integer uid);

4) 在映射文件AccountDao.xml中增加statement
<select id="findByUid" parameterType="int" resultType="account">
    select * from account where uid = #{id}
</select>

2. 在核心配置文件中开启懒加载
<!-- 把settings标签放到typeAliases之前 -->
<settings>
    <!-- 启动延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 禁用积极加载:使用按需加载 -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

3. 编写测试代码
/**
 * 测试一对多实现懒加载:查询用户,及其关联的帐号集合
 */
@Test
public void testQueryAllUsers(){
    List<User> users = userDao.queryAllUsers();
    for (User user : users) {
        System.out.println(user.getUsername()+", " + user.getSex());
        //执行页面这行代码,才会发起查询account的SQL语句
        System.out.println(user.getAccounts());
    }
}

小结

  • 在查询嵌套方式的基础上,开启懒加载的全局开关
<settings>
    <!--开启懒加载-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!--关闭积极加载-->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

五、Mybatis的注解开发

Mybatis也支持注解开发。但是需要明确的是,Mybatis仅仅是把映射文件 使用注解代替了;而Mybatis的核心配置文件,仍然是xml配置(加载映射器的xml配置文件改成自动注册指定包下的映射器)

1 准备环境

  1. 创建Java项目,导入jar包

  2. 创建JavaBean:User和Account

    public class User {
        private Integer id;
        private String username;
        private Date birthday;
        private String sex;
        private String address;
    
        private List<Account> accounts;
    
        //get/set...
        //toString...
    }
    
    
    public class Account {
        private Integer id;
        private Integer uid;
        private Double money;
    
        private User user;
    
        //get/set...
        //toString...
    }
    
    
  3. 创建映射器接口UserDao和AccountDao,备用

  4. 准备Mybatis的核心配置文件,配置好别名和映射器

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <typeAliases>
            <package name="com.itheima.domain"/>
        </typeAliases>
        <environments default="mysql_mybatis">
            <environment id="mysql_mybatis">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql:///mybatis"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                </dataSource>
            </environment>
        </environments>
        
        <--自动注册指定包下所有的映射器接口-->
        <mappers>
            <package name="com.itheima.dao"/>
        </mappers>
    </configuration>
    
    
  5. 准备好单元测试类备用

    /**
     * Mybatis的注解开发功能测试--简单的CURD操作
     */
    public class MybatisAnnotationTest {
        private InputStream is;
        private SqlSession session;
        private UserDao userDao;
        private AccountDao accountDao;
    
        @Before
        public void init() throws IOException {
            is = Resources.getResourceAsStream("SqlMapConfig.xml");
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
            session = factory.openSession();
            userDao = session.getMapper(UserDao.class);
            accountDao = session.getMapper(AccountDao.class);
        }
        @After
        public void destory() throws IOException {
            session.close();
            is.close();
        }
    }
    
    

2. 常用注解介绍

  • @Select:相当于映射文件里的select标签:用于配置查询方法的语句
  • @Insert:相当于映射文件里的insert标签
  • @SelectKey:相当于映射文件里的selectKey标签,用于添加数据后获取最新的主键值
  • @Update:相当于映射文件里的update标签
  • @Delete:相当于映射文件里的delete标签
  • @Results:相当于映射文件里的resultMap标签
  • @Result:相当于映射文件里的result标签,和@Results配合使用,封装结果集的
  • @One:相当于映射文件里的association,用于封装关联的一个JavaBean对象
  • @Many:相当于映射文件里的collection标签,用于封装关联的一个JavaBean对象集合

3. 简单CURD操作【掌握】

查询全部用户

  1. 在映射器接口UserDao中增加方法

    @Select("select * from user")
    List<User> queryAll();
    
    
  2. 在测试类MybatisAnnotationTest中编写测试代码

    @Test
    public void testQueryAll(){
        List<User> users = dao.queryAll();
        for (User user : users) {
            System.out.println(user);
        }
    }
    
    

根据主键查询一个用户

  1. 在映射器接口UserDao中增加方法

    @Select("select * from user where id = #{id}")
    User findById(Integer id);
    
    
  2. 在测试类MybatisAnnotationTest中编写测试代码

    @Test
    public void testFindById(){
        User user = dao.findById(41);
        System.out.println(user);
    }
    
    

添加用户

  1. 在映射器接口UserDao中增加方法

    @Insert("insert into user (id,username,birthday,sex,address) values (#{id},#{username},#{birthday},#{sex},#{address})")
    @SelectKey(
        statement = "select last_insert_id()", //查询最新主键值的SQL语句
        resultType = Integer.class,  //得到最新主键值的类型
        keyProperty = "id",  //得到最新主键值,保存到哪个属性里
        before = false  //是否在insert操作之前查询最新主键值
    )
    void save(User user);
    
    
  2. 在测试类MybatisAnnotationTest中编写测试代码

    @Test
    public void testSave(){
        User user = new User();
        user.setUsername("小红");
        user.setSex("女");
        user.setAddress("中粮商务公园");
        user.setBirthday(new Date());
    
        System.out.println("保存之前:" + user);
        dao.save(user);
        session.commit();
        System.out.println("保存之后:" + user);
    }
    
    

修改用户

  1. 在映射器接口UserDao中增加方法

    @Update("update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}")
    void edit(User user);
    
    
  2. 在测试类MybatisAnnotationTest中编写测试代码

    @Test
    public void testEdit(){
        User user = dao.findById(57);
        user.setAddress("广州");
    
        dao.edit(user);
        session.commit();
    }
    
    

删除用户

  1. 在映射器接口UserDao中增加方法

    @Delete("delete from user where id = #{id}")
    void delete(Integer id);
    
    
  2. 在测试类MybatisAnnotationTest中编写测试代码

    @Test
    public void testDelete(){
        dao.delete(57);
        session.commit();
    }
    
    

JavaBean属性名和字段名不一致的情况处理

  1. 创建JavaBean: User2

    public class User2 {
        private Integer userId;
        private String username;
        private Date userBirthday;
        private String userSex;
        private String userAddress;
    
        //get/set...
        //toString...
    }
    
    
  2. 在映射器接口UserDao中增加方法

    @Select("select * from user")
    @Results({
        @Result(property = "userId", column = "id", id = true),
        @Result(property = "username", column = "username"),
        @Result(property = "userBirthday", column = "birthday"),
        @Result(property = "userSex", column = "sex"),
        @Result(property = "userAddress", column = "address")
    })
    List<User2> queryAllUser2();
    
    
  3. 在测试类MybatisAnnotationTest中编写测试代码

    @Test
    public void testQueryAllUser2(){
        List<User2> user2List = dao.queryAllUser2();
        for (User2 user2 : user2List) {
            System.out.println(user2);
        }
    }
    
    

4. 多表关联查询

一对一(多对一)关联查询,实现懒加载(fetchType = FetchType.LAZY)

需求描述
  • 需求:查询帐号信息,及其关联的用户信息
需求实现
  1. 修改映射器接口UserDao,增加方法findById(供关联查询时使用)

    @Select("select * from user where id = #{id}")
    User findById(Integer id);
    
    
  2. 创建JavaBean:Account

    注意:Account中要有User的引用,前边已经准备好

  3. 修改映射器接口AccountDao,增加方法

    @Select("select * from account where id = #{id}")
    @Results({
        @Result(property = "id", column = "id", id = true),
        @Result(property = "uid", column = "uid"),
        @Result(property = "money", column = "money"),
        @Result(
            property = "user",
            javaType = User.class,
            column = "uid",
            one = @One(
                //一对一关联查询,调用select配置的statement,得到关联的User对象
                select = "com.itheima.dao.UserDao.findById",
                //FetchType.LAZY 表示要使用延迟加载
                fetchType = FetchType.LAZY
            )
        )
    })
    Account findById(Integer id);
    
    
  4. 编写测试代码

    @Test
    public void testOne2One(){
        Account account = accountDao.findById(1);
        System.out.println(account.getId() + ", "+ account.getMoney());
        
        //如果不执行下面这行代码,Mybatis不会发起查询用户的SQL
        System.out.println(account.getUser());
    }
    
    

一对多(多对多)关联查询,实现懒加载

需求描述
  • 需求:查询用户信息,及其关联的帐号集合信息
需求实现
  1. 修改映射器AccountDao,增加方法findAccountsByUid(供关联查询时使用)

    @Select("select * from account where uid = #{uid}")
    List<Account> findAccountsByUid(Integer uid);
    
    
  2. 修改JavaBean:User

    注意:User中需要有Account的集合,前边已经准备好

  3. 修改映射器接口UserDao,增加方法

    /**
         * 查询用户信息,及其关联的帐号信息集合
         * @param id
         * @return
         */
    @Select("select * from user where id = #{id}")
    @Results({
        @Result(property = "id",column = "id",id = true),
        @Result(property = "username",column = "username"),
        @Result(property = "birthday",column = "birthday"),
        @Result(property = "sex",column = "sex"),
        @Result(property = "address",column = "address"),
        @Result(
            property = "accounts",
            javaType = List.class, //注意,这里是List.class,而不是Account.class
            column = "id",
            many = @Many(
                //一对多关联查询,调用select对应的statement,得到帐号集合
                select = "com.itheima.dao.AccountDao.findAccountsByUid",
                //FetchType.LAZY 表示要使用延迟加载
                fetchType = FetchType.LAZY
            )
        )
    })
    User findUserAccountsById(Integer id);
    
    
  4. 编写测试代码

    @Test
    public void testOne2Many(){
        User user = userDao.findUserAccountsById(41);
        System.out.println(user.getUsername()+", " + user.getAddress());
        
        //如果不执行下面这行代码,Mybatis不会发起查询帐号的SQL语句
        System.out.println(user.getAccounts());
    }
    
    

上午内容复习

  • 动态sql拼接
<select>
	<include refif="selectUser"/>
	<where>
    	<if test="ids != null and ids.size() > 0">
            <foreach collection="ids" item="id" separator="," open="and id in(" close=")">
                #{id}
            </foreach>
        </if>
        <if test="user != null">
        	<if test="user.username != null and user.username.length() > 0">
            	and username like #{user.username}
            </if>
        </if>
    </where>
</select>
<sql id="selectUser"></sql>

  • 缓存

    • 一级缓存:本地缓存,是SqlSession对象的缓存
      • 使用SqlSession对象第一次查询一个数据,这个数据会被缓存起来
      • 使用相同SqlSession,再次查询相同的数据,会优先从缓存里查找
      • 当执行增删改,提交/回滚,手动清除缓存,关闭SqlSession时,会清除缓存
    • 二级缓存:全局缓存,是Mapper级别的缓存,即:相同的Mapper之间可以进行数据共享
      • 清除:当执行增删改时数据会清除
  • 多表查询

    • 对一查询

      • JavaBean要求:一个Account关联一个User,所以Account里要有一个User对象
      • sql语句要求:查询结果集一定不能有重名列。可以给列起别名保证列名不重复
      • 在映射文件里,要使用resultMap手动设置映射
      <select id="xxx" resultMap="yyy">
      	多表关联查询sql
      </select>
      <resultMap id="yyy" type="映射的JavaBean的全限定类名或别名">
      	
          <association property="关联JavaBean的那个属性名" javaType="关联的JavaBean的类型">
          	<id property="" column=""/>
              <result property="" column=""/>
          </association>
      </resultMap>
      
      
      • 查询嵌套的方式:查询所有的帐号,及关联的用户
      <select id="xxx" resultMap="yyy">
      	select * from account
      </select>
      <resultMap>
      	<id property="id" column="id"/>
          <result property="uid" column="uid"/>
          ....
          
          <!-- 配置 让Mybatis调用select指定方法,并传参uid的值,查询得到User对象 -->
          <association property="user" javaType="User" 
                       select="com.itheima.dao.UserDao.findById" column="uid"/>
      </resultMap>
      
      
    • 对多查询

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页