Mybatis基本用法--上

Mybatis基本用法--上

本文只是为自己查漏补缺。全面的请看官方文档,支持中英文
原理参考:http://blog.csdn.net/luanlouis/article/details/40422941

第一部分 基本概念

1.1 什么是MyBatis

  MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

1.2 经典配置

从 XML 中构建 SqlSessionFactory

  每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的。 而 SqlSessionFactory 本 身 是 由 SqlSessionFactoryBuilder 创建的,它可以从 XML 配置,注解或手动配置 Java 来创建 SqlSessionFactory。但是当Mybatis与一些依赖注入框架(如Spring或者Guice)同时使用时,SqlSessions将被依赖注入框架所创建,所以你不需要使用SqlSessionFactoryBuilder或者SqlSessionFactory

  从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,可使从 classpath 或其他位置加载资源文件更加容易。

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="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

  要注意 XML 头部的声明,用来验证 XML 文档正确性。environment 元素体中包含了事务管理和连接池的配置。mappers 元素则是包含一组 mapper 映射器(这些 mapper 的 XML 文件包含了 SQL 代码和映射定义信息)

从 SqlSessionFactory 中获取 SqlSession

  既然有了 SqlSessionFactory ,顾名思义,我们就可以从中获得 SqlSession 的实例了。SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。

SqlSession session = sqlSessionFactory.openSession();

通过 XML 定义

<?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="org.mybatis.example.BlogMapper">
  <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
  </select>
</mapper>

那么利用上面的SqlSession

try {
  Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
  session.close();
}

通过注解定义

@Select({"select * from Blog where id=#{id}"})
Blog selectBlog(int id);

那么利用上面的SqlSession

try {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
} finally {
  session.close();
}

  可以看出:使用接口(基于注解),不但可以执行更清晰和类型安全的代码,而且还不用担心易错的字符串字面值以及强制类型转换。
  其实可以结合使用,接口中:简单的方法使用注解,复杂的方法使用xml配置。毕竟,对于简单语句来说,注解使代码显得更加简洁,然而 Java 注解对于稍微复杂的语句就会力不从心并且会显得更加混乱
  要求:

  1. mapper命名空间org.mybatis.example.BlogMapper应该对应类路径,即接口应该在org.mybatis.example.BlogMapper类路径下;
  2. 具有相同的文件名,比如BlogMapper.java的配置为BlogMapper.xml(** 看不清请Ctrl+鼠标滚轮放大页面 **);
  3. xml配置可以放在resources对应目录下,且路径也为org.mybatis.example.BlogMapper。
      下面给出例子,但为NewsDAO的配置

  即上面的xml配置文件不变,删去注解@Select({"select * from Blog where id=#{id}"})

Blog selectBlog(int id);

1.3 作用域(Scope)和生命周期

对于依赖注入框架Spring
  依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器(mapper)并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。如果对如何通过依赖注入框架来使用 MyBatis 感兴趣可以研究一下 MyBatis-Spring 或 MyBatis-Guice 两个子项目。

其他:

  SqlSessionFactoryBuilder:一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
  SqlSessionFactory:一旦被创建就应该在应用的运行期间一直存在,因此 SqlSessionFactory 的最佳作用域是应用作用域
  SqlSession:每个线程都应该有它自己的 SqlSession 实例。所以它的最佳的作用域是请求或方法作用域。每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。
  映射器实例(Mapper Instances):最好把映射器放在方法作用域(method scope)内。即上面的BlogMapper mapper = session.getMapper(BlogMapper.class);

第二部分 XML 映射配置文件

文档的顶层结构如下:

