2023最新中级难度MyBatis面试题,包含答案。刷题必备!记录一下。

发布时间 2023-12-12 13:05:04作者: 小满独家

好记性不如烂笔头

内容来自 面试宝典-中级难度MyBatis面试题合集

问: MyBatis与Hibernate有何区别?

好的,我会尽力回答这个问题。

首先,MyBatis和Hibernate都是Java中的持久化框架,用于处理数据库操作。

在理解和使用难度上,Hibernate的难度通常会大于MyBatis。因为Hibernate的功能更加强大和全面,但同时也意味着学习曲线更加陡峭。相反,MyBatis相对来说比较简单,更容易上手。

从业务需求的角度来看,如果一个项目的业务逻辑比较简单,只需要进行简单的增删改查操作,并且不涉及复杂的查询,那么使用Hibernate可能更为方便。这是因为Hibernate可以通过对象关系映射,自动地生成SQL语句,从而简化了开发过程。但是,如果项目的业务逻辑比较复杂,需要执行复杂的SQL查询,或者需要调用Oracle中的存储过程,那么使用MyBatis可能会更加适合。这是因为MyBatis允许用户直接编写SQL语句,并能够更好地控制数据库的操作。

在开发工作量方面,Hibernate和MyBatis都提供了代码生成工具,可以帮助开发者快速生成基础的DAO层方法。然而,对于复杂的查询,MyBatis需要手动编写SQL语句和ResultMap,这可能会增加一些开发的工作量。而Hibernate则提供了良好的映射机制,可以自动地处理SQL的生成和结果映射,使得开发者可以更加专注于业务流程。

在SQL优化方面,Hibernate不需要编写大量的SQL语句,就能够完全映射数据表,而且提供了日志、缓存、级联等特性,还可以使用HQL(Hibernate Query Language)来操作POJO。相比之下,MyBatis的SQL语句需要手动编写,但这也意味着可以根据需求指定查询的字段,并且具有更好的性能和可读性。

总的来说,MyBatis和Hibernate各有优缺点,选择哪个框架取决于具体的应用场景和需求。如果项目的需求比较简单,或者希望节省开发的时间和精力,那么可以选择Hibernate。如果项目的业务逻辑比较复杂,需要更多的灵活性和可控性,那么可以选择MyBatis。

问: 你如何在MyBatis中实现事务管理?

好的,我会尽力回答这个问题。

在MyBatis中实现事务管理,一般有两种方式:

第一种方式是使用JDBC的事务管理机制。这种方式需要在代码中手动创建Connection对象,然后通过Connection对象来完成对事务的提交、回滚等操作。例如:

