Mybatis入门案例
创建maven工程,引入依赖配置项
pom.xml中引入maven依赖:
代码语言:javascript复制<dependencies>
<!--MyBatis坐标-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
数据库
代码语言:javascript复制CREATE DATABASE mybatis;
USE mybatis;
CREATE TABLE `t_user` (
`uid` INT(11) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(40) DEFAULT NULL,
`sex` VARCHAR(10) DEFAULT NULL,
`birthday` DATE DEFAULT NULL,
`address` VARCHAR(40) DEFAULT NULL,
`level` VARCHAR(10) DEFAULT NULL,
PRIMARY KEY (`uid`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
insert into `t_user`(`uid`,`username`,`sex`,`birthday`,`address`,`level`) values (1,'zs','男','2018-08-08','北京','LOW'),(2,'ls','女','2018-08-30','武汉','MIDDLE'),(3,'ww','男','2018-08-08','北京','LOW');
javabean
User.java类
代码语言:javascript复制public class User implements Serializable {
private int uid; //用户id
private String username;// 用户姓名
private String sex;// 性别
private Date birthday;// 生日
private String address;// 地址
// get、set...
}
jdbc.properties
代码语言:javascript复制driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis
username=root
password=root
mybatis核心配置文件
resources目录下创建:mybatis-config.xml,并写入以下内容:
代码语言:javascript复制<?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>
<!-- 引入外部配置文件 -->
<properties resource="jdbc.properties" />
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="useActualParamName" value="true"/>
<setting name="logImpl" value="SLF4J"/>
<!-- 自定义的VFS -->
<setting name="vfsImpl" value="org.byron4j.vfs.MyVFS"/>
</settings>
<!--别名方式一:单个-->
<typeAliases>
<typeAlias alias="user2" type="org.byron4j.bean.User"/>
<typeAlias alias="int" type="java.lang.Integer"/>
<!--方式二,该包下的所有类都默认取别名为类名,并且不区分大小写
如: org.byron4j.bean.Product 别名将为 product、Product、ProDuCt 都可以
-->
<package name="org.byron4j.bean"/>
</typeAliases>
<typeHandlers>
<!--方式一: 单个处理器-->
<!--<typeHandler handler="org.byron4j.handler.MyHandler" javaType="org.byron4j.bean.User"/>-->
<typeHandler handler="org.byron4j.handler.MyDateHandler" javaType="long" jdbcType="TIMESTAMP"/>
<!--统一自动查找-->
<package name="org.byron4j.handler"/>
</typeHandlers>
<environments default="dev">
<environment id="dev">
<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>
<environment id="test">
<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>
<!--<databaseIdProvider type="DB_VENDOR" />-->
<mappers>
<mapper resource="org/byron4j/mapper/UserMapper.xml"/>
</mappers>
</configuration>
mapper.xml映射文件
resources目录下创建目录org/byron4j/mapper
目录(注意该目录需要和接口的包层级一致),新建一个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">
<!--namespace: 名称空间,接口的全限定名-->
<mapper namespace="org.byron4j.mapper.UserMapper">
<!--
id属性: 和接口的方法名一致
resultType属性:返回类型,写全限定名,集合则写元素的泛型类型
标签体:sql语句
-->
<!-- 这里使用了别名User01不区分大小写;只有一个参数时可以随便写参数名比如这里aaa,但是实际开发中建议和参数名保持一致 -->
<select id="findById" resultType="User01">
select * from t_user where uid = #{aaa}
</select>
</mapper>
Mapper.java接口
包org.byron4j.mapper
创建一个接口:UserMapper.java
public interface UserDao {
User findById(Integer id);
}
测试类
编写一个测试类:
代码语言:javascript复制@Test
public void test() throws Exception {
// 1. 读取配置文件作为流
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 根据核心配置文件获取SqlSessionFactory对象
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(is);
// 3.获取SqlSession对象(类似connection)
SqlSession sqlSession = build.openSession();
// 4. 获取代理对象
UserDao mapper = sqlSession.getMapper(UserDao.class);
// 5. 操作,释放资源
User user = mapper.findById(2);
System.out.println(user);
sqlSession.close();
is.close();
}
这是一个简单入门案例,会用了之后。我们就可以来学习Mybatis加载核心配置文件流程了。
Mybatis加载核心配置文件流程
1.入口
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String)
这个方法第一个参数是一个mybatis配置文件代表的输入流;第二个参数是环境,这里我们缺省。
它调用了下面的方法:
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)
。
在我们的案例中只有第一个参数有值,后面的都是null。
下面开始跟踪源代码:
代码语言:javascript复制public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 创建一个【XMLConfigBuilder】解析器类
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.
}
}
}
2.创建一个【XMLConfigBuilder】解析器类
继续跟踪org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(核心配置xml文件, 环境env, 不指定properties则为null)
方法:
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()),
environment, props);
}
方法体又调用了重载构造器:org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(org.apache.ibatis.parsing.XPathParser, java.lang.String, java.util.Properties)
这里的第一个参数是一个XPathParser类型的解析器(看名字知道是XPath的解析器)。
这里用到了XPathParser构造器,我们接下来看看。
3.【XPathParser】解析器的构造器
构造器:org.apache.ibatis.parsing.XPathParser#XPathParser(java.io.InputStream, true, java.util.Properties为null, org.xml.sax.EntityResolver)
第四个参数是一个实体解析器:org.apache.ibatis.builder.xml.XMLMapperEntityResolver,实现了org.xml.sax.EntityResolver 接口;
所以我们又需要来查看一下XMLMapperEntityResolver
这个类是啥玩意。
4.【XMLMapperEntityResolver】实体解析器
org.apache.ibatis.builder.xml.XMLMapperEntityResolver,实现了org.xml.sax.EntityResolver 接口
;实现这个接口后,可以用于自定义拦截解析XML,比如使用了mybatis的定义约束就可以条件判断做一些其他的逻辑。
以下是一个简单地示例,仅仅做一个说明:
public class MyResolver implements EntityResolver {
public InputSource resolveEntity (String publicId, String systemId)
{
if (systemId.equals("http://www.myhost.com/today")) {
// 包含了自定义的,则自己解析处理
MyReader reader = new MyReader();
return new InputSource(reader);
} else {
// use the default behaviour
return null;
}
}
}
XMLMapperEntityResolver这个解析器就是判断了下systemId(xml的文档声明)包含org/apache/ibatis/builder/xml/mybatis-3-config.dtd
就使用mybatis自定义的InputSource(xml文件配置输入源):
private InputSource getInputSource(String path, String publicId, String systemId) {
InputSource source = null;
if (path != null) {
try {
// path是mybatis-3-config.dtd的路径
InputStream in = Resources.getResourceAsStream(path);
source = new InputSource(in);
source.setPublicId(publicId);
source.setSystemId(systemId);
} catch (IOException e) {
// ignore, null is ok
}
}
return source;
}
所以你应该可以理解通过XMLMapperEntityResolver只是获取了一个InputSource对象(SAX解析器将使用这个InputSource对象来确定如何读取XML输入)。
5.重新回到第【3】步来:构造XPathParser
XMLMapperEntityResolver了解了之后,我们现在回到XPathParser这里,构造XPathParser:org.apache.ibatis.parsing.XPathParser#XPathParser(java.io.InputStream, boolean, java.util.Properties, org.xml.sax.EntityResolver)
构造器:(在XPathParser类的123行处)
代码语言:javascript复制// inputStream是我们项目中的mybatis核心配置文件,如:`D: 07mybatis_demo01targetclassesSqlMapperConfig.xml`
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
// 这个构造器会初始化内部的一个属性:javax.xml.xpath.XPath
commonConstructor(validation, variables, entityResolver);
// 初始化内部属性:org.w3c.dom.Document,得到一个dom文档
this.document = createDocument(new InputSource(inputStream));
}
6.重新回到第【2】步来,81行左右:
XPathParser的构造明白后,重新回到第【2】步,看一下XMLConfigBuilder
的构造逻辑
代码位置XMLConfigBuilder:org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(org.apache.ibatis.parsing.XPathParser, java.lang.String, java.util.Properties)
实际调用了85行左右的代码的方法:org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(org.apache.ibatis.parsing.XPathParser, java.lang.String, java.util.Properties)
方法如下,这里主要看一下第一行代码也是Configuration的创建:
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
// 设置一些属性
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
这个方法里面的逻辑我们接着来细看下。
6.1. 第一行super(new Configuration());
第一行代码调用了父类的构造器,即:org.apache.ibatis.builder.BaseBuilder#BaseBuilder
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
6.2. Configuration 类是Mybatis的核心配置类,再重要不过了!
我们来看看直接new出来的配置信息类的无参构造器: new Configuration()
:
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
这里面的都是对应我们在mybatis核心配置文件(mybatis-config.xml)中配置的属性值对应的具体的java类的映射。
Configuration类还定义了很多的属性,这里主要是关于加载的流程分析,不展开讨论这些属性,只在下面代码注释中列举几个说明下:
代码语言:javascript复制public class Configuration {
// 环境配置:<environment id="dev">
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
// 输出日志时的前缀设置
protected String logPrefix;
// 指定日志实现类;否则默认按顺序:SLF4J、Apache Commons Logging、Log4j 2、Log4j、JDK logging 查找实现;
protected Class <? extends Log> logImpl;
// 虚拟文件系统,会读取接口Mapper.class文件
protected Class <? extends VFS> vfsImpl;
// 默认一级缓存为session级别(另一个值是statement级别)
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
// 在调用这些方法的时候,延迟加载的对象会在调用这些方法之前被加载出来
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
// 默认的执行器的类型,SIMPLE(默认), REUSE, BATCH
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
// 设置 mapper.xml中表的列(column)和javabean的属性(properties)自动映射的行为
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
*/
protected Class<?> configurationFactory;
// 映射器注册表:使用代理模式;是由MapperProxy代理创建对应的接口Mapper的实例的
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
// 拦截器链,里面list属性interceptors封装了所有的拦截器对象(org.apache.ibatis.plugin.Interceptor)
protected final InterceptorChain interceptorChain = new InterceptorChain();
// 类型处理器注册表
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
// 类型别名注册表
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
// 语言驱动程序注册表(这些对象一般指org.apache.ibatis.scripting.LanguageDriver的子类)
// mybatis默认org.apache.ibatis.scripting.xmltags.XMLLanguageDriver类型
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
protected final Set<String> loadedResources = new HashSet<String>();
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
/*
* A map holds cache-ref relationship. The key is the namespace that
* references a cache bound to another namespace and the value is the
* namespace which the actual cache is bound to.
*/
protected final Map<String, String> cacheRefMap = new HashMap<String, String>();
}
7.再回到第一步SqlSessionFactoryBuilder#build
截至目前为止,已经构造好了 XMLConfigBuilder 对象了。
我们再回到第一步org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)
,也就是SqlSessionFactoryBuilder类的75行左右。
Configuration对象public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// parser已经拿到了;【截至目前为止,已经构造好了 XMLConfigBuilder 对象了。】
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 开始追踪这里....[parser.parse()得到Configuration对象]
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.
}
}
}
7.1 查看org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)
方法
build(parser.parse())
这行代码就是调用这个方法:
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
这个方法传入了一个Configuration对象作为参数,这个参数我们知道是通过parser.parse()
得到的。
所以来关注下parser.parse()
。
7.2 parser.parse()
方法
其实就是:org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
方法。在XMLConfigBuilder
类的94行左右。
public Configuration parse() {
if (parsed) {// mybatis核心配置文件只允许加载一次
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 是使用XPath获取根节点:就是config.xml里面的根节点: `<configuration>`。
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
parser.evalNode("/configuration") 这行代码是使用XPath获取根节点:就是config.xml里面的根节点: <configuration>
。
7.3 然后再执行这个方法XMLConfigBuilder#parseConfiguration
再来看看这行代码parseConfiguration(parser.evalNode("/configuration"));
。
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
,在XMLConfigBuilder
类的103行左右。
这个方法就是解析读取mybatis-config.xml文件的各个配置项,并且赋值到configuration里面去(底层是configuration.setXXX方法设置):
代码语言:javascript复制private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
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);
}
}
这里我们就选取这一行代码 environmentsElement(root.evalNode("environments"));
简单追踪下:
org.apache.ibatis.builder.xml.XMLConfigBuilder#environmentsElement
方法如下,你应该可以看到一些熟悉的字眼(数据源的配置信息):
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
就是获取environments节点的信息:
代码语言:javascript复制<environments default="dev">
<environment id="dev">
<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>
<environment id="test">
<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>
这就是MyBatis核心配置对象Configuration解析加载的主要流程。