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

  • 相关阅读:
    selenium(七)expected_conditions EC
    Alpine Linux常用命令
    python logging模块,升级print调试到logging。
    用flask Flask-RESTful,实现RESTful API
    python,判断操作系统是windows,linux
    在docker hub,用github的dockerfile自动生成docker镜像
    解决pycharm问题:module 'pip' has no attribute 'main'
    alpine linux docker 安装 lxml出错的解决办法。
    (转载)服务端技术选型
    maven的pom文件
  • 原文地址:https://www.cnblogs.com/yangyongjie/p/11141308.html
Copyright © 2020-2023  润新知