mybatis学习-day02


mybatis-day02

目录
  • mybatis-day02
  • 一、SQL深入-动态sql【重点】
    • 准备环境
    • 标签:
      • 需求描述
      • 需求实现
        • 1) 在映射器UserDao中增加方法
        • 2) 在映射文件UserDao.xml中配置
        • 3) 在单元测试类中编写测试代码
      • 小结
    • 标签
      • 需求描述
      • 需求实现
      • 小结
    • 标签
      • 需求描述
      • 需求实现
        • 1) 在映射器接口UserDao中增加方法
        • 2) 在映射文件UserDao.xml中添加statement
        • 3) 在单元测试类中编写测试代码
      • 小结
    • 标签
      • 需求描述
      • 需求实现
        • 1) 在映射文件UserDao.xml中定义SQL片段
        • 2) 在映射文件UserDao.xml中使用SQL片段
      • 小结
    • 小结
  • 二、Mybatis的缓存
    • 一级缓存
      • 什么是一级缓存
      • 一级缓存效果演示
      • 一级缓存的源码分析
    • 二级缓存
      • 什么是二级缓存
      • 二级缓存效果演示
        • 1) 修改全局配置文件,开启全局的二级缓存开关
        • 2) 修改映射文件UserDao.xml,让映射器支持二级缓存
        • 3) 修改映射文件UserDao中的findById,让此方法(statement)支持二级缓存
        • 4) 修改JavaBean:User
        • 5) 编写测试代码
      • 二级缓存原理
  • 三、多表关联查询【重点】
    • 准备工作
    • 一对一(多对一)关联查询
      • 方案一:类继承方式(不推荐)
        • 1) 创建JavaBean:UserAccount
        • 2) 在映射器AccountDao中增加方法
        • 3) 在映射文件AccountDao.xml中增加配置
        • 4) 编写测试代码
      • 方案二:类引用方式(推荐)
        • 1) 修改JavaBean:Account类
        • 2) 在映射器AccountDao中增加方法
        • 3) 在映射文件AccountDao.xml中增加配置
        • 4) 编写测试代码
      • 方案三:查询嵌套方式(推荐)
        • 1) 修改JavaBean:Account类
        • 2) 在映射器AccountDao中增加方法
        • 3) 在映射文件AccountDao.xml中增加配置
        • 4) 在映射器UserDao中增加方法
        • 5) 在映射文件UserDao.xml中增加配置
        • 6) 编写测试代码
      • 小结
    • 一对多(多对多)关联查询
      • 类引用方式
        • 1) 修改JavaBean:User类
        • 2) 在映射器UserDao中增加方法
        • 3) 在映射文件UserDao.xml中增加配置
        • 4) 编写测试代码
      • 查询嵌套方式
        • 1) 修改JavaBean:User类
        • 2) 在映射器UserDao中增加方法
        • 3) 在映射文件UserDao.xml中增加配置
        • 4) 在映射器AccountDao中增加方法
        • 5) 在映射文件AccountDao.xml中增加配置
        • 6) 编写测试代码
      • 小结
    • 练习-多对多关联查询
      • 准备工作
      • 练习1:查询所有用户,及关联的角色集合
        • 1) 在映射器接口UserDao中增加方法
        • 2) 在映射文件UserDao.xml中增加statement
        • 3) 在单元测试类中编写测试代码
      • 练习2:查询所有角色,及关联的用户集合
        • 1) 在映射器接口RoleDao中增加方法
        • 2) 在映射文件RoleDao.xml中增加statement
        • 3) 在单元测试类中编写测试代码
      • 小结
  • 四、Mybatis的延迟加载【重点】
    • 什么是延迟加载
      • 立即加载
      • 延迟加载
      • 延迟加载的使用场景
    • 延迟加载的实现
      • 对一的延迟加载
        • 1. 用查询嵌套方式,查询帐号及关联的用户
          • 1) 在映射器AccountDao中增加方法
          • 2) 在映射文件AccountDao.xml中增加配置
          • 3) 在映射器UserDao中增加方法
          • 4) 在映射文件UserDao.xml中增加配置
        • 2. 在核心配置文件中开启懒加载
        • 3. 编写测试代码
      • 对多的延迟加载
        • 1. 用查询嵌套方式,查询用户及关联的帐号
          • 1) 在映射器UserDao中增加方法
          • 2) 在映射文件UserDao.xml中增加statement
          • 3) 在映射器AccountDao中增加方法
          • 4) 在映射文件AccountDao.xml中增加statement
        • 2. 在核心配置文件中开启懒加载
        • 3. 编写测试代码
    • 小结
  • 五、Mybatis的注解开发
    • 1 准备环境
    • 2. 常用注解介绍
    • 3. 简单CURD操作【掌握】
      • 查询全部用户
      • 根据主键查询一个用户
      • 添加用户
      • 修改用户
      • 删除用户
      • JavaBean属性名和字段名不一致的情况处理
    • 4. 多表关联查询
      • 一对一(多对一)关联查询,实现懒加载
        • 需求描述
        • 需求实现
      • 一对多(多对多)关联查询,实现懒加载
        • 需求描述
        • 需求实现

  • 搭建开发环境

    • 准备工作:
      • 导入依赖:mysql驱动,mybatis,log4j, junit
      • 准备JavaBean:建议属性名和表字段名保持一致
    • 使用mybatis
      • 创建映射器:dao层的接口,接口里写方法
      • 创建映射文件:在映射文件里给每个方法增加配置,主要配置sql语句
    • 提供全局配置文件:
      • typeAliases类型别名
      • 数据库环境
      • 映射器mappers
    • 提供日志配置文件:log4j.properties放到类路径下(普通工程src是类路径;Maven工程src\main\java,和src\main\resources是类路径)
    • 功能测试
  • 代理方式CURD

    • 增加:insert标签

      • id属性:写对应的方法名
      • parameterType属性:参数类型,写全限定类名 或者 别名
      • 在insert标签里边写sql语句
      
      	select last_insert_id()
      
      insert into tbl () values (#{JavaBean的属性名},...)
      
    • 修改:update标签

      • id属性:写对应的方法名
      • parameterType属性:参数类型,写全限定类名 或者 别名
    • 删除:delete标签

      • id属性:写对应的方法名
      • parameterType属性:参数类型,写全限定类名 或者 别名
    • 查询:select标签

      • id属性:写对应的方法名
      • parameterType属性:参数类型,写全限定类名 或者 别名
      • resultType属性:自动映射,Mybatis会针对JavaBean属性名和字段相同的 进行自动映射
      • resultMap属性:手动映射,手动配置JavaBean属性名和字段名的映射关系
  • parameterType

    • 可以有一个参数:
      • 假如参数是简单类型,#{随意写}
      • 假如参数是POJO,#{pojo的属性名}
      • 假如参数是Map,#{map的key}
      • 假如参数是List,#{list[索引]} #{collection[索引]}
      • 假如参数是数组,#{array[索引]}
    • 可以有多个参数
      • 没有使用@Param配置参数名称
        • #{arg0}, #{arg1}, ...
        • #{param1}, #{param2},....
      • 假如使用了@Param配置了参数名称
        • #{配置的参数名称}
        • #{param1}, #{param2},....

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

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

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

常用的标签有:

  • :用来进行判断,相当于Java里的if判断
  • :通常和if配合,用来代替SQL语句中的where 1=1
  • :用来遍历一个集合,把集合里的内容拼接到SQL语句中。例如拼接:in (value1, value2, ...)
  • :用于定义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();
        }
    }
    

