java design pattern three

4) Create IOC container related classes

1) BeanFactory interface

The uniform specification of the IOC container and the method of obtaining the bean object are defined in this interface.

/**
 * IOC容器父接口
 * @author spikeCong
 * @date 2022/10/28
 **/
public interface BeanFactory {
    
    

    Object getBean(String name)throws Exception;

    //泛型方法,传入当前类或者其子类
    <T> T getBean(String name ,Class<? extends T> clazz)throws Exception;
}

2) ApplicationContext interface

refresh()All sub-implementation classes of this interface are non-delayed in the creation of bean objects, so the method is defined in this interface . This method mainly completes the following two functions:

  • Load the configuration file.
  • The bean object is created according to the data encapsulated by the BeanDefinition object in the registry.
/**
 * 定义非延时加载功能
 * @author spikeCong
 * @date 2022/10/28
 **/
public interface ApplicationContext extends BeanFactory {
    
    

    //进行配置文件加载,并进行对象创建
    void refresh();
}

3) AbstractApplicationContext类

  • As a subclass of the ApplicationContext interface, this class is also non-delayed loading, so you need to define a Map collection in this class as a container for bean object storage.
  • Declare a variable of type BeanDefinitionReader for parsing the xml configuration file, in line with the principle of single responsibility.
  • The creation of objects of type BeanDefinitionReader is implemented by subclasses, because only subclasses clearly define which subclass object of BeanDefinitionReader to create.
/**
 * ApplicationContext接口的子实现类
 *      创建容器对象时,加载配置文件,对bean进行初始化
 * @author spikeCong
 * @date 2022/10/28
 **/
public abstract class AbstractApplicationContext implements ApplicationContext {
    
    

    //声明解析器变量
    protected BeanDefinitionReader beanDefinitionReader;

    //定义存储bean对象的Map集合
    protected Map<String,Object> singletonObjects = new HashMap<>();

    //声明配置文件类路径的变量
    protected String configLocation;

    @Override
    public void refresh() {
    
    

        //加载beanDefinition对象
        try {
    
    
            beanDefinitionReader.loadBeanDefinitions(configLocation);
            //初始化bean
            finishBeanInitialization();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

    }

    //bean初始化
    protected  void finishBeanInitialization() throws Exception {
    
    
        //获取对应的注册表对象
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();

        //获取beanDefinition对象
        String[] beanNames = registry.getBeanDefinitionNames();
        for (String beanName : beanNames) {
    
    
            //进行bean的初始化
            getBean(beanName);
        }
    };
}

4) ClassPathXmlApplicationContext类

This class mainly loads configuration files under the class path and creates bean objects. It mainly completes the following functions:

  • In the constructor, create a BeanDefinitionReader object.
  • In the construction method, call the refresh() method to load the configuration file, create a bean object and store it in the container.
  • Rewrite the getBean() method in the parent interface and implement dependency injection operations.
/**
 * IOC容器具体的子实现类,加载XML格式配置文件
 * @author spikeCong
 * @date 2022/10/28
 **/
public class ClassPathXmlApplicationContext extends AbstractApplicationContext{
    
    

    public ClassPathXmlApplicationContext(String configLocation) {
    
    
        this.configLocation = configLocation;
        //构建解析器对象
        this.beanDefinitionReader = new XmlBeanDefinitionReader();

        this.refresh();
    }

    //跟据bean的对象名称获取bean对象
    @Override
    public Object getBean(String name) throws Exception {
    
    
        //判断对象容器中是否包含指定名称的bean对象,如果包含就返回,否则自行创建
        Object obj = singletonObjects.get(name);
        if(obj != null){
    
    
            return obj;
        }

        //自行创建,获取beanDefinition对象
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
        BeanDefinition beanDefinition = registry.getBeanDefinition(name);

        //通过反射创建对象
        String className = beanDefinition.getClassName();
        Class<?> clazz = Class.forName(className);
        Object beanObj = clazz.newInstance();

        //CourseService与UserDao存依赖,所以要将UserDao一同初始化,进行依赖注入
        MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
        for (PropertyValue propertyValue : propertyValues) {
    
    
            //获取name属性值
            String propertyName = propertyValue.getName();
            //获取Value属性
            String value = propertyValue.getValue();
            //获取ref属性
            String ref = propertyValue.getRef();

            //ref与value只能存在一个
            if(ref != null && !"".equals(ref)){
    
    
                //获取依赖的bean对象,拼接set set+Course
                Object bean = getBean(ref);
                String methodName = StringUtils.getSetterMethodFieldName(propertyName);

                //获取所有方法对象
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
    
    
                    if(methodName.equals(method.getName())){
    
    
                        //执行该set方法
                        method.invoke(beanObj,bean);
                    }
                }
            }

            if(value != null && !"".equals(value)){
    
    
                String methodName = StringUtils.getSetterMethodFieldName(propertyName);
                //获取method
                Method method = clazz.getMethod(methodName, String.class);
                method.invoke(beanObj,value);
            }
        }

