企业开发基础-4-Mybatis-映射篇
顶级元素 ( 按照应被定义的顺序列出 )
元素 | 说明 |
---|---|
cache | 该命名空间的缓存配置 |
cache-ref | 引用其它命名空间的缓存配置 |
resultMap | 描述如何从数据库结果集中加载对象 |
sql | 可被其它语句引用的可重用语句块 |
insert | 映射插入语句 |
update | 映射更新语句 |
delete | 映射删除语句 |
select | 映射查询语句 |
文件结构
命名规则:数据库表对应的类名+Mapper.xml
一个映射文件对应一个实体类,对应一个表中的操作
映射文件主要用于编写SQL、访问以及操作表中的数据
映射文件存放位置是maven工程下的
src/main/resources/mappers
目录下映射配置文件要保证两个一致
- mapper接口的全类名和映射文件的命名空间namespace保持一致
- mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致
1 |
|
select
1 | <select id="selectPerson" parameterType="int" resultType="hashmap"> |
字段 | 说明 |
---|---|
id | 指定mapper接口中对应的方法名selectPerson |
parameterType | 接受一个 int(或 Integer)类型的参数 |
resultType | 返回一个 HashMap 类型的对象 |
注意事项
#{id}
告诉MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个“?”来标识
1
2
3
4 // 近似的 JDBC 代码,非 MyBatis 代码...
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);
1 | <select |
属性 | 说明 |
---|---|
id | 指定mapper接口中对应的方法名 |
parameterType | 将会传入这条语句的参数的类全限定名或别名 |
resultType | 返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。resultType属性表示自动映射,用于属性名和表中字段名一致的情况 |
resultMap | 对外部 resultMap 的命名引用。resultMap属性表示自定义映射,用于一对多或多对一或字段名和属性名不一致的情况 |
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 (依赖数据库驱动)。 |
insert, update 和 delete
insert
主键自增
如果数据库支持自动生成主键的字段,可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置为目标属性
1 | <insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id"> |
属性 | 说明 |
---|---|
useGeneratedKeys=”true” | 支持自动生成主键的字段 |
keyProperty=”id” | 把 keyProperty 设置为目标属性 |
对于不支持自动生成主键列的数据库和可能不支持自动生成主键的 JDBC 驱动
1 | <insert id="insertAuthor"> |
首先会运行 selectKey 元素中的语句,并设置 Author 的 id,然后才会调用插入语句,就实现了数据库自动生成主键类似的行为
1 | <selectKey |
属性 | 描述 |
---|---|
keyProperty |
selectKey 语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
keyColumn |
返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
resultType |
结果的类型。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果生成列不止一个,则可以使用包含期望属性的 Object 或 Map。 |
order |
可以设置为 BEFORE 或 AFTER 。如果设置为 BEFORE ,那么它首先会生成主键,设置 keyProperty 再执行插入语句。如果设置为 AFTER ,那么先执行插入语句,然后是 selectKey 中的语句 |
statementType |
MyBatis 支持 STATEMENT ,PREPARED 和 CALLABLE 类型的映射语句,分别代表 Statement , PreparedStatement 和 CallableStatement 类型。 |
update
1 | <update id="updateAuthor"> |
delete
1 | <delete id="deleteAuthor"> |
参数
sql语句中参数获取方式
参数获取方式 | 说明 |
---|---|
${参数} | 本质是字符串拼接,会将参数原原本本拼接入SQL语句中,如果参数是字符串类型的需要手动加上引号,无法防止sql注入 |
#{参数} | 本质是占位符赋值,会自动加上引号,如果是字符串类型的参数无需手动加上引号,可以防止sql注入 |
参数类型
单个字面量类型的参数
字面量是指基本数据类型以及包装类还有自定义类等
1 | <select id="getUserByUsername" resultType="User"> |
此时获取参数时花括号里边的参数名不做要求,可以是随意的名称,如果使用${}的方式记得加引号
多个字面量类型的参数
若mapper接口中的方法参数为多个时,MyBatis会自动将这些参数放在一个map集合中,此时参数名不能是随意的名称
arg和param是同时存在于同一个map中的,在SQL语句获取参数时只需指定这些键的名称即可
- arg可以通过#{arg0}、#{arg1}、#{arg2}…获取接口中相对应的参数
- param可以通过#{param1}、#{param2}、#{param3}…获取接口中相对应的参数。
1 | <!--arg的方式--> |
map集合类型的参数
- 通过把参数以键值对的方式存储到map集合中,然后把map集合作为mapper接口类的参数,在mapper映射文件中编写SQL语句需要调用参数值时,可以通过map集合的key调用
- 通过自定义键的名称,我们在SQL语句里就可以使用自定义的参数名了
- 比如,这个自定义的map集合可以是
{("username","参数值"),("password","参数值")}
1 | public List<User> findUserMap(Map<String, Object> parameterMap); |
1 | <select id="findUserMap" parameterType="map" resultType="user"> |
@Param注解标识的参数
可以通过使用@Param注解在映射方法的形参中指定好参数名, 通过 @Param() 注解给参数加上key,在mapper映射文件中就可以通过key,所谓的key也就是@Param内value中的值。
关于@Param
- 基本类型的参数或者String类型需要加上
- 引用类型不需要加
- SQL中引用的是@Param(“id”)设定的属性名
1 | User getUserByParam(; String username, String password) |
1 | <select id="checkLoginByParam" resultType="User"> |
实体类型的参数
如果映射方法的形参是一个实体类型时,可以通过访问实体类对象中的属性名获取属性值
1 | public class User { |
1 | <insert id="insertUser"> |
上述小结
单个参数或者多个参数,都用注解的方式,如果是实体类那就用实体类属性的方式
字符串替换
默认情况下,使用 #{}
参数语法时,MyBatis 会创建 PreparedStatement
参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以:
1 | ORDER BY ${columnName} |
这样,MyBatis 就不会修改或转义该字符串了。
结果映射
元素 | 说明 |
---|---|
constructor |
用于在实例化类时,注入结果到构造方法中;idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能;arg - 将被注入到构造方法的一个普通结果 |
id |
ID 结果;标记出作为 ID 的结果可以帮助提高整体性能 |
result |
注入到字段或 JavaBean 属性的普通结果 |
association |
复杂类型的关联;许多结果将包装成这种类型;嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用 |
collection |
复杂类型的集合;嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用 |
discriminator |
使用结果值来决定使用哪个 resultMap ;case – 基于某些值的结果映射;嵌套结果映射 – case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射 |
显示配置ResultMap: 解决列名不匹配
1 | <resultMap id="userResultMap" type="User"> |
1 | <select id="selectUsers" resultMap="userResultMap"> |
id 和 result属性
1 | <id property="id" column="post_id"/> |
id 和 result 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。
这两者之间的唯一不同是,id 元素对应的属性会被标记为对象的标识符,在比较对象实例时使用。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。
Id 和 Result 的属性
属性 | 描述 |
---|---|
property |
映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 |
column |
数据库中的列名,或者是列的别名。 |
javaType |
一个 Java 类的全限定名,或一个类型别名, 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么应该明确地指定 javaType |
jdbcType |
JDBC 类型, 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。 |
typeHandler |
使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。 |
构造方法
通过修改对象属性的方式,可以满足大多数的数据传输对象以及绝大部分领域模型的要求。但有些情况下你想使用不可变类。 一般来说,很少改变或基本不变的包含引用或数据的表,很适合使用不可变类。 构造方法注入允许你在初始化时为类设置属性的值,而不用暴露出公有方法。MyBatis 也支持私有属性和私有 JavaBean 属性来完成注入
属性 | 描述 |
---|---|
column |
数据库中的列名,或者是列的别名。 |
javaType |
一个 Java 类的完全限定名,或一个类型别名(。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。 |
jdbcType |
JDBC 类型 |
typeHandler |
使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。 |
select |
用于加载复杂类型属性的映射语句的 ID,它会从 column 属性中指定的列检索数据,作为参数传递给此 select 语句 |
resultMap |
结果映射的 ID,可以将嵌套的结果集映射到一个合适的对象树中。 它可以作为使用额外 select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet 。这样的 ResultSet 将会将包含重复或部分数据重复的结果集。为了将结果集正确地映射到嵌套的对象树中,MyBatis 允许你 “串联”结果映射,以便解决嵌套结果集的问题。 |
name |
构造方法形参的名字。 |
1 | public class User { |
为了将结果注入构造方法,MyBatis 需要通过某种方式定位相应的构造方法
1 | <constructor> |
当你在处理一个带有多个形参的构造方法时,很容易搞乱 arg 元素的顺序 。为了通过名称来引用构造方法参数,你可以添加 @Param
注解,或者使用 ‘-parameters’ 编译选项并启用 useActualParamName
选项(默认开启)来编译项目。
1 | <constructor> |
关联
N+1查询问题
- 执行了一个单独的 SQL 语句来获取结果的一个列表(就是“+1”)。
- 对列表返回的每条记录,你执行一个 select 查询语句来为每条记录加载详细信息(就是“N”)。
关联元素处理“有一个”类型的关系。 比如,一个博客有一个用户。
属性 | 描述 |
---|---|
property |
映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用。否则 MyBatis 将会寻找给定名称的字段。 |
javaType |
一个 Java 类的完全限定名,或一个类型别名。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。 |
jdbcType |
JDBC 类型 |
typeHandler |
使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。 |
1 | <association property="author" column="blog_author_id" javaType="Author"> |
关联结果映射和其它类型的映射工作方式差不多,关联的不同之处是,你需要告诉 MyBatis 如何加载关联
加载方式 | 说明 |
---|---|
嵌套 Select 查询 | 通过执行另外一个 SQL 映射语句来加载期望的复杂类型。 |
嵌套结果映射 | 使用嵌套的结果映射来处理连接结果的重复子集。 |
关联的嵌套 Select 查询
属性 | 描述 |
---|---|
column |
数据库中的列名,或者是列的别名。 注意:在使用复合主键的时候,你可以使用 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 ,使用属性的值。 |
1 | <resultMap id="blogResult" type="Blog"> |
有两个 select 查询语句:一个用来加载博客(Blog),另外一个用来加载作者(Author),而且博客的结果映射描述了应该使用 selectAuthor
语句加载它的 author 属性。 其它所有的属性将会被自动加载,只要它们的列名和属性名相匹配。
关联的嵌套结果映射
属性 | 描述 |
---|---|
resultMap |
结果映射的 ID,可以将此关联的嵌套结果集映射到一个合适的对象树中。 它可以作为使用额外 select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet 。这样的 ResultSet 有部分数据是重复的。 为了将结果集正确地映射到嵌套的对象树中, MyBatis 允许你“串联”结果映射,以便解决嵌套结果集的问题。 |
columnPrefix |
当连接多个表时,你可能会不得不使用列别名来避免在 ResultSet 中产生重复的列名。指定 columnPrefix 列名前缀允许你将带有这些前缀的列映射到一个外部的结果映射中。 |
notNullColumn |
默认情况下,在至少一个被映射到属性的列不为空时,子对象才会被创建。 你可以在这个属性上指定非空的列来改变默认行为,指定后,Mybatis 将只在这些列中任意一列非空时才创建一个子对象。可以使用逗号分隔来指定多个列。默认值:未设置(unset)。 |
autoMapping |
如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。注意,本属性对外部的结果映射无效,所以不能搭配 select 或 resultMap 元素使用。默认值:未设置(unset)。 |
现在将博客表和作者表连接在一起,而不是执行一个独立的查询语句
1 | <select id="selectBlog" resultMap="blogResult"> |
注意查询中的连接,以及为确保结果能够拥有唯一且清晰的名字,我们设置的别名。 这使得进行映射非常简单
1 | <resultMap id="blogResult" type="Blog"> |
博客(Blog)作者(author)的关联元素委托名为 “authorResult” 的结果映射来加载作者对象的实例,上面的示例使用了外部的结果映射元素来映射关联。这使得 Author 的结果映射可以被重用
如果你不打算重用它,或者你更喜欢将你所有的结果映射放在一个具有描述性的结果映射元素中。 你可以直接将结果映射作为子元素嵌套在内
1 | <resultMap id="blogResult" type="Blog"> |
那如果博客(blog)有一个共同作者(co-author)该怎么办?
1 | <select id="selectBlog" resultMap="blogResult"> |
Author 的结果映射定义
1 | <resultMap id="authorResult" type="Author"> |
由于结果中的列名与结果映射中的列名不同。你需要指定 columnPrefix
以便重复使用该结果映射来映射 co-author 的结果。
1 | <resultMap id="blogResult" type="Blog"> |
关联的多结果集
属性 | 描述 |
---|---|
column |
当使用多个结果集时,该属性指定结果集中用于与 foreignColumn 匹配的列(多个列名以逗号隔开),以识别关系中的父类型与子类型。 |
foreignColumn |
指定外键对应的列名,指定的列将与父类型中 column 的给出的列进行匹配。 |
resultSet |
指定用于加载复杂类型的结果集名字。 |
解决 N+1 查询问题的方法。
某些数据库允许存储过程返回多个结果集,或一次性执行多个语句,每个语句返回一个结果集。 我们可以利用这个特性,在不使用连接的情况下,只访问数据库一次就能获得相关数据。
存储过程执行下面的查询并返回两个结果集。第一个结果集会返回博客(Blog)的结果,第二个则返回作者(Author)的结果。
1 | SELECT * FROM BLOG WHERE ID = #{id} |
在映射语句中,必须通过 resultSets
属性为每个结果集指定一个名字,多个名字使用逗号隔开。
1 | <select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE"> |
可以指定使用 “authors” 结果集的数据来填充 “author” 关联:
1 | <resultMap id="blogResult" type="Blog"> |
集合
1 | <collection property="posts" ofType="domain.blog.Post"> |
映射嵌套结果集合到一个 List 中,可以使用集合元素。 和关联元素一样,我们可以使用嵌套 Select 查询,或基于连接的嵌套结果映射集合。
一个博客(Blog)只有一个作者(Author)。但一个博客有很多文章(Post)。
1 | private List<Post> posts; |
集合的嵌套 Select 查询
使用嵌套 Select 查询来为博客加载文章。
1 | <resultMap id="blogResult" type="Blog"> |
属性 | 说明 |
---|---|
ofType | 它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来 |
1 | <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/> |
指 “posts 是一个存储 Post 的 ArrayList 集合”
集合的嵌套结果映射
连接了博客表和文章表,并且为每一列都赋予了一个有意义的别名,以便映射保持简单,映射博客里面的文章集合
1 | <select id="selectBlog" resultMap="blogResult"> |
更详略的、可重用的结果映射
1 | <resultMap id="blogResult" type="Blog"> |
集合的多结果集
像关联元素那样,我们可以通过执行存储过程实现,它会执行两个查询并返回两个结果集,一个是博客的结果集,另一个是文章的结果集:
1 | SELECT * FROM BLOG WHERE ID = #{id} |
在映射语句中,必须通过 resultSets
属性为每个结果集指定一个名字,多个名字使用逗号隔开。
1 | <select id="selectBlog" resultSets="blogs,posts" resultMap="blogResult"> |
指定 “posts” 集合将会使用存储在 “posts” 结果集中的数据进行填充:
1 | <resultMap id="blogResult" type="Blog"> |
鉴别器
1 | <discriminator javaType="int" column="draft"> |
有时候,一个数据库查询可能会返回多个不同的结果集(但总体上还是有一定的联系的)。 鉴别器(discriminator)元素就是被设计来应对这种情况的,另外也能处理其它情况,例如类的继承层次结构。 鉴别器的概念很好理解——它很像 Java 语言中的 switch 语句。
一个鉴别器的定义需要指定 column 和 javaType 属性。
属性 | 说明 |
---|---|
column | column 指定了 MyBatis 查询被比较值的地方 |
javaType | javaType 用来确保使用正确的相等测试 |
在这个示例中,MyBatis 会从结果集中得到每条记录,然后比较它的 vehicle type 值。 如果它匹配任意一个鉴别器的 case,就会使用这个 case 指定的结果映射。
1 | <resultMap id="vehicleResult" type="Vehicle"> |
如果不能匹配任何一个 case,MyBatis 就只会使用鉴别器块外定义的结果映射。
自动映射
当自动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略大小写)。 这意味着如果发现了 ID 列和 id 属性,MyBatis 会将列 ID 的值赋给 id 属性。
通常数据库列使用大写字母组成的单词命名,单词间用下划线分隔;而 Java 属性一般遵循驼峰命名法约定。为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase
设置为 true。
自动映射等级:
自动映射等级 | 说明 |
---|---|
NONE |
禁用自动映射。仅对手动映射的属性进行映射 |
PARTIAL |
对除在内部定义了嵌套结果映射(也就是连接的属性)以外的属性进行映射 |
FULL |
自动映射所有属性。 |
无论设置的自动映射等级是哪种,你都可以通过在结果映射上设置 autoMapping
属性来为指定的结果映射设置启用/禁用自动映射。
1 | <resultMap id="userResultMap" type="User" autoMapping="false"> |
谨慎使用FULL
默认值是 PARTIAL
,这是有原因的。当对连接查询的结果使用 FULL
时,连接查询会在同一行中获取多个不同实体的数据,因此可能导致非预期的映射。 下面的例子将展示这种风险:
1 | <select id="selectBlog" resultMap="blogResult"> |
1 | <resultMap id="blogResult" type="Blog"> |
在该结果映射中,Blog 和 Author 均将被自动映射。但是注意 Author 有一个 id 属性,在 ResultSet 中也有一个名为 id 的列,所以 Author 的 id 将填入 Blog 的 id,这可不是你期望的行为。 所以,要谨慎使用 FULL
。