mybatis框架学习总结第四天

mybatis框架学习总结第四天

mybatis延迟加载策略

引入

通过前面的学习,我们已经掌握了 Mybatis 中一对一,一对多,多对多关系的配置及实现,可以实现对象的关联查询。实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的账户信息。此时就是我们所说的延迟加载。

何为延迟加载

延迟加载:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.

好处

先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

坏处

因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

实现需求

需求

查询账户(Account)信息并且关联查询用户(User)信息。如果先查询账户(Account)信息即可满足要求,当我们需要查询用户(User)信息时再查询用户(User)信息。把对用户(User)信息的按需去查询就是延迟加载。

分析:mybatis第三天实现多表操作时,我们使用了resultMap来实现一对一,一对多,多对多关系的操作。主要是通过 association、collection 实现一对一及一对多映射。association、collection 具备延迟加载功能。

使用assocation实现延迟加载

需求

查询账户信息同时查询用户信息。

账户的持久层dao
package com.itheima.dao;

import com.itheima.domain.Account;

import java.util.List;

public interface IAccountDao {
    
    /**
     * 查询所有账户,同时还要获取当前账户的所属用户信息
     * @return
     */
    List<Account> findAll();
    
}
账户的持久层映射文件

注意

assocation标签

主要用于加载一对一的关系映射,配置封装额外查询的内容

select属性

用于指定查询用户的唯一标识,

column属性

用于指定用户根据id查询时,所需要的参数的值

<?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.itheima.dao.IAccountDao">

    <!--定义封装account和user的resultMap-->
    <resultMap id="accountUserMap" type="account">
        <id property="id" column="id"></id>
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>
        <!--
        一对一的关系映射,配置封装user的内容
        select属性指定的内容:查询用户的唯一标识
        column属性指定的内容:用户根据id查询时,所需要的参数的值
        -->
        <association property="user" column="uid" javaType="user" select="com.itheima.dao.IUserDao.findById">

        </association>
    </resultMap>

    <!--查询所有-->
    <select id="findAll" resultMap="accountUserMap">
        select * from account
    </select>
     
  </mapper>
用户的持久层接口
package com.itheima.dao;

import com.itheima.domain.User;

import java.util.List;

/**
 * 用户的持久层接口
 */
public interface IUserDao {

    /**
     * 根据id查询用户信息
     * @param userId
     * @return
     */
    User findById(Integer userId);

}
用户的映射文件
<?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.itheima.dao.IUserDao">

    <!--根据id查询用户-->
    <select id="findById" parameterType="INT" resultType="user">
        select * from user where id = #{uid};
    </select>

</mapper>
开启mybatis的延迟加载策略

我们需要在 Mybatis 的配置文件 SqlMapConfig.xml 文件中添加延迟加载的配置

<!--配置参数-->
<settings>
    <!--开启Mybatis支持延迟加载-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>
测试类
package com.itheima.test;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;
import java.util.List;

/**
 * 测试mybatis的crud操作
 */
public class AccountTest {

    private InputStream in;
    private SqlSession sqlSession;
    private IAccountDao accountDao;

    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3.获取SqlSession对象
        //openSession方法设置为true就可以不用提交事务
        //注意:在mybatis中独立的这种事务的设置是可以的,但是如转账这种多事务这样对每个事务的独立设置是不可以的
        sqlSession = factory.openSession(true);
        //4.获取dao的代理对象
        accountDao = sqlSession.getMapper(IAccountDao.class);
    }

    @After //用于在测试方法执行之后执行
    public void destroy() throws Exception {
        //提交事务
        //sqlSession.commit(); //没有这条语句就会产生事务回滚,无法正常添加
        //6.释放资源
        sqlSession.close();
        in.close();
    }

    /**
     * 测试查询所有账户
     * 实现一对一的延迟加载
     * 注释的段落有利于理解
     */
    @Test
    public void testFindAll() {
        List<Account> accounts = accountDao.findAll();
        /*for (Account account : accounts) {
            System.out.println("---------------每个account的信息--------------");
            System.out.println(account);
            System.out.println(account.getUser());
        }*/
    }

}
输出结果

在这里插入图片描述

注意

若开启testFindAll()方法注释的部分,输出结果为,从而证实懒加载配置成功

在这里插入图片描述

使用Collection实现延迟加载

同样我们也可以在一对多关系配置的结点中配置延迟加载策略。结点中也有 select 属性,column 属性。

