简述Mybatis框架(四)

本人小白一枚,欢迎大家一起讨论学习,如有错误,还望大家指教。

Mybatis延迟加载策略

什么是延迟加载?
就是在我们进行级联操作时,需要的数据进行加载,不需要的数据就不加载,可以理解为按需加载,延迟加载又称懒加载。
延迟加载的优点: 先从单表查询,需要时再从关联表中去关联查询,大大提高了数据库性能,因为查询单表比关联查询多张表速度要快。
延迟加载的缺点: 因为只有当需要用到数据时才进行数据库查询,在存在大批量数据查询时,查询的工作要消耗很多时间,可能会造成用户等待时间过长,造成用户体验下降。
使用延迟加载的时机: 数据表有四种对应关系,有一对多、多对一、一对一、多对多。当一对多或者多对多时,我们通常采用延迟加载。当多对一或者一对一时我们通常采用立即加载。
演示案例:
现有两张表,account账户表和user用户表,一个用户可以有多个账户,但一个账户只能有一个用户,也就是用户和账户是一对多的关系,而账户和用户是一对一的关系。当我们查询账户信息时关联查询用户信息,如果只查账户信息即可满足要求就不用再去查询用户信息了,当我们查询用户信息再想查询用户信息时,把对用户信息的按需查询这就是延迟加载了。

  • account账户表以及user用户表的详情如下图所示,并在工程中创建对应的实体类,这里我特意将user实体类中的字段与表中的字段没有对应。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
public class Account {
    private int id;
    private int uid;
    private Double money;
    // 用来存储对应的用户信息
    private User user;
    // get/set等方法自行补齐............
}
public class User {
    private int userId;
    private String username;
    private LocalDate userBirthday;
    private String userSex;
    private String userAddress;
    // get/set等方法自行补齐............
}
  • 在账户的持久层接口中添加findAll()的方法,同时在用户的持久层接口中添加findById()方法
public interface AccountMapper {
    /**
     * 查询所有账户并查询所属用户信息
     * @return
     */
    List<Account> findAll();
}
public interface UserMapper {
    /**
     * 根据用户id查询用户信息
     * @param id 用户主键
     * @return
     */
    public User findById(int id);
}
  • 在账户的映射配置文件和用户的映射配置文件添加如下内容,这里要注意select属性,该属性的内容是我们要调用的select映射的id。column属性的内容是我们传递给select映射的参数。
<mapper namespace="mapper.AccountMapper">
    <resultMap id="accountUserMap" type="account">
        <id property="id" column="id"/>
        <result property="uid" column="uid"/>
        <result property="money" column="money"/>
        <association property="user" javaType="user" select="mapper.UserMapper.findById" column="uid"/>
    </resultMap>
    <select id="findAll" resultMap="accountUserMap">
        SELECT * FROM account
    </select>
</mapper>    
<mapper namespace="mapper.UserMapper">
    <cache readOnly="true"></cache>
    <resultMap id="userMap" type="user">
        <id column="id" property="userId"/>
        <result column="username" property="username"/>
        <result column="address" property="userAddress"/>
        <result column="sex" property="userSex"/>
        <result column="birthday" property="userBirthday"/>
    </resultMap>
    <select id="findById" resultMap="userMap" parameterType="int">
        SELECT * FROM user WHERE id = #{userId}
    </select>
</mapper>
  • 在主配置文件开启延迟加载,这两个参数的设置我们在下面会具体叙述
    <!--开启懒加载-->
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
  • 添加测试方法并查看测试结果,当我们只调用了账户信息,没有查看用户信息时,我们可以看到,用户信息没有加载。
public class Demo {
    private InputStream in;
    private SqlSessionFactory factory;
    private SqlSession session;

    @Before
    public void init() throws Exception{
        in = Resources.getResourceAsStream("MapConfig.xml");
        factory = new SqlSessionFactoryBuilder().build(in);
        session = factory.openSession();
    }

    @Test
    public void test1() {
        AccountMapper accountMapper = session.getMapper(AccountMapper.class);
        List<Account> accounts = accountMapper.findAll();
        accounts.forEach(item -> System.out.println(item.getId()));
    }

    @After
    public void destory() throws Exception {
        session.commit();
        session.close();
        in.close();
    }
 }

在这里插入图片描述

  • 添加测试方法并查看测试结果,当我们查看用户的信息时,他才会对用户的信息进行加载。
    @Test
    public void test1() {
        AccountMapper accountMapper = session.getMapper(AccountMapper.class);
        List<Account> accounts = accountMapper.findAll();

        accounts.forEach(item -> System.out.println(item.getUser()));
    }