<?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>
    <!-- 全局映射器启用缓存 -->
    <properties resource="org/mybatis/example/config.properties">
        <property name="username" value="dev_user"/>
        <property name="password" value="F2Fa3!33TYyg"/>
    </properties>

    <settings>
        <setting name="cacheEnabled" value="true"/>
        ...
    </settings>

    <typeAliases>
        <typeAlias alias="Author" type="domain.blog.Author"/>
          ...
    </typeAliases>

    <plugins>
        <plugin interceptor="org.mybatis.example.ExamplePlugin">
            <property name="someProperty" value="100"/>
        </plugin>
    </plugins>

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

    <!-- 数据库提供厂家 -->
    <databaseIdProvider type="DB_VENDOR">
        <property name="SQL Server" value="sqlserver"/>
        <property name="DB2" value="db2"/>
        <property name="Oracle" value="oracle" />
    </databaseIdProvider>

    <mappers>
        <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
        ...
    </mappers>
</configuration>

2.1 properties 属性

  这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递。例如:

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

  其中的属性就可以在整个配置文件中使用来替换需要动态配置的属性值。比如:

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

  这个例子中的 username 和 password 将会由 properties 元素中设置的相应值来替换。 driver 和 url 属性将会由 config.properties 文件中对应的值来替换。

2.2 settings 设置

设置参数 描述 有效值 默认值
cacheEnabled 所有映射器中配置的缓存全局开关。 true ` ` false
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 true ` `false
aggressiveLazyLoading 开启时,任何方法的调用都会加载该对象的所有属性,否则每个属性会按需加载. true ` ` false
multipleResultSetsEnabled 对于未知的SQL查询,允许单一语句返回不同的结果集以达到通用的效果。 true ` ` false
useColumnLabel 允许使用列标签代替列名 true ` ` false
useGeneratedKeys (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段) true ` ` false
autoMappingBehavior 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 NONE, PARTIAL, FULL PARTIAL
defaultExecutorType 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重复用预处理语句(prepared statements); BATCH 执行器将重复用语句(即多次执行以批量操作)并执行批量更新。 SIMPLE,REUSE,BATCH SIMPLE
defaultStatementTimeout 设置超时时间,它决定驱动等待数据库响应的秒数。 任意正整数 Not Set (null)
mapUnderscoreToCamelCase 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 true` `false
callSettersOnNulls 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。 true ` ` false
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J,LOG4J,LOG4J2,JDK_LOGGING,COMMONS_LOGGING,STDOUT_LOGGING,NO_LOGGING Not set

  延迟加载:延迟加载(lazy load)是(也称为懒加载)Hibernate3关联关系对象默认的加载方式,所谓延迟加载就是当调用load方法加载对象时,返回代理对象,等到真正用到对象的内容时才发出sql语句,这个对象上的所有属性都是默认值。
有如下程序代码:

User user=(User)session.load(class, id);//直接返回的是代理对象
System.out.println(user.getId());//没有发送sql语句到数据库加载,因为id不用查就知道
user.getName();//创建真实的User实例,并发送sql语句到数据库中

2.3 typeAliases 类型别名

  类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。例如:

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
</typeAliases>

  当这样配置时,Blog可以用在任何使用domain.blog.Blog的地方。
  当然也可以用注解指定,看下面的例子:

@Alias("author")
public class Author {
    ...
}

2.4 typeHandlers 类型处理器

  无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
  要注意 MyBatis 不会窥探数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明那是哪种类型的字段, 以使其能够绑定到正确的类型处理器上。 这是因为:MyBatis 直到语句被执行才清楚数据类型。可以在这里设定:

<typeHandlers>
  <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/>
</typeHandlers>

也可以在映射器文件中设定:

<mapper namespace="org.apache.ibatis.submitted.rounding.Mapper">
	<resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap">
		<id column="id" property="id"/>
		<result column="name" property="name"/>
		<result column="funkyNumber" property="funkyNumber"/>
		<result column="roundingMode" property="roundingMode typeHandler="org.apache.ibatis.type.EnumTypeHandler""/>
	</resultMap>

	<select id="getUser" resultMap="usermap">
		select * from users
	</select>
	<insert id="insert">
	    insert into users (id, name, funkyNumber, roundingMode) values (
	    	#{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
	    )
	</insert>
