Mybatis 学习笔记
Mybatis的结构
基本结构及其生命周期
SqlsessionFactoryBuilder
SqISessionFactoryBuilder是利用XM或者Java编码获得资源来构建SqlSessionFactory的,通过它可以构建多个SessionFactory。它的作用就是一个构建器,一旦我们构建了 SqlSession Factory,它的作用就已经完结,所以它的生命周期只存在于方法的局部,它的作用就是生sqlsession Factor对象。
SqlsessionFactory
问数据库,我们就要通过Sq| Session Factory创建 SqlSession,所以SqlSessionFactory应该在MyBatis应用的整个生命周期中。而如果我们多次创建同一个数据库的SqlSession Factory,则每次创建SqlSession Factory会打开更多的数据库连接( Connection)资源,那么连接资源就很快会被耗尽。因此Sq| Session Factory的责任是唯一的它的责任就是创建 SqlSession,所以我们果断采用单例模式。如果我们采用多例,那么它对数据库连接的消耗是很大的,不利于我们统的管理,所以正确的做法应该是使得每个数据库只对应一个 SqlSession Factory,管理好数据库资源的分配,避免过多的 Connection被消耗。
Sqlsession
SqlSession是一个会话,相当于JDBC的个 Connection对象,它的生命周期应该是在请求数据库处理事务的过程中。它是一个线程不安全的对象,在涉及多线程的时候需要特别的当心,操作数据库需要注意其隔离级别,数据库锁等髙级特性。此外,每次创建的Sqlsession都必须及时关闭它,它长期存在就会使数据库连接池的活动资源减少,对系统性能的影响很大。往往通过fna语句块保证我们正确的关闭SqlSession。它存活于一个应用的请求和操作,可以执行多条SQL,保证事务的一致性。
Mapper
Mapper是一个接口,而没有任何实现类,它的作用是发送SQL,然后返回我们需要的结果,或执行SQL从而修改数据库的数据,因此它应该在一个SqlSession事务方法之内,是一个方法级别的东西。它就如同JDBC中的一条SQL语句的执行,它最大的范围和
SqlSession是相同的。尽管我们想一直保存着 Mapper,但是你会发现它很难控制,所以尽量在—个 SqlSession事务的方法中使用它们,然后废弃掉。
JDBC类型
为了以后可能的使用场景,MyBatis 通过内置的 jdbcType 枚举类型支持下面的 JDBC 类型
BIT | FLOAT | CHAR | TIMESTAMP | OTHER | UNDEFINED |
---|---|---|---|---|---|
TINYINT | REAL | VARCHAR | BINARY | BLOB | NVARCHAR |
SMALLINT | DOUBLE | LONGVARCHAR | VARBINARY | CLOB | NCHAR |
INTEGER | NUMERIC | DATE | LONGVARBINARY | BOOLEAN | NCLOB |
BIGINT | DECIMAL | TIME | NULL | CURSOR | ARRAY |
Mybatis的使用
配置
配置顺序
mybatis的配置项要按照一定的顺序,颠倒属性会出现解析异常。
< configuration>
<properties></properties><!--属性-->
<settings/><!--设置-->
<typeAliases/><!--别名配置-->
<typeHandlers/><!--类型处理器-->
<objectFactory/><!--对象工厂-->
<plugins/><!--插件-->
<environments><!--配置环境-->
<environment><!--环境变量-->
<ctransactionManager/><!--事务管理器--
<data Source><!--据源-->
</environment>
</environments>
<databased Provider><!--数据库厂商标识-->
<mappers><!--映射器-->
</configuration>
properties
properties是一个配置属性的元素,让我们能在配置文件的上下,文中使用它。MyBatis提供3种配置方式:
property子元素
<properties>
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
</properties>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
</dataSource>
properties配置文件
<properties resource="datasource.properties"></properties>
datasource.url=jdbc:mysql://localhost:3306/pet?charset=utf8mb4&useSSL=false&serverTimezone=UTC
<dataSource type="POOLED">
<property name="url" value="${datasource.url}"/>
</dataSource>
程序参数传递
Properties properties=new Properties();
properties.setProperty("username","root");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream,properties);
<dataSource type="POOLED">
<property name="url" value="${username}"/>
</dataSource>
优先级
以上三种方式重复配置时,优先级为:程序参数传递》配置文件》子元素。
settings
具体详细配置:https://www.w3cschool.cn/mybatis/7zy61ilv.html 或者https://mybatis.org/mybatis-3/zh/configuration.html#settings
一个配置完整的 settings 元素的示例如下:
<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="safeRowBoundsEnabled" value="false"/><!-- 允许在嵌套语句中使用分页 -->
<setting name="mapUnderscoreToCamelCase" value="false"/><!--是否开启自动驼峰命名规则 -->
<setting name="localCacheScope" value="SESSION"/><!--利用本地缓存机制 -->
<setting name="jdbcTypeForNull" value="OTHER"/><!--当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型 -->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/><!-- 指定哪个对象的方法触发一次延迟加载-->
</settings>
typeAliases
使用xml配置别名:
<typeAliases>
<typeAlias alias="user" type="com.java.xrc.entity.User"/>
</typeAliases>
使用包扫描方式配置别名:
<typeAliases>
<package name="com.java.xrc.entity"/>
</typeAliases>
包扫描方式,将包下的类名首字母小写注册别名,也可使用注解自定义别名:
@Alias("User")
typeHandlers
自定义typeHandler
继承TypeHandler的抽象实现类BaseTypeHandler,重写四个方法。
全局使用
使用xml配置
<typeHandlers>
<typeHandler handler="com.java.xrc.mapper.typehandler.MyStringTypeHandler" javaType="string" jdbcType="VARCHAR"/>
</typeHandlers>
包扫描配置
指定扫描包,
<typeHandlers>
<package name="com.java.xrc.mapper.typehandler"/>
</typeHandlers>
扫描包中类带有以下注解的类,
@MappedTypes(String.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
局部使用
resultMap中使用
<resultMap type="user"id="userMap">
<result column="name" property="name" typeHandler="com.java.xrc.mapper.typehandler.MyStringTypeHandler">
</result>
</resultMap>
参数中使用
<select id="selectByName" resultType="User">
select id,name,password from user
<where>
name=#{name javaType=String,jdbcType=VARCHAR,typeHandler=com.java.xrc.mapper.typehandler.MyStringTypeHandler}
</where>
</select>
objectFactory
plugins
environments
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${datasource.driver}"/>
<property name="url" value="${datasource.url}"/>
<property name="username" value="${datasource.username}"/>
<property name="password" value="${datasource.password}"/>
</dataSource>
</environment>
</environments>
transactionManager
JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false。
dataSource
UNPOOLED– 这个数据源的实现只是每次被请求时打开和关闭连接。
POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这是一种使得并发 Web 应用快速响应请求的流行处理方式。
JNDI – 这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
databaseIdProvider
配置厂商表示名可以针对不同类型的数据库提供不同sql的支持。
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
使用
<select id="selectById" resultType="User" databaseId="mysql">
select id,name,password from user
<where>
id=#{id}
</where>
</select>
<select id="selectById" resultType="User" databaseId="oracle">
select id,name,password from user
<where>
id=#{id}
</where>
</select>
mappers
resource方式加载映射文件
<mappers>
<!-- 这是resource方式加载配置文件 -->
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
class方式:这里需要注意:接口类和映射文件放在同一个目录下,并文件名要一致
<mappers>
<!-- class方式加载配置文件-->
<mapper class="com.java.xrc.mapper.UserMapper"/>
</mappers>
包扫描加载映射文件 : 接口类 和映射文件放在同一个目录下,并文件名要一致
<mappers>
<!-- 批量mapper的扫描 -->
<package name="com.java.xrc.mapper"/>
</mappers>
使用本地文件(不推荐)
<mappers>
<mapper url="file:///c:/mapper/UserMapper.xml"/>
</mappers>
注意:
IDEA编译后默认会把resource下的文件放到target的classpath下,但是src下的只有Java文件编译生成.class文件放入classpath下,其他文件会忽略的。
mybatis原始开发xml文件与接口文件不在同一路径下,仅能用resource方式加载映射文件。
映射
select
描述 | |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType | 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置(unset)。 |
parameterMap | 这是引用外部 parameterMap 的已经被废弃的方法。 |
resultType | 从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。可以使用 resultType 或 resultMap,但不能同时使用。 |
resultMap | 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。可以使用 resultMap 或 resultType,但不能同时使用。 |
flushCache | 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。 |
useCache | 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。 |
fetchSize | 这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)。 |
statementType | STATEMENT,PREPARED 或 CALLABLE 中的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
resultSetType | FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖驱动)。 |
databaseId | 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。 |
resultOrdered | 这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。 这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false 。 |
resultSets | 这个设置仅对多结果集的情况适用。它将列出语句执行后返回的结果集并给每个结果集一个名称,名称是逗号分隔的。 |
insert、update、delete
属性:id,parameterType,parameterMap,flushCache,timeout,statementType, databaseId同select一样。
属性 | 描述 |
---|---|
useGeneratedKeys | (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。 |
keyProperty | (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认值:未设置(unset )。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
keyColumn | (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望使用多个生成的列,也可以设置为逗号分隔的属性名称列表。 |
自定义主键生成规则
<insert id="insertAuthor">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1 <!--随机生成id主键-->
</selectKey>
insert into Author
(id, username, password)
values
(#{id}, #{username}, #{password})
</insert>
selectKey 属性
通过selectkey能完成Oracle使用序列生成主键的返回。
属性 | 描述 |
---|---|
keyProperty | selectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
keyColumn | 匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
resultType | 结果的类型。MyBatis 通常可以推断出来,但是为了更加精确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的 Object 或一个 Map。 |
order | 这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先生成主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。 |
statementType | 与前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型。 |
参数
参数配置
定义参数属性的时候, MyBatis不允许换行。
指定特定的类型:
#{name javaType=String,jdbcType=VARCHAR}
指定 type Handle
name=#{name javaType=String,jdbcType=VARCHAR,typeHandler=MyTypeHandler}
对一些数值型的参数设置其保存的精度
price=#{price javaType=double,jdbcType=NUMERIC, numericScale=2}
存储过程参数
对于存储过程而言,存在3种参数,输入参数(N)、输出参数(OUT)、输入输出参数(NOUT)。 mode属性来确定其是哪一种参数当参数,Mybatis会将存储过程返回的结果设置到你制定的参数中。
返回的是一个游标(也就是我们制定Jdbctype= CURSOR)的时候,你还需要去设置 resultMap以便My Batis将存储过程的参数映射到对应的类型,这时 MyBatis就会通过你所设置的 resuptMap自动为你设置映射结果。
#{role, mode=oUT,jdbcType=CURSOR,javaType=Resultset,resultMap=roleResultMapy}
这里的 javaType是可选的,即使你不指定它, MyBatist会自动检测它,MyBatis还支持一些高级特性,比如说结构体,但是当注册参数的时候,你就需要去指定语句类型的名称( gdbcTypeName)
#{role, mode=oUT,jdbctype=STRUCT,jdbcTypeName=MY_TYPE,resultMap=droleResultMap}
特殊字符(#和$)
在 MyBatis中,我们常常传递字符串,我们设置的参数#(name)在大部分的情况下 My Batis会用创建预编译的语句,然后 MyBatis为它设值,而有时候我们需要的是传递SQL语句的本身,而不是SQ所需要的参数。例如,在一些动态表格(有时候经常遇到根据不同的条件产生不同的动态列)中,我们要传递SQL的列名,根据某些列进行排序,或者传递列名给SQL都是比较常见的场景,当然 My Batis也对这样的场景进行了支持。
例如,在程序中传递变量 columns="co1,co2,co3…“给SQL让其组装成为SQL语句。我们当然不想被 MyBatis像处理普通参数样把它设为"col1,co2,co3…”,那么我们就可以写成如下语句。
select ${columns} from user
但这样容易造成sql注入攻击。使用则需要对参数进行安全控制。
#{} :是以预编译的形式,将参数设置到sql语句中,可以防止SQL注入。
${} :取出的值直接拼接在SQL语句中,会有SQL注入的风险。
sql
这个元素可以被用来定义可重用的 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>
这样可以达到代码片段的重用,属性值也可以被用在 include 元素的 refid 属性里或 include 元素的内部语句中,例如:
<sql id="sometable">
${prefix}Table
</sql>
<sql id="someinclude">
from
<include refid="${include_target}"/>
</sql>
<select id="select" resultType="map">
select
field1, field2, field3
<include refid="someinclude">
<property name="prefix" value="Some"/>
<property name="include_target" value="sometable"/>
</include>
</select>
resultMap映射集
resultMap是 MyBatis里面最复杂的元素。它的作用是定义映射规则、级联的更新、定制类型转化器等。
resultMap定义的主要是一个结果集的映射关系。 MyBatis现有的版本只支持 result查询。
构成
<resultMap>
<constructor>
<idArg/>
<arg/>
</constructor>
<id/>
<result/>
<association/>
<collection/>
<discriminator>
<case/>
</discriminator>
</resultMap>
constructor
当映射集需要使用到有参构造器时:
<constructor>
<idArg column="id" javaType="int"/>
<arg column="username" javaType="String"/>
<arg column="age" javaType="_int"/>
</constructor>
属性 | 描述 |
---|---|
column | 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 |
javaType | 一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。 |
jdbcType | JDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可能存在空值的列指定这个类型。 |
typeHandler | 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。 |
select | 用于加载复杂类型属性的映射语句的 ID,它会从 column 属性中指定的列检索数据,作为参数传递给此 select 语句。具体请参考关联元素。 |
resultMap | 结果映射的 ID,可以将嵌套的结果集映射到一个合适的对象树中。 它可以作为使用额外 select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet 。这样的 ResultSet 将会将包含重复或部分数据重复的结果集。为了将结果集正确地映射到嵌套的对象树中,MyBatis 允许你 “串联”结果映射,以便解决嵌套结果集的问题。 |
name | 构造方法形参的名字。从 3.4.3 版本开始,通过指定具体的参数名,你可以以任意顺序写入 arg 元素 |
id、resulet
主键,允许多个主键,称为联级主键。
属性 | 描述 |
---|---|
property | 映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用。否则 MyBatis 将会寻找给定名称的字段。 无论是哪一种情形,你都可以使用通常的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。 |
column | 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 |
javaType | 一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。 |
jdbcType | JDBC 类型,所支持的 JDBC 类型参见这个表格之后的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可能存在空值的列指定这个类型。 |
typeHandler | 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。 |
级联
关联(association)元素处理“有一个”类型的关系。 比如,在我们的示例中,一个博客有一个用户。关联结果映射和其它类型的映射工作方式差不多。 你需要指定目标属性名以及属性的javaType(很多时候 MyBatis 可以自己推断出来),在必要的情况下你还可以设置 JDBC 类型,如果你想覆盖获取结果值的过程,还可以设置类型处理器。
关联的不同之处是,你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:
嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。
属性 | 描述 |
---|---|
column | 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 和 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。 |
select | 用于加载复杂类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。 具体请参考下面的例子。注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 和 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。 |
fetchType | 可选的。有效值为 lazy 和 eager 。 指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled ,使用属性的值。 |
association (一对一)
<mapper namespace="com.java.xrc.mapper.CardMapper">
<select id="selectById" resultType="card">
select id,serial,address from card
<where>
id=#{id}
</where>
</select>
</mapper>
<mapper namespace="com.java.xrc.mapper.StudentMapper">
<resultMap id="studentVo1" type="studentVo">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<association property="card" column="card_id" select="com.java.xrc.mapper.CardMapper.selectById"><!-- clomn 为传入的查询值 select 调用级联方法-->
</association>
</resultMap>
<select id="selectVoById" resultMap="studentVo1">
select id,name,sex,card_id from student
<where>
id=#{id}
</where>
</select>
</mapper>
collection(一对多)
<mapper namespace="com.java.xrc.mapper.LectureMapper">
<select id="selectByStudentId" resultType="lecture">
select id,name from lecture
<where>
student_id=#{id}
</where>
</select>
</mapper>
<mapper namespace="com.java.xrc.mapper.StudentMapper">
<resultMap id="studentVo" type="studentVo">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<association property="card" column="card_id" select="com.java.xrc.mapper.CardMapper.selectById"/>
<collection property="lectures" column="id" select="com.java.xrc.mapper.LectureMapper.selectByStudentId"/><!-- 使用一对多级联 -->
</resultMap>
<select id="selectVoById" resultMap="studentVo">
select id,name,sex,card_id from student
<where>
id=#{id}
</where>
</select>
</mapper>
discriminator(鉴别器)
<mapper namespace="com.java.xrc.mapper.StudentMapper">
<resultMap id="studentVo" type="studentVo">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<association property="card" column="card_id" select="com.java.xrc.mapper.CardMapper.selectById"/>
<!-- 使用鉴别器,传入参数选择不同结果集 -->
<discriminator javaType="int" column="sex">
<case value="0" resultMap="studentVo1"></case>
<case value="1" resultMap="studentVo2"></case>
</discriminator>
</resultMap>
<resultMap id="studentVo1" type="studentVo" extends="studentVo">
<collection property="lectures" column="id" select="com.java.xrc.mapper.LectureMapper.selectByStudentId"/>
</resultMap>
<select id="selectVoById" resultMap="studentVo">
select id,name,sex,card_id from student
<where>
id=#{id}
</where>
</select>
</mapper>
延迟加载
全局配置
<settings>
<setting name="lazyLoadingEnabled" value="true"/><!-- trues时:全局级联查询为懒加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- true时:访问的级联值时,一次加载所有的懒加载 -->
</settings>
局部配置
在级联标签中设置fetchType=“lazy”,开启懒加载设置。
<association fetchType="lazy" property="card" column="card_id" select="com.java.xrc.mapper.CardMapper.selectById"/>
<collection fetchType="eager" property="lectures" column="id" select="com.java.xrc.mapper.LectureMapper.selectByStudentId"/>
直接映射
通过sql级联查询出所有值,然后使用result标签映射到pojo。可以使用card.id给对象设置值。
<mapper namespace="com.java.xrc.mapper.StudentMapper">
<resultMap id="studentVo" type="studentVo">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="card.id" column="card_id"/>
<result property="card.serial" column="card_serial"/>
<result property="card.address" column="card_address"/>
</resultMap>
<select id="selectVoById" resultMap="studentVo">
SELECT
s.id id,
s.NAME NAME,
s.sex sex,
c.id card_id,
c.serial card_serial,
c.address card_address
FROM
student s
LEFT JOIN card c ON s.card_id = c.id
<where>
s.id=#{id}
</where>
</select>
属性 | 描述 |
---|---|
resultMap | 结果映射的 ID,可以将此关联的嵌套结果集映射到一个合适的对象树中。 它可以作为使用额外 select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet 。这样的 ResultSet 有部分数据是重复的。 为了将结果集正确地映射到嵌套的对象树中, MyBatis 允许你“串联”结果映射,以便解决嵌套结果集的问题。使用嵌套结果映射的一个例子在表格以后。 |
columnPrefix | 当连接多个表时,你可能会不得不使用列别名来避免在 ResultSet 中产生重复的列名。指定 columnPrefix 列名前缀允许你将带有这些前缀的列映射到一个外部的结果映射中。 详细说明请参考后面的例子。 |
notNullColumn | 默认情况下,在至少一个被映射到属性的列不为空时,子对象才会被创建。 你可以在这个属性上指定非空的列来改变默认行为,指定后,Mybatis 将只在这些列非空时才创建一个子对象。可以使用逗号分隔来指定多个列。默认值:未设置(unset)。 |
autoMapping | 如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 此属性会覆盖全局的属性 autoMappingBehavior。注意,本属性对外部的结果映射无效,所以不能搭配 select 或 resultMap 元素使用。默认值:未设置(unset)。 |
<mapper namespace="com.java.xrc.mapper.StudentMapper">
<resultMap id="studentVo" type="studentVo">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<association property="card" resultMap="card" columnPrefix="card_" notNullColumn="id" >
</association>
</resultMap>
<resultMap id="card" type="card">
<id property="id" column="id"/>
<result property="serial" column="serial"/>
<result property="address" column="address"/>
</resultMap>
<select id="selectVoById" resultMap="studentVo">
SELECT
s.id id,
s.NAME NAME,
s.sex sex,
c.id card_id,
c.serial card_serial,
c.address card_address
FROM
student s
LEFT JOIN card c ON s.card_id = c.id
<where>
s.id=#{id}
</where>
</select>
</mapper>
缓存
缓存是在计算机内存上保存的数据,在读取的时候无需再从磁盘读入,因此具备快速读取和使用的特点,如果缓存命中率高,那么可以极大地提高系统的性能。如果缓存命中率很低,那么缓存就不存在使用的意义了,所以使用缓存的关键在于存储内容访问的命中率。
一级缓存
mybatis默认开启只开启一级,所以在参数和SQL完全一样的情况下,我们使用同一个Session对象调用同一个 Mapper的方法,往往只执行次SQL,因为使用 SqlSession第一次查询后,MBas会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没超时的情况下,SqISession都只会取出当前缓存的数据,而不会再次发送SQL到数据,但是不同的 SqlSesion对象,因为不同的Session都是相互隔离的则不会缓存。
二级缓存
二级缓存针对于不同的会话(SqlSesion对象)有效,一个namespace(命名空间)就是一个二级缓存单位。
要启用全局的二级缓存,可以开启全局缓存配置,
<settings>
<setting name="cacheEnabled" value="true"/><!--开启缓存-->
</settings>
单个namespace开启则在SQL 映射文件中添加一行:
<cache/>
默认缓存效果如下:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
这些属性可以通过 cache 元素的属性来修改。比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
eviction
代表的是缓存回收策略,目前 My Batis提供以下策略:
- LRU,最近最少使用的,移除最长时间不用的对象
- FIFO,先进先出,按对象进入缓存的顺序来移除它
- SOFT,软引用,移除基于垃圾回收器状态和软引用规则的对象
- WEAK,弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval
刷新间隔时间,单位为毫秒,这里配置的是100秒刷新,如果你不配置它,那么当SQL被执行的时候才会去刷新缓存。
size
引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出。这里配置的是1024个对象
readOnly
只读,意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有办法修改缓存,它的默认值为台false,不允许我们修改
注意:
- 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
- 实现二级缓存的时候, MyBatis要求返回的POJO必须是可序列化的,也就是要求实现 Serializable接口。
自定义缓存
也可以设置自定义的缓存:
<cache type="com.java.xrc.cache.MyCache"/>
这个示例展示了如何使用一个自定义的缓存实现。type 属性指定的类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器。 这个接口是 MyBatis 框架中许多复杂的接口之一,但是行为却非常简单。
public interface Cache {
String getId();//获取缓存编号
int getSize();//获取缓存对象大小
void putObject(Object key, Object value);//保存key值的缓存对象
Object getObject(Object key);//通过key值获取缓存对象
Object removeObject(Object key);//通过key值删除缓存对象
void clear();//清空缓存
ReadWriteLock getReadWriteLock();//获取缓存的读写锁
}
为了对你的缓存进行配置,只需要简单地在你的缓存实现中添加公有的 JavaBean 属性,然后通过 cache 元素传递属性值。
例如,下面的例子将在你的缓存实现上调用一个名为 setCacheFile(String file)
的方法:
<cache type="com.java.xrc.cache.MyCache">
<property name="cacheFile" value="file.txt"/>
</cache>
cache-ref
对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新。 但你可能会想要在多个命名空间中共享相同的缓存配置和实例。要实现这种需求,你可以使用 cache-ref 元素来引用另一个缓存。
<cache-ref namespace="com.java.xrc.mapper.UserMapper"/>
刷新缓存
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
动态SQL
if
<if test=""></if>
choose、when、otherwise
只会进入一个when中就结束。
<choose>
<when test=""></when>
<when test=""></when>
<otherwise></otherwise>
</choose>
trim、where、set
trim 使用
属性 | 值 |
---|---|
prefix | 整个拼接的字符串,添加的前缀 |
prefixOverrides | 整个拼接的字符串,要去掉的前缀 |
suffix | 整个拼接的字符串,添加的后缀 |
suffixOverrides | 整个拼接的字符串,要去掉的后缀 |
直接使用标签等价于:
<trim prefix="where" prefixOverrides="and">
and s.id=#{id}
</trim>
直接使用标签等价于:
<update id="update">
update student
<trim prefix="set" suffixOverrides=",">
name=#{name},
</trim>
</update>
foreach
属性 | 值 |
---|---|
collection | 指定要遍历的集合 |
item | 将当前要遍历的元素赋值给指定的变量 |
index | 将当前要遍历的元素的索引赋值给指定的变量 |
separator | 每个元素之间的分隔符 |
open | 遍历所有结果拼接一个开始的字符。 |
close | 遍历所有结果拼接一个结束的字符。 |
动态 SQL 的另外一个常用的操作需求是对一个集合进行遍历,通常是在构建 IN 条件语句或者批量插入的时候。比如:
<select id="selectPostIn" resultType="Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
<insert id="insertMul" parameterType="list">
insert into student (name,sex,card_id)
values
<foreach collection="list" index="index" item="stu" separator=",">
(#{stu.name},#{stu.sex},#{stu.cardId})
</foreach>
</insert>
foreach* 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及在迭代结果之间放置分隔符。
注意 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象传递给 foreach 作为集合参数。当使用可迭代对象或者数组时,index 是当前迭代的次数,item 的值是本次迭代获取的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
script
要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素。比如:
@Update({"<script>",
"update Author",
" <set>",
" <if test='username != null'>username=#{username},</if>",
" <if test='password != null'>password=#{password},</if>",
" <if test='email != null'>email=#{email},</if>",
" <if test='bio != null'>bio=#{bio}</if>",
" </set>",
"where id=#{id}",
"</script>"})
void updateAuthorValues(Author author);
test
<if test="name!=null and name!=''"></if>
内置参数
_parameter
mybatis会将所有参数封装成一map集合,通过此参数可以获取该map。可通过该参数判断方法是否传参,也可通过该参数获取任意参数值。
_databaseId
表示当前数据库的别名。可根据该属性写出不同数据库应执行的代码。
bind
bind
元素可以从 OGNL 表达式中创建一个变量并将其绑定到上下文。比如模糊查询:
<select id="selectByName" resultType="User">
<bind name="pattern" value="'%' + name + '%'" />
SELECT id,name,sex FROM User
WHERE name LIKE #{pattern}
</select>
动态 SQL 中的可插拔脚本语言
MyBatis 从 3.2 开始支持可插拔脚本语言,这允许你插入一种脚本语言驱动,并基于这种语言来编写动态 SQL 查询语句。
可以通过实现以下接口来插入一种语言:
public interface LanguageDriver {
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}
一旦设定了自定义语言驱动,你就可以在 mybatis-config.xml 文件中将它设置为默认语言:
<typeAliases>
<typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings>
<setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>
除了设置默认语言,你也可以针对特殊的语句指定特定语言,可以通过如下的 lang
属性来完成:
<select id="selectBlog" lang="myLanguage">
SELECT * FROM BLOG
</select>
或者,如果你使用的是映射器接口类,在抽象方法上加上 @Lang
注解即可:
public interface Mapper {
@Lang(MyLanguageDriver.class)
@Select("SELECT * FROM BLOG")
List<Blog> selectBlog();
}
注意 可以将 Apache Velocity 作为动态语言来使用,更多细节请参考 MyBatis-Velocity 项目。
你前面看到的所有 xml 标签都是由默认 MyBatis 语言提供的,而它由别名为 xml
的语言驱动器 org.apache.ibatis.scripting.xmltags.XmlLanguageDriver
所提供。