一、延迟加载策略

1.1 什么是延迟加载(懒加载)

  • 在需要用到数据时才进行加载,不需要用到数据时就不加载数据。也称为懒加载
  • 好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表比关联查询多张表速度要快。
  • 坏处:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

1.2 实现懒加载

  • 使用“账户Account - 用户User”,表示一对一,一个账户只能被一个用户拥有。
  • 使用“用户User - 账户Account”,表示一对多,一个用户可以拥有多个账户。
  • Account中含有private User user;
  • User中含有private List<Account> accounts;

1.2.1 Account持久层DAO接口

public interface IAccountDao {
    /**
     * 查询所有账户
     * @return
     */
    List<Account> findAll();

    /**
     * 根据用户id查询账户信息
     * @param uid
     * @return
     */
    List<Account> findAccountByUid(Integer uid);
}

1.2.2 Account持久层映射文件

  • association:表示一对一关系

    • property:实体类中的属性名
    • javaType:查询结构以user对象表示
    • select:需要用到对方的查询方法。<font color="red">全限定类名+方法名</font>
    • 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.ruki.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" javaType="user" select="com.ruki.dao.IUserDao.findById" column="uid"></association>
    </resultMap>

    <!-- 查询所有用户 -->
    <!--<select id="findAll" resultType="com.ruki.domain.User">-->
    <select id="findAll" resultMap="accountUserMap">
        select * from account
    </select>

    <select id="findAccountByUid" parameterType="integer" resultType="account">
        select * from account where uid=#{uid}
    </select>

</mapper>

1.2.3 User持久层DAO接口

public interface IUserDao {
    /**
     * 查找所有
     * @return
     */
    List<User> findAll();

    /**
     * 根据ID查用户
     * @param userId
     * @return
     */
    User findById(Integer userId);
}

1.2.4 User持久层映射文件

  • collection:于建立一对多中集合属性的对应关系

    • property:包含的属性名
    • ofType:指定集合元素的数据类型。(<font color="red">泛型</font>)
    • select:用于指定查询账户的唯一标识(账户的dao全限定类名加上方法名称)。调用对方的查询方法
    • column:指定使用哪个字段的值作为条件查询
<?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.ruki.dao.IUserDao">
    <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>

        <collection property="accounts" ofType="account" select="com.ruki.dao.IAccountDao.findAccountByUid" column="id"></collection>
    </resultMap>


    <!-- 查询所有用户 -->
    <!--<select id="findAll" resultType="com.ruki.domain.User">-->
    <select id="findAll" resultMap="userAccountMap">
        select * from user
    </select>

    <!-- 根据ID查用户-->
    <select id="findById" parameterType="int" resultType="com.ruki.domain.User">
        select * from user where id=#{id};
    </select>
</mapper>

1.2.5 开启MyBatis的延迟加载策略

  • SqlMapConfig.xml

    • lazyLoadingEnabled:懒加载,默认为true
    • aggressiveLazyLoading:立即加载,需要关掉
    <!-- 配置参数 -->
    <settings>
        <!-- 开启MyBatis支持延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 关闭MyBatis立即加载-->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

1.2.6 测试

    /**
     * 测试查询所有账户
     */
    @Test
    public void testFindAll() throws IOException {
        List<Account> accounts = accountDao.findAll();
        for(Account account : accounts){
            System.out.println("---每一个account的信息");
            System.out.println(account);
            System.out.println(account.getUser());
        }
    }
//----------------------------------------------------------------------------
    /**
     * 测试查询所有账户
     */
    @Test
    public void testFindAll() throws IOException {
        List<User> users = userDao.findAll();
        for(User user : users){
            System.out.println(user);
            System.out.println(user.getAccounts());
        }
    }

二、MyBatis缓存

2.1 一级缓存

  • 一级缓存是SqlSession级别的缓存,只要SqlSession没有flushclose,它就存在。
  • 因为一级缓存的存在,导致第二次查询id相同的记录时,并没有发出sql语句从数据库中查询数据,而是从一级缓存中查询。

    • 此时查询出来的System.out.println(user1 == user2);结果是<font color="red">true</font>
  • 当调用SqlSession的<font color="red">修改,添加,删除,commit(),close()</font>等方法时,就会<font color="red">清空</font>一级缓存,避免脏读。

