mybatis独立使用及源码分析

Posted 我爱看明朝

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis独立使用及源码分析相关的知识,希望对你有一定的参考价值。

mybatis独立使用及源码分析

不使用mybatis使用jdbc

JdbcExample

public class JdbcExample {

    public static void main(String[] args) throws Exception{
        Class.forName("org.postgresql.Driver");
        String url = "jdbc:postgresql://10.25.76.198:5432/artifact";
        String username = "dev_root";
        String password = "dev_root";
        Connection connection = DriverManager.getConnection(url, username, password);

        Statement stmt = connection.createStatement();
        PreparedStatement preparedStatement = connection.prepareStatement("select * from clean_policy where id = ?");
        preparedStatement.setLong(1,292L);

        //prepare sql进行预处理
        ResultSet resultSet = preparedStatement.executeQuery();
        while (resultSet.next()) {
            String name = resultSet.getString("name");
            System.out.println("name:" + name);
        }
        //statement 不预处理直接替换
        String sql = "select * from clean_policy where id = {0}";
        sql = MessageFormat.format(sql, 292L);
        ResultSet resultSet1 = stmt.executeQuery(sql);
        while (resultSet1.next()) {
            String name = resultSet1.getString("name");
            System.out.println("name1:" + name);
        }

    }
}

我们直接使用jdbc查询语句有两种方式:
1. 预处理
2. 直接替换

两种方式的本质区别:预处理可以避免sql注入;直接替换无法避免预处理

SPI

Class.forName

//加载pg的驱动,这里加载类会触发类的静态代码块
Class.forName("org.postgresql.Driver");


org.postgresql.Driver

class Driver{

//org.postgresql.Driver类的静态代码块,再类加载的时候就执行
  static {
        try {
            register();
        } catch (SQLException var1) {
            throw new ExceptionInInitializerError(var1);
        }
  }


  public static void register() throws SQLException {
      if (isRegistered()) {
            throw new IllegalStateException("Driver is already registered. It can only be registered once.");
      } else {
          Driver registeredDriver = new Driver();
          // 这里把驱动注册到驱动管理器 DriverManager的registeredDrivers集合中
          DriverManager.registerDriver(registeredDriver);
          Driver.registeredDriver = registeredDriver;
      }
  }
}

获取到数据库连接从已加载的驱动中获取

DriverManager.getConnection(url, username, password);

private static Connection getConnection(){

  for(DriverInfo aDriver : registeredDrivers) {

    Connection con = aDriver.driver.connect(url, info);
    return con
  }
}

SPI的机制

上面JdbcExample中main方法我们去掉Class.forName方法,发现还是可以正常获取数据连接,这说明数据库驱动是被
正常加载的,这种机制是如何实现的呢? 这种就是我们接下来要了解的SPI机制(service provider interface)

public class DriverManager {

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

    private static void loadInitialDrivers() {
            while(driversIterator.hasNext()) {
              driversIterator.next();
          }
    }
}


// 通过下面的源码我们可以看到driversIterator == lookupIterator

class ServiceLoader{

   public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }
}


//driversIterator.hasNext()
private static final String PREFIX = "META-INF/services/";
 
private boolean hasNextService() {      
  if (configs == null) {
     try {
          // service.getName() == java.sql.Driver
           String fullName = PREFIX + service.getName();
           if (loader == null)
               configs = ClassLoader.getSystemResources(fullName);
            else
               configs = loader.getResources(fullName);
       } catch (IOException x) {
               fail(service, "Error locating configuration files", x);
       }
  }
     
     while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        //读取到jar包org.postgresql的META-INF下的services的java.sql.Driver的内容org.postgresql.Driver
        pending = parse(service, configs.nextElement());
    }
    nextName = pending.next();
    return true;
}

// driversIterator.next();

private S nextService() {
  // 这里nextName就是从hasNextService方法中读到的org.postgresql.Driver,
  String cn = nextName;
  //和加载org.postgresql.Driver类,执行静态代码块,把自己注册到DriverManager的registeredDrivers集合中
  c = Class.forName(cn, false, loader);
}

