MyBatis是Apache基金会下的一个持久层框架,并且也是市场中主流的持久层框架,相比较于其他的持久层框架,它支持定制化 SQL、存储过程以及高级映射。
MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs
(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。使用MyBatis我们只需要去更改xml配置文件就可以去改变sql语句,不需要去更改java代码,这是毋庸置疑支持开闭原则
的。
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.11version>
dependency>
其他版本依赖链接:Maven Repository: org.mybatis » mybatis (mvnrepository.com)
使用MyBatis需要有两个xml配置文件,第一个是核心配置文件mybatis-config.xml
对于文件名来说,不需要做硬性规定,之后在Spring的配置中可以修改,不一定就是这个配置文件;第二个是mapping映射配置文件,但是它并不是单独一个文件就可以起作用的,它需要和Java接口共同组成映射器
:
映射器由 Java 接口和 XML 文件(或注解)共同组成,它的作用如下:
映射器的xml配置文件也不是一定需要,在之后我们可以使用MyBatis的注解实现xml配置中的功能,从而省略xml的配置时间。
一般来说,mybatis-config.xml需要有以下的内容
DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties>properties>
<settings>
<setting name="" value=""/>
settings>
<typeAliases>typeAliases>
<typeHandlers>typeHandlers>
<objectFactory type="">objectFactory>
<plugins>
<plugin interceptor="">plugin>
plugins>
<environments default="">
<environment id="">
<transactionManager type="">transactionManager>
<dataSource type="">dataSource>
environment>
environments>
<databaseIdProvider type="">databaseIdProvider>
<mappers>mappers>
configuration>
除去xml头部配置之外,mybatis-config.xml文件只有configuration一个顶层标签项,之后还会在configuration标签中写入很多信息。
以下是MyBatis官方给出的字段参考文档:
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
configuration是MyBatis的xml配置文件顶层标签(根标签,根元素),其他的标签配置都需要写在configuration标签中才能生效。
看到这个标签值我们可以很容易想到,**.properties
这种格式的配置文件,一般来说,在之前的学习里都是用来配置JDBC链接的"四件套":驱动(driver)、资源定位(url)、连接账户(username)、连接密码(password);在这里我们同样可以使用这种配置。
我们可以在maven的项目骨架中的resources文件夹下创建db.properties
文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mybatis
username=root
Password=root
然后在mybatis-config.xml文件下使用properties标签来连接文件
<properties resource="db.properties"/>
链接完后就可以在environments标签下的子标签dataSource 中使用其中的元素了
<environments default="development">
<environment id="mysql_environment">
<transactionManager type="">transactionManager>
<dataSource type="mysql_test">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
setting是指定MyBatis的一些全局配置属性,这是MyBatis中极为重要的调整设置,它们会改变MyBatis的运行时行为,settings中的元素过多,只会记住其中重要的就可以,其余的在有需要的时候查询,更何况settings中的值是有默认属性的:
设置参数 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 该配置影响的所有映射器中配置的缓存的全局开关 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态 | true | false | false |
aggressiveLazyLoading | 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载。 | true | false | true |
multipleResultSetsEnabled | 是否允许单一语句返回多结果集(需要兼容驱动)。 | true | false | true |
useColumnLabel | 使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 | true | false | true |
useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 | true | false | False |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 | SIMPLE REUSE BATCH | SIMPLE |
defaultStatementTimeout | 设置超时时间,它决定驱动等待数据库响应的秒数。 | Any positive integer | Not Set (null) |
defaultFetchSize | Sets the driver a hint as to control fetching size for return results. This parameter value can be override by a query setting. | Any positive integer | Not Set (null) |
safeRowBoundsEnabled | 允许在嵌套语句中使用分页(RowBounds)。 | true | false | False |
mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 | true | false | False |
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 | SESSION | STATEMENT | SESSION |
jdbcTypeForNull | 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER | OTHER |
lazyLoadTriggerMethods | 指定哪个对象的方法触发一次延迟加载。 | A method name list separated by commas | equals,clone,hashCode,toString |
defaultScriptingLanguage | 指定动态 SQL 生成的默认语言。 | A type alias or fully qualified class name. | org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver |
callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。 | true | false | false |
logPrefix | 指定 MyBatis 增加到日志名称的前缀。 | Any String | Not set |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | Not set |
proxyFactory | 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。 | CGLIB | JAVASSIST | JAVASSIST (MyBatis 3.3 or above) |
<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="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>
在后续的mapping映射配置文件中,需要配置用于封装的POJO类,和sql语句参数设置需要的类,这里一般来说会使用到很多的全路径类,比如String类就需要写成java.lang.String
,这样mapping才能找到String来通过反射来创建,但是这样的全路径显示过于繁琐,所以我们就可以在typeAliases标签中设置别名:
<typeAliases>
<typeAlias type="java.lang.String" alias="str">typeAlias>
<package name="java.lang"/>
typeAliases>
我们所学习的每一门编程语言都有自己特殊的类型体系,比如java作为强类型语言,需要给每一个变量都定义一个类型:**int、long、float、double、char等等基础类型,除此之外还有String、StringBuffer等等这些底层为基础类型的封装类;那么我们的数据库作为一个独立的存储系统,对于数据类型格式的要求也是需要相当的严格,比如mysql中的:int、varchar、date等等;**那么我们将从数据库中查出来的数据进行封装,成为一个POJO对象,其中两个系统不同的数据类型格式该如何对应和转换呢;这就是typeHandlers标签的作用:我们可以在标签下对两个数据库中不同类型的数据进行配置来一一对应;
<typeHandlers>
<typeHandler handler="" javaType="" jdbcType=""/>
typeHandlers>
MyBatis中默认类型的转换类,一般来说简单的数据封装也不用自己实现TypeHandler接口。
对象工厂顾名思义可以想到设计模式中的工厂方法,而在org.apache.ibatis.reflection.factory.DefaultObjectFactory
类中,使用java中泛型的特性,使得更加符合抽象工厂的特点。
public <T> T create(Class<T> type) {
return this.create(type, (List)null, (List)null);
}
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
Class<?> classToCreate = this.resolveInterface(type);
return this.instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
}
在org.apache.ibatis.reflection.factory.DefaultObjectFactory
类的结构上来看能够给子类继承的有:空参构造方法、两个create方法,和判空的isCollection方法、和使用protected修饰的resolveInterface方法。
而在resolveInterface方法中将传入的class对象进行判断是否为集合类或者迭代器等等,再进行更细致的判断,赋予创建的Class对象其子类的特性;之后在由create方法调用,最后使用instantiateClass方法通过反射出构造方法创建出对象。
protected Class<?> resolveInterface(Class<?> type) {
Class classToCreate;
if (type != List.class && type != Collection.class && type != Iterable.class) {
if (type == Map.class) {
classToCreate = HashMap.class;
} else if (type == SortedSet.class) {
classToCreate = TreeSet.class;
} else if (type == Set.class) {
classToCreate = HashSet.class;
} else {
classToCreate = type;
}
} else {
classToCreate = ArrayList.class;
}
return classToCreate;
}
--------------------------------------------------------------------------------------
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
Class<?> classToCreate = this.resolveInterface(type);
return this.instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
}
---------------------------------------------------------------------------------------
private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
try {
Constructor constructor;
if (constructorArgTypes != null && constructorArgs != null) {
constructor = type.getDeclaredConstructor((Class[])constructorArgTypes.toArray(new Class[0]));
try {
return constructor.newInstance(constructorArgs.toArray(new Object[0]));
} catch (IllegalAccessException var7) {
if (Reflector.canControlMemberAccessible()) {
constructor.setAccessible(true);
return constructor.newInstance(constructorArgs.toArray(new Object[0]));
} else {
throw var7;
}
}
} else {
constructor = type.getDeclaredConstructor();
try {
return constructor.newInstance();
} catch (IllegalAccessException var8) {
if (Reflector.canControlMemberAccessible()) {
constructor.setAccessible(true);
return constructor.newInstance();
} else {
throw var8;
}
}
}
} catch (Exception var9) {
String argTypes = (String)((List)Optional.ofNullable(constructorArgTypes).orElseGet(Collections::emptyList)).stream().map(Class::getSimpleName).collect(Collectors.joining(","));
String argValues = (String)((List)Optional.ofNullable(constructorArgs).orElseGet(Collections::emptyList)).stream().map(String::valueOf).collect(Collectors.joining(","));
throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + var9, var9);
}
}
我们可以继承DefaultObjectFactory类来实现我们自定义的对象工厂,或者我们观察DefaultObjectFactory的结构:
public class DefaultObjectFactory implements ObjectFactory, Serializable
DefaultObjectFactory也是实现ObjectFactory接口,所以我们可以进一步直接实现ObjectFactory接口,同样可以来创建自定义的对象工厂。
在mybatis-config.xml配置文件中我们需要这样去填写:
<objectFactory type="cn.sleep.mybatis_test.TestObjectFactory">
<property name="***key***" value="***value***"/>
objectFactory>
这样就可以使用我们自定义的工厂类了。
plugins直译过来的意思是“插件”,但是在MyBatis中,plugins对应的是拦截器
,对等的就是web开发中的过滤器(拦截器)。
在传统的原生JDBC执行sql语句的流程中拥有以下:
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
try {
Connection connection = DriverManager.getConnection("url","username","password");
} catch (SQLException e) {
throw new RuntimeException(e);
}
String sql = "................?";
PreparedStatement pre = connection.prepareStatement(sql);
ResultSet rs = pre.executeQuery();
connection.close();
pre.close();
rs.close();
但是在MyBatis中,这中流程过于繁琐,并且对于不同的封装类,要执行不同的Sql语句,从而需要定义不同的方法,虽然可以在之后可以使用泛型和反射来简化抽象这个过程,但是相较于MyBatis的实现还是比较繁琐。
在MyBatis中,这个过程进行了相应的简化。MyBatis执行的过程中拥有“四大对象”,与原生JDBC相类似:
那么为什么MyBatis要使用这样的四大对象来执行sql语句呢?这就需要涉及到面向切面编程
的思想了:
面向切面编程(AOP)就是将与核心业务无关的,但又影响着多个类的公共行为抽取、封装到一个可重用模块,从而实现代码复用和模块解耦的目的,这种开发思想则被称为面向切面编程。
那原生的JDBC公共行为是什么呢?
传参,获取结果集,执行、和预编译,那么我们将这些提取出来后,JDBC的核心业务是什么呢?那不就是执行不同的sql语句吗?MyBatis恰恰将这四种公共的执行过程提取封装起来。
上述这四大对象和pulgins有相应的关系吗?有的,pulgins恰恰是用来拦截这四大对象的。
Mybatis可以对这四个接口中所有的方法进行拦截。
我们如果想要实现拦截器只需要去实现Interceptor 接口,但是如果你不是非常了解拦截器各个方法的行为就去实现并配置的话,很有可能会破坏Mybatis的核心模块。
至于详细的去配置一个plugins可以去参考MyBatis 插件(plugins)介绍 - MyBatis中文官网
environments(环境配置)
MyBatis作为一个兼容性较好的持久层框架,它能够配置多个数据库环境,比如连接多个数据库,只需要在environments下配置多个子标签environment就可以了。
关于不同数据库之前的切换,environment子标签可以有多个,但是id属性是唯一的,我们可以修改environments标签中的default属性使其与某个environment中的id值相同就表示选中了这个环境。
`
<environments default="sleep_mysql_environment">
<environment id="sleep_mysql_environment">
<transactionManager type="JDBC">
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>
比如上述中environments中的default属性与environment中的id相同,就代表现在MyBatis选中了“sleep_mysql_environment”这个环境。
environment(环境变量)
一个environment标签代表一个环境变量,一个环境变量对应一个数据库,我们可以定义多个environment标签来操作多个数据库,这是MyBatis中最基本的标签,其他大部分标签都有默认的处理类,但是environment如果不配置则不能连接到数据库。
transactionManager(事务管理器)
MyBatis中有两种类型的事务管理器,对应的transactionManager标签type属性只有两个值**[JDBC|MANAGED]**
JDBC – 这个配置直接使用了 JDBC 的提交和回滚功能,它依赖从数据源获得的连接来管理事务作用域。。默认情况下,为了与某些驱动程序兼容,它在关闭连接时启用自动提交。然而,对于某些驱动程序来说,启用自动提交不仅是不必要的,而且是一个代价高昂的操作。因此,从 3.5.10 版本开始,你可以通过将 “skipSetAutoCommitOnClose” 属性设置为 “true” 来跳过这个步骤。例如:
<transactionManager type="JDBC">
<property name="skipSetAutoCommitOnClose" value="true"/>
transactionManager>
MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期。比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:
<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>
dataSource(数据源)
数据源这一块,在JDBC阶段都有接触到,c3p0、阿里旗下的德鲁伊Druid都是数据源,而在MyBatis中数据源一共有三种**[UNPOOLED|POOLED|JNDI],不同的标签需要配置的属性也不尽相同,但是与JDBC最接近的是UNPOOLED**:
这三种数据源属性都必须写在dataSource中的type属性中才能生效。
driver
– 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。url
– 这是数据库的 JDBC URL 地址。username
– 登录数据库的用户名。password
– 登录数据库的密码。defaultTransactionIsolationLevel
– 默认的连接事务隔离级别。defaultNetworkTimeout
– 等待数据库操作完成的默认网络超时时间(单位:毫秒)。这也是最简单实现的数据源,而其他两种都是为了解决不同的场景中的问题而出现的,最常用的还是UNPOOLED。
可以参考:mybatis – MyBatis 3 | 配置
databaseIdProvider(数据库厂商标识)
MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId
属性。不同数据库厂商的sql语句会带有差异,称之为Sql方言
,MyBatis解决Sql方言的办法就是在映射语句中添加databaseId属性。
<select id="selectUser" databaseId="MySQL" parameterType="User" resultType="User">
.....
select>
上述查询语句,只需要为databaseId属性设置一个值,就可以指定MyBatis去MySQL的数据源中执行Sql语句。
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
databaseIdProvider>
可以通过上述语句来设置别名,简化厂商名的书写。
你可以通过实现接口 org.apache.ibatis.mapping.DatabaseIdProvider
并在 mybatis-config.xml 中注册来构建自己的 DatabaseIdProvider:
mappers(映射器)
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:///
形式的 URL),或类名和包名等。例如:
这些配置会告诉 MyBatis 去哪里找映射文件,剩下的细节就应该是每个 SQL 映射文件了。
mappers的配置只是指定了mapper映射文件查找的路径,并不能通过mappers去配置映射,MyBatis中的映射是需要mapper.xml和java代码共同实现的,mappers只是指定了路径。
写了部分注释的mybatis-config.xml文件
DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties"/>
<typeAliases>
<typeAlias type="java.lang.String" alias="str">typeAlias>
<package name="java.lang"/>
typeAliases>
<typeHandlers>
<typeHandler handler="" javaType="" jdbcType=""/>
typeHandlers>
<objectFactory type="cn.sleep.mybatis_test.TestObjectFactory">
<property name="***key***" value="***value***"/>
objectFactory>
<environments default="development">
<environment id="mysql_environment">
<transactionManager type="JDBC">
transactionManager>
<dataSource type="mysql_test">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
databaseIdProvider>
configuration>
配置文件参考链接:
MyBatis 配置文件详解 - MyBatis中文官网
MyBatis 插件(plugins)介绍 - MyBatis中文官网
上述问题解决的最详细的还是这一篇官方文档,事无巨细的解释了所有标签:
mybatis – MyBatis 3 | 配置