        //在返回beanObj之前 ,需要将对象存储到Map容器中
        this.singletonObjects.put(name,beanObj);


        return beanObj;
    }

    @Override
    public <T> T getBean(String name, Class<? extends T> clazz) throws Exception {
    
    
        Object bean = getBean(name);
        if(bean == null){
    
    
            return null;
        }

       return clazz.cast(bean);
    }
}
5) Custom IOC container test

Step 1: Install the custom IOC container project we wrote into the maven warehouse so that other projects can introduce their dependencies

//依赖信息
<dependencies>
    <dependency>
        <groupId>com.mashibing</groupId>
        <artifactId>user_defined_springioc</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

Step 2: Create a new maven project and introduce the above dependencies. The project structure is as follows

Step 3: Finish writing the code

  • dao
public interface CourseDao {
    
    
    public void add();
}

public class CourseDaoImpl implements CourseDao {
    
    

    //value注入
    private String courseName;

    public String getCourseName() {
    
    
        return courseName;
    }

    public void setCourseName(String courseName) {
    
    
        this.courseName = courseName;
    }

    public CourseDaoImpl() {
    
    
        System.out.println("CourseDaoImpl创建了......");
    }

    @Override
    public void add() {
    
    
        System.out.println("CourseDaoImpl的add方法执行了......" + courseName);
    }
}
  • service
public interface CourseService {
    
    

    public void add();
}

public class CourseServiceImpl implements CourseService {
    
    

    public CourseServiceImpl() {
    
    
        System.out.println("CourseServiceImpl创建了......");
    }

    private CourseDao courseDao;

    public void setCourseDao(CourseDao courseDao) {
    
    
        this.courseDao = courseDao;
    }

    @Override
    public void add() {
    
    
        System.out.println("CourseServiceImpl的add方法执行了......");
        courseDao.add();
    }
}
  • applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="courseService" class="com.mashibing.test_springioc.service.impl.CourseServiceImpl">
        <property name="courseDao" ref="courseDao"></property>
    </bean>
    <bean id="courseDao" class="com.mashibing.test_springioc.dao.impl.CourseDaoImpl">
        <property name="courseName" value="java"></property>
    </bean>
</beans>
  • Controller
public class CourseController{
    
    

    public static void main(String[] args) {
    
    
        //1.创建Spring的容器对象
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        //2.从容器对象中获取CourseService对象
        CourseService courseService = context.getBean("courseService", CourseService.class);

        //3.调用UserService的add方法
        courseService.add();
    }
}
6) Design patterns used in the case
  • factory pattern. This uses the factory mode + configuration file.
  • Singleton pattern. The bean objects managed by Spring IOC are all singletons. The singleton here is not controlled by the constructor, but the spring framework creates only one object for each bean.
  • Template method pattern. The finishBeanInitialization() method in the AbstractApplicationContext class calls the getBean() method of the subclass, because the implementation of getBean() is closely related to the environment.
  • Iterator pattern. The iterator mode is used for the definition of the MutablePropertyValues ​​class, because this class stores and manages PropertyValue objects and also belongs to a container, so it provides a traversal method for the container.

7.2 Analyze the classic design patterns used in the MyBatis framework

7.2.1 MyBatis review

7.2.1.1 MyBatis and ORM framework

MyBatis is an ORM (Object Relational Mapping, object-relational mapping) framework. The ORM framework is mainly based on the mapping relationship between classes and database tables to help programmers automatically realize the mutual conversion between objects and data in the database.

ORM is responsible for storing objects in the program into the database and converting data in the database into objects in the program

image-20220530160637842 **Mybatis Architecture** image-20220530160637842
1、mybatis配置

SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。

mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。

2、通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂

3、由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。

4、mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。

5、Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。

6、Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。

7、Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。

7.2.1.1 Basic use of MyBatis

