手写Spring:第21章-Spring整合Mybatis

文章目录

  • 一、目标:Spring整合Mybatis
  • 二、设计:Spring整合Mybatis
  • 三、实现:Spring整合Mybatis
    • 3.0 引入依赖
    • 3.1 工程结构
    • 3.2 Spring整合ORM框架的核心类图
    • 3.3 简单ORM框架
      • 3.3.1 定义节点对象类
      • 3.3.2 定义会话接口
      • 3.3.3 会话接口实现类
      • 3.3.4 资源处理类
      • 3.3.5 链接信息和数据源配置类
      • 3.3.6 定义会话工厂接口
      • 3.3.7 会话工厂接口实现类
      • 3.3.8 会话工厂建造者实现类
    • 3.4 Spring整合对外注册
      • 3.4.1 Bean对象注册接口
      • 3.4.2 应用上下文中完成注册
      • 3.4.3 实现默认bean创建的抽象bean工厂超类
    • 3.5 整合ORM框架
      • 3.5.1 会话工厂对象实现类
      • 3.5.2 映射器扫描配置实现类
      • 3.5.3 映射器工厂对象实现类
  • 四、测试:Spring整合Mybatis
    • 4.1 添加测试配置
      • 4.1.1 用户类
      • 4.1.2 用户DAO接口
      • 4.1.3 用户XML配置文件
      • 4.1.4 mybatis配置文件
      • 4.1.5 spring配置文件
    • 4.2 单元测试
  • 五、总结:Spring整合Mybatis

一、目标:Spring整合Mybatis

如何将 ORM 框架整合到 Spring Bean 容器中?

  • ORM 框架与 Spring 整合的需求结果就是我们常用的 Mybatis-Spring 框架。
  • Mybatis-Spring 框架会将 Mybatis 代码无缝地整合到 Spring 中。
    • 允许 Mybatis 参与 Spring 的事务管理,创建映射器 MapperSqlSession,并将其注入 Bean
    • Mybatis 的异常转换为 SpringDataAccessException,实现应用代码不依赖于 MybatisSpringMybatis-Spring
  • 通过 MapperScannerConfigurer 扫描配置目录的方式,将包装了 sqlSessionFactory 对数据库操作细节的 Dao 接口类的代理类 MapperFactoryBean 注册到 Spring Bean 容器中,便于使用 Mybatis

二、设计:Spring整合Mybatis

设计:Spring整合Mybatis

  • 如果不需要为使用 MybatisDao 接口类写实现类,那么实现这部分接口的数据库操作就需要使用代理类。
    • 这部分实现类包括对 sqlSessionFactory 的调用和对 SqlSession 方法的使用。
  • Spring 中正常使用 Dao 时,需要先将 Dao 注入相应的类属性中,再将代理类的 Bean 对象注册到 Spring Bean 容器中,交给 Spring 管理。

手写Spring:第21章-Spring整合Mybatis_第1张图片

  • 方案设计包括:
    • 扫描需要注册对象、实现代理类、注册 Bean 对象。这些是将 ORMSpring 结合的核心内容。
    • 当所有的内容实现后,就可以通过 SqlSessionFactoryBuilderSpring 连接到 ORM 框架。
    • 与此同时,还需要在 Spring 框架中添加 BeanDefinitionRegistryPostProcessor 实现,便于将数据库操作 Mapper 的代理对象注入 Spring Bean 容器中。

三、实现:Spring整合Mybatis

3.0 引入依赖

pom.xml

<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>fastjsonartifactId>
    <version>1.2.75version>
dependency>
<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>8.0.11version>
    <scope>testscope>
dependency>
<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druidartifactId>
    <version>1.2.9version>
dependency>

3.1 工程结构