在这里插入图片描述
下面我们具体叙述下lazyLoadingEnabledaggressiveLazyLoading属性。
lazyLoadingEnabled属性的含义是开启全局懒加载的开关,也就是真正开启懒加载的属性只有lazyLoadingEnabled这个属性,默认值为false。aggressiveLazyLoading可以控制具有懒加载特性对象的属性的加载情况。true表示如果对具有懒加载特性的对象任意的调用都会导致懒加载,而false表示只有调用具有懒加载特性的属性的get方法时,才会进行懒加载,这个属性在3.4.1(包含)之前默认都为true,之后默认为false。
当我们不想将全部的级联操作都设计成懒加载的时候怎么办呢?
Mybaits在associationcollection标签提供fetchType属性,可以使用该属性覆盖全局的懒加载状态,eager表示这个级联不使用懒加载即使用立即加载,lazy表示使用懒加载。注意:discriminator没有fetchType属性,因此它只能根据全局的懒加载的配置。同时也要注意,我们可以直接使用fetchType这个属性来控制懒加载,不用配置全局的也是可以的。

Mybatis缓存

像大多数的持久层框架一样,Mybatis框架也提供了缓存策略,通过缓存策略来减少对数据的查询次数,从未提高性能。Mybatis中的缓存分为一级缓存和二级缓存。一级缓存不需要任何配置,Mybatis默认开启一级缓存。
在这里插入图片描述

  • 测试一级缓存并查看结果,可以看到只执行了一次查询,第二次是从一级缓存中读取出来的没有查询数据库,并且这两个对象是同一个地址
@Test
  public void test3() {
      UserMapper userMapper = session.getMapper(UserMapper.class);
      User user1 = userMapper.findById(46);
      System.out.println(user1);
      User user2 = userMapper.findById(46);
      System.out.println(user2);
      System.out.println(user1 == user2);
  }

在这里插入图片描述

  • 一级缓存是SqlSession范围的缓存,当调用SqlSession的修改、添加、删除、commit()方法、close()方法等,就会清空一级缓存。其实一级缓存就是一个Map,例如我们第一次查询id为1的用户信息时,先去找缓存中是否有id为1的用户信息,如果没有,从数据查询用户。将得到的用户信息存到一级缓存中,如果SqlSession去执行commit操作(执行插入、更新、删除等)就会清空一级缓存,这样做的目的是存储最新的信息,避免脏读。
    在这里插入图片描述
  • 清除一级缓存测试并查看结果,就会发现当我们清除缓存之后,它在查询缓存中没有时就会去数据库中去查询
@Test
public void test3() {
    UserMapper userMapper = session.getMapper(UserMapper.class);
    User user1 = userMapper.findById(46);
    System.out.println(user1);
    // 清除缓存
    session.clearCache();
    User user2 = userMapper.findById(46);
    System.out.println(user2);
    System.out.println(user1 == user2);
}

在这里插入图片描述

  • 二级缓存是Mapper映射级别的缓存,由同一个SqlSessionFactory对象创建的多个SqlSession去操作同一个Mapper映射的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
    在这里插入图片描述
  • 第一步:在主配置文件开启耳机缓存,因为cacheEnabled默认就是true,所以这一步也可以省略不写,true表示开启,false表示关闭。
  <settings>
        <!--开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
  • 第二步:配置相关的Mapper映射文件,<cache>标签当前这个mapper映射将使用二级缓存,区分的标准就看mapper的namespace值。
<?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="mapper.UserMapper">
    <!--开启二级缓存的支持-->
    <cache></cache>
</mapper>
  • 第三步:配置statement上面的useCache属性,如果每次查询都需要最新的数据,要设置成useCache=“false”,禁用二级缓存。
<resultMap id="userMap" type="user">
        <id column="id" property="userId"/>
        <result column="username" property="username"/>
        <result column="address" property="userAddress"/>
        <result column="sex" property="userSex"/>
        <result column="birthday" property="userBirthday"/>
    </resultMap>
    <select id="findById" resultMap="userMap" parameterType="int" useCache="true">
        SELECT * FROM user WHERE id = #{userId}
    </select>
</resultMap>   
  • 第四步:要让实体类实现Serializable序列化接口,具体原因在下面叙述。
public class User implements Serializable {
    private int userId;
    private String username;
    private LocalDate userBirthday;
    private String userSex;
    private String userAddress;
    // get/set等方法自行补齐............
}
  • 第五步:测试代码并查看结果。
   @Test
    public void test4() {
        SqlSession sqlSession1 = factory.openSession();
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        User user1 = userMapper1.findById(46);
        sqlSession1.close();
        SqlSession sqlSession2 = factory.openSession();
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = userMapper2.findById(46);
        System.out.println(user1 == user2);
    }