Because MyBatis depends on the JDBC driver, to use MyBatis in the project, in addition to the introduction of the MyBatis framework itself (mybatis.jar), it is also necessary to introduce the JDBC driver (for example, to access MySQL's JDBC driver implementation class library mysql-connector-java. jar).

  1. Import MyBatis coordinates and other related coordinates
<!--mybatis坐标-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.4</version>
</dependency>
<!--mysql驱动坐标-->
<dependency>    
    <groupId>mysql</groupId>   
    <artifactId>mysql-connector-java</artifactId>    
    <version>5.1.6</version>    
    <scope>runtime</scope>
</dependency>
<!--单元测试坐标-->
<dependency>    
    <groupId>junit</groupId>    
    <artifactId>junit</artifactId>    
    <version>4.12</version>    
    <scope>test</scope>
</dependency>
<!--日志坐标-->
<dependency>    
    <groupId>log4j</groupId>    
    <artifactId>log4j</artifactId>    
    <version>1.2.17</version>
</dependency>
  1. Create user data table
CREATE DATABASE `mybatis_db`;
USE `mybatis_db`;

CREATE TABLE `user` (
  `id` int(11) NOT NULL auto_increment,
  `username` varchar(32) NOT NULL COMMENT '用户名称',
  `birthday` datetime default NULL COMMENT '生日',
  `sex` char(1) default NULL COMMENT '性别',
  `address` varchar(256) default NULL COMMENT '地址',
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- insert.... 略
  1. Write the User entity
public class User {
    
    
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    // getter/setter 略
}
  1. Write UserMapper mapping file
public interface UserMapper {
    
    

    public List<User> findAll();
}
<?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.mashibing.mapper.UserMapper">
    <!--查询所有-->
    <select id="findAll" resultType="com.mashibing.domain.User">
        select * from user
    </select>
</mapper>
  1. Write MyBatis core file
<?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="mysql">
        <!--使用MySQL环境-->
        <environment id="mysql">
            <!--使用JDBC类型事务管理器-->
            <transactionManager type="JDBC"></transactionManager>
            <!--使用连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql:///mybatis_test"></property>
                <property name="username" value="root"></property>
                <property name="password" value="123456"></property>
            </dataSource>
        </environment>
    </environments>

    <!--加载映射配置-->
    <mappers>
        <mapper resource="com/mashibing/mapper/UserMapper.xml"></mapper>
    </mappers>
</configuration>
  1. write test class
public class MainApp {
    
    

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

        // 加载核心配置文件
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获取SqlSessionFactory工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        // 获取SqlSession会话对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 执行sql
        List<User> list = sqlSession.selectList("com.mashibing.mapper.UserMapper.findAll");
        for (User user : list) {
    
    
            System.out.println(user);
        }
        // 释放资源
        sqlSession.close();
    }
}

7.2.2 Design patterns used in MyBatis

7.2.2.1 Builder mode

The definition of Builder mode is "to separate the construction of a complex object from its representation, so that the same construction process can create different representations." It belongs to the creation class mode, and the builder mode can separate parts from their assembly process, one step Create a complex object in one step. The user only needs to specify the type of the complex object to get the object without knowing the details of its internal construction.

Article 2 in "effective-java" also mentioned: When encountering multiple constructor parameters, consider using the Builder (Builder) mode .

image-20220530160637842

During the initialization process of the Mybatis environment, SqlSessionFactoryBuilderit will call to XMLConfigBuilderread all MybatisMapConfig.xmland all *Mapper.xmlfiles, build the core object object that Mybatis runs Configuration, and then use Configurationthe object as a parameter to build an SqlSessionFactoryobject.

image-20220530160637842

Among them , when XMLConfigBuilderbuilding Configurationan object, it will also be called XMLMapperBuilderto read *.Mapperthe file, and XMLMapperBuilderwill be used XMLStatementBuilderto read and build all SQL statements.

image-20220530160637842 image-20220530160637842 image-20220530160637842

In this process, there is a similar feature, that is, these Builders will read files or configurations, and then do a lot of steps such as XPathParser analysis, configuration or syntax analysis, reflection generation objects, and storage of result caches. It is not included in a constructor, so a large number of Builder patterns are used to solve it.

For the specific class of builder, most of the methods start with build*the beginning. For SqlSessionFactoryBuilderexample, it contains the following methods:

image-20220530160637842

From the original design intention of the builder mode, although SqlSessionFactoryBuilder has a Builder suffix, don't be confused by its name, it is not a standard builder mode. On the one hand, the construction of the original class SqlSessionFactory requires only one parameter and is not complicated.

On the other hand, the Builder class SqlSessionFactoryBuilder still defines multiple constructors with different parameter lists. In fact, the original intention of SqlSessionFactoryBuilder design is simply to simplify development. Because building SqlSessionFactory needs to build Configuration first, and building Configuration is very complicated and requires a lot of work, such as configuration reading, parsing, and creation of multiple objects. In order to hide the process of building SqlSessionFactory and make it transparent to programmers, MyBatis designed the SqlSessionFactoryBuilder class to encapsulate these construction details.

7.2.2.2 Factory pattern

In Mybatis, for example SqlSessionFactory, the factory model is used. The factory does not have such complicated logic, it is a simple factory model.

Simple Factory Pattern: Also known as the Static Factory Method pattern, it belongs to the class creation pattern. In the simple factory pattern, instances of different classes can be returned according to different parameters. The simple factory pattern specifically defines a class to be responsible for creating instances of other classes, and the created instances usually have a common parent class.

image-20220530160637842

SqlSessionIt can be considered as the core interface of Mybatis work, through which you can execute SQL statements, obtain Mappers, and manage transactions. An object similar to connecting to MySQL Connection.

image-20220530160637842

It can be seen that there openSession()are many overloaded methods of the Factory, which respectively support the input of parameters such as autoCommit, Executor, Transactionand so on, to build core SqlSessionobjects.

In DefaultSqlSessionFactorythe default factory implementation of , there is a method to see how the factory produces a product:

image-20220530160637842

openSessionFromDataSource method,

image-20220530160637842

This is an underlying method called by openSession. This method first reads the corresponding environment configuration from the configuration, then initializes to TransactionFactoryobtain an Transactionobject, and then Transactionobtains an Executorobject, and finally constructs it through the three parameters of configuration, Executor, and whether to autoCommit SqlSession.

7.2.2.3 Singleton pattern

Singleton Pattern: The singleton pattern ensures that there is only one instance of a certain class, and instantiates itself and provides this instance to the entire system. This class is called a singleton class, which provides global access methods.

There are three main points of the singleton mode: one is that a class can only have one instance; the other is that it must create this instance by itself; the third is that it must provide this instance to the entire system by itself. The singleton pattern is an object creation pattern. Singleton mode is also called singleton mode or single state mode.

image-20220530160637842

There are two places where the singleton pattern is used in Mybatis, ErrorContextand the singleton used in each thread scope is used to record the execution environment error information of the thread, and the LogFactorylog factory provided to the entire Mybatis , used to obtain the log object configured for the project.ErrorContextLogFactory

public class ErrorContext {
    
    

    private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<>();


    private ErrorContext() {
    
    
    }

    public static ErrorContext instance() {
    
    
        ErrorContext context = LOCAL.get();
        if (context == null) {
    
    
            context = new ErrorContext();
            LOCAL.set(context);
        }
        return context;
    }

}

The constructor is private modified, with a static local instance variable and a method of obtaining the instance variable. In the method of obtaining the instance, first judge whether it is empty, if so, create it first, and then return the constructed object.

The only interesting thing here is that the static instance variable of LOCAL is modified ThreadLocal, that is to say, it belongs to the data of each thread, and in instance()the method, first obtain the instance of this thread, if not, create the thread independently Yes ErrorContext.

7.2.2.4 Proxy mode

The proxy mode can be considered as the core mode used by Mybatis. It is precisely because of this mode that we only need to write Mapper.javathe interface without implementing it. The Mybatis background will help us complete the specific SQL execution.

Proxy Pattern: Provide a proxy to an object, and the proxy object controls the reference to the original object. The English of the proxy mode is called Proxy, which is an object structure mode.

image-20220530160637842

There are two steps here, the first is to create a Proxy in advance, the second is to automatically request the Proxy when using it, and then the Proxy will perform specific transactions;

When we use Configurationthe getMappermethod, the method is called mapperRegistry.getMapper, which in turn is called mapperProxyFactory.newInstance(sqlSession)to generate a concrete proxy:

image-20220530160637842 image-20220530160637842
/**
 * Mapper Proxy 工厂类
 *
 * @author Lasse Voss
 */
public class MapperProxyFactory<T> {
    
    

    /**
     * Mapper 接口
     */
    private final Class<T> mapperInterface;
    /**
     * 方法与 MapperMethod 的映射
     */
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

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

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

    public Map<Method, MapperMethod> getMethodCache() {
    
    
        return methodCache;
    }

    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
    
    

        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{
    
    mapperInterface}, mapperProxy);
    }

   //MapperProxyFactory类中的newInstance方法
    public T newInstance(SqlSession sqlSession) {
    
    
        // 创建了JDK动态代理的invocationHandler接口的实现类mapperProxy
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        // 调用了重载方法
        return newInstance(mapperProxy);
    }

}