标签:


	SQL语句内容, 如果判断为true,这里的SQL语句就会进行拼接

需求描述

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

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

需求实现

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

小结

标签

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

  1. 标签代替了where 1=1

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

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

需求描述

? 使用标签代替where 1=1

需求实现

  • 只需要修改一下配置文件:
    
  • 再次运行测试代码,查看结果仍然正常

小结

标签

? 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标签实现。



    #{id}

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 findByIds(QueryVO vo);
2) 在映射文件UserDao.xml中添加statement


3) 在单元测试类中编写测试代码
    @Test
    public void testFindUserByIdsQueryVO(){
        QueryVO vo = new QueryVO();
        vo.setIds(new Integer[]{41, 42});

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

小结


   #{每一个元素的变量名}


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

标签

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

定义SQL片段:

sql语句片段

引用SQL片段:


扩展:

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

?

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

需求描述

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

需求实现

1) 在映射文件UserDao.xml中定义SQL片段
    
        select * from user
    
2) 在映射文件UserDao.xml中使用SQL片段

小结

小结



select * from user

二、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);
    }

    @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接口:缓存接口

二级缓存

什么是二级缓存

什么是二级缓存?

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

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

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

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

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

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

注意事项:

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

二级缓存效果演示

1) 修改全局配置文件,开启全局的二级缓存开关

    
    

2) 修改映射文件UserDao.xml,让映射器支持二级缓存

    
    
    ......

3) 修改映射文件UserDao中的findById,让此方法(statement)支持二级缓存


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 queryAllAccounts1();
3) 在映射文件AccountDao.xml中增加配置
  • 注意:SQL语句查询结果集中,不能有重名列。如果有,给列起别名保证没有重名列

4) 编写测试代码
@Test
public void testQueryAllAccounts(){
    List 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 queryAllAccounts2();
3) 在映射文件AccountDao.xml中增加配置
  • 注意:SQL语句查询结果集中,不能有重名列。如果有,给列起别名保证没有重名列



    
    
    
    
    
        
        
        
        
        
    

