通过上一章我们学习了通过mybatis框架实现增删改查的操作,这一章我们主要学习mybatis的进阶级语法,包括动态SQL、缓存、关联查询,以及通过工具生成一个数据库表在Java对应的实体类、mapper接口、mapper配置文件和Spring Mybatis的整合等。

动态SQL

if标签

通过if标签来判断传的参数是否为空,然后拼接到SQL语句上,示例:

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = 'ACTIVE'
  <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字段也是动态拼接的,那这里就有问题了,比如我三个条件都没有时,拼出来的sql语句就是SELECT * FROM BLOG WHERE显然是无法执行的,这就要用到我们的where标签

choose...when...otherwise标签

有时我们不想应用到所有的条件语句,而只想从中择其一项。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

使用示例:

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

tirm,where,set标签

<where>标签

当我们拼接动态SQL时,如果一个查询条件都没有,那我们就不需要where子句,而如果有至少一个条件我们就需要where子句。这样,我们就需要做个判断,而mybatis里的标签就省去了我们自己做这个判断。 使用示例:

<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>
  </where>
</select>

当一个查询条件都没有拼接时, mybatis会自动将where关键字和拼接多个条件之间的诸如AND、OR这些多余的关键字去掉

<set>标签

set 元素会动态前置 SET 关键字,同时也会删掉无关的逗号(如:语句最后的逗号)
使用示例:

<update id="updateAuthorIfNecessary">
  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}
</update>

<trim>标签

trim标签可以
常用属性有:

  • prefix: 添加指定前缀
  • prefixOverrides: 删除指定前缀
  • suffixOverrides: 删除指定后缀
    示例一:用标签实现标签功能
<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

prefixOverrides的作用是移除字符串开头的内容中所有指定在 prefixOverrides 属性中的内容,并且插入 prefix 属性中指定的内容。

示例二: 用标签实现标签功能

<!-- 以下内容实现了动态的拼接更新的SQL操作 -->    
<update id="updateById" parameterType="BookType">
	update booktype
	<trim prefix="set" suffixOverrides=",">
        <if test="id != null">
            id = #{id},
        </if>
        <if test="tname != null">
            tname = #{tname},
        </if>
	</trim>
	where id = #{id}
</update>

注意这里我们删去的是后缀值,同时添加了前缀值。

foreach标签

foreach标签用于通过循环的方式动态拼接SQL,该标签的属性有collectionitemindex,这三个下面重点介绍,而open属性是在循环开始时添加我们要加的字符串,separator属性则是循环中间添加字符串,close属性是循环结束时添加字符串。使用方法如下:

<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>
  • item: 指定在循环中可以用的item属性名称。当循环的对象是List、Set、数组时item是当前迭代的对象的元素值;而当循环对象是Map时,item是key对应的值
  • index: 指定在循环中可以用的index属性名称。当循环的对象是List、Set、数组时index是当前迭代的次数;而当循环对象是Map时,index是key

迭代集合类型参数

java代码:

int batchInsertByNormal(List<Teacher> teacherList);

xml中迭代

