Mybatis原理及源码分析

什么是Mybatis?

  Mybatis是一个半自动化的持久层ORM(对象关系映射)框架。

  Mybatis可以将向PreparedStatement中的输入参数自动进行映射(输入映射),将结果集映射成Java对象(输出映射)

 

为什么使用Mybatis?

  JDBC:

    SQL夹杂在Java代码块中,耦合度高导致硬编码

    维护不易且实际开发需求中SQL有变化,频繁修改的情况多见

  Hibernate和JPA:

    长难复杂SQL,对于Hibernate而言处理也不容易

    内部自动生成的SQL,不容易做特殊优化

    基于全映射的全自动框架,大量字段的POJO进行部分映射时比较苦难,导致数据库性能下降

而实际开发中,对开发人员而言,核心SQL还是需要自己优化,而Mybatis中SQL和Java代码分开,功能边界清晰,一个专注业务,一个专注数据

 

配置文件,mybatis-config.xml

<?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>
    <properties resource=""></properties><!--加载配置文件-->
    <settings>
        <!--开启二级缓存,默认开启-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    <typeAliases>

        <!--设置单个pojo别名-->
        <!--<typeAlias alias="Employee" type="com.yang.domain.Employee"/>-->
        <!--对整个包下的pojo设置别名,别名为类名,如果类上使用了@Alias("")注解指定了别名则用注解设置的-->
        <package name="com.yang.domain"/>
    </typeAliases>
    <!--与Spring整合后,environment配置将废除-->
    <environments default="development">
        <environment id="development">
            <!--使用jdbc事务管理,由mybatis自己管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--数据库连接池,由mybatis自己管理-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="yang"/>
                <property name="password" value="yang"/>
            </dataSource>
        </environment>
    </environments>
    <!--我们写的sql映射文件-->
    <mappers>
        <mapper resource="mybatis/xxxMapper.xml"/>
    </mappers>
</configuration>

 logback.xml,打印sql

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_HOME" value="/logback/LogFile"/>
    <!--控制台输出-->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%date{yyyy-MM-dd HH:mm:ss.SSS}|%thread|%-5level|%r|%X{threadId}|%C|%msg%n</pattern>
        </encoder>
    </appender>
    <!--sql相关-->
    <logger name="java.sql">
        <level value="debug" />
    </logger>
    <logger name="org.apache.ibatis">
        <level value="info" />
    </logger>
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

 简单的Mybatis操作数据库步骤:

  1:创建Mybatis全局配置文件,包含了影响Mybatis行为的设置(setting)和属性(properties)信息、如数据库连接池信息等

  2:创建SQL映射文件,映射文件的作用就相当于是定义Dao接口的实现类如何工作

  3:将sql映射文件注册到全局配置中

  4:持久化代码

    1):根据全局配置文件得到SqlSessionFactory

    2):使用SqlSessionFactory,获取到SqlSession对象使用它来执行增删改查,一个SqlSession就是代表和数据库的一次会话,用完则关闭

    3):使用sql的唯一标志,namespace+id,执行sql

String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 1、获取sqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 2、获取sqlSession对象
        SqlSession openSession = sqlSessionFactory.openSession();
        try {
            // 3、获取接口的实现类对象
            //会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
            Employee employee = (Employee) openSession.selectOne(
                    "com.atguigu.mybatis.EmployeeMapper.selectEmp", 1);
        } finally {
            openSession.close();
        }

第二种方式,接口式编程

  xxxMapper.xml中的namespace设置为xxxMapper接口的全路径名,Mybatis会为Mapper接口创建一个代理对象

  使用接口式编程会有更强的类型检查,参数控制等

 String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 1、获取sqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 2、获取sqlSession对象
        SqlSession openSession = sqlSessionFactory.openSession();
        try {
            // 3、获取接口的实现类对象
            //会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            Employee employee = mapper.getEmpById(1);
        } finally {
            openSession.close();
        }

 SqlSession,需要注意:

  1、SqlSession的实例不是线程安全的,因此是不能被共享的

  2、SqlSession每次使用完成后需要正确关闭,这个关闭操作是必须的

  3、SqlSession可以直接调用方法的id进行数据库操作,不过一般推荐使用SqlSession获取到Dao接口的代理类,执行代理对象的方法,可以更安全的进行类型检查操作

 

代理Mapper执行方法的源码:

