MyBatis基础

Posted by AaronYang on August 10, 2020

MyBatis基础

简介

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

优点

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的orm字段关系映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • 提供xml标签,支持编写动态sql。

构建

  • 导入依赖

使用Maven来构建项目,需导入以下依赖

<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>${mysql-connector.version}</version>
</dependency>
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis</artifactId>
	<version>${mybatis.version}</version>
</dependency>
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>${junit.version}</version>
</dependency>
  • 编写核心配置文件,构建SqlSessionFactory

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- 核心配置文件 -->
<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://192.168.0.112:3306/cosmos?useSSL=true&amp;amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="yanglin10"/>
            </dataSource>
        </environment>
    </environments>
</configuration>
  • SqlSessionFactory中获取SqlSession

SqlSession 提供了在数据库执行 SQL 命令所需的所有方法 ,即可以通过SqlSession实例来直接执行已映射的SQL语句。

public class MyBatisUtils {

    public static SqlSessionFactory sqlSessionFactory;

    static{
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSqlSession(){
        return  sqlSessionFactory.openSession();
    }
}
  • 编写接口文件,并进行SQL映射
/**
 * 用户信息类
 */
@Data
@ToString
public class User {
    private static final long serialVersionUID = 1L;
    /**
     * ID
     */
    private int id;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 邮箱
     */
    private String email;
    /**
     * 权限
     */
    private String role;
    /**
     * 地址
     */
    private String address;
    /**
     * 联系方式
     */
    private String telephone;
}
public interface UserMapper {
    public User selectUser(int id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mybatis.test01.dao.UserMapper">
    <select id="selectUser" resultType="com.mybatis.test01.pojo.User">
        select * from cosmos.user where id = #{id}
    </select>
</mapper>

命名空间com.mybatis.test01.dao.UserMapper对应以前的Dao接口,映射语句selectUser即接口的方法。类比如下SQL语句执行过程。resultType使用全限定名。

User user = (User) session.selectOne("com.mybatis.test01.dao.UserMapper.selectUser",1);
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUser(101);

接口实现类由原来的UserDaoImpl转变为一个Mapper配置文件。 第二种方法有很多优势,首先它不依赖于字符串字面值,会更安全一点;其次,如果你的 IDE 有代码补全功能,那么代码补全可以帮你快速选择到映射好的 SQL 语句。

  • 注册Mapper.xml文件
<mappers>
	<mapper resource="com/mybatis/test01/dao/UserMapper.xml"></mapper>
</mappers

但是Mapper.xml如果不是放在resources目录下将出现资源过滤问题,需要在pom文件中添加资源扫描路径

<build>
	<resources>
		<resource>
			<directory>src/main/resources</directory>
			<includes>
				<include>**/*.properties</include>
				<include>**/*.xml</include>
			</includes>
			<filtering>true</filtering>
		</resource>
		<resource>
			<directory>src/main/java</directory>
			<includes>
				<include>**/*.properties</include>
				<include>**/*.xml</include>
			</includes>
			<filtering>true</filtering>
		</resource>
	</resources>
</build>

对于XML映射器,也可以使用Java注解来配置。

public interface UserInfoMapper {
    @Select("select * from cosmos.user")
    List<User> getUsers();
}

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

配置

属性(properties)

这些属性可以在外部进行配置,并可以进行动态替换。

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.0.112:3306/cosmos?useSSL=true&amp;amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC
username=root
password=yanglin10

引入属性文件并使用

<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>
  • 首先读取在 properties 元素体内指定的属性。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。

cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true | false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true | false false
aggressiveLazyLoading 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。 true | false false (在 3.4.1 及之前的版本中默认为 true)
multipleResultSetsEnabled 是否允许单个语句返回多结果集(需要数据库驱动支持)。 true | false true
useColumnLabel 使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。 true | false true
useGeneratedKeys 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。 true | false False
autoMappingBehavior 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 NONE, PARTIAL, FULL PARTIAL
autoMappingUnknownColumnBehavior 指定发现自动映射目标未知列(或未知属性类型)的行为。NONE: 不做任何反应WARNING: 输出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARNFAILING: 映射失败 (抛出 SqlSessionException) NONE, WARNING, FAILING NONE
defaultExecutorType 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。 SIMPLE REUSE BATCH SIMPLE
defaultStatementTimeout 设置超时时间,它决定数据库驱动等待数据库响应的秒数。 任意正整数 未设置 (null)
defaultFetchSize 为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。 任意正整数 未设置 (null)
defaultResultSetType 指定语句默认的滚动策略。(新增于 3.5.2) FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同于未设置) 未设置 (null)
safeRowBoundsEnabled 是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。 true | false False
safeResultHandlerEnabled 是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。 true | false True
mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 true | false False
localCacheScope MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。 SESSION | STATEMENT SESSION
jdbcTypeForNull 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。 OTHER
lazyLoadTriggerMethods 指定对象的哪些方法触发一次延迟加载。 用逗号分隔的方法列表。 equals,clone,hashCode,toString
defaultScriptingLanguage 指定动态 SQL 生成使用的默认脚本语言。 一个类型别名或全限定类名。 org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler 指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5) 一个类型别名或全限定类名。 org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。 true | false false
returnInstanceForEmptyRow 当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2) true | false false
logPrefix 指定 MyBatis 增加到日志名称的前缀。 任何字符串 未设置
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置
proxyFactory 指定 Mybatis 创建可延迟加载对象所用到的代理工具。 CGLIB | JAVASSIST JAVASSIST (MyBatis 3.3 以上)
vfsImpl 指定 VFS 的实现 自定义 VFS 的实现的类全限定名,以逗号分隔。 未设置
useActualParamName 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1) true | false true
configurationFactory 指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3) 一个类型别名或完全限定类名。 未设置
shrinkWhitespacesInSql Removes extra whitespace characters from the SQL. Note that this also affects literal strings in SQL. (Since 3.5.5) true | false false
<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

