一文让你了解mybatis常见用法及面试题

编程入门 行业动态 更新时间:2024-10-28 04:30:22

Mybatis

一.代码结构
1.实体代码

public class User {
 
  private int id;  //id
  private String name;  //姓名
  private String pwd;  //密码
 
  //构造,有参,无参
  //set/get
  //toString()
 
}

2.Dao接口UserDao

import com.wei.pojo.User;

import java.util.List;

/**
 * @author vv
 * @create 2022-04-05 16:33
 */
public interface UserDao {
    List<User> selectUser();
}

3.接口实现类,编写UserMapper.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis//DTD Mapper 3.0//EN"
        "http://mybatis/dtd/mybatis-3-mapper.dtd">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.wei.dao.UserDao">
    <select id="selectUser" resultType="com.wei.pojo.User">
        select * from mybatis.user
    </select>
</mapper>

4.mybatis-config.xml
配置解析


属性(properties)
db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=****

设置

  <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <setting name="mapUnderscoreToCamelCase" value="true"/> //下划线驼峰自动转换
    </settings>

类型别名(typeAliases)优化

 <!--给实体类起别名-->
    <typeAliases>
        //方法一:当实体类型少的时候用这个
        <typeAlias type="com.wei.pojo.User" alias="User"/>
        //方法二:当实体类型多的时候用这个
        //在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名,若有注解,则别名为其注解值。
        <typeAliases>
          	<package name="com.wei.pojo"/>
        </typeAliases>
    </typeAliases>

环境配置 environments元素
子元素节点:transactionManager - [ 事务管理器 ](有两种):JDBC (默认)| MANAGED
子元素节点:数据源(dataSource)(有三种):UNPOOLED|POOLED(默认)|JNDI
unpooled: 这个数据源的实现只是每次被请求时打开和关闭连接。
pooled: 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来 , 这是一种使得并发 Web 应用快速响应请求的流行处理方式。
jndi:这个数据源的实现是为了能在如 Spring 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis//DTD Config 3.0//EN"
        "http://mybatis/dtd/mybatis-3-config.dtd">
<configuration>
    <!--引入外部配置文件-->
    <properties resource="db.properties"/>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册!-->
    <mappers>
    	//方法一:
        <mapper resource="com/wei/dao/UserMapper.xml"/>
        //方法二:需要配置文件名称和接口名称一致,并且位于同一目录下即UserMapper和UserMapper.xml的名字要一致
        <mapper class="com.wei.dao.UserMapper"/>
        //方法三:
        <package name="com.wei.dao.UserMapper"/>
    </mappers>
</configuration>

5.MybatidUtils