需求

完成加载用户对象时,查询该用户所拥有的账户信息。

用户的实体类
package com.itheima.domain;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

/*
* 实体类名称跟数据库字段不一样
* */
public class User implements Serializable {

    private Integer id;
    private String username;
    private String address;
    private String sex;
    private Date birthday;

    //一对多关系映射,主表实体应该包含从表实体的集合引用
    private List<Account> accounts;
    
}
用户的持久层dao
package com.itheima.dao;

import com.itheima.domain.User;

import java.util.List;

/**
 * 用户的持久层接口
 */
public interface IUserDao {

    /**
     * 查询所有用户,同时获取到用户下所有账户的信息
     * @return
     */
    List<User> findAll();
    
}
用户持久层映射文件

注意

collection标签

主要用于加载关联的集合对象

select属性

用于指定查询 account 列表的 sql 语句,所以填写的是该 sql 映射的 id

column属性

用于指定 select 属性的 sql 语句的参数来源,上面的参数来自于 user 的 id 列,所以就写成 id 这一个字段名了

ofType属性

用于指定在集合中元素的数据类型

<?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.itheima.dao.IUserDao">

    <!--定义User的resultMap-->
    <resultMap id="userAccountMap" type="user">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="address" column="address"></result>
        <result property="sex" column="sex"></result>
        <result property="birthday" column="birthday"></result>
        <!--
        配置user对象中accounts集合的映射
        ofType属性是指在集合中元素的位置
        -->
        <collection property="accounts" ofType="account" select="com.itheima.dao.IAccountDao.findAccountByUid" column="id">
        </collection>
    </resultMap>

    <!--查询所有 mybatis框架可以识别重复的部分并且把对应的信息封装进入-->
    <select id="findAll" resultMap="userAccountMap">
        select * from user
    </select>

</mapper>
账户的持久层dao
/**
 * 根据用户id查询账户信息
 * @param uid
 * @return
 */
List<Account> findAccountByUid(Integer uid);
账户持久层映射文件
<!--根据用户id查询账户列表-->
<select id="findAccountByUid" resultType="account">
    select * from account where uid = #{uid}
</select>
测试类
package com.itheima.test;

import com.itheima.dao.IUserDao;
import com.itheima.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;
import java.util.List;

/**
 * 测试mybatis的crud操作
 */
public class UserTest {

    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3.获取SqlSession对象
        //openSession方法设置为true就可以不用提交事务
        //注意:在mybatis中独立的这种事务的设置是可以的,但是如转账这种多事务这样对每个事务的独立设置是不可以的
        sqlSession = factory.openSession(true);
        //4.获取dao的代理对象
        userDao = sqlSession.getMapper(IUserDao.class);
    }

    @After //用于在测试方法执行之后执行
    public void destroy() throws Exception {
        //提交事务
        //sqlSession.commit(); //没有这条语句就会产生事务回滚,无法正常添加
        //6.释放资源
        sqlSession.close();
        in.close();
    }

    /**
     * 测试查询所有账户
     */
    @Test
    public void testFindAll() {
        List<User> users = userDao.findAll();
        /*for (User user : users) {
            System.out.println("---------------每个用户的信息--------------");
            System.out.println(user);
            System.out.println(user.getAccounts());
        }*/
    }
}
输出结果

在这里插入图片描述

注意

若开启testFindAll()方法注释的部分,输出结果为,从而证实懒加载配置成功

在这里插入图片描述

mybatis缓存

像大多数的持久化框架一样,Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。Mybatis 中缓存分为一级缓存,二级缓存。

图解

在这里插入图片描述

mybatis一级缓存

一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。

用户持久层dao

package com.itheima.dao;

import com.itheima.domain.User;

import java.util.List;

/**
 * 用户的持久层接口
 */
public interface IUserDao {

    /**
     * 根据id查询用户信息
     * @param userId
     * @return
     */
    User findById(Integer userId);

}

用户持久层映射文件

<!--根据id查询用户-->
<select id="findById" parameterType="INT" resultType="user" useCache="true">
    select * from user where id = #{uid};
</select>
package com.itheima.test;

import com.itheima.dao.IUserDao;
import com.itheima.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;
import java.util.List;

/**
 * 测试mybatis的crud操作
 */
public class UserTest {

