include标签
在我们使用查找的时候,经常会使用这样的语句:
select * from mytable;
但是这不好,会对效率产生影响,因为有的时候我们只是需要获取某些字段而已。
select id,age,name from mytable;
但是这样写很繁琐。
可以进行如下的修改:
<sql id="example"> id,age </sql>
<select id="selectBlog" resultMap="mymap">
select
<include refid="example"></include>
from mytable
</select>
sql标签中定义了两个字段,而include标签则自动将这两个字段填充进去。
甚至还能这么改:
<sql id="example"> ${abc},age </sql>
<select id="selectBlog" resultMap="mymap">
select
<include refid="example">
<property name="abc" value="id"/>
</include>
from mytable
</select>
property标签将sql中的abc自动修改成了id。
sql标签和include标签的合作不仅仅能够代替字段,更能代替mysql语句的任何一个地方。
<sql id="example"> from ${table} </sql>
<select id="selectBlog" resultMap="mymap">
select *
<include refid="example">
<property name="table" value="mytable"/>
</include>
</select>
这个例子中,include自动填充了from子句。
#{}和${}
#{}是参数占位符,mybatis会对其进行预编译。
#{}能有效防止sql注入问题。
${}是字符串替换,mybatis对其进行编译。
${}不能防止sql注入问题。
变量替换后,#{}对应的变量会自动加上单引号,而${}对应的变量不会自动加上。
使用时,#{}不一定要加@Param注解,而${}一定需要。
只有在获取用户DAO中的参数数据时才能使用#{}。
表名作参数时,必须用 ${}。如:
select * from ${tableName}
但是!如果输入的表名为"mytable;drop table mytable;",那mybatis直接报异常,你应该庆幸mybatis做了处理,没有让坏人得逞。(mysql注入)
order by 时,必须用 ${}。如:
select * from t_user order by ${columnName}
使用 ${} 时,要注意何时加或不加单引号,即 ${} 和 ’ ${} ’
能用 #{} 的地方就用 #{},不用或少用 ${}
深刻探究ResultMap
借用W3Cschool的一个例子(稍作修改)来探究resultmap能够如何映射复杂的关系。
首先看这个复杂的select查询语句:
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio,
A.favourite_section as author_favourite_section,
P.id as post_id,
P.blog_id as post_blog_id,
P.author_id as post_author_id,
P.created_on as post_created_on,
P.section as post_section,
P.subject as post_subject,
P.draft as draft,
P.body as post_body,
PA.id as ppost_author_id,
PA.username as post_author_username,
PA.password as post_author_password,
PA.email as post_author_email,
PA.bio as post_author_bio,
PA.favourite_section as post_author_favourite_section,
C.id as comment_id,
C.post_id as comment_post_id,
C.name as comment_name,
C.comment as comment_text,
T.id as tag_id,
T.name as tag_name
from Blog B
left outer join Author A on B.author_id = A.id
left outer join Post P on B.id = P.blog_id
left outer join Author PA on P.author_id=PA.id
left outer join Comment C on P.id = C.post_id
left outer join Post_Tag PT on PT.post_id = P.id
left outer join Tag T on PT.tag_id = T.id
where B.id = #{id}
</select>
可以理解为一个blog,有自己的author,其中包含了很多post,而每个post也包含了自己的author,包含了很多的评论和标签。
我们这样设计映射文件:
<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">
<id property="id" column="post_author_id"/>
<result property="username" column="post_author_username"/>
<result property="password" column="post_author_password"/>
<result property="email" column="post_author_email"/>
<result property="bio" column="post_author_bio"/>
<result property="favouriteSection" column="post_author_favourite_section"/>
</association>
<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>
我们一点点来解析这份映射文件。
首先,constructor会调用对象的构造方法。
<constructor>
<idArg column="id" javaType="int"/>
<arg column="username" javaType="String"/>
</constructor>
这可以对应某个对象的这个构造函数:
public class User{
public User(int id,String username){
...
}
}
不过要注意参数的顺序和类型与构造函数相同。构造方法中的参数名称无所谓。
association帮助我们把数据映射到对象中。
<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>
这个Blog中包含了一个author对象,而author对象中的各字段就被一一映射到位,很方便。
collection帮助我们把数据映射到对象列表中。
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
可见这个post对象中包含一个Tag的list。数据就会被映射进去。
collection也可以嵌套使用。
discriminator是一个鉴别器,其作用很类似java中的switch语句。
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
如果draft列中是1,那么这个post将被返回成draftpost类型。
类似的用法:
<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>
鉴别器会按照vehicle_type列的内容来进行不同的映射和返回类型的区别。如果不满足所有的case,则默认不会执行。
自动映射
当自动映射查询结果时,MyBatis 会获取 sql 返回的列名并在 java 类中查找相同名字的属性(忽略大小写)。 这意味着如果 Mybatis 发现了ID 列和 id 属性,Mybatis会将ID的值赋给 id。
这项特性可以和上面是resultmap混合使用,如果在resultmap中没有特别声明,那么mybatis将进行自动映射。
对于每一个 result map,所有的 ResultSet 提供的列, 如果没有被手工映射,则将被自动映射。自动映射处理完毕后手工映射才会被处理。
通常数据库列使用大写单词命名,单词间用下划线分隔;而java属性一般遵循驼峰命名法。 为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase设置为 true。
一共三个匹配级别:
NONE:禁止自动匹配
PARTIAL(默认):自动匹配所有属性,有内部嵌套(association、collection)除外
FULL:自动匹配属性(不推荐)
这在conf.xml中配置。加上如下代码:
<settings>
<setting name="autoMappingBehavior" value="NONE"/>
</settings>
或者对于每一个resultmap都可以设置自动映射的开关:
<resultMap id="mymap" type="others.Blog" autoMapping="false">
<id property="id" column="id" />
</resultMap>
缓存设置
MyBatis存在缓存支持,提供了一级缓存和二级缓存。
一级缓存是基于PerpetualCache的HashMap本地缓存,作用范围为session内,当session flush或者close以后,该session的缓存会被清空
二级缓存就是全局缓存,超出了session范围,可以被多个sqlSession共享。
一级缓存的是SQL语句,二级缓存的是结果对象。
二级缓存如此配置:
在conf.xml中的setting加入如下代码:
<setting name="cacheEnabled" value="true"/>
然后再具体的映射文件中加入如下代码:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
它创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会 导致冲突。
然后再把需要使用cache的查询语句的useCache属性设置为true。
接下来谈一谈readOnly属性。
1.(true)只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。
2.(false)而可读写的缓存会(通过序列化)返回缓存对象的拷贝。速度上会慢一些,但是更安全,因此默认值是 false。
也就是说在readOnly在true的时候,mybatis对于处于不同线程的使用者返回的对象将是同一个。换句话说,如果A修改了这个对象,B再去查询,B将获得这个修改过的对象,这就引起了冲突。但是如果保证仅仅是只读的话,性能提升是很高的。
相对的,readOnly是false的时候,mybatis每次都会返回一个找到对象的复制,这虽然引起了速度上的损耗,但是提升了可靠性。