mybatis源码学习记录

"本文详细介绍了MyBatis在操作数据库时的传统JDBC步骤、动态代理获取注解SQL以及责任链模式的应用。通过一个具体的示例展示了如何使用MyBatis的XML配置和Java代码方式执行SQL。同时,探讨了JDBC的常用接口,以及MyBatis中的拦截器机制,揭示了#{}
摘要由CSDN通过智能技术生成

1.传统jdbc的代码

2.反射获取注解的sql

3.动态代理执行,

​ 获取方法参数的名字

责任链模式

4.mybatis的独立的一套使用方法

​ mybatis解析xml,及tomcat解析xml使用的digister

​ SqlSessionFactory接口(从连接或数据源中获取SqlSession)

​ sqlSession接口(执行sql,获取mappers,管理事务)

建表

CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `name` varchar(25) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

实体类

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String name;
    private Integer age;
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zzhua</groupId>
    <artifactId>mybatis-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArgs>
                        <!---可以支持通过反射拿到接口中的参数的具体名字-->
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

1.传统JDBC代码

传统的jdbc操作,步骤很多,操作麻烦,很多都是重复的代码

import java.sql.*;
import java.util.ArrayList;

public class JDBCTest {
    public static void main(String[] args) {
        // 1. 注册驱动
        try {
            // Class.forName("com.mysql.jdbc.Driver");
            // DriverManager.registerDriver(new Driver()); // 自己手动注册mysql的驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        ArrayList<User> list = new ArrayList<>();
        try {
            // 2. 获取连接
            connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mybatis-demo?"+
                                                     "serverTimezone=UTC", "zzhua195", "123456");
            // 3. 创建statement,用来执行sql
            statement = connection.createStatement();
            // 4. 返回结果集
            resultSet = statement.executeQuery("select * from user");
            // 5. 封装对象
            while (resultSet.next()) { // 将resultSet的游标向下移动一行,然后读取数据
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                int age = resultSet.getInt("age");
                list.add(new User(id, name, age));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭资源
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }

        list.forEach(System.out::println);

    }
}

上面看上去操作很简单,但是我觉得还是有不少细节,当然还有其它的,后面找些资料具体看下,先挖个坑

  • JDBC对操作数据库的整个抽象,如Connection接口,statement接口,ResultSet接口
  • 通过反射注册驱动的这个过程,如static静态代码块里注册驱动的代码
  • 通过DriverManager获取Connection连接对象的过程

2.模式介绍

1.jdk动态代理

定义一个Mapper接口,目标是获取Select注解上的内容,并执行,执行这一块后面再说

public interface UserMapper {
    @Select("select * from user where id = #{id}")
    User selectUser(Integer id,String name);
}

测试类

  • 关于动态代理的具体实现原理,看设计模式里面的动态代理笔记就知道了,其实只要涉及到jdk动态代理,关注构造这个动态代理时,传入的InvocationHandler的invoke方法就行了
public class UserMapperProxyTest {
    public static void main(String[] args) {

        UserMapper userMapperProxy = 
            (UserMapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(), 
                                                new Class<?>[]{UserMapper.class}, 
                                                new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) {
                Select selectAnno = method.getAnnotation(Select.class);
                if (selectAnno != null) {
                    String[] value = selectAnno.value();
                    System.out.println(Arrays.toString(value));
                }
                
                Arrays.asList(method.getParameters())
                    .forEach(param-> System.out.println(param.getName()));
                
                // 或者使用spring的读取class文件的方式获取,本来只能读取类的具体参数名,不能读取接口的方法的具体参数名
                String[] paramNames = new DefaultParameterNameDiscoverer().getParameterNames(method);
                Arrays.asList(paramNames).forEach(System.out::println);
                return null;
            }
        });

        userMapperProxy.selectUser(1, "zj");

    }
}

执行结果