    private InputStream in;
    private SqlSession sqlSession;
    private SqlSessionFactory factory;
    private IUserDao userDao;

    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory
        factory = new SqlSessionFactoryBuilder().build(in);
        //3.获取SqlSession对象
        //openSession方法设置为true就可以不用提交事务
        //注意:在mybatis中独立的这种事务的设置是可以的,但是如转账这种多事务这样对每个事务的独立设置是不可以的
        sqlSession = factory.openSession(true);
        //4.获取dao的代理对象
        userDao = sqlSession.getMapper(IUserDao.class);
    }

    @After //用于在测试方法执行之后执行
    public void destroy() throws Exception {
        //提交事务
        //sqlSession.commit(); //没有这条语句就会产生事务回滚,无法正常添加
        //6.释放资源
        sqlSession.close();
        in.close();
    }

    /**
     * 测试一级缓存
     */
    @Test
    public void testFirstLevelCache() {
        User user1 = userDao.findById(41);
        System.out.println("第一次查询的用户:" + user1);
        User user2 = userDao.findById(41);
        System.out.println("第二次查询的用户:" + user2);
        System.out.println(user1 == user2); //true,说明第二次是从SqlSession缓存中取得数据
    }

}

测试结果

在这里插入图片描述

分析

我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是 Mybatis 提供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询 id 为 41 的记录时,并没有发出 sql 语句从数据库中查询数据,而是从一级缓存中查询。

一级缓存的分析

注意

一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。

图解
在这里插入图片描述

分析

第一次发起查询用户 id 为 41 的用户信息,先去找缓存中是否有 id 为 41 的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

第二次发起查询用户 id 为 41 的用户信息,先去找缓存中是否有 id 为 41 的用户信息,缓存中有,直接从缓存中获取用户信息。

测试一级缓存的清空

一、close()
/**
 * 测试一级缓存
 */
@Test
public void testFirstLevelCache() {
    User user1 = userDao.findById(41);
    System.out.println("第一次查询的用户:" + user1);

    sqlSession.close();
    //再次获取SqlSession对象
    sqlSession = factory.openSession();
    userDao = sqlSession.getMapper(IUserDao.class);

    User user2 = userDao.findById(41);
    System.out.println("第二次查询的用户:" + user2);

    System.out.println(user1 == user2); //false
}

输出结果

在这里插入图片描述

分析

当执行sqlSession.close()后,再次获取sqlSession并查询id=41的User对象时,又重新执行了sql 语句,从数据库进行了查询操作。

二、clearCache()
/**
 * 测试一级缓存
 */
@Test
public void testFirstLevelCache() {
    User user1 = userDao.findById(41);
    System.out.println("第一次查询的用户:" + user1);

    sqlSession.clearCache(); //此方法也可以清空缓存

    User user2 = userDao.findById(41);
    System.out.println("第二次查询的用户:" + user2);

    System.out.println(user1 == user2); //false
}

输出结果

在这里插入图片描述

分析

当执行sqlSession.clearCache()后,再次获取sqlSession并查询id=41的User对象时,又重新执行了sql 语句,从数据库进行了查询操作。

三、commit()
/**
 * 测试一级缓存
 */
@Test
public void testFirstLevelCache() {
    User user1 = userDao.findById(41);
    System.out.println("第一次查询的用户:" + user1);

    sqlSession.commit(); //此方法也可以清空缓存

    User user2 = userDao.findById(41);
    System.out.println("第二次查询的用户:" + user2);

    System.out.println(user1 == user2); //false
}

输出结果
在这里插入图片描述

分析

当执行sqlSession.commit()后,再次获取sqlSession并查询id=41的User对象时,又重新执行了sql 语句,从数据库进行了查询操作。

测试缓存的同步(即调用修改清空缓存)
/**
 * 测试缓存的同步
 * 一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等,缓存会清空
 */
@Test
public void testClearCache() {
    //1.根据id查询用户
    User user1 = userDao.findById(41);
    System.out.println(user1);
    //2.更新用户信息
    user1.setUsername("update user clear cache");
    user1.setAddress("北京市海淀区");
    userDao.updateUser(user1);
    //3.再次查询id为41的用户
    User user2 = userDao.findById(41);
    System.out.println(user2);

    System.out.println(user1 == user2);
}

输出结果

在这里插入图片描述

分析

当通过update方法修改数据时,再次查询该条数据不会从sqlSession的一级缓存中获取,而是再通过数据库获取并将查询的结果再次放入缓存。

mybatis二级缓存

二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。

