本文主要是介绍【Mybatis】Mapper 标签学习总结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
【学习描述】
最近完善了一版APP接口的统计需求,之前单独出了统计服务,所以就安排这部分工作在统计服务中进行。上线之前,自己去看了看开发写的代码,一个有六七年工作经验的程序员,对我来说,她的经验比我丰富很多,看过代码之后,确实发现了值得我去学习的地方。
项目底层用的Mybatis框架,之前有过项目经验,但其实并不是很清楚XML中写的那些东西,如果没有一个示例,可能自己连一个手写的思路都没有,所以本篇博客以Mybatis Mapper的XML文件为中心,总结下其中各个标签的学习和使用。
【知识积累】
MyBatis 的真正强大在于它的映射语句,由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。
SQL映射文件的几个顶级元素:
- cache 给定命名空间的缓存配置
- cache-ref 其他命名空间缓存配置的引用
- resultMap 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象
- parameterMap 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除
- sql 可被其他语句引用的可重用语句块
- insert 映射插入语句
- update 映射更新语句
- delete 映射删除语句
- select 映射查询语句
下面从语句出发,总结下每个元素的细节内容
(一)select
查询语句是 MyBatis 中最常用的元素之一,光能把数据存到数据库中价值并不大,如果还能重新取出来才有用,多数应用也都是查询比修改要频繁。对每个插入、更新或删除操作,通常对应多个查询操作。这是 MyBatis 的基本原则之一,也是将焦点和努力放到查询和结果映射的原因。简单查询的 select 元素是非常简单的。比如:
<select id="selectUser" parameterType="int" resultType="hashmap">SELECT * FROM User WHERE ID = #{id}
</select>
这个语句被称作 selectUser,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值。
参数符号#{id},是指告诉Mybatis创建一个预处理语句参数,通过JDBC,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中。
select属性列表:
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType | 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。 |
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 中的一个,默认值为 unset (依赖驱动)。。 |
databaseId | 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。 |
resultOrdered | 这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。 |
resultSets | 这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的。 |
(二)insert,update,delete
属性列表:
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType | 将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。 |
flushCache | 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true(对应插入、更新和删除语句)。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。 |
statementType | STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
useGeneratedKeys | (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。 |
keyProperty | (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
keyColumn | (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
databaseId | 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。 |
使用示例:
<insert id="insertUser">insert into User (id,username,password,email)values (#{id},#{username},#{password},#{email})
</insert><update id="updateUser">update User setusername = #{username},password = #{password},email = #{email}where id = #{id}
</update><delete id="deleteUser">delete from User where id = #{id}
</delete>
一般我们使用Mysql数据库,都会定义自动生成主键的字段,那么可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置到目标属性。例如,如果上面的 User 表已经对 id 使用了自动生成的列类型,那么语句可以修改为:
<insert id="insertUser" useGeneratedKeys="true"keyProperty="id">insert into Author (username,password,email)values (#{username},#{password},#{email}
</insert>
如果批量插入的话,我们可以定义User数组或集合,并返回自动生成的主键:
<insert id="insertBatchUser" useGeneratedKeys="true"keyProperty="id">insert into User (username, password, email) values<foreach item="item" collection="list" separator=",">(#{item.username}, #{item.password}, #{item.email})</foreach>
</insert>
(三)sql
这个元素可以被用来定义可重用的 SQL 代码段,可以包含在其他语句中。它可以被静态地(在加载参数) 参数化. 不同的属性值通过包含的实例变化. 比如:
<sql id="Base_Column_List">app_name ,`app_key`,app_secret,create_time,url,company_id</sql><select id="getAppSystemByAppKey" resultMap="BaseResultMap" parameterType="java.lang.String">SELECT<include refid="Base_Column_List"/>FROM wms_app_system<where>app_key = #{app_key,jdbcType=VARCHAR}</where></select>
属性值也可以被用在include元素的refid属性里
<include refid="${include_target}"/>
或include内部语句中
${prefix}Table
示例:
<sql id="table">${prefix}_app_system</sql><sql id="sysinclude">from<include refid="${include_target}"/></sql><select id="selectInclude" resultType="map">selectapp_name, `app_key`, app_secret<include refid="sysinclude"><property name="prefix" value="wms"/><property name="include_target" value="table"/></include><where>app_key = #{app_key,jdbcType=VARCHAR}</where></select>
打印的sql语句,参数和结果:
Preparing: select app_name, `app_key`, app_secret from wms_app_system WHERE app_key = ?
Parameters: benbaolian(String)
Total: 1
(四)参数
<select id="selectUsers" resultType="User">select id, username, passwordfrom userswhere id = #{id}
</select>
上面的这个示例说明了一个非常简单的命名参数映射。参数类型被设置为 int,这样这个参数就可以被设置成任何内容。原生的类型或简单数据类型(比如整型和字符串)因为没有相关属性,它会完全用参数值来替代。然而,如果传入一个复杂的对象,行为就会有一点不同了。比如:
<insert id="insertUser" parameterType="User">insert into users (id, username, password)values (#{id}, #{username}, #{password})
</insert>
如果 User 类型的参数对象传递到了语句中,id、username 和 password 属性将会被查找,然后将它们的值传入预处理语句的参数中。这点相对于向语句中传参是比较好的。
像 MyBatis 的其他部分一样,参数也可以指定一个特殊的数据类型,对于数值类型,还有一个小数保留位数设置,来确定小数点后保留的位数。
#{property,javaType=int,jdbcType=NUMERIC}#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
默认情况下,使用 #{} 格式的语法会导致 MyBatis 创建 PreparedStatement 参数并安全地设置参数(就像使用 ? 一样)。这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中插入一个不转义的字符串。比如,像 ORDER BY,你可以这样来使用:
ORDER BY ${columnName}
用这种方式接受用户的输入,并将其用于语句中的参数是不安全的,会导致潜在的 SQL 注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验。
(五)Result Maps
resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来, 并在一些情形下允许你做一些 JDBC 不支持的事情。 实际上,在对复杂语句进行联合映射的时候,它很可能可以代替数千行的同等功能的代码。 ResultMap 的设计思想是,简单的语句不需要明确的结果映射,而复杂一点的语句只需要描述它们的关系就行了。
查询结果返回一个javaBean示例:
<select id="selectUsers" resultType="com.someapp.model.User">select id, username, hashedPasswordfrom some_tablewhere id = #{id}
</select>
类型别名是你的好帮手。使用它们,你就可以不用输入类的完全限定名称了。比如:
<!-- In mybatis-config.xml file -->
<typeAlias type="com.someapp.model.User" alias="User"/><!-- In SQL Mapping XML file -->
<select id="selectUsers" resultType="User">select id, username, hashedPasswordfrom some_tablewhere id = #{id}
</select>
这些情况下,MyBatis 会在幕后自动创建一个 ResultMap,再基于属性名来映射列到 JavaBean 的属性上。如果列名和属性名没有精确匹配,可以在 SELECT 语句中对列使用别名(这是一个 基本的 SQL 特性)来匹配标签。比如:
<resultMap id="userResultMap" type="User"><id property="id" column="user_id" /><result property="username" column="user_name"/><result property="password" column="hashed_password"/>
</resultMap>
外部使用resultMap示例,解决列名不匹配的另外一种方式:
<resultMap id="BaseResultMap" type="com.uqiauto.statistics.business.appsystem.entity.AppSystem"><result property="appName" column="app_name" jdbcType="VARCHAR" /><result property="appKey" column="app_key" jdbcType="VARCHAR" /><result property="appSecret" column="app_secret" jdbcType="VARCHAR" /><result property="createTime" column="create_time" jdbcType="VARCHAR" /><result property="url" column="url" jdbcType="VARCHAR" /><result property="companyId" column="company_id" jdbcType="VARCHAR" /></resultMap><select id="getAppSystemByAppKey" resultMap="BaseResultMap" parameterType="java.lang.String">SELECT app_name,app_key,app_secret FROM wms_app_systemWHERE app_key = #{app_key,jdbcType=VARCHAR}</select>
找了一个复杂的Result Map的使用示例,记录一下,也许很多标签以后都可能用到:
<resultMap id="detailedBlogResultMap" type="Blog"><constructor><idArg column="blog_id" javaType="int"/></constructor><result property="title" column="blog_title"/><association property="author" javaType="Author"><id property="id" column="author_id"/><result property="username" column="author_username"/><result property="password" column="author_password"/><result property="email" column="author_email"/><result property="bio" column="author_bio"/><result property="favouriteSection" column="author_favourite_section"/></association><collection property="posts" ofType="Post"><id property="id" column="post_id"/><result property="subject" column="post_subject"/><association property="author" javaType="Author"/><collection property="comments" ofType="Comment"><id property="id" column="comment_id"/></collection><collection property="tags" ofType="Tag" ><id property="id" column="tag_id"/></collection><discriminator javaType="int" column="draft"><case value="1" resultType="DraftPost"/></discriminator></collection>
</resultMap>
下面是详细总结:
- id & result
<id property="id" column="post_id"/><result property="subject" column="post_subject"/>
这些是结果映射最基本的内容。id 和 result 都将一个列的值映射到一个简单数据类型(字符串,整型,双精度浮点数,日期等)的属性或字段。
两者之间的唯一不同是, id 表示的结果将是对象的标识属性,这会在比较对象实例时用到。 这样可以提高整体的性能,尤其是缓存和嵌套结果映射(也就是联合映射)的时候。
属性列表:
属性 | 描述 |
---|---|
property | 映射到列结果的字段或属性。如果用来匹配的 JavaBeans 存在给定名字的属性,那么它将会被使用。否则 MyBatis 将会寻找给定名称 property 的字段。 无论是哪一种情形,你都可以使用通常的点式分隔形式进行复杂属性导航。比如,你可以这样映射一些简单的东西: “username” ,或者映射到一些复杂的东西: “address.street.number” 。 |
column | 数据库中的列名,或者是列的别名。一般情况下,这和 传递给 resultSet.getString(columnName) 方法的参数一样。 |
javaType | 一个 Java 类的完全限定名,或一个类型别名(参考上面内建类型别名 的列表) 。如果你映射到一个 JavaBean,MyBatis 通常可以断定类型。 然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证期望的行为。 |
jdbcType | JDBC 类型,所支持的 JDBC 类型参见这个表格之后的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可能为 null 的值指定这个类型。 |
typeHandler | 使用这个属性,你可以覆盖默 认的类型处理器。这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。 |
- constructor 构造方法
public class User {//...public User(Integer id, String username, int age) {//...}
//...
}
为了将结果注入构造方法,MyBatis需要通过某种方式定位相应的构造方法。 在下面的例子中,MyBatis搜索一个声明了三个形参的的构造方法,以 java.lang.Integer, java.lang.String and int 的顺序排列。
<constructor><idArg column="id" javaType="int"/><arg column="username" javaType="String"/><arg column="age" javaType="_int"/>
</constructor>
当你在处理一个带有多个形参的构造方法时,很容易在保证 arg 元素的正确顺序上出错。 从版本 3.4.3 开始,可以在指定参数名称的前提下,以任意顺序编写 arg 元素。 为了通过名称来引用构造方法参数,你可以添加 @Param 注解,或者使用 ‘-parameters’ 编译选项并启用 useActualParamName 选项(默认开启)来编译项目。 下面的例子对于同一个构造方法依然是有效的,尽管第二和第三个形参顺序与构造方法中声明的顺序不匹配。
<constructor><idArg column="id" javaType="int" name="id" /><arg column="age" javaType="_int" name="age" /><arg column="username" javaType="String" name="username" />
</constructor>
- 关联
<association property="author" column="blog_author_id" javaType="Author"><id property="id" column="author_id"/><result property="username" column="author_username"/>
</association>
关联元素处理“有一个”类型的关系。比如,在我们的示例中,一个博客有一个用户。 关联映射就工作于这种结果之上。你指定了目标属性,来获取值的列,属性的 java 类型(很 多情况下 MyBatis 可以自己算出来) ,如果需要的话还有 jdbc 类型,如果你想覆盖或获取的 结果值还需要类型控制器。
关联中不同的是你需要告诉 MyBatis 如何加载关联。MyBatis 在这方面会有两种不同的方式:
嵌套查询:通过执行另外一个 SQL 映射语句来返回预期的复杂类型。
嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集。
嵌套查询的方式很简单:
<resultMap id="blogResult" type="Blog"><association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap><select id="selectBlog" resultMap="blogResult">SELECT * FROM BLOG WHERE ID = #{id}
</select><select id="selectAuthor" resultType="Author">SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
但是对于大型数据集合和列表将不会表现很好。 问题就是我们熟知的 “N+1 查询问题”。概括地讲,N+1 查询问题可以是这样引起的:
你执行了一个单独的 SQL 语句来获取结果列表(就是“+1”)。
对返回的每条记录,你执行了一个查询语句来为每个加载细节(就是“N”)。
这个问题会导致成百上千的 SQL 语句被执行。这通常不是期望的。
MyBatis 能延迟加载这样的查询就是一个好处,因此你可以分散这些语句同时运行的消耗。然而,如果你加载一个列表,之后迅速迭代来访问嵌套的数据,你会调用所有的延迟加载,这样的行为可能是很糟糕的。
所以一般我们采用嵌套结果的方法:
<resultMap id="blogResult" type="Blog"><id property="id" column="blog_id" /><result property="title" column="blog_title"/><association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
</resultMap><resultMap id="authorResult" type="Author"><id property="id" column="author_id"/><result property="username" column="author_username"/><result property="password" column="author_password"/><result property="email" column="author_email"/><result property="bio" column="author_bio"/>
</resultMap>
id元素在嵌套结果映射中扮演着非常重要的角色。我们应该总是指定一个或多个可以唯一标识结果的属性。实际上如果不指定它的话, MyBatis仍然可以工作,但是会有严重的性能问题。在可以唯一标识结果的情况下, 尽可能少的选择属性。主键是一个显而易见的选择(即使是复合主键)。
集合
集合和关联的用法很相似,只是多应用了一个ofType属性:
<resultMap id="blogResult" type="Blog"><id property="id" column="blog_id" /><result property="title" column="blog_title"/><collection property="posts" ofType="Post"><id property="id" column="post_id"/><result property="subject" column="post_subject"/><result property="body" column="post_body"/></collection>
</resultMap>
- 鉴别器
有时一个单独的数据库查询也许返回很多不同 (但是希望有些关联) 数据类型的结果集。 鉴别器元素就是被设计来处理这个情况的, 还有包括类的继承层次结构。 鉴别器非常容易理 解,因为它的表现很像 Java 语言中的 switch 语句。
<resultMap id="vehicleResult" type="Vehicle"><id property="id" column="id" /><result property="vin" column="vin"/><result property="year" column="year"/><result property="make" column="make"/><result property="model" column="model"/><result property="color" column="color"/><discriminator javaType="int" column="vehicle_type"><case value="1" resultType="carResult"><result property="doorCount" column="door_count" /></case><case value="2" resultType="truckResult"><result property="boxSize" column="box_size" /><result property="extendedCab" column="extended_cab" /></case><case value="3" resultType="vanResult"><result property="powerSlidingDoor" column="power_sliding_door" /></case><case value="4" resultType="suvResult"><result property="allWheelDrive" column="all_wheel_drive" /></case></discriminator>
</resultMap>
- 自动映射
当自动映射查询结果时,MyBatis会获取sql返回的列名并在java类中查找相同名字的属性(忽略大小写)。 这意味着如果Mybatis发现了ID列和id属性,Mybatis会将ID的值赋给id。
通常数据库列使用大写单词命名,单词间用下划线分隔;而java属性一般遵循驼峰命名法。 为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase设置为true。
有三种自动映射等级:
NONE - 禁用自动映射。仅设置手动映射属性。
PARTIAL - 将自动映射结果除了那些有内部定义内嵌结果映射的(joins).
FULL - 自动映射所有。
默认值是PARTIAL
【心得总结】
通过以上的学习,再看看项目中的代码,其实会发现,我们更多时候还是通过手写sql的方式进行查询,一些Mybatis中给我们提供的标签很少使用,从而使得我们再看自己写的xml文件会变得困难些,要是能多使用一些这样的标签,相信会简单很多。
这篇关于【Mybatis】Mapper 标签学习总结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!