[select * from user where id = #{id}]
id
name
id
name

2.责任链模式

第一种责任链:每个拦截器都会拦截到

mybatis使用的是下面这种

// 拦截器接口
public interface Interceptor {
    public Object intercept(Object target);
}
import com.google.common.collect.Lists;
// 拦截器链
public class InterceptorChain /*implements Interceptor*/ { // 可以考虑下实现这个接口

    List<Interceptor> interceptors = Lists.newArrayList();

    public Object interceptAll(Object target) {
        /*
         * 遍历所有的拦截器, 每个拦截器接收上一个拦截器的结果,经过处理后,把结果交给下一个拦截器处理
         *                这样每一个拦截器都能对初始的那个对象搞事情
         */
        for (Interceptor interceptor : interceptors) {
            target = interceptor.intercept(target);
        }
        return target;
    }

    public void add(Interceptor interceptor) {
        this.interceptors.add(interceptor);
    }
}
public class InterceptorTest {
    public static void main(String[] args) {

        InterceptorChain chain = new InterceptorChain();
        chain.add(new Interceptor() {
            @Override
            public Object intercept(Object target) {
                System.out.println("拦截A");
                return target;
            }
        });
        chain.add(new Interceptor() {
            @Override
            public Object intercept(Object target) {
                System.out.println("拦截B");
                return target;
            }
        });
        chain.add(new Interceptor() {
            @Override
            public Object intercept(Object target) {
                System.out.println("拦截C");
                return target;
            }
        });
        chain.interceptAll(new Object());

    }
}
/*输出*/
/*
拦截A
拦截B
拦截C
*/

第二种责任链模式:前面的拦截器可以阻断后面的拦截器

跟上面的区别在于,把拦截器链本身传了过去,并且用了一个position这个可以理解为状态变量的东西,每调用一个拦截器,那就记录一下,当前需要被执行的拦截器的索引。当其中一个拦截器断了的时候,即没有调用chain.interceptAll(target,chain);,那这个执行链后面的拦截器就没得玩了。然后逆序调用回去。

public interface Interceptor {

    void intercept(Object target,InterceptorChain chain);

}
public class InterceptorChain {

    List<Interceptor> interceptors = Lists.newArrayList();

    private int position = -1; // 拦截器的位置

    public void interceptAll(Object target, InterceptorChain chain) {
        position++;
        if (position == interceptors.size()) {
            return;
        }
        interceptors.get(position).intercept(target, this);
    }

    public void add(Interceptor interceptor) {
        this.interceptors.add(interceptor);
    }
}
public class InterceptorTest {
    public static void main(String[] args) {

        InterceptorChain chain = new InterceptorChain();
        
        chain.add(new Interceptor() {
            @Override
            public void intercept(Object target,InterceptorChain chain) {
                System.out.println("拦截A");
                chain.interceptAll(target,chain);
            }
        });
        
        chain.add(new Interceptor() {
            @Override
            public void intercept(Object target,InterceptorChain chain) {
                System.out.println("拦截B");
                chain.interceptAll(target,chain);

            }
        });
        
        chain.add(new Interceptor() {
            @Override
            public void intercept(Object target,InterceptorChain chain) {
                System.out.println("拦截C");
                chain.interceptAll(target,chain);

            }
        });
        
        chain.interceptAll(new Object(),chain);

    }
}

3.mybatis使用代码

mybatis使用方式,官网给出2种方式,

  • 一种是使用xml,
  • 一种是java代码的方式

mybatis-confi.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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" 
                          value="jdbc:mysql://127.0.0.1:3306/mybatis-demo?serverTimezone=UTC"/>
                <property name="username" value="zzhua195"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

User类

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String name;
    private Integer age;
}

在resources下创建mapper/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.zzhua.mapper.UserMapper">
    <select id="selectUser" resultType="com.zzhua.jdbc.User">
        select * from user where id = #{id}
    </select>
</mapper>

UserMapper.java

public interface UserMapper {
    User selectUser(Integer id); // 现在上面暂时不要用注解的方式哦
}

概述

  • 2种方式不管是xml还是java代码,目的都是先创建Configuration实例,然后把Configuration实例传给DefaultSqlSessionFactory,然后使用DefaultSqlSessionFactory创建SqlSession,使用sqlSession实现sql语句的执行,所以关键就是这个Configuration实例。
    -在这里插入图片描述

    • 之后再看下xml文件是怎么解析的,再挖个坑

xml方式

public class XmlTest {

    public static void main(String[] args) throws IOException {

        String resource = "mybatis-config.xml";
        // 使用mybatis提供的Resources工具类读取resource,使用的类加载器方式去读的,可以记一下,下次备用
        InputStream inputStream = Resources.getResourceAsStream(resource);

        // 与下面等价的写法,关流什么的就不纠结了
        // SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, null, null);
        Configuration configuration = parser.parse();
        DefaultSqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);


