mybatis原理及整合spring原理
ztj100 2025-01-07 17:22 11 浏览 0 评论
话题导入:首先我一开始接触mybatis的时候,还是在做SSM课程设计,我会在项目的spring配置文件中会有如下配置:
<!-- ===================整合Mybatis=================== -->
<!-- 配置数据库环境 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!-- mysql url -->
<property name="url"
value="jdbc:mysql://localhost:3306/mybaits"></property>
<!-- 驱动 -->
<property name="driverClassName"
value="com.mysql.jdbc.Driver"></property>
<!-- 用户名密码 -->
<property name="username" value="root"></property>
<property name="password" value=""></property>
</bean>
<!-- 创建数据库映射器,扫描包下所有接口,并为其创建动态代理类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.project.dao" />
</bean>
<!-- 配置 Mybatis 的 SqlSessionFactory -->
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 指定数据库环境 -->
<property name="dataSource" ref="dataSource"></property>
<!-- 指定Mybatis配置文件 -->
<property name="configLocation"
value="classpath:config/mybatisConfig.xml"></property>
<!-- 指定Mybatis映射文件,*.xml 会匹配所有xml文件 -->
<!-- <property name="mapperLocations" value="classpath:com/project/mapper/*.xml"></property> -->
</bean>
<!-- 创建事务管理器:针对jdbc或Mybatis的事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 指定环境, name的dataSource是固定写法,ref是前面创建的环境id -->
<property name="dataSource" ref="dataSource"></property>
</bean>
复制代码
\
准备知识:
1、jdbc使用
public class DbUtil {
public static final String URL = "jdbc:mysql://localhost:3306/imooc";
public static final String USER = "liulx";
public static final String PASSWORD = "123456";
public static void main(String[] args) throws Exception {
//1.加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
//2. 获得数据库连接
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
//3.操作数据库,实现增删改查
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT user_name, age FROM imooc_goddess");
//如果有数据,rs.next()返回true
while(rs.next()){
System.out.println(rs.getString("user_name")+" 年龄:"+rs.getInt("age"));
}
//prepareStatement用法
//sql
String sql = "INSERT INTO imooc_goddess(user_name, sex, age, birthday, email, mobile,"+
"create_user, create_date, update_user, update_date, isdel)"
+"values("+"?,?,?,?,?,?,?,CURRENT_DATE(),?,CURRENT_DATE(),?)";
//预编译
PreparedStatement ptmt = conn.prepareStatement(sql); //预编译SQL,减少sql执行
//传参
ptmt.setString(1, g.getUser_name());
ptmt.setInt(2, g.getSex());
ptmt.setInt(3, g.getAge());
ptmt.setDate(4, new Date(g.getBirthday().getTime()));
ptmt.setString(5, g.getEmail());
ptmt.setString(6, g.getMobile());
ptmt.setString(7, g.getCreate_user());
ptmt.setString(8, g.getUpdate_user());
ptmt.setInt(9, g.getIsDel());
//执行
ptmt.execute();
}
}
复制代码
2、动态代理
说到动态代理前先讲下静态代理:
\
\
\
先从静态代理说起:
生活举例子:江俊想买一辆车子,想直接去大众厂家拿车,但是拿不到,就只能从经销商(4儿子)那里拿车,但是4儿子想赚钱,就会强制让我们买装潢(贴膜,导航,行车记录仪),还会让我们买保养(购车后开了1w公里去他们那里保养一下),4儿子就是中间的代理(Proxy)
//汽车售卖接口
public interface CarSale {
/**
* 卖车
*/
void saleCar();
}
//经销商卖车
public class CarFactorySale implements CarSale {
/**
* 卖车
*/
@Override
public void saleCar() {
System.out.println("成本价卖车");
}
}
//4儿子店卖车
public class FourSsale implements CarSale{
/**
* 厂家
*/
private CarFactorySale carFactorySale;
public FourSsale(CarFactorySale carFactorySale) {
this.carFactorySale = carFactorySale;
}
/**
* 卖车
*/
@Override
public void saleCar() {
System.out.println("必须先来买我的装潢");
carFactorySale.saleCar();
System.out.println("买完车,之后必须到我这里做保养");
}
}
小结:代理类与实际类都要基础同一个接口,代理类中引入了实际类,代理类其实就是对实际类功能的增强。
复制代码
题外话:是不是想到了切面?
接下来是jdk动态代理(必须有接口):以helloWorld为例子
//接口类
public interface HelloWorld {
void sayHello();
}
//实际类
public class HelloWorldImpl implements HelloWorld {
@Override
public void sayHello() {
System.out.println("hello world");
}
}
//调用处理器
public class MyInvocationHandler implements InvocationHandler{
//传入实际类
private Object realTarget;
public MyInvocationHandler(Object target) {
this.realTarget = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method:" + method.getName() + "is invoked!");
System.out.println("在执行实际类逻辑之前,我在。。。");
Object returnObject = method.invoke(realTarget, args);
System.out.println("在执行实际类逻辑之后,我在。。。");
return returnObject;
}
}
//测试类
public class JDKProxyTest {
public void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//创建代理类 代理类class内容见下一段代码
Class<?> proxyClass = Proxy.getProxyClass(JDKProxyTest.class.getClassLoader(), HelloWorld.class);
//获取代理类构造方法
final Constructor<?> constructors = proxyClass.getConstructor(InvocationHandler.class);
//new一个调度器(里面包含增强逻辑)
final InvocationHandler invocationHandler = new MyInvocationHandler(new HelloWorldImpl());
//将包含增强逻辑的调度器作为入池传入代理类构造方法,new一个代理类
HelloWorld helloWorld = (HelloWorld)constructors.newInstance(invocationHandler);
//执行代理类的业务方法
helloWorld.sayHello();
// 保存生成的代理类的字节码文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//写法二
// HelloWorld helloWorld = (HelloWorld) Proxy.newProxyInstance(JDKProxyTest.class.getClassLoader(),
// new Class<?>[]{HelloWorld.class}, new MyInvocationHandler(new HelloWorldImpl()));
// helloWorld.sayHello();
}
}
//jdk帮我们生成的代理类
public final class $Proxy0 extends Proxy implements HelloWorld {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
//包含调度器的构造方法
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
//代理方法
public final void sayHello() throws {
try {
//最终还是调用调用器中的增强方法
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
//以下三个方法为jdk代理生成的Object下的三个方法
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("component.HelloWorld").getMethod("sayHello");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
小结:动态代理的代理类不用像静态代理那样必须手写一个,而是由jdk生成class文件。这就是静态和动态的区别。
我理解的最大好处就是相比静态代理,动态代理不必为一个新接口就重新写一个调度器,而静态代理如果来了一个新接口 就必须
再写一个代理类。
查资料得知:springAop中用的就是动态代理,统一拦截所有接口请求。如果是静态代理就做不到了,
得一个个写代理类,明显是不可取的。
复制代码
3、xml解析
mybatis中很重要的第一步便是解析配置文件,以及mapper.xml
解析用的都是W3C包下的Document以及javax.xml包下的xpath
<location>
<property>
<name>city</name>
<value>beijing</value>
</property>
<property>
<name>district</name>
<value>chaoyang</value>
</property>
</location>
复制代码
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class XPathTest {
public static void main(String args[]){
try {
//解析文档
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware(true); // never forget this!
DocumentBuilder builder = domFactory.newDocumentBuilder();
Document doc = builder.parse("city.xml");
XPathFactory factory = XPathFactory.newInstance(); //创建 XPathFactory
XPath xpath = factory.newXPath();//用这个工厂创建 XPath 对象
NodeList nodes = (NodeList)xpath.evaluate("location/property", doc, XPathConstants.NODESET);
String name = "";
String value = "";
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
name = (String) xpath.evaluate("name", node, XPathConstants.STRING);
value = (String) xpath.evaluate("value", node, XPathConstants.STRING);
System.out.println("name="+name+";value="+value);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
复制代码
进入正题:
步骤1:读取配置文件,生成全局Configuration对象
一、解析配置
不整合spring使用mybatis示例代码:
public static void main(String[] args) {
String resource = "mybatis-config.xml";
Reader reader;
try {
//将XML配置文件构建为Configuration配置类
reader = Resources.getResourceAsReader(resource);
// 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
// 数据源 执行器 DefaultSqlSession
SqlSession session = sqlMapper.openSession();
try {
// 执行查询 底层执行jdbc
User user = (User)session.selectOne("com.tuling.mapper.UserMapper.selectById", 1);
/*UserMapper mapper = session.getMapper(UserMapper.class);
System.out.println(mapper.getClass());
User user = mapper.selectById(1L);*/
session.commit();
System.out.println(user.getUserName());
} catch (Exception e) {
e.printStackTrace();
}finally {
session.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
复制代码
\
//获取sqlsessionFactory
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
复制代码
//解析配置入口
public Configuration parse() {
//如果已经解析过了,报错
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// <?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="development">
// <environment id="development">
// <transactionManager type="JDBC"/>
// <dataSource type="POOLED">
// <property name="driver" value="${driver}"/>
// <property name="url" value="${url}"/>
// <property name="username" value="${username}"/>
// <property name="password" value="${password}"/>
// </dataSource>
// </environment>
// </environments>
// <mappers>
// <mapper resource="org/mybatis/example/BlogMapper.xml"/>
// </mappers>
// </configuration>
//根节点是configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
复制代码
\
//解析配置文件中每一种配置
private void parseConfiguration(XNode root) {
try {
//分步骤解析
//issue #117 read properties first
//1.properties
propertiesElement(root.evalNode("properties"));
//2.类型别名
typeAliasesElement(root.evalNode("typeAliases"));
//3.插件
pluginElement(root.evalNode("plugins"));
//4.对象工厂
objectFactoryElement(root.evalNode("objectFactory"));
//5.对象包装工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//6.设置
settingsElement(root.evalNode("settings"));
// read it after objectFactory and objectWrapperFactory issue #631
//7.环境
environmentsElement(root.evalNode("environments"));
//8.databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//9.类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
//10.映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
复制代码
接下来详细介绍如何解析mapper文件:
//解析mapper所在路径
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//10.4自动扫描包下所有映射器
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
复制代码
//扫描mapper所在包路径,获取所有接口包 遍历添加
public void addMappers(String packageName, Class<?> superType) {
//查找包下所有是superType的类
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
复制代码
//添加一个映射,及寻找xml文件
public <T> void addMapper(Class<T> type) {
//mapper必须是接口!才会添加
if (type.isInterface()) {
if (hasMapper(type)) {
//如果重复添加了,报错
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//***这一步就是新建出代理类工厂放入一个map中,便于之后取出调用
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
//扫描sql注解
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
//寻找xml文件
parser.parse();
loadCompleted = true;
} finally {
//如果加载过程中出现异常需要再将这个mapper从mybatis中删除,这种方式比较丑陋吧,难道是不得已而为之?
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
复制代码
//加载与接口同名的mapper xml文件
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
// 将mapper接口转为xml路径 比如com.burton.usermapper -> com/burton/usermapper.xml
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
//解析mapper的xml文件
xmlParser.parse();
}
}
}
复制代码
public void parse() {
//如果没有加载过再加载,防止重复加载
if (!configuration.isResourceLoaded(resource)) {
//配置解析mapper
configurationElement(parser.evalNode("/mapper"));
//标记一下,已经加载过了
configuration.addLoadedResource(resource);
//绑定映射器到namespace
bindMapperForNamespace();
}
//还有没解析完的东东这里接着解析?
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
复制代码
\
//解析mapper文件里面内容
private void configurationElement(XNode context) {
try {
//1.配置namespace
String namespace = context.getStringAttribute("namespace");
if (namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//2.配置cache-ref
cacheRefElement(context.evalNode("cache-ref"));
//3.配置cache 二级缓存 不是很重要 责任链模式 很多种cache一层套一层
cacheElement(context.evalNode("cache"));
//4.配置parameterMap(已经废弃,老式风格的参数映射)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//5.配置resultMap(高级功能)
resultMapElements(context.evalNodes("/mapper/resultMap"));
//6.配置sql(定义可重用的 SQL 代码段)
sqlElement(context.evalNodes("/mapper/sql"));
//7.配置select|insert|update|delete TODO
**buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
//构建语句
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
//构建所有语句,一个mapper下可以有很多select
//语句比较复杂,核心都在这里面,所以调用XMLStatementBuilder
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//**核心XMLStatementBuilder.parseStatementNode
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
//如果出现SQL语句不完整,把它记下来,塞到configuration去
configuration.addIncompleteStatement(statementParser);
}
}
}
/**
statementParser.parseStatementNode()方法中有langDriver.createSqlSource方法负责将sql语句解析成一层层的sqlNode封装在sqlSource中
* 通过class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver来解析我们的
* sql脚本对象 . 解析SqlNode. 注意, 只是解析成一个个的SqlNode, 并不会完全解析sql,因为这个时候参数都没确定,动态sql无法解析
*/
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//最终将mapper文件中某一句增删改查的全部信息(sql语句解析为sqlsource,还有paramtype,resultMap等)构建出MappedStatement,加入configration的map中,key为namespace+id
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
//为id加上namespace前缀
id = applyCurrentNamespace(id, false);
//是否是select语句
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//又是建造者模式
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);
statementBuilder.resource(resource);
statementBuilder.fetchSize(fetchSize);
statementBuilder.statementType(statementType);
statementBuilder.keyGenerator(keyGenerator);
statementBuilder.keyProperty(keyProperty);
statementBuilder.keyColumn(keyColumn);
statementBuilder.databaseId(databaseId);
statementBuilder.lang(lang);
statementBuilder.resultOrdered(resultOrdered);
statementBuilder.resulSets(resultSets);
setStatementTimeout(timeout, statementBuilder);
//1.参数映射
setStatementParameterMap(parameterMap, parameterType, statementBuilder);
//2.结果映射
setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);
MappedStatement statement = statementBuilder.build();
//建造好调用configuration.addMappedStatement
configuration.addMappedStatement(statement);
return statement;
}
复制代码
总结:当mybatis配置文件里配置了dao层接口的包名,mybatis会去对应的报下获取所有的接口,遍历,分别再找到同名的mapper.xml文件。解析xml文件,将resultMap,sql,insert,select,delete,update(增删改查都会封装mapperedStatement存到map中 key为namaspace+id,mapperedStatement中包含sqlSource,sqlSource中是sqlNode)都封装进全局configration对象中。
所有配置都解析到configration对象中,configration对象封装在sqlSessionFactory中,通过sqlSessionFactory获取sqlSession,所以sqlsession中也有configration。
**sqlSession—configration-**MapperRegistry-knownMappers(一个map,包含接口class为key, 接口代理工厂MapperProxyFactory
(通过这个工厂拿到代理类(MapperProxyFactory.newInstance),真正执行拦截器以及sql在代理类MapperProxy的invoke方法里,通过sqlSwssion.getMapper就能通过以上步骤拿到代理类)为value,mapper文件解析过了,则存在这个knownmappers中,不允许重复解析mapper文件,是在解析配置文件中mapper所在路径的时候放到这个map中的,key为mapper的class,value为MapperProxyFactory,这个类可以生成动态代理)
\
Configuration类中mappedStatements字段是一个map,保存了解析mapper.xml中的select update insert等,map的value是MappedStatement就是一个select/ update/ insert语句解析好的信息(解析得到sqlNode),key是mapper文件的namespace+select update insert的id
【其他】1、TypeHandler 类型转换器 负责设置sql语句prepareStatement参数Java类型转为数据库类型 以及查到结果将resultSet中数据库结果类型转为java类型(其实就是使用jdbc)。各种转换器注册在TypeHandlerRegister中。
2、#{id} 在后续会解析为sql中的?,最后给prepareStatement设置参数参数时,会把?的值设置为id的值(通过typeHandler)。
\
二、获取mapper代理
一、讲mapper(整合spring我们一般都是这么使用)代理之前先讲下,另一种调用方式通过statmentId执行sql(mapper文件中的一句sql会封装未一个mapperedStatement)
// 执行查询 底层执行jdbc com.tuling.mapper.UserMapper.selectById为statementId
User user = (User)session.selectOne("com.tuling.mapper.UserMapper.selectById", 1);
/*UserMapper mapper = session.getMapper(UserMapper.class);
System.out.println(mapper.getClass());
User user = mapper.selectById(1L);*/
session.commit();
System.out.println(user.getUserName());
复制代码
二、执行sql前获取mapper接口的代理
//defaultSqlSession的方法:获取mapper
public <T> T getMapper(Class<T> type) {
//最后会去调用MapperRegistry.getMapper
return configuration.<T>getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
//MapperRegistry类的getMapper方法 返回代理类
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
//mapperProxyFactory类通过jdk动态代理构建代理类
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
//是不是看到了熟悉的动态代理
protected T newInstance(MapperProxy<T> mapperProxy) {
//用JDK自带的动态代理生成映射器,参数mapperProxy相当于之前讲的任务调度器,也肯定实现了InvocationHandler jdk调度接口
//最后的真正sql调用数据库肯定再这个代理类里发起
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
//MapperProxy是真正执行逻辑的地方,MapperProxy的invock方法里会真正去拼装sql,jdbc执行sql
复制代码
总结:获取mapper代理类的流程:
- 从sqlSession中获取mapper
- 从configration中获取mapper
- 从MapperRegistry中获取mapper
- 从knownMappers中获取mapper的动态代理类工厂
- 用工厂生成mapper的代理类(代理中传入了sqlSession)
三、Executor执行sql
3、执行sql
//mapper调度器(代理类是jdk生成的字节码文件,最后代理类会调这个调度器,逻辑在调度器里)
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
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 {
//代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
//并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//这里优化了,去缓存中找MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行
return mapperMethod.execute(sqlSession, args);
}
//去缓存中找MapperMethod
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
//找不到才去new
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
复制代码
//去缓存中找MapperMethod或者新建mapperMehod
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
//找不到才去new
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
//构造器新建mapperMethod
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, method);
}
//新建MethodSignature 方法签名相关信息
public MethodSignature(Configuration configuration, Method method) {
this.returnType = method.getReturnType();
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
this.mapKey = getMapKey(method);
this.returnsMap = (this.mapKey != null);
this.hasNamedParameters = hasNamedParams(method);
//以下重复循环2遍调用getUniqueParamIndex,是不是降低效率了
//记下RowBounds是第几个参数
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
//记下ResultHandler是第几个参数
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
**this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
}
//获取接口入参
//得到所有参数
private SortedMap<Integer, String> getParams(Method method, boolean hasNamedParameters) {
//用一个TreeMap,这样就保证还是按参数的先后顺序
final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
final Class<?>[] argTypes = method.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
//是否不是RowBounds/ResultHandler类型的参数
if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) {
//参数名字默认为0,1,2,这就是为什么xml里面可以用#{1}这样的写法来表示参数了
String paramName = String.valueOf(params.size());
if (hasNamedParameters) {
//还可以用注解@Param来重命名参数
paramName = getParamNameFromAnnotation(method, i, paramName);
}
params.put(i, paramName);
}
}
return params;
}
//解析入参@param参数
private String getParamNameFromAnnotation(Method method, int i, String paramName) {
final Object[] paramAnnos = method.getParameterAnnotations()[i];
for (Object paramAnno : paramAnnos) {
if (paramAnno instanceof Param) {
paramName = ((Param) paramAnno).value();
}
}
return paramName;
}
//将方法入参转化为xml文件中用#{}可以解析出来的参数
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
//如果没参数
return null;
} else if (!hasNamedParameters && paramCount == 1) {
//如果只有一个参数
return args[params.keySet().iterator().next().intValue()];
} else {
//否则,返回一个ParamMap,修改参数名,参数名就是其位置
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : params.entrySet()) {
//1.先加一个#{0},#{1},#{2}...参数
param.put(entry.getValue(), args[entry.getKey().intValue()]);
// issue #71, add param names as param1, param2...but ensure backward compatibility
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
//2.再加一个#{param1},#{param2}...参数
//你可以传递多个参数给一个映射器方法。如果你这样做了,
//默认情况下它们将会以它们在参数列表中的位置来命名,比如:#{param1},#{param2}等。
//如果你想改变参数的名称(只在多参数情况下) ,那么你可以在参数上使用@Param(“paramName”)注解。
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
//通过上述代码,我们在xml文件中取出入参可以用#{0},#{param0},#{paramName}三种方式
复制代码
//执行数据库操作
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//可以看到执行时就是4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
//如果有结果处理器
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//如果结果有多条记录
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//如果结果是map
result = executeForMap(sqlSession, args);
} else {
//否则就是一条记录
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
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;
}
复制代码
我们就选一个最简单的select返回一条跟进源码看一下
//核心selectOne
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
//转而去调用selectList,很简单的,如果得到0条则返回null,得到1条则返回1条,得到多条报TooManyResultsException错
// 特别需要主要的是当没有查询到结果的时候就会返回null。因此一般建议在mapper中编写resultType的时候使用包装类型
//而不是基本类型,比如推荐使用Integer而不是int。这样就可以避免NPE
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
看到TooManyResultsException这个异常是不是有点熟悉,可能我们代码会出现这个异常。
返回类型最好用包装类,避免查不到结果返回null,导致控制住
复制代码
//从configration中根据namespace+方法id去粗mapperedStatment
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//根据statement id找到对应的MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
//转而用执行器来查询结果,注意这里传入的ResultHandler是null
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
获取解析过的sql对象
//SqlSession.selectList会调用此方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//得到绑定sql,该boundsql中的sql为解析后的sql,动态sql中若参数是${},则会直接解析成参数,若参数是#{},则会解析成?,解析成?的会在下文stmt = prepareStatement方法中替换为参数
BoundSql boundSql = ms.getBoundSql(parameter);
//创建缓存Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//查询
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
//创建statmentHandler以及prepareStatment(jdbc)
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//新建一个StatementHandler
//这里看到ResultHandler传入了
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//准备语句
stmt = prepareStatement(handler, ms.getStatementLog());
//StatementHandler.query
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
//给动态sql中#{}的在boundSql的?替换为真实参数
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Connection connection = this.getConnection(statementLog);
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
//jdbc执行
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
//先执行Statement.execute,然后交给ResultSetHandler.handleResultSets
return resultSetHandler.<E>handleResultSets(statement);
}
复制代码
小结:调接口查询时,首先执行了调度器中方法,创建mapperedMethod,并且创建方法签名,比如会把方法入参获取放入一个map中,key时0,1,2,3.。。。param0,param1.。。paramName。根据方法的包名及方法名从configration中获取先前解析好的mapperdStatement,里面包含解析过的sql语句。调jdbc进行执行查数据库。
\
mybatis中重要的几个类:
- Configration 全局配置类
- MapperedStatement 封装一个sql(select/insert/update/delete)信息。MapperMethod封装了一个sql,也是根据当前该sql的类型(select/insert/update/delete)发起sqlSession执行总入口
- BoundSql 直接存储sql的类,将#{}转换为?后的sql
- Executor sql执行类 所有sql语句执行都下这个类方法里
- MapperRegistry,包含一个Map<Class, MapperProxyFactory> knownMappers的map属性,key为一个mapper接口类的class,value为这个mapper代理类的工程
\
四、整合spring的原理
当我们使用@Autowired注解的时候有没有想过,为啥能这么简单得 我们什么都没做就能拿到这了一个类,然后还能执行我们写在mapper.xml中的sql?神奇啊
1、首先看过获取mapper代理流程的应该清楚 为啥能从一个mapper的java接口就能执行我们写在mapper.xml中的sql? 动态代理!!
2、为啥这个动态代理类能被我们用@autowired注入进来?肯定是这个 代理类在spring容器中!!
那么最终的问题就变成这个代理类是怎么到spring容器中的?下面进行一步步解析:
mybatis整合spring的一个javaconfig配置类:
主要有两个重点:1、@MapperScan 2、SqlSessionFactoryBean.getObject
@Configuration
// 重点看这里 重点看这里 重点看这里 重点看这里 MapperScan MapperScan 这里就是spring整合的重点
@MapperScan(basePackages = "com.wwdz.mall.mapper.single", sqlSessionTemplateRef = "singleSqlTemplate")
public class DataSourceSingleConfig {
@Bean(name = "rdsPolar")
@ConfigurationProperties(prefix = "spring.datasource.rds-polar")
public DataSource dataSourceRdsPolar() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setName("rdsPolar");
DatasourceUtil.setDruidProperty(dataSource);
return dataSource;
}
/** 重点看这里 重点看这里 重点看这里
构建SqlSessionFactory的过程,和单独使用mybatis一样,就是解析mybatis配置文件,把所有配置配置放到configuration中,然后configuration
最终放到sqlSession中(当然包括解析mapper.xml文件)
**/
@Bean("singleSqlFactory")
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceRdsPolar());
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mappers/single/*.xml"));
// 这句代码就是开始解析 下面会介绍
return sqlSessionFactoryBean.getObject();
}
@Bean("singleSqlTemplate")
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("singleSqlFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean(name = "singleTransactionManager")
public PlatformTransactionManager singleTransactionManager(@Qualifier("rdsPolar") DataSource dataSource) throws Exception {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "singleTransactionTemplate")
public TransactionTemplate transactionTemplate(@Qualifier("singleTransactionManager") PlatformTransactionManager mybatisTransactionManager) {
TransactionTemplate transactionTemplate = new TransactionTemplate();
transactionTemplate.setIsolationLevel(ISOLATION_DEFAULT);
transactionTemplate.setTimeout(3);
transactionTemplate.setTransactionManager(mybatisTransactionManager);
return transactionTemplate;
}
}
复制代码
1、SqlSessionFactoryBean.getObject->SqlSessionFactoryBean.afterPropertiesSet->SqlSessionFactoryBean.buildSqlSessionFactory 这一串流程会解析SqlSessionFactoryBean中配置的配置信息最终解析出来都放到configuration中,然后把configuration放到sqlSessionFactory中,返回sqlSessionFactory;
2、重点关注@MapperScan
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class}) // 重点看这里啊啊 MapperScannerRegistrar这个类
@Repeatable(MapperScans.class)
public @interface MapperScan {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends Annotation> annotationClass() default Annotation.class;
Class<?> markerInterface() default Class.class;
String sqlSessionTemplateRef() default "";
String sqlSessionFactoryRef() default "";
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
/**
这个类继承了spring的ImportBeanDefinitionRegistrar,需要实现registerBeanDefinitions方法
**/
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
public MapperScannerRegistrar() {
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
// 这个方法是继承自spring中的ImportBeanDefinitionRegistrar
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取@MapperScan注解上的属性
AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
this.registerBeanDefinitions(mapperScanAttrs, registry);
}
}
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
// 重点 看这里
// 这个ClassPathMapperScanner类也很关键 是用来扫码指定路径的mapper接口变为BeanDefinition的
// 这个类继承自spring的ClassPathBeanDefinitionScanner,spring的这个类的作用是在spring容器启动的过程中,
// 去读取指定路径下的类变成BeanDefinition,但是只会读普通类,接口和抽象类不会读,所以mybatis-spring的需要继承这个类之后对
// 这个类中的isCandidateComponent方法重写,变为只读取接口,因为我们的mapper.java都是接口。其他方法还是用父类的
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
Optional var10000 = Optional.ofNullable(this.resourceLoader);
Objects.requireNonNull(scanner);
var10000.ifPresent(scanner::setResourceLoader);
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBeanClass(mapperFactoryBeanClass);
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList();
basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
scanner.registerFilters();
// 开始扫描指定包路径下的mapper接口为beanDifinition
scanner.doScan(StringUtils.toStringArray(basePackages));
}
}
复制代码
ClassPathMapperScanner中重要的几个方法:非常重要
/**
* 方法实现说明:真正调用扫描我们@MapperScan指定的路径下的mapper包
*
* @author:xsls
* @param basePackages
* :路径长度
* @return:
* @exception:
* @date:2019/8/21 17:27
* 这个doscan方法其实是重写了spring中父类ClassPathBeanDefinitionScanner的doscan
*/
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
/**
* 调用父类ClassPathBeanDefinitionScanner 来进行扫描
* 然后这个方法中会调用isCandidateComponent这个方法,这个方法又是被当前ClassPathMapperScanner这个类重写过了
* 只会扫描mapper接口,所以最终的结果是会拿到所有mapper接口的beanDefinition
* 父类spring中的ClassPathBeanDefinitionScanner的isCandidateComponent只会扫描普通的类成为beanDifinition到ioc容器中,抽象类和接口都不会
*/
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
/**
* 若扫描后 我们mapper包下有接口类,那么扫描bean定义就不会为空
*/
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
/**
* 正是在这里mybaits做了一个很牛逼的功能,将spring的 的bean定义玩到极致(做了偷天换日的操作) 现在我们知道t通过父类扫描出来的mapper是接口类型的
* 比如我们com.tuling.mapper.UserMapper 他是一个接口 我们有基础的同学可能会知道我们的bean定义最终会被实例化成
* 对象,但是我们接口是不能实例化的,所以在processBeanDefinitions 来进行偷天换日
*/
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
/**
* 循环我们所有扫描出mapper的bean定义出来
*/
for (BeanDefinitionHolder holder : beanDefinitions) {
// 获取我们的bean定义
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// 获取我们的bean定义的名称 这里是mapper接口的名字
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
/**
* 进行真的偷天换日操作,也就是这二行代码是最最最最最重要的, 关乎我们 spring整合mybaits的整合 definition.setBeanClass(this.mapperFactoryBeanClass);
*/
// 设置ConstructorArgumentValues 会通过构造器初始化对象 MapperFactoryBean有一个构造方法 入参为真正mapper接口的beanClassName
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
// 设置成factoryBean this.mapperFactoryBeanClass = MapperFactoryBean.class 把mapper接口的beanDifiniton的class类型设置为MapperFactoryBean,这就是偷天换日
definition.setBeanClass(this.mapperFactoryBeanClass);
// 总结下上面两步做的:拿到每一个mapper接口的beanDifiniton,将beanDifiniton中的beanClass设置为MapperFactoryBean.class,然后通过MapperFactoryBean的构造方法设置真正mapper接口的beanclassname
// MapperFactoryBean是FactoryBean的子类,在spring中,通过beanDifinition创建bean实例的时候会判断这个beanDifinition的beanClass是否是FactoryBean,是的话,则放到spring容器中的对象是FactoryBean的getObject方法返回的对象
// MapperFactoryBean对象的getObject方法返回的就是mapper接口的jdk代理对象
definition.getPropertyValues().add("addToConfig", this.addToConfig);
/**
* 为我们的Mapper对象绑定我们的sqlSessionFactory引用 说白了就是我们的UserMapper(实际上是就是为我们的MapperFactoryBean添加一个sqlSessionFactory的属性)
* 然后SpringIoc在实例话我们的MapperFactoryBean的时候会经历populate()方法为我么你的UserMapper(MapperFactoryBean)
* 的sqlSessionFactory赋值(调用set方法)
*/
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
/**
* 为我们的Mapper对象绑定我们的sqlSessionTemplate属性对象
* 说白了就是我们的UserMapper(实际上是就是为我们的MapperFactoryBean添加一个sqlSessionTemplate的属性)
* 然后SpringIoc在实例话我们的MapperFactoryBean的时候会经历populate()方法为我么你的UserMapper(MapperFactoryBean)
* 的sqlSessionTemplate赋值(调用set方法)
*/
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
// 将sqlSessionTemplate通过AUTOWIRE_BY_TYPE自动装配
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
/**
* 设置UserMapper<MapperFactoryBean>定义的注入模型是通过 包扫描进来的,所有我们的默认注入模型就是
* AutowireCapableBeanFactory.AUTOWIRE_NO=0注入模型为0的时候,在这种情况下,若我们的MapperFactoryBean
* 的字段属性是永远自动注入不了值的因为字段上是没有 @AutoWired注解,所以我们需要把UserMapper<MapperFactoryBean> 的bean定义的注入模型给改成我们的 AUTOWIRE_BY_TYPE
* = 1,指定这个类型就是根据类型装配的话, 第一:我们的字段上不需要写@AutoWired注解,为啥? springioc会把当前UserMapper<MapperFactoryBean>中的setXXX(入参)
* 都会去解析一次入参,入参的值可定会从ioc容器中获取,然后调用setXXX方法给赋值好. 或 AUTOWIRE_By_Type=1
* 指定这个类型就是根据类型装配的话,第一:我们的字段上不需要写@AutoWired注解,为啥? springioc会把当前UserMapper<MapperFactoryBean>中的setXXX(入参)都会去解析一次入参,
* 入参的值可定会从ioc容器中获取,然后调用setXXX方法给赋值好.
*
* ??????????????为啥在这里mybaits却要指定AUTOWIRE_BY_TYPE了? 假设我们指定的是by_name的话, 那么他会通过setXXX(入参)的引用名去ioc容器中获取值,
* 假设我们自己配置的bean的名称不是相同的那么就会抛出异常
*
* public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (this.sqlSessionTemplate == null ||
* sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) { this.sqlSessionTemplate
* =createSqlSessionTemplate(sqlSessionFactory); } } 所以在IOC实例化我们的UserMapper<MapperFactoryBean>的时候,会调用父类
* SqlSessionDaoSupport的setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)
* 方法,而我们的sqlSessionFactory需要去容器中获取(也就是我们自己配置的SqlSessionFactoryBean)
*
*/
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
/**
* 设置bean定义的加载模型(是否为懒加载)
*/
definition.setLazyInit(lazyInitialization);
}
}
/**
* {@inheritDoc}
* 这里很关键 重写了父类的方法 spring 父类中的这个方法 只会扫描类 不会要接口和抽象类 这里重写为只要接口(mapper接口)
*/
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
复制代码
// spring中的FactoryBean类,作用就是spring容器启动的时候,会把这个类getObject方法返回的对象注册到ioc容器中
public interface FactoryBean<T> {
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
复制代码
/**
* @vlog: 高于生活,源于生活
* @desc: 类的描述:这个了就是我们UserMapper的代理类,他也会经过 springIoc容器bean的生命周期,在bean的生命周期方法populate()方法会给属性进行赋值
* 由于在ClassMapperScan类中已经把当前的bean定义的注入模型给修改了by_type 所以,凡是写了setXXX的方法的,spring ioc在populate() 去进行调用
* @author: xsls
* @createDate: 2019/8/22 19:20
* @version: 1.0
* MapperFactoryBean 继承了spring的FactoryBean 重点重点重点
*/
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
// intentionally empty
}
/**
* 在这里又是一个牛逼的设计闪光点:我们知道在ClassPathMapperScanner 扫描我们的UserMapper<MapperFactoryBean>的时候做了一个牛逼的事情,
* 他扫描我们的UserMapper的时候的bean定义是接口类型的,我们知道接口类型是不能够被实例化的 所以在ClassPathMapperScanner扫描之后马上进行来处理UserMapper的bean定义
* definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
* definition.setBeanClass(this.mapperFactoryBeanClass); 把UserMapper的bean定义给改成我们的MapperFactoryBean,
* 最终我们实例化UserMapper就是我们的MapperFactoryBean类型,
* definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
* 就是来指定我们的实例化MapperFactoryBean的构造函数的参数。这么做的目的就是 因为MapperFactoryBean 是我们的Factorybean对象, 最终返回的是getObject()方法放回的对象
* 而getObject()对象返回的是一个jdk代理对象,我们知道jdk代理对象需要代理接口, 所以这里就是为了保存我们传入进来的接口类型
*
* @param mapperInterface
*/
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* 方法实现说明:在UserMapper<MapperFactoryBean> 父类DaoSupport 的bean的生命周期回调
* InitializingBean.afterPropertiesSet()方法的时候,会进行checkDaoConfig();检查
*
* @author:xsls
* @return:
* @exception:
* @date:2019/8/22 20:07
*/
@Override
protected void checkDaoConfig() {
/**
* 调用父类的SqlSessionDaoSupport的方法来检查我们的SqlSessionFactory 或者sqlSessionTemplate是否为空
*/
super.checkDaoConfig();
/**
* 断言我们的mapperInterface(我们mapper接口class类型是否为空)
*/
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
/**
* 在这里进行了二个操作,第一步:getSqlSession() 是调用父类的获取SqlSession类型(接口)实现类 SqlSessionTemplate 第二步:getConfiuration
* 是调用sqlSessionTemplate的sqlSessionFactory对象获取他的Configuration属性
*/
Configuration configuration = getSqlSession().getConfiguration();
/**
* 判断我们的mapperRegistry 的knownMappers Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
*/
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
/**
* 把我们的接口类型保存到sqlSessionFactory的属性Configuration对象 的MapperRegistry属性中
*/
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
/**
* 请看这里 最重要的就是这里这一步骤 真实注入到ioc容器的是getSqlSession().getMapper(this.mapperInterface)返回的代理类(这是mybatis原生的代码逻辑了)
* 方法实现说明:由于是我们factoryBean,那么我们service中注入我们的UserMapper的时候 就会调用我们的getObject()
*
* @author:xsls
* @return:
* @exception:
* @date:2019/8/22 20:35
*/
@Override
public T getObject() throws Exception {
/**
* 第一步:就是获取我么女的SqlSessionTemplate 第二步:获取我们的SqlSessionTemplate.getMapper(mapperInterface)方法
*/
return getSqlSession().getMapper(this.mapperInterface);
}
/**
* {@inheritDoc}
*/
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSingleton() {
return true;
}
// ------------- mutators --------------
/**
* Sets the mapper interface of the MyBatis mapper
*
* @param mapperInterface
* class of the interface
*/
public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* Return the mapper interface of the MyBatis mapper
*
* @return class of the interface
*/
public Class<T> getMapperInterface() {
return mapperInterface;
}
/**
* If addToConfig is false the mapper will not be added to MyBatis. This means it must have been included in
* mybatis-config.xml.
* <p>
* If it is true, the mapper will be added to MyBatis in the case it is not already registered.
* <p>
* By default addToConfig is true.
*
* @param addToConfig
* a flag that whether add mapper to MyBatis or not
*/
public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}
/**
* Return the flag for addition into MyBatis config.
*
* @return true if the mapper will be added to MyBatis in the case it is not already registered.
*/
public boolean isAddToConfig() {
return addToConfig;
}
}
复制代码
画个spring整合mybatis的流程图:
整合spring总结:
1、@mapperScan注解引入了MapperScannerRegistrar,这是ImportBeanDefinitionRegistrar的子类,在spring启动中会执行ImportBeanDefinitionRegistrar的registerBeanDefinitions方法往容器中注册beanDefinition。
2、ClassPathMapperScanner类继承自spring的ClassPathBeanDefinitionScanner,通过该类的doscan方法把mapper接口都扫描并得到对应的beanDefiniton。
3、将beanDifiniton中的beanClass设置为MapperFactoryBean.class(spring中FactoryBean的子类),然后通过MapperFactoryBean的构造方法设置真正mapper接口的beanclassname,MapperFactoryBean的getObject方法放回的是sqlSesiion.getMapper返回真正的mapper接口的代理对象。
五、重要类
- MapperRegistry:有一个属性knownMappers本质上是一个Map,其中的key是Mapper接口的全限定名,value的MapperProxyFactory;
- MapperProxyFactory:这个类是MapperRegistry中存的value值,在通过 sqlSession获取Mapper时,其实先获取到的是这个工厂,然后通过这个工厂创建 Mapper的动态代理类;
- MapperProxy:实现了InvocationHandler接口,Mapper的动态代理接口方法的调 用都会到达这个类的invoke方法;
- MapperMethod:判断你当前执行的方式是增删改查哪一种,并通过SqlSession执 行相应的操作;
- SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能,Executor会创建好放在这个类里;
- Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护,一级缓存在这个类里,所以看出一级缓存是sqlSession级别的,每个sqlSesiion都有对应的一级缓存;
- StatementHandler:封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合;
- ParameterHandler:负责对用户传递的参数转换成JDBC Statement 所需要的参数;
- ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
- TypeHandler:负责java数据类型和jdbc数据类型之间的映射和转换;
- MappedStatement:MappedStatement维护了一条<select|update|delete|insert>节点的封装;
- SqlSource:负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回;
- BoundSql:表示动态生成的SQL语句以及相应的参数信息;
- Configuration:MyBatis所有的配置信息都维持在Configuration对象之中。
调试主要关注点
MapperProxy.invoke方法:MyBatis的所有Mapper对象都是通过动态代理生成
的,任何方法的调用都会调到invoke方法,这个方法的主要功能就是创建
MapperMethod对象,并放进缓存。所以调试时我们可以在这个位置打个断点,看下是
否成功拿到了MapperMethod对象,并执行了execute方法。
MapperMethod.execute方法:这个方法会判断你当前执行的方式是增删改查哪一
种,并通过SqlSession执行相应的操作。Debug时也建议在此打个断点看下。
DefaultSqlSession.selectList方法:这个方法获取了获取了MappedStatement对
象,并最终调用了Executor的query方法;
总流程图
相关推荐
- Whoosh,纯python编写轻量级搜索工具
-
引言在许多应用程序中,搜索功能是至关重要的。Whoosh是一个纯Python编写的轻量级搜索引擎库,可以帮助我们快速构建搜索功能。无论是在网站、博客还是本地应用程序中,Whoosh都能提供高效的全文搜...
- 如何用Python实现二分搜索算法(python二分法查找代码)
-
如何用Python实现二分搜索算法二分搜索(BinarySearch)是一种高效的查找算法,适用于在有序数组中快速定位目标值。其核心思想是通过不断缩小搜索范围,每次将问题规模减半,时间复杂度为(O...
- 路径扫描 -- dirsearch(路径查找器怎么使用)
-
外表干净是尊重别人,内心干净是尊重自己,干净,在今天这个时代,应该是一种极高的赞美和珍贵。。。----网易云热评一、软件介绍Dirsearch是一种命令行工具,可以强制获取web服务器中的目录和文件...
- 78行Python代码帮你复现微信撤回消息!
-
来源:悟空智能科技本文约700字,建议阅读5分钟。本文基于python的微信开源库itchat,教你如何收集私聊撤回的信息。...
- 从零开始学习 Python!2《进阶知识》 Python进阶之路
-
欢迎来到Python学习的进阶篇章!如果你说已经掌握了基础语法,那么这篇就是你开启高手之路的大门。我们将一起探讨面向对象编程...
- 白帽黑客如何通过dirsearch脚本工具扫描和收集网站敏感文件
-
一、背景介绍...
- Python之txt数据预定替换word预定义定位标记生成word报告(四)
-
续接Python之txt数据预定替换word预定义定位标记生成word报告(一)https://mp.toutiao.com/profile_v4/graphic/preview?pgc_id=748...
- Python——字符串和正则表达式中的反斜杠('\')问题详解
-
在本篇文章里小编给大家整理的是关于Python字符串和正则表达式中的反斜杠('\')问题以及相关知识点,有需要的朋友们可以学习下。在Python普通字符串中在Python中,我们用'\'来转义某些普通...
- Python re模块:正则表达式综合指南
-
Python...
- python之re模块(python re模块sub)
-
re模块一.re模块的介绍1.什么是正则表达式"定义:正则表达式是一种对字符和特殊字符操作的一种逻辑公式,从特定的字符中,用正则表达字符来过滤的逻辑。(也是一种文本模式;)2、正则表达式可以帮助我们...
- MySQL、PostgreSQL、SQL Server 数据库导入导出实操全解
-
在数字化时代,数据是关键资产,数据库的导入导出操作则是连接数据与应用场景的桥梁。以下是常见数据库导入导出的实用方法及代码,包含更多细节和特殊情况处理,助你应对各种实际场景。一、MySQL数据库...
- Zabbix监控系统系列之六:监控 mysql
-
zabbix监控mysql1、监控规划在创建监控项之前要尽量考虑清楚要监控什么,怎么监控,监控数据如何存储,监控数据如何展现,如何处理报警等。要进行监控的系统规划需要对Zabbix很了解,这里只是...
- mysql系列之一文详解Navicat工具的使用(二)
-
本章内容是系列内容的第二部分,主要介绍Navicat工具的使用。若查看第一部分请见:...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- Whoosh,纯python编写轻量级搜索工具
- 如何用Python实现二分搜索算法(python二分法查找代码)
- 路径扫描 -- dirsearch(路径查找器怎么使用)
- 78行Python代码帮你复现微信撤回消息!
- 从零开始学习 Python!2《进阶知识》 Python进阶之路
- 白帽黑客如何通过dirsearch脚本工具扫描和收集网站敏感文件
- Python之txt数据预定替换word预定义定位标记生成word报告(四)
- 假期苦短,我用Python!这有个自动回复拜年信息的小程序
- Python——字符串和正则表达式中的反斜杠('\')问题详解
- Python re模块:正则表达式综合指南
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- node卸载 (33)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- exceptionininitializererror (33)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)