MyBatis - DAO接口不需要实现类分析(上)

简介: MyBatis - DAO接口不需要实现类分析(上)

相信大家在刚开始学习mybatis注解方式,或者spring+mybatis注解方式的时候,一定会有一个疑问,为什么mybatis的dao接口只需要一个接口,不需要实现类,就可以正常使用,笔者最开始的时候也会有这种疑问,当时在网上查了很多资料,也问过公司比较年长的同事,但是并没有得到答案,后来通过自己看mybatis的源码的方式才明白其中道理,接下来我就对大家分享,为什么dao接口不需要实现类的原理,这篇文章的讲解主要分为两部分:

1. mybatis注解方式是怎样通过没有实现类的dao接口进行数据库操作

2. spring+mybatis注解方式是怎样在没有实现类的dao接口的情况下结合



一、结论

  1. mybatis注解方式通过没有实现类的dao接口进行数据库操作的原理,一句话概括,就是jdk proxy,就是jdk代理

    1)原理上:JDK动态动态代理的原理是根据 InvocationHandler 中的invoke()方法,由jdk为你的接口手动生成了一个实现了对应接口的类,因此,你的接口可以调用,这是理解mybatis接口没有实现类能被调用的关键。

    2)功能上:可以看出mybatis中的接口就是XML文件的描述,一方面这样做的目的是和spring集成,将接口交给spring管理;另一方面是为了更加方便的管理XML文件(使用接口的package+interface作为namespace,method作为ID。
  2. spring+mybatis注解方式,也是没有实现类的,但是spring会默认返回MapperFactoryBean对象作为实现类的替换,但是这个只是被spring使用的,mybatis本身还是通过jdk代理来运行的。

二、讲解

(1)mybatis注解方式是怎样通过没有实现类的dao接口进行数据库操作

/**  
 * 类UserMapper.java的实现描述:TODO 类实现描述  
 * @author yuezhihua 2015年7月9日 上午11:18:30 
 */  
public interface UserMapper {      
    /** 
     * 根据用户id查询用户角色 
     * @param userId 
     * @return 
     */  
    @Select("select * from role_main a INNER JOIN user_role b ON a.id = b.role_id WHERE b.user_id=#{userId}")  
    public List<RolePO> getRolesByUserId(@Param("userId")Integer userId);  
    /** 
     * 根据用户id查询用户角色名 
     * @param userId 
     * @return 
     */  
    @Select("select a.role_name from role_main a INNER JOIN user_role b ON a.id = b.role_id WHERE b.user_id=#{userId}")  
    public Set<String> getRoleNamesByUserId(@Param("userId")Integer userId);  
    /** 
     * 根据userid查询用户的所有权限 
     * @param userId 
     * @return 
     */  
    @Select("SELECT a.permission_name FROM permission_main a INNER JOIN role_permission b ON a.id=b.permission_id WHERE b.role_id IN (SELECT d.role_id from user_main c INNER JOIN user_role d ON c.id = d.user_id WHERE c.id=#{userId})")  
    public Set<String> getPermissionsByUserId(@Param("userId")Integer userId);  
    /** 
     * 通过用户名查询用户信息 
     * @param username 
     * @return 
     */  
    @Select("select * from user_main where username=#{username}")  
    @Results({  
          @Result(property = "roleNames", column = "id", many = @Many(fetchType=FetchType.LAZY,select = "getRoleNamesByUserId")),  
          @Result(property = "permissionNames", column = "id", many = @Many(fetchType=FetchType.LAZY,select = "getPermissionsByUserId"))  
      })  
    public UserPO getUserByUsername(@Param("username")String username);  
    @Select("select username from user_main")  
    public List<String> getRoleMain();  
}

测试用例

/**   
 * 类SqlTemplateTest.java的实现描述:TODO 类实现描述  
 * @author yuezhihua 2015年7月29日 下午2:07:44 
 */  
@RunWith(SpringJUnit4ClassRunner.class)  
@ContextConfiguration(locations = {  
        "classpath*:spring/demo-locator.xml"  
})  
public class SqlTemplateTest {  
    @Autowired  
    private SqlSessionTemplate sqlSessionTemplate;  
    @Autowired  
    private SqlSessionFactory sqlSessionFactory;  
    /** 
     * 初始化datasource 
     */  
    @Before  
    public void init(){  
        DataSource ds = null;  
        try {  
            ds = BaseDataTest.createUnpooledDataSource(BaseDataTest.DERBY_PROPERTIES);  
            BaseDataTest.runScript(ds, "com/mybatis/demo/databases/lazyloader/lazyloader-schema.sql");  
            BaseDataTest.runScript(ds, "com/mybatis/demo/databases/lazyloader/lazyloader-dataload.sql");  
        } catch (IOException e) {  
            e.printStackTrace();  
        } catch (SQLException e) {  
            e.printStackTrace();  
        }  
    }       
    /** 
     * 测试mybatis自身的查询 
     */  
    @Test  
    public void testMybatisSelect(){  
        SqlSession session = sqlSessionFactory.openSession();  
        UserMapper mapper = session.getMapper(UserMapper.class);  
        UserPO userPo = mapper.getUserByUsername("zhangsan");  
        System.out.println("-----testMybatisSelect:"+userPo.getUsername());  
    }           
}

笔者这里不是纯使用mybatis,而是使用mybatis+spring,讲解第一部分的时候,我还是会用带有spring的方式来给大家讲解,大家注重看原理就好。

第一部分的时候会用到测试用例:testMybatisSelect

大家可以看到,测试用例里边获取dao接口的方法时session.getMapper(UserMapper.class);那咱们就看看sqlsession是怎么样获取usermapper接口的,返回的这个usermaper接口又有什么改变!

获取usermapper接口代理对象的时序图


image.png



返回的Usermapper编程了jdk代理对象,org.apache.ibatis.binding.MapperProxy@7e276594

虽然这里打印信息显示貌似mapperproxy是usermapper的实现类,但是笔者认为,mapperproxy不能算是usermapper的实现类,因为笔者觉得实现类的概念是应该实现usermapper接口的,但是mapperproxy不是,mapperproxy只是usermapper执行方法之前的一个拦截器。

所以session.getMapper(UserMapper.class)返回的其实是usermapper的代理对象,而且usermapper中定义的方法执行都是通过mapperproxy的invoke方法代理执行的。

接下来我们看看mapper.getUserByUsername("zhangsan");这行代码的执行过程,通过usermapper一个方法的执行来讲解mybatis是怎么通过dao接口执行数据库操作的

mapperproxy.invoke(相当于一个拦截器)

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);  
}

