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
- 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
- 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 theaddMapper
method, create theMapperProxyFactory
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.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。