使用mybatis进行数据库查询的代码如下:
public class MessageDaoDemo {
@Test
public void queryMessageList() throws IOException{
SqlSession sqlSession=getSessionFactory().openSession();
MessageMapperDao messageMapper = sqlSession.getMapper(MessageMapperDao.class);
Message msg = new Message();
msg.setCommand("555");
msg.setDescription("2");
List message = messageMapper.queryMessageList(msg.getCommand(),msg.getDescription());
for(Message m :message){
System.out.println(m.toString());
}
}
private SqlSessionFactory getSessionFactory() {
SqlSessionFactory sessionFactory = null;
String resource = "mybatis-config.xml";
try {
sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
// String resource1 = "src/main/resources/mybatis-config.xml";
// File file = new File(resource1);
// sessionFactory = new SqlSessionFactoryBuilder().build(new FileInputStream(file));
} catch (IOException e) {
e.printStackTrace();
}
return sessionFactory;
}
}
首先mybatis必须通过SqlSession来进行持久化操作,想要拿到SqlSession就要拿到SqlSessionFactory,而SqlSessionFactory对象是通过SqlSessionFactoryBuilder创建出来的,所以我们来看看SqlSessionFactoryBuilder是如何构建SqlSessionFactory的,SqlSessionFactory通过build()方法来构建SqlSessionFactory,build()有很多重载方法,重载的方法可以分为3类,一类是通过字符流Reader对象读取总的配置文件,另一类是通过字节流InputStream对象读取总的配置文件,最后一类是用总配置文件mybatis-config.xml中的信息创建一个Configuration对象,然后构建一个SqlSessionFactory,一般来说我们选择前两类中的一个来构建SqlSessionFactory,因为前两类会自动为我们创建Configuration对象。上面代码分别列出了使用Reader和使用InputStream是如何读取mybatis配置文件的。
图1
Mybatis使用dom来解析xml配置文件。在Mybatis中由XmlConfigBuilder负责总的配置文件的解析工作,XmlConfigBuilder的继承结构图如图1所示。mybatis总配置文件共有11个标签(全部在代码中),这11个标签的顺序是固定的,标签的顺序在mybatis-3-config.dtd文件中定义,在mybatis配置文件的头部引用了mybatis-3-config.dtd,其中最常用的标签有4个,分别是properties,typeAliases,environments,mapper标签,
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 {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
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);
}
}
下面来分别看看这些标签的作用然后通过源码来看看mybatis是如何使用这些标签的
properties标签:
properties配置如下
dbConfig.properties配置如下
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mybatis?characterEncoding=utf8
username=root
password=root
解析properties标签的代码在XMLConfigBuilder类的propertiesElement(...)方法中(参数省略),代码如下,在代码1 处将properties中配置的属性以键值对的形式存储到defaults中,这个时候value中的值还是以${...}的形式存储,接下来因为resource属性的值不为空,所以会用外部的properties文件中的内容直接覆盖defaults中对应的记录,所以如果我们将数据库连接的信息是从外部引入的,property标签中的value配置成什么样都是无所谓的,但是为了规范最好还是按照大家都熟悉的方式来写,用外部文件进行替换之后,mybatis会将这些信息存储到configuration的variables属性中,configuration是一个十分重要的对象,mybatis将配置文件中的所有信息都存放到该对象中
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//将properties中配置的属性以键值对的形式存储到Properties中
Properties defaults = context.getChildrenAsProperties();// 1
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
//用外部的peoperties文件覆盖property属性的值
defaults.putAll(Resources.getResourceAsProperties(resource)); // 2
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables(); // 3
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
//将property属性中配置的键值对全部存储到defaults中
configuration.setVariables(defaults); //4
}
}
typeAliases标签:
typeAliases配置如下
typeAliases配置有两种形式,一种是package,另一种是typeAlias,名字上就可以看出来,第一种是为包下所有的类按默认的规则生成别名,第二种是为特定的类生成指定的别名,具体代码如下
,mybatis会根据类的全路径名为我们生成一个对应的Class对象,并将这些信息存放到typeAliasRegistry对象中,该对象是Configuration对象(存放所有配置信息的对象)的一个属性,这些类的全名和别名会简化我们在存放SQL配置文件中的开发。
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//为某个包下的所有类生成别名,如果包名为net.snail,该包下有个Message类,则该类生成的别名为message
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
//将类的全路径名和别名的对应信息存到typeAliasRegistry对象中
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
值得一提的是mybatis已经默认帮我们生成了很多别名与全名的对应关系
在创建TypeAliasRegistry对象时
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);
registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);
registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);
registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
还有在创建configuration时,mybatis已经为我们初始化好了一些对应关系
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);
}
environments标签:
environmets标签配置如下:
environments标签可以有多个environment子标签,在environments标签中我们通过default属性来让那个标签生效,这为我们在多套不同的环境中切换提供了方便。我们还可以通过在调用SqlSessionFactoryBuilder的build(...)方法时指定让那个environment生效
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
来看看源码在解析environments标签时为我们做了哪些工作,解析environments源码如下
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"));// 1
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); // 2
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
在代码1处取到了transactionManager标签的内容,上面我们配置了type="JDBC",在transactionManagerElement(...)会去TypeAliasRegistry对象中找是否有和"JDBC"对应的类(和TypeAliasRegistry对象相关的内容上面已经讲过),mybatis在解析typeAliases标签时已经为我们创建了和"JDBC"别名对应的类JdbcTransactionFactory.class,transactionManagerElement(...)方法会根据该class对象通过反射创建一个JdbcTransactionFactory对象并返回,代码如下
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
解析dataSource的过程是类似的,mybatis在解析typeAliases标签时已经为我们创建了和"POOLED"别名对应的类PooledDataSourceFactory.class,解析完后mybatis会将environments标签中的信息存放到configuration对象的environment属性中。在解析dataSource标签的时候,dataSource标签中的属性是以EL表达式${...}的形式存放的,mybatis使用GenericTokenParser类来解析${...}并用实际的值来进行替换。
防止篇幅过长,本篇文章我们就讲述这三个标签,其余标签会在接下来的文章中继续分析!!!