public class MybatisUtils {
    //提升作用域
    private static SqlSessionFactory sqlSessionFactory;
    static{
        String resource = "mybatis-config.xml";
        try {
            //获取sqlSessionFactory对象
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
        //写成下面这样就是事务自动提交,增删改就不需要在提交事务了
         return sqlSessionFactory.openSession(true);
    }
}

6.测试

public class UserDaoTest {
    @Test
    public void test1(){
        //第一步:获取SqlSession对象
        SqlSession sqlSession=MybatisUtils.getSqlSession();
        //第二步:执行SQL
        UserDao userDao=sqlSession.getMapper(UserDao.class);
        List<User> users = userDao.selectUser();
        for(User user:users){
            System.out.println(user);
        }
        //关闭SqlSession
        sqlSession.close();

    }
}

以后在这个框架基础上每次只需要改UserDao、UserMapper、UserDaoTest
增删改查操作(增删改操作要提交事务)
UserDao

//(查)根据id查询全部用户
User getUserById(int id);
//insert一个用户
int addUser(User user);
//修改用户
int updateUser(User user);
//删除用户
int deleteUser(int id);

UserMapper.xml

//查
<select id="getUserById" parameterType="int" resultType="com.wei.pojo.User">
        select * from mybatis.user where id=#{id}
</select>
<insert id="addUser" parameterType="com.wei.pojo.User" >
     insert into mybatis.user(id,name,pwd) values(#{id},#{name},#{pwd});
</insert>
<update id="updateUser" parameterType="com.wei.pojo.User">
     update mybatis.user
     set name = #{name},pwd=#{pwd}
     where id=#{id};
</update>
<delete id="deleteUser" parameterType="int">
      delete from mybatis.user where id=#{id}
</delete>

UserDaoTest

//查
@Test
    public void getUserById(){
        //第一步:获取SqlSession对象
        SqlSession sqlSession=MybatisUtils.getSqlSession();
        //第二步:执行SQL
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);
        User users = mapper.getUserById(1);
        System.out.println(users);
        //关闭SqlSession
        sqlSession.close();
    }
//增删改需要提交事务
    @Test
    public void addUser(){
        //第一步:获取SqlSession对象
        SqlSession sqlSession=MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int res=mapper.addUser(new User(4,"七七","123333"));
        if(res>0){
            System.out.println("插入成功");
        }
        //提交事务
        sqlSession.commit();
        //关闭SqlSession
        sqlSession.close();
    }
    @Test
    public void updateUser(){
        //第一步:获取SqlSession对象
        SqlSession sqlSession=MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int 威威 = mapper.updateUser(new User(4, "威威", "123"));
        //提交事务
        sqlSession.commit();
        //关闭SqlSession
        sqlSession.close();
    }
    @Test
    public void deleteUser(){
        //第一步:获取SqlSession对象
        SqlSession sqlSession=MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.deleteUser(4);
        //提交事务
        sqlSession.commit();
        //关闭SqlSession
        sqlSession.close();
    }

二.SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession

SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。
三.万能的map
当参数过多时考虑用map实现,参数较少时,直接传递参数即可,通过map可以想写哪个参数就写哪个参数
(1)在接口方法中,参数直接传递Map;

//万能Map,不需要知道数据库中的所有属性,只需要查对应的字段
    int addUser2(Map<String,Object> map);

(2)编写sql语句的时候,需要传递参数类型,参数类型为map

<insert id="addUser2" parameterType="map">
        insert into mybatis.user(id,name,pwd) values (#{userid},#{username},#{password});
 </insert>

(3)在使用方法的时候,Map的 key 为 sql中取的值即可,没有顺序要求!

@Test
    public void addUser2(){
        //第一步:获取SqlSession对象
        SqlSession sqlSession=MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        HashMap<String, Object> map = new HashMap<>();
        map.put("userid",6);
        map.put("username","喵喵");
        map.put("password",1234);
        mapper.addUser2(map);
        //提交事务
        sqlSession.commit();
        //关闭SqlSession
        sqlSession.close();
    }

四.模糊查询
(1)接口

//模糊查询
    List<User> getUserLike(String value);

(2)编写对应的配置文件SQL

<select id="getUserLike" resultType="com.wei.pojo.User" >
        select * from user where name like #{value}
</select>

(3)测试

@Test
    public void getUserLike(){
        //第一步:获取SqlSession对象
        SqlSession sqlSession=MybatisUtils.getSqlSession();
        //第二步:执行SQL
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);
        List<User> userlist = mapper.getUserLike("李%");
        for(User user:userlist){
            System.out.println(user);
        }
        //关闭SqlSession
        sqlSession.close();
    }

第1种:在Java代码中添加sql通配符。

List<User> userList=mapper.getUserLike("%李%");

第2种:在sql语句中拼接通配符,会引起sql注入

select * from user where name like "%"#{value}"%"

五.ResultMap(解决实体类中属性名和数据库中字段名不一致的情况)

<resultMap id="UserMap" type="User">//UserMap是随便取的,要和下面select标签中的resultMap对应,User是要将结果集映射为User
    <!-- column是数据库表的列名 , property是对应实体类的属性名 -->
    <!--一致的就不需要写出来了比如id和name-->
        <result column="pwd" property="password"/>
</resultMap>
<select id="getUserById" resultMap="UserMap">
        select * from mybatis.user where id=#{id}
 </select>

六.分页的实现
Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:
SLF4J、Apache Commons Logging、Log4j 2、Log4j、JDK logging
Log4j
(1)导入log4j的包 在pom.xml中

<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>1.2.17</version>
</dependency>

(2) 配置文件编写,建立log4j.properties

#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/wei.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

(3)setting设置日志实现 在mybatis-config.xml

<settings>
  <setting name="logImpl" value="LOG4J"/>
</settings>

(4)在程序中使用Log4j进行输出!

//注意导包:org.apache.log4j.Logger
static Logger logger = Logger.getLogger(MyTest.class);
@Test
public void selectUser() {
  logger.info("info:进入selectUser方法");
  logger.debug("debug:进入selectUser方法");
  logger.error("error: 进入selectUser方法");
  SqlSession session = MybatisUtils.getSession();
  UserMapper mapper = session.getMapper(UserMapper.class);
  List<User> users = mapper.selectUser();
  for (User user: users){
    System.out.println(user);
 }
  session.close();
}

七.分页的实现
(1) MyBatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页;(2) 可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能,(3) 也可以使用分页插件来完成物理分页。
方法一:limit实现分页
(1)Mapper接口,参数为map

List<User> getUserByLimit(Map<String,Integer>map);

(2)修改Mapper.xml文件

