Java学习笔记分享之MyBatis篇(下)

    科技2022-07-11  76

    1.1 注解模式开发

    ​ 初期的 MyBatis 是一个 XML 驱动的框架。配置信息是基于 XML 的,映射语句也是定义在 XML 中的。而在 MyBatis 3 中,提供了其它的配置方式。MyBatis 3 构建在全面且强大的基于 Java 语言的配置 API 之上。它是 XML 和注解配置的基础。注解提供了一种简单且低成本的方式来实现简单的映射语句。

    ​ 提示: Java 注解的表达能力和灵活性十分有限。尽管MyBatis团队花了很多时间在调查、设计和试验上,但最强大的 MyBatis 映射并不能用注解来构建。

    ​ 经验对于单表的操作使用注解更加便利,但是对于复杂的多表,使用注解模式就显得难以维护建议使用XML。

    常用注解

    注解用途@Insert新增@Update更新@Delete删除@Select查询@Result封装结果集@Results与@Result配合,封装多个结果集@One实现一对一结果集封装@Many实现一对多结果集封装

    以下的查询案例和要求均和XML模式一致,只是将xml操作替换为注解操作

    对于注解的查询操作是不需要Mapper.xml配置文件的,因为sql的配置和结果映射都通过注解实现了

    SqlMapConfig.xml更新

    <mappers> <!-- 注解方式Mapper的信息已经在接口中使用功能注解实现,所以扫描接口解析注解获取 --> <package name="com.zyj.mapper"/> </mappers>
    1.1.1简单操作

    对于基础的单表新增、更新、查询、删除操作如下,sql语句和xml一致,这里不多介绍。

    public interface UserMapper { // 新增 @Insert(value = "insert into user(id, username, `password`, birthday) values (#{id}, #{username}, #{password}, #{birthday})") void addUser(User user); // 更新 @Update(value = "update user set username = #{username}, `password` = #{password}, birthday = #{birthday} where id = #{id}") void updateUser(User user); // 查询 @Select(value = "select * from user") List<User> selectAll(); // 删除 @Delete(value = "delete from user where id = #{id}") void deleteUser(Integer id); }
    1.1.2 一对一查询

    执行过程:

    1)执行@Select标签的sql语句。

    2)执行@Results标签的语句封装@Select的查询结果。

    3)封装结果的时候有一个属性是user ,内部的one属性关联到了另外一个查询结果。所以就会去UserMapper中执行selectById 根据 column=uid 将 order 的uid作为参数去查询结果。

    // 订单一对一查询 public interface OrdersMapper { // select * from orders o left join user u on o.uid = u.id @Results({ @Result(property = "id", column = "id"), @Result(property = "ordertime", column = "ordertime"), @Result(property = "total", column = "total"), @Result( javaType = User.class, property = "user", column = "uid", one = @One(select = "com.zyj.mapper.UserMapper.selectById")) }) @Select(value = "select * from orders") List<Order> findAllOrderAndUser(); } // 用户查询 public interface UserMapper { @Select(value = "select * from user where id = #{id}") User selectById(Integer id); }

    注解和标签对应关系总结:

    注解标签@Selectselect@ResultsresultMap@Resultid和<result@Oneassociation
    1.1.3 一对多查询

    执行过程:

    1)执行@Select标签的sql语句。

    2)执行@Results标签的语句封装@Select的查询结果。

    3)封装结果的时候有一个属性是orderList ,内部的Many属性关联到了另外一个查询结果。所以就会去OrderMapper中执行selectByUId 根据 column=id 将 User 的id作为参数去查询结果。

    // 用户查询一对多 public interface UserMapper { // select * from user u left join orders o on u.id = o.uid; @Results({ @Result(property = "id", column = "id"), @Result(property = "username", column = "username"), @Result(property = "password", column = "password"), @Result(property = "birthday", column = "birthday"), @Result( property = "orderList", column = "id", javaType = List.class, many = @Many(select = "com.zyj.mapper.OrdersMapper.selectByUId") ) }) @Select(value = "select * from user") List<User> findAllUserAndOrder(); } // 根据用户id查询用户订单 public interface OrdersMapper { // 根据用户id返回查询结果 @Select(value = "select * from orders where uid = #{uid}") List<Order> selectByUId(Integer uid); }

    注解和标签对应关系总结:

    注解标签@Select@Results@Result和@Many
    1.1.4多对多查询

    执行过程:

    1)执行@Select标签的sql语句。

    2)执行@Results标签的语句封装@Select的查询结果。

    3)封装结果的时候有一个属性是roleList ,内部的Many属性关联到了另外一个查询结果。所以就会去RoleMapper中执行selectByUserId 根据 column=id 将 User 的id作为参数去查询结果。

    public interface UserMapper { // select * from user u left join sys_user_role ur on u.id = ur.userid left join sys_role r on ur.roleid = r.id; @Results({ @Result(property = "id", column = "id"), @Result(property = "username", column = "username"), @Result(property = "password", column = "password"), @Result(property = "birthday", column = "birthday"), @Result( property = "roleList", column = "id", javaType = List.class, many = @Many(select = "com.zyj.mapper.RoleMapper.selectByUserId") ) }) @Select(value = "select * from user") List<User> findAllUserAndRole(); } // 根据用户id查询角色 public interface RoleMapper { @Select(value = "select r.* from sys_role r inner join sys_user_role ur on r.id = ur.roleid where userid = #{uid}") List<Role> selectByUserId(Integer uid); }

    注解和标签对应关系总结:

    注解标签@Selectselect@ResultsresultMap@Resultid和result@Manycollection
    1.1.5 注解的动态SQL

    ​ MyBatis提供的动态Sql标签在注解模式中也是一样适用,但是写法和刚才的一对一、一对多、多对多 的查询有所差异。作用和效果是一样的。所以这里只给出一个案例即可。

    @Update({"<script>", "update Author", " <set>", " <if test='username != null'>username=#{username},</if>", " <if test='password != null'>password=#{password},</if>", " <if test='email != null'>email=#{email},</if>", " <if test='bio != null'>bio=#{bio}</if>", " </set>", "where id=#{id}", "</script>"}) void updateAuthorValues(Author author);

    如果想在注解的映射器接口中使用动态SQL,那么可以使用script元素。

    1.2 缓存机制

    Mybatis 使用到了两种缓存:本地缓存(local cache)和二级缓存(second level cache)。 两个缓存的关系如下图:

    ​ 每当一个新 session 被创建,MyBatis 就会创建一个与之相关联的本地缓存。任何在 session 执行过的查询结果都会被保存在本地缓存中,所以,当再次执行参数相同的相同查询时,就不需要实际查询数据库了。本地缓存将会在做出修改、事务提交或回滚,以及关闭 session 时清空。

    1.2.1 验证一级缓存

    1)在同一个sqlSession中,对User用户表根据id进行2次查询,观察控制台的sql打印情况。

    @Test public void test1(){ // 获取一个sqlSession SqlSession sqlSession = sessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 使用sqlSession执行第一次查询 User u1 = userMapper.selectUserByUserId(1); System.out.println(u1); // 使用sqlSession执行第二次查询 User u2 = userMapper.selectUserByUserId(1); System.out.println(u2); sqlSession.close(); }

    2)在同一个sqlSession中,对User用户表根据id进行2次查询。但是,中间对查询到的用户执行Update更新操作,并提交事务,观察控制台的sql打印情况。

    @Test public void test2(){ // 获取一个sqlSession SqlSession sqlSession = sessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 使用sqlSession执行第一次查询 User u1 = userMapper.selectUserByUserId( 1 ); System.out.println(u1); // 第二次查询之前执行更新操作并提交事务 u1.setSex("女"); userMapper.updateUserByUserId(u1); sqlSession.commit(); // 使用sqlSession执行第二次查询 User u2 = userMapper.selectUserByUserId(1); System.out.println(u2); sqlSession.close(); }

    总结:

    ​ 1)第一次查询用户的时候,会在一级缓存中去查询用户信息,如果查询不到就去数据库中查询。并将查询到的结果更新到以及缓存中。

    ​ 2)如果同一个SqlSession中执行了commit事务提交操作(增加、更新、删除),那么就会清空SqlSession中的缓存信息,避免下次查询的时候出现脏数据。

    ​ 3)第二次查询的时候同样去一级缓存查询,如果有值就获取返回,如果没有就查询数据库并添加到一级缓存中。

    1.2.2 验证二级缓存

    ​ 二级缓存和一级缓存原理类似,只不过缓存的级别更高而已。一级缓存针对sqlSession,但是二级缓存就针对Mapper文件。所以二级缓存可以被多个sqlSession共享。

    ​ 二级缓存和一级缓存不一样,一级缓存默认都是开启的,二级缓存默认是关闭的。所以需要配置开启。二级缓存的开启需要配置2个地方。

    总开关开启二级缓存SqlMapConfig.xml配置文件开启。 <!-- 二级缓存总开关 --> <settings> <setting name="cacheEnabled" value="true"/> </settings> 由于二级缓存是Mapper级别的缓存,所以可以针对单个Mapper配置是否需要开启。 <!--对应的Mapper.xml开启二级缓存。如:UserMapper.xml、OrderMapper.xml--> <cache></cache>

    基于上诉流程测试二级缓存

    @Test public` `void` `testTwoCache(){ // 获取sqlSession SqlSession sqlSession1 = sessionFactory.openSession(); SqlSession sqlSession2 = sessionFactory.openSession(); SqlSession sqlSession3 = sessionFactory.openSession(); String statement = "com.zyj.UserMapper.selectById" ; UserMapper userMapper1 = sqlSession1.getMapper(UserMapper. class ); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper. class ); UserMapper userMapper3 = sqlSession2.getMapper(UserMapper. class ); // 第一次执行查询,结果会放入二级缓存中 User u1 = userMapper1.selectById( 1 ); System.out.println(u1); sqlSession1.close(); // 关闭第一个session // 执行更新操作,并提交 u1.setUsername("知春秋"); userMapper3.selectById(u1); sqlSession3.commit(); // 第二次查询,由于更新操作导致二级缓存被更新,所以会重新查询数据库 User u2 = userMapper2.selectById( 1 ); System.out.println(u2); sqlSession2.close(); }

    1.3 MyBatis学习思维导图(提供参考)

    1.4 MyBatis源码剖析思维导图(提供参考)

    知春秋 认证博客专家 博客专家 Java高级研发 不忘初心,方得始终。初心易得,始终难守。
    Processed: 0.017, SQL: 8