图解
在这里插入图片描述

解释

sqlSession1 去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。

如果 SqlSession3 去执行相同 mapper 映射下 sql,执行 commit 提交,将会清空该 mapper 映射下的二级缓存区域的数据。

sqlSession2 去查询与 sqlSession1 相同的用户信息,首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据。

二级缓存的开启与关闭

第一步:在SqlMapConfig.xml文件开启二级缓存
<settings>
    <!--配置二级缓存 可不配置,因为其默认值就是true -->
    <setting name="cacheEnabled" value="true"/>
</settings>
因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为false 代表不开启二级缓存。
第二步:配置相关的Mapper映射文件
<?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.itheima.dao.IUserDao">
    <!--开启user支持二级缓存-->
    <cache/>
 </mapper>
第三步:配置statement上面的useCache属性
<!--根据id查询用户-->
<select id="findById" parameterType="INT" resultType="user" useCache="true">
    select * from user where id = #{uid};
</select>

将 UserDao.xml 映射文件中的<select>标签中设置 useCache=”true”代表当前这个 statement 要使用二级缓存,如果不使用二级缓存可以设置为 false。
注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。
测试类
package com.itheima.test;

import com.itheima.dao.IUserDao;
import com.itheima.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;

/**
 * 测试mybatis的crud操作
 */
public class SecondLevelCacheTest {

    private InputStream in;

    private SqlSessionFactory factory;


    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory
        factory = new SqlSessionFactoryBuilder().build(in);
    }

    @After //用于在测试方法执行之后执行
    public void destroy() throws Exception {
        in.close();
    }

    /**
     * 测试二级缓存
     */
    @Test
    public void testSecondLevelCache() {
        SqlSession sqlSession1 = factory.openSession();
        IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
        User user1 = dao1.findById(41);
        System.out.println(user1);
        sqlSession1.close(); //一级缓存消失

        SqlSession sqlSession2 = factory.openSession();
        IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
        User user2 = dao2.findById(41);
        System.out.println(user2);
        sqlSession2.close();

        System.out.println(user1 == user2); //false
    }

}
输出结果

在这里插入图片描述

分析

经过上面的测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二次查询时,我们发现并没有对数据库发出 sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存。但是为什么输出false,因为二级缓存存放的时内容,而不是对象,例如:{“id”:41,username:“老王”,“address”:“北京”},用的时候把该内容拿过来,创建一个新的用户,再把这个数据放进去,故为false

二级缓存注意事项

当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。

package com.itheima.domain;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

/*
* 实体类名称跟数据库字段不一样
* */
public class User implements Serializable {

    private Integer id;
    private String username;
    private String address;
    private String sex;
    private Date birthday;

}

mybatis注解开发

Mybatis 也可以使用注解开发方式,这样我们就可以减少编写 Mapper 映射文件了。本次我们先围绕一些基本的 CRUD 来学习,再学习复杂映射关系及延迟加载。

mybatis的常用注解说明

@Insert:实现新增

@Update:实现更新

@Delete:实现删除

@Select:实现查询

@Result:实现结果集封装

@Results:可以与@Result 一起使用,封装多个结果集

@ResultMap:实现引用@Results 定义的封装

@One:实现一对一结果集封装

@Many:实现一对多结果集封装

@SelectProvider: 实现动态 SQL 映射

@CacheNamespace:实现注解二级缓存的使用

使用mybatis注解实现基本CRUD

实体类
package com.itheima.domain;

import java.io.Serializable;
import java.util.Date;

public class User implements Serializable {

    private Integer id;
    private String username;
    private String address;
    private String sex;
    private Date birthday;
    
}
使用注解方式开发持久层接口
package com.itheima.dao;

import com.itheima.domain.User;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
/*
* 在mybatis中针对CRUD一共有四个注解
* @Select @Insert @Update @Delete
* */
public interface IUserDao {
    /**
     * 查询所有用户
     * @return
     */
    /*只有一个注解value,可省略*/
    @Select("select * from user")
    List<User> findAll();

    /**
     * 保存用户
     * @param user
     */
    @Insert("insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday})")
    void saveUser(User user);

    /**
     * 更新用户
     * @param user
     */
    @Update("update user set username=#{username},sex=#{sex},birthday=#{birthday},address=#{address} where id=#{id}")
    void updateUser(User user);

    /**
     * 删除用户
     * @param userId
     */
    @Delete("delete from user where id=#{id}")
    void deleteUser(Integer userId);