 <select id="getUserByLimit" parameterType="map" resultType="User">
        select * from user limit #{startIndex},#{pageSize}
    </select>

(3) 在测试类中传入参数测试

@Test
    public void getUserByLimit(){
        //第一步:获取SqlSession对象
        SqlSession sqlSession=MybatisUtils.getSqlSession();
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);
        HashMap<String, Integer> map = new HashMap<>();
        map.put("startIndex",0);
        map.put("pageSize",2);
        List<User> userList = mapper.getUserByLimit(map);
        for (User user : userList) {
            System.out.println(user);
        }
        sqlSession.close();
    }

方法二:RowBounds分页(了解即可)
(1)mapper接口 UserMapper.java

//分页2
List<User> getUserByRowBounds();

(2)mapper文件 UserMapper.xml

<select id="getUserByRowBounds" resultMap="UserMap">
        select * from user
</select>

(3) 测试类UserDaoTest

@Test
    public void getUserByRowBounds(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //RowBounds实现
        RowBounds rowBounds = new RowBounds(1, 2);
        //通过java代码层面实现分页
        List<User> userList = sqlSession.selectList("com.wei.dao.UserMapper.getUserByRowBounds",null,rowBounds);
        for (User user : userList) {
            System.out.println(user);
        }
        sqlSession.close();
    }

方法三:PageHelper分页插件
八.注解开发(使用注解就不需要mapper.xml映射文件了)
(1)接口中添加注解 UserMapper

public interface UserMapper {
    @Select("select * from user")
    List<User> getUsers();
    @Insert("insert into user(id,name,pwd) values (#{id},#{name},#{password})")
    int addUser(User user);
    @Update("update user set name=#{name},pwd=#{password} where id=#{id}")
    int updateUser(User user);
    //根据id删除用
    @Delete("delete from user where id = #{uid}")
    int deleteUser(@Param("uid")int id);
}

(2)mybatis-config.xml

<!--绑定接口-->
    <mappers>
        <mapper class="com.wei.dao.UserMapper"/>
    </mappers>

(3)测试 UserMapperTest

@Test
    public  void test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //底层主要应用反射
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.getUsers();
        for (User user : users) {
            System.out.println(user);
        }

        sqlSession.close();
    }
//下面这个增删改没有对事物提交因为工具类中用来true的那个
@Test
    public void addUser2(){
        //第一步:获取SqlSession对象
        SqlSession sqlSession=MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.addUser(new User(7,"小红","123456"));
        //关闭SqlSession
        sqlSession.close();
    }
    @Test
    public void updateUser(){
        //第一步:获取SqlSession对象
        SqlSession sqlSession=MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.updateUser(new User(6,"小强","123456"));
        //关闭SqlSession
        sqlSession.close();
    }
    @Test
    public void testDeleteUser() {
        SqlSession session = MybatisUtils.getSqlSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        mapper.deleteUser(6);

        session.close();
    }

