文档说明

此文档为拉钩Java高薪训练营2期课程学习过程,记录的文档。顺便说一句拉钩的课程,整个课程,体系非常全,价格上也是所有培训课程中最便宜的。如果希望构建一个整体的技术视野,非常推荐。

整体架构

image.png

SQL 执行流程

image.png

配置文件

SqlMapConfig.xml

image.png

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  3. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  4. <configuration>
  5. <!--加载外部的properties文件-->
  6. <properties resource="jdbc.properties"></properties>
  7. <!--开启二级缓存 -->
  8. <!-- <settings>
  9. <setting name="cacheEnabled" value="true"/>
  10. </settings> -->
  11. <!--给实体类的全限定类名给别名-->
  12. <typeAliases>
  13. <!--批量起别名:该包下所有的类的本身的类名:别名还不区分大小写-->
  14. <package name="com.beau.pojo"/>
  15. </typeAliases>
  16. <plugins>
  17. <!-- 翻页插件配置 -->
  18. <plugin interceptor="com.github.pagehelper.PageHelper">
  19. <property name="dialect" value="mysql"/>
  20. </plugin>
  21. <!-- 通用 Mapper 配置 -->
  22. <plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
  23. <!--指定当前通用mapper接口使用的是哪一个-->
  24. <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
  25. </plugin>
  26. </plugins>
  27. <environments default="development">
  28. <environment id="development">
  29. <!--当前事务交由JDBC进行管理-->
  30. <transactionManager type="JDBC"></transactionManager>
  31. <dataSource type="POOLED">
  32. <property name="driver" value="${jdbc.driver}"/>
  33. <property name="url" value="${jdbc.url}"/>
  34. <property name="username" value="${jdbc.username}"/>
  35. <property name="password" value="${jdbc.password}"/>
  36. </dataSource>
  37. </environment>
  38. </environments>
  39. <mappers>
  40. <package name="com.beau.mapper"/>
  41. </mappers>
  42. </configuration>

mapper.xml

Mapper 动态代理开发规范

  1. Mapper.xml 文件中的 namespace 与 mapper 接口的类路径相同。
  2. Mapper 接口方法名和 Mapper.xml 中定义的每个statement的id相同
  3. Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同
  4. Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同
  5. Mapper.xml 文件存放位置和接口存放位置包名相同,可以在 resource 下面创建同名包存放 mapper 文件。(此规则非强制,只要能扫描并到就可以了)

