zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Java 编程基础
    LING 实战
    C# 3.0\3.5 新特性
    EF Code First 入门
    C# 4.0 新特性
    JavaScript学习(二)
    JavaScript学习(一)
    csdn的blog后台程序的导航菜单的实现
    HashTable的遍历
    开通啦
  • 原文地址:https://www.cnblogs.com/javawxid/p/12812093.html
Copyright © 2011-2022 走看看