类型别名(typeAliasers)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

<typeAliases>
    <typeAlias alias="User" type="com.mybatis.test01.pojo.User"/>
</typeAliases>

也可以指定一个包名,MyBatis会扫描包下的Java Bean。

 <typeAliases>
     <package name="com.mybatis.test01.pojo"/>
</typeAliases>

在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 若有注解,则别名为其注解值。

@Alias("UserEntity")
public class User {
    ...
}

环境配置(environments)

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中 。 尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。 每个数据库对应一个SqlSessionFactory实例。

<environments default="development">
  <environment id="development">
    <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|MANAGED]“)。
  • 数据源的配置(比如: type="[UNPOOLED|POOLED|JNDI]” )。

事务管理器

配置JDBC将使用JDBC的提交和回滚设施,它依赖从数据源获得的连接来管理实务作用域。 MANAGED 从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期。如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

数据源

UNPOOLED: 这个数据源的实现会每次请求时打开和关闭连接。

POOLED: 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。这种处理方式很流行,能使并发 Web 应用快速响应请求。

JNDI: 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用

映射器(mappers)

MyBatis的行为由上述元素配置约束了,我们需要查找SQL映射语句,最好的方法是直接告诉MyBatis到哪去查找映射文件。

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<!-- 接口和Mapper配置文件同名并在同一包下 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<!-- 接口和Mapper配置文件同名并在同一包下 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

推荐使用第一种方式。

XML映射器

Select语句

接收一个int类型的参数,并返回一个UserMap结果集。

<select id="selectUser" resultMap="UserMap">
    select * from cosmos.user where id=#{id}
</select>
<select
  id="selectPerson"
  parameterType="int"
  parameterMap="deprecated"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">

insert/update/delete

<insert
  id="insertAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  keyProperty=""
  keyColumn=""
  useGeneratedKeys=""
  timeout="20">

<update
  id="updateAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

<delete
  id="deleteAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">
属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
resultType 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
resultMap 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
fetchSize 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。
statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultSetType FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
resultOrdered 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false
resultSets 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。

SQL

定义可重用的 SQL 代码片段,以便在其它语句中使用。 参数可以静态地(在加载的时候)确定下来,并且可以在不同的 include 元素中定义不同的参数值。

<sql id="userColumns"> username, password </sql>

<select id="selectUserInfoByUsername" resultType="User">
    SELECT
    <include refid="userColumns"/>
    FROM cosmos.user WHERE username = #{username}
</select>

参数

  • 字符串替换

默认情况下,使用 #{} 参数语法时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。

Preparing: SELECT username, password FROM cosmos.user WHERE username = ?

想直接在 SQL 语句中直接插入一个不转义的字符串需要用到${}

<select id="selectUserInfoByColumn" resultType="User">
    SELECT * FROM cosmos.user WHERE ${column} = #{value}
</select>
User selectUserInfoByColumn(@Param("column") String column,@Param("value") int id);

其中column会被直接替换成字符串而不是?

[com.lyricyang.container.cosmos.mapper.UserInfoMapper.selectUserInfoByColumn]-==> Preparing: SELECT * FROM cosmos.user WHERE id = ? 
[com.lyricyang.container.cosmos.mapper.UserInfoMapper.selectUserInfoByColumn]-==> Parameters: 5(Integer)

结果映射

JavaBean:

@Alias("UserEntity")
public class User {
    private static final long serialVersionUID = 1L;
    /**
     * ID
     */
    private int id;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String pwd;
    /**
     * 邮箱
     */
    private String email;
    /**
     * 权限
     */
    private String role;
    /**
     * 地址
     */
    private String address;
    /**
     * 联系方式
     */
    private String telephone;
}

数据库中的pwd属性对应的字段为password,通过以下SQL映射获取结果出现属性为null的情况。

<select id="selectUser" resultType="UserEntity">
    select * from cosmos.user where id=#{id}
</select>

User(id=5, username=18248825295, pwd=null, email=null, role=ROLE_ADMIN, address=null, telephone=null)

在这些情况下,MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。 如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名(这是一个基本的 SQL 特性)来完成匹配。

<select id="selectUser" resultType="UserEntity">
    select *, password as pwd from cosmos.user where id=#{id}
</select>

另外,可以显式使用外部resultMap来解决列名不匹配的问题。

<resultMap id="UserMap" type="UserEntity">
    <result column="password" property="pwd"/>
</resultMap>

<select id="selectUser" resultMap="UserMap">
    select * from cosmos.user where id=#{id}
</select>

然后在引用它的语句中设置 resultMap 属性就行了(注意我们去掉了 resultType 属性)。

属性 描述
property 映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 无论是哪一种情形,你都可以使用常见的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
column 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。
javaType 一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
jdbcType JDBC 类型,所支持的 JDBC 类型参见这个表格之后的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值的列指定这个类型。
typeHandler 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。

高级结果映射

  • id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
  • result – 注入到字段或 JavaBean 属性的普通结果
  • association – 一个复杂类型的关联;许多结果将包装成这种类型
    • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
  • collection – 一个复杂类型的集合
    • 嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用

一对多(collection):

<resultMap id="UserStudy" type="User">
    <id property="id" column="id"/>
    <result property="username" column="uname"/>
    <result property="password" column="upwd"/>
    <collection property="plans" ofType="Mission">
        <id property="mid" column="mid"/>
        <result property="task" column="task"/>
        <result property="status" column="sta"/>
    </collection>
</resultMap>

<select id="getUsers" resultMap="UserStudy">
    select id, u.username as uname, u.`password` as upwd, mid , task , m.`status` as sta
    from user as u LEFT JOIN mission as m on u.id = m.uid where u.id = #{id}
</select>

多对一(association):

<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>

# 或者将结果嵌套在内
<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <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"/>
  </association>
</resultMap>

javaType: 指定实体类中属性的类型

ofType: 指定实体类中集合的元素类型

动态SQL

动态 SQL 是 MyBatis 的强大特性之一。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

if

<select id="selectUserInfo" resultType="User">
    SELECT * FROM cosmos.user WHERE id = #{id}
    <if test=" username != null">
        AND username = #{username}
    </if>
</select>
[com.lyricyang.container.cosmos.mapper.UserInfoMapper.selectUserInfo]-==>  Preparing: SELECT * FROM cosmos.user WHERE id = ? 
[com.lyricyang.container.cosmos.mapper.UserInfoMapper.selectUserInfo]-==> Parameters: 5(Integer)

当传入的username为null时会自动忽略and条件

choose、when、otherwise

从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

<select id="selectUserInfo" resultType="User">
    SELECT * FROM cosmos.user
    <choose>
        <when test="username != null">
            WHERE username = #{username}
        </when>
        <otherwise>
            WHERE id = #{id}
        </otherwise>
    </choose>
</select>

传入username就通过username条件来查找,否则通过id来查找。

trim、where、set

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

<select id="selectUserInfo" resultType="User">
    SELECT * FROM cosmos.user
    <where>
        <if test="username != null">
            AND username = #{username}
        </if>
        <if test="id > 0">
            AND id = #{id}
        </if>
    </where>
</select>
[com.lyricyang.container.cosmos.mapper.UserInfoMapper.selectUserInfo]-==>  Preparing: SELECT * FROM cosmos.user WHERE id = ? 
[com.lyricyang.container.cosmos.mapper.UserInfoMapper.selectUserInfo]-==> Parameters: 5(Integer)

如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

<select id="selectUserInfo" resultType="User">
    SELECT * FROM cosmos.user
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
        <if test="username != null">
            AND username = #{username}
        </if>
        <if test="id > 0">
            AND id = #{id}
        </if>
    </trim>
</select>

用于动态更新语句的类似解决方案叫做 set

foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。 当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素。

@Update({"<script>",
         "update Author",
         "  <set>",
         "    <if test='username != null'>username=#{username},</if>",
         "    <if test='password != null'>password=#{password},</if>",
         "    <if test='email != null'>email=#{email},</if>",
         "    <if test='bio != null'>bio=#{bio}</if>",
         "  </set>",
         "where id=#{id}",
         "</script>"})
void updateAuthorValues(Author author);

日志

Mybatis 通过使用内置的日志工厂提供日志功能。

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j
  • JDK logging

在MyBatis核心配置文件中配置日志

<!-- 标准日志实现 -->
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- log4j的实现 -->
<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>

log4j.properties的配置文件

#将等级为DEBUG的日志信息输出到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/aaron.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.PrepareStatement=DEBUG

缓存

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存(SqlSession级别,开始至关闭期间)。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行(Mapper级别):

<cache/>
<!-- 参数设置 -->
<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

基本上就是这样。这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

作用域和生命周期

SqlSessionFactoryBuilder

一旦创建了SqlSessionFactory就不需要它了,因此SqlSessionFactoryBuilder实例的最佳作用域是方法作用域。 可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

SqlSessionFactory

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

SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域

绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。

Mapper映射器

映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。 最好将映射器放在方法作用域内 。

附录

Q:com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException异常

A:将配置文件中的中文注释删除。