    /**
     * 根据id查询用户
     * @param userId
     * @return
     */
    @Select("select * from user where id=#{id}")
    User findById(Integer userId);

    /**
     * 根据用户名称模糊查询
     * @param username
     * @return
     */
//    @Select("select * from user where username like #{username}")
    @Select("select * from user where username like '%${value}%'")
    List<User> findUserByName(String username);

    /**
     * 查询总用户数量
     * @return
     */
    @Select("select count(*) from user")
    int findTotalUser();
}
SqlMapConfig.xml

注意:只要使用了注解开发,但同时在resources下的dao下包含了xml,此时不管用不用xml都会报错
解决:可以将xml删除,或者移到别的路径下

<!--指定带有注解的dao所在位置,以下两种配置都可以-->
    <mappers>
        <!--class属性:如果是用注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名-->
        <!--<mapper class="com.itheima.dao.IUserDao"></mapper>-->
        <!--用于指定dao接口所在的包,当指定之后就不需要再写mapper以及resource或class了,但注意dao和xml必须放在同一个名的包下-->
        <package name="com.itheima.dao"/>
    </mappers>
测试类
package com.itheima.test;

import com.itheima.dao.IUserDao;
import com.itheima.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;

public class AnnotationCRUDTest {

    private InputStream in;
    private SqlSessionFactory factory;
    private SqlSession session;
    private IUserDao userDao;

    @Before
    public void init() throws IOException {
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        factory = new SqlSessionFactoryBuilder().build(in);
        session = factory.openSession();
        userDao = session.getMapper(IUserDao.class);
    }

    @After
    public void destroy() throws IOException {
        session.commit();
        session.close();
        in.close();
    }

    @Test
    public void testFindAll() {
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }
    }

    @Test
    public void testSave() {
        User user = new User();
        user.setUsername("mybatis annotation");
        user.setAddress("北京市昌平区");

        userDao.saveUser(user);
    }

    @Test
    public void testUpdate() {
        User user = new User();
        user.setId(52);
        user.setUsername("mybatis annotation update");
        user.setAddress("北京市海淀区");
        user.setSex("男");
        user.setBirthday(new Date());

        userDao.updateUser(user);
    }

    @Test
    public void testDelete() {
        userDao.deleteUser(50);
    }

    @Test
    public void testFindOne() {
        User user = userDao.findById(48);
        System.out.println(user);
    }

    @Test
    public void testFindByName() {
//        List<User> users = userDao.findUserByName("%王%");
        List<User> users = userDao.findUserByName("王");
        for (User user : users) {
            System.out.println(user);
        }
    }

    @Test
    public void testFindTotal() {
        int total = userDao.findTotalUser();
        System.out.println(total);
    }
}

使用注解实现复杂关系映射开发

实现复杂关系映射之前我们可以在映射文件中通过配置来实现,在使用注解开发时我们需要借助@Results 注解,@Result 注解,@One 注解,@Many 注解。

复杂关系映射的注解说明

@Results 注解

代替的是标签,该注解中可以使用单个@Result 注解,也可以使用@Result 集合

@Results({@Result(),@Result()})或@Results(@Result())

@Result 注解

代替了id标签和result标签

@Result中属性介绍

id 是否是主键字段

column 数据库的列名

property 需要装配的属性名

one 需要使用的@One 注解(@Result(one=@One)()))

many 需要使用的@Many 注解(@Result(many=@many)()))

@One 注解(一对一)

代替了assocation标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。

@One注解属性介绍

select 指定用来多表查询的sqlmapper

fetchType 会覆盖全局的配置参数 lazyLoadingEnabled

使用格式:@Result(column=" “,property=”",one=@One(select=""))

@Many 注解(多对一)

代替了Collection标签,是多表查询的关键,在注解中用来指定子查询返回对象集合。

注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType

(一般为 ArrayList)但是注解中可以不定义;

使用格式:@Result(property="",column="",many=@Many(select=""))

使用注解实现一对一复杂关系映射及延迟加载
需求

加载账户信息时并且加载该账户的用户信息,根据情况可实现延迟加载。(注解方式实现)

添加User实体类及Account实体类
package com.itheima.domain;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

public class User implements Serializable {

    private Integer userId;
    private String userName;
    private String userAddress;
    private String userSex;
    private Date userBirthday;
    
}
package com.itheima.domain;