2.2 二级缓存

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

    1. sqlSession1去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。
    2. 如果SqlSession3去执行相同 mapper映射下sql,执行commit提交,将会清空该 mapper映射下的二级缓存区域的数据。
    3. sqlSession2去查询与sqlSession1相同的用户信息,首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据,并包装成<font color="red">新的对象</font>返回

      • 此时System.out.println(user1 == user2);结果是<font color="red">false</font>

2.2.1 二级缓存的开启与关闭

2.2.1.1 在SqlMapConfig.xml中开启二级缓存

  • 因为cacheEnabled的取值默认就为true,所以这一步可以省略不配置。为true代表开启二级缓存;为false代表不开启二级缓存。
    <settings>
        <!-- 开启二级缓存支持-->
        <setting name="cacheEnabled" value="true"/>
    </settings>

2.2.1.2 配置相关的Mapper映射文件

  • <cache>标签表示当前这个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.ruki.dao.IUserDao">
    <!-- 开启user支持二级缓存-->
    <cache />
    
    <!-- 查询所有用户 -->
    <select id="findAll" resultType="User">
        select * from user
    </select>

    <!-- 根据ID查用户-->
    <select id="findById" parameterType="int" resultType="com.ruki.domain.User" useCache="true">
        select * from user where id=#{id};
    </select>

    <!-- 更新用户信息-->
    <update id="updateUser" parameterType="user">
        update user set username=#{username}, address=#{address} where id=#{id}
    </update>
</mapper>

2.2.1.3 配置select语句上的userCache属性

  • useCachetrue表示使用二级缓存
    <!-- 根据ID查用户-->
    <select id="findById" parameterType="int" resultType="com.ruki.domain.User" useCache="true">
        select * from user where id=#{id};
    </select>

2.2.2 二级缓存使用注意事项

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

三、MyBatis注解开发

3.1 常用注解总结

  1. @Insert :实现新增
  2. @Update:实现更新
  3. @Delete:实现删除
  4. @Select:实现查询
  5. @Result:实现结果集封装
  6. @Results:可以与@Result一起使用,封装多个结果集
  7. @ResultMap:实现引用
  8. @Results定义的封装
  9. @One:实现一对一结果集封装
  10. @Many:实现一对多结果集封装
  11. @SelectProvider: 实现动态SQL映射
  12. @CacheNamespace:实现注解二级缓存的使用

3.2 基于注解实现基本的CRUD

3.2.1 实体类

public class User implements Serializable {
    private Integer id;
    private String username;
    private String address;
    private String sex;
    private Date birthday;
    
    //getter setter toString
}

3.2.2 注解形式的DAO持久层接口

package com.ruki.dao;

import com.ruki.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;

public interface UserDao {
    /**
     * 查找所有
     * @return
     */
    @Select("select * from user")
    List<User> findAll();

    /**
     * 添加用户
     */
    @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}, address=#{address}, sex=#{sex}, birthday=#{birthday} where id=#{id}")
    void updateUser(User user);

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

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

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

    /**
     * 查询全部记录数
     * @return
     */
    @Select("select count(*) from user")
    int findTotal();
}

3.2.3 SqlMapConfig.xml

  • 主要是为了添加别名
<?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>
    <!-- 引入外部文件 -->
    <properties resource="jdbcConfig.properties"></properties>
    <!-- 设置别名 -->
    <typeAliases>
        <package name="com.ruki.domain"/>
    </typeAliases>
    <!-- 配置环境 -->
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    
    <mappers>
        <!-- 引入带有注解的dao接口的所在位置-->
        <package name="com.ruki.dao"/>
    </mappers>
</configuration>

3.2.4 测试

public class MyBatisAnnoTest {
    private InputStream in;
    private SqlSessionFactoryBuilder builder;
    private SqlSessionFactory sessionFactory;
    private SqlSession sqlSession;
    private UserDao userDao;
    @Before
    public void init() throws IOException {
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        builder = new SqlSessionFactoryBuilder();
        sessionFactory = builder.build(in);
        sqlSession = sessionFactory.openSession(true);
        userDao = sqlSession.getMapper(UserDao.class);
    }
    @After
    public void destroy() throws IOException {
        sqlSession.close();
        in.close();
    }


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