基本增删改查

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="com.lagou.dao.IUserDao">
  6. <!--namespace : 名称空间:与id组成sql的唯一标识
  7. resultType: 表明返回值类型-->
  8. <!--抽取sql片段-->
  9. <sql id="selectUser">
  10. select * from user
  11. </sql>
  12. <!--查询用户-->
  13. <select id="findAll" resultType="uSeR">
  14. <include refid="selectUser"></include>
  15. </select>
  16. <!--添加用户-->
  17. <!--parameterType:参数类型-->
  18. <insert id="saveUser" parameterType="user" >
  19. insert into user values(#{id},#{username})
  20. </insert>
  21. <!--修改-->
  22. <update id="updateUser" parameterType="user">
  23. update user set username = #{username} where id = #{id}
  24. </update>
  25. <!--删除-->
  26. <delete id="deleteUser" parameterType="int">
  27. delete from user where id = #{abc}
  28. </delete>
  29. </mapper>

动态SQL

  1. <!--多条件组合查询:演示if-->
  2. <select id="findByCondition" parameterType="user" resultType="user">
  3. <include refid="selectUser"></include>
  4. <where>
  5. <if test="id !=null">
  6. and id = #{id}
  7. </if>
  8. <if test="username !=null">
  9. and username = #{username}
  10. </if>
  11. </where>
  12. </select>
  13. <!--多值查询:演示foreach-->
  14. <select id="findByIds" parameterType="list" resultType="user">
  15. <include refid="selectUser"></include>
  16. <where>
  17. <foreach collection="array" open="id in (" close=")" item="id" separator=",">
  18. #{id}
  19. </foreach>
  20. </where>
  21. </select>

关联查询

一对一查询

如,在用户和订单中,订单对用户,就是一对一。

  1. 在 POJO 中,用 User 对象,表示关联关系

    1. private User user;
  2. IOrderMappper.xml ```xml

  1. <a name="wfiTR"></a>
  2. #### 一对多查询
  3. 如,在用户和订单的关系中,用户对订单,其实就是一对多的关系。
  4. 1. 在 POJO 中用集合接收关联对象
  5. ```java
  6. private List<Order> orderList
  1. IUserMapper.xml ```xml

  1. <a name="GB2e0"></a>
  2. #### 多对多查询
  3. 多对多,其实就是多个一对多。需要在两个关联的 POJO 类中,都用集合接收关联的结果。
  4. <a name="4nnkC"></a>
  5. # 注解开发
  6. 1. `@Insert`
  7. 1. `@Update`
  8. 1. `@Delete`
  9. 1. `@Select`
  10. 1. `@Result` 实现结果集封装
  11. 1. `@Results` 可以与 `@Result` 一起使用,封装多个结果集
  12. 1. `@One` 实现一对一结果集封装
  13. 1. `@Many` 实现一对多结果集封装
  14. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/794563/1583712551681-b0668532-6d2e-48eb-8805-ea4cd4f5488b.png#align=left&display=inline&height=219&margin=%5Bobject%20Object%5D&name=image.png&originHeight=437&originWidth=1230&size=144928&status=done&style=none&width=615)
  15. <a name="kcc8P"></a>
  16. ## 一对一查询
  17. 使用注解开发,通过订单查询用户,在 `@one` 注解中,通过 `select` 指定调用了 `UserMapper` 中的 `findById` 方法。注意 ① 的部分, `prpperty` 指定的是在 `OrderPOJO` 中, `User` 所对应的属性,而 `column` 则是指定的通过 `select * from orders` 这条语句查询出来的关联用户的列名。查询出来的每个 `uid` 都会调用 `select * from user where id=#{id}` 将结果集返回。
  18. ```java
  19. public interface OrderMapper {
  20. @Select("select * from orders")
  21. @Results({
  22. @Result(id=true,property = "id",column = "id"),
  23. @Result(property = "ordertime",column = "ordertime"),
  24. @Result(property = "total",column = "total"),
  25. @Result(property = "user",column = "uid", ①
  26. javaType = User.class,one = @One(select ="com.lagou.mapper.UserMapper.findById"))
  27. })
  28. List<Order> findAll();
  29. }
  1. public interface UserMapper {
  2. @Select("select * from user where id=#{id}")
  3. User findById(int id);
  4. }

一对多查询

  1. public interface UserMapper {
  2. @Select("select * from user")
  3. @Results({
  4. @Result(id = true,property = "id",column = "id"),
  5. @Result(property = "username",column = "username"),
  6. @Result(property = "password",column = "password"),
  7. @Result(property = "birthday",column = "birthday"),
  8. @Result(property = "orderList",column = "id",
  9. javaType = List.class,many = @Many(select ="com.lagou.mapper.OrderMapper.findByUid"))
  10. })
  11. List<User> findAllUserAndOrder();
  12. }
  1. public interface OrderMapper {
  2. @Select("select * from orders where uid=#{uid}")
  3. List<Order> findByUid(int uid);
  4. }

高级

缓存

  • 缓存分为一级缓存和二级缓存。
  • 一级缓存是 SqlSession 级别,内部结构是 HashMap,不同的 SqlSession 中的缓存互不干扰。当发生 CUD 时,缓存的数据会失效。一级缓存默认是开启的。
  • 二级缓存是 namespace(mapper) 级别的,当 namespace 中出现 CUD 时,缓存数据会失效。二级缓存在多表的情况下,可能会出现脏读(另一个 Mapper 中更新了当前 Mapper 中关联查询的对象,当前 Mapper 中的缓存并不会失效),因此不推荐使用。
  • 如果要自己实现二级缓存,需要实现 Cache 接口。POJO 类也需要实现序列化接口。官方也有提供 redis 版的二级缓存实现。

    插件

插件机制

MyBatis 插件本质上是拦截器。可以拦截如下方法:

  • 执行器 Executor 中的 updata query commit rollbak 等方法。
  • SQL 语法构建器 StatementHandler 中的 prepare parametrize batch update query 等方法。
  • 参数处理器 ParameterHandler 中的 getParameterObject setParameters 等方法。
  • 结果集处理器 ResultSetHandler 中的 handleResultsSets handleOutputParameters 等方法。

MyBatis 插件在拦截上面方法时,会对其进行增强,生成相应的代理对象。

自定义插件

  1. 实现 Inteceptor 接口
  1. @Intercepts({
  2. @Signature(type= StatementHandler.class, // 指定拦截的方法
  3. method = "prepare",
  4. args = {Connection.class,Integer.class})
  5. })
  6. public class MyPlugin implements Interceptor {
  7. /*
  8. 拦截方法:只要被拦截的目标对象的目标方法被执行时,每次都会执行intercept方法
  9. */
  10. @Override
  11. public Object intercept(Invocation invocation) throws Throwable {
  12. System.out.println("对方法进行了增强....");
  13. return invocation.proceed(); //原方法执行
  14. }
  15. /*
  16. 主要为了把当前的拦截器生成代理存到拦截器链中
  17. */
  18. @Override
  19. public Object plugin(Object target) {
  20. Object wrap = Plugin.wrap(target, this);
  21. return wrap;
  22. }
  23. /*
  24. 获取配置文件的参数
  25. */
  26. @Override
  27. public void setProperties(Properties properties) {
  28. System.out.println("获取到的配置文件的参数是:"+properties);
  29. }
  30. }
  1. 在 sqlMapConfig.xml 加载插件
  1. <plugins>
  2. <plugin interceptor="com.lagou.plugin.MySqlPagingPlugin">
  3. <!-- 参数配置 -->
  4. <property name="name" value="Bob"/>
  5. </plugin>
  6. </plugins>

分页插件

  1. 导入依赖
  1. <dependency>
  2. <groupId>com.github.pagehelper</groupId>
  3. <artifactId>pagehelper</artifactId>
  4. <version>3.7.5</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.github.jsqlparser</groupId>
  8. <artifactId>jsqlparser</artifactId>
  9. <version>0.9.1</version>
  10. </dependency>
  1. 在 sqlMapConfig.xml 中配置加载插件
  1. <plugins>
  2. <plugin interceptor="com.github.pagehelper.PageHelper">
  3. <property name="dialect" value="mysql"/>
  4. </plugin>
  5. </plugins>
  1. 使用
  1. @Test
  2. public void pageHelperTest(){
  3. PageHelper.startPage(1,1);
  4. List<User> users = userMapper.selectUser();
  5. for (User user : users) {
  6. System.out.println(user);
  7. }
  8. PageInfo<User> pageInfo = new PageInfo<>(users);
  9. System.out.println("总条数:"+pageInfo.getTotal());
  10. System.out.println("总页数:"+pageInfo.getPages());
  11. System.out.println("当前页:"+pageInfo.getPageNum());
  12. System.out.println("每页显示的条数:"+pageInfo.getPageSize());
  13. }

通用 Mapper 插件

:::info 如果同时使用分页插件和通用 Mapper 插件,一定要把分页插件配置在通用 Mapper 插件之前。 :::

  1. 导入依赖
  1. <dependency>
  2. <groupId>tk.mybatis</groupId>
  3. <artifactId>mapper</artifactId>
  4. <version>3.1.2</version>
  5. </dependency>
  1. sqlMapConfig.xml 中加载插件
  1. <plugins>
  2. <plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
  3. <!--指定当前通用mapper接口使用的是哪一个-->
  4. <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
  5. </plugin>
  6. </plugins>
  1. 使用
  1. @Test
  2. public void mapperTest() throws IOException {
  3. InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
  4. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
  5. SqlSession sqlSession = sqlSessionFactory.openSession();
  6. UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  7. User user = new User();
  8. user.setId(1);
  9. User user1 = mapper.selectOne(user);
  10. System.out.println(user1);
  11. //2.example方法
  12. Example example = new Example(User.class);
  13. example.createCriteria().andEqualTo("id",1);
  14. List<User> users = mapper.selectByExample(example);
  15. for (User user2 : users) {
  16. System.out.println(user2);
  17. }
  18. }

经典问题

Mybatis延迟加载原理

  • MyBatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一查询,collection 指的是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false
  • MyBatis 延迟加载原理:使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器 invoke() 方法发现 a.getB() 是null值,那么就会单独发送事先保存好的查询关联B对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName() 方法的调用。

    Mybatis都有哪些Executor执行器

  • SimpleExecutor:每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象。

  • ReuseExecutor:执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map 内,供下一次使用。简言之,就是重复使用Statement 对象。
  • BatchExecutor:执行 update(没有 select,JDBC批处理不支持 select),将所有 sql 都添加到批处理中(addBatch()),等待统一执行( executeBatch() ),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch() 完毕后,等待逐一执行 executeBatch() 批处理。与 JDBC 批处理相同。

核心接口

  1. Configuration
  2. MappedStatement
  3. SqlSession
  4. SqlSessionFactory
  5. Executor