这个方法中,首先会过滤object中的通用方法,遇到object方法会直接执行。

但是如果是非通用方法,就会调用mappermethod.execute来代理执行方法——mappermethod.execute


这个execute方法会根据不同的注解@select,@update,@delete,@insert来分配不同的执行sql环境,进行操作数据库,其实这四个操作可以分为两类,一类是更新类型,返回更新的行数;一类是查询类型,返回查询的结果,这两部分的内部原理我会在其他的文章中进行详细解释。

所以,可以总结说,mybatis执行jdk代理的dao接口方法,跳转到mappermethod,execute方法来执行具体的数据库操作,并且返回结果;而且通过jdk代理的方法返回的代理对象,让人感觉和原接口对象一样,造成使用没有实现类的接口来执行的感觉。

目录
相关文章
|
2月前
|
SQL XML Java
mybatis-源码深入分析(一)
mybatis-源码深入分析(一)
|
1月前
|
SQL Java 数据库连接
mybatis使用四:dao接口参数与mapper 接口中SQL的对应和对应方式的总结,MyBatis的parameterType传入参数类型
这篇文章是关于MyBatis中DAO接口参数与Mapper接口中SQL的对应关系,以及如何使用parameterType传入参数类型的详细总结。
30 10
|
3月前
|
XML Java 数据库连接
MyBatis中的接口代理机制及其使用
【8月更文挑战第5天】MyBatis的接口代理机制是其核心功能之一,允许通过定义接口并在运行时生成代理对象来操作数据库。开发者声明一个带有`@Mapper`注解的接口,MyBatis则依据接口方法、映射配置(XML或注解)及数据库信息动态生成代理类。此机制分为四步:创建接口、配置映射文件或使用注解、最后在业务逻辑中注入并使用代理对象。这种方式简化了数据库操作,提高了代码的可读性和可维护性。例如,在电商系统中可通过`OrderMapper`处理订单数据,在社交应用中利用`MessageMapper`管理消息,实现高效且清晰的数据库交互。
|
4月前
|
Java 数据库连接 Maven
文本,使用SpringBoot工程创建一个Mybatis-plus项目,Mybatis-plus在编写数据层接口,用extends BaseMapper<User>继承实体类
文本,使用SpringBoot工程创建一个Mybatis-plus项目,Mybatis-plus在编写数据层接口,用extends BaseMapper<User>继承实体类
|
4月前
|
Java 数据库连接 mybatis
SpringBoot配置Mybatis注意事项,mappers层下的name命名空间,要落实到Dao的video类,resultType要落到bean,配置好mybatis的对应依赖。
SpringBoot配置Mybatis注意事项,mappers层下的name命名空间,要落实到Dao的video类,resultType要落到bean,配置好mybatis的对应依赖。
MybatisPlus--IService接口基本用法,MP提供了Service接口,save(T) 这里的意思是新增了一个T, saveBatch 是批量新增的意思,saveOrUpdate是增或改
MybatisPlus--IService接口基本用法,MP提供了Service接口,save(T) 这里的意思是新增了一个T, saveBatch 是批量新增的意思,saveOrUpdate是增或改
|
4月前
|
XML Java 数据格式
支付系统----微信支付20---创建案例项目--集成Mybatis-plus的补充,target下只有接口的编译文件,xml文件了,添加日志的写法
支付系统----微信支付20---创建案例项目--集成Mybatis-plus的补充,target下只有接口的编译文件,xml文件了,添加日志的写法
接口模板,文本常用的接口Controller层,常用的controller层模板,Mybatisplus的相关配置
接口模板,文本常用的接口Controller层,常用的controller层模板,Mybatisplus的相关配置
|
4月前
|
Java 数据库连接 mybatis
mybatis包扫描环境分析,最简单的环境准备
mybatis包扫描环境分析,最简单的环境准备
|
4月前
|
Java 数据库连接 Maven
Private method ‘getVideoList()‘ is never used,mybatis必须指定Mapper文件和实体目录,在参考其他人写的代码,要认真分析别人的代码,不要丢失
Private method ‘getVideoList()‘ is never used,mybatis必须指定Mapper文件和实体目录,在参考其他人写的代码,要认真分析别人的代码,不要丢失