Mybatis 配置文件入门
从这篇文章开始,我们将从其核心配置文件入手,对Mybatis支持的核心配置文件进行简单详细的描述。
从下面这段代码是我们在使用mybatis前的配置初始化过程
我们通过阅读其源码来逐步了解内部实现原理。
// Mybatis 通过SqlSessionFactory获取SqlSession, 然后才能通过SqlSession与数据库进行交互 private static SqlSessionFactory getSessionFactory() { SqlSessionFactory sessionFactory = null; String resource = "configuration.xml"; try { sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource)); } catch (IOException e) { e.printStackTrace(); } return sessionFactory; }
我们进入到SqlSessionFactoryBuilder类里面
查看其源码:
/** * Copyright 2009-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ibatis.session; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.util.Properties; import org.apache.ibatis.builder.xml.XMLConfigBuilder; import org.apache.ibatis.exceptions.ExceptionFactory; import org.apache.ibatis.executor.ErrorContext; import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory; /** * Builds {@link SqlSession} instances. * * @author Clinton Begin */ public class SqlSessionFactoryBuilder { public SqlSessionFactory build(Reader reader) { return build(reader, null, null); } public SqlSessionFactory build(Reader reader, String environment) { return build(reader, environment, null); } public SqlSessionFactory build(Reader reader, Properties properties) { return build(reader, null, properties); } public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null); } public SqlSessionFactory build(InputStream inputStream, String environment) { return build(inputStream, environment, null); } public SqlSessionFactory build(InputStream inputStream, Properties properties) { return build(inputStream, null, properties); } public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { 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. } } } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } }
在这个类中,支持多种构造SqlSessionFactory的方法。可以只传入mybatis配置文件,也可以同时传入properties配置文件替代mybatis配置文件中的<properties>元素标签,另外也支持传入环境参数envirmont参数。
我们跟随着源码继续往下看:
public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }
这里创建了一个XMLConfigBuilder类实例,通过他来对mybatis配置文件(一个xml配置文件)进行解析。
解析的代码入口如下所示:
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")); 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); } }
从这里看出,配置文件是以configuration为根节点,在根节点之下有多个子节点,它们分别为:settings、properties、typeAliases、plugins、objectFactory、objectWrapperFactory、environments、databaseIdProvider、typeHandlers、mappers。
MyBatis核心配置文件标签简介
XML 映射配置文件
MyBatis
的配置文件包含了影响MyBatis行为甚深的设置(settings)和属性(properties)信息。文档的顶层结果如下:
configuration
配置
properties
属性
setting
设置
typeAliases
类型命名
typeHandlers
类型处理器
objectFactory
对象工厂
plugins
插件
environments
环境
environment
环境变量
transactionManager
事务管理器
dataSource
数据源
databaseIdProyider
数据库厂商标识
mappers
映射
properties
属性都是可外部配置且可动态替换的,既可以在典型的Java属性文件中配置,亦可通过properties元素的子元素来传递。
例如:
<!-- mybatis的核心配置文件 1.数据库的连接信息(连接池) --> <properties resource="jdbc.properties"></properties>
其中的属性就可以在整个配置文件中使用来替换需要动态配置的属性值。
比如:
<!-- 默认连接池 --> <dataSource type="POOLED"> <property name="driver" value="${driverClass}"/> <property name="url" value="${url}"/> <property name="username" value="${userid}"/> <property name="password" value="${password}"/> </dataSource>
properties
属性:将数据库连接参数单独配置在jdbc.properties中,只需要在mybatis.xml文件中加载jdbc.properties的属性值。 在mybatis.xml中就不需要对数据库连接参数硬编码(硬编码是指将可变变量用一个固定值来代替的方法)。在properties 元素体内定义的属性首先被读取。然后会读取properties元素中resource或url加载属性,它会覆盖已读取的同名属性。
注意:如果在properties标签里面定义的属性被${}所引用了,对#{}不管用。那么它不会读取parameterType里面的参数值。比如properties里面定义了id属性,值为40,在映射文件中引用该值,${id}那么我从parameterType里面传值时,不管我传基本类型还是引用类型进去都不会覆盖这个${id}值。始终都会读取40.
属性也可以被传递到SqlSessionBuilder.build()方法中。
例如:
SqlSessionFactoryBuilder
源码:
public SqlSessionFactory build(Reader reader, Properties properties) { return build(reader, null, properties); } public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }
typeAliases
类型别名是为Java类型设置一个短的名字。它只和XML配置有关,存在意义仅在于用来减少类完全限定名的冗余。
例如:
<!-- 给类定义别名 --> <typeAliases> <typeAlias type="cn.et.lesson02.annotion.Food" alias="food"/> <typeAlias alias="Author" type="domain.blog.Author"/> <typeAlias alias="Blog" type="domain.blog.Blog"/> </typeAliases>
当这样配置时,Blog可以用在任何使用domain.blog.Blog的地方。
也可以指定一个包名,MyBatis会在包名下面搜索需要的Java Bean
比如:
<typeAliases> <package name="domain.blog"/> </typeAliases>
每一个包domain.blog中的Java Bean,在没有注解的情况下,会使用Bean的首字母小写的非限定类名来作为它的别名。比如domain.blog.Author的别名为author;若有注解,则别名为其注解值。
看下面的例子:
@Alias("author") public class Author { ... }
mapper
标签(映射配置):加载映射文件
<mappers> <!-- 通过resource加载单个映射文件 加载单个类路径下的映射文件 使用相对于类路径的资源: --> <mapper resource="cn/et/lesson01/FoodMapper.xml"/> <!-- 使用类路径加载单个映射文件 <mapper url="cn.domarvel.dao.UserMapper"/> 注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。并且还有一个前提是:使用的是mapper代理方法 --> <!-- 自动批量加载指定包下的所有Mapper接口配置文件 <package name="cn.domarvel.dao"/> 注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。并且还有一个前提是:使用的是mapper代理方法 --> </mappers>
Mapper XML文件
MyBatis的真正强大在于他的映射语句,也是它的魔力所在,由于它的异常强大,映射器的XML文件就显得相对简单。如果拿它跟具有相同功能的JDBC代码进行对比,你会立即发现省掉了将近95%的代码。MyBatis就是针对SQL构建的,并且比普通的方法做的更好。
mapper
标签:映射文件的根节点,在根节点中支持九个元素。
namespace是用于绑定Dao接口的,当你的namespace绑定接口后,你可以不用写接口实现类,mybatis会通过该绑定自动帮你找到对应要执行的SQL语句
注意:接口中的方法与映射文件中的SQL语句的ID一一对应
<mapper namespace="a"> </mapper> <mapper namespace="cn.et.lesson02.xml.FoodMapper" > </mapper>
SQL映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):
cache
:给定命名空间的缓存配置
cache-ref
:其命名空间缓存配置的引用。
resultMap
:是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
parameterMap
:已废弃,老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
sql
:可被其他语句引用的可重用语句块。
insert
:映射插入语句
update
:映射更新语句
delete
:映射更新语句
select
:映射查询语句
select
查询语句是MyBatis中最常用的元素之一,光能把数据存到数据库中价值并不大,如果还能重新取出来才有用,多数应用也都是查询比修改要频繁。对每个插入、更新或删除操作,通常对应多个查询操作。这是MyBatis的基本原则之一,也是将焦点和努力放在查询和结果映射的原因。简单查询的select元素时非常简单的。
比如
<select id="selectPerson" parameterType="int" resultType="hashmap"> SELECT * FROM PERSON WHERE ID = #{id} </select>
这个语句被称作selectPerson,接收一个int(或Integer)类型的参数,并返回一个HashMap类型的对象,其中的键是列名,值便是结果行中的对应值。
注意参数符号:#{id}
select元素有很多属性允许你配置,来决定每条语句的作用细节。
SELECT多条件查询
parameterType用于传递参数多参数可以使用传入对象以及map的方式传递多个参数。
#{}表示传递的参数值 类同jdbc的? ${}表示直接将参数值替换类同'%值%'
比如:
<select id=“selectPerson” parameterType=“map” resultType=“person”> SELECT * FROM PERSON WHERE ID = #{id} and name like ‘%${name}%' </select>
Map中必须存在id和name的键值对
SELECT调用存储过程
创建存储过程prg_add(p1 in number,p2 in number,p3 out number)
Mybatis映射文件中使用select调用存储过程
<select id=“prgAdd" statementType="CALLABLE"> <![CDATA[ {call pro_hello ( #{p1,mode=IN,jdbcType=NUMBER}, #{p2,mode=IN,jdbcType=NUMBER}, #{result,mode=OUT,jdbcType=NUMBER})} ]]> </select>
测试调用过程
Map<String, String> param = new HashMap<String, String>(); param.put(“p1”, 1); param.put(“p2”, 2); String returnValue = (String) session.selectOne(" prgAdd ", param); System.out.println("result=" + param.get("result")); System.out.println("returnValue=" + returnValue);
insert、update和delete
数据更变语句insert、update和delete的实现非常接近
<insert id="insertAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" keyProperty="" keyColumn="" useGeneratedKeys="" timeout="20"> <update id="updateAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" timeout="20"> <delete id="deleteAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" timeout="20">
insert、update和delete参数
parameterType
:入参的全限定类名或类型别名
keyColumn
:设置数据表自动生成的主键名。对特定数据库(如PostgreSQL),若自动生成的主键不是第一个字段则必须设置
keyProperty
:默认值unset,用于设置getGeneratedKeys方法或selectKey子元素返回值将赋值到领域模型的哪个属性中
useGeneratedKey
:取值范围true|false(默认值)设置是否使用JDBC的getGenereatedKeys方法获取主键并赋值到keyProperty设置的领域模型属性中。
MySQL和SQLServer执行auto-generated key field,因此当数据库设置好自增长主键后,可通过JDBC的getGeneratedKeys方法获取。但像Oralce等不支持auto-generated key field的数据库就不能用这种方法获取主键了
flushCache
:取值范围true(默认值)|false,设置执行该操作后是否会清空二级缓存和本地缓存
timeout
:默认为unset(依赖jdbc驱动器的设置),设置执行该操作的最大时限,超时将抛异常
databaseId
:取值范围oracle|mysql等,表示数据库厂家,元素内部可通过`<if test="_databaseId = 'oracle'">`来为特定数据库指定不同的sql语句
selectKey
对于不支持自动生成类型的数据库或可能不支持自动生成主键JDBC驱动来说,MyBatis有另外一种方法来生成主键。
这里有一个简单的示例,它可以生成一个随机ID(最好不要这么做,但这里展示了MyBatis处理问题的灵活性及其所关心的广度):
<insert id="saveFood" > <!-- selectKey 在Mybatis中是为了解决Insert数据时不支持主键自动生成的问题,他可以很随意的设置生成主键的方式。 SelectKey需要注意order属性,像Mysql一类支持自动增长类型的数据库中, order需要设置为after才会取到正确的值。像Oracle这样取序列的情况,需要设置为before,否则会报错。 keyProperty:selectKey 语句结果应该被设置的目标属性。 order:可设置为 BEFORE 或 AFTER,如果设置为 BEFORE,那么它会首先选择主键,设置 keyProperty 然后执行插入语句。 如果设置为 AFTER,那么先执行插入语句,然后是 执行selectKey元素 statementType:和前面的相 同,MyBatis 支持 STATEMENT ,PREPARED 和CALLABLE 语句的映射类型, 分别代表 PreparedStatement 和CallableStatement 类型。 注意:selectKey操作会将操作查询结果赋值到insert元素的parameterType的入参实例下对应的属性中。并提供给insert语句使用 --> <selectKey keyProperty="foodId" order="BEFORE" resultType="int" statementType="STATEMENT"> select FOOD_SEC.Nextval from dual </selectKey> insert into food values(#{foodId},#{foodName},#{price}) </insert>
在上面的示例中,selectKey元素将会首先运行,food的id会被设置,然后插入语句会被调用,这给了你一个和数据库中来处理自动生成的主键类似的行为,避免了使Java代码变得复杂
SQL
这个元素可以被用来定义可重用的SQL代码段,可以包含在其他语句中,它可以被静态地(在加载参数)参数化,不同的属性值太高包含的实例变化。
比如:
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
这个SQL片段可以被包含在其他语句中,例如:
<select id="selectUsers" resultType="map"> select <include refid="userColumns"> <property name="alias" value="t1"/></include>, <include refid="userColumns"><property name="alias" value="t2"/> </include> from some_table t1 cross join some_table t2 </select>
属性值可以用于包含的refid属性或者包含的字句里面的属性值。
Result Map
resultMap 元素时MyBatis中最重要最强大的元素。它就是让你远离90%的需要从结果集中取出数据的JDBC代码的那个东西,而且在一些情形下允许你做一些JDBC不支持的事情。事实上,编写相似于对复杂语句联合映射这些等同的代码,也许可以跨过上千行的代码。ResultMap的设计就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系。
<!-- resultMap 基本作用:建立SQL查询结果字段与实体属性的映射关系 属性 id属性 ,resultMap标签的标识。 type属性 ,返回值的全限定类名,或类型别名。 autoMapping 属性 ,值范围true(默认值)|false,设置是否启动自动映射功能, 自动映射功能就是自动查找与字段名小写同名的属性名,并调用setter方法。而设置为false后, 则需要在resultMap内明确注明映射关系才会调用对应的setter方法。 子元素: id元素 ,用于设置主键字段与领域模型属性的映射关系 result元素 ,用于设置普通字段与领域模型属性的映射关系 property 需要映射到JavaBean 的属性名称。 column 数据表的列名或者标签别名。 javaType 一个完整的类名,或者是一个类型别名。如果你匹配的是一个JavaBean, 那MyBatis 通常会自行检测到。然后,如果你是要映射到一个HashMap, 那你需要指定javaType 要达到的目的。 jdbcType 数据表支持的类型列表。这个属性只在insert,update 或delete 的时候针对允许空的列有用。 JDBC 需要这项,但MyBatis 不需要。如果你是直接针对JDBC 编码,且有允许空的列,而你要指定这项。 typeHandler 使用这个属性可以覆写类型处理器。这项值可以是一个完整的类名,也可以是一个类型别名。 association联合 联合元素用来处理“一对一”的关系。需要指定映射的Java实体类的属性,属性的javaType(通常MyBatis 自己会识别)。 对应的数据库表的列名称。如果想覆写的话返回结果的值,需要指定typeHandler。 不同情况需要告诉MyBatis 如何加载一个联合。MyBatis 可以用两种方式加载: select: 执行一个其它映射的SQL 语句返回一个Java实体类型。较灵活; resultsMap: 使用一个嵌套的结果映射来处理通过join查询结果集,映射成Java实体类型。 --> <resultMap type="grade" id="gradeMap" autoMapping="true" > <!-- 因为 autoMapping="false"(默认是true) 所以要明确注明映射关系 如果为true则会自动映射 不需要写下面这行代码 <result column="gid" property="gid"/> 还有下面这种方法 需要gid为主键的情况下使用 列和属性的关系 主键使用id 非主键使用result <id column="gid" property="gid"/> --> <!-- 将查询的结果gname赋给gname1 --> <id column="gid" property="gid"/> <result column="gname" property="gname"/> </resultMap>
以上为个人经验,希望能给大家一个参考,也希望大家多多支持自由互联。