文章目录
- 第四章 MyBatis的核心组件
- 4.1 使用MyBatis操作数据库
- 4.2 MyBatis核心组件
- 4.3 Configuration组件
- 4.3.1 属性
- 4.3.2 设置
- 4.3.3 类型别名
- 4.3.3 类型处理器
- 4.3.5 对象工厂
- 4.3.6 插件
- 4.3.7 配置环境
- 4.3.8 映射器
第四章 MyBatis的核心组件
4.1 使用MyBatis操作数据库
在研究MyBatis的核心组件之前,有必要了解一下如何使用MyBatis操作数据库。本文使用maven创建一个简单的示例项目来演示。
(1)准备数据库环境:使用MySQL数据库,创建一个新的database:mybatis_demo,新的表user,并插入数据。
CREATE DATABASE mybatis_demo;
USE mybatis_demo;
CREATE TABLE `user` (
`id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT(11) DEFAULT NULL,
`phone` VARCHAR(20) DEFAULT NULL,
`birthday` DATETIME DEFAULT NULL
)
insert into user (name, age, phone, birthday) values('孙悟空', 1500, '18705464523', '0000-01-01');
insert into user (name, age, phone, birthday) values('猪八戒', 1000, '15235468789', '0500-03-10');
(2)创建maven项目,导入相关依赖。
<dependencies>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.15</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
(3)编写MyBatis主配置文件mybatis-config.xml。
<?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>
<settings>
<!--支持返回自动生成主键-->
<setting name="useGeneratedKeys" value="true"/>
<!--支持实体名驼峰原则-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!--别名处理-->
<typeAliases>
<package name="com.star.mybatis.entity"/>
</typeAliases>
<!--环境信息-->
<environments default="dev">
<environment id="dev">
<!--事务管理器-->
<transactionManager type="JDBC"/>
<!--数据源:用户名和密码需要改成自己定义的-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/>
<property name="username" value="******"/>
<property name="password" value="******"/>
</dataSource>
</environment>
</environments>
<!--Mapper映射文件-->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
MyBatis主配置文件用于配置MyBatis框架的配置信息,这些参数会改变MyBatis的运行时行为。
(4)编写Java实体User。
public class User {
private Integer id;
private String name;
private Integer age;
private String phone;
private Date birthday;
// constructor getter setter ...
}
(5)编写UserMapper接口。
public interface UserMapper {
List<User> selectAll();
@Select("select * from user where id = #{id, jdbcType=INTEGER}")
User selectById(@Param("id") Integer id);
}
为了演示方便,这里使用两种方式配置SQL语句,selectAll()
方法用XML的方式,selectById()
方法用注解的方式。
(6)编写映射文件UserMapper.xml。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.star.mybatis.mapper.UserMapper">
<select id="selectAll" resultType="User">
select * from user
</select>
</mapper>
(7)编写单元测试。
@Test
public void testMybatis() throws IOException {
// 读取配置文件
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
// 创建会话
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取Mapper接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 操作数据库
List<User> userList = userMapper.selectAll();
userList.forEach(System.out::println);
System.out.println("---------");
User user = userMapper.selectById(1);
System.out.println(user.toString());
}
(8)执行单元测试,控制台打印结果。
User{id=1, name='孙悟空', age=1500, phone='18705464523', birthday=Thu Jan 01 00:00:00 CST 1}
User{id=2, name='猪八戒', age=1000, phone='15235468789', birthday=Fri Mar 10 00:00:00 CST 500}
---------
User{id=1, name='孙悟空', age=1500, phone='18705464523', birthday=Thu Jan 01 00:00:00 CST 1}
如示例代码所示,SqlSession是MyBatis提供的与数据库交互的接口,其实例通过工厂模式创建。
为了创建SqlSession对象,首先需要创建SqlSessionFactory对象,而SqlSessionFactory对象的创建又依赖于SqlSessionFactoryBuilder类,该类提供了一系列重载的build()
方法,将主配置文件的输入流作为参数传入SqlSessionFactoryBuilder对象的build()
方法,则可以返回一个SqlSessionFactory对象。
有了SqlSessionFactory对象后,调用其openSession()
方法即可获得一个与数据库建立连接的SqlSession实例。
调用SqlSession对象的getMapper()
方法创建一个Mapper接口的动态代理对象,然后调用代理实例的方法即可完成与数据库的交互。
需要注意的是,MyBatis来源于iBatis项目,依然保留了iBatis执行Mapper的方式,例如:
// 兼容iBatis执行Mapper的方式
List<User> userList = sqlSession.selectList("com.star.mybatis.mapper.UserMapper.selectAll");
另外,MyBatis还提供了一个SqlSessionManager类,同样可以用于与数据库的交互,例如可以把单元测试方法修改成如下:
@Test
public void testSqlSessionManager() throws IOException {
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
// 创建SqlSessionManager对象
SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(reader);
sqlSessionManager.startManagedSession();
// 通过SqlSessionManager获取Mapper接口
UserMapper userMapper = sqlSessionManager.getMapper(UserMapper.class);
List<User> userList = userMapper.selectAll();
userList.forEach(System.out::println);
System.out.println("---------");
User user = userMapper.selectById(1);
System.out.println(user.toString());
}
如示例代码所示,SqlSessionManager使用了单例模式,在整个应用程序中只存在一个实例。通过该类的静态方法newInstance()
即可获取SqlSessionManager的实例。
SqlSessionManager实现了SqlSessionFactory和SqlSession接口,因此可以替代SqlSession完成与数据库的交互。
4.2 MyBatis核心组件
在上面的示例项目中,有一个比较核心的MyBatis组件——SqlSession,它是面向用户的操作数据库的API。除此之外,MyBatis还有一些其他的核心组件,其执行流程如下图所示:
这些核心组件的作用大致描述如下:
- Configuration
用于描述MyBatis的主配置信息。其他组件需要获取配置信息时,可以直接通过Configuration对象获取。MyBatis在应用启动时,会读取主配置文件mybatis-config.xml中的配置,并将这些配置注册到Configuration组件中,其他组件需要这些配置时,可以直接通过Configuration对象获取。
- MappedStatement
用于描述Mapper中的SQL配置信息,是对Mapper XML配置文件中的、、、等标签或者@Select、@Update、@Delete、@Insert等注解的封装。
- SqlSession
是MyBatis提供的面向用户的API,表示和数据库交互时的会话对象,用于完成数据库的增删改查功能。SqlSession是Executor组件的外观,目的是对外提供易于理解和使用的数据库操作接口。
- Executor
Executor是MyBatis的SQL执行器,MyBatis中对数据库的所有增删改查操作都是由Executor组件完成的。
- StatementHandler
StatementHandler封装了对JDBC的Statement对象的操作,比如为Statement对象设置参数、调用Statement接口提供的方法与数据库交互等。
- ParameterHandler
当MyBatis使用的Statement类型为PreparedStatement和CallableStatement时,ParameterHandler用于为Statement对象的参数占位符设置值。
- ResultSetHandler
ResultSetHandler封装了对JDBC中的ResultSet对象的操作,当执行SQL类型为SELECT语句时,ResultSetHandler用于将查询结果转换为Java对象。
- TypeHandler
TypeHandler是MyBatis的类型处理器,用于处理Java类型与JDBC类型之间的映射。它能够根据Java类型调用PreparedStatement或CallableStatement对象的setXXX()
方法为Statement对象设置值,而且能够根据Java类型调用ResultSet对象的getXXX()
方法获取SQL执行结果。
MyBatis操作数据库的过程大致如下:
- SqlSession是用户层面的API,它是Executor组件的外观,目的是为用户提供更友好的数据库操作接口。
- 真正执行SQL操作的是Executor组件,它是SQL执行器,会使用StatementHandler组件对JDBC的Statement对象进行操作。
- 当Statement类型为PreparedStatement和CallableStatement时,会通过ParameterHandler组件为参数占位符赋值。
- ParameterHandler组件会根据Java类型找到对应的TypeHandler对象,TypeHandler中会通过Statement对象提供的
setXXX()
方法为Statement对象中的参数占位符设置值。 - StatementHandler组件使用JDBC中的Statement对象与数据库完成交互后,当SQL语句类型为SELECT时,通过ResultSetHandler组件从Statement对象中获取ResultSet对象,然后将ResultSet对象转换为Java对象。
4.3 Configuration组件
MyBatis框架的配置信息有两种,一种是配置MyBatis框架属性的主配置文件,另一种是配置执行SQL语句的Mapper配置文件。
Configuration组件的作用是描述MyBatis主配置文件的信息,即mybatis-config.xml文件。
4.3.1 属性
在mybatis-config.xml文件中,属性使用标签进行配置,其特点是可外部配置和可动态替换。例如:
<!-- mybatis-config.xml -->
<properties resource="C:\workspace\mybatis_demo2\config\config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="*****"/>
</properties>
可外部配置的意思是,属性既可以在标签下使用标签直接配置,也可以通过resource属性引用外部的一个properties文件。如果两种方式都配置了属性,则优先加载标签下的属性,再加载resource路径下properties文件中的属性,并覆盖已有的同名属性。
可动态替换的意思是,标签下设置的属性,可以在整个配置文件中用于替换需要动态配置的属性值。例如,在配置文件的其它位置使用<property name="username" value="${username}"/>
时,${username}
会被替换为dev_user
。
在Configuration组件中,组合了一个Properties对象,用于保存属性配置。
源码1:org.apache.ibatis.session.Configuration
protected Properties variables = new Properties();
借助Debug工具,可以看到属性均被加载到Configuration组件中:
4.3.2 设置
在mybatis-config.xml文件中,属性使用标签进行配置,这些设置属性的值会改变MyBatis运行时的行为,因此非常重要。
相对应的,Configuration组件定义了一系列属性来保存这些设置属性。例如:
<!-- mybatis-config.xml -->
<settings>
<!--支持返回自动生成主键-->
<setting name="useGeneratedKeys" value="true"/>
<!--支持自动驼峰命名规则-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
对应到Configuration组件定义的属性是:
源码2:org.apache.ibatis.session.Configuration
public class Configuration {
protected boolean mapUnderscoreToCamelCase;
protected boolean useGeneratedKeys;
// ......
}
除了 源码1 中列出的2个设置属性,还有一系列属性,这些属性的作用、默认值等可以参见 W3Cschool的文档-MyBatis教程-XML配置 中的一张表。
借助Debug工具,可以看到设置属性被加载到Configuration组件中:
4.3.3 类型别名
类型别名就是为Java类型设置一个短的名字,用来减少类的完全限定名的冗余。例如使用User
作为com.star.mybatis.entity.User
的别名:
在mybatis-config.xml文件中,类型别名使用标签进行配置。例如:
<!-- mybatis-config.xml -->
<typeAliases>
<typeAlias alias="User" type="com.star.mybatis.entity.User"/>
</typeAliases>
也可以指定一个包名,MyBatis会在包下面搜索需要的Java对象,例如:
<!-- mybatis-config.xml -->
<typeAliases>
<package name="com.star.mybatis.entity"/>
</typeAliases>
在没有@Alias注解的情况下,MyBatis会使用Java对象的首字母小写的非限定类名来作为它的别名,例如com.star.mybatis.entity
包下的User类的别名为user
;若该类有@Alias注解,则别名为其注解值。
@Alias("user")
public class User {...}
MyBatis本身已经为许多常见的Java类型内置了相应的类型别名,如下表:
对应到Configuration组件,组合了一个类型别名注册器,用于注册所有的类型别名。其源码如下:
源码3:org.apache.ibatis.session.Configuration
// 用于注册所有的类型别名
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
借助Debug工具,可以看到加载到Configuration组件中的别名有81个,其中包括自定义的User类的别名:
4.3.3 类型处理器
类型处理器的作用在于,MyBatis在预处理语句(PreparedStatement)中设置一个参数时,或者从ResulrSet结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成JDBC类型或Java类型。
在mybatis-config.xml文件中,类型别名使用标签进行配置。例如:
<!-- mybatis-config.xml -->
<typeHandlers>
<typeHandler handler="com.star.mybatis.handler.ExampleTypeHandler"
javaType="String"
jdbcType="VARCHAR"/>
</typeHandlers>
MyBatis有许多默认的类型处理器实现,基本涵盖Java全部基本数据类型,如下表所示:
另外,类型处理器支持重写,或创建自定义的类型处理器来处理不支持的或非标准的类型。
具体做法是:实现 org.apache.ibatis.type.TypeHandler
接口, 或继承org.apache.ibatis.type.BaseTypeHandler
,然后在主配置文件mybatis-config.xml中使用标签进行配置。例如:
@MappedTypes({String.class})
@MappedJdbcTypes({JdbcType.VARCHAR})
public class ExampleTypeHandler extends BaseTypeHandler {...}
那如何确定这个自定义的类型处理器是处理什么类型的呢?
设置类型处理器处理的Java类型:
- 在类型处理器的配置元素上增加一个javaType属性,如:
<typeHandler javaType="String"/>
; - 在类型处理器的类上增加一个@MappedTypes注解,如:
@MappedTypes({String.class})
。 如果同时配置javaType属性和@MappedTypes注解,则注解方式将被忽略。
设置类型处理器处理的JDBC类型:
- 在类型处理器的配置元素上增加一个jdbcType属性,如:
<typeHandler jdbcType="VARCHAR"/>
; - 在类型处理器的类上增加一个@MappedJdbcTypes注解,如:
@MappedJdbcTypes({JdbcType.VARCHAR})
。 如果同时配置jdbcType属性和@MappedJdbcTypes注解,则注解方式将被忽略。
最后,还可以通过设置包名让MyBatis自动查找类型处理器,此时只能通过注解方式来指定类型处理器要处理的Java类型和JDBC类型。例如:
<!-- mybatis-config.xml -->
<typeHandlers>
<package name="com.star.mybatis.handler"/>
</typeHandlers>
在Configuration组件中,组合了一个类型处理器的注册器,用于注册所有的类型处理器。
源码4:org.apache.ibatis.session.Configuration
// 用于注册所有的类型处理器
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
借助Debug可知,Configuration组件中最终注册的类型处理器一共有41个,包括1个自定义的ExampleTypeHandler:
4.3.5 对象工厂
MyBatis每次创建结果映射对象的新实例时,会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。
在mybatis-config.xml文件中,对象工厂使用标签进行配置。例如:
<!-- mybatis-config.xml -->
<objectFactory type="com.star.mybatis.factory.ExampleObjectFactory">
<property name="name" value="test"/>
</objectFactory>
通过创建自定义的对象工厂,可以覆盖对象工厂的默认行为。例如:
public class ExampleObjectFactory extends DefaultObjectFactory {
// 处理默认构造方法
@Override
public <T> T create(Class<T> type) {
return super.create(type);
}
// 处理带参数的构造方法
@Override
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
return super.create(type, constructorArgTypes, constructorArgs);
}
// 配置ObjectFactory属性
@Override
public void setProperties(Properties properties) {
}
}
在Configuration组件中,组合了一个ObjectFactory对象,用于保存对象工厂,默认实现类为DefaultObjectFactory。
源码5:org.apache.ibatis.session.Configuration
protected ObjectFactory objectFactory = new DefaultObjectFactory();
借助Debug可知,Configuration组件最终会将自定义的对象工厂注册到Configuration组件中:
4.3.6 插件
MyBatis允许在SQL语句执行过程中的某一点使用插件进行拦截调用。默认情况下,允许使用插件来拦截的方法包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
自定义一个插件比较简单,只需实现Interceptor接口,并指定了想要拦截的方法:
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class, Object.class})})
public class ExamplePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
}
在mybatis-config.xml文件中,插件使用标签进行配置。例如:
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="com.star.mybatis.plugin.ExamplePlugin">
<property name="name" value="ExamplePlugin"/>
</plugin>
</plugins>
在Configuration组件中,组合了一个拦截器链,用于存放自定义的插件。
源码6:org.apache.ibatis.session.Configuration
protected final InterceptorChain interceptorChain = new InterceptorChain();
借助Debug可知,Configuration组件最终会注册自定义的插件:
4.3.7 配置环境
MyBatis可以配置成适应多种环境,例如,开发、测试和生产环境有不同的配置。
但要注意,尽管可以配置多个环境,但每个SqlSessionFactory实例只能选择其中一个环境。因此,有几个数据库需要连接,就需要创建几个SqlSessionFactory实例。
在mybatis-config.xml文件中,配置环境使用标签进行配置。例如:
<!-- mybatis-config.xml -->
<environments default="dev">
<environment id="dev">
<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>
<environment id="test">
...
</environment>
<environment id="prod">
...
</environment>
...
</environments>
如例子所示,标签的default属性要和标签的id属性相匹配,选择出当前生效的环境,生效的环境才会注册到Configuration组件中的环境配置。
- 事务管理器:
在MyBatis中,有两种事务管理器:JDBC、MANAGED。
(1)JDBC
这种事务管理器直接使用JDBC的提交和回滚设置,它依赖于从数据源得到的连接来管理事务。
(2)MANAGED
这种事务管理器相当于没有事务管理器,因为它从不提交或回滚一个连接,而是全权交给了容器来管理。
- 数据源:
注意,这里使用${...}
的方式,就是取用【4.3.1 属性】中配置的属性。
标签的type属性是指数据源类型,它的取值包括UNPOOLED、POOLED、JNDI。
(1)UNPOOLED
即非“池”类型的数据源,它的实现会在每次请求时打开和关闭连接。相比连接池,它虽然有一点慢,但对于没有性能要求的简单应用程序是一个很好的选择。
UNPOOLED类型的数据源仅需要配置以下5种属性:
driver
– JDBC驱动的Java类的完全限定名。url
– 数据库的JDBC URL地址。username
– 登录数据库的用户名。password
– 登录数据库的密码。defaultTransactionIsolationLevel
– 默认的连接事务隔离级别。
(2)POOLED
即“池”类型的数据源,它的实现会将JDBC连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。这是一种使得并发Web应用快速响应请求的流行处理方式。
除了上述提到的5种属性,还有更多属性用来配置POOLED类型的数据源:
poolMaximumActiveConnections
– 最大活动连接数,默认值:10。poolMaximumIdleConnections
– 最大空闲连接数。poolMaximumCheckoutTime
– 池中连接最大被检出时间,超时将被强制返回,默认值:20000毫秒。poolTimeToWait
– 获取连接如果超过这个时间,则会给连接池打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败但没有任何提示),默认值:20000毫秒。poolPingQuery
– 发送到数据库的心跳查询SQL语句,用来检验连接是否处在正常状态并准备接受请求。默认是NO PING QUERY SET
,当数据库驱动失败时,返回一个错误消息。poolPingEnabled
– 是否启用心跳查询。若开启,必须使用一个可执行的SQL语句设置poolPingQuery属性(最好是一个非常快的SQL语句),默认值:false。poolPingConnectionsNotUsedFor
– 配置poolPingQuery的使用频度。可以设置成数据库连接超时时间,来避免不必要的检测,默认值:0(即所有连接每一时刻都被检测 — 当然仅当poolPingEnabled为true时适用)。
(3)JNDI
这种数据源的实现是为了能在如EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的引用。
这种数据源配置只需要2个属性:
initial_context
– 用来在InitialContext中寻找上下文。这是个可选属性。data_source
– 引用数据源实例位置的上下文的路径。提供了initial_context配置时会在其返回的上下文中进行查找,没有提供时则直接在InitialContext中查找。
在Configuration组件中,组合了一个Environment对象,用于存放被选中的环境配置。
源码7:org.apache.ibatis.session.Configuration
protected Environment environment;
借助Debug可知,Configuration组件最终会注册一个Environment对象:
4.3.8 映射器
映射器用于配置SQL映射文件的位置,也就是告诉MyBatis到哪里去找SQL映射文件。
在mybatis-config.xml文件中,映射器使用标签进行配置。例如:
<!--映射文件-->
<mappers>
<!--通过指定XML文件的类路径来注册-->
<mapper resource="mapper/UserMapper.xml"/>
<!--通过指定XML文件的完全限定资源定位符来注册-->
<!--<mapper url="file:///C:\workspace\mybatis_demo2\src\main\resources\mapper\UserMapper.xml"/>-->
<!--通过Mapper接口的类路径来注册-->
<!--<mapper class="com.star.mybatis.mapper.UserMapper"/>-->
<!--通过Mapper接口所在包路径类注册-->
<!--<package name="com.star.mybatis.mapper"/>-->
</mappers>
如上面的配置所示,配置SQL映射文件的位置有4种方法是:第一是使用XML文件的类路径;第二是使用XML文件的完全限定资源定位符(即包括 file:///
的URL);第三是使用Mapper接口的类路径;第四是使用Mapper接口所在包路径。
在Configuration组件中,组合了一个Mapper注册器,用于注册Mapper接口信息:
源码:
protected final MapperRegistry mapperRegistry;
借助Debug可知,Configuration组件最终会注册配置好的映射器:
…
本节完,更多内容请查阅分类专栏:MyBatis3源码深度解析