Here, an object T newInstance(SqlSession sqlSession)will be obtained through the method first, and then the proxy object will be generated by calling and then returned.MapperProxyT newInstance(MapperProxy<T> mapperProxy)

When looking at MapperProxythe code, you can see the following:

public class MapperProxy<T> implements InvocationHandler, Serializable {
    
    

    private static final long serialVersionUID = -6424540398559729838L;

    /**
     * SqlSession 对象
     */
    private final SqlSession sqlSession;
    /**
     * Mapper 接口
     */
    private final Class<T> mapperInterface;
    /**
     * 方法与 MapperMethod 的映射
     *
     * 从 {@link MapperProxyFactory#methodCache} 传递过来
     */
    private final Map<Method, MapperMethod> methodCache;

    // 构造,传入了SqlSession,说明每个session中的代理对象的不同的!
    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    
    
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        try {
    
    
            // 如果是 Object 定义的方法,直接调用
            if (Object.class.equals(method.getDeclaringClass())) {
    
    
                return method.invoke(this, args);

            } else if (isDefaultMethod(method)) {
    
    
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
    
    
            throw ExceptionUtil.unwrapThrowable(t);
        }
        // 获得 MapperMethod 对象
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        // 重点在这:MapperMethod最终调用了执行的方法
        return mapperMethod.execute(sqlSession, args);
    }

}

Typically, MapperProxythe class implements InvocationHandlerthe interface and implements invokethe methods of that interface.

In this way, we only need to write Mapper.javathe interface class. When an Mapperinterface is actually executed, it will be forwarded to MapperProxy.invokethe method, and the method will call a series of subsequent sqlSession.cud>executor.execute>prepareStatementmethods to complete the execution and return of SQL.

7.2.2.5 Combined mode

