MyBatis框架:第五章:源码解析及Mapper接口方式的mybatis的增,删,改,查实现

简介: MyBatis框架:第五章:源码解析及Mapper接口方式的mybatis的增,删,改,查实现

Mapper接口编程的命名习惯

Mapper接口方式的编程,需要先有一个接口。这个接口的命名一般是xxxxMapper。

比如:

User模块的Mapper,接口命名为UserMapper。

Book模块的Mapper,接口命名为BookMapper。

Mapper接口开发有四个开发规范必须遵守

1、对应的mapper配置文件的namespace属性值必须是Mapper接口的全类名。

2、Mapper接口中的方法名必须与mapper配置文件中对应的id值相同。

3、Mapper接口的方法的参数类型必须与mapper配置文件中配置的parameterType类型匹配上

4、Mapper接口的方法返回值类型必须与mapper配置文件中配置的resultType 类型匹配上

提前准备工作,准备好的项目

在mybatis-config.xml中配置你的库名我的是mybatis

<?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>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <!-- 修改数据库的四个连接属性  -->
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
      </dataSource>
    </environment>
  </environments>
  <!-- 配置sql语句 的那个mapper配置文件 -->
  <mappers>
    <mapper resource="com/dao/UserMapper.xml"/>
  </mappers>
</configuration>

UserMapper代码:

public interface UserMapper {    
    // 保存用户
    public int saveUser(User user);    
    // 更新用户
    public int updateUser(User user);    
    // 根据id删除用户
    public int deleteUserById(int id);    
    // 根据id搜索用户
    public User findUserById(int id);    
    // 搜索全部用户
    public List<User> findUsers();
}

UserMapper.xml代码:

<?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.dao.UserMapper">
<!--  public Integer saveUser(User user); -->
  <insert id="saveUser" parameterType="com.pojo.User">
    insert into t_user(`last_name`,`sex`) values(#{lastName},#{sex})
  </insert>
<!--  public Integer updateUser(User user); -->
  <update id="updateUser" parameterType="com.pojo.User">
    update t_user set last_name = #{lastName} , sex = #{sex} where id = #{id}
  </update>
<!--  public Integer deleteUserById(Integer id); -->
  <delete id="deleteUserById">
    delete from t_user where id = #{id}
  </delete> 
<!--  public User findUserById(Integer id); -->
  <select id="findUserById" resultType="com.pojo.User">
    select id,last_name lastName,sex from t_user where id = #{id}
  </select>
<!--  public List<User> findUsers(); -->
  <select id="findUsers" resultType="com.pojo.User">
    select id,last_name lastName,sex from t_user
  </select>
</mapper>

测试类:

public class UserMapperTest {
  static SqlSessionFactory sqlSessionFactory;
  @BeforeClass
  public static void setUpBeforeClass() throws Exception {
    String url = "mybatis-config.xml";
    // 读取配置文件
    InputStream inputStream = Resources.getResourceAsStream(url);
    // 创建SqlSessionFactory对象
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  }
  @Test
  public void testSaveUser() {
    SqlSession session = sqlSessionFactory.openSession();
    try {
      UserMapper userMapper = session.getMapper(UserMapper.class);
      User user = new User(0, "ddddd", 1);
      userMapper.saveUser(user);
      session.commit();
      System.out.println(user);
    } finally {
      session.close();
    }
  }
  @Test
  public void testUpdateUser() {
    SqlSession session = sqlSessionFactory.openSession();
    try {
      UserMapper userMapper = session.getMapper(UserMapper.class);
      User user = new User(4, "eeeee", 1);
      userMapper.updateUser(user);
      session.commit();
    } finally {
      session.close();
    }
  }
  @Test
  public void testDeleteUserById() {
    SqlSession session = sqlSessionFactory.openSession();
    try {
      UserMapper userMapper = session.getMapper(UserMapper.class);
      userMapper.deleteUserById(4);
      session.commit();
    } finally {
      session.close();
    }
  }
  @Test
  public void testFindUserById() {
    SqlSession session = sqlSessionFactory.openSession();
    try {
      UserMapper userMapper = session.getMapper(UserMapper.class);
      System.out.println(userMapper.findUserById(1));
    } finally {
      session.close();
    }
  }
  @Test
  public void testFindUsers() {
    SqlSession session = sqlSessionFactory.openSession();
    try {
      UserMapper userMapper = session.getMapper(UserMapper.class);
      System.out.println(userMapper.findUsers());
    } finally {
      session.close();
    }
  }
}

源码解析

它是怎么工作的呢?拿测试类中查询举例

1.读取配置文件mybatis-config.xml,通过Resources.getResourceAsStream(“mybatis-config.xml”);返回一个流对象InputStream

2.通过SqlSessionFactoryBuilder().build(流对象InputStream放进来)来创建SqlSessionFactory对象

3.使用SqlSessionFactory对象打开一个session方法,sqlSessionFactory.openSession();获取SqlSession对象

4.使用SqlSession调用getMapper()方法,我们进入源码查看该方法

@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

我们之前放进来的UserMapper.class对应Class type,它作为 configuration.getMapper(type, this)的参数再次传递,我们进入它的方法体内

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

它再次被当做参数和sqlSession一起传递,进入它的方法体内,现在才是重点

@SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

我们可以看到MapperProxyFactory这个类是Mapper接口的代理工厂,这个mapper的代理就是我们之前的UserMapper 的实现类

看看之前的代码UserMapper userMapper = session.getMapper(UserMapper.class);

接着查看源码return mapperProxyFactory.newInstance(sqlSession);通过newInstance方法它要创建UserMapper接口的实例了,反射有讲newInstance()创建实例。

mapperProxyFactory.newInstance(sqlSession)进入它的方法体内

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

newInstance(mapperProxy)进入它的方法体内

@SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

Proxy.newProxyInstance()方法解析:用来new一个jdk动态代理的

看看里面的参数 (mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy)

mapperInterface.getClassLoader():类加载器

new Class[] { mapperInterface }:interface com.dao.UserMapper

mapperProxy:

protected T newInstance(MapperProxy mapperProxy) {

进入MapperProxy类中

public class MapperProxy implements InvocationHandler, Serializable {

可以看到MapperProxy实现了InvocationHandler

回到之前测试类的代码:UserMapper userMapper = session.getMapper(UserMapper.class);

可以看到它已经是mapper的代理,jdk动态代理,这样可以解释为什么我们没有写接口也可以用接口里的方法,因为它通过代理给你创建了一个实现类。

继续看System.out.println(userMapper.findUsers());进入方法体内

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

为什么会进入到这个方法体内呢?userMapper.findUsers()前面证明了userMapper是jdk动态代理,jdk动态代理在执行任何方法时都会执行InvocationHandler执行里面的invoke方法

执行 final MapperMethod mapperMethod = cachedMapperMethod(method);

可以看到它已经找到了UserMapper接口中对应的方法了再看

可以看到它找到了select标签,我们接口中的方法名和我们的id值是不是对应上了,我们还可以深入的看看,select只有二种情况,一种是执行selectOne,一种是执行selectList.

当我们执行到return mapperMethod.execute(sqlSession, args);进入它的方法体内

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

通过command.getType()判断类型SELECT执行

   case SELECT:
            if (method.returnsVoid() && method.hasResultHandler()) {
              executeWithResultHandler(sqlSession, args);
              result = null;
            } else if (method.returnsMany()) {
              result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
              result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
              result = executeForCursor(sqlSession, args);
            } else {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = sqlSession.selectOne(command.getName(), param);
            }
            break;

method.returnsVoid() 可以看到通过方法的返回值区别是执行selectOne,是执行selectList.

  // 根据id搜索用户
  public User findUserById(int id);    
  // 搜索全部用户
  public List<User> findUsers();

method.returnsMany() 它是返回集合,就是返回多个,可以深入的看看

public boolean returnsMany() {

return returnsMany;

}

进入方法体内

  public static class MethodSignature {
    private final boolean returnsMany;

ctrl+f查找returnsMany

public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
  Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
  if (resolvedReturnType instanceof Class<?>) {
    this.returnType = (Class<?>) resolvedReturnType;
  } else if (resolvedReturnType instanceof ParameterizedType) {
    this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
  } else {
    this.returnType = method.getReturnType();
  }
  this.returnsVoid = void.class.equals(this.returnType);
  this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());

通过this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());可以看到它是不是集合还是数组

如果是list集合进入到result = executeForMany(sqlSession, args);进入到它的方法体内

  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

可以看到它找到了SelectList

if (method.hasRowBounds()) {
          RowBounds rowBounds = method.extractRowBounds(args);
          result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
        } else {
          result = sqlSession.<E>selectList(command.getName(), param);
        }

method.returnsMap() 是不是Map

method.returnsCursor() 是不是游标

最后 result = sqlSession.selectOne(command.getName(), param);可以看到它找到了selectOne

相关文章
|
5月前
|
Java 数据库连接 API
Java 对象模型现代化实践 基于 Spring Boot 与 MyBatis Plus 的实现方案深度解析
本文介绍了基于Spring Boot与MyBatis-Plus的Java对象模型现代化实践方案。采用Spring Boot 3.1.2作为基础框架,结合MyBatis-Plus 3.5.3.1进行数据访问层实现,使用Lombok简化PO对象,MapStruct处理对象转换。文章详细讲解了数据库设计、PO对象实现、DAO层构建、业务逻辑封装以及DTO/VO转换等核心环节,提供了一个完整的现代化Java对象模型实现案例。通过分层设计和对象转换,实现了业务逻辑与数据访问的解耦,提高了代码的可维护性和扩展性。
216 1
|
4月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
150 0
|
7月前
|
SQL 存储 Java
Mybatis源码解析:详述初始化过程
以上就是MyBatis的初始化过程,这个过程主要包括SqlSessionFactory的创建、配置文件的解析和加载、映射文件的加载、SqlSession的创建、SQL的执行和SqlSession的关闭。这个过程涉及到了MyBatis的核心类和接口,包括SqlSessionFactory、SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、Configuration、SqlSession和Executor等。通过这个过程,我们可以看出MyBatis的灵活性和强大性,它可以很好地支持定制化SQL、存储过程以及高级映射,同时也避免了几
134 20
|
8月前
|
人工智能 API 开发者
HarmonyOS Next~鸿蒙应用框架开发实战:Ability Kit与Accessibility Kit深度解析
本书深入解析HarmonyOS应用框架开发,聚焦Ability Kit与Accessibility Kit两大核心组件。Ability Kit通过FA/PA双引擎架构实现跨设备协同,支持分布式能力开发;Accessibility Kit提供无障碍服务构建方案,优化用户体验。内容涵盖设计理念、实践案例、调试优化及未来演进方向,助力开发者打造高效、包容的分布式应用,体现HarmonyOS生态价值。
476 27
|
8月前
|
机器学习/深度学习 人工智能 Java
Java机器学习实战:基于DJL框架的手写数字识别全解析
在人工智能蓬勃发展的今天,Python凭借丰富的生态库(如TensorFlow、PyTorch)成为AI开发的首选语言。但Java作为企业级应用的基石,其在生产环境部署、性能优化和工程化方面的优势不容忽视。DJL(Deep Java Library)的出现完美填补了Java在深度学习领域的空白,它提供了一套统一的API,允许开发者无缝对接主流深度学习框架,将AI模型高效部署到Java生态中。本文将通过手写数字识别的完整流程,深入解析DJL框架的核心机制与应用实践。
480 3
|
8月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
279 4
|
8月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
8月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
5月前
|
Java 数据库连接 数据库
Spring boot 使用mybatis generator 自动生成代码插件
本文介绍了在Spring Boot项目中使用MyBatis Generator插件自动生成代码的详细步骤。首先创建一个新的Spring Boot项目,接着引入MyBatis Generator插件并配置`pom.xml`文件。然后删除默认的`application.properties`文件,创建`application.yml`进行相关配置,如设置Mapper路径和实体类包名。重点在于配置`generatorConfig.xml`文件,包括数据库驱动、连接信息、生成模型、映射文件及DAO的包名和位置。最后通过IDE配置运行插件生成代码,并在主类添加`@MapperScan`注解完成整合
932 1
Spring boot 使用mybatis generator 自动生成代码插件
|
8月前
|
XML Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于注解的整合
本文介绍了Spring Boot集成MyBatis的两种方式:基于XML和注解的形式。重点讲解了注解方式,包括@Select、@Insert、@Update、@Delete等常用注解的使用方法,以及多参数时@Param注解的应用。同时,针对字段映射不一致的问题,提供了@Results和@ResultMap的解决方案。文章还提到实际项目中常结合XML与注解的优点,灵活使用两者以提高开发效率,并附带课程源码供下载学习。
666 0

推荐镜像

更多
  • DNS