mybatis java的持久化框架,和直接使用JDBC的区别是:
使用JDBC需要在代码中写sql语句,返回类型等只能通过JDBC方法来确定,耦合程度高。
mabatis通过约定的mapper接口和config xml来将sql的配置和使用隔离开,清晰的返回类型,方便的连接数据库等等。
本文将就mybatis常用的功能说,更多的关于mybatis的配置请查阅官网
resource文件夹下新建mybatis-config.xml,主要是配置在各种环境下通过什么策略来连接数据库(比如dev环境和生产环境的数据库是不同的),以及Mapper类的相应config信息
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.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="****"/>
<property name="password" value="****"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="dao/UserMapper.xml"/>
mappers>
configuration>
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
properties>
配置好了properties后,在xml的配置文件中可以使用${}来获取相应的属性,也是将配置和使用隔离开的模式
例如
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
package utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static SqlSession openSqlSession() {
return sqlSessionFactory.openSession();
}
}
package dao;
import java.util.List;
public interface UserMapper {
List<User> getUsers();
User getUserByName(String name);
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dao.UserMapper">
<select id="getUsers" resultType="dao.User">
select * from mybatis.User
select>
<select id="getUserByName" resultType="dao.User">
select * from mybatis.User where name = #{name}
select>
mapper>
SqlSession sqlSession = MybatisUtil.openSqlSession();
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User users = mapper.getUserByName("A");
System.out.println(users);
// users.stream().forEach(System.out::println);
}
finally {
sqlSession.close();
}
可以看出mybatis在简单的增删改查中主要就是配置,首先配置怎么连接数据库,然后读取数据库配置并返回相应Java 连接对象,再通过配置好的Mapper调用相应的方法就可以实现和数据库的交互,整体上实现了配置和使用的分离。mybatis的配置只要一次,Mapper的配置是简单的,多一个sql语句就多一行的配置
为java类型创建别名以简化xml配置的书写
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
typeAliases>
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
mappers>
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
mappers>
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
mappers>
<mappers>
<package name="org.mybatis.builder"/>
mappers>
使用一个持久层框架时,很关键的一点就是为了实现数据库到内存对象的映射(否则为什么不直接print出来呢),mybatis能够流行的重要原因就是其内置的表项的映射非常友好
mybatis支持如下的映射方式:
把resultMap放在前面是因为复杂的映射仍然需要用resultMap实现,而简单的映射已经可以使用mybatis3中的java注解来实现
resultMap功能强大,可以将非常复杂的表正确的映射到内存对象中
resultMap中存在以下关键字段:
constructor - 用于在实例化类时,注入结果到构造方法中
idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
arg - 将被注入到构造方法的一个普通结果
id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
result – 注入到字段或 JavaBean 属性的普通结果
association – 一个复杂类型的关联;许多结果将包装成这种类型
嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
collection – 一个复杂类型的集合
嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
discriminator – 使用结果值来决定使用哪个 resultMap
case – 基于某些值的结果映射
其中constructor, id, result都是非常简单的字段
其通用的匹配方式如下
<result,id,idArg,arg property="*" column="*">
其中property表明了其在java对象中的字段名,column表示其在表中的字段名
而association表明了一个复杂的类型,通常需要进行更进一步的拆分,例如如下的询问
<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 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就应该如下,其中association就需要用若干个字段组装起来
<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>
如果觉得resultMap重新列出来非常麻烦,可以使用如下语法,可以不用列出resultMap
<association property="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>
难以想象这种东西在JDBC里面要写多少行,为了将映射关系隔离出来,你可能会需要创建一些工具类以避免代码的耦合,在这些工具类中还需要进行仔细的字段传递,而mybatis很好的解决了这些问题
一个鉴别器可以如下
<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" resultMap="carResult"/>
<case value="2" resultMap="truckResult"/>
<case value="3" resultMap="vanResult"/>
<case value="4" resultMap="suvResult"/>
discriminator>
resultMap>
鉴别器的作用类似于switch case,可以将表项根据某些字段的结果进行不同对象的封装
mybatis的自动映射将获取到的表和java对象字段进行忽略大小写的,忽略驼峰和蛇形命名法的匹配。有三种自动映射等级:
NONE:禁用自动映射
PARTIAL:对除了连接的属性进行映射
FULL:全自动映射
默认值为PARTIAL,PARTIAL和FULL的区别如下:
<select id="selectBlog" resultMap="blogResult">
select
B.id,
B.title,
A.username,
from Blog B left outer join Author A on B.author_id = A.id
where B.id = #{id}
select>
<resultMap id="blogResult" type="Blog">
<association property="author" resultMap="authorResult"/>
resultMap>
<resultMap id="authorResult" type="Author">
<result property="username" column="author_username"/>
resultMap>
由于表中存在id字段,而Auther对象中也存在id字段,因此这两者进行了自动映射,而这显然是不符合我们预期的,因此一般来说用PARTIAL即可
而resultType映射就相当于只是指定了一个java对象并让mybatis进行自动映射
常用的映射注解:
@Property
@ConstructorArgs @Arg
@TypeDsicriminator @Case
@Results @Result
@MapKey
@Insert @Update @Delete @Select
@InsertProvide @UpdateProvider @DeleteProvider @SelectProvider
@ResultMap @ResultType @SelectKey
后端和数据库的交互通常需要附加不同的查询条件
通过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>
类似于switch case语句选择sql语句进行执行
<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>
当where从句中不存在任何一个结果必定返回时,使用上面的if,choose可能都会很尴尬,但是where标签可以自动去掉最前面的AND,并且只有当子结果中返回至少一个结果时加入WHERE sql语句
<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>
类似的解决方案也有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>
如果where和set的行为无法满足要求,可以用定制的trim语句,对where的定制如下,prefix表示满足某个条件时加入的sql语句,prefixoerrides表示要删掉的前缀,文档提到这里的AND 必须要加空格,应该是为了满足管道分隔符的格式
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
trim>
foreach主要是用来判断表项是否在某个集合中的,其中item和index用来描述java对象的内容和下标,collection表示集合类型,open后的内容指定sql的内容,包括
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
foreach>
where>
select>