The Composite Pattern is defined as: combining objects into a tree structure to represent the hierarchy of the entire part. The Composite Pattern allows users to treat single objects and combinations of objects uniformly.

Composite mode is actually to organize a group of objects (folders and files) into a tree structure to represent a 'part-whole' hierarchy (the nested structure of directories and subdirectories). The composite mode allows clients to Unify the processing logic (recursive traversal) of single objects (files) and combined objects (folders).

image-20220530160637842

Mybatis supports the powerful functions of dynamic SQL, such as the following SQL:

<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
    UPDATE users
    <trim prefix="SET" prefixOverrides=",">
        <if test="name != null and name != ''">
            name = #{name}
        </if>
        <if test="age != null and age != ''">
            , age = #{age}
        </if>
        <if test="birthday != null and birthday != ''">
            , birthday = #{birthday}
        </if>
    </trim>
    where id = ${id}
</update>

Dynamic elements such as trim and if are used in it, and SQL in different situations can be generated according to conditions;

In DynamicSqlSource.getBoundSqlthe method, the method is called rootSqlNode.apply(context), and applythe method is the interface implemented by all dynamic nodes:

/**
 * SQL Node 接口,每个 XML Node 会解析成对应的 SQL Node 对象
 * @author Clinton Begin
 */
public interface SqlNode {
    
    

    /**
     * 应用当前 SQL Node 节点
     *
     * @param context 上下文
     * @return 当前 SQL Node 节点是否应用成功。
     */
    boolean apply(DynamicContext context);
}

For all nodes that implement this SqlSourceinterface, it is each node of the entire composition pattern tree:

image-20220530160637842

The simplicity of the combination mode is that all child nodes are of the same type and can be executed recursively downward. For example, for TextSqlNode, because it is the bottom leaf node, the corresponding content is directly appended to the SQL statement:

image-20220530160637842

But for IfSqlNode, you need to make a judgment first. If the judgment is passed, the SqlNode of the child element will still be called, that is, contents.applythe method to achieve recursive analysis.

image-20220530160637842

7.2.2.6 Template method pattern

Template method pattern (template method pattern) original definition is: define the framework of the algorithm in the operation, deferring some steps to subclasses. Template methods let subclasses redefine certain steps of an algorithm without changing the structure of the algorithm.

The algorithm in the template method can be understood as business logic in a broad sense, not a specific algorithm. The framework of the algorithm mentioned in the definition is the template, and the method containing the algorithm framework is the template method.

Template classes define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Allows subclasses to redefine some specific steps of an algorithm without changing the structure of the algorithm.

image-20220530160637842

In Mybatis, the SQL execution of sqlSession is entrusted to Executor. Executor contains the following structure:

image-20220530160637842

Among them, BaseExecutor adopts the template method mode, which implements most of the SQL execution logic, and then assigns the following methods to subclasses for customization:

image-20220530160637842
protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

This template method class has concrete implementations of several subclasses, using different strategies:

  • Simple : open an object SimpleExecutorevery time the updateor is executed, and close the object immediately when it is used up. (can be or object)selectStatementStatementStatementPrepareStatement
  • Reuse ReuseExecutor: Execute updateor select, use sql as the key to find Statementthe object, use it if it exists, and create it if it does not exist. After use, the object will not be closed Statement, but placed Mapinside for the next use. (can be Statementor PrepareStatementobject)
  • Batch BatchExecutor: Execute update (no select, JDBC batch processing does not support select), add all sql to batch processing ( addBatch()), wait for unified execution ( executeBatch()), it caches multiple Statement objects, each Statement object is addBatch()completed , waiting to execute executeBatch()batch processing one by one; BatchExecutorit is equivalent to maintaining multiple buckets, and each bucket is filled with a lot of SQL belonging to itself, just like apples are filled with many apples, tomato blue is filled with many tomatoes, and finally, Unified into the warehouse. (can be a Statement or PrepareStatement object)

For example, the doUpdate method is implemented in SimpleExecutor like this:

image-20220530160637842

The template pattern implements code reuse based on inheritance. If the abstract class contains a template method, the template method calls the abstract method to be implemented by the subclass, then this is generally the code implementation of the template pattern. Moreover, in terms of naming, there is generally a one-to-one correspondence between the template method and the abstract method. The abstract method has an extra "do" in front of the template method. For example, in the BaseExecutor class, one of the template methods is called update(), and the corresponding abstract method The method is called doUpdate().

7.2.2.7 Adapter mode

Adapter Pattern: Convert an interface into another interface that the customer wants. The adapter pattern enables those classes with incompatible interfaces to work together. Its alias is Wrapper. The Adapter pattern can be used both as a class-structural pattern and as an object-structural pattern.

image-20220530160637842

In the logging package of Mybatsi, there is a Log interface:

image-20220530160637842

This interface defines the log method used directly by Mybatis, but who will implement the Log interface? Mybatis provides a variety of log framework implementations, all of which match the interface methods defined by the Log interface, and finally realize the adaptation of all external log frameworks to the Mybatis log package:

image-20220530160637842

