一、设计
思考:1、映射器的注册提供注册机处理,满足用户可以在使用的时候提供一个包的路径即可完成扫描和注册,2、对 SqlSession 进行规范化处理,让它可以把我们的映射器代理和方法调用进行包装
sqlsessionFactory:sqlSession的创建工厂,通过openSession()创建出sqlsession
sqlsession:规范化处理,其中selectOne是数据库的查询操作,getMapper获取对应的映射器
MapperRegister:扫描包路径下的所有接口,并对其进行代理,注册到mapperProxyMap上
MapperProxyFactory:上一步有说明,映射器MapperProxy工厂,newInstance()方法创建映射器
MapperProxy:代理对象,对数据库进行操作
二、实现
MapperRegistry 提供包路径的扫描和映射器代理类注册机服务,完成接口对象的代理类注册处理。
SqlSession、DefaultSqlSession 用于定义执行 SQL 标准、获取映射器以及将来管理事务等方面的操作。基本我们平常使用 Mybatis 的 API 接口也都是从这个接口类定义的方法进行使用的。
SqlSessionFactory 是一个简单工厂模式,用于提供 SqlSession 服务,屏蔽创建细节,延迟创建过程。
2.1代码结构
mybatis-step-02
└── src
├── main
│ └── java
│ └── com.qf.mybatis
│ ├── bind
│ │ ├── MapperProxy.java
│ │ ├── MapperProxyFactory.java
│ │ └── MapperRegistry.java
│ └── session
│ ├── defaults
│ │ ├── DefaultSqlSession.java
│ │ └── DefaultSqlSessionFactory.java
│ ├── SqlSession.java
│ └── SqlSessionFactory.java
└── test
└── java
└──com.qf.mybatis
├── dao
│ ├── ISchoolDao.java
│ └── IUserDao.java
└── ApiTest.java
2.2映射器注册机类
package com.qf.mybatis.bind;
import cn.hutool.core.lang.ClassScanner;
import com.qf.mybatis.session.SqlSession;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 扫描指定路径下面的所有接口,并以map的形式储存(接口,实现的mapperProxyFactory)
*/
public class MapperRegister {
/**
* 将已添加的映射器代理加入到 HashMap
*/
private Map<Class<?>,MapperProxyFactory<?>> mapperProxyMap=new HashMap<Class<?>, MapperProxyFactory<?>>();
/**
* 扫描指定路径下所有的包(添加mapper)
*/
public void addMapper(String packageName){
//注册所有的mapper到hashMap
Set<Class<?>> classes = ClassScanner.scanPackage(packageName);
for (Class<?> aClass : classes) {
addMapper(aClass);
}
}
/**
* 获取代理对象(映射器)缓存设计
* @param
* @param <T>
*/
public <T> T getMapper(Class<T> type, SqlSession sqlSession){
MapperProxyFactory<?> tMapperProxyFactory = mapperProxyMap.get(type);
if (tMapperProxyFactory==null){
throw new RuntimeException("Type"+type+"is not yet register to the known map");
}
try{
return (T) tMapperProxyFactory.newInstance(sqlSession);
}catch (Exception e){
throw new RuntimeException("Error in getting mapper instance:"+e);
}
}
public <T> void addMapper(Class<T> aClass){
//判断是否为接口
if (aClass.isInterface()){
if (!hasRegister(aClass)){
throw new RuntimeException("Type"+aClass+"is already register to know map!");
}
mapperProxyMap.put(aClass,new MapperProxyFactory(aClass));
}
}
public boolean hasRegister(Class<?> aClass){
return mapperProxyMap.get(aClass)==null;
}
}
MapperRegistry 映射器注册类的核心主要在于提供了 ClassScanner.scanPackage 扫描包路径,调用 addMapper 方法,给接口类创建 MapperProxyFactory 映射器代理类,并写入到 knownMappers 的 HashMap 缓存中。
另外就是这个类也提供了对应的 getMapper 获取映射器代理类的方法,其实这步就包装了我们上一章节手动操作实例化的过程,更加方便在 DefaultSqlSession 中获取 Mapper 时进行使用
2.3SqlSession的标准定义
package com.qf.mybatis.session;
public interface SqlSession {
/**
*先完善查找一个selectone,后续会有更多
*/
<T> T selectOne(String statement);
/**
* 据指定的SqlID获取一条记录的封装对象,只不过这个方法容许我们可以给sql传递一些参数
* * 一般在实际使用中,这个参数传递的是pojo,或者Map或者ImmutableMap
*/
<T> T selectOne(String statement,Object param);
/**
* 获取映射器,代理的实例对象
*/
<T> T getMapper(Class<T> type);
}
目前只定义了selectOne查询,后续会新增
getMapper获取已注册在MapperRegistry 的代理对象
2.3SqlSession标准实现
package com.qf.mybatis.session.bind;
import com.qf.mybatis.bind.MapperRegister;
import com.qf.mybatis.session.SqlSession;
public class DefaultSqlSession implements SqlSession {
private MapperRegister mapperRegister;
public DefaultSqlSession(MapperRegister mapperRegister) {
this.mapperRegister=mapperRegister;
}
/**
* 目前是返回方法
* @param statement
* @param <T>
* @return
*/
public <T> T selectOne(String statement) {
return (T) ("你被代理了"+statement);
}
public <T> T selectOne(String statement, Object param) {
return (T) ("你被代理了"+"方法:"+statement+"入参:"+param);
}
public <T> T getMapper(Class<T> type) {
return mapperRegister.getMapper(type,this);
}
}
MapperRegistry 获取代理对象
2.4SqlSessionFactory 工厂定义
package com.qf.mybatis.session;
public interface SqlSessionFactory {
/**
* 开启session,获取session对象
*/
SqlSession openSession();
}
简单工厂的接口,定义了开启sqlSession的能力
package com.qf.mybatis.session.bind;
import com.qf.mybatis.bind.MapperRegister;
import com.qf.mybatis.session.SqlSession;
import com.qf.mybatis.session.SqlSessionFactory;
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private MapperRegister mapperRegister;
public DefaultSqlSessionFactory(MapperRegister mapperRegister) {
this.mapperRegister=mapperRegister;
}
public SqlSession openSession() {
return new DefaultSqlSession(mapperRegister);
}
}
简单工厂实现,处理开启 SqlSession 时,对 DefaultSqlSession 的创建以及传递 mapperRegistry,这样就可以在使用 SqlSession 时获取每个代理类的映射器对象了
3.、测试
定义的接口
package com.qf.mybatis.dao;
public interface UserDao {
String queryName(String uid);
String queryUser(String uid);
}
package com.qf.mybatis.dao;
import java.math.BigDecimal;
public interface ProductDao {
BigDecimal calPrice();
String productName();
}
ApiTest
package com.qf.mybatis.test;
import com.qf.mybatis.bind.MapperRegister;
import com.qf.mybatis.dao.UserDao;
import com.qf.mybatis.session.SqlSession;
import com.qf.mybatis.session.SqlSessionFactory;
import com.qf.mybatis.session.bind.DefaultSqlSessionFactory;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ApiTest {
private Logger logger=LoggerFactory.getLogger(ApiTest.class);
@Test
public void sqlSessionTest(){
//1、注册指定包路径下的dao接口,注册
MapperRegister mapperRegister=new MapperRegister();
mapperRegister.addMapper("com.qf.mybatis.dao");
//2、从sqlSession工厂里面获取sqlSession
SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(mapperRegister);
SqlSession sqlSession = sqlSessionFactory.openSession();
//3、获取映射器
UserDao userDao = sqlSession.getMapper(UserDao.class);
//4、验证结果
String queryName = userDao.queryName("111L");
logger.info("结果:{}",queryName);
}
}
结果:
"C:\Program Files\Java\jdk1.8.0_301\bin\java.exe" -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\idea2020\IntelliJ IDEA 2020.2.4\lib\idea_rt.jar=27634:D:\idea2020\IntelliJ IDEA 2020.2.4\bin" -Dfile.encoding=UTF-8 -classpath "D:\idea2020\IntelliJ IDEA 2020.2.4\lib\idea_rt.jar;D:\idea2020\IntelliJ IDEA 2020.2.4\plugins\junit\lib\junit5-rt.jar;D:\idea2020\IntelliJ IDEA 2020.2.4\plugins\junit\lib\junit-rt.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_301\jre\lib\rt.jar;D:\zhoutao\handwrite-mybatis\step-02-mapperregistery\target\test-classes;D:\zhoutao\handwrite-mybatis\step-02-mapperregistery\target\classes;C:\Users\Administrator\.m2\repository\com\alibaba\fastjson\1.2.75\fastjson-1.2.75.jar;C:\Users\Administrator\.m2\repository\org\dom4j\dom4j\2.1.3\dom4j-2.1.3.jar;C:\Users\Administrator\.m2\repository\junit\junit\4.7\junit-4.7.jar;C:\Users\Administrator\.m2\repository\cn\hutool\hutool-all\5.5.0\hutool-all-5.5.0.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\1.7.5\slf4j-api-1.7.5.jar;C:\Users\Administrator\.m2\repository\org\slf4j\jcl-over-slf4j\1.7.5\jcl-over-slf4j-1.7.5.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-classic\1.0.9\logback-classic-1.0.9.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-core\1.0.9\logback-core-1.0.9.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 com.qf.mybatis.test.ApiTest,sqlSessionTest
09:40:22.578 [main] INFO com.qf.mybatis.test.ApiTest - 结果:你被代理了方法:queryName入参:[Ljava.lang.Object;@5fe5c6f
Process finished with exit code 0
从结果来看,我们自定义的接口已经被MapperRegister 扫描到,完成了简单注册和代理功能
4、总结
SqlSessionFactory 的工厂实现类包装了 SqlSession 的标准定义实现类,并由 SqlSession 完成对映射器对象的注册和使用。
本章学习要注意几个重要的知识点,包括:映射器、代理类、注册机、接口标准、工厂模式、上下文。这些工程开发的技巧都是在手写 Mybatis 的过程中非常重要的部分,了解和熟悉才能更好的在自己的业务中进行使用。