1、JDK动态代理创建Mapper的代理类 

    public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
        ClassLoader classLoader = mapperInterface.getClassLoader();
        Class<?>[] interfaces = new Class[]{mapperInterface};
        MapperProxy proxy = new MapperProxy(sqlSession);
        return Proxy.newProxyInstance(classLoader, interfaces, proxy);
    }

2、代理类执行方法

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this, args);
        } else {
            Class<?> declaringInterface = this.findDeclaringInterface(proxy, method);
            MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, this.sqlSession);
            Object result = mapperMethod.execute(args);
            if (result == null && method.getReturnType().isPrimitive() && !method.getReturnType().equals(Void.TYPE)) {
                throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
            } else {
                return result;
            }
        }
    }

execute:

    public Object execute(Object[] args) {
        Object result = null;
        Object param;
    // 判断执行sql类型,insert,update、delete或select,然后封装参数,调用的还是sqlSession的增删改查方法
if (SqlCommandType.INSERT == this.type) { param = this.getParam(args); result = this.sqlSession.insert(this.commandName, param); } else if (SqlCommandType.UPDATE == this.type) { param = this.getParam(args); result = this.sqlSession.update(this.commandName, param); } else if (SqlCommandType.DELETE == this.type) { param = this.getParam(args); result = this.sqlSession.delete(this.commandName, param); } else { if (SqlCommandType.SELECT != this.type) { throw new BindingException("Unknown execution method for: " + this.commandName); } if (this.returnsVoid && this.resultHandlerIndex != null) { this.executeWithResultHandler(args); } else if (this.returnsList) { result = this.executeForList(args); } else if (this.returnsMap) { result = this.executeForMap(args); } else { param = this.getParam(args); result = this.sqlSession.selectOne(this.commandName, param); } } return result; }

getParam方法:

 

 

Mybatis的缓存:

  Mybatis提供查询缓存,用于减轻数据库压力,提高数据库性能,Mybatis提供一级缓存,二级缓存

  一级缓存(粒度小):SqlSession级别的缓存,在操作数据库时需要构造SqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据,不同的SqlSession之间的缓

    存数据区域是互相不影响的。

    当第一次发起查询请求时,先去缓存中查找有没有符合的信息,如果没有,就从数据库中去查,然后将结果信息存储到一级缓存中

    如果SqlSession执行了Commit操作(插入、删除、更新)等,将清空SqlSession中的一级缓存,为了让缓存中的信息是最新信息,避免脏读,Mybatis默认是支持一级缓存的,

    关掉一级缓存的话需要在配置文件中配置。

    SqlSession关闭,一级缓存就清空

    应用:将Mybatis和Spring整合开发,事务控制是在service中,开始执行时,开启事务,创建SqlSession对象。

      在service方法内第一次调用,第二次调用将从一级缓存中取数据,方法结束,SqlSession关闭

      如果执行了两次service方法调用查询相同的信息,不走一级缓存,因为service方法结束,SqlSession就关闭了,一级缓存也随之清空

    

  二级缓存(粒度大):Mapper级别的缓存,多个SqlSession去操作同一个mapper sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

    多个SqlSession共享一个Mapper的二级缓存区域,按照namespace区分,每个mapper有都按自己的namespace区分的缓存区域。二级缓存默认是也是开启的

    开启二级缓存:

      1):在mybatis-config.xml配置文件的setting标签中设置二级缓存的开关

      2):在每个具体的mapper.xml文件中开启二级缓存

      3):调用pojo类实现序列化接口,因为为了将缓存数据取出执行反序列操作,因为二级缓存存储介质多种多样,不一定在内存

    禁用缓存:

      在每个select标签中设置useCache="false",如果想要针对每次查询都需要最新的数据,则需要设置禁用二级缓存

    

Mybatis和Spring整合:

  1)、需要Spring通过单例方式管理SqlSessionFactory,注入org.mybatis.spring.SqlSessionFactoryBean指定mybatis配置文件地址、dataSource、mapper.xml、别名等

  2)、Spring和Mybatis整合生成代理对象,使用SqlSessionFactory创建Session(整合自动完成),提供SqlSessionTemplate

  3)、持久层的mapper都需要由Spring进行管理

posted @ 2019-07-05 23:53  杨岂  阅读(1158)  评论(0编辑  收藏  举报