        try (SqlSession session = sqlSessionFactory.openSession()) {
            User user = (User) session.selectOne("com.zzhua.mapper.UserMapper.selectUser", 1);
            System.out.println(user);
        }

        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            User user = mapper.selectUser(1);
            System.out.println(user);
        }

    }

}

java代码方式

public class ConfigurationTest {
    public static void main(String[] args) {
        
        PooledDataSource dataSource = new PooledDataSource("com.mysql.cj.jdbc.Driver",
                        "jdbc:mysql://127.0.0.1:3306/mybatis-demo?serverTimezone=UTC",
                        "zzhua195",
                        "123456");
        
        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        
        // Environment里面有个数据源,和事务管理器的工厂,这跟xml种标签的配置是一样的
        Environment environment = new Environment("development", transactionFactory, dataSource);
        // 把environment传给了configuration
        Configuration configuration = new Configuration(environment);
        
        // 加载mapper接口,放到Configuration实例的mapperRegistry属性
        configuration.addMapper(UserMapper.class);
        
        // SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
        SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);

        try (SqlSession session = sqlSessionFactory.openSession()) {
            User user = (User) session.selectOne("com.zzhua.mapper.UserMapper.selectUser", 1);
            System.out.println(user);
        }

        try (SqlSession session = sqlSessionFactory.openSession()) {
            com.zzhua.mapper.UserMapper mapper = session.getMapper(com.zzhua.mapper.UserMapper.class);
            User user = mapper.selectUser(1);
            System.out.println(user);
        }

    }
}

在这里插入图片描述

1.configuration解析出所有的信息,包括数据源和注解和xml等等,并把configuration封装给SqlSessionFactoryBuilder。

2.SqlSessionFactoryBuilder为SqlSession创建Executor执行器,并把Configuration和Executor和autocommit封装给SqlSession

3.从mappedStatement

拦截器

疑问:#{}是怎么变成?的

mybatis调用存储过程:https://blog.csdn.net/dwenxue/article/details/82257944

mybatis缓存文章:https://blog.csdn.net/weixin_34334744/article/details/89045718

一级缓存

一级缓存是SqlSession级别的,默认开启(Configuration的成员变量默认cacheEnabled=true),在使用同一个sqlSession中调用2次,那么只会查询1次,然后把结果放入缓存。如果在查询第一次,使用sqlSession.commit()提交了事务,那么第二次还是会去查询数据库。

二级缓存

mybatis先去查的二级缓存,二级缓存为空才会去查一级缓存(CachingExecutor#query()这个方法里可以看到)

四大对象:

这四个对象都是通过Configuration对应的的newXXX方法创建的(比如newExecutor、newStatementHandler等等),并且在创建完这些对象之后,都会马上接着使用Configuration实例的interceptorChain中的拦截器对这些对象进行拦截。

Executor对象:

  • 在openSession的时候创建(DefaultSqlSessionFactory#openSessionFromDataSource),
  • 一个sqlsession对应一个Executor对象

StatementHandler对象

  • 在执行sql语句的时候创建(SimpleExecutor#doQuery)
  • 在SimpleExecutor#prepareStatement方法中,StatementHandler对象使用了Connection连接对象去创建Statement

ParameterHandler对象

  • 在创建StatementHandler对象的构造器里,创建了DefaultParameterHandler对象,并且保存到了StatementHandler的parameterHandler属性里。
  • StatementHandler创建了statement之后,接着把statement设置给了ParameterHandler对象,由ParameterHandler对象来把参数和statement生成执行的sql(DefaultParameterHandler#setParameters)。

ResultSetHandler对象

  • 在创建StatementHandler对象的构造器里,创建了DefaultResultSetHandler对象,并且保存到了StatementHandler的parameterHandler属性里。

  • StatementHandler使用parameterHandler生成执行的sql之后,执行sql(PreparedStatementHandler#query),然后由DefaultResultSetHandler来处理statement执行之后获取到的ResultSet结果集DefaultResultSetHandler#handleResultSets

    ,MysqlType枚举类里面标识了数据库字段类型和java类的对应关系(结果集里面可以获取到返回的rs里的meta信息,meta信息里有数据库表的字段信息,然后根据字段类型查这个枚举类找到对应的class)。

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值