For example, for Log4jImplthe implementation of , the implementation holds the org.apache.log4j.Loggerinstance of , and then all log methods are entrusted to the instance to implement.

/**
 * Copyright 2009-2017 the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.ibatis.logging.log4j;

import org.apache.ibatis.logging.Log;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
 * @author Eduardo Macarron
 */
public class Log4jImpl implements Log {
    
    

    private static final String FQCN = Log4jImpl.class.getName();

    private final Logger log;

    public Log4jImpl(String clazz) {
    
    
        log = Logger.getLogger(clazz);
    }

    @Override
    public boolean isDebugEnabled() {
    
    
        return log.isDebugEnabled();
    }

    @Override
    public boolean isTraceEnabled() {
    
    
        return log.isTraceEnabled();
    }

    @Override
    public void error(String s, Throwable e) {
    
    
        log.log(FQCN, Level.ERROR, s, e);
    }

    @Override
    public void error(String s) {
    
    
        log.log(FQCN, Level.ERROR, s, null);
    }

    @Override
    public void debug(String s) {
    
    
        log.log(FQCN, Level.DEBUG, s, null);
    }

    @Override
    public void trace(String s) {
    
    
        log.log(FQCN, Level.TRACE, s, null);
    }

    @Override
    public void warn(String s) {
    
    
        log.log(FQCN, Level.WARN, s, null);
    }
}

In the adapter mode, the adapted class object is passed to the adapter constructor, and here is clazz (equivalent to the log name name), so, in terms of code implementation, it is not a standard adapter mode. However, from the perspective of the application scenario, it does play a role in adaptation, which is a typical application scenario of the adapter mode.

7.2.2.8 Decorator pattern

Decorator Pattern: Dynamically add some additional responsibilities (Responsibility) to an object. In terms of adding object functions, the decoration pattern is more flexible than generating subclass implementations. Its alias can also be called a wrapper (Wrapper), which is the same as the alias of the adapter mode, but they are suitable for different occasions. According to different translations, the decoration mode is also called "painter mode", which is an object structure mode.

image-20220530160637842

In mybatis, the function of the cache Cache(org.apache.ibatis.cache.Cache)is defined by the root interface. The whole system adopts the decorator design pattern. The basic functions of data storage and caching PerpetualCache(org.apache.ibatis.cache.impl.PerpetualCache)are realized by the permanent cache, and then a series of decorators are used to PerpetualCachecontrol the cache strategy of the permanent cache. As shown below:

image-20220530160637842

There are 8 standard decorators for decorating PerpetualCache (all in org.apache.ibatis.cache.decorators package):

image-20220530160637842
  1. FifoCache: First-in-first-out algorithm, cache recycling strategy
  2. LoggingCache: output the log information of the cache hit
  3. LruCache: least recently used algorithm, cache recycling strategy
  4. ScheduledCache: Scheduling cache, responsible for regularly emptying the cache
  5. SerializedCache: cache serialization and deserialization storage
  6. SoftCache: Cache management strategy implemented based on soft references
  7. SynchronizedCache: Synchronized cache decorator, used to prevent concurrent access by multiple threads
  8. WeakCache: Cache management strategy implemented based on weak references

The reason why MyBatis uses the decorator mode to implement the caching function is because the decorator mode uses combination instead of inheritance, which is more flexible and can effectively avoid the combination explosion of inheritance relationships.

MyBatis level 1 cache

Cache is data in memory, often from saving the results of database queries. Using the cache, we can avoid frequent interaction with the database, thereby improving the response speed. MyBatis also provides support for the cache, which is divided into a first-level cache and a second-level cache, which can be understood through the following figure:

image-20220530160637842

①. The first level cache is the cache at the SqlSession level. When operating the database, it is necessary to construct a sqlSession object, and there is a data structure (HashMap) in the object for storing cached data. The cache data area (HashMap) between different sqlSessions does not affect each other.

②. The secondary cache is a cache at the mapper level. Multiple SqlSessions operate the sql statement of the same Mapper. Multiple SqlSessions can share the secondary cache. The secondary cache is cross-SqlSession

Level 1 cache is enabled by default

①. We use the same sqlSession to query the User table twice according to the same id, and check their sql statements

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

        // 加载核心配置文件
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");

        // 获取SqlSessionFactory工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);

        // 获取SqlSession会话对象
        SqlSession sqlSession = sqlSessionFactory.openSession();

        User user1 = sqlSession.selectOne("com.mashibing.mapper.UserMapper.findById",1);

        User user2 = sqlSession.selectOne("com.mashibing.mapper.UserMapper.findById",1);
        System.out.println(user1);
        System.out.println(user2);

        System.out.println(user1 == user2);

        // 释放资源
        sqlSession.close();
    }


//打印结果
User{
    
    id=1, username='tom', birthday=Sat Oct 01 17:27:27 CST 2022, sex='0', address='北京'}
User{
    
    id=1, username='tom', birthday=Sat Oct 01 17:27:27 CST 2022, sex='0', address='北京'}