spring-step-20
|-src
	|-main
	|	|-java
	|		|-com.lino
	|		|	|-mybatis
	|		|	|	|-Configuration.java
	|		|	|	|-DefaultSqlSession.java
	|		|	|	|-DefaultSqlSessionFactory.java
	|		|	|	|-Resources.java
	|		|	|	|-SqlSession.java
	|		|	|	|-SqlSessionFactory.java
	|		|	|	|-SqlSessionFactoryBuilder.java
	|		|	|	|-XNode.java
	|		|	|-springframework
	|		|	|	|-aop
	|		|	|	|-beans
	|		|	|	|	|-factory
	|		|	|	|	|	|-config
	|		|	|	|	|	|-support
	|		|	|	|	|	|	|-AbstractAutowireCapableBeanFactory.java
	|		|	|	|	|	|	|-BeanDefinitionRegistryPostProcessor.java
	|		|	|	|-context
	|		|	|	|	|-annotation
	|		|	|	|	|-event
	|		|	|	|	|-support
	|		|	|	|	|	|-AbstractApplicationContext.java
	|		|	|	|-mybatis
	|		|	|	|	|-MapperFactoryBean.java
	|		|	|	|	|-MapperScannerConfigurer.java
	|		|	|	|	|-SqlSessionFactoryBean.java
	|-test
		|-java
			|-com.lino.springframework.test
				|-dao
					|-IUserDao.java
				|-po
					|-User.java
                |-ApiTest.java
		|-resources
			|	|-mapper
			|	|	|-User_Mapper.xml
			|-mybatis-config-datasource.xml
			|-spring.xml

3.2 Spring整合ORM框架的核心类图

手写Spring:第21章-Spring整合Mybatis_第2张图片

  • 实现 Mybatis-Spring 的核心类主要包括
    • 扫描和注入类(MapperScannerConfigurer)。
    • 代理类(MapperFactoryBean)。
    • SqlSessionFactoryBean 类是对 SqlSessionFactoryBuilder 类的使用,也是实现 FactoryBean 的一个 Bean 对象。

3.3 简单ORM框架

3.3.1 定义节点对象类

XNode.java

package com.lino.mybatis;

import java.util.Map;

/**
 * @description: 节点
 */
public class XNode {

    private String namespace;
    private String id;
    private String parameterType;
    private String resultType;
    private String sql;
    private Map<Integer, String> parameter;

    public String getNamespace() {
        return namespace;
    }

    public void setNamespace(String namespace) {
        this.namespace = namespace;
    }

    public String getId() {
        return id;
    }

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

    public String getParameterType() {
        return parameterType;
    }

    public void setParameterType(String parameterType) {
        this.parameterType = parameterType;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }

    public Map<Integer, String> getParameter() {
        return parameter;
    }

    public void setParameter(Map<Integer, String> parameter) {
        this.parameter = parameter;
    }
}

3.3.2 定义会话接口

SqlSession.java

package com.lino.mybatis;

import java.util.List;

/**
 * @description: SqlSession 接口
 */
public interface SqlSession {

    <T> T selectOne(String statement);

    <T> T selectOne(String statement, Object parameter);

    <T> List<T> selectList(String statement);

    <T> List<T> selectList(String statement, Object parameter);

    void close();
}
  • 这里定义了数据库操作的查询接口 SqlSession,用于查询一个或多个结果,同时包括有参数和无参数的方法。

3.3.3 会话接口实现类

DefaultSqlSession.java

package com.lino.mybatis;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.Date;
import java.util.*;

/**
 * @description: 默认会话类
 */
public class DefaultSqlSession implements SqlSession {

    private Connection connection;
    private Map<String, XNode> mapperElement;