</mapper>

2.5 plugins 插件

  MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)

  这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 的发行包中的源代码。 假设你想做的不仅仅是监控方法的调用,那么你应该很好的了解正在重写的方法的行为。 因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。 这些都是更低层的类和方法,所以使用插件的时候要特别当心。

  通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定了想要拦截的方法签名即可。

// ExamplePlugin.java
  @Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
  public class ExamplePlugin implements Interceptor {
    public Object intercept(Invocation invocation) throws Throwable {
      return invocation.proceed();
    }
    public Object plugin(Object target) {
      return Plugin.wrap(target, this);
    }
    public void setProperties(Properties properties) {
    }
  }
<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

  上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行低层映射语句的内部对象。

2.6 environments 环境

  没必要配置 ,Spring + MyBatis完全可以将不同环境的配置放到application-dev.yml,application-prod.yml,application.yml等配置文件中包括dataSource 数据源的配置

2.7 dataSource 数据源

  有三种内建的数据源类型(也就是 type=”[UNPOOLED|POOLED|JNDI]”):
UNPOOLED
  这个数据源的实现只是每次被请求时打开和关闭连接。虽然有一点慢,它对在及时可用连接方面没有性能要求的简单应用程序是一个很好的选择。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:

属性 描述
driver 这是 JDBC 驱动的 Java 类的完全限定名。
url 这是数据库的 JDBC URL 地址。
username 登录数据库的用户名。
password 登录数据库的密码。
defaultTransactionIsolationLevel 默认的连接事务隔离级别。
driver.encoding UTF8(可选项)

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

  除了上述提到 UNPOOLED 下的属性外,会有更多属性用来配置 POOLED 的数据源:

属性 描述
poolMaximumActiveConnections 在任意时间可以同时使用的最大连接数量,默认值:10
poolMaximumIdleConnections 任意时间可能存在的空闲连接数,经验值建议设置与poolMaximumActiveConnections相同即可
poolMaximumCheckoutTime 获取链接时如果没有idleConnection同时activeConnection达到最大值,则从activeConnections列表第一个链接开始(即最先开始的链接,也最可能快速结束),检查是否超过该设置的时间,如果超过,则被强制失效,返回链接。默认值为20000毫秒(即 20 秒),建议设置在预期最大的SQL执行时间。
poolTimeToWait 这是一个底层设置,如果获取连接花费相当长的时间,它会给连接池打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直安静的失败),默认值:20000 毫秒(即 20 秒)。
poolPingQuery 发送到数据库的侦测查询,用来检验连接是否处在正常工作秩序中并准备接受请求。默认是“NO PING QUERY SET”,建议使用select 1,开销小
poolPingEnabled 是否启用侦测查询。若开启,也必须使用一个可执行的 SQL 语句设置 poolPingQuery 属性(最好是一个非常快的 SQL),默认值:false,建议启用,防止服务器端异常关闭,导致客户端错误。
poolPingConnectionsNotUsedFor 用来配置poolPingQuery多长时间被调用一次。可以被设置匹配标准的数据库链接超时时间,来避免不必要的侦测。默认值0(也就是所有链接每一时刻都被侦测到,但仅仅当poolPingEnabled为true时适用)。建议小于服务器端超时时间,MySQL默认超时是8小时。

JNDI
  这个数据源是为了使用如Spring或应用服务器这类的容器,容器可以集中或在外部配置数据源,然后设置JNDI上下文的引用。
这个数据源只需要配置两个属性:

属性 描述
initial_context 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么 data_source 属性将会直接从 InitialContext 中寻找。
data_source 这是引用数据源实例位置的上下文的路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。

  和其他数据源配置类似,可以通过添加前缀“env.”直接把属性传递给初始上下文。比如:
env.encoding=UTF8

2.8 mappers 映射器

  告诉 MyBatis 到哪里去找映射文件。
比如:

<mappers>
  <!-- Using classpath relative resources -->
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <!-- Using url fully qualified paths -->
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <!-- Register all interfaces in a package as mappers -->
  <package name="org.mybatis.builder"/>
</mappers>

第三部分 Mapper XML 文件

3.1 SQL 映射文件的顶级元素

cache – 给定命名空间的缓存配置。
cache-ref – 其他命名空间缓存配置的引用。
resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
sql – 可被其他语句引用的可重用语句块。
insert – 映射插入语句
update – 映射更新语句
delete – 映射删除语句
select – 映射查询语句

  下面分别介绍:

3.2 select

  比如一个简单的Mapper XML 文件

<?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.nowcoder.dao.NewsDAO">
    <sql id="table">news</sql>
    <sql id="selectFields">id,title, link, image, like_count, comment_count,created_date,user_id
    </sql>
    <select id="selectByUserIdAndOffset" resultType="com.nowcoder.model.News">
        SELECT
        <include refid="selectFields"/>
        FROM
        <include refid="table"/>

        <if test="userId != 0">
            WHERE user_id = #{userId}
        </if>
        ORDER BY id DESC
        LIMIT #{offset},#{limit}
    </select>
</mapper>

  这需要很多单独的 JDBC 的代码来提取结果并将它们映射到对象实例中,这就是 MyBatis 节省你时间的地方。

select元素属性

<select
  id="selectPerson"
  parameterType="int"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10000"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">
属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。
resultType 返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用 resultType 或 resultMap,但不能同时使用。
resultMap 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,对其有一个很好的理解的话,许多复杂映射的情形都能迎刃而解。
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 中的一个,默认值为 unset (依赖驱动)。
databaseId 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。
resultSets 这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的。

resultSetType

FORWORD_ONLY 结果集的游标只能向下滚动。
SCROLL_INSENSITIVE 结果集的游标可以上下移动,当数据库变化时,当前结果集不变。
SCROLL_SENSITIVE 返回可滚动的结果集,当数据库变化时,当前结果集同步改变。

3.3 insert, update 和 delete

Insert, Update, Delete 's Attributes