true

② The user table is also queried twice, but an update operation is performed between the two queries.

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

    // 加载核心配置文件
    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");

    // 获取SqlSessionFactory工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);

    // 获取SqlSession会话对象
    SqlSession sqlSession = sqlSessionFactory.openSession();


    User user1 = sqlSession.selectOne("com.mashibing.mapper.UserMapper.findById",1);
    System.out.println(user1);

    sqlSession.delete("com.mashibing.mapper.UserMapper.deleteById",3);
    sqlSession.commit();

    User user2 = sqlSession.selectOne("com.mashibing.mapper.UserMapper.findById",1);

    System.out.println(user2);

    System.out.println(user1 == user2);

    // 释放资源
    sqlSession.close();
}

//结果
User{
    
    id=1, username='tom', birthday=Sat Oct 01 17:27:27 CST 2022, sex='0', address='北京'}
User{
    
    id=1, username='tom', birthday=Sat Oct 01 17:27:27 CST 2022, sex='0', address='北京'}
false

③. Summary

1. Initiate the query of user information with user id 1 for the first time, first check whether there is user information with id 1 in the cache, if not, query user information from the database. Obtain user information and store the user information in the first-level cache.

2. If the intermediate sqlSession performs a commit operation (insert, update, delete), the first-level cache in the SqlSession will be cleared. The purpose of this is to store the latest information in the cache and avoid dirty reading.

3. Initiate the query for the user information with user id 1 for the second time, first find out whether there is user information with id 1 in the cache, if there is in the cache, obtain the user information directly from the cache

1. What is the underlying data structure of the first-level cache?

As I said before 不同SqlSession的一级缓存互不影响, so I started with the SqlSession class

image-20220530160637842

As you can see, org.apache.ibatis.session.SqlSessionthere is a method related to caching - clearCache()the method of refreshing the cache, click in and find its implementation classDefaultSqlSession

  @Override
  public void clearCache() {
    
    
    executor.clearLocalCache();
  }

Click in again executor.clearLocalCache(), click in again and find its implementation class BaseExecutor,

  @Override
  public void clearLocalCache() {
    
    
    if (!closed) {
    
    
      localCache.clear();
      localOutputParameterCache.clear();
    }
  

Enter localCache.clear()method. entered org.apache.ibatis.cache.impl.PerpetualCachethe class

package org.apache.ibatis.cache.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;
/**
 * @author Clinton Begin
 */
public class PerpetualCache implements Cache {
    
    
  private final String id;

  private Map<Object, Object> cache = new HashMap<Object, Object>();

  public PerpetualCache(String id) {
    
    
    this.id = id;
  }

  //省略部分...
  @Override
  public void clear() {
    
    
    cache.clear();
  }
  //省略部分...
}

We saw that PerpetualCachethere is an attribute in the class , obviously it is a HashMap, the method private Map<Object, Object> cache = new HashMap<Object, Object>()we call is actually the clear method of the Map called.clear()

image-20220530160637842

get conclusion:

The data structure of the first level cache is indeed HashMap

image-20220530160637842
The execution flow of the first level cache

We entered and org.apache.ibatis.executor.Executorsaw
a method CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql), as the name implies, it is a method of creating CacheKey
to find its implementation class and methodorg.apache.ibatis.executor.BaseExecuto.createCacheKey

image-20220530160637842

Let's analyze the code that creates the CacheKey:

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    
    
    if (closed) {
    
    
      throw new ExecutorException("Executor was closed.");
    }
    //初始化CacheKey
    CacheKey cacheKey = new CacheKey();
    //存入statementId
    cacheKey.update(ms.getId());
    //分别存入分页需要的Offset和Limit
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    //把从BoundSql中封装的sql取出并存入到cacheKey对象中
    cacheKey.update(boundSql.getSql());
    //下面这一块就是封装参数
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();

    for (ParameterMapping parameterMapping : parameterMappings) {
    
    
      if (parameterMapping.getMode() != ParameterMode.OUT) {
    
    
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
    
    
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
    
    
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
    
    
          value = parameterObject;
        } else {
    
    
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    //从configuration对象中(也就是载入配置文件后存放的对象)把EnvironmentId存入
        /**
     *     <environments default="development">
     *         <environment id="development"> //就是这个id
     *             <!--当前事务交由JDBC进行管理-->
     *             <transactionManager type="JDBC"></transactionManager>
     *             <!--当前使用mybatis提供的连接池-->
     *             <dataSource type="POOLED">
     *                 <property name="driver" value="${jdbc.driver}"/>
     *                 <property name="url" value="${jdbc.url}"/>
     *                 <property name="username" value="${jdbc.username}"/>
     *                 <property name="password" value="${jdbc.password}"/>
     *             </dataSource>
     *         </environment>
     *     </environments>
     */
    if (configuration.getEnvironment() != null) {
    
    
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    //返回
    return cacheKey;
  }

Let's click in again cacheKey.update()to see how


public class CacheKey implements Cloneable, Serializable {
    
    
  private static final long serialVersionUID = 1146682552656046210L;
  public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
  private static final int DEFAULT_MULTIPLYER = 37;
  private static final int DEFAULT_HASHCODE = 17;

  private final int multiplier;
  private int hashcode;
  private long checksum;
  private int count;
  //值存入的地方
  private transient List<Object> updateList;
  //省略部分方法......
  //省略部分方法......
  public void update(Object object) {
    
    
    int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); 
    count++;
    checksum += baseHashCode;
    baseHashCode *= count;
    hashcode = multiplier * hashcode + baseHashCode;
    //看到把值传入到了一个list中
    updateList.add(object);
  }
 
  //省略部分方法......
}

We know how those data are stored in the CacheKey object. Next we return createCacheKey()the method.

image-20220530160637842

We can see that the query() method is called after the CacheKey is created, and we click in again:

image-20220530160637842

How to find Key in the first-level cache before executing SQL, then sql will be executed, let’s see what will be done before and after executing sql, enterlist = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    
    
    List<E> list;
    //1. 把key存入缓存,value放一个占位符
	localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
    
    
      //2. 与数据库交互
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
    
    
      //3. 如果第2步出了什么异常,把第1步存入的key删除
      localCache.removeObject(key);
    }
      //4. 把结果存入缓存
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
    
    
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