总结:通过约定jar包具体目录存放接口的实现类,来动态可插拔加载类的方式
这里通过: 接口 + 配置文件 通过策略模式来实现spi机制,

  1. springboot中stater中就是通过这种方式来实现自动加载注入配置的。
  2. java中领域参数校验Validation也有采用这种机制

不依赖spring使用mybaits简单实例

创建Maven项目

MybatisUse

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;

/**
 * @author xuelongjiang109
 * @description
 **/
public class MybatisUse {

    public static void main(String[] args) throws Exception{
        String mybatisXml = "mybatis.xml";
        InputStream inputStream = Resources.getResourceAsStream(mybatisXml);

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        CleanPolicyMapper cleanPolicyMapper = sqlSession.getMapper(CleanPolicyMapper.class);

        CleanPolicy cleanPolicy = cleanPolicyMapper.selectById(292L);
        System.out.println("name:" + cleanPolicy.getName());

        sqlSession.commit();
        sqlSession.flushStatements();
        sqlSession.close();

    }
}

mybaits.xml

位于resources目录下

<?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 核心配置文件 -->
<configuration>


    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="org.postgresql.Driver"/>
                <property name="url" value="jdbc:postgresql://10.25.76.198:5432/artifact"/>
                <property name="username" value="dev_root"/>
                <property name="password" value="dev_root"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 每一个 Mapper.XML 都需要在 Mybatis 核心配置文件中注册!!-->
    <mappers>
        <mapper class="com.xuelongjiang.testanyone.mybatis.CleanPolicyMapper"/>
        <!--<mapper class="com.song.dao.UserMapper"/>-->
        <!--<package name="com.song.dao"/>-->
    </mappers>
</configuration>

CleanPolicyMapper

import org.apache.ibatis.annotations.Select;

public interface CleanPolicyMapper {

    @Select("select * from clean_policy where id = #{id} ")
    CleanPolicy selectById(Long id);

}

CleanPolicy实体


/**
 * @author xuelongjiang109
 * @description
 **/
public class CleanPolicy {

    private Long id;

    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

源码分析

下面源码会省略部分代码,我们只要焦距在主流程就行。

生成sql会话工厂

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//sqlSessionFactory是sqlSession的工厂,sqlSession是对数据库一次会话的抽象

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {

      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // parser.parse() 返回org.apache.ibatis.session.Configuration
      return build(parser.parse());
}

//返回默认的会话工厂 DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

parser.parse()

 public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 读取到xml的根节点configuration
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

// 解析xml中的节点及其内容
private void parseConfiguration(XNode root) {
    environmentsElement(root.evalNode("environments"));
    mapperElement(root.evalNode("mappers"));
    // ....省略了解析其他节点如:properties,plugins,plugins
}
解析数据库配置
// 解析读取数据库配置 结果存放到configuration的environment属性
private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
          //得到指定数据源的id 对用我们配置的 development
        environment = context.getStringAttribute("default");
      }
      // 读取environment节点的内容
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        //如果当前环境的id等于指定的id则继续读取数据库相关配置
        if (isSpecifiedEnvironment(id)) {
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);   
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

解析mapper

依次读取resource、url、class 注意这里如果mapper中的这个三个属性只能配置一个

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
          //如果配置的是package则加载其下的所有类
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
            // 如果是mapper 依次读取resource、url、class 注意这里如果mapper中的这个三个属性只能配置一个
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

// 继续解析mappers中的sql
mapperParser.parse();
// 解析xml中的select,iseret,update,delete语句
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
// 解析sql语句的各种配置以及Sql内容构造出MappedStatement来存储这些信息
statementParser.parseStatementNode();
//注意:MappedStatement中的id是namespace.id
MappedStatement
//MappedStatement接下来被放到configuration.mappedStatements中(这一个map StrictMap是mybatis继承HashMap重写了部分方法)
configuration.addMappedStatement(statement);
public V put以上是关于mybatis独立使用及源码分析的主要内容,如果未能解决你的问题,请参考以下文章

myBatis执行流程及源码分析

MyBatis简单源码分析1 - 环境搭建

mybatis关于二级缓存的配置及源码分析

MyBatis框架的使用及源码分析 StatementHandler

MyBatis架构设计及源代码分析系列:MyBatis架构

Spring事务源码分析专题Mybatis的使用及跟Spring整合原理分析