MyBatis详解(二)
通过上一章我们学习了通过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,该标签的属性有collection
,item
,index
,这三个下面重点介绍,而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。
使用示例
- 添加依赖包
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</dependency>
- 配置数据源
<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>
- 配置Spring、Mybatis整个的管理Bean
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 指定XML Mapper文件的路径 -->
<property name="mapperLocations" value="classpath:/mapper/*"/>
</bean>
- 通过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>
- 使用
经过上面的几步配置后, 我们就可以直接从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);
}
}