第一章 初始化 SqlSessionFactory 类
前言
1,创建一个Spring项目
2,引入Mybatis并且配置好Mysql地址
3,创建一个MyBatisTest类
1.0 创建测试类
@Slf4j
@SpringBootTest
public class MyBatisTest {
@Autowired
private MyBatisService myBatisService;
@Test
void init() {
MyBatis myBatis = myBatisService.getOne(1L);
log.info("MyBatis::=>{}", JSON.toJSON(myBatis));
}
public SqlSessionFactory getSqlSessionFactory() throws IOException {
//注意此处路径不要写错
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
/**
* 1、根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
* 2、sql映射文件;配置了每一个sql,以及sql的封装规则等。
* 3、将sql映射文件注册在全局配置文件中
* 4、写代码:
* 1)、根据全局配置文件得到SqlSessionFactory;
* 2)、使用sqlSession工厂,获取到sqlSession对象使用他来执行增删改查
* 一个sqlSession就是代表和数据库的一次会话,用完关闭
* 3)、使用sql的唯一标志来告诉MyBatis执行哪个sql。sql都是保存在sql映射文件中的。
* @throws IOException
*/
@Test
void test() throws IOException {
//1、获取SqlSessionFactory实例
// XMLConfigBuilder装配mybatis-config.xml到Configuration中的environment对象里去
// XMLMapperBuilder装配xxMapper.xml文件的select|insert|update|delete到Configuration中的mappedStatements对象里去
// XMLMapperBuilder装配xxMapper.xml文件的resultMap到Configuration中的resultMaps对象里去
// SqlSessionFactory 取默认的实现类 DefaultSqlSessionFactory
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//2、打开一个会话
// try (SqlSession openSession = sqlSessionFactory.openSession()) 会自动关闭调openSession
// 这里SqlSession取的也是默认的:DefaultSqlSession
try (SqlSession openSession = sqlSessionFactory.openSession()) {
// 3、获取接口的实现类对象,会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
// 获取到的代理对象中好像并没有对方法做什么操作,只是原味的方法
MyBatisMapper mapper = openSession.getMapper(MyBatisMapper.class);
//org.apache.ibatis.binding.MapperProxy@29ebbdf4 获取@Mapper的代理对象,使用代理对象做查询
// 疑问,代理对象调用方法时,又谁代理的呢?
MyBatis myBatis = mapper.getOne(1L);
log.info("MyBatis::=>{}", JSON.toJSON(myBatis));
}
//4、使用完毕后关闭会话
}
}
SqlSessionFactory 的主要作用是创建SqlSession,同时初始化mybatis所需要的各种基础配置
1.1 加载配置类并初始化 SqlSessionFactory
String resource="mybatis-config.xml";
InputStream inputStream=Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory=SqlSessionFactoryBuilder().build(inputStream);
build 方法做的事
/**
* environment = null
* properties = null
*/
public SqlSessionFactory build(InputStream inputStream,String environment,Properties properties){
try{
// 这里加载配置类
XMLConfigBuilder parser=new XMLConfigBuilder(inputStream,environment,properties);
// parser 作用是个解析器
return build(parser.parse());
}
}
new XMLConfigBuilder(inputStream, environment, properties) 做的事
private XMLConfigBuilder(XPathParser parser,String environment,Properties props){
// 初始化配置项
super(new Configuration());
// mybatis放在threadlocal中的异常处理类
ErrorContext.instance().resource("SQL Mapper Configuration");
// 此时props还是null
this.configuration.setVariables(props);
// 标记是否解析过
this.parsed=false;
// 设置环境 此时也为null
this.environment=environment;
// 设置解析器 (到此为止已经初始化成功了一个XMLConfigBuilder)
this.parser=parser;
}
super(new Configuration()); 做的事
/**
* 这里的 new Configuration() 是实例化了一个mybatis的配置类,里面包含了很多mybatis需要使用的基础信息
* 比如下面的:
* configuration.getTypeAliasRegistry() 注册类型别名
* 就是将 Configuration中的TypeAliasRegistry属性中的map赋值,map里包含了各种各样的java类型
* configuration.getTypeHandlerRegistry() 注册处理器
* 处理器是mybatis针对上述每种类型都设置了对应的处理机制,每个类型都有其处理器
*/
public BaseBuilder(Configuration configuration){
this.configuration=configuration;
this.typeAliasRegistry=this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry=this.configuration.getTypeHandlerRegistry();
}
build(parser.parse()) 做的事
// 会去解析配置,返回一个Configuration对象
parser.parse();
public Configuration parse(){
// 上面初始化方法已经设置了为 parsed = false
// 如果已经解析过则抛出重复解析异常
if(parsed){
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed=true;
/**
* parser.evalNode("/configuration")
* 这里解析出来的是 mybatis-config.xml中的所有配置的标签和值:
* <configuration>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/path2java?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/AdvertiseSiteMapper.xml"/>
</mappers>
</configuration>
进入evalNode()方法中后,会有一个document对象,是在创建XMLConfigBuilder时初始化的
*/
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root){
try{
// 读取 mybatis-config.xml中的environments标签,并将其配置放入环境信息 environment 这个变量里
environmentsElement(root.evalNode("environments"));
// 读取 mappers 标签的内容,将xml文件加入配置
mapperElement(root.evalNode("mappers"));
}catch(Exception e){
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+e,e);
}
}
xxMapper.xml 对应的解析类是 XMLMapperBuilder
mapperElement 解析各种mapper.xml配置 XMLMapperBuilder 类是专门解析 **mapper.xml文件的
if(resource!=null&&url==null&&mapperClass==null){
ErrorContext.instance().resource(resource);
InputStream inputStream=Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser=new XMLMapperBuilder(inputStream,configuration,resource,configuration.getSqlFragments());
mapperParser.parse();
}
mapperParser.parse();
public void parse(){
// 判断此xml是否被加载过
if(!configuration.isResourceLoaded(resource)){
// 加载mapper文件
configurationElement(parser.evalNode("/mapper"));
// 放入已加载列表
configuration.addLoadedResource(resource);
// 绑定带有@Mapper的接口 Configuration类中有个方法,叫hasMapper,是用来校验@mapper有没有被解析过的
// hasMapper 会调用Configuration类中mapperRegistry属性的hasMapper方法,而这个方法内部,是去请求
// MapperRegistry 对象中的knownMappers属性,knownMappers属性中存放的是当前所有被映射过的mapper
// MapperRegistry::hasMapper() ->
// public <T> boolean hasMapper(Class<T> type) {
// return knownMappers.containsKey(type);
// }
// 以加载的类名,也会放到Configuration类中loadedResources属性里,这里是一个Set<String>
// 值是@mapper的全路径名:cn.kgm.mapper.MyBatisMapper
// 这些都做完了,会将@mapper对象放入Configuration->MapperRegistry->knowsMappers属性里并且会包装一层,放入MapperProxyFactory对象里
// 最后根据@mapper.class获取实体类的时候也是从MapperProxyFactory中实例化的
// knownMappers.put(type, new MapperProxyFactory<T>(type));
// class MapperProxyFactory{
// private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
// }
bindMapperForNamespace();
}
}
configurationElement(parser.evalNode("/mapper"));
private void configurationElement(XNode context){
try{
// 获取@mapper的地址
String namespace=context.getStringAttribute("namespace");
if(namespace==null||namespace.equals("")){
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 构建器助手设置@mapper的路径
builderAssistant.setCurrentNamespace(namespace);
// 获取缓存
cacheRefElement(context.evalNode("cache-ref"));
// 获取缓存配置
cacheElement(context.evalNode("cache"));
// 获取 <paramentMap>
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 获取资源映射关系:
// <resultMap id="BaseResultMap" type="cn.kgm.entity.MyBatis">
// <id column="id" property="id"/>
// <id column="content" property="content"/>
// <result column="create_time" property="createTime"/>
// </resultMap>
// 经过resultMapElements()方法解析过的resultMap配置,会先放入XMLMapperBuilder的助手类
// MapperBuilderAssistant中,然后再由MapperBuilderAssistant中::addResultMap()方法,将
// 解析出来的List<ResultMapping>对象放入Configuration类中的resultMaps属性中
//protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
// StrictMap MyBatis的严格map,获取和插入时不允许为null值
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 获取预置文件:
// <sql id="BaseColumnList">
// id,content,create_time
// </sql>
sqlElement(context.evalNodes("/mapper/sql"));
// 获取四种标签并解析,解析完成后形成MappedStatement对象,放到Configuration对象中的MappedStatement对象中
// protected final Map<String, MappedStatement> mappedStatements =
// new StrictMap<MappedStatement>("Mapped Statements collection");
// public void addMappedStatement(MappedStatement ms) {
// mappedStatements.put(ms.getId(), ms);
// }
// key是@mapper接口中方法的全路径名加方法名:cn.kgm.mapper.MyBatisMapper.getOne,
// value 是MappedStatement对象
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
}catch(Exception e){
throw new BuilderException("Error parsing Mapper XML. Cause: "+e,e);
}
}
openSession.getMapper(MyBatisMapper.class);
//此方法旨在根据@mapper.class获取其代理对象,而最后返回的也是一个被MapperProxy包裹的代理对象
//MapperProxy继承了InvocationHandler,在调用Proxy.newInstance时传入MapperProxy包裹的对象,可以通过invoke方法对@mapper中的方法做代理
MyBatisMapper mapper=openSession.getMapper(MyBatisMapper.class);
protected T newInstance(MapperProxy<T> mapperProxy){
return(T)Proxy.newProxyInstance(mapperInterface.getClassLoader(),new Class[]{mapperInterface},mapperProxy);
}
MyBatis myBatis = mapper.getOne(1L);
// 前面已经看到了,mapper对象是被MapperProxy类包裹过的,所以请求mapper对象的任何方法时,都会走一遍MapperProxy.invoke方法做代理
@Override
public Object invoke(Object proxy,Method method,Object[]args)throws Throwable{
...
// 检查是否已经缓存过此方法,如果没有缓存过,则methodCache.put(method, mapperMethod);
// methodCache 是MapperProxy类中的一个属性
final MapperMethod mapperMethod=cachedMapperMethod(method);
// 执行SQL
return mapperMethod.execute(sqlSession,args);
}
执行SQL mapperMethod.execute(sqlSession, args)
public Object execute(SqlSession sqlSession,Object[]args){
Object result;
switch(command.getType()){
case INSERT:{
...
}
case UPDATE:{
...
}
case DELETE:{
...
}
case SELECT:
// 没有返回值或者设置了返回值处理器
if(method.returnsVoid()&&method.hasResultHandler()){
executeWithResultHandler(sqlSession,args);
result=null;
}
else if(method.returnsMany()){
// 返回的是list
result=executeForMany(sqlSession,args);
}else if(method.returnsMap()){
// 返回的map?
result=executeForMap(sqlSession,args);
}else if(method.returnsCursor()){
// 返回的指针?
result=executeForCursor(sqlSession,args);
}else{
// 自定义返回值
// 解析参数,并设置一个以param+数字的变量,例如只传id属性时,param中就有两个属性
// {
// "id":1,
// "param1":1
// }
Object param=method.convertArgsToSqlCommandParam(args);
// 这里的selectOne到后面还是selectList,只不过get(0)了
result=sqlSession.selectOne(command.getName(),param);
}
break;
case FLUSH:
...
default:
throw new BindingException("Unknown execution method for: "+command.getName());
}
if(result==null&&method.getReturnType().isPrimitive()&&!method.returnsVoid()){
throw new BindingException("Mapper method '"+command.getName()
+" attempted to return null from a method with a primitive return type ("+method.getReturnType()+").");
}
return result;
}
问题和解答
- MyBatis是如何判断一个xxMapper.xml文件有没有加载的?
Configuration 中有个 Set 类型属性
protected final Set loadedResources = new HashSet();
已加载的xxMapper.xml文件的全路径名会被当做一个Key放进去,每次解析xxMapper.xml时都会判断这个Set中是否已经存在此路径
具体代码在 XMLMapperBuilder::parse()方法中
if (!configuration.isResourceLoaded(resource)){...}
xxMapper.xml解析完成后下面有段代码会将xxMapper.xml的全路径名放入Set中
configuration.addLoadedResource(resource);