九.Mybatis详细的执行流程

十.@Param的使用
关于@Param(基本数据类型或String类型,需要加上,引用类型不需要加)
@Param注解用于给方法参数起一个名字。以下是总结的使用原则:
在方法只接受一个参数的情况下,可以不使用@Param。
在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
如果参数是 JavaBean , 则不能使用@Param。
不使用@Param注解时,参数只能有一个,并且是Javabean。
十一.多对一的处理(多个学生对一个老师)
获取所有学生及对应老师的信息

@Data
public class Student {
    private int id;
    private String name;
    //学生需要关联一个老师
    private Teacher teacher;
}
public class Teacher {
    private int id;
    private String nmae;
}

方法一:查询嵌套
按照查询进行嵌套处理就像SQL中的子查询
(1)StudentMapper接口

//查询所有学生信息,以及对应的老师的信息
    public List<Student> getStudent();

TeacherMapper接口

//在TeacherMapper中测试是否搭建环境成功
public interface TeacherMapper {
    @Select("select * from teacher where id=#{tid}")
    Teacher getTeacher(@Param("tid")int id);
}
<!--
    需求:获取所有学生及对应老师的信息
    思路:
    1. 获取所有学生的信息
    2. 根据获取的学生信息的老师ID->获取该老师的信息
    . 思考问题,这样学生的结果集中应该包含老师,该如何处理呢,数据库中我们一般
    使用关联查询?
    1. 做一个结果集映射:StudentTeacher
    2. StudentTeacher结果集的类型为 Student
    3. 学生中老师的属性为teacher,对应数据库中为tid。
    多个 [1,...)学生关联一个老师=> 一对一,一对多
    4. 查看官网找到:association – 一个复杂类型的关联;使用它来处理关联查询
-->
    <select id="getStudent" resultMap="StudentTeacher">
        select * from student
    </select>
    <resultMap id="StudentTeacher" type="Student">
    <!--association关联属性 property属性名 javaType属性类型 column在多
的一方的表中的列名-->
        <association property="teacher" column="tid" javaType="Teacher"  select="getTeacher"/>
    </resultMap>
    <!--
这里传递过来的id,只有一个属性的时候,下面可以写任何值
association中column多参数配置:
column="{key=value,key=value}"
其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的
字段名。
-->
    <select id="getTeacher" resultType="Teacher">
        select * from teacher where id = #{id}
    </select>

方法二:按照结果处理
按照结果进行嵌套处理就像SQL中的联表查询

public List<Student> getStudent2();
<!--方式二:按照结果嵌套处理-->
    <select id="getStudent2" resultMap="StudentTeacher2">
        select s.id sid,s.name sname,t.name tname,t.id tid
        from student s,teacher t
        where s.tid=t.id
    </select>
    <resultMap id="StudentTeacher2" type="Student">
        <id property="id" column="sid"/>
        <result property="name" column="sname"/>
        <!--关联对象property 关联对象在Student实体类中的属性-->
        <association property="teacher" javaType="Teacher">
        <result property="name" column="tname"/>
            <result property="id" column="tid"/>
        </association>
    </resultMap>

十二.一对多处理(一个老师对多个学生)
(1)实体类

@Data
public class Student {
    private int id;
    private String name;
    //学生需要关联一个老师
    private int tid;
}
@Data
public class Teacher {
    private int id;
    private String name;
    //一个老师拥有多个学生
    private List<Student> students;
}

方法一:按结果嵌套

<!--
思路:
    1. 从学生表和老师表中查出学生id,学生姓名,老师姓名
    2. 对查询出来的操作做结果集映射
    1. 集合的话,使用collection!
    JavaType和ofType都是用来指定对象类型的
    JavaType是用来指定pojo中属性的类型
    ofType指定的是映射到list集合属性中pojo的类型。
-->
    <select id="getTeacher" resultMap="TeacherStudent">
        select s.id sid,s.name sname,t.name tname,t.id tid
        from student s,teacher t 
        where s.tid=t.id and t.id=#{tid}
    </select>
    <resultMap id="TeacherStudent" type="Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <collection property="students" ofType="Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>