Summarize

  1. The data structure of the first-level cache is one HashMap<Object,Object>, its value is the query result, its key is CacheKey, CacheKeythere is a list attribute in it, statementId,params,rowbounds,sqland other parameters are stored in this list
  2. Create first CacheKey, it will first check CacheKeywhether there is any in the query cache, if yes, process the parameters in the cache, if not, execute sql, execute sql after executing sql, and store the result in the cache

7.2.2.9 Iterator pattern

Iterator (Iterator) mode, also known as cursor (Cursor) mode. The definition given by GOF is: Provide a method to access each element in a container object without exposing the internal details of the object.

image-20220530160637842

In a software system, container objects have two responsibilities: one is to store data, but to traverse data. From the perspective of dependency, the former is the basic responsibility of aggregate objects. The latter is changeable and separable. Therefore The behavior of traversing data can be extracted from the container, encapsulated into an iterator object, and the iterator provides the behavior of traversing data, which will simplify the design of aggregated objects and be more in line with the principle of single responsibility.

Java Iteratoris the interface of the iterator mode. As long as the interface is implemented, it is equivalent to applying the iterator mode:

image-20220530160637842

For example, Mybatis PropertyTokenizeris a heavyweight class in the property package, which is frequently referenced by other classes in the reflection package. This class implements the interface, and this function in the interface Iteratoris often used when using it .IteratorhasNext

/**
 * 属性分词器
 *
 * @author Clinton Begin
 */
public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
    
    

    /**
     * 当前字符串
     */
    private String name;
    /**
     * 索引的 {@link #name} ,因为 {@link #name} 如果存在 {@link #index} 会被更改
     */
    private final String indexedName;
    /**
     * 编号。
     *
     * 对于数组 name[0] ,则 index = 0
     * 对于 Map map[key] ,则 index = key
     */
    private String index;
    /**
     * 剩余字符串
     */
    private final String children;

    public PropertyTokenizer(String fullname) {
    
    
        // 初始化 name、children 字符串,使用 . 作为分隔
        int delim = fullname.indexOf('.');
        if (delim > -1) {
    
    
            name = fullname.substring(0, delim);
            children = fullname.substring(delim + 1);
        } else {
    
    
            name = fullname;
            children = null;
        }
        // 记录当前 name
        indexedName = name;
        // 若存在 [ ,则获得 index ,并修改 name 。
        delim = name.indexOf('[');
        if (delim > -1) {
    
    
            index = name.substring(delim + 1, name.length() - 1);
            name = name.substring(0, delim);
        }
    }

    public String getName() {
    
    
        return name;
    }

    public String getIndex() {
    
    
        return index;
    }

    public String getIndexedName() {
    
    
        return indexedName;
    }

    public String getChildren() {
    
    
        return children;
    }

    @Override
    public boolean hasNext() {
    
    
        return children != null;
    }

    @Override
    public PropertyTokenizer next() {
    
    
        return new PropertyTokenizer(children);
    }

    @Override
    public void remove() {
    
    
        throw new UnsupportedOperationException("Remove is not supported, as it has no meaning in the context of properties.");
    }

}

It can be seen that this class passes a string to the constructor, and then provides an iterator method to traverse the parsed substring, which is a very commonly used method class.

In fact, the PropertyTokenizer class is not a standard iterator class. It couples the configuration parsing, parsed elements, and iterators, which should have been put into three classes, into one class, so it seems a little difficult to understand. However, the advantage of doing this is that it can do lazy parsing. We don't need to parse the entire configuration into multiple PropertyTokenizer objects in advance. Part of the configuration will only be parsed when we call the next() function.

Guess you like

Origin blog.csdn.net/weixin_47068446/article/details/128447230