zoukankan      html  css  js  c++  java
  • Mybatis原理及源码分析

    什么是Mybatis?

      Mybatis是一个半自动化的持久层框架。

      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进行管理

  • 相关阅读:
    与众不同 windows phone (50)
    与众不同 windows phone (49)
    重新想象 Windows 8.1 Store Apps (93)
    重新想象 Windows 8.1 Store Apps 系列文章索引
    重新想象 Windows 8.1 Store Apps (92)
    重新想象 Windows 8.1 Store Apps (91)
    重新想象 Windows 8.1 Store Apps (90)
    重新想象 Windows 8.1 Store Apps (89)
    重新想象 Windows 8.1 Store Apps (88)
    重新想象 Windows 8.1 Store Apps (87)
  • 原文地址:https://www.cnblogs.com/yangyongjie/p/11141308.html
Copyright © 2011-2022 走看看