前言:
今天我们对“mybatis和spring整合原理”大体比较珍视,看官们都想要剖析一些“mybatis和spring整合原理”的相关知识。那么小编同时在网摘上收集了一些有关“mybatis和spring整合原理””的相关文章,希望大家能喜欢,同学们一起来了解一下吧!话题导入:首先我一开始接触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(); } }复制代码
\
//获取sqlsessionFactorypublic 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" // ";> // <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为statementIdUser 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的类,将#{}转换为?后的sqlExecutor 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方法;
总流程图
标签: #mybatis和spring整合原理