写在前面:自己整理的一些Mybatis的知识点,个别内容参考了一些我认为大神的理解.
1.#{}和${}的区别
#{} 完全相当于? 代表占位符
#{} 底层走的是预编译对象,SQL只编译一次
#{} 能够防止SQL注入
#{} 当传入的参数为一个简单类型时,#{}随便写
#{} 当传入的参数是javaBean时,#{}里面写实体的属性名称
${} 代表字符串拼接
${} SQL每次需要重新编译
${} 不能够防止SQL注入
${} 当传入的参数为一个简单类型时,${}只能写value
${ } 当传入的参数是javaBean时,${}里面写实体的属性名称
2.当实体类的属性名和表中的字段名不一样,怎么办
1) 通过在查询的 sql 语句中定义字段名的别名,让字段名的别名和实体
类的属性名一致
2) 通过<resultMap>来映射字段名和实体类属性名的一一对应的关系
3.模糊查询like语句怎么写
1)在 Java 代码中添加 sql 通配符
/** * 模糊查询 方式一 */ @Test public void testFindByNameLike(){ UserDao userDao = sqlSession.getMapper(UserDao.class); List<User> users = userDao.findByNameLike("%幂幂%"); for (User user : users) { System.out.println(user); } }
<!--模糊查询 方式一--> <select id="findByNameLike" resultType="com.jia.domain.User"> SELECT * from USER where NAME LIKE #{name} </select>
2)在 sql 语句中拼接通配符,会引起 sql 注入
<!--模糊查询 方式二oracel不能用 ""只用于别名--> <select id="findByNameLike1" resultType="com.jia.domain.User"> SELECT * from USER where NAME LIKE "%"#{name}"%" </select> <!--模糊查询 方式三--> <select id="findByNameLike2" resultType="com.jia.domain.User"> SELECT * from USER where NAME LIKE concat("%",#{name},"%") </select> <!--模糊查询 方式四--> <select id="findByNameLike3" resultType="com.jia.domain.User"> SELECT * from USER where NAME LIKE "%${value}%" </select>
4.通常一个xml映射文件,都会写一个Dao接口与之对应,这个Dao接口的工作原理是什么
Dao接口里的方法参数不同时,方法能重载吗
Dao 接口,就是人们常说的 Mapper 接口,接口的全限名,就是映射文件中的 namespace 的值,接口的方法名,就是映射文件中 MappedStatement 的 id值,接口方法内的参数,就是传递给 sql 的参数。 Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个 MappedStatement,
举例: com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到 namespace 为 com.mybatis3.mappers.StudentDao 下面 id = findStudentById 的 MappedStatement。在 Mybatis 中,每一个<select>、 <insert>、 <update>、 <delete>标签,都会被解析为一个 MappedStatement 对象。
Dao 接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。Dao 接口的工作原理是 JDK 动态代理, Mybatis 运行时会使用 JDK 动态代理为Dao 接口生成代理 proxy 对象,代理对象 proxy 会拦截接口方法,转而执行 MappedStatement 所代表的 sql,然后将 sql 执行结果返回。
5.MyBatis是如何进行分页的?分页插件的原理是什么
Mybatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行
的内存分页,而非物理分页,可以在 sql 内直接书写带有物理分页的参数来完
成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义插件,在插
件的拦截方法内拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加
对应的物理分页语句和物理分页参数。
6.MyBatis是如何将sql执行结果封装为目标对象返回的?都有哪些映射形式?
1)使用 sql 列的别名功能,将列别名书写为对象属性名
2)可以使用resultMap进行自定义结果映射,逐一定义列名和对象属性名之间的映射
关系 .
7.如何执行批量插入?
1)传统拼接
2)使用ExecutorType.BATCH创建SqlSession,推荐使用.
8.如何获取自动生成的(主)键值
第一种:
<!-- 使用有限定只支持主键自动生成的数据库,比如mysql sqlserver db2 告诉数据库要使用数据库生成的主键 useGeneratedKeys 将查回来的主键封装进传入参数的哪个属性 keyProperty --> <!--<insert id="save" useGeneratedKeys="true" keyProperty="uid" parameterType="com.jia.domain.User"> insert into user(name,password,email,birthday) values(#{name},#{password},#{email},#{birthday}) </insert>-->
第二种:
<!--通用方法 使用范围广,基本可以适用于所有数据库 order 表示在执行sql语句之前还是之后执行本语句 resultType 表示把语句的返回值转换成(resultType类型) keyProperty 然后封装到传入参数的哪个属性 --> <insert id="save" parameterType="com.jia.domain.User"> <selectKey order="AFTER" resultType="java.lang.Integer" keyProperty="uid"> select last_insert_id() </selectKey> insert into user(name,password,email,birthday) values(#{name},#{password},#{email},#{birthday}) </insert>
9.在mapper中如何传递多个参数
dao接口:
//多个条件查询 方式一,不推荐 List<User> findByNameAndEmail(String name, String email); //多个条件查询 方式二,不推荐 List<User> findByNameAndEmail1(String name, String email); //多个条件查询 方式三推荐 List<User> findByNameAndEmail2(@Param("name") String name, @Param("email") String email); //多个条件查询 方式四推荐 使用参数很多的时候 List<User> findByNameAndEmail3(User user);
mapper:
<!--多个条件查询 方式一--> <select id="findByNameAndEmail" resultType="com.jia.domain.User"> SELECT * from USER where NAME = #{arg0} and email = #{arg1}; </select> <!--多个条件查询 方式二--> <select id="findByNameAndEmail1" resultType="com.jia.domain.User"> SELECT * from USER where NAME = #{param1} and email = #{param2}; </select> <!--多个条件查询 方式三 推荐--> <select id="findByNameAndEmail2" resultType="com.jia.domain.User"> SELECT * from USER where NAME = #{name} and email = #{email}; </select> <!--多个条件查询 方式四 推荐 参数很多的时候--> <select id="findByNameAndEmail3" parameterType="com.jia.domain.User" resultType="com.jia.domain.User"> SELECT * from USER where NAME = #{name} and email = #{email}; </select>
10.MyBatis动态sql是做什么的?都有哪些动态sql?动态sql的执行原理
Mybatis 动态 sql 可以让我们在 Xml 映射文件内,以标签的形式编写动态 sql,
完成逻辑判断和动态拼接 sql 的功能。
Mybatis 提供了 9 种动态 sql 标签: trim|where|set|foreach|if|choo
se|when|otherwise|bind。
if用于单分支条件判断
场景:
根据user查询 name email
有name 品上name
有email 品上email
都有 都拼上
都没有 查询所有
Choose 用于多分支条件判断
场景:
根据user查询 name email
有name 就只按照name查
如果没有name 有email 就只按照email 查
都有 就只按照name查
都没有 一个没有
<!--修改--> <!-- set 语句中必须至少有一个返回true 在代码块前面加上一个set关键字 去掉最后一个, --> <!-- <update id="update" parameterType="com.jia.domain.User"> update user <set> <if test="name != null and name != ''"> name = #{name}, </if> <if test="password != null and password != ''"> password= #{password}, </if> <if test="email != null and email != ''"> email = #{email}, </if> <if test="birthday != null and birthday != ''"> birthday= #{birthday}, </if> </set> where uid = #{uid} </update>--> <update id="update" parameterType="com.jia.domain.User"> update user <!-- trim prefix 代表在代码块前面添加点什么,prefixOverrides代表在代码块前面删除点什么 suffix 代表在代码块后面添加点什么,suffixOverrides代表在代码块后面删除点什么 --> <trim prefix="set" suffixOverrides="," > <if test="name != null and name != ''"> name = #{name}, </if> <if test="password != null and password != ''"> password= #{password}, </if> <if test="email != null and email != ''"> email = #{email}, </if> <if test="birthday != null and birthday != ''"> birthday= #{birthday}, </if> </trim> where uid = #{uid} </update> <!--parameterType="" 可以省略,底层可以通过反射技术拿到--> <!-- 如果代码块中的语句全部是false,整个where标签不生效 如果代码块中的语句一旦有一个返回true 在代码块的前面添加一个where关键字 如果代码块是以and|or开头,就去掉第一个and|or --> <!-- <select id="findByUser1" resultType="com.jia.domain.User"> select * from user <where> <if test="name != null and name != ''"> and name = #{name} </if> <if test="email != null and email != ''"> and email = #{email} </if> </where> </select>--> <select id="findByUser1" resultType="com.jia.domain.User"> select * from user <!-- trim prefix 代表在代码块前面添加点什么,prefixOverrides代表在代码块前面删除点什么 suffix 代表在代码块后面添加点什么,suffixOverrides代表在代码块后面删除点什么 --> <trim prefix="where" prefixOverrides="and|or"> <if test="name != null and name != ''"> and name = #{name} </if> <if test="email != null and email != ''"> and email = #{email} </if> </trim> </select> <select id="findByUser2" resultType="com.jia.domain.User"> select * from user <where> <choose> <when test="name != null and name != ''"> and name = #{name} </when> <when test="email != null and email != ''"> and email = #{email} </when> <otherwise> 1=2 </otherwise> </choose> </where> </select> <select id="findByUids1" resultType="com.jia.domain.User"> select * from user where uid in <!-- collection 代表数据源,传入的参数 1 2 3 item 临时变量,用来存储每次遍历的元素 separator 每次遍历中间的间隔符 open 开头 close 结尾 index="" 遍历到第几次,从0开始 --> <foreach collection="collection" item="it" separator="," open="(" close=")" > #{it} </foreach> </select> <select id="findByUids2" resultType="com.jia.domain.User"> select * from user where uid in <foreach collection="array" item="it" separator="," open="(" close=")" > #{it} </foreach> </select> <select id="findByUids3" resultType="com.jia.domain.User"> select * from user where uid in <foreach collection="uids" item="it" separator="," open="(" close=")" > #{it} </foreach> </select> <!--Foreach标签中Collection属性的取值: 如果传入的参数是List(Set) ------ collcetion 如果传入的参数是数组 -----------------array 如果传入的参数是pojo------------------pojo中属性-->
执行原理为:
使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的
值动态拼接 sql,以此来完成动态 sql 的功能
11.MyBatis的xml映射文件中,不同的xml映射文件,id是否可以重复
不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有
配置 namespace,那么 id 不能重复;毕竟 namespace 不是必须的,只是最
佳实践而已。
原因就是 namespace+id 是作为 Map<String, MappedStatement>的 key
使用的,如果没有 namespace,就剩下 id,那么, id 重复会导致数据互相覆
盖。有了 namespace,自然 id 就可以重复, namespace 不同, namespace
+id 自然也就不同。
12.一对一,一对多的关联查询
<resultMap id="accountMap" type="com.jia.domain.Account"> <id column="aid" property="aid"></id> <result column="accountName" property="accountName"></result> <result column="balance" property="balance"></result> <!--association 重新封装一个新的对象 封装对象的类型由javaType指定 然后将新封装的对象付给当前对象(type="com.jia.domain.Account")的哪个(property="")属性 --> <association property="user" javaType="com.jia.domain.User"> <result column="uid" property="uid"></result> <result column="name" property="name"></result> <result column="password" property="password"></result> <result column="email" property="email"></result> <result column="birthday" property="birthday"></result> </association> </resultMap> <resultMap id="userMap" type="com.jia.domain.User" extends="baseMap"> <!-- 1、创建一个一个的新对象(com.jia.domain.Account) 2、将这些对象放进一个集合中,、 3、把集合赋值给当前对象(type="com.jia.domain.User")的property指定的属性(property="accounts") ofType 代表实体中的集合中每一个pojo的类型 javaType 代表实体中的每一个pojo的类型 --> <collection property="accounts" ofType="com.jia.domain.Account"> <id column="aid" property="aid"></id> <result column="accountName" property="accountName"></result> <result column="balance" property="balance"></result> </collection> </resultMap> <select id="findAllWithAccounts" resultMap="userMap"> SELECT * FROM USER u LEFT JOIN account a ON a.`uid` = u.`uid` </select>
总结:一对一用association javaType 一对多用的是collection ofType
13.ResultType和ResultMap的区别
当数据库返回的结果集中的字段和实体类中的属性一一对应时,可以使用resultType进行结果映射
当数据库返回的结果集中的字段和实体类中的属性存在不对应的情况时,可以使用resultMap进行自定义结果映射
14.SqlSessionFactory和SqlSession的生命周期的比较
SqlSessionFactory
创建和销毁非常耗费时间,所以一个项目中保持一个(单例)
生命周期:项目启动出生,项目结束才死亡
SqlSession
生命周期:用的时候创建,用完销毁 线程安全
15.关于映射文件中的SQL写法
参数
简单
1个 随便写
多个 注解
pojo 属性名
返回值
Pojo pojo的类型
集合 pojo的类型
16.collection传入参数不同的时候,取值有什么区别
<!--Foreach标签中Collection属性的取值:
如果传入的参数是List(Set) ------ collcetion
如果传入的参数是数组 -----------------array
如果传入的参数是pojo------------------pojo中属性-->
17.嵌套查询(重点)
嵌套查询指的是,不在使用联合查询的方式查询数据,而采用多次单表查询操作
<select id="findByUid" resultType="com.jia.domain.Account"> SELECT * FROM account WHERE uid = #{uid} </select>
<resultMap id="accountMap" type="com.jia.domain.Account"> <id column="aid" property="aid"></id> <result column="accountName" property="accountName"></result> <result column="balance" property="balance"></result> <!--association 重新封装一个新的对象 封装对象的类型由javaType指定 然后将新封装的对象付给当前对象(type="com.jia.domain.Account")的哪个(property="")属性 select 代表调用另外一个查询方法进行数据查询 查询的时候,会顺便将column对应的数据作为参数传递给方法 查询结果回来以后,会将结果设置到当前对象(Account)的property指定的属性中 fetchType 指定加载策略 lazy懒加载 earge 立即加载 --> <association property="user" javaType="com.jia.domain.User" select="com.jia.dao.UserDao.findByUid" column="uid" fetchType="lazy" > </association> </resultMap> <select id="findAllWithUser" resultMap="accountMap"> SELECT * FROM account; </select>
18.加载策略
加载策略指的是,当多个模型存在关联关系时,当查询一个模型的时候是否要将其关联的模型立即查询。这种决策成为加载策略。
查一个模型的时候,立即查出其相关联模型的数据,这种加载策略成为立即加载。
查一个模型的时候,不用立即查出其相关联模型的数据,等到真正需要使用的时候再进行查询,这种加载策略成为延迟加载(懒加载)。
Mybatis默认的加载策略是立即加载。
<resultMap id="accountMap" type="com.jia.domain.Account"> <id column="aid" property="aid"></id> <result column="accountName" property="accountName"></result> <result column="balance" property="balance"></result> <!--association 重新封装一个新的对象 封装对象的类型由javaType指定 然后将新封装的对象付给当前对象(type="com.jia.domain.Account")的哪个(property="")属性 select 代表调用另外一个查询方法进行数据查询 查询的时候,会顺便将column对应的数据作为参数传递给方法 查询结果回来以后,会将结果设置到当前对象(Account)的property指定的属性中 fetchType 指定加载策略 lazy懒加载 earge 立即加载 --> <association property="user" javaType="com.jia.domain.User" select="com.jia.dao.UserDao.findByUid" column="uid" fetchType="lazy" > </association>
19.缓存机制
缓存的作用是提高查询的效率。
优秀的持久层框架都有缓存机制。
MYbatis支持的缓存有两级
一级缓存是sqlSession级别的,它是默认开启,而且不能关闭。
二级缓存是映射器(Mapper)级别的,他是默认开启的,但是可以关闭。二级缓存的使用要求实体类必须实现序列化接口
<!--开启二级缓存 true(开启,默认) false(关闭) flushCache=true 代表每次强制刷新缓存,也就是干掉缓存中的数据,去数据库查 --> <select id="findByUid" parameterType="int" resultType="user" useCache="true" flushCache="true"> SELECT * FROM USER WHERE uid = #{uid} </select>
20.手写MyBatis入门案例
目标:保存一个user到数据库中去
创建工程-->引入坐标-->准备数据库表-->准备实体类-->配置Mapper映射文件(sql)-->准备主配置文件(数据库信息)-->添加日志配置文件(log4j)
测试代码
public class MyBtaisTest { // 保存一个User对象到数据库中去 @Test public void testSave() throws IOException { User user = new User(); user.setName("大幂幂"); user.setPassword("adminadmin"); user.setEmail("damimi@java.cn"); user.setBirthday(new Date()); //Mybatis使用步骤 //1、读取配置文件,读成流 InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); //2、创建一个SqlSeesionFactory SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3、获取SqlSession SqlSession sqlSession = sessionFactory.openSession(); //4、调用SqlSessionAPI进行保存 sqlSession.insert("userMapper.save",user); //5、提交事务(MyBatis默认不自动提交) sqlSession.commit(); //6、关闭资源 sqlSession.close(); } }