import java.io.Serializable;

public class Account implements Serializable {

    private Integer id;
    private Integer uid;
    private Double money;

    //多对一(mybatis中称之为一对一)的映射:一个账户只能属于一个用户
    private User user;
    
}
添加账户的持久层接口并使用注解配置

注意:@One:相当于的配置

select 属性:代表将要执行的 sql 语句位置

fetchType 属性:代表加载方式,一般如果要延迟加载都设置为 LAZY 的值,立即加载设置为 EAGER 的值,默认为立即加载设置为 DEFAULT

package com.itheima.dao;

import com.itheima.domain.Account;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;

public interface IAccountDao {
    /**
     * 查询所有账户,并且获取每个账户所属的用户信息
     * @return
     */
    @Select("select * from account")
    @Results(id = "userMap",value = {
            @Result(id = true,column = "id",property = "id"),
            @Result(column = "uid",property = "uid"),
            @Result(column = "money",property = "money"),
            @Result(property = "user",column = "uid",one = @One(select = "com.itheima.dao.IUserDao.findById",fetchType = FetchType.LAZY))
    })
    List<Account> findAll();
}
添加用户的持久层接口并使用注解配置
package com.itheima.dao;

import com.itheima.domain.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;
/*
* 在mybatis中针对CRUD一共有四个注解
* @Select @Insert @Update @Delete
* */
public interface IUserDao {
    /**
     * 查询所有用户
     * @return
     */
    /*只有一个属性value,可省略*/
    /*
    * 实体类中修改属性和数据库的字段名称不一致时,可以使用起别名的方式,但此时所有的select都要改,不好
    * 解决:使用@Results注解
    * id = "userMap"保证后面要用到的地方不用再这样封装
    * */
    @Select("select * from user")
    @Results(id = "userMap",value={
            @Result(id=true,column = "id",property = "userId"),
            @Result(column = "username",property = "userName"),
            @Result(column = "address",property = "userAddress"),
            @Result(column = "sex",property = "userSex"),
            @Result(column = "birthday",property = "userBirthday")
    })
    List<User> findAll();

    /**
     * 根据id查询用户
     * @param userId
     * @return
     */
    @Select("select * from user where id=#{id}")
    //@ResultMap(value = {"userMap"}) //标准写法
    @ResultMap("userMap") //如果只有一个属性且是value时value可以省略,数组元素只有一个{}可以省略
    User findById(Integer userId);

}
测试一对一关联及延迟加载
package com.itheima.test;

import com.itheima.dao.IAccountDao;
import com.itheima.dao.IUserDao;
import com.itheima.domain.Account;
import com.itheima.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class AccountTest {

    private InputStream in;
    private SqlSessionFactory factory;
    private SqlSession session;
    private IAccountDao accountDao;

    @Before
    public void init() throws IOException {
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        factory = new SqlSessionFactoryBuilder().build(in);
        session = factory.openSession();
        accountDao = session.getMapper(IAccountDao.class);
    }

    @After
    public void destroy() throws IOException {
        session.commit();
        session.close();
        in.close();
    }

    @Test
    public void testFindAll() {
        List<Account> accounts = accountDao.findAll();
        /*for (Account account : accounts) {
            System.out.println("--------每个账户的信息--------");
            System.out.println(account);
            System.out.println(account.getUser());
        }*/
    }

}
输出结果

在这里插入图片描述

注意

若开启testFindAll()方法注释的部分,输出结果为,从而证实懒加载配置成功

在这里插入图片描述

附加

若在账户的持久层接口配置立即加载,即不用也加载,配置:one = @One(select = “com.itheima.dao.IUserDao.findById”,fetchType = FetchType.EAGER),此时即使注释testFindAll方法的注释部分结果为

在这里插入图片描述

使用注解实现一对多复杂关系映射
需求

查询用户信息时,也要查询他的账户列表。使用注解方式实现。

分析

一个用户具有多个账户信息,所以形成了用户(User)与账户(Account)之间的一对多关系。

User实体类加入accounts
package com.itheima.domain;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

public class User implements Serializable {

    private Integer userId;
    private String userName;
    private String userAddress;
    private String userSex;
    private Date userBirthday;

    //一对多关系映射,一个用户对应多个账户
    private List<Account> accounts;
    
} 
编写用户的持久层接口并使用注解配置

注意

@Many:相当于的配置

select 属性:代表将要执行的 sql 语句位置