在这里插入图片描述总结:可能大家对于上面的案例有两个问题,一个就是为什么实体类要实现Serializable接口,还有就是为什么打印的结果是false。原因就是Mybatis使用SerializedCache序列化缓存来实现可读写缓存类,并通过反序列化从缓存获取数据时,得到的当然是一个新的实例。如果配置成只读缓存,Mybatis就会使用Map来存储缓存值,这种情况下,从缓存中获取的对象就是同一个实例。如何配置只读缓存呢?在<cache>标签有个readOnly属性,将readOnly设置为true表示将缓存设置成只读缓存。只读缓存将对所有调用者返回同一个实例,因为对象没有进行序列化,所以速度最快,可写的缓存将通过反序列化返回一个缓存对象,速度慢,并且得到的对象是一个新的对象,线程安全。Mybatis的二级缓存默认就是可写缓存,即readOnly=false。那为什么使用序列化缓存呢?因为当我们将对象序列化成二进制在缓存时,节省了内存,并且反序列化得到缓存时,线程是安全的。
在这里插入图片描述

使用Mybatis注解实现基本的CRUD

  • 这里同样使用上面的实体类User,注意这里实体类的属性名和表中的列名不一致。
public class User implements Serializable {
    private int userId;
    private String username;
    private Date userBirthday;
    private String userSex;
    private String userAddress;
    // get/set等方法自行补齐............
}
  • 使用注解方式开发持久层接口
public interface UserMapper {
    /**
     * 查询所有用户
     */
    @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")
    })
    @Select("SELECT * FROM user")
    List<User> findAll();

    /**
     * 保存用户信息,同时将插入的主键保存到实体中,注意这里的主键是自增长的
     */
    @Insert("INSERT INTO user(username, address, sex, birthday) VALUES(#{username}, #{userAddress}, #{userSex}, #{userBirthday})")
    @SelectKey(keyColumn = "id", keyProperty = "userId", resultType = int.class, before = false, statement = {"SELECT last_insert_id()"})
    int save(User user);

    /**
     * 更新用户信息
     */
    @Update("UPDATE user set username=#{username}, address=#{userAddress}, sex=#{userSex}, birthday=#{userBirthday} WHERE id=#{userId}")
    int update(User user);

    /**
     * 多条件查询,Mybatis提供了注解方式的动态SQL,在<script>标签中声明动态SQL
     */
    @Select({"<script>",
            "SELECT * FROM user",
            "<where>",
            "<if test=\"username != null and username != ''\">",
            "AND username LIKE \"%\"#{username}\"%\"",
            "</if>",
            "<if test=\"userSex != null and userSex != ''\">",
            "AND sex LIKE \"%\"#{userSex}\"%\"",
            "</if>",
            "</where>",
            "</script>"})
    @ResultMap("userMap")
    List<User> find(User user);

    /**
     * 删除用户信息
     */
    @Delete("DELETE FROM user WHERE id=#{id}")
    int delete(int id);

    /**
     * 查询记录的总条数
     */
    @Select("SELECT COUNT(*) FROM user")
    int findTotal();

    /**
     * 根据id查询用户信息
     */
    @ResultMap("userMap")
    @Select("SELECT * FROM user WHERE id=#{id}")
    User findById(int id);
}
  • 编写主配置文件
<?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="dp.properties"/>
    <!--注册别名-->
    <typeAliases>
        <package name="entity"/>
    </typeAliases>
    <!--配置数据源-->
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"/>
            <dataSource type="datasource.DruidDataSourceFactory">
                <property name="driverClass" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--注册映射文件-->
    <mappers>
        <package name="mapper"/>
    </mappers>
</configuration>

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

使用注解实现一对一复杂关系映射及延迟加载:加载账户信息时并加载账户的用户的信息,使用注解方式

  • 在Account实体类添加User类型的属性
    在这里插入图片描述
  • 添加账户的持久层接口(注解方式)
public interface AccountMapper {
    @Results(id = "accountMap", value = {
            @Result(id=true, column = "id", property = "id"),
            @Result(column = "uid", property = "uid"),
            @Result(column = "money", property = "money"),
            @Result(column = "uid", property = "user", one=@One(select = "mapper.UserMapper.findById", fetchType = FetchType.LAZY))
    })
    @Select("SELECT * FROM account")
    List<Account> findAll();

    @Select("SELECT * FROM account WHERE uid=#{uid}")
    List<Account> findByUid(int uid);
}

使用注解实现一对多复杂关系映射:查询用户信息时,也要查询出关联的账户列表,使用注解方式实现

  • 在User实体类中添加Account集合的属性
    在这里插入图片描述
  • 在用户持久层添加对应的注解
    在这里插入图片描述
  • 在账户的持久层添加对应的注解
    在这里插入图片描述
    mybatis基于注解的二级缓存
  • 在主配置文件开启二级缓存
<!--开启二级缓存-->
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
  • 在持久层接口是使用注解配置二级缓存
    在这里插入图片描述
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值