【精选必看】MyBatis映射文件及动态SQL,一级,二级缓存介绍
文章目录
MyBatis映射文件
< r e s u l t M a p > <resultMap> <resultMap>
MyBatis映射文件中除了<insert>
、<delete>
、<update>
、<select>
外,还有一些标签可以使用:
resultMap
标签的作用的自定义映射关系。
MyBatis可以将数据库结果集封装到对象中,是因为结果集的列名和对象属性名相同:
当POJO属性名和数据库列名不一致时,MyBatis无法自动完成映射关系。
此时有两种解决方案:
-
Sql语句的查询字段起与POJO属性相同的别名。
<select id="findAll" resultType="com.mybatis.pojo.Teacher"> select tid as id,tname as teacherName from teacher; </select>
-
自定义映射关系
- 在映射文件中,使用
<resultMap>
自定义映射关系:
<!-- id:自定义映射名 type:自定义映射的对象类型 --> <resultMap id="teacherMapper" type="com.mybatis.pojo.Teacher"> <!-- id定义主键列 property:POJO属性名 column:数据库列名 --> <id property="id" column="tid"></id> <!-- result定义普通列 property:POJO属性名 column:数据库列名 --> <result property="teacherName" column="tname"></result> </resultMap>
- 在
<select>
标签中,使用resultMap
属性代替resultType
属性,使用自定义映射关系。
<select id="findAll" resultMap="teacherMapper"> select * from teacher </select>
- 在映射文件中,使用
< sql>&< include>
<sql>
用来定义可重用的Sql片段,通过<include>
引入该片段。如:Sql语句的查询字段起与POJO属性相同的别名,该Sql片段就可以重用。
<sql id="selectAllField">
select tid as id,tname as teacherName
</sql>
<select id="findAll" resultType="com.mybatis.pojo.Teacher">
<include refid="selectAllField"></include>
from teacher;
</select>
<select id="findById" resultType="com.mybatis.pojo.Teacher">
<include refid="selectAllField"></include>
from teacher where tid = #{id}
</select>
特殊字符处理
在Mybatis映射文件中尽量不要使用一些特殊字符,如:<
,>
等。
我们可以使用符号的实体来表示:(带后面的分号)
符号 | 实体 |
---|---|
< | & lt; |
> | & gt; |
& | & amp; |
‘ | & apos; |
“ | & quot; |
如:
<select id="findById2" resultType="com.mybatis.pojo.Teacher">
<include refid="selectAllField"></include>
from teacher where tid > #{id}
</select>
动态SQL
一个查询的方法的Sql语句不一定是固定的。比如电商网站的查询商品,用户使用不同条件查询,Sql语句就会添加不同的查询条件。此时就需要在方法中使用动态Sql语句。
< i f > < if> <if>
<if>
标签内的Sql片段在满足条件后才会添加,用法为:<if test="条件">
。例如:根据不同条件查询用户:
-
持久层接口添加方法
// 用户通用查询 List<User> findByCondition(User user);
-
映射文件添加标签
<select id="findByCondition" parameterType="com.mybatis.pojo.User" resultType="com.mybatis.pojo.User"> select * from user where 1 = 1 <if test="username != null and username.length() != 0"> and username like #{username} </if> <if test="sex != null and sex.length() != 0"> and sex = #{sex} </if> <if test="address != null and address.length() != 0"> and address = #{address} </if> </select>
-
编写测试方法
@Test public void testFindByCondition(){ User user = new User(); List<User> users1 = userMapper2.findByCondition(user); //users1.forEach(System.out::println); user.setUsername("%张三%"); List<User> users2 = userMapper2.findByCondition(user); users2.forEach(System.out::println); user.setAddress("北京"); List<User> users3 = userMapper2.findByCondition(user); users3.forEach(System.out::println); }
if中的条件不能使用&&/||,而应该使用and/or
if中的条件可以直接通过属性名获取参数POJO的属性值,并且该值可以调用方法。
where后为什么要加1=1?
任意条件都可能拼接到Sql中。如果有多个条件,从第二个条件开始前都需要加And关键字。加上1=1这个永久成立的条件,就不需要考虑后面的条件哪个是第一个条件,后面的条件前都加And关键字即可。
< w h e r e > <where> <where>
<where>
可以代替sql中的where 1=1 和第一个and,更符合程序员的开发习惯,使用<where>
后的映射文件如下:
<select id="findByCondition" resultType="com.mybatis.user.User" parameterType="com.mybatis.user.User">
select * from user
<where>
<if test="username != null and username.length() != 0">
username like #{username}
</if>
<if test="sex != null and sex.length() != 0">
and sex = #{sex}
</if>
</where>
</select>
< s e t > <set> <set>
<set>
标签用在update语句中。借助<if>
,可以只对有具体值的字段进行更新。<set>
会自动添加set关键字,并去掉最后一个if语句中多余的逗号。
<update id="update" parameterType="com.mybatis.user.User">
update user
<set>
<if test="username != null and username.length() > 0">
username = #{username},
</if>
<if test="sex != null and sex.length() > 0">
sex = #{sex},
</if>
</set>
<where>
id = #{id}
</where>
</update>
< c h o o s e > < choose> <choose>、 < w h e n > < when> <when>、 < o t h e r w i s e > < otherwise> <otherwise>
这些标签表示多条件分支,类似JAVA中的switch...case
。<choose>
类似switch
,<when>
类似case
,<otherwise>
类似default
,用法如下:
<select id="findByCondition" resultType="com.mybatis.user.User" parameterType="com.mybatis.user.User">
select * from user
<where>
<choose>
<when test="username.length() < 5">
username like #{username}
</when>
<when test="username.length() < 10">
username = #{username}
</when>
<otherwise>
id = 1
</otherwise>
</choose>
</where>
</select>
这段代码的含义为:用户名<5时使用模糊查询,用户名>=5并且<10时使用精确查询,否则查询id为1的用户
< f o r e a c h > <foreach> <foreach>
<foreach>
类似JAVA中的for循环,可以遍历集合或数组。<foreach>
有如下属性:
- collection:遍历的对象类型
- open:开始的sql语句
- close:结束的sql语句
- separator:遍历每项间的分隔符
- item:表示本次遍历获取的元素,遍历List、Set、数组时表示每项元素,遍历map时表示键值对的值。
- index:遍历List、数组时表示遍历的索引,遍历map时表示键值对的键。
遍历数组
我们使用<foreach>
遍历数组进行批量删除。
-
持久层接口添加方法
void deleteBatch(int[] ids);
-
映射文件添加标签
<delete id="deleteBatch" parameterType="int"> delete from user <where> <foreach open="id in(" close=")" separator="," collection="array" item="id" > #{id} </foreach> </where> </delete>
-
编写测试方法
@Test public void testDeleteBatch(){ int[] ids = {9,11}; userMapper.deleteBatch(ids); session.commit(); }
遍历Collection
<foreach>
遍历List和Set的方法是一样的,我们使用<foreach>
遍历List进行批量添加。
-
持久层接口添加方法
void insertBatch(List<User> users);
-
映射文件添加标签
<insert id="insertBatch" parameterType="com.mybatis.user.User"> insert into user values <foreach collection="list" item="user" separator=","> (null ,#{user.username},#{user.sex},#{user.address}) </foreach> </insert>
-
编写测试方法
@Test public void testInsertBatch(){ User user1 = new User("程序员1", "男", "北京"); User user2 = new User("程序员2", "女", "上海"); List<User> users = new ArrayList(); users.add(user1); users.add(user2); userMapper2.insertBatch(users); session.commit(); }
遍历Map
我们使用<foreach>
遍历Map进行多条件查询。
-
持久层接口添加方法
/** * 多条件查询 * @param map 查询的条件键值对 键:属性名 值:属性值 * @return */ List<User> findUser(@Param("queryMap") Map<String,Object> map);
-
映射文件添加标签
<select id="findUser" parameterType="map" resultType="com.mybatis.pojo.User"> select * from user <where> <foreach collection="queryMap" separator="and" index="key" item="value"> ${key} = #{value} </foreach> </where> </select>
-
编写测试方法
@Test public void testFindUser(){ Map<String,Object> queryMap = new HashMap(); queryMap.put("sex","男"); queryMap.put("address","北京"); List<User> users = userMapper2.findUser(queryMap); users.forEach(System.out::println); }
MyBatis缓存
缓存介绍
缓存是内存当中一块存储数据的区域,目的是提高查询效率。MyBatis会将查询结果存储在缓存当中,当下次执行相同的SQL时不访问数据库,而是直接从缓存中获取结果,从而减少服务器的压力。
-
什么是缓存?
存在于内存中的一块数据。
-
缓存有什么作用?
减少程序和数据库的交互,提高查询效率,降低服务器和数据库的压力。
-
什么样的数据使用缓存?
经常查询但不常改变的,改变后对结果影响不大的数据。
-
MyBatis缓存分为哪几类?
一级缓存和二级缓存
-
如何判断两次Sql是相同的?
- 查询的Sql语句相同
- 传递的参数值相同
- 对结果集的要求相同
- 预编译的模板Id相同
MyBatis一级缓存
- MyBatis一级缓存也叫本地缓存。SqlSession对象中包含一个Executor对象,Executor对象中包含一个PerpetualCache对象,在该对象存放一级缓存数据。
- 由于一级缓存是在SqlSession对象中,所以只有使用同一个SqlSession对象操作数据库时才能共享一级缓存。
- MyBatis的一级缓存是默认开启的,不需要任何的配置。
测试一级缓存
@Test
public void testCache1() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
SqlSession session = factory.openSession();
// 使用同一个SqlSession查询
UserMapper mapper1 = session.getMapper(UserMapper.class);
UserMapper mapper2 = session.getMapper(UserMapper.class);
User user1 = mapper1.findById(1);
System.out.println(user1.hashCode());
System.out.println("-------------------------------------------");
User user2 = mapper2.findById(1);
System.out.println(user2.hashCode());
}
@Test
public void testCache2() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
SqlSession session1 = factory.openSession();
SqlSession session2 = factory.openSession();
// 使用不同的SqlSession查询
UserMapper mapper1 = session1.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user1 = mapper1.findById(1);
System.out.println(user1.hashCode());
System.out.println("-------------------------------------------");
User user2 = mapper2.findById(1);
System.out.println(user2.hashCode());
}
MyBatis清空一级缓存
进行以下操作可以清空MyBatis一级缓存:
SqlSession
调用close()
:操作后SqlSession对象不可用,该对象的缓存数据也不可用。SqlSession
调用clearCache()
/commit()
:操作会清空一级缓存数据。SqlSession
调用增删改方法:操作会清空一级缓存数据,因为增删改后数据库发生改变,缓存数据将不准确。
@Test
public void testCache3() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
SqlSession session = factory.openSession();
UserMapper mapper1 = session.getMapper(UserMapper.class);
UserMapper mapper2 = session.getMapper(UserMapper.class);
User user1 = mapper1.findById(1);
System.out.println(user1.hashCode());
// session.close();
// session.clearCache();
// session.commit();
mapper1.delete(2);
System.out.println("-------------------------------------------");
User user2 = mapper2.findById(1);
System.out.println(user2.hashCode());
}
MyBatis二级缓存
-
MyBatis二级缓存也叫全局缓存。数据存放在SqlSessionFactory中,只要是同一个工厂对象创建的SqlSession,在进行查询时都能共享数据。一般在项目中只有一个SqlSessionFactory对象,所以二级缓存的数据是全项目共享的。
-
MyBatis一级缓存存放的是对象,二级缓存存放的是对象的数据。所以要求二级缓存存放的POJO必须是可序列化的,也就是要实现Serializable接口。
-
MyBatis二级缓存默认不开启,手动开启后数据先存放在一级缓存中,只有一级缓存数据清空后,数据才会存到二级缓存中。
SqlSession
调用clearCache()
无法将数据存到二级缓存中。
开启二级缓存
-
POJO类实现Serializable接口。
public class User implements Serializable { private int id; private String username; private String sex; private String address; }
-
在MyBatis配置文件添加如下设置:
<settings> <setting name="cacheEnabled" value="true"/> </settings>
由于cacheEnabled默认值是true,所以该设置可以省略。
-
在映射文件添加
<cache />
标签,该映射文件下的所有方法都支持二级缓存。如果查询到的集合中对象过多,二级缓存只能缓存1024个对象引用。可以通过
<cache />
标签的size属性修改该数量。<cache size="2048"/>
-
测试二级缓存
@Test public void testCache4() throws IOException { InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); SqlSession session1 = factory.openSession(); SqlSession session2 = factory.openSession(); UserMapper mapper1 = session1.getMapper(UserMapper.class); UserMapper mapper2 = session2.getMapper(UserMapper.class); User user1 = mapper1.findById(1); System.out.println(user1); System.out.println(user1.hashCode()); // 让一级缓存失效 session1.commit(); System.out.println("-------------------------------------------"); User user2 = mapper2.findById(1); System.out.println(user2); System.out.println(user2.hashCode()); }