3
头图

Author: Xiao Fu Ge Blog: https://bugstack.cn

Precipitate, share, grow, and let yourself and others gain something! 😄

I. Introduction

如何面对复杂系统的设计?

We can call large frameworks such as Spring, Mybatis, Dubbo or some core projects within the company as complex systems. This kind of project is not just a toy project in the hands of beginners. There is no so-called CRUD. More often, it is the structure design of the system layer and the realization of the aggregation logic function, and then realize and realize through layer-by-layer conversion. transfer.

This will be very uncomfortable for many small code farmers who have just started. They don't know where to start, but they want to eat a fat man in one bite. In fact, this is unrealistic, because there are too many contents in the framework of these complex systems that you have not yet understood and familiar with.

In fact, for solving such complex project problems, the core is to narrow down the main problem points, and the specific means include: divide and conquer, abstraction and knowledge. Use the relevant knowledge of design patterns and design principles to reasonably divide the problem space into several sub-problems. The smaller the problem, the easier it is to understand and deal with. Just like you can make a lot of content into a single independent case, you end up doing aggregate usage.

2. Goals

In the previous chapter, we have a preliminary understanding of how to generate a corresponding mapper proxy for an interface class, and complete some user calls to interface methods in the proxy. Although we have seen a core logic processing method, there are still some slash-and-burn methods in use, including: need to code to tell MapperProxyFactory which interface to proxy, and write a fake SqlSession to handle the return result when the interface is actually called.

Combining these two problems, we will provide registration machine processing for the registration of mappers in this chapter, so that users can complete scanning and registration by providing a package path when using them. At the same time, SqlSession needs to be standardized, so that it can wrap our mapper proxy and method calls, and establish a life cycle model structure to facilitate subsequent content additions.

3. Design

Since we want to associate the DAO interface for database operations under the entire project package with the Mapper mapper, we need to wrap a registrar class that can scan the package path to complete the mapping.

Of course, we have to improve the simplified SqlSession in the previous chapter. The SqlSession defines the database processing interface and the operation of obtaining the Mapper object, and gives it to the mapper proxy class for use. This part is the improvement of the content of the previous chapter

After you have SqlSession, you can understand it as a functional service. After you have a functional service, you need to provide a factory for this functional service to provide such services in a unified manner. For example, a very common operation in Mybatis is to open a SqlSession. The entire design can be shown in Figure 3-1

图 3-1 映射器的注册和使用

  • Aiming to provide the mapper proxy class for the packaging interface, complete the mapper registration machine MapperRegistry , automatically scan the interface under the package and store all the proxy classes mapped by each interface class into the HashMap cache of the mapper proxy .
  • SqlSession and SqlSessionFactory are encapsulated by standard definitions and external services provided in the last layer of the registered mapper proxy, which is convenient for users to use. We regard the user as the user . After such encapsulation, it will be more convenient for us to continue to expand the functions of the framework in the future. We also hope that you can think about such a design structure in the process of learning. It can help you solve some problems. Domain service packaging during business function development.

4. Realization

1. Engineering structure

 mybatis-step-02
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.mybatis
    │           ├── binding
    │           │   ├── MapperProxy.java
    │           │   ├── MapperProxyFactory.java
    │           │   └── MapperRegistry.java
    │           └── session
    │               ├── defaults
    │               │   ├── DefaultSqlSession.java
    │               │   └── DefaultSqlSessionFactory.java
    │               ├── SqlSession.java
    │               └── SqlSessionFactory.java
    └── test
        └── java
            └── cn.bugstack.mybatis.test.dao
                ├── dao
                │   ├── ISchoolDao.java
                │   └── IUserDao.java
                └── ApiTest.java

Project source code: https://t.zsxq.com/bmqNFQ7

The mapper standard defines the implementation relationship, as shown in Figure 3-2

图 3-2 映射器标准定义实现关系

  • MapperRegistry provides package path scanning and mapper proxy class registration machine services to complete proxy class registration processing of interface objects.
  • SqlSession, DefaultSqlSession are used to define operations such as executing SQL standards, getting mappers, and managing transactions in the future. Basically, the API interfaces we usually use Mybatis are also used from the methods defined in this interface class.
  • SqlSessionFactory is a simple factory pattern for providing SqlSession services, shielding creation details and delaying the creation process.

2. Mapper registration machine

See the source code for details : cn.bugstack.mybatis.binding.MapperRegistry

 public class MapperRegistry {

    /**
     * 将已添加的映射器代理加入到 HashMap
     */
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new RuntimeException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new RuntimeException("Error getting mapper instance. Cause: " + e, e);
        }
    }

    public <T> void addMapper(Class<T> type) {
        /* Mapper 必须是接口才会注册 */
        if (type.isInterface()) {
            if (hasMapper(type)) {
                // 如果重复添加了,报错
                throw new RuntimeException("Type " + type + " is already known to the MapperRegistry.");
            }
            // 注册映射器代理工厂
            knownMappers.put(type, new MapperProxyFactory<>(type));
        }
    }

    public void addMappers(String packageName) {
        Set<Class<?>> mapperSet = ClassScanner.scanPackage(packageName);
        for (Class<?> mapperClass : mapperSet) {
            addMapper(mapperClass);
        }
    }

}
  • The core of the MapperRegistry mapper registration class is to provide ClassScanner.scanPackage scan the package path, call the addMapper method, create the MapperProxyFactory mapper proxy class for the interface class, and write into the HashMap cache of knownMappers.
  • In addition, this class also provides the corresponding getMapper method to obtain the mapper proxy class. In fact, this step wraps the process of manual instantiation in the previous chapter, which is more convenient to use when obtaining Mapper in DefaultSqlSession.