    public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) {
        this.connection = connection;
        this.mapperElement = mapperElement;
    }

    @Override
    public <T> T selectOne(String statement) {
        try {
            XNode xNode = mapperElement.get(statement);
            PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
            ResultSet resultSet = preparedStatement.executeQuery();
            List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
            return objects.get(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public <T> T selectOne(String statement, Object parameter) {
        XNode xNode = mapperElement.get(statement);
        Map<Integer, String> parameterMap = xNode.getParameter();
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
            buildParameter(preparedStatement, parameter, parameterMap);
            ResultSet resultSet = preparedStatement.executeQuery();
            List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
            return objects.get(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public <T> List<T> selectList(String statement) {
        XNode xNode = mapperElement.get(statement);
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
            ResultSet resultSet = preparedStatement.executeQuery();
            return resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public <T> List<T> selectList(String statement, Object parameter) {
        XNode xNode = mapperElement.get(statement);
        Map<Integer, String> parameterMap = xNode.getParameter();
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
            buildParameter(preparedStatement, parameter, parameterMap);
            ResultSet resultSet = preparedStatement.executeQuery();
            return resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private void buildParameter(PreparedStatement preparedStatement, Object parameter, Map<Integer, String> parameterMap) throws SQLException, IllegalAccessException {

        int size = parameterMap.size();
        // 单个参数
        if (parameter instanceof Long) {
            for (int i = 1; i <= size; i++) {
                preparedStatement.setLong(i, Long.parseLong(parameter.toString()));
            }
            return;
        }

        if (parameter instanceof Integer) {
            for (int i = 1; i <= size; i++) {
                preparedStatement.setInt(i, Integer.parseInt(parameter.toString()));
            }
            return;
        }

        if (parameter instanceof String) {
            for (int i = 1; i <= size; i++) {
                preparedStatement.setString(i, parameter.toString());
            }
            return;
        }

        Map<String, Object> fieldMap = new HashMap<>();
        // 对象参数
        Field[] declaredFields = parameter.getClass().getDeclaredFields();
        for (Field field : declaredFields) {
            String name = field.getName();
            field.setAccessible(true);
            Object obj = field.get(parameter);
            field.setAccessible(false);
            fieldMap.put(name, obj);
        }

        for (int i = 1; i <= size; i++) {
            String parameterDefine = parameterMap.get(i);
            Object obj = fieldMap.get(parameterDefine);

            if (obj instanceof Short) {
                preparedStatement.setShort(i, Short.parseShort(obj.toString()));
                continue;
            }

            if (obj instanceof Integer) {
                preparedStatement.setInt(i, Integer.parseInt(obj.toString()));
                continue;
            }

            if (obj instanceof Long) {
                preparedStatement.setLong(i, Long.parseLong(obj.toString()));
                continue;
            }

            if (obj instanceof String) {
                preparedStatement.setString(i, obj.toString());
                continue;
            }

            if (obj instanceof Date) {
                preparedStatement.setDate(i, (java.sql.Date) obj);
            }

        }

    }

    private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {
        List<T> list = new ArrayList<>();
        try {
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            // 每次遍历行值
            while (resultSet.next()) {
                T obj = (T) clazz.newInstance();
                for (int i = 1; i <= columnCount; i++) {
                    Object value = resultSet.getObject(i);
                    String columnName = metaData.getColumnName(i);
                    String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
                    Method method;
                    if (value instanceof Timestamp) {
                        method = clazz.getMethod(setMethod, Date.class);
                    } else {
                        method = clazz.getMethod(setMethod, value.getClass());
                    }
                    method.invoke(obj, value);
                }
                list.add(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    @Override
    public void close() {
        if (null == connection) return;
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
  • 这里包括了接口定义的方法实现,即包装了 JDBC 层。通过包装可以隐藏数据库的 JDBC 操作,当调用外部接口时,由内部处理入参和出参。

3.3.4 资源处理类

Resources.java

package com.lino.mybatis;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;

/**
 * @description: 资源处理类
 */
public class Resources {

    public static Reader getResourceAsReader(String resource) throws IOException {
        return new InputStreamReader(getResourceAsStream(resource));
    }

    private static InputStream getResourceAsStream(String resource) throws IOException {
        ClassLoader[] classLoaders = getClassLoaders();
        for (ClassLoader classLoader : classLoaders) {
            InputStream inputStream = classLoader.getResourceAsStream(resource);
            if (null != inputStream) {
                return inputStream;
            }
        }
        throw new IOException("Could not find resource " + resource);
    }

    private static ClassLoader[] getClassLoaders() {
        return new ClassLoader[]{
                ClassLoader.getSystemClassLoader(),
                Thread.currentThread().getContextClassLoader()};
    }

}

3.3.5 链接信息和数据源配置类

Configuration.java

package com.lino.mybatis;

import java.sql.Connection;
import java.util.Map;

/**
 * @description: 链接信息和数据源配置类
 */
public class Configuration {

    protected Connection connection;
    protected Map<String, String> dataSource;
    protected Map<String, XNode> mapperElement;

    public void setConnection(Connection connection) {
        this.connection = connection;
    }

    public void setDataSource(Map<String, String> dataSource) {
        this.dataSource = dataSource;
    }

    public void setMapperElement(Map<String, XNode> mapperElement) {
        this.mapperElement = mapperElement;
    }

}

3.3.6 定义会话工厂接口

SqlSessionFactory.java

package com.lino.mybatis;

/**
 * @description: SqlSessionFactory 接口
 */
public interface SqlSessionFactory {

    SqlSession openSession();
}
  • 开启一个 SqlSession 接口,这是平常使用时都需要操作的内容。当操作数据库时,会获取每一次执行的 SqlSession

3.3.7 会话工厂接口实现类

DefaultSqlSessionFactory.java

package com.lino.mybatis;

/**
 * @description: 默认会话类工厂
 */
public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private final Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(configuration.connection, configuration.mapperElement);
    }

}
  • DefaultSqlSessionFactoryMybatis 最常用的类,这里实现了一个简单版本。
    • 当开启 SqlSession 时,会返回一个 DefaultSqlSession 构造函数。这个构造函数向下传递了 Configuration 配置文件。
    • 而配置文件包括 Connection connectionMap dataSourceMap mapperElement

3.3.8 会话工厂建造者实现类

SqlSessionFactoryBuilder.java

package com.lino.mybatis;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;
import java.io.InputStream;
import java.io.Reader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @description: SqlSessionFactoryBuilder
 */
public class SqlSessionFactoryBuilder {

    public DefaultSqlSessionFactory build(Reader reader) {
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(new InputSource(reader));
            Configuration configuration = parseConfiguration(document.getRootElement());
            return new DefaultSqlSessionFactory(configuration);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return null;
    }

    public DefaultSqlSessionFactory build(InputStream inputStream) {
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(inputStream);
            Configuration configuration = parseConfiguration(document.getRootElement());
            return new DefaultSqlSessionFactory(configuration);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return null;
    }

    private Configuration parseConfiguration(Element root) {
        Configuration configuration = new Configuration();
        configuration.setDataSource(dataSource(root.element("environments").element("environment").element("dataSource")));
        configuration.setConnection(connection(configuration.dataSource));
        configuration.setMapperElement(mapperElement(root.element("mappers")));
        return configuration;
    }

    // 获取数据源配置信息
    private Map<String, String> dataSource(Element element) {
        Map<String, String> dataSource = new HashMap<>(4);
        List<Element> propertyList = element.elements("property");
        for (Element e : propertyList) {
            String name = e.attributeValue("name");
            String value = e.attributeValue("value");
            dataSource.put(name, value);
        }
        return dataSource;
    }

    private Connection connection(Map<String, String> dataSource) {
        try {
            Class.forName(dataSource.get("driver"));
            return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    // 获取SQL语句信息
    private Map<String, XNode> mapperElement(Element mappers) {
        Map<String, XNode> map = new HashMap<>();

        List<Element> mapperList = mappers.elements("mapper");
        for (Element e : mapperList) {
            String resource = e.attributeValue("resource");

            try {
                Reader reader = Resources.getResourceAsReader(resource);
                SAXReader saxReader = new SAXReader();
                Document document = saxReader.read(new InputSource(reader));
                Element root = document.getRootElement();
                //命名空间
                String namespace = root.attributeValue("namespace");

                // SELECT
                List<Element> selectNodes = root.elements("select");
                for (Element node : selectNodes) {
                    String id = node.attributeValue("id");
                    String parameterType = node.attributeValue("parameterType");
                    String resultType = node.attributeValue("resultType");
                    String sql = node.getText();

                    // ? 匹配
                    Map<Integer, String> parameter = new HashMap<>();
                    Pattern pattern = Pattern.compile("(#\\{(.*?)})");
                    Matcher matcher = pattern.matcher(sql);
                    for (int i = 1; matcher.find(); i++) {
                        String g1 = matcher.group(1);
                        String g2 = matcher.group(2);
                        parameter.put(i, g2);
                        sql = sql.replace(g1, "?");
                    }

                    XNode xNode = new XNode();
                    xNode.setNamespace(namespace);
                    xNode.setId(id);
                    xNode.setParameterType(parameterType);
                    xNode.setResultType(resultType);
                    xNode.setSql(sql);
                    xNode.setParameter(parameter);

                    map.put(namespace + "." + id, xNode);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }

        }
        return map;
    }
}
  • SqlSessionFactoryBuilder 类包括的核心方法。
    • build (构建实例化元素)
      • build 类:主要用于创建解析 XML 文件中的类,以及初始化 SqlSession 工厂中的类 DefaultSqlSessionFactory
      • 注意:使用 saxReader.read(new InputSource(reader)) 获取 XML 配置文件。
    • parseConfiguration (解析配置)
      • 获取 XML 文件中的元素:这里主要获取 dataSource 数据源配置和 mappers 映射语句配置。
      • 在这两个配置中,一个是数据库的链接信息,另一个是对数据库操作语句的解析。
    • connection(Map dataSource) (链接数据库)
      • 用户可以使用 Class.forName(dataSource.get("driver")) 来链接数据库。
      • 这样包装后,外部不需要知道具体的操作。同时,当需要链接多个数据库时,也可以在这里进行扩展设置。
    • mapperElement (解析 SQL 语句)
      • 核心是解析 XML 文件中 SQL 语句的配置。这里使用正则表达式进行解析操作。
      • 解析完成的 SQL 语句就有了一个名称和 SQL 的映射关系。当操作数据库时,这个组件就可以通过映射关系获取对应的 SQL 语句并进行其他操作。

3.4 Spring整合对外注册

3.4.1 Bean对象注册接口

BeanDefinitionRegistryPostProcessor.java

package com.lino.springframework.beans.factory.support;

import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.factory.config.BeanFactoryPostProcessor;

/**
 * @description: Bean对象注册接口
 */
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {

    /**
     * 注册Bean对象
     *
     * @param registry Bean注册对象
     * @throws BeansException 异常
     */
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
  • BeanDefinitionRegistryPostProcessor 类继承了 BeanFactoryPostProcessor 类。
    • 凡是实现了 BeanDefinitionRegistryPostProcessor 类的接口,即可主动向 Spring Bean 容器注册 Bean 对象。

3.4.2 应用上下文中完成注册

AbstractApplicationContext.java

package com.lino.springframework.context.support;

import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.factory.ConfigurableListableBeanFactory;
import com.lino.springframework.beans.factory.config.BeanFactoryPostProcessor;
import com.lino.springframework.beans.factory.config.BeanPostProcessor;
import com.lino.springframework.beans.factory.support.BeanDefinitionRegistry;
import com.lino.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import com.lino.springframework.context.ApplicationEvent;
import com.lino.springframework.context.ApplicationListener;
import com.lino.springframework.context.ConfigurableApplicationContext;
import com.lino.springframework.context.event.ApplicationEventMulticaster;
import com.lino.springframework.context.event.ContextClosedEvent;
import com.lino.springframework.context.event.ContextRefreshedEvent;
import com.lino.springframework.context.event.SimpleApplicationEventMulticaster;
import com.lino.springframework.core.convert.ConversionService;
import com.lino.springframework.core.io.DefaultResourceLoader;
import java.util.Collection;
import java.util.Map;

/**
 * @description: 抽象应用上下文
 */
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {

    public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";

    private ApplicationEventMulticaster applicationEventMulticaster;

    @Override
    public void refresh() throws BeansException {
        // 1.创建 BeanFactory,并加载 BeanDefinition
        refreshBeanFactory();

        // 2.获取 BeanFactory
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();

        // 3.添加 ApplicationContextAwareProcessor,让继承自 ApplicationContextAware 的 Bean 对象都能感知所属的 ApplicationContext
        beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));

        // 4.在 Bean 实例化之前,执行 BeanFactoryPostProcess
        invokeBeanFactoryPostProcessor(beanFactory);

        // 5.BeanPostProcessor 需要提前与其他 Bean 对象实例化之前执行注册操作
        registerBeanPostProcessor(beanFactory);

        // 6.初始化事件发布者
        initApplicationEventMulticaster();

        // 7.注册事件监听器
        registerListeners();

        // 8.设置类型转换器、提前实例化单例 Bean 对象
        finishBeanFactoryInitialization(beanFactory);

        // 9.发布容器刷新完成事件
        finishRefresh();
    }

    ...

    private void invokeBeanFactoryPostProcessor(ConfigurableListableBeanFactory beanFactory) {
        Map<String, BeanFactoryPostProcessor> beanFactoryPostProcessorMap = beanFactory.getBeansOfType(BeanFactoryPostProcessor.class);
        for (BeanFactoryPostProcessor beanFactoryPostProcessor : beanFactoryPostProcessorMap.values()) {
            beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
        }

        // 注册对象
        if (beanFactory instanceof BeanDefinitionRegistry) {
            BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
            for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessorMap.values()) {
                if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
                    BeanDefinitionRegistryPostProcessor registryPostProcessor = (BeanDefinitionRegistryPostProcessor) postProcessor;
                    registryPostProcessor.postProcessBeanDefinitionRegistry(registry);
                }
            }
        }
    }

    ...

}
  • AbstractApplicationContext#invokeBeanFactoryPostProcessor 中扩展对 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry 的调用,并注册 Bean 对象,这样就可以将 ORM 中对数据库的操作对象注册进来。

3.4.3 实现默认bean创建的抽象bean工厂超类

AbstractAutowireCapableBeanFactory.java

package com.lino.springframework.beans.factory.support;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.TypeUtil;
import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.PropertyValue;
import com.lino.springframework.beans.PropertyValues;
import com.lino.springframework.beans.factory.*;
import com.lino.springframework.beans.factory.config.*;
import com.lino.springframework.core.convert.ConversionService;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * @description: 实现默认bean创建的抽象bean工厂超类
 */
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {

    private void applyPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) {
        try {
            PropertyValues propertyValues = beanDefinition.getPropertyValues();
            for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {

                String name = propertyValue.getName();
                Object value = propertyValue.getValue();

                if (value instanceof BeanReference) {
                    // A 依赖 B,获取 B 的实例化
                    BeanReference beanReference = (BeanReference) value;
                    value = getBean(beanReference.getBeanName());
                }
//                // 类型转换
//                else {
//                    Class sourceType = value.getClass();
//                    Class targetType = (Class) TypeUtil.getFieldType(bean.getClass(), name);
//                    ConversionService conversionService = getConversionService();
//                    if (conversionService != null) {
//                        if (conversionService.canConvert(sourceType, targetType)) {
//                            value = conversionService.convert(value, targetType);
//                        }
//                    }
//                }

                // 属性填充
                BeanUtil.setFieldValue(bean, name, value);
            }
        } catch (Exception e) {
            throw new BeansException("Error setting property values: " + beanName + " message: " + e);
        }
    }

    ...
}
  • 暂时注释掉 类型转换的代码,避免 bug 的出现。

3.5 整合ORM框架

3.5.1 会话工厂对象实现类

SqlSessionFactoryBean.java

package com.lino.springframework.mybatis;

import com.lino.mybatis.SqlSessionFactory;
import com.lino.mybatis.SqlSessionFactoryBuilder;
import com.lino.springframework.beans.factory.FactoryBean;
import com.lino.springframework.beans.factory.InitializingBean;
import com.lino.springframework.core.io.DefaultResourceLoader;
import com.lino.springframework.core.io.Resource;
import java.io.InputStream;

/**
 * @description: 会话工厂
 */
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean {

    private String resource;
    private SqlSessionFactory sqlSessionFactory;

    @Override
    public SqlSessionFactory getObject() throws Exception {
        return sqlSessionFactory;
    }

    @Override
    public Class<?> getObjectType() {
        return SqlSessionFactory.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader();
        Resource resource = defaultResourceLoader.getResource(this.resource);

        try (InputStream inputStream = resource.getInputStream()) {
            this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void setResource(String resource) {
        this.resource = resource;
    }
}
  • SqlSessionFactoryBean 类主要实现了 FactoryBean、InitializingBean 类,用于加载 Mybatis 核心流程类。
    • 实现 InitializingBean 类主要用于加载 Mybatis 相关的内容。如:解析 XML、构造 SqlSession、链接数据库等。
    • FactoryBean 类主要包括 getObject 方法、getObjectType 方法和 isSingleton 方法。

3.5.2 映射器扫描配置实现类

MapperScannerConfigurer.java

package com.lino.springframework.mybatis;

import cn.hutool.core.lang.ClassScanner;
import com.lino.mybatis.SqlSessionFactory;
import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.PropertyValue;
import com.lino.springframework.beans.PropertyValues;
import com.lino.springframework.beans.factory.ConfigurableListableBeanFactory;
import com.lino.springframework.beans.factory.config.BeanDefinition;
import com.lino.springframework.beans.factory.support.BeanDefinitionRegistry;
import com.lino.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import java.util.Set;

/**
 * @description: Mapper 扫描配置,根据包信息扫描接口类并注册
 */
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor {

    private String basePackage;
    private SqlSessionFactory sqlSessionFactory;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        try {
            Set<Class<?>> classes = ClassScanner.scanPackage(basePackage);
            for (Class<?> clazz : classes) {
                // Bean 对象定义
                BeanDefinition beanDefinition = new BeanDefinition(clazz);
                PropertyValues propertyValues = new PropertyValues();
                propertyValues.addPropertyValue(new PropertyValue("mapperInterface", clazz));
                propertyValues.addPropertyValue(new PropertyValue("sqlSessionFactory", sqlSessionFactory));
                beanDefinition.setPropertyValues(propertyValues);
                beanDefinition.setBeanClass(MapperFactoryBean.class);
                // Bean 对象注册
                registry.registerBeanDefinition(clazz.getSimpleName(), beanDefinition);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }
}
  • MapperScannerConfigurer 类处理的核心内容是将用户配置在 XMLDao 接口类的地址全部扫描出来,并将代理对象注册到 Spring Bean 容器中。
    • 首先,通过 ClassScanner.scanPackage(basePackage) 处理类的扫描注册 classpath:com.lino.springframework.test.dao.**.class,解析 class 文件来获取资源信息。
    • 然后,在设置 Bean 对象的定义时,也设置 beanDefinition.setBeanClass(MapperFactoryBean.class),同时在前面设置了相应的属性信息。
    • 最后,执行注册操作 registry.registerBeanDefinition(clazz.getSimpleName(), beanDefinition)

3.5.3 映射器工厂对象实现类

MapperFactoryBean.java

package com.lino.springframework.mybatis;

import com.lino.mybatis.SqlSessionFactory;
import com.lino.springframework.beans.factory.FactoryBean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * @description: 数据库操作映射工厂对象。每一个 DAO 接口对应的 Mapper 的代理对象,通过代理对象完成数据库的操作
 */
public class MapperFactoryBean<T> implements FactoryBean<T> {

    private Class<T> mapperInterface;
    private SqlSessionFactory sqlSessionFactory;

    public MapperFactoryBean() {
    }

    public MapperFactoryBean(Class<T> mapperInterface, SqlSessionFactory sqlSessionFactory) {
        this.mapperInterface = mapperInterface;
        this.sqlSessionFactory = sqlSessionFactory;
    }

    @Override
    public T getObject() throws Exception {
        InvocationHandler handler = (proxy, method, args) -> {
            // 排除 Object 方法;toString、hashCode
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
            try {
                System.out.println("你被代理了,执行SQL操作!" + method.getName());
                return sqlSessionFactory.openSession().selectOne(mapperInterface.getName() + "." + method.getName(), args[0]);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return method.getReturnType().newInstance();
        };
        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{mapperInterface}, handler);
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterface;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    public Class<T> getMapperInterface() {
        return mapperInterface;
    }

    public void setMapperInterface(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public SqlSessionFactory getSqlSessionFactory() {
        return sqlSessionFactory;
    }

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }
}
  • MapperFactoryBean 类非常重要,所有的 Dao 接口类实际上都是它,它对 SQL 的所有操作进行分发处理。
    • T getObject() 是一个 Java 代理类的实现,这个代理类对象会被注入 Spring Bean 容器中。
      • 在调用方法内容时,会执行代理类的实现部分。也就是 你被代理了,执行SQL操作!
      • InvocationHandler 代理类的实现较简单,主要开启 SqlSession,并通过固定的 key 返回 SQL 操作。
      • 返回执行结果后,查询到的结果信息会反射操作成对象类。

四、测试:Spring整合Mybatis

4.1 添加测试配置

4.1.1 用户类

User.java

package com.lino.springframework.test.po;

import java.util.Date;

/**
 * @description: 用户类
 */
public class User {
    /**
     * 主键ID
     */
    private Long id;
    /**
     * 用户ID
     */
    private String userId;
    /**
     * 用户头像
     */
    private String userHead;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 更新时间
     */
    private Date updateTime;

    public Long getId() {
        return id;
    }

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

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUserHead() {
        return userHead;
    }

    public void setUserHead(String userHead) {
        this.userHead = userHead;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }
}

4.1.2 用户DAO接口

IUserDao.java

package com.lino.springframework.test.dao;

import com.lino.springframework.test.po.User;

/**
 * @description: 用户DAO接口
 */
public interface IUserDao {

    /**
     * 根据用户ID获取用户信息
     *
     * @param id 用户ID
     * @return 用户信息
     */
    User queryUserInfoById(Long id);
}

4.1.3 用户XML配置文件

User_Mapper.xml


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lino.springframework.test.dao.IUserDao">

    <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="com.lino.springframework.test.po.User">
        SELECT id, userId, userHead, createTime
        FROM user
        where id = #{id}
    select>

mapper>

4.1.4 mybatis配置文件

mybatis-config-datasource.xml.java


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://localhost:3306/spring?useSSL=false&serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            dataSource>
        environment>
    environments>

    <mappers>
        <mapper resource="mapper/User_Mapper.xml"/>
    mappers>

configuration>

4.1.5 spring配置文件

spring.xml


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	         http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="sqlSessionFactory" class="com.lino.springframework.mybatis.SqlSessionFactoryBean">
        <property name="resource" value="classpath:mybatis-config-datasource.xml"/>
    bean>

    <bean class="com.lino.springframework.mybatis.MapperScannerConfigurer">
        
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
        
        <property name="basePackage" value="com.lino.springframework.test.dao"/>
    bean>
beans>

4.2 单元测试

ApiTest.java

@Test
public void test_ClassPathXmlApplicationContext() {
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:spring.xml");
    IUserDao userDao = beanFactory.getBean("IUserDao", IUserDao.class);
    User user = userDao.queryUserInfoById(1L);
    System.out.println("测试结果:" + JSON.toJSONString(user));
}

测试结果

你被代理了,执行SQL操作!queryUserInfoById
测试结果:{"createTime":1670572325000,"id":1,"userHead":"01_50","userId":"184172133"}
  • 从测试结果看,Mybatis 已经交给 Spring 容器管理。返回的结果也是正确的。

五、总结:Spring整合Mybatis

  • 实现 MapperFactoryBeanMapperScannerConfigurerSqlSessionFactoryBean 这些核心关键类后,可以将 SpringMybatis 结合起来使用,解决了没有实现类的接口不能处理数据库的增、删、改、查操作的问题。

你可能感兴趣的:(手写spring,mybatis,spring,java)