public void insertStudent() {
    Connection connection = null;
    try {
        connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "root", "password");
        connection.setAutoCommit(false); // 开启手动提交

        // 执行插入操作...
        
        connection.commit(); // 提交事务
    } catch (SQLException e) {
        if (connection != null) {
            try {
                connection.rollback(); // 如果发生异常,则回滚事务
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }
        e.printStackTrace();
    } finally {
        if (connection != null) {
            try {
                connection.close(); // 关闭连接
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

第二种方式是使用MyBatis的内置事务管理机制。这种方式需要在MyBatis的配置文件中配置事务管理器,然后在DAO中使用SqlSession提供的事务管理方法。例如:

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mydatabase"/>
                <property name="username" value="root"/>
                <property name="password" value="password"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 其他配置... -->
</configuration>

public interface UserMapper {
    void updateUser(User user);
}

public class UserService {
    private final SqlSessionFactory sqlSessionFactory;

    public UserService(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    public void updateUserInfo(String userId, String email) {
        SqlSession sqlSession = sqlSessionFactory.openSession();

        try {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            User user = new User(userId, email);
            mapper.updateUser(user);

            sqlSession.commit(); // 提交事务
        } catch (Exception e) {
            sqlSession.rollback(); // 如果发生异常,则回滚事务
            e.printStackTrace();
        } finally {
            sqlSession.close(); // 关闭会话
        }
    }
}

以上就是在MyBatis中实现事务管理的基本方法。需要注意的是,不同的应用场景和需求可能需要选择不同的事务管理方式。在实际的项目中,应该根据实际情况灵活选择和配置事务管理策略。

问: 请解释MyBatis中的Mapper文件是什么,它的作用是什么?

好的,我会尽力回答这个问题。

在MyBatis中,Mapper文件是一个XML格式的文件,它主要用于定义SQL查询语句和其他数据库操作。Mapper文件的作用主要有以下几个方面:

  1. 定义SQL查询语句:在Mapper文件中,可以通过元素来定义一个具体的SQL查询。每个元素都可以包含多个元素定义了一个名为getUserById的查询操作,它接受一个整数类型的参数,并返回一个User类型的对象。

    1. 定义结果映射:在Mapper文件中,可以通过元素来定义结果映射。结果映射主要用于描述查询结果应该如何映射到Java对象上。

    例如:

    <resultMap id="userResultMap" type="com.example.User">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="email" column="email"/>
    </resultMap>
    

    在这个例子中,元素定义了一个名为userResultMap的结果映射,它将查询结果中的id、name和email三个字段映射到User类的对应属性上。

    1. 定义动态SQL:在Mapper文件中,可以通过等元素来编写动态SQL。动态SQL可以在运行时根据条件来决定是否执行某个部分的SQL语句。

    例如:

    <select id="getUsersByStatus" parameterType="string" resultType="com.example.User">
        SELECT * FROM users WHERE status = #{status}
        <if test="sortField != null and sortField != ''">
            ORDER BY ${sortField} ${sortOrder}
        </if>
    </select>
    

    在这个例子中,元素用于检查sortField参数是否为空或空字符串。如果该参数不为空,则在查询结果中添加排序条件。

    1. 引入其他Mapper文件:在Mapper文件中,可以通过元素来引入其他的Mapper文件。这样就可以在一个文件中复用其他文件中的SQL语句和结果映射。

    例如:

    <mapper namespace="com.example.UserProfileMapper">
        <import resource="userMapper.xml"/>
        ...
    </mapper>
    

    在这个例子中,元素引入了userMapper.xml文件中的所有SQL语句和结果映射。

    总之,在MyBatis中,Mapper文件是定义SQL查询和其他数据库操作的重要组件。通过合理地使用Mapper文件,可以使SQL语句和Java代码解耦,从而提高代码的可维护性和可读性。

    问: 在MyBatis中,#{}和${}有什么区别?

    好的,我会尽力回答这个问题。

    在MyBatis中,#{}和${}都是用来传递参数的方式,但它们之间有一些重要的区别。

    1. 功能不同:#{}是预处理占位符,它可以防止SQL注入攻击,因为它会对传入的值进行转义处理。而${}则是字符串替换占位符,它不会对传入的值进行任何处理。

    2. 使用场景不同:在MyBatis中,我们应该尽可能地使用#{}来进行参数传递。只有当需要传递的值是SQL命令或关键字时,才应该使用${}。但是,在使用${}之前,必须确保输入的数据已经进行了充分的安全检查和清理,否则可能会导致SQL注入攻击。

    3. 安全性不同:由于#{}会对传入的值进行转义处理,因此它是安全的。而${}不会对传入的值进行处理,因此可能存在安全隐患。除非我们非常确定传入的值是安全的,否则不应该使用${}。

    例如,假设我们要根据用户输入的ID来查找用户,我们可以这样做:

    SELECT * FROM users WHERE id = #{id}
    

    在这种情况下,无论用户输入什么,都不会影响SQL语句的正确执行。但如果使用${},则可能会导致SQL注入攻击:

    SELECT * FROM users WHERE id = ${id}
    

    假设用户输入了' OR '1'='1,那么生成的SQL语句将会变成:

    SELECT * FROM users WHERE id = '' OR '1'='1'
    

    这将返回所有的用户记录,而不是只返回ID匹配的记录。

    因此,为了避免SQL注入攻击,我们在MyBatis中应尽可能地使用#{}来传递参数。

    问: 如何使用MyBatis实现分页查询?

    好的,我会尽力回答这个问题。

    在MyBatis中,我们可以使用RowBounds参数来实现分页查询。具体来说,我们可以使用SqlSession的selectList()方法,并将RowBounds对象作为参数传递给它。这样,MyBatis就会在底层执行分页查询。

    例如:

    RowBounds rowBounds = new RowBounds(startIndex, pageSize);
    List<User> users = sqlSession.selectList("com.example.UserMapper.getUserList", params, rowBounds);
    

    在这个例子中,startindex表示从第几条开始获取数据,pageSize表示每页的数量。

    此外,我们也可以自定义分页查询。例如,我们可以编写一个自定义的分页插件,以实现更高级的分页功能,如总行数计算、分页跳转等。

    例如:

    public class PaginationInterceptor implements Interceptor {
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            BoundSql boundSql = ((MappedStatement)invocation.getTarget()).getBoundSql(invocation.getArgs()[0]);
            Object parameterObject = boundSql.getParameterObject();
            Page page = (Page)parameterObject;
            long start = (page.getPageNum() - 1) * page.getPageSize();
            long end = page.getPageNum() * page.getPageSize();
    
            List<Object> args = new ArrayList<>();
            args.add(page.getParams());
            args.add(new RowBounds(start, end));
    
            Object result = invocation.proceed(args.toArray());
    
            MetaObject metaObject = SystemMetaObject.forObject(result);
            List list = (List)metaObject.getValue("list");
            long total = ((Number)metaObject.getValue("total")).longValue();
    
            return new PageImpl<>(list, page, total);
        }
    
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
    
        @Override
        public void setProperties(Properties properties) {
        }
    }
    

    在这个例子中,我们实现了PaginationInterceptor接口,并重写了intercept()方法。在该方法中,我们先计算出分页的起始位置和结束位置,然后修改参数列表并调用proceed()方法。最后,我们将查询结果转换成自定义的Page对象。

    总的来说,在MyBatis中实现分页查询有多种方法,可以根据实际需求来选择合适的方法。

    问: MyBatis有哪些常用的注解,能简要解释一下它们的功能吗?

    好的,我会尽力回答这个问题。

    以下是MyBatis中最常用的几个注解及其功能:

    1. @Select:用于标注查询语句,指定SQL语句或SQL语句的ID,用于执行查询操作。

    2. @Insert:用于标注插入语句,指定SQL语句或SQL语句的ID。

    3. @Update:用于标注更新语句,指定SQL语句或SQL语句的ID。

    4. @Delete:用于标注删除语句,指定SQL语句或SQL语句的ID。

    5. @Results:用于设置结果映射,可以指定列名、类型、别名等信息。

    6. @Result:用于设置单个结果的映射,可以指定列名、类型、别名等信息。

    7. @Id:用于标记主键字段,可以在结果映射中使用。

    8. @One:用于设置一对一关联映射。

    9. @Many:用于设置一对多关联映射。

    10. @Column:用于设置列名或列别名。

    11. @Param:用于设置参数名称,可以避免重复命名冲突。

    12. @Options:用于设置映射选项,如延迟加载、嵌套结果等。

    13. @CacheNamespace:用于启用缓存功能,可以设置缓存的相关信息。

    14. @CacheRef:用于引用另一个缓存空间。

    15. @Flush:用于刷新缓存。

    16. @FlushInterval:用于设置缓存刷新间隔。

    17. @UseGeneratedKeys:用于指定自动生成的主键。

    18. @KeyProperty:用于指定主键属性。

    19. @KeyColumn:用于指定主键列。

    20. @UseGeneratedKeys:用于指定使用自动生成的主键。

    21. @FlushCache:用于刷新缓存。

    以上就是MyBatis中最常用的注解及其功能。需要注意的是,有些注解在MyBatis的不同版本中可能有不同的用法和功能。因此,在使用过程中,建议查阅相关的文档或官方教程。

    问: 你如何在MyBatis中传递多个参数?

    好的,我会尽力回答这个问题。

    在MyBatis中,可以使用以下两种方式来传递多个参数:

    1. 将多个参数封装到一个Java对象中,然后将该对象作为参数传递。这是最常用的方式,因为它可以让SQL语句更加清晰,易于维护。

    例如,假设我们需要查询所有年龄大于20岁且姓名为“John”的用户,我们可以定义一个UserFilter类,并将其作为参数传递给SQL语句:

    class UserFilter {
        private int age;
        private String name;
    
        // getter 和 setter 方法...
    }
    
    UserFilter filter = new UserFilter();
    filter.setAge(20);
    filter.setName("John");
    
    List<User> users = sqlSession.selectList("com.example.UserMapper.getUserList", filter);
    
    1. 使用map对象作为参数,其中键是参数名,值是对应的值。

    例如:

    Map<String, Object> params = new HashMap<>();
    params.put("age", 20);
    params.put("name", "John");
    
    List<User> users = sqlSession.selectList("com.example.UserMapper.getUserList", params);
    

    在这两种方式中,推荐使用第一种方式,因为它可以使SQL语句更加清晰,也便于在DAO层和Service层之间传递参数。如果参数过多,也可以考虑使用DTO(Data Transfer Object)模式,即将多个参数封装到一个对象中。

    问: 如何处理MyBatis中的关联查询,比如一对多,多对多的情况?

    好的,我会尽力回答这个问题。

    MyBatis提供了一种强大的方式来处理关联查询,可以支持一对一、一对多和多对多的情况。以下是一些常见的关联查询的例子:

    1. 一对一查询:假设有一个Teacher表和一个Course表,一个老师只能教授一门课程,这种情况下可以用@One注解来建立一对一的关联关系。
    <mapper namespace="com.example.TeacherMapper">
        <select id="findTeacherAndCourseById" resultType="com.example.Teacher">
            SELECT *
            FROM teacher t
            LEFT JOIN course c ON t.id = c.teacher_id
            WHERE t.id = #{id}
        </select>
    </mapper>
    

    在这个例子中,Teacher和Course是一对一的关系,所以可以直接在查询中用JOIN操作关联查询。

    1. 一对多查询:假设有一个Department表和一个Employee表,一个部门有很多员工,可以用@OneToMany注解来建立一对多的关联关系。
    <mapper namespace="com.example.DepartmentMapper">
        <resultMap id="departmentMap" type="com.example.Department">
            <id property="id" column="id"/>
            <collection property="employees" ofType="com.example.Employee">
                SELECT * FROM employee WHERE department_id = #{id}
            </collection>
        </resultMap>
    
        <select id="findDepartmentWithEmployees" resultMap="departmentMap">
            SELECT * FROM department WHERE id = #{id}
        </select>
    </mapper>
    

    在这个例子中,Department和Employee是一对多的关系,所以我们需要使用collection元素来建立关联关系。

    1. 多对多查询:假设有一个User表和一个Role表,一个用户有多重角色,一个角色有多重用户,可以用@ManyToMany注解来建立多对多的关联关系。
    <mapper namespace="com.example.RoleMapper">
        <resultMap id="roleMap" type="com.example.Role">
            <id property="id" column="id"/>
            <collection property="users" ofType="com.example.User">
                SELECT * FROM user WHERE role_id = #{id}
            </collection>
        </resultMap>
    
        <select id="findRoleWithUsers" resultMap="roleMap">
            SELECT * FROM role WHERE id = #{id}
        </select>
    </mapper>
    

    在这个例子中,User和Role是多对多的关系,所以我们需要用collection元素来建立关联关系。

    在MyBatis中处理关联查询需要理解这些基本的概念和方法,同时也需要熟练掌握SQL的知识,以便于根据不同的情况调整SQL查询语句。

    问: 请解释一下MyBatis的动态SQL是什么,以及如何使用它?

    好的,我会尽力回答这个问题。

    MyBatis的动态SQL是一种技术,它允许开发者根据条件来动态地拼接SQL语句。例如,我们可以根据条件来添加WHERE子句,从而减少SQL语句的数量,降低代码的复杂度。

    在动态SQL中,我们可以使用以下几种标签来拼接SQL语句:

    1. if标签:如果满足某种条件,就执行某个SQL片段。
    <select id="selectUserByAge" resultType="com.example.User">
        SELECT * FROM users
        <if test="age != null">
            WHERE age > #{age}
        </if>
    </select>
    

    在这个例子中,如果age参数不为空,则查询语句中包含WHERE子句。

    1. choose标签:根据条件来选择执行哪个SQL片段。
    <select id="selectUserByAge" resultType="com.example.User">
        SELECT * FROM users
        <choose>
            <when test="age != null">
                WHERE age > #{age}
            </when>
            <when test="name != null">
                AND name LIKE #{name}
            </when>
        </choose>
    </select>
    

    在这个例子中,如果有age参数,则查询语句包含WHERE子句;如果有name参数,则添加AND子句。

    1. foreach标签:遍历集合中的元素,并根据每个元素的值来拼接SQL语句。
    <select id="selectAllUsers" resultType="com.example.User">
        SELECT * FROM users
        WHERE id IN
        <foreach item="item" index="index" collection="ids" open="(" separator="," close=")">
            #{item}
        </foreach>
    </select>
    

    在这个例子中,ids参数是一个List,将它的每个元素都添加到IN子句中。

    1. trim标签:用来拼接SQL片段,可以添加前缀和后缀。
    <select id="selectUsersByNames" resultType="com.example.User">
        SELECT * FROM users
        WHERE name IN
        <trim prefix="(" suffix=")">
            <foreach item="item" index="index" collection="names" separator=",">
                #{item}
            </foreach>
        </trim>
    </select>
    

    在这个例子中,names参数是一个List,将它的每个元素都添加到IN子句中,并添加括号前缀和后缀。

    总的来说,在MyBatis中使用动态SQL可以使我们的SQL语句更加灵活、可读性强,并可以减少代码量。

    问: MyBatis的插件是什么,你如何编写一个MyBatis插件?

    好的,我会尽力回答这个问题。

    MyBatis插件是MyBatis的一个重要功能,它可以让开发者定制自己的拦截器,来实现特殊的功能。比如:统计SQL的运行时间、打印SQL的日志等。

    为了编写一个MyBatis插件,我们需要继承InterceptorsPlugin抽象类,并重写其intercept方法:

    public class LogPlugin extends Interceptor {
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            long start = System.currentTimeMillis();
            Object object = invocation.proceed();
            long end = System.currentTimeMillis();
            log.info("执行SQL耗时:" + (end - start));
            return object;
        }
    }
    

    在这个例子中,我们创建了一个LogPlugin,它会在每次执行SQL语句之后打印执行的时间。

    接下来,我们需要注册插件:

    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    builder.addInterceptor(new LogPlugin());
    SqlSessionFactory factory = builder.build(inputStream);
    SqlSession session = factory.openSession();
    

    另外,我们还可以重写intercept方法,以实现其他功能,如审计、权限控制等。

    需要注意的是,MyBatis插件是按照顺序执行的,所以在注册插件时需要注意插件的执行顺序。默认情况下,MyBatis插件的执行顺序是从前到后,即前面的插件优先执行。如果需要改变插件的执行顺序,可以在XML文件中指定order属性,越小的order越早执行。

    另外,MyBatis还提供了一些内置的插件,如LoggingInterceptor(日志插件)、CachingInterceptor(缓存插件)等,这些插件可以直接使用,也可以自定义扩展。