fetchType 属性:代表加载方式,一般如果要延迟加载都设置为 LAZY 的值,立即加载设置为 EAGER 的值,默认为立即加载设置为 DEFAULT

package com.itheima.dao;

import com.itheima.domain.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;
/*
* 在mybatis中针对CRUD一共有四个注解
* @Select @Insert @Update @Delete
* */
public interface IUserDao {
    /**
     * 查询所有用户
     * @return
     */
    /*只有一个注解value,可省略*/
    /*
    * 实体类中修改属性和数据库的字段名称不一致时,可以使用起别名的方式,但此时所有的select都要改,不好
    * 解决:使用@Results注解
    * id = "userMap"保证后面要用到的地方不用再这样封装
    * */
    @Select("select * from user")
    @Results(id = "userMap",value={
            @Result(id=true,column = "id",property = "userId"),
            @Result(column = "username",property = "userName"),
            @Result(column = "address",property = "userAddress"),
            @Result(column = "sex",property = "userSex"),
            @Result(column = "birthday",property = "userBirthday"),
            @Result(property = "accounts",column = "id",
                    many = @Many(select = "com.itheima.dao.IAccountDao.findAccountByUid",fetchType = FetchType.LAZY))
    })
    List<User> findAll();

    /**
     * 根据id查询用户
     * @param userId
     * @return
     */
    @Select("select * from user where id=#{id}")
    //@ResultMap(value = {"userMap"}) //标准写法
    @ResultMap("userMap") //如果只有一个属性且是value时value可以省略,数组元素只有一个{}可以省略
    User findById(Integer userId);

}
编写账户的持久层接口并使用注解配置
/**
 * 根据用户id查询账户信息
 * @param userId
 * @return
 */
@Select("select * from account where uid = #{userId}")
List<Account> findAccountByUid(Integer userId);
添加测试方法
package com.itheima.test;

import com.itheima.dao.IUserDao;
import com.itheima.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;

public class AnnotationCRUDTest {

    private InputStream in;
    private SqlSessionFactory factory;
    private SqlSession session;
    private IUserDao userDao;

    @Before
    public void init() throws IOException {
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        factory = new SqlSessionFactoryBuilder().build(in);
        session = factory.openSession();
        userDao = session.getMapper(IUserDao.class);
    }

    @After
    public void destroy() throws IOException {
        session.commit();
        session.close();
        in.close();
    }

    @Test
    public void testFindAll() {
        List<User> users = userDao.findAll();
        //注释为了检验延迟加载的效果
        /*for (User user : users) {
            System.out.println("-------每个用户的信息-------");
            System.out.println(user);
            System.out.println(user.getAccounts());
        }*/
    }
    
}
输出结果

在这里插入图片描述

注意

若开启testFindAll()方法注释的部分,输出结果为,从而证实懒加载配置成功

在这里插入图片描述

附加

如果想要立即加载,只需在用户的持久层接口修改配置

many = @Many(select = “com.itheima.dao.IAccountDao.findAccountByUid”,fetchType = FetchType.EAGER)

mybatis基于注解的二级缓存

在SqlMapConfig中开启二级缓存支持
<!--配置开启二级缓存 可不配置,默认是true-->
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
在持久层接口中使用注解配置二级缓存
//开启二级缓存 配置后SecondLevelCatchTest类中的testFindOne方法第二次查询就会查询缓存(即只执行一次sql语句)
@CacheNamespace(blocking = true)
public interface IUserDao {}
测试类
package com.itheima.test;

import com.itheima.dao.IUserDao;
import com.itheima.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

public class SecondLevelCatchTest {
    private InputStream in;
    private SqlSessionFactory factory;
    private SqlSession session;
    private IUserDao userDao;

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

    @After
    public void destroy() throws IOException {
        in.close();
    }

    @Test
    public void testFindOne() {
        SqlSession session = factory.openSession();
        IUserDao userDao = session.getMapper(IUserDao.class);
        User user = userDao.findById(48);
        System.out.println(user);

        session.close(); //释放一级缓存

        SqlSession session1 = factory.openSession(); //再次打开session
        IUserDao userDao1 = session1.getMapper(IUserDao.class);
        User user1 = userDao1.findById(48);
        System.out.println(user1);

        session1.close();
    }

}
输出结果

在这里插入图片描述

分析

通过输出结果可以看到第二次查询未执行sql语句,故配置成功

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值