<insert id="batchInsertByNormal" parameterType="Teacher">
    insert into teacher (tname, age) values
    <!-- 迭代list类型参数时,collection的值写list, 这时index就是迭代次数,item是迭代的元素 -->        
    <foreach collection="list" index="idx" item="teacher" separator="," close=";">
             (#{teacher.tname}, #{teacher.age})
    </foreach>
</insert>

迭代数组类型参数(和迭代集合类型类似)

java代码:

int batchInsertByNormal(List<Teacher> teacherList);

xml代码:

<insert id="batchInsertByNormal" parameterType="Teacher">
    insert into teacher (tname, age) values
    <!-- 迭代数组类型参数时,collection的值写array, 这时index就是迭代次数,item是迭代的元素 -->        
    <foreach collection="array" index="idx" item="teacher" separator="," close=";">
             (#{teacher.tname}, #{teacher.age})
    </foreach>
</insert>

迭代Map类型参数

java代码:

void testForeachMap(@Param("data") Map<String, Integer> data);

xml代码:

<select id="testForeachMap" parameterType="hashmap">
    insert into teacher (tname, age) values
    <!-- 迭代map类型参数时,collection写接口中通过@Param注解指定的map参数名称,这时index就是map的key,item就是map的value -->
    <foreach collection="data" index="key" item="value" separator=",">
            (#{key}, #{value})
    </foreach>
</select>

缓存

一级缓存(本地缓存)

配置方法:

<setting name="localCacheScope" value="SESSION"/>

取值有两个:SESSION和STATEMENT分别对应缓存应用session会话范围和一次statement范围

  • MyBatis一级缓存的生命周期和SqlSession一致。
  • MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能上有所欠缺。
  • MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。

二级缓存

开启二级缓存的方法

第一步,打开全局二级缓存开关

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

在具体的Mapper中开启二级缓存

<cache/> 

可配置参数:

  • type:cache使用的类型,默认是
  • PerpetualCache,这在一级缓存中提到过。
  • eviction: 定义回收的策略,常见的有FIFO,LRU。
  • flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
  • size: 最多缓存对象的个数。
  • readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
  • blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。

二级缓存可以跨越不同的SqlSession之间,可以达到namespace级别(但必须是同一个SqlSessionFactory)

缓存查找顺序

二级缓存 --> 一级缓存 --> 查数据库

关联查询

一对一关联

一对一的关联需要在xml配置中的<resultMap>标签中使用<association>标签,示例:

<resultMap id="book" type="Book">
        <id column="b_id" property="id"/>
        <result column="bname" property="bname"/>
        <result column="author" property="author"/>
        <result column="author_gender" property="authorGender"/>
        <result column="price" property="price"/>
        <result column="description" property="description"/>
    	<!-- bookType属性是引用类型:BookType,一本书对应一种图书类型 -->
        <association property="bookType" javaType="BookType">
            <id column="bt_id" property="id"/>
            <result column="tname" property="tname"/>
        </association>
    </resultMap>

多对多关联

多对多的关联需要使用xml配置中的<collection>标签,示例:

    <resultMap id="stu" type="Student">
        <result property="id" column="id" />
        <result property="sname" column="sname" />
        <result property="age" column="age" />
        <result property="gender" column="gender" />
        <result property="nickName" column="nick_name" />
        <!-- book属性同样是引用类型Book,一个学生对应多本书 -->
        <collection property="book" ofType="Book">
            <id column="b_id" property="id"/>
            <result column="bname" property="bname"/>
            <result column="author" property="author"/>
            <result column="author_gender" property="authorGender"/>
            <result column="price" property="price"/>
            <result column="description" property="description"/>
            <!-- bookType属性是引用类型:BookType,一本书对应一种图书类型 -->
            <association property="bookType" javaType="BookType">
                <id column="bt_id" property="id"/>
                <result column="tname" property="tname"/>
            </association>
        </collection>
    </resultMap>

官方代码生成工具-Generator

命令行方式使用

命令行方式就需要mybatis-generator-core-1.3.7.jar包,以及一个xml配置文件,下面给出配置文件demo

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    
	<!-- 引入第三方依赖包 -->
	<classPathEntry location="D:\m2\maven repository\mysql\mysql-connector-java\5.1.47\mysql-connector-java-5.1.47.jar" />
	
	<!--
     targetRuntime常用值:
        MyBatis3Simple(只生成基本的CRUD和少量的动态SQL)
        MyBatis3(生成完整的CRUD,包含CriteriaAPI方法Example后缀的方法)
     -->
    <context id="localhost_mysql" targetRuntime="MyBatis3Simple">

        <!-- 不生成注解 -->
        <commentGenerator>
            <property name="suppressAllComments" value="true" />
        </commentGenerator>

        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/books?characterEncoding=utf8"
                        userId="root"
                        password="root">
        </jdbcConnection>

        <javaTypeResolver >
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

		<!-- 生成实体类 -->
        <javaModelGenerator targetPackage="com.spring.bean" targetProject="src/main/java">
            <property name="enableSubPackages" value="false" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

		<!-- 生成XML Mapper -->
        <sqlMapGenerator targetPackage="src/main/resources/mapper" targetProject=".">
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>

		<!-- 生成Mapper接口 -->
        <!-- 生成的Mapper类型:ANNOTATEDMAPPER(注解)、MIXEDMAPPER(混合)、XMLMAPPER(XML) -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.spring.mapper"  targetProject="src/main/java">
            <!-- 是否将数据库中的schema作为包名的一部分,默认就是false -->
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>
        
        <table schema="books" tableName="book" domainObjectName="BookDO"
               enableCountByExample="false">
            <!-- 是否用数据库中的字段名作为POJO属性名(不自动转小驼峰),默认值是false -->

            <!--
            <property name="useActualColumnNames" value="true"/>
            -->
            <!-- 生成代码时支持获取插入数据后自增的ID, 需要通过sqlStatement配置数据库类型。 -->

            <generatedKey column="id" sqlStatement="mysql" identity="true" />

            <!-- 此标签用于在生成代码时忽略数据库中的某个字段 -->
            <!--
            <ignoreColumn column="FRED" />
            -->
            <!-- 通过此标签重写mybatis从数据库读到的元信息,自定义列相关配置,包括(名称、类型) -->
            <!--
            <columnOverride column="aa" property="sname" />
            -->
        </table>
    </context>
</generatorConfiguration>

然后就可以使用下面的命令来生成代码了(配置文件名为:generatorConfig.xml)

java -jar mybatis-generator-core-1.3.7.jar -configfile generatorConfig.xml -overwrite

echo "SUCCESS"

注:如果不存在src\main\java这三个文件夹,那么就不会生成bean实体类,因此如果设置了生成的代码放置的文件夹,那么就需要创建它们。

为了避免这种情况,我们可以在运行jar包之前先通过命令创建需要的文件夹

mkdir src\main\java

java -jar mybatis-generator-core-1.3.7.jar -configfile generatorConfig.xml -overwrite

echo "SUCCESS"

我们可以通过将上面的命令先写在txt中,然后存储为.bat格式,这种格式可以在windows平台直接运行,这样就不需要每次都有输入上面的命令。如果在Linux系统则可以存储为.sh格式,且创建文件夹的命令要加一个-p参数,如:mkdir -p src\main\java

maven方式使用

首先要在pom.xml文件中添加一个插件

    <build>
        <plugins>
            <!-- 引用插件 -->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
            </plugin>
        </plugins>

        <pluginManagement>
            <plugins>
                <!-- 生成实体类、mapper等代码的插件 -->
                <plugin>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-maven-plugin</artifactId>
                    <version>1.3.7</version>
                    <executions>
                        <execution>
                            <id>Generate MyBatis Artifacts</id>
                            <goals>
                                <goal>generate</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <!-- 指定自定义配置文件(代码生成工具)路径,配置文件默认会在resources文件夹下找generatorConfig.xml,如果换了地方(名字)需要在下面标签中指定 -->
                        <!-- <configurationFile>${basedir}/src/main/resources/mybatis/generatorConfig.xml</configurationFile> -->
                        <includeCompileDependencies>true</includeCompileDependencies>
                        <overwrite>true</overwrite>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

将插件引入项目中后,还需要在resources文件夹下添加一个名为generatorConfig.xml的配置文件,这个配置文件与命令行方式的配置文件基本一致,只是不再需要引用jar包,因为我们已经在pom.xml文件中对插件授权了共享项目中引入的依赖,因此不引用jar包的前提是项目中已经添加了数据库驱动的依赖。

Spring MyBatis整合

概述

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。

使用示例

  1. 添加依赖包
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
</dependency>
  1. 配置数据源
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="username" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
  1. 配置Spring、Mybatis整个的管理Bean
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!-- 指定XML Mapper文件的路径 -->
    	<property name="mapperLocations" value="classpath:/mapper/*"/>
    </bean>
  1. 通过mybatis scheme来自动扫描所有的Mapper接口
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
        http://mybatis.org/schema/mybatis-spring
        http://mybatis.org/schema/mybatis-spring.xsd">
    
    <!-- 自动扫描所有Mapper接口 -->
    <mybatis:scan base-package="com.lanou3g.spring.mapper" />
</beans>
  1. 使用

经过上面的几步配置后, 我们就可以直接从Spring的IOC容器中获取Mapper操作接口了

public class AppTest {
    
    /**
     * 测试mybatis-spring
     */
    @Test
    public void testMyBatisAndSpring(){
        // 1. 初始化IOC容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 2. 从容器中获取Service对象(Mapper对象已经自动注入到Service中了)
        MessageServiceImpl messageService = ctx.getBean(MessageServiceImpl.class);
        // 3. 执行查询所有方法
        List<Message> messageList = messageService.queryAll();
        log.info("" + messageList);
    }
}