MyBatis 笔记
MyBatis 可以说是后端必备,而在最初学习完后没有好好回顾过,这里总结+笔记过一次。
Mybatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。Mybatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。Mybatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(普通老式 Java 对象)为数据库中的记录。
1. 构建 SqlSessionFactory
我们既然要学习 MyBatis ,就必须知道它是以 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例通过 SqlSessionFactoryBuilder 获得。SqlSessionFactoryBuilder 就可以通过 XML 配置文件或一个预先构建的 Configuration 实例来构建出 SqlSessionFactory 实例(就是对 Configuration 对象进行赋值,它的字段就是下面要配置的属性,包括 properties、settings、typeAliases、typeHandlers、objectFactory、plugins、environments 和 mappers)。这两种配置方式都可以实现相应的功能,如果你在不只一个地方进行了配置,那么,mybatis 将按照如下顺序来加载:
- 首先读取 properties 元素内指定的属性;
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,并覆盖之前读取过的同名属性;
- 最后读取作为方法参数传递的属性,并覆盖之前读取的同名属性。
因此,通过方法参数传递的属性具有最高优先级。
⋇在实际生产中我们使用数据库连接池来管理,直接配置在yaml 文件中,读取配置文件更灵活。
1.1 properties 属性配置
# properties 配置,使用 Spring 后,这些都可以交给 Spring 来管理,只需
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"></property>
<property name="password" value="1frs1"></property>
</properties>
# config.properties
<dataSource type="POOLED">
<property name="driver" value="${driver}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
</dataSource>
后面的 username 和 password 将会由 properties 元素中的相同属性的值进行替换,driver 和 url 属性将由 config.properties 中对应的值来替换。这样就为配置提供了诸多灵活选择。
也可以在 SqlSessionFactoryBuilder.build()
方法中传入属性值:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader,environment,props);
1.2 settings 属性配置
一个完整的 settings 元素的示例如下:
<settings>
<setting name="cacheEnabled" value="true" /> <!-- 全局性地开启或关闭所有映射配置文件中已配置的任何缓存 -->
<setting name="lazyLoadingEnabled" value="true" /> <!-- 延迟加载的全局开关。特定关联关系中可通过设置 fetchType 来覆盖该项的开关状态 -->
<setting name="multipleResultSetsEnabled" value="true"/><!-- 是否允许单个语句返回多结果集(需要数据库驱动支持) -->
<setting name="useColumnLabel" value="true"/><!-- 使用列标签代替列名 -->
<setting name="useGeneratedKeys" value="false"/><!-- 允许 JDBC 支持自动生成主键,需要数据库驱动支持 -->
<setting name="autoMappingBehavior" value="PARTIAL"/><!-- 指定 Mybatis 应如何自动映射 列 到字段或属性 -->
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/><!-- ... -->
<setting name="defaultExecutorType" value="SIMPLE"/><!-- 配置默认的执行器 -->
<setting name="defaultStatementTimeout" value="25"/><!-- 设置超时时间 -->
<setting name="defaultFetchSize" value="100"/><!-- 为驱动的结果集获取数量(fetchSize)设置一个建议值 -->
<setting name="safeRowBoundsEnabled" value="false"/><!-- 是否允许在嵌套语句中使用分页,允许设置为 false -->
<setting name="mapUnderscoreToCamelCase" value="false"/><!-- 是否开启驼峰命名自动映射,即从 A_COLUMN 到 Java 属性名 aColumn -->
<setting name="localCacheScope" value="SESSION"/><!-- -->
<setting name="jdbcTypeForNull" value="OTHER"/><!-- 当没有为参数指定特定的 JDBC 类型时对应的默认 JDBC 类型 -->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/><!-- 指定对象的那些方法触发一次延迟加载 -->
</settings>
1.3 类型别名配置
类型别名可以为 java 类型设置一个缩写名字。它仅用于 XML 配置,意在降低冗余的全限定类名书写。
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"></typeAlias>
</typeAliases>
1.4 类型处理器
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时,都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。
![](/Users/chappyzhao/Downloads/pictures/截屏2022-07-26%2017.50.04.png)
也可以重写已有的类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型。具体做法为:实现org.apache.ibatis.type.TypeHandler
接口,或继承一个很便利的类org.apache.ibatis.type.BaseTypeHandler
,并且可以(可选的)将它映射到一个 JDBC 类型:
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extend BaseTypeHandler {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
}
<!-- mybatis-config.xml -->
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"></typeHandler>
</typeHandlers>
使用上述的类型处理器将会覆盖已有的处理 Java String 类型的属性以及 VARCHAR 类型的参数和结果的类型处理器。注意MyBatis不会检测数据库源信息来决定来使用哪种类型,所以你必须在参数和结果映射中指明字段是 VARCHAR 类型。这是因为MyBatis 只有到语句被执行才知道数据类型。
MyBatis 得知某种类型处理器处理的 Java 类型有两种方式:
- 在类型处理器的配置元素(typeHandler)中新增一个 javaType 属性,如
javaType="String"
; - 在类型处理器的类上增加
@MappedTypes
注解指定与其关联的 Java 类型列表。如果在javaType
属性中也同时指定,则注解上的配置将被忽略。
Mybatis 也可以通过两种方式来得知指定的 JDBC 类型,就是上面的代码:
- 在类型处理器的配置元素中新增一个 javaType 属性,如 JavaType=“VARCHAR”;
- 在类型处理器的类上增加一个
@MappedJdbcTypes
注解指定与其关联的 JDBC 列表。如果在javaType
属性中也同时指定,则注解上的配置将被忽略。
使用 MyBatis 自动查找类型处理器:
<!-- mybatis-config.xml -->
<typeHandlers>
<package name="org.mybatis.example"></package>
</typeHandlers>
1.5 环境配置
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中。如,开发、测试和生产环境需要有不同的配置,或者想在具有相同的 Schema 的多个生产数据库中使用相同的 SQL 映射。
尽管可以配置多个环境,但是每个 SqlSessionFactory 实例只能选择一种环境。
所以,如果想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,而如果连接三个,就需要创建三个,以此类推。
environments 元素定义了如何配置环境:
<environments default="development">
<environment>
<transactionManager type="JDBC">
<property name="..." value="..." />
</transactionManager>
<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>
⚠️一些关键点:
- 默认使用的环境 ID(比如:default=“development”)
- 每个 environment 元素定义的环境 ID(比如:id=“development”)
- 事务管理器的配置(type=“JDBC”),type 有两种类型:JDBC|MANAGED,JDBC 直接使用了 JDBC 的提交和回滚功能;而 MANAGED 几乎什么都不做。(我们平时使用的 Spring + MyBatis 则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置)
- 数据源的配置(type=“POOLED”),dataSource 使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。有三种内建的数据源类型(UNPOOLED|POOLED|JNDI),UNPOOLED 数据源的实现会每次请求时打开和关闭连接;POOLED 数据源的实现利用“池”的概念将 JDBC 对象组织管理起来,避免了创建新的连接实例时所必需的初始化和认证时间,这是最流行的处理方式;JNDI 数据源实现是为了能在 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
1.6 映射器 mappers
上述已经配置完了 MyBatis 的基本行为,现在定义 SQL 映射语句。有如下方式:
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
接下来就是=单个 SQL 的映射了。
2. XML 映射器
MyBatis 的强大之处在于它的映射语气映射,这是它的魔力所在。与 JDBC 代码相比,它省掉了将近 95% 的代码,使用户能更专注于 SQL 代码。
SQL 映射文件只有很少的几个顶级元素:cache(该命名空间的缓存配置)、cache-ref(引用其他的缓存配置)、resultMap(描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素)、sql(可被其他语句重复引用的语句块)、insert、update、delete、select。
2.1 select
查询是最常用的元素之一。MyBatis 的基本原则之一是:在每个插入、更新或删除操作之间,通常会执行多个查询操作。
<select id="selectPerson" parameterType="int" resultType="hashmap">
select * from PERSON WHERE ID = #{id}
</select>
这个语句接收一个 int 参数,返回一个 HashMap 类型的对象,其中键是列名,值是结果行中对应的值。
#{}
告诉 MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个 “?”来标识,并被传递到一个新的预处理语句中,就像这样:
// 近似的 JDBC 代码
String selectPerson = "select * from PERSON where ID=? ";
PreparedStatement ps = conn.preparedStatement(selectPerson);
ps.setInt(1,id);
所以 #{}
就等价于 ?
<select
<!-- 在命名空间中唯一的标识符,可以用来引用这条语句 -->
id="selectPerson"
<!-- 这个属性可选,因为 MyBatis 将会通过类型处理器推断出参数类型 -->
parameterType="int"
<!-- 期望从这条语句中返回类的全限定类名。注意:如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型(就是如果返回了 id,name,age的多个集合数据,然后我们将这些数据封装在 user 对象中,则 resultType 指定为 user,而不是map) -->
resultType="hashmap"
resultMap="personResultMap"<!-- 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,resultType 和resultMap 只能同时使用一个 -->
flushCache="false"<!-- 默认false,设置为true后,只要语句调用就会导致本地和二级缓存被清空 -->
useCache="true"<!-- 对select默认为true,设置为true后,只要语句被调用就会导致本语句的结果被二级缓存存起来 -->
timeout="10"<!-- 等待数据返回的最长时间 -->
fetchSize="256"<!-- 给驱动的建议值,建议每次驱动批量返回的结果行 -->
statementType="PREPARED"
resultSetType="FORWARD_ONLY"></select>
2.2 insert、update 和 delete
<insert
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="false"<!-- 对于 insert、update和delete默认为true -->
statementType="PREPARED"
keyProperty=""<!-- 仅适用于insert和update,指定能够唯一识别对象的属性,MyBatis会使用getGeneratedKeys的返回值或insert语句的selectKey 子元素设置它的值。默认:unset -->
keyColumn=""<!-- 仅适用于insert和update,设置生成键值在表中的别名,在某些数据库如PostgreSQL中,当主键列不是表中的第一列的时候,是必须设置的。 -->
useGeneratedKeys="true"<!-- 仅适用于insert和update,这会令 MyBatis 使用 JDBC 的getGeneratedKeys方法取出由数据库内部生成的主键,默认false -->
timeout="20">
<update
id="updateAuthor"
parameterType="domain.blog.Author">
<delete
id="deleteAuthor"
parameterType="domain.blog.Author">
示例:
<insert id="insertAuthor">
insert into Author(id, name, password, email, bio)
values(#{id}, #{name}, #{password}, #{email}, #{bio})
</insert>
<update id="updateAuthor">
update Author set
name=#{name},
password=#{password},
email=#{email},
bio=#{bio}
where id=#{id}
</update>
<delete id="deleteAuthor">
delete from Author where id=#{id}
</delete>
如上所示,插入语句的配置规则更加丰富,在插入语句里有一些额外的属性和子元素用来处理主键的生成。
2.3 sql
这个元素可以用来定义可重用的 SQL 代码片段。参数可以静态的(在加载的时候)确定下来,并且可以在不同的 include 元素中定义不同的参数值。比如:
<sql id="userColumns">
${alias}.id,${alias}.username,${alias}.password
</sql>
这个 SQL 片段可以在其他语句中使用,如:
<select id="selectUsers" resultType="map">
select
<include refid="userColumn"><property name="alias" value="t1"></property></include>
<include refid="userColumn"><property name="alias" value="t2"></property></include>
from some_table t1
cross join some_table t2
</select>
也可以在 include 的 refid 属性或内部语句中使用属性值。如:
<sql id="sometable">
${prefix}Table
</sql>
<sql id="someinclude">
from
<include refid="${include_target}" />
</sql>
<select id="select" resultType="map">
select
field1, field2, field3
<include refid="someinclude">
<property name="prefix" value="Some"></property>
<property name="include_target" value="sometable"></property>
</include>
</select>
2.4 字符串替换
默认情况下,使用#{}
参数语法时,MyBatis 会创建 PreparedStatement
参数占位符,并通过占位符安全的设置参数,就像使用 ?
一样。这样做更安全、更迅速,通常也是首选,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。如:ORDER BY ${columnName}
。这样 MyBatis 就不会修改或转义该字符串了。
@Select("select * from user where id=#{id}")
User findById(@Param("id") long id);
@Select("select * from user where name = #{name}")
User findByName(@Param("name") String name);
@Select("select * from user where email = #{email}")
User findByEmail(@Param("email") String email);
#...其他的findByXXX
# 以上这些都可以写成如下:
@Select("select * from user where ${columnName} = #{value}")
User findByColumnName(@Param("columnName")String columnName,@Param("value")String value);
其中 ${}
会被直接替换,而#{}
会被当作 ? 对待。但是用这种方式接受用户的输入的话参数时不安全的,会存在 SQL 注入的风险,因此,要么不允许用户输入这些字段,要么自行转义并做校验。
3. 结果映射
resultMap 元素是 MyBatis 中最重要最强大的元素,它可以让你从 90% 的JDBC resultSets 数据提取代码中解放出来,并在一些情况下允许你进行一些 JDBC 不支持的操作。实际上,复杂代码的话,一份 resultMap 能够代替通等功能的数千行代码。
ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需描述关系就行。
# 这个语句可以直接将所有的列映射到 HashMap 的*键*上,这是 resultType 属性决定的。
<select id="selectUsers" resultType="map">
select id,username,hashedPassword
from some_table
where id=#{id}
</select>
虽然大部分情况下直接映射到 HashMap 这个功能够用,但是很多时候我们需要来映射自己创建的 JavaBean 或 POJO(Plain Old Java Objects,普通老式Java对象),如
@Data
public class User{
private int id;
private String username;
private String hashedPassword;
}
这样的一个 JavaBean 可以被映射到 ResultSet,就像映射到 hashmap 一样简单。
<!-- mybatis-config.xml 中 -->
<typeAlias type="com.someapp.model.User" alias="User"/>
<!-- SQL 映射 XML 中 -->
<select id="selectUsers" resultType="User">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
3.1 高级结果映射
MyBatis 创建时的一个思想就是:数据库永远不可能是你所想或所需的样子。而 resultMap 就是用来适配各种不同需求的(之前的例子都没有显式的配置 resultMap)。
<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,
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 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>
而其对应的结果映射:
<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" />
<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>
resultMap 转义后应该就是对应的 Java 对象,而初始化一个对象往往有一些共同点。下面是 resultMap 元素的概念视图。
3.2 结果映射
- constructor ——用于在实例化类时,注入结果到构造方法中
- idArg——ID参数,标记处作为ID 的结果可以帮助提高整体性能
- arg——将被注入到构造方法的普通结果
- id——一个ID结果,标记出作为 ID 的结果可以帮助提高整体性能
- result——注入到字段或 Javabean 属性的结果
- association——一个复杂类型的关联,许多结果将包装成这种类型(多对一的时候使用)
- 嵌套结果映射:resultMap 元素或其他映射引用
- collection——一个复杂类型的集合(一对多的时候使用)
- 嵌套结果映射:resultMap 元素或其他映射引用
- discriminator——使用结果值来决定使用哪个 resultMap
- case——基于某些值的结果映射
3.3 关联的嵌套结果映射
下面是一个非常简单的例子,用来演示嵌套结果映射如何工作,现在将博客表和作者表连接在一起而不是单独查询:
<select id="selectBlog" resultMap="blogResult">
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
from Blog B left outer join Author A on B.author_id = A.id
where B.id = #{id}
</select>
查询中使用了 JOIN,使用别名使得映射非常简单。
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title" />
<association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult" />
</resultMap>
<resultMap id="authorResult" type="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"/>
</resultMap>
4. 动态SQL
JDBC 或其他类似的框架,拼接 SQL 会很麻烦,如拼接时要确保添加必要的空格,还要注意去掉列表最后一个列名的逗号等。使用 MyBatis 动态 SQL 你将摆脱这种痛苦。
实现动态 SQL 需使用动态SQL语言:if、choose(when,otherwise)、trim(where,set)、foreach
- if
<select id="findActiveBlogWithTitleLike" resultType="Blog">
select * from BLOG
where state ='ACTIVE'
<if test="title != null">
AND title like #{title}
</if>
</select>
- choose、when、otherwise
有时候我们并不想应用所有条件,而是想从多个条件中选择一个。而使用 if 标签不能满足我们的需求,这时候就可以使用 choose 标签,它类似于 java 中的 switch。
# choose 标签按照顺序判断其内部 when 标签内的 test 条件是否成立,如果有一个成立,则choose结束。条件都不满足时,执行otherwise中的SQL
<select id="getUserList" resultMap="resultMap_user" parameterType="com.gui.pojo.User">
select * from user u
<where>
<choose>
<when test="username != null">
u.username LIKE CONCAT(CONCAT('%',#{username,jdbcType=VARCHAR}),'%')
</when>
<when test="sex != null and sex != ''">
AND u.sex = #{sex,jdbcType=INTEGER}
</when>
<when test="birthday != null">
AND u.birthday = #{birthday,jdbcType=DATE}
</when>
<otherwise>
</otherwise>
</choose>
</where>
</select>
- trim、where、set
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
这里将 state 也设置为了动态的,当state 条件不满足时,就会变成:
SELECT * FROM BLOG
WHERE
#或
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’
所以这里就出现了<where>
,当子元素返回内容的情况下才插入 where 子句,若子句的开头为 AND 或 OR,where 元素也会将它们去除。
- foreach
动态SQL的另一个使用场景就是对集合进行遍历(尤其在构建了 IN 语句的时候)
<select id="selectPostIn" resultType="domain.blog.Post">
select * from POST p
<where>
<foreach item="item" index="index" collection="list" open="ID in (" sepqrator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>
Foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在集合内部使用的集合项(item)和索引(index),和指定开头和结尾的字符串以及集合项迭代时的分隔符。
5. 日志 logging
MyBatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志委托给下面的实现之一:slf4j(logback)、Apache Commons Logging、Log4j 2、JDK Logging,MyBatis 会基于自省机制顺序查找合适的工具,如果一个都未找到,日志功能就会关闭。
不少应用服务器(如 tomcat和websphere)的类路径中已经包含了Commons Logging (它们都是Apache下的),所以在这种配置环境下的 Mybatis 会把它们当作日志工具,所以你如果配置了 Log4j 将会被忽略,而 slf4j 不会,记住这点很重要。如果你想用自己的,可以在 MyBatis 配置文件中配置。
Logback 的具体配置可以见这里