属性 描述
id 同select
parameterType 同select
flushCache 同select,默认值:true(对应插入、更新和删除语句)。
timeout 同select
statementType 同select,默认值:PREPARED。
useGeneratedKeys (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。
keyProperty (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
databaseId 同select

例如:

<insert id="insertAuthor">
  insert into Author (id,username,password,email,bio)
  values (#{id},#{username},#{password},#{email},#{bio})
</insert>

<update id="updateAuthor">
  update Author set
    username = #{username},
    password = #{password},
    email = #{email},
    bio = #{bio}
  where id = #{id}
</update>

<delete id="deleteAuthor">
  delete from Author where id = #{id}
</delete>

  如果 id 使用了自动生成的列类型:

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username,password,email,bio)
  values (#{username},#{password},#{email},#{bio})
</insert>

  如果你的数据库还支持多行插入, 你也可以传入一个Authors数组或集合,并返回自动生成的主键。

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username, password, email, bio) values
  <foreach item="item" collection="list" separator=",">
    (#{item.username}, #{item.password}, #{item.email}, #{item.bio})
  </foreach>
</insert>

3.4 sql

  这个元素可以被用来定义可重用的 SQL 代码段,可以包含在其他语句中。比如:

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    inner join some_table t2
</select>

3.5 参数(Parameters)

  这不是一个顶级元素,但很重要。
  默认情况下,使用#{}格式的语法会导致 MyBatis 创建预处理语句属性并安全地设置值(比如?)。这样做更安全,更迅速,通常也是首选做法。
比如:

<insert id="insertUser" parameterType="User">
  insert into users (id, username, password)
  values (#{id}, #{username}, #{password})
</insert>

  如果 User 类型的参数对象传递到了语句中,id、username 和 password 属性将会被查找,然后将它们的值传入预处理语句的参数中。你也可以

#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}

  但其实你只需要简单指定属性名,其他的事情 MyBatis 会自己去推断,最多你需要为可能为空的列名指定 jdbcType。

#{firstName}
#{middleInitial,jdbcType=VARCHAR}

字符串替换
  不过有时你只是想直接在 SQL 语句中插入一个不改变的字符串。比如,像 ORDER BY,你可以这样来使用:

ORDER BY ${columnName}

  这里 MyBatis 不会修改或转义字符串。
  **注意:这种方式是不安全的,会导致潜在的 SQL 注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验(即将输入中的特殊字符转义处理,比如"&"→ "&", "<"→"<"," "→"  "。

3.6 Result Maps

  resultMap 元素是 MyBatis 中最重要最强大的元素。它就是让你远离 90%的需要从结果 集中取出数据的 JDBC 代码的那个东西。 ResultMap 的设计就是简单语句不需要明确的结果映射,而很多复杂语句只需要描述它们的关系。
  当应用程序使用 JavaBeans 或 POJOs(Plain Old Java Objects,普通 Java 对象)来作为领域模型,大部分可以省略 resultMap,MyBatis 会在幕后自动创建一个 ResultMap,基于属性名来映射列到 JavaBean 的属性上。
  要记住类型别名是你的伙伴。使用它们你可以不用输入类的全路径。

<!-- In mybatis-config.xml file -->
<typeAlias type="com.someapp.model.User" alias="User"/>

<!-- In SQL Mapping XML file -->
<select id="selectUsers" resultType="User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

解决列名不匹配的两种方式

第一种:

<select id="selectUsers" resultType="User">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password     as "hashedPassword"
  from some_table
  where id = #{id}
</select>

第二种:

<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="user_name"/>
  <result property="password" column="hashed_password"/>
</resultMap>

  引用它的语句使用 resultMap 属性就行了(注意我们去掉了 resultType 属性)。比如:

<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password
  from some_table
  where id = #{id}
</select>
ResultMap Attributes
属性| 描述 --- | ----- id |当前命名空间中的一个唯一标识,用于标识一个result map. type |类的全限定名, 或者一个类型别名 autoMapping |如果设置这个属性,MyBatis将会为这个ResultMap开启或者关闭自动映射。这个属性会覆盖全局的属性autoMappingBehavior。默认值为:unset。

id & result

<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>

  这些是结果映射最基本内容。id 和 result 都映射一个单独列的值到简单数据类型(字符 串,整型,双精度浮点数,日期等)的单独属性或字段。

  唯一不同是 id 表示的结果将是当比较对象实例时用到的标识属性,类似于主键。这帮助来改进整体表现,特别是缓存和嵌入结果映射(也就是联合映射) 。

Id and Result Attributes
属性| 描述 --- | ----- property |映射到列结果的字段或属性。比如,你可以这样映射一些东西: “username” ,或者映射到一些复杂的东西: “address.street.number” 。 column |从数据库中得到的列名,或者是列名的重命名标签。 javaType|一个 Java 类的完全限定名,或一个类型别名。如果你映射到一个JavaBean,MyBatis 通常可以断定类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证所需的行为。 jdbcType |**JDBC 类型仅仅需要对插入,更新和删除操作可能为空的列进行设置**。 typeHandler |使用这个属性,你可以覆盖默认的类型处理器。这个属性值是类的完全限定名或者是一个类型处理器的实现,或者是类型别名。

3.7 Result Maps高级用法

1.首先,我们先看看一个常见的博客页面的组成,如下:
  a.页面上能够展示的部分:正文,标题,日期,作者,评论正文,评论时间,评论人等等
  b.页面之外的部分:用户名,用户id,用户密码,用户基本信息(电话,邮箱,地址,兴趣,特长,等等)
2.将我们页面上的信息从数据库中查出来的SQL语句转化为Mapper文件中的语句,可能是如下内容:

<!-- Very Complex Statement -->
<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>

其对应着非常复杂的结果集合,Mapper文件可能长这个样子,(注意当我们在select语句中使用B.title as blog_title,在resultMap的<result property="title" column="blog_title"/> 可以不设,系统会自动映射生成<result property="title" column="blog_title"/>,但是加上更清晰,也不会增加系统负担)如下:

<!-- Very Complex Result Map -->  
<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"/> 
    <!-- 由于自动映射等级为:PARTIAL,自动的映射author_id;同时注意上面对Author类已经映射过了, -->  
    <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>  

  注意上面是以Blog为中心,来找对应的作者,对应的文章,对应的评论和标签。下面,开始详细说明每一个元素,请读者一定按照单元测试的方法推进,千万不要一次性配置大量属性,以免影响学习兴趣。

3.7.1 构造方法

<constructor>  
   <idArg column="id" javaType="int"/>  
   <arg column="username" javaType="String"/> 
   <arg column="age" javaType="_int"/> 
</constructor>  

  尽管对于大部分的数据传输对象(DTO)对象,以及我们的domain模型,属性值都是能够起到相应的作用,但是,在某些情况下如我们想使用一些固定的类。比如:表格中包括一些仅供浏览的数据或者很少改变的数据。Mybatis的构造函数注入功能允许我们在类初始化时就设置某些值,而不暴露其中的public方法。
例如,程序中我们存在这样一个实体类,如下:

public class User {
   //...
   public User(Integer id, String username, int age) {
     //...
  }
//...
}

  在Mybatis中,为了向这个构造方法中注入结果,Mybatis需要通过它的参数来表示构造方法。java中,没有反射参数名称的方法,因此,当创建一个构造方法的元素时,必须保证参数是按照顺序排列的,而且,数据类型也必须匹配!

3.7.2 关联

<association property="author" column="blog_author_id" javaType="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
</association>

  关联元素用来处理数据模型中的“has-one”关系。比如一个博客账号只能属于一个用户。关联映射大部分是基于这种应用场景。关联中不同的是你需要告诉 MyBatis 如何加载关联。MyBatis 在这方面会有两种不同的方式:

  • 嵌套查询:通过执行另外一个 SQL 映射语句来返回预期的复杂类型。
  • 嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集。

关联的嵌套查询:即分别执行sql语句,一个sql语句的执行依赖于另外一条语句的结果,比如:

<resultMap id="blogResult" type="Blog">
  <association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectAuthor" resultType="Author">
  SELECT * FROM AUTHOR WHERE ID = #{id}
</select>

  我们有两个查询语句:一个来加载博客,另外一个来加载作者,而且博客的结果映射描 述了“selectAuthor”语句应该被用来加载它的 author 属性。其他所有的属性将会被自动加载,假设它们的列和属性名相匹配。
  这种方式很简单, 但是对于大型数据集合和列表将不会表现很好。 问题就是我们熟知的 “N+1 查询问题”。比如我们需要获得4个作者对应的博客列表,按照嵌套查询的方法:

select * from BLOG; 
select * from BLOG where Author_ID=1;
select * from BLOG where Author_ID=2;
select * from BLOG where Author_ID=3;
select * from BLOG where Author_ID=4;

  select语句的数目太多,需要频繁的访问数据库,会影响检索性能。如果需要查询n个作者,那么必须执行n+1次select查询语句。这就是经典的n+1次select查询问题。 这种检索策略没有利用SQL的连接查询功能,例如以上5条select语句完全可以通过以下1条select语句来完成:

select * from BLOG left outer join Author on BLOG.Author_ID=AUTHOR.Author_ID 

关联的嵌套结果
使用嵌套结果来联合查询,比如左连接,右连接,内连接等。比如:

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

  非常重要: id元素在嵌套结果映射中扮演着非常重要的角色。你应该总是指定一个或多个可以唯一标识结果的属性。实际上如果你不指定它的话, MyBatis仍然可以工作,但是会有严重的性能问题。在可以唯一标识结果的情况下, 尽可能少的选择属性。主键是一个显而易见的选择(即使是复合主键)。
  现在,上面的示例用了外部的结果映射元素来映射关联。这使得 Author 结果映射可以重用。然而,如果你不需要重用它的话。你可以嵌套结果映射:

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

  如果blog有一个co-author怎么办? select语句将看起来这个样子:

<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    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,
    CA.id           as co_author_id,
    CA.username     as co_author_username,
    CA.password     as co_author_password,
    CA.email        as co_author_email,
    CA.bio          as co_author_bio
  from Blog B
  left outer join Author A on B.author_id = A.id
  left outer join Author CA on B.co_author_id = CA.id
  where B.id = #{id}
</select>

  再次调用Author的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>

  当连接多表时,你将不得不使用列别名来避免ResultSet中的重复列名。指定columnPrefix允许你映射列名到一个外部的结果集中。你需要指定columnPrefix去重用映射resultMap。

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author"
    resultMap="authorResult" />
  <association property="coAuthor"
    resultMap="authorResult"
    columnPrefix="co_" />
</resultMap>

  上面已经看到了如何处理“has-one”类型关联。但是“has-many”是怎样的,比如一个作者只有一个博客账号,但是却有多篇文章?下面这个部分就是来讨论这个主题的。

3.7.3 集合

<collection property="posts" ofType="domain.blog.Post">
  <id property="id" column="post_id"/>
  <result property="subject" column="post_subject"/>
  <result property="body" column="post_body"/>
</collection>

  一个作者有很多文章,那么结果返回的接口写法如下:

private List<Post> posts;

  集合也有嵌套查询和嵌套结果,我们掌握后者:

<select id="selectBlog" resultMap="blogResult">
  select
  B.id as blog_id,
  B.title as blog_title,
  B.author_id as blog_author_id,
  P.id as post_id,
  P.subject as post_subject,
  P.body as post_body,
  from Blog B
  left outer join Post P on B.id = P.blog_id
  where B.id = #{id}
</select>

我们联合了博客表和文章表,每个博客账号有多篇文章,和association相比唯一不同点在ofType="Post":

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <result property="body" column="post_body"/>
  </collection>
</resultMap>

下面的写法方便重用:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>

<resultMap id="blogPostResult" type="Post">
  <id property="id" column="id"/>
  <result property="subject" column="subject"/>
  <result property="body" column="body"/>
</resultMap>

3.7.4 鉴别器

<resultMap id="vehicleResult" type="Vehicle">
  <id property="id" column="id" />
  <result property="vin" column="vin"/>
  <result property="year" column="year"/>
  <result property="make" column="make"/>
  <result property="model" column="model"/>
  <result property="color" column="color"/>
  <discriminator javaType="int" column="vehicle_type">
    <case value="1" resultType="carResult">
      <result property="doorCount" column="door_count" />
    </case>
    <case value="2" resultType="truckResult">
      <result property="boxSize" column="box_size" />
      <result property="extendedCab" column="extended_cab" />
    </case>
    <case value="3" resultType="vanResult">
      <result property="powerSlidingDoor" column="power_sliding_door" />
    </case>
    <case value="4" resultType="suvResult">
      <result property="allWheelDrive" column="all_wheel_drive" />
    </case>
  </discriminator>
</resultMap>

  有时一个单独的数据库查询也许返回很多不同 (但是希望有些关联) 数据类型的结果集。鉴别器元素就是被设计来处理这个情况的, 还有包括类的继承层次结构。 鉴别器非常容易理 解,因为它的表现很像 Java 语言中的 switch 语句。

3.7.5 自动映射

  在简单的场景下,MyBatis可以替你自动映射查询结果。 如果遇到复杂的场景,你需要构建一个result map。当自动映射查询结果时,MyBatis会获取sql返回的列名并在java类中查找相同名字的属性(忽略大小写)。 这意味着如果Mybatis发现了ID列和id属性,Mybatis会将ID的值赋给id。
  通常数据库列使用大写单词命名,单词间用下划线分隔;而java属性一般遵循驼峰命名法。 为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase设置为true。
  自动映射的功能也能够在特殊的resultMap下继续工作。在这种情况下,对于每一个结果映射的集合,所有出现在结果集当中的列,如果没有被手动的设置映射,那么它都会被自动的映射。 在接下来的例子中, id 和 userName列将被自动映射, hashed_password 列将根据配置映射。

<select id="selectUsers" resultMap="userResultMap">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password
  from some_table
  where id = #{id}
</select>
<resultMap id="userResultMap" type="User">
  <result property="password" column="hashed_password"/>
</resultMap>

有三种自动映射等级

NONE - 禁用自动映射。仅设置手动映射属性。
PARTIAL - 会自动的映射结果,除了那些定义在内部的已经存在嵌套的映射(默认)
FULL - 自动映射所有(但当不同表有相同的列名时容易出错,别用)。
通过添加autoMapping属性可以忽略自动映射等级配置,你可以启用或者禁用自动映射指定的ResultMap。

<resultMap id="userResultMap" type="User" autoMapping="false">
  <result property="password" column="hashed_password"/>
</resultMap>

3.8 缓存

  mybaits提供一级缓存,和二级缓存。

  一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。但如果开启了二级缓存,那么在关闭sqlsession后,会把该sqlsession一级缓存中的数据添加到namespace的二级缓存中。
  对sqlsession执行commit操作,也就意味着用户执行了update、delete等操作,那么数据库中的数据势必会发生变化,如果用户请求数据仍然使用之前内存中的数据,那么将读到脏数据。所以在执行sqlsession操作后,会清除保存数据的HashMap,用户在发起查询请求时就会重新读取数据并放入一级缓存中了。
如何开启二级缓存:

1、 在mybatis总配置文件中加入一行设置
<settings>
   <!--开启二级缓存-->
    <setting name="cacheEnabled" value="true"/>
</settings>

2、在需要开启二级缓存的mapper.xml中加入caceh标签
<cache/>

  二级缓存是mapper级别的缓存,按namespace分,如果namespace相同则使用同一个相同的二级缓存区,多个SqlSession去操作数据库得到数据会存在二级缓存区域。注意:即使开启了二级缓存,不同的sqlsession之间的缓存数据也不是想互访就能互访的,必须等到sqlsession关闭了以后,才会把其一级缓存中的数据写入二级缓存。

但是我们并不使用mybaits提供的二级缓存,理由如下:

  1. 缓存是以namespace为单位的,不同namespace下的操作互不影响。例如UserMapper.xml包含一个命名空间,所有针对user表的insert,update,delete操作都在这个命名空间下。假设另外一个XXXMapper.xml对user表的内容进行select查询,并将查询结果二级缓存。然后我们对user表进行insert,update或者delete操作,insert,update,delete操作会清空所在namespace下的全部缓存,但是XXXMapper.xml命名空间下的缓存却没有变化,导致XXXMapper.xml再次查询错误。
  2. 多表操作不能使用缓存。比如我要查询某一个作者的全部文章,作者为一张表,文章为一张表。首先,对不同表的增删改一般放到不同的namespace,原因是:假设我将多表的全部操作放到一个namespace,那么我对任意一张表的增删改都会触发清空这个namespace的全部缓存,导致缓存一直在变,那我就要一直查表,那要缓存也没意义了。然后,假设将查询某一个作者的全部文章这一操作放到作者所在的那个namespace,那么文章表的增删改由于和查询某一个作者的全部文章这一操作不在同一namespace,导致这一操作的二级缓存不变,查询错误。
      所以放弃Mybatis二级缓存,在业务层使用可控制的二级缓存代替更好,即服务器上的缓存推荐使用redis或者ehcache做分布式二级缓存
posted @ 2017-05-02 16:02  何必等明天  阅读(11001)  评论(0编辑  收藏  举报