3. SqlSession standard definition and implementation

See the source code : cn.bugstack.mybatis.session.SqlSession

 public interface SqlSession {

    /**
     * Retrieve a single row mapped from the statement key
     * 根据指定的SqlID获取一条记录的封装对象
     *
     * @param <T>       the returned object type 封装之后的对象类型
     * @param statement sqlID
     * @return Mapped object 封装之后的对象
     */
    <T> T selectOne(String statement);

    /**
     * Retrieve a single row mapped from the statement key and parameter.
     * 根据指定的SqlID获取一条记录的封装对象,只不过这个方法容许我们可以给sql传递一些参数
     * 一般在实际使用中,这个参数传递的是pojo,或者Map或者ImmutableMap
     *
     * @param <T>       the returned object type
     * @param statement Unique identifier matching the statement to use.
     * @param parameter A parameter object to pass to the statement.
     * @return Mapped object
     */
    <T> T selectOne(String statement, Object parameter);

    /**
     * Retrieves a mapper.
     * 得到映射器,这个巧妙的使用了泛型,使得类型安全
     *
     * @param <T>  the mapper type
     * @param type Mapper interface class
     * @return a mapper bound to this SqlSession
     */
    <T> T getMapper(Class<T> type);

}
  • A standard interface for executing SQL, obtaining mapper objects, and subsequently managing transaction operations is defined in SqlSession.
  • At present, only selectOne is provided for database operations in this interface, and other methods will be defined in the future.

See the source code for details : cn.bugstack.mybatis.session.defaults

 public class DefaultSqlSession implements SqlSession {

    /**
     * 映射器注册机
     */
    private MapperRegistry mapperRegistry;

    @Override
    public <T> T selectOne(String statement, Object parameter) {
        return (T) ("你被代理了!" + "方法:" + statement + " 入参:" + parameter);
    }

    @Override
    public <T> T getMapper(Class<T> type) {
        return mapperRegistry.getMapper(type, this);
    }

}
  • Implement the SqlSession interface through the DefaultSqlSession implementation class.
  • The mapper object obtained in the getMapper method is obtained through the MapperRegistry class, and this part will be replaced by the configuration class later.
  • In selectOne, it is a simple content return, which has not yet been associated with the database. This part is gradually realized in our progressive development process.

4. SqlSessionFactory factory definition and implementation

See the source code : cn.bugstack.mybatis.session.SqlSessionFactory

 public interface SqlSessionFactory {

    /**
     * 打开一个 session
     * @return SqlSession
     */
   SqlSession openSession();

}
  • This is actually the definition of a simple factory, which provides the ability to implement interface classes in the factory, that is, the ability to open SqlSession provided by the SqlSessionFactory factory.

See the source code : cn.bugstack.mybatis.session.defaults.DefaultSqlSessionFactory

 public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private final MapperRegistry mapperRegistry;

    public DefaultSqlSessionFactory(MapperRegistry mapperRegistry) {
        this.mapperRegistry = mapperRegistry;
    }

    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(mapperRegistry);
    }

}
  • The default simple factory implementation handles the creation of DefaultSqlSession and passing mapperRegistry when SqlSession is opened, so that the mapper object of each proxy class can be obtained when using SqlSession.

5. Test

1. Prepare in advance

Under the same package path, provide more than 2 Dao interfaces:

 public interface ISchoolDao {

    String querySchoolName(String uId);

}

public interface IUserDao {

    String queryUserName(String uId);

    Integer queryUserAge(String uId);

}

2. Unit testing

 @Test
public void test_MapperProxyFactory() {
    // 1. 注册 Mapper
    MapperRegistry registry = new MapperRegistry();
    registry.addMappers("cn.bugstack.mybatis.test.dao");
    
    // 2. 从 SqlSession 工厂获取 Session
    SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(registry);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    
    // 3. 获取映射器对象
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);
    
    // 4. 测试验证
    String res = userDao.queryUserName("10001");
    logger.info("测试结果:{}", res);
}
  • In the unit test, the registration machine scans the package path to register the mapper proxy object, and passes the registration machine to the SqlSessionFactory factory, thus completing a linking process.
  • After that, the implementation class of the corresponding DAO type is obtained through SqlSession, and the method is verified.

Test Results

 22:43:23.254 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试结果:你被代理了!方法:queryUserName 入参:[Ljava.lang.Object;@50cbc42f

Process finished with exit code 0
  • As you can see from the test, we have completed the registration and use process of the proxy class in a handwritten ORM framework with the shadow of Mybatis.

6. Summary

  • First of all, we must understand the encapsulation of the specific functional structure by the factory pattern from the design structure, shield the details of the process, limit the context relationship, and reduce the coupling of external use.
  • From this process, readers and partners can also find that the factory implementation class of SqlSessionFactory wraps the standard definition implementation class of SqlSession, and SqlSession completes the registration and use of mapper objects.
  • In this chapter, we should pay attention to several important knowledge points, including: mappers, proxy classes, registration machines, interface standards, factory patterns, and contexts. These engineering development skills are very important parts in the process of handwriting Mybatis, and understanding and familiarity can better use them in your own business.

小傅哥
4.8k 声望28.4k 粉丝

CodeGuide | 程序员编码指南 - 原创文章、案例源码、资料书籍、简历模版等下载。