方法二:按查询嵌套

<select id="getTeacher2" resultMap="TeacherStudent">
        select * from teacher where id=#{tid}
    </select>
    <resultMap id="TeacherStudent" type="Teacher">
        <collection property="students"  javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/>
    </resultMap>
    <select id="getStudentByTeacherId" resultType="Student">
        select * from student where tid=#{tid}
    </select>

小结

  1. 关联-association
  2. 集合-collection
  3. 所以association是用于一对一和多对一,而collection是用于一对多的关系
  4. JavaType和ofType都是用来指定对象类型的
    JavaType是用来指定pojo中属性的类型
    ofType指定的是映射到list集合属性中pojo的类型。
    十三.动态SQL
    MyBatis 动态 sql 可以让我们在 xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能。其执行原理为,使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。
    一.if 语句
    需求:根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
    1.编写接口类
//查询博客
    List<Blog> queryBlogIF(Map map);

2.编写SQL语句

<select id="queryBlogIF" parameterType="map" resultType="blog">
        select * from blog
        <where>
            <if test="title!=null">
                and title=#{title}
            </if>
            <if test="author!=null">
                and author=#{author}
            </if>
        </where>
</select>
  1. 测试
@Test
    public void testQueryBlogIf(){
        SqlSession session = MybatisUtils.getSqlSession();
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        HashMap<String, String> map = new HashMap<String, String>();
        //map.put("title","Mybatis如此简单");
        map.put("author","狂神说");
        List<Blog> blogs = mapper.queryBlogIF(map);
        System.out.println(blogs);
        session.close();
    }

二.choose语句
有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose标签可以解决此类问题,类似于 Java 的 switch 语句

<select id="queryBlogChoose" parameterType="map" resultType="blog">
        select * from blog
        <where>
            <choose>
                <when test="title!=null">
                    title=#{title}
                </when>
                <when test="author!=null">
                    and author=#{author}
                </when>
                <otherwise>
                    and views=#{views}
                </otherwise>
            </choose>
        </where>
    </select>

三.Set
set元素会动态前置SET关键字,同时也会删除无关的逗号,因为用了条件语句之后很可能就会在生成SQL语句的后面留下这些逗号(因为用的是“if"元素,若最后一个”if"没有匹配上上而前面的匹配上,SQL语句的最后就会有一个逗号遗留)

<update id="updateBlog" parameterType="map">
        update blog
        <set>
            <if test="title!=null">
                title=#{title},
            </if>
            <if test="author!=null">
                author=#{author}
            </if>
        </set>
        where id=#{id}
    </update>

四.Foreach

   <select id="queryBlogForeach" parameterType="map" resultType="blog">
        select * from blog
        <where>
            <foreach collection="ids" item="id" open="and (" close=")" separator="or">
                id=#{id}
            </foreach>
        </where>
    </select>
@Test
    public void testQueryBlogForeach(){
        SqlSession session = MybatisUtils.getSqlSession();
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        HashMap map = new HashMap();
        List<Integer> ids = new ArrayList<Integer>();
        ids.add(1);
        ids.add(2);
        ids.add(3);
        map.put("ids",ids);
        List<Blog> blogs = mapper.queryBlogForeach(map);
        System.out.println(blogs);
        session.close();
    }

五.SQL片段

<!--提取sql-->
<sql id="if-title-author">
        <if test="title != null">
        title = #{title}
        </if>
        <if test="author != null">
        and author = #{author}
        </if>
    </sql>
<!--引用sql-->
<select id="queryBlogIF" parameterType="map" resultType="blog">
        select * from blog
        <where>
            <include refid="if-title-author">

            </include>
        </where>
    </select>            

十四.Mybatis缓存
一级缓存失效的四种情况
1.sqlSession不同
2.sqlSession相同,查询条件不同
3. sqlSession相同,两次查询之间执行了增删改操作!

  1. sqlSession相同,手动清除一级缓存
 session.clearCache();//手动清除缓存