    @Test
    public void testSaveUser(){
        User user = new User();
        user.setUsername("Annotation user");
        user.setAddress("上海");
        user.setSex("女");
        user.setBirthday(new Date());

        userDao.saveUser(user);
    }

    @Test
    public void testUpdateUser(){
        User user = new User();
        user.setId(53);
        user.setUsername("Annotation user update");
        user.setAddress("上海");
        user.setSex("女");
        user.setBirthday(new Date());

        userDao.updateUser(user);
    }

    @Test
    public void testDeleteUser(){
        userDao.deleteUser(52);
    }

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

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

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

3.3 基于注解的复杂关系映射

  1. @Results注解

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

      • @Results({@Result(),@Result()})
      • @Results(@Result())
  2. @Result注解

    1. 代替了 <id>标签和<result>标签
    2. @Result 中属性介绍:

      1. id 是否是主键字段
      2. column 数据库的列名
      3. property需要装配的属性名
      4. one 需要使用的@One注解
      5. many 需要使用的@Many注解
  3. @One注解(一对一)

    1. 代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
    2. @One注解属性介绍:

      1. select 指定用来多表查询的sqlmapper
      2. fetchType会覆盖全局的配置参数lazyLoadingEnabled
      3. 使用格式:

        • @Result(column=" ",property="",one=@One(select=""))
  4. @Many注解(多对一)

    1. 代替了<Collection>标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。
    2. 注意:聚集元素用来处理“一对多”的关系。需要指定映射的Java实体类的属性,属性的javaType(一般为ArrayList)但是注解中可以不定义;
    3. 使用格式:

      • @Result(property="",column="",many=@Many(select=""))

3.3.1 实体类

  • User
public class User implements Serializable {
    private Integer userId;
    private String userName;
    private String userAddress;
    private String userSex;
    private Date userBirthday;

    //一对多关系映射,一个用户对应多个张华
    private List<Account> accounts;
    
    //getter setter toString
}
  • Account
public class Account implements Serializable {
    private Integer id;
    private Integer uid;
    private Double money;

    //多对一(mybatis中称之为一对一)的映射,一个账户只能属于一个用户
    private User user;
 
    //getter setter toString
}

3.3.2 基于注解持久层DAO接口

  • UserDao
@CacheNamespace(blocking = true)
public interface UserDao {
    /**
     * 查找所有
     * @return
     */
    @Select("select * from user")
    @Results(id = "userMap",value = {
            @Result(id = true, property = "userId", column = "id"),
            @Result(property = "userName", column = "username"),
            @Result(property = "userAddress", column = "address"),
            @Result(property = "userSex", column = "sex"),
            @Result(property = "userBirthday", column = "birthday"),
            @Result(property = "accounts", column = "id",
                    many = @Many(select = "com.ruki.dao.AccountDao.findAccountByUid",
                            fetchType = FetchType.LAZY))
    })
    List<User> findAll();

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

    /**
     * 根据用户名模糊查询
     * @param name
     * @return
     */
    @Select("select * from user where username like #{name}")
    @ResultMap(value = "userMap")
    List<User> findByName(String name);

}
  • AccountDao
public interface AccountDao {
    /**
        * 查找所有
        */
    @Select("select * from account")
    @Results(id="accountMap", value = {
            @Result(id=true, property = "id", column = "id"),
            @Result(property = "uid", column = "uid"),
            @Result(property = "money", column = "money"),
            @Result(property = "user",column = "uid",
                    one=@One(select = "com.ruki.dao.UserDao.findById", fetchType = FetchType.EAGER))
    })
    List<Account> findAll();

    /**
     * 根据用户ID查询账户信息
     * @param userId
     * @return
     */
    @Select("select * from account where uid=#{userId}")
    List<Account> findAccountByUid(Integer userId);
}

3.2.3 测试

    @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());
        }
    }

3.4 基于注解的二级缓存

3.4.1 SqlMapConfig.xml中开启

    <!-- 配置二级缓存 -->
    <settings>
        <!-- 开启二级缓存的支持 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>

3.4.2 持久层接口中使用注解配置二级缓存

@CacheNamespace(blocking = true)
public interface UserDao {}
Last modification:September 17th, 2019 at 08:45 am