4) 编写测试代码
@Test
public void testQueryAllAccounts2(){
    List accounts = accountDao.queryAllAccounts2();
    for (Account account : accounts) {
        System.out.println(account);
    }
}

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

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

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

        
        
    
4) 在映射器UserDao中增加方法
User findById(Integer id);
5) 在映射文件UserDao.xml中增加配置

6) 编写测试代码
    @Test
    public void testOne() throws IOException {
        List accounts = accountDao.queryAllAccounts3();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }

小结

对一关联查询注意事项:

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

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



    
    
    
    

    
    
        
        
        
        
        
    

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

  1. 查询所有帐号。把帐号的数据封装到Account对象里
  2. Account里需要的那个User:调用另外一个方法,查询当前帐号关联的那个User对象

    
        
        
        
		
        
        
    
  • 被调用的方法
public interface UserDao {
    User findById(Integer id);
}

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

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

类引用方式

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

    //get/set...
    //toString...
}
2) 在映射器UserDao中增加方法
List queryAllUsers();
3) 在映射文件UserDao.xml中增加配置
  • 注意:SQL语句查询结果集中,不能有重名列。如果有,给列起别名保证没有重名列



    
    
    
    
    

    
    
        
        
        
    

4) 编写测试代码
@Test
public void testQueryAllUsers(){
    List users = userDao.queryAllUsers();
    for (User user : users) {
        System.out.println(user);
    }
}

查询嵌套方式

1) 修改JavaBean:User类
  • 同类引用方式里的JavaBean相同,略
2) 在映射器UserDao中增加方法
List queryAllUser2();
3) 在映射文件UserDao.xml中增加配置


    
    
    
    
    

    

4) 在映射器AccountDao中增加方法
List findByUid(Integer uid);
5) 在映射文件AccountDao.xml中增加配置

6) 编写测试代码
    @Test
    public void testMany() throws IOException {
        UserDao userDao = session.getMapper(UserDao.class);
        List 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 roles;
    
        //get/set方法......
        //toString方法......
    }
    
    public class Role {
        private Integer id;
        private String roleName;
        private String roleDesc;
    
        private List 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 queryAllUsers();
2) 在映射文件UserDao.xml中增加statement



    
    
    
    
    

    
    
        
        
        
    

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

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

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


    
    
    

    
        
        
        
        
        
    

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

小结

  • 只要是多表关联,就需要修改JavaBean
    • 如果是关联一个,Account里要有一个User
    • 如果是关联多个,User里要有一个List
  • sql语句:查询结果集一定不能有重名列,通过起别名保证所有列不重复
  • 在映射文件里,使用resultMap手动映射


	
    
    
    
    
    
    
    
    
    
    
    

四、Mybatis的延迟加载【重点】

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

什么是延迟加载

立即加载

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

延迟加载

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

延迟加载的使用场景

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

延迟加载的实现

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

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

对一的延迟加载

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

步骤:

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

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

1. 用查询嵌套方式,查询帐号及关联的用户
1) 在映射器AccountDao中增加方法
List queryAllAccounts();
2) 在映射文件AccountDao.xml中增加配置


    
    
    
    
    

3) 在映射器UserDao中增加方法
User findById(Integer id);
4) 在映射文件UserDao.xml中增加配置

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


    
    
    
    

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

对多的延迟加载

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

实现步骤:

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

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

1. 用查询嵌套方式,查询用户及关联的帐号
1) 在映射器UserDao中增加方法
List queryAllUsers();
2) 在映射文件UserDao.xml中增加statement


    
    
    
    
    

    

3) 在映射器AccountDao中增加方法
List findByUid(Integer uid);
4) 在映射文件AccountDao.xml中增加statement

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


    
    
    
    

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

小结

  • 在查询嵌套方式的基础上,开启懒加载的全局开关

    
    
    
    

五、Mybatis的注解开发

 Mybatis也支持注解开发。但是需要明确的是,Mybatis仅仅是把映射文件 使用注解代替了;而Mybatis的核心配置文件,仍然是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 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"?>
    
    
        
            
        
        
            
                
                
                    
                    
                    
                    
                
            
        
        
            
        
    
    
  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 queryAll();
    
  2. 在测试类MybatisAnnotationTest中编写测试代码

    @Test
    public void testQueryAll(){
        List 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 queryAllUser2();
    
  3. 在测试类MybatisAnnotationTest中编写测试代码

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

4. 多表关联查询

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

需求描述
  • 需求:查询帐号信息,及其关联的用户信息
需求实现
  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.guang.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 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.guang.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拼接


  • 缓存

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

    • 对一查询

      • JavaBean要求:一个Account关联一个User,所以Account里要有一个User对象
      • sql语句要求:查询结果集一定不能有重名列。可以给列起别名保证列名不重复
      • 在映射文件里,要使用resultMap手动设置映射
      
      
      	
          
          	
              
          
      
      
      • 查询嵌套的方式:查询所有的帐号,及关联的用户
      
      
      	
          
          ....
          
          
          
      
      
    • 对多查询