Mybatis(二)
Mybatis的config文件配置
- properties标签
properties为属性配置文件,它为上下文提供相关的资源,properties有三种配置资源的方式:
方式一:通过properties子元素配置
通过properties子元素property配置username和password变量,然后在properties下文environment节点中引用这些变量
<properties>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>
在environments节点中引用username和password变量
<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>
方式二:通过properise的属性配置
<properties resource="dbConfig/db.properties" />
这里我们是在maven的resources下创建的dbConfig/db.properties
方式三:通过属性参数传递配置
即把属性传递到SqlSessionFactoryBuilder.build()中,让初始化Configuration时将值传递进去
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);
//重载
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props);
Tip:当在mybatis配置文件中同时存在三种方式时,优先级顺序如下:
(1)首先读取properties子元素属性
(2)其次读取properties元素的属性(resource和url)
(3)最后读取作为方法参数传递的属性,并覆盖以读取的同名属性
三种方式,存在优先级,且排在后面的配置覆盖排在前面同名属性的配置,鉴于此,建议在配置时,不要使用混合方式。
- settings属性
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。
- cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 有效值true | false
默认值:true
- lazyLoadingEnabled 是否开启懒加载 默认false
- useGeneratedKeys 是否开启主键自增 默认false
等等
完整的如下:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
详细的内容可参考Mybatis的官方文档:https://mybatis.org/mybatis-3/zh/configuration.html#settings
- typeAliases类别名
类型别名为Java类型设置一个简短名字,它只与xml有关,用来减少类完全限定名的冗余
<typeAliases>
<typeAlias type="com.xx.xx.xx(全类名)" alias="自定义别名"/>
</typeAliases>
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
<typeAliases>
<package name="com.sang.entity" />
</typeAliases>
这样在com.sang.entity
以及其子包下的所有JavaBean都会使用 JavaBean的首字母小写的非限定类名来作为它的别名,当然,存在问题:当子包中出现类名相同的情况
这时可以使用注解@Alias("自定义别名")
加在多个名重复的类上,那么就会分开识别。
-
typeHandlers
typeHandlers主要将获取的值合理地转化为java类型,可以转换预处理阶段(PreparedStatement)的参数和结果集中的值。mybatis提供了标准的类型处理,详细请参照官网:http://www.mybatis.org/mybatis-3/zh/configuration.html#typeHandlers
-
plugins
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
执行器
- ParameterHandler (getParameterObject, setParameters)
参数处理器
- ResultSetHandler (handleResultSets, handleOutputParameters)
结果映射处理器
- StatementHandler (prepare, parameterize, batch, update, query)
sql处理器
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
public Object intercept(Invocation invocation) throws Throwable {
// implement pre processing if need
Object returnObject = invocation.proceed();
// implement post processing if need
return returnObject;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行底层映射语句的内部对象。
- environments
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,
现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的
SQL 映射。还有许多类似的使用场景。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<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>
注意一些关键点:
默认使用的环境 ID(比如:default="development")。
每个 environment 元素定义的环境 ID(比如:id="development")。
事务管理器的配置(比如:type="JDBC")。
数据源的配置(比如:type="POOLED")。
默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。
事务管理器(transactionManager)
在 MyBatis 中有两种类型的事务管理器,type的值
- JDBC – 这个
配置直接使用了 JDBC 的提交和回滚设施
,它依赖从数据源获得的连接来管理事务作用域。 - MANAGED – 这个配置几乎没做什么。它
从不提交或回滚一个连接
,而是让容器来管理事务的整个生命周期
(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:
<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>
如果你正在使用 Spring + MyBatis,
则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。
这两种事务管理器类型都不需要设置任何属性。它们其实是类型别名,换句话说,你可以用 TransactionFactory
接口实现类的全限定名或类型别名代替它们。dataSource 的type:
- UNPOOLED:每次连接都需要开启和关闭连接
- POOLED:连接池的概念
- JNDI :这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性
- databaseIdProvider
MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis
会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId
和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性,只要像下面这样在 mybatis-config.xml
文件中加入 databaseIdProvider 即可:
<databaseIdProvider type="DB_VENDOR" />
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
- mappers
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句
。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。
第一种:resource:在类路径下的mapper的xml配置文件
这里是在maven环境下,文件名需要/分离
<mappers>
<mapper resource="com/sang/dao/StudentDao.xml"/>
</mappers>
第二种:url:使用完全限定资源定位符
可以是磁盘的文件
<mappers>
<mapper url="file:///com/sang/dao/StudentDao.xml"/>
</mappers>
第三种:class:接口全限定名称
<mappers>
<mapper url="com.sang.dao.StudentDao"/>
</mappers>
第四种:package作为包扫描:接口的包全类名
<mappers>
<package name="com.sang.dao"/>
</mappers>
Mybatis运行原理
初始化Configuration对象
public class MybatisUtil {
private static SqlSessionFactory sessionFactory =null;
private static String configPath="mybatisConfig/mybatisConfig.xml";
static{
try {
InputStream inputStream = Resources.getResourceAsStream(configPath);
sessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlsession(){
SqlSession sqlSession=null;
if(sessionFactory!=null){
sqlSession= sessionFactory.openSession();//openSession(true)表示事务自己控制
}
return sqlSession;
}
}
根据编写好的Mybatis配置文件,创建出流对象,SqlSessionFactoryBuilder根据流对象在底层初始化Configuration对象
点击build()方法进入源码分析:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
// XMLConfigBuilder是专门解析mybatis的配置文件的类
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//这里又调用了一个重载方法。parser.parse()的返回值是Configuration对象
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
这里的XMLConfiguration
是根据流对象xml文件中的标签及其属性进行封装,this.build(parser.parse())
随后进行调用方法也就是返回Configuration对象
也就是说,
初始化配置文件信息的本质就是创建Configuration对象,将解析的xml数据封装到Configuration内部的属性中。
SqlSession对象
SqlSession是一个接口,它有两个实现类:
DefaultSqlSession
(默认)和SqlSessionManager(弃用,不做介绍)
SqlSession是MyBatis中用于和数据库交互的接触类,通常将它与ThreadLocal绑定,一个连接会话使用一个SqlSession,并且在使用完毕后需要close关闭。
private final Configuration configuration;
private final Executor executor;
SqlSession中的两个最重要的参数,configuration
就是上文的流对象创建返回的Configuration对象,Executor为执行器
进入openSession方法中查看源码:
// 进入openSession方法。
public SqlSession openSession() {
//getDefaultExecutorType()传递的是SimpleExecutor
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
//进入openSessionFromDataSource。
//ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别,autoCommit是否开启事务
//openSession的多个重载方法可以指定获得的SeqSession的Executor类型和事务的处理
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//根据参数创建指定类型的Executor
final Executor executor = configuration.newExecutor(tx, execType);
//返回的是DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
Executor
Executor也是一个接口,他有三个常用的实现类
BatchExecutor
(重用语句并执行批量更新),ReuseExecutor
(重用预处理语句prepared statements),SimpleExecutor
(普通的执行器,默认)。
SqlSession API方法
点击Sqlsession接口进入源码,发现许多增删改查的方法
举一例子:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//根据传入的全限定名+方法名从映射的Map中取出MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
//调用Executor中的方法处理
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();
}
}
关键:
1
根据传入的全限定名+方法名从映射的Map中取出MappedStatement对象
2调用Executor中的方法处理
MappedStatement
- 作用: MappedStatement与Mapper配置文件中的一个select/update/insert/delete标签结点相对应。mapper中配置的标签都被封装到了此对象中,
主要用途是描述一条SQL语句
。- 初始化过程:回顾刚开始介绍的加载配置文件的过程中,会对mybatis-config.xml中的各个标签都进行解析,其中有 mappers标签用来注册mapper.xml文件或者配置mapper接口的目录。
例如:
<select id="findStudentById" resultType="student">
select * from student where id=#{id}
</select>
这样的在mapper文件的一个标签,在初始化Configuration对象
时,被解析封装成一个MappedStatement
对象,然后存储在Configuration对象的mappedStatements
属性中,mappedStatements 是一个HashMap
,存储时key = 全类名 + 方法名
,value = 对应的MappedStatement对象
。
在Configuration对象中MappedStatements属性如下:
Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
最后 executor.query()进行执行根据map中根据全类名.方法名查到的sql语句(也就是MappedStatement
)对象
接口式执行原理
单元测试如下:
@Test
public void Test() throws IOException {
InputStream inputStream= Resources.getResourceAsStream("mybatisConfig/mybatisConfig.xml");
SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sessionFactory = factoryBuilder.build(inputStream);
SqlSession sqlSession = sessionFactory.openSession();
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
Student student = studentDao.findStudentById(1);
System.out.println(student);
sqlSession.close();
}
与原生方式在初始化configuration对象到创建sqlsession对象相似,但是
sqlsession对象调用getMapper对象获得接口的字节码对象进行创建代理对象
这是关键!
先说明一下
MyBatis初始化时对接口的处理
:MapperRegistry是Configuration中的一个属性,它内部拥有一个属性HashMap
用于存放mapper接口的工厂类,每个接口对应一个工厂类。mappers中可以配置接口的包路径,或者某个具体的接口类。
<mappers>
<package name="com.sang.dao"/>
<!-- <mapper resource="com/sang/dao/StudentDao.xml"/>-->
</mappers>
- 当解析mappers标签时,它会判断解析到的是mapper配置文件时,会再将对应配置文件中的增删改查标签一 一封装成MappedStatement对象,存入mappedStatements中。
- 当判断解析到接口时,
会创建此接口对应的MapperProxyFactory对象
,存入HashMap中,key =接口的字节码对象
,value =此接口对应的MapperProxyFactory对象。
动态代理在第一篇已经写有
在动态代理返回了实例后,我们就可以直接调用mapper类中的方法了,说明在MapperProxy中的invoke方法中已经为我们实现了方法。
接口class文件和mapper.xml文件位置分析
我们都知道:在config文件中配置
<mappers>
<package name="com.sang.dao"/>
<!-- <mapper resource="com/sang/dao/StudentDao.xml"/>-->
</mappers>
然后mybatis根据config.xml配置文件加载后初始化configuration对象,在这里方法中定义了一个while死循环,主要是便利mappers节点下面的所有元素。因为我们采用的是在主配置文件中使用package扫描的方式挂载的mapper映射文件。所以跳入if代码块。在if块中通过获取name属性的值,拿到了mapper文件的所属的包名
,通过configuration对象调用addMappers方法把mapper映射文件所在的包传入。如:
创建ResolverUtil工具类,通过调用find方法把包下面的字节码对象找出来,并存入到Set集合中,通过调用getClasses方法取出,进行遍历。把每一个字节码对象传入addMapper方法。如
意思就是:在初始化configuration对象的时候,对xml文件进行提取属性,判断用了package标签后,就行添加指定的mapper.xml文件,
并且通过mapper.xml文件的位置进行查找相同名字的java字节码文件
,将其加载到hashMap中去
所以,mapper文件和接口文件在最后的生成的target目录中必须在同一目录