一级缓存是SqlSession级别的,一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个时间段,一级缓存相当于一个Map
二级缓存
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
工作机制
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,新的会话查询信息,就可以从二级缓存中获取内容;不同的mapper查出的数据会放在自己对应的缓存(map)中;
使用步骤
5. 开启全局缓存 【mybatis-config.xml】

<setting name="cacheEnabled" value="true"/>
  1. 去每个mapper.xml中配置使用二级缓存,这个配置非常简单;【xxxMapper.xml】
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的
512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者
产生冲突。
上面是官方给出的,也可以自定义参数
  1. 代码测试
    所有的实体类先实现序列化接口,否则就会报错 User.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private int id;
    private String name;
    private String pud;


}
 @Test
    public void test3(){
        SqlSession session = MybatisUtils.getSqlSession();
        SqlSession session2 = MybatisUtils.getSqlSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        UserMapper mapper2 = session2.getMapper(UserMapper.class);
        User user = mapper.queryUserById(1);
        System.out.println(user);
        session.close();
        User user2 = mapper2.queryUserById(1);
        System.out.println(user2);
        System.out.println(user==user2);
        session2.close();
    }


常见面试题
1.#{} 和 ${} 的区别是什么?
是 P r o p e r t i e s 文件中的变量占位符,它可以用于标签属性值和 s q l 内部,属于静态文本替换,比如 {}是 Properties 文件中的变量占位符,它可以用于标签属性值和 sql 内部,属于静态文本替换,比如 Properties文件中的变量占位符,它可以用于标签属性值和sql内部,属于静态文本替换,比如{driver}会被静态替换为com.mysql.jdbc. Driver。
#{}是 sql 的参数占位符,MyBatis 会将 sql 中的#{}替换为? 号,在 sql 执行前会使用 PreparedStatement 的参数设置方法,按序给 sql 的? 号占位符设置参数值,比如 ps.setInt(0, parameterValue),#{item.name} 的取值方式为使用反射从参数对象中获取 item 对象的 name 属性值,相当于 param.getItem().getName()
#{}与${}的区别
#{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符? 【推荐使用】
${} 的作用是直接进行字符串替换
2.xml 映射文件中,除了常见的 select、insert、update、delete 标签之外,还有哪些标签?
还有很多其他的标签, 、 、 、 、 ,加上动态 sql 的 9 个标签, trim|where|set|foreach|if|choose|when|otherwise|bind 等,其中 为 sql 片段标签,通过 标签引入 sql 片段, 为不支持自增的主键生成策略标签。
3.Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?
Dao 接口的工作原理是 JDK 动态代理,MyBatis 运行时会使用 JDK 动态代理为 Dao 接口生成代理 proxy 对象,代理对象 proxy 会拦截接口方法,转而执行 MappedStatement 所代表的 sql,然后将 sql 执行结果返回。
答:最佳实践中,通常一个 xml 映射文件,都会写一个 Dao 接口与之对应。Dao 接口就是人们常说的 Mapper 接口,接口的全限名,就是映射文件中的 namespace 的值,接口的方法名,就是映射文件中 MappedStatement 的 id 值,接口方法内的参数,就是传递给 sql 的参数。 Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个 MappedStatement ,举例: com.mybatis3.mappers. StudentDao.findStudentById ,可以唯一找到 namespace 为 com.mybatis3.mappers. StudentDao 下面 id = findStudentById 的 MappedStatement 。在 MyBatis 中,每一个 、 、 、 标签,都会被解析为一个 MappedStatement 对象。
Dao 接口里的方法可以重载,但是 Mybatis 的 xml 里面的 ID 不允许重复。
Mybatis 的 Dao 接口可以有多个重载方法,但是多个接口对应的映射必须只有一个,否则启动会报错。
Dao 接口方法可以重载,但是需要满足以下条件:

仅有一个无参方法和一个有参方法
多个有参方法时,参数数量必须一致。且使用相同的 @Param ,或者使用 param1 这种
4.简述 MyBatis 的插件运行原理,以及如何编写一个插件。
MyBatis 仅可以编写针对 ParameterHandler 、 ResultSetHandler 、 StatementHandler 、 Executor 这 4 种接口的插件,MyBatis 使用 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 invoke() 方法,当然,只会拦截那些你指定需要拦截的方法。

实现 MyBatis 的 Interceptor 接口并复写 intercept() 方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。

5.MyBatis 是如何将 sql 执行结果封装为目标对象并返回的?都有哪些映射形式
第一种是使用 标签,逐一定义列名和对象属性名之间的映射关系。第二种是使用 sql 列的别名功能,将列别名书写为对象属性名
6.MyBatis 是否支持延迟加载?如果支持,它的实现原理是什么?
MyBatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 MyBatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。

它的原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName() ,拦截器 invoke() 方法发现 a.getB() 是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName() 方法的调用。这就是延迟加载的基本原理。

当然了,不光是 MyBatis,几乎所有的包括 Hibernate,支持延迟加载的原理都是一样的。

7.MyBatis 都有哪些 Executor 执行器?它们之间的区别是什么?
Mybatis中使用 BatchExecutor 完成批处理。
MyBatis 有三种基本的 Executor 执行器:

SimpleExecutor: 每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象。
ReuseExecutor: 执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map<String, Statement>内,供下一次使用。简言之,就是重复使用 Statement 对象。 BatchExecutor 执行 update(没有 select,JDBC 批处理不支持 select),将所有 sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行 executeBatch()批处理。与 JDBC 批处理相同。
作用范围:Executor 的这些特点,都严格限制在 SqlSession 生命周期范围内。

MyBatis 中如何指定使用哪一种 Executor 执行器?
在 MyBatis 配置文件中,可以指定默认的 ExecutorType 执行器类型,也可以手动给 DefaultSqlSessionFactory 的创建 SqlSession 的方法传递 ExecutorType 类型参数。
8.MyBatis 是否可以映射 Enum 枚举类?
MyBatis 可以映射枚举类,不单可以映射枚举类,MyBatis 可以映射任何对象到表的一列上。映射方式为自定义一个 TypeHandler ,实现 TypeHandler 的 setParameter() 和 getResult() 接口方法。 TypeHandler 有两个作用:

一是完成从 javaType 至 jdbcType 的转换;
二是完成 jdbcType 至 javaType 的转换,体现为 setParameter() 和 getResult() 两个方法,分别代表设置 sql 问号占位符参数和获取列查询结果。
9.MyBatis 映射文件中,如果 A 标签通过 include 引用了 B 标签的内容,请问,B 标签能否定义在 A 标签的后面,还是说必须定义在 A 标签的前面?
虽然 MyBatis 解析 xml 映射文件是按照顺序解析的,但是,被引用的 B 标签依然可以定义在任何地方,MyBatis 都可以正确识别。

原理是,MyBatis 解析 A 标签,发现 A 标签引用了 B 标签,但是 B 标签尚未解析到,尚不存在,此时,MyBatis 会将 A 标签标记为未解析状态,然后继续解析余下的标签,包含 B 标签,待所有标签解析完毕,MyBatis 会重新解析那些被标记为未解析的标签,此时再解析 A 标签时,B 标签已经存在,A 标签也就可以正常解析完成了

10.简述 MyBatis 的 xml 映射文件和 MyBatis 内部数据结构之间的映射关系?
MyBatis 将所有 xml 配置信息都封装到 All-In-One 重量级对象 Configuration 内部。在 xml 映射文件中, 标签会被解析为 ParameterMap 对象,其每个子元素会被解析为 ParameterMapping 对象。 标签会被解析为 ResultMap 对象,其每个子元素会被解析为 ResultMapping 对象。每一个 、、、 标签均会被解析为 MappedStatement 对象,标签内的 sql 会被解析为 BoundSql 对象。
11.为什么说 MyBatis 是半自动 ORM 映射工具?它与全自动的区别在哪里?
Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而 MyBatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。

更多推荐

一文让你了解mybatis常见用法及面试题

本文发布于:2023-06-14 09:07:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1459736.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:让你   一文   面试题   常见   mybatis

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!