核心的类 MyBatis有几个核心的类如:SqlSessionFactoryBuilder
,SqlSessionFactory
,SqlSession
,Configuration
等
使用 MyBatis 的主要 Java 接口就是 SqlSession。可以通过这个接口来执行命令,获取Mapper接口和管理事务。SqlSessions 是由 SqlSessionFactory 实例创建的。SqlSessionFactory 对象包含创建 SqlSession 实例的各种方法。而 SqlSessionFactory 本身是由 SqlSessionFactoryBuilder 创建的,它可以从 XML、注解或 Java 配置代码来创建 SqlSessionFactory。
Configuration 在构建 SqlSessionFactory
之前先要配置 Configuration
实例,Mybatis所有的配置都在这个类里面,在运行时可以通过 SqlSessionFactory#getConfiguration() 来获得并检查配置。
以下删除了很多配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 public class Configuration { protected Environment environment; protected boolean safeRowBoundsEnabled; protected boolean safeResultHandlerEnabled = true ; protected boolean mapUnderscoreToCamelCase; protected boolean useActualParamName = true ; protected boolean returnInstanceForEmptyRow; protected String logPrefix; protected Class<? extends Log > logImpl; protected Class<? extends VFS > vfsImpl; protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION; protected Properties variables = new Properties (); protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory (); protected ObjectFactory objectFactory = new DefaultObjectFactory (); protected ProxyFactory proxyFactory = new JavassistProxyFactory (); protected String databaseId; protected Class<?> configurationFactory; protected final MapperRegistry mapperRegistry = new MapperRegistry (this ); protected final InterceptorChain interceptorChain = new InterceptorChain (); protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry (this ); protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry (); protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry (); protected final Map<String, MappedStatement> mappedStatements = new StrictMap <MappedStatement>("Mapped Statements collection" ) .conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource() + " and " + targetValue.getResource()); protected final Map<String, Cache> caches = new StrictMap <>("Caches collection" ); protected final Map<String, ResultMap> resultMaps = new StrictMap <>("Result Maps collection" ); protected final Map<String, ParameterMap> parameterMaps = new StrictMap <>("Parameter Maps collection" ); protected final Map<String, KeyGenerator> keyGenerators = new StrictMap <>("Key Generators collection" ); protected final Set<String> loadedResources = new HashSet <>(); protected final Map<String, XNode> sqlFragments = new StrictMap <>("XML fragments parsed from previous mappers" ); }
SqlSessionFactory 1,XML中构建 SqlSessionFactory 下面是Mybatis配置文件(这里只是Mybatis的配置,没有与Spring结合)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?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" > <property name ="" value ="" /> </transactionManager > <dataSource type ="UNPOOLED" > <property name ="driver" value ="com.mysql.cj.jdbc.Driver" /> <property name ="url" value ="" /> <property name ="username" value ="" /> <property name ="password" value ="" /> </dataSource > </environment > </environments > <mappers > <mapper class ="com.ddmcc.UserMapper" /> </mappers > </configuration >
1 2 3 4 5 6 7 public static void main (String[] args) throws Exception{ SqlSessionFactory sqlSessionFactory; try (Reader reader = Resources.getResourceAsReader("com/ddmcc/mybatis-config.xml" )) { sqlSessionFactory = new SqlSessionFactoryBuilder ().build(reader); } }
读取配置文件创建流,利用SqlSessionFactoryBuilder对象将流构建成SqlSessionFactory
对象。SqlSessionFactory其实是一个接口,调用build方法返回的 是DefaultSqlSessionFactory 对象,它是SqlSessionFactory的默认实现。在DefaultSqlSessionFactory中,有一个Configuration 对象,基本上我们的所有配置属性,mapper接口等都会 保存在这个对象中
build() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public SqlSessionFactory build (Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder (reader, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession." , e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { } } }
获得配置文件configuration节点下的所有的子节点,逐一解析,赋值到Configuration对象中对应的属性。
可以看到下面解析的方法都是XMLConfigBuilder类内部的方法,所有configuration的属性都是在各个方法里设置的,因为在XMLConfigBuilder中,有一个configuration属性。(parser.parse()方法会判断是否已经解析过了, 如果执行不止一次parse()方法那么两次解析出来的配置就会窜在一起了???)
parse() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public Configuration parse () { if (parsed) { throw new BuilderException ("Each XMLConfigBuilder can only be used once." ); } parsed = true ; parseConfiguration(parser.evalNode("/configuration" )); return configuration; } private void parseConfiguration (XNode root) { try { propertiesElement(root.evalNode("properties" )); Properties settings = settingsAsProperties(root.evalNode("settings" )); loadCustomVfs(settings); loadCustomLogImpl(settings); typeAliasesElement(root.evalNode("typeAliases" )); pluginElement(root.evalNode("plugins" )); objectFactoryElement(root.evalNode("objectFactory" )); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory" )); reflectorFactoryElement(root.evalNode("reflectorFactory" )); settingsElement(settings); environmentsElement(root.evalNode("environments" )); databaseIdProviderElement(root.evalNode("databaseIdProvider" )); typeHandlerElement(root.evalNode("typeHandlers" )); mapperElement(root.evalNode("mappers" )); } catch (Exception e) { throw new BuilderException ("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
2,Java代码构建SqlSessionFactory 下面是直接拷贝的官方文档中Java代码构建SqlSessionFactory的示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 DataSource dataSource = BaseDataTest.createBlogDataSource();TransactionFactory transactionFactory = new JdbcTransactionFactory ();Environment environment = new Environment ("development" , transactionFactory, dataSource);Configuration configuration = new Configuration (environment);configuration.setLazyLoadingEnabled(true ); configuration.setEnhancementEnabled(true ); configuration.getTypeAliasRegistry().registerAlias(Blog.class); configuration.getTypeAliasRegistry().registerAlias(Post.class); configuration.getTypeAliasRegistry().registerAlias(Author.class); configuration.addMapper(BoundBlogMapper.class); configuration.addMapper(BoundAuthorMapper.class); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder ();SqlSessionFactory factory = builder.build(configuration);
对象的作用域
SqlSessionFactoryBuilder (局部方法 )
一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以便释放所有的 XML 资源文件
SqlSessionFactory (全局,整个应用 )
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession (一次请求,局部方法或者说一个线程 )
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:
try (SqlSession session = sqlSessionFactory.openSession()) { // 你的应用逻辑代码 }
解析mappers 解析 mapper
的入口方法是 mapperElement ,取得 mappers 节点下的子节点循环添加。子节点有 package
和 mapper
, 所以解析的入口也不同。一个先获取包下的接口,循环接口去寻找xml,解析,映射,一个通过namespace找接口
加载 package 添加 mapper 接口的操作都是在 configuration 对象的 mapperRegistry
对象属性里进行的,解析出来的接口也都是以 key,value 形式存在一个HashMap中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 public <T> void addMapper (Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException ("Type " + type + " is already known to the MapperRegistry." ); } boolean loadCompleted = false ; try { knownMappers.put(type, new MapperProxyFactory <>(type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder (config, type); parser.parse(); loadCompleted = true ; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } public void parse () { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { loadXmlResource(); configuration.addLoadedResource(resource); assistant.setCurrentNamespace(type.getName()); parseCache(); parseCacheRef(); Method[] methods = type.getMethods(); for (Method method : methods) { try { if (!method.isBridge()) { parseStatement(method); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver (this , method)); } } } parsePendingMethods(); } private void loadXmlResource () { if (!configuration.isResourceLoaded("namespace:" + type.getName())) { String xmlResource = type.getName().replace('.' , '/' ) + ".xml" ; InputStream inputStream = type.getResourceAsStream("/" + xmlResource); if (inputStream == null ) { try { inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource); } catch (IOException e2) { } } if (inputStream != null ) { XMLMapperBuilder xmlParser = new XMLMapperBuilder (inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()); xmlParser.parse(); } } }
其它 SQL注入
仅在字符串中发生
仅在不使用准备好的语句时发生
仅在用户输入时发生
${}与#{}
可以看成是动态sql,在Mybatis中,会在获得Statement执行对象之前对sql进行映射替换
如 SELECT * FROM USER WHERE USER_NAME = ${name} ${name}= “111 OR 1 = 1”
那么在Mybatis解析完sql后,就会变成
SELECT * FROM USER WHERE USER_NAME = 111 OR 1 = 1
是静态sql,Mybatis在解析sql的时候会将 #{name} 替换成 占位符 ?
然后用sql生成 PreparedStatement 对象 pstm
再对对象设查询参数 pstm.setStringParameter(“111 OR 1 = 1”);
这是真正执行的sql 就会变成 SELECT * FROM USER WHERE USER_NAME = ‘111 OR 1 = 1’
Mybatis的参数映射问题 args0,args1… param1,param2…
@Param注册
JDK1.8后不使用Param注解,直接用参数名映射