MyBatis

MyBatis 持久层

  • 对JDBC的封装
  • 将sql语句放在映射文件中(xml)
  • 自动将输入参数映射到sql语句的动态参数上
  • 自动将sql语句执行的结果映射成java对象

MyBatis配置

  1. 需要导入mybatis包
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.1</version>
</dependency>
  1. 创建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="db.properties"/>
      <!-- 用于定义类型别名 -->
      <typeAliases>
          <package name="com.xm.pojo"/>
      </typeAliases>

    <!--定义数据源-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--定义映射文件-->
    <mappers>
    <!--<mapper resource="com.xm.mybatis.mapper.BlogMapper.xml"/>-->
        <package name="com.xm.mybatis.mapper"/>
    </mappers>
</configuration>

相关配置说明

  1. enviironments用于配置多种运行环境
  2. default指定默认的环境id
  3. 每个environment元素可以定义自己的环境id
  4. transactionManager是事务管理器,MyBatis中有两种类型的事务管理器
  • JDBC 这个配置直接使用了JDBC的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域
  • MANAGER这个配置几乎什么都没做,它从来不提交或者回滚一个连接,而是让容器来管理事务的整个生命周期
  1. datasource配置数据源,type有三种类型可选
  • UNPLOOLED:每次请求时都打开和关闭连接
  • POOLED:使用连接池,可以设置poolMaximumActiveConnection最大连接数量;poolMaximumIdleConnections最大空闲数;poolMaximumCheckoutTime在被强制返回之前连接超时时间,默认20000毫秒(20秒);poolTimeWait超时时间(默认20秒)等等
  • JNDI
  1. mappers映射器,用于告诉MyBatis去哪里寻找sql映射语句,可以使用类路径,完全限定资源定位符(URL),类名,包名等
<mappers>	
    <!-- 使用相对类路径的资源引用 -->
    <mapper resource="com.xm.mybatis.xxxMapper.xml"/>
    <!-- 使用完全限定资源定位符(URL)-->
    <mapper url="file:///var/mappers/xxxMapper.xml"/>
    <!-- 使用类名 -->
    <mapper class="com.xm.mybatis.xxxMapper"/>
    <!--将包内的所用内容注册为映射器 -->
    <package name="com.xm.mybatis"/>
</mappers>
  1. typeAliases用于配置类型别名,即对实体类进行命名,方便后面在配置映射文件时指定具体实体类,可以指定具体类或指定一个包的方式进行配置
<!-- 通过指定具体类型 -->
<typeAliases>
      <typeAliase alias="Blog" type="com.xm.pojo.Blog"/>
</typeAliases>
<!-- 指定包名,MyBatis会在包名下搜索,默认别名为类名 -->
<typeAliases>
    <package name="com.xm.pojo"/>
</typeAliases>

在Mybatis中,已经封装了一些常见的类型别名:
int表示integer
_int表示int

映射文件的配置

<?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.xm.mapper.BlogMapper">
      <!-- 需要创建相应的接口,id的值为接口方法名 -->
    <select id="selectBlog" parameterType="int" resultType="Blog">
        select * from Blogs where id = #{id}
    </select>
</mapper>

对应的接口

package com.xm.mapper;

import com.xm.pojo.Blog;

public interface BlogMapper {
    public Blog selectBlog(int id);
}

构建SqlSessionFactory

每个基于MyBatis的应用都需要以一个SqlSessionFactory的实例为核心,SqlSessionFactoryd的实例可以通过SqlSessionFactoryBuilder获得,通过SqlSesstionFactory就可以获得sqlSession实例,该实例包含了面向数据库执行SQL命令所需的所有方法,通过sqlSession可以获取指定的mapper映射类
具体使用方法如下:

package com.xm.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 = null;
    static {
        try {
              //加载mybatis配置文件
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
              //获取sqlSessionFactory
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public MyBatisUtil() {
    }

    public static SqlSession sqlSession(){
          //获取sqlSession实例
        return sqlSessionFactory.openSession();
    }
}

测试案例

/**
* 需要创建BlogMapper接口的方式 
*/
@Test
public void testSelectBlog(){
    SqlSession sqlSession = MyBatisUtil.sqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Blog blog = mapper.selectBlog(1);//执行映射文件id为selectBlog的方法
    System.out.println(blog);
    sqlSession.close();
}

/**
* 不需要创建BlogMapper的方式
*/
@Test
public void testSelectBlogNoInterface(){
    SqlSession sqlSession = MyBatisUtil.sqlSession();
      //指定到BlogMapper.xml配置的命名空间
    Blog blog = sqlSession.selectOne(
            "com.xm.mapper.BlogMapper.selectBlog",1);
    sqlSession.close();
    System.out.println(blog);
}

mapper相关使用细节

列名与属性名不一致的情况

当对类中的某个属性定义的属性名与所对应的数据表的字段名不一致时,需要手动配置指定对应字段名,否则会读取不到数据
配置字段对应类属性名的方式:

  1. 在select语句中对字段起别名
<select id="selectBlog" parameterType="int" resultType="Blog">
    select id,title,author_id as authorId,featured,style from Blogs where id = #{id}
</select>
  1. 配置resultMap:在mapper配置文件中,添加resultMap节点,同时在statement中指定resultMap.
<mapper namespace="com.xm.mapper.BlogMapper">
    <resultMap id="blogResultMap" type="Blog">
        <id column="id" property="id" javaType="INTEGER"/>
        <result column="author_id" property="authorId" javaType="INTEGER"/>
    </resultMap>

    <select id="selectBlog2" parameterType="int" resultMap="blogResultMap">
        select * from blogs where id = #{id}
    </select>
</mapper>

模糊查询#和$

#表示的是占位符,即会使用?符号进行预处理,而$表示的是字符串拼接符,会直接替换内容
当使用$的时候,如果要设置其值(非列名和表名的情况),只能使用value作为参数,但是会有sql注入问题,一般不建议这样使用

<select id="selectBlogByTitle" parameterType="string" resultMap="blogResultMap">
    select * from blogs where lower(title) like lower(#{title})
</select>

<select id="selectBlogByTitle2" parameterType="string" resultMap="blogResultMap">
    select * from blogs where lower (title) like lower (${value})
</select>

查询排序

对于查询中使用列名,表名的情况,只能使用$,使用#没有效果

<select id="selectBlogBySort" parameterType="string" resultMap="blogResultMap">
      <!-- convert函数用于指定字符编码,针对中文排序的情况 -->
    select * from blogs order by convert(${value} using gbk);
</select>

多参数传递

可以有多种方法进行多参数传递

  1. 使用索引:按照参数排序,arg0开始,param1开始
<select id="selectBlogByPage" resultMap="blogResultMap">
    select * from blogs limit #{arg0},#{arg1}
</select>

<select id="selectBlogByPage" resultMap="blogResultMap">
    select * from blogs limit #{param1},#{param2}
</select>
  1. 使用注解:注解的value值要和mapper的占位参数名一致
<select id="selectBlogByPage2" resultMap="blogResultMap">
    select * from blogs limit #{offset},#{pagesize}
</select>

对应的接口:

List<Blog> selectBlogByPage2(
        @Param(value = "offset") int offset,
        @Param(value = "pagesize") int pagesize);
  1. 使用map
    mapper的配置保持不变,其中的占位符参数名要和测试中map的key一一对应
    接口:
List<Blog> selectBlogByPage3(Map<String,Object> map);

测试:

@Test
public void testSelectBlogByPage3(){
    SqlSession sqlSession = MyBatisUtil.sqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    //自己构建一个map
    Map<String,Object> map = new HashMap<String, Object>();
    //注意key要和参数名对应
    map.put("offset",0);
    map.put("pagesize",2);
    List<Blog> blogs = mapper.selectBlogByPage3(map);
    sqlSession.close();
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
}

插入和获取最新插入id

在mapeer中使用insert节点,insert方法返回的是该操作影响的行数

<insert id="insertBlog" parameterType="Blog" >
    insert into blogs (title, author_id, featured, style) value (#{title},#{authorId},#{featured},#{style})
</insert>

测试:

@Test
public void testInsertBlog(){
    SqlSession sqlSession = MyBatisUtil.sqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Blog blog = new Blog();
    int id = mapper.insertBlog(blog);//返回影响行数
    sqlSession.commit();//要进行提交
    System.out.println(id);
    sqlSession.close();
}

获取插入id的三种方式

  1. 在mapper中配置insert节点的属性

    useGeneratedKeys = true keyProperty=”id”

  • useGeneratedKeys仅对 insert 和 update 有用,这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。
  • keyProperty仅对 insert 和 update 有用)唯一标记一个属性,指定主键名是什么,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认值:未设置(unset)。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
<insert id="insertBlog" parameterType="Blog" useGeneratedKeys="true" keyProperty="id">
    insert into blogs (title, author_id, featured, style) value (#{title},#{authorId},#{featured},#{style})
</insert>

测试:

@Test
public void testInsertBlog(){
    SqlSession sqlSession = MyBatisUtil.sqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Blog blog = new Blog();
    System.out.println(blog);//blog的id属性尚未赋值
    int id = mapper.insertBlog(blog);//返回影响行数
    sqlSession.commit();//要进行提交
    System.out.println(id);
    System.out.println(blog);//在事务提交执行成功之后自动为对象的id属性赋值
    sqlSession.close();
}
  1. 在全局配置文件中配置settings节点,并且在mapper的insert节点指定主键名,即配置keyProperty=“id
<settings>
    <setting name="useGeneratedKeys" value="true"/>
</settings>
  1. (针对Oracle等无主键的数据库)在inseert节点中加入selectKey属性,其有一下变量属性:
    • keyProperty:selectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
    • keyColumn:匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
    • resultType:结果的类型。MyBatis 通常可以推断出来,但是为了更加精确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的 Object 或一个 Map。-
    • order:这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先生成主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。
    • statementType:STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
<insert id="insertBlog2" parameterType="Blog" keyProperty="id">
    <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
        select LAST_INSERT_ID()
    </selectKey>
    insert into blogs (title, author_id, featured, style) value (#{title},#{authorId},#{featured},#{style})
</insert>

修改

使用update节点

<update id="updateBlog" parameterType="Blog">
    update blogs set title=#{title},author_id=#{authorId},featured=#{featured},style=#{style} where id=#{id}
</update>

在实际应用中,如果只针对某个字段做修改,可以先查询获得实体类,再对实体类属性进行修改
为了进一步提高效率,可以使用动态sql的方式

删除

<delete id="deleteBlogById" parameterType="int">
    delete from blogs where id=#{id}
</delete>

动态sql

动态sql有以下几种类型

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

if

if提供了可选的查询方案,如果if中的条件成立,则执行if中的语句,否则只执行if外的sql语句

<select id="selectActiveBlogByTitle" parameterType="string" resultMap="blogResultMap">
    select * from blogs where featured=false
    <if test="_parameter != null">
        AND title like #{title}
    </if>
</select>

注意
当参数类型为string时,test中的属性名不能直接写参数名,否则会抛出异常:

There is no getter for property named ‘XXX’ in ‘class java.lang.String

解决办法

  1. 将test中的属性名更换为_parameter
  2. 加上单引号
  3. 可以在接口处的参数使用@param修饰
List<Blog> selectActiveBlogByTitle(
@Param(value = "title") String title);

choose(when,otherwise)

用于多个条件判断,当满足其中某个when条件时,sql语句加上其中的内容,否则只执行外部的内容

一旦某个when条件成立,就不会再判断下面的when语句了

<!-- 此时接收的参数为一个对象 -->
<select id="selectActiveBlogByTitleOrOther" parameterType="Blog" resultMap="blogResultMap">
    select * from blogs where featured=false
    <choose>
        <when test="title != null">
            AND title =#{title}
        </when>
        <when test="authorId != null">
            and author_id = #{authorId}
        </when>
        <when test="style">
            and style = #{style}
        </when>
        <otherwise>
            and id>0
        </otherwise>
    </choose>
</select>

where

如果希望where条件语句也在需要的时候才加入,可以使用where来进行判断。
where元素只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。而且,若语句的开头为“AND”或“OR”,where元素也会将它们去除

<select id="selectActiveBlogByTitle2"  resultMap="blogResultMap">
    select * from blogs
    <where>
        <if test="featured != null">
            featured = #{featured}
        </if>
        <if test="title != null">
            AND title like #{title}
        </if>
    </where>
</select>

set

可以使用set元素实现按需更新,指定具体需要更新的字段,set元素会动态前置 SET 关键字,同时也会删掉无关的逗号。

<update id="updateBlogByCondition" parameterType="Blog">
    update blogs
    <set>
        <if test="title != null">title = #{title},</if>
        <if test="authorId != null">author_id = #{authorId},</if>
        <if test="featured != null">featured = #{featured},</if>
        <if test="style != null">style = #{style},</if>
    </set>
    where id = #{id}
</update>

trim

trim可以用于指定加入的前缀和指定自动去除的前面或后面的内容,它有下面四个属性

  • prefix :加入的前置名
  • suffix:加入的后缀名
  • prefixOverrides:自动去除前面的内容
  • suffiexOverrides:自动去除后面的内容

prefixOverrides属性会忽略通过管道分隔的文本序列(注意此例中的空格也是必要的)。它的作用是移除所有指定在prefixOverrides属性中的内容,并且插入prefix属性中指定的内容。(suffiexOverrides同理)

where属性相当于:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

set属性相当于:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

foreach

foreach是对一个集合进行遍历,通常是在构建 IN 条件语句的时候。

  • item:集合的值
  • index:集合的索引
  • collection:指定传入单数的类型, List、Set、Map 对象或者数组对象等都可以传递给foreach作为集合参数
  • open:指定开头
  • sepatator:指定分隔符
  • close:指定结尾

当使用可迭代对象或者数组时,index 是当前迭代的次数,item 的值是本次迭代获取的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

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

sql

这个元素可以被用来定义可重用的 SQL 代码段,这些 SQL 代码可以被包含在其他语句中。它可以(在加载的时候)被静态地设置参数。 在不同的包含语句中可以设置不同的值到参数占位符上

<sql id="columnBase">
    id,title,author_id as authorId,featured,style
</sql>

<!-- 引用方式 -->
<select id="selectBlog" parameterType="int" resultType="Blog">
    select 
    <include refid="columnBase"></include>
    from Blogs where id = #{id}
</select>

鉴别器

如果一个数据表中,某个字段为不同值会影响其他字段,比如说A字段为1时,它的x字段有值,而y字段为空;而当A字段为2时,x字段为空,y字段有值。此时可以通过子类继承拓展的方式实现:
父类不包含x,y字段,两个子类继承父类分别包含x和y,然后在映射中使用鉴别器discriminator进行判别,自动生成对应的字类。

<resultMap type="Vehicle" id="vehicleResultMap">
      <id column="id" property="id" jdbcType="INTEGER"/>

      <!-- 根据指定字段的值设置不同的返回类型 -->
      <discriminator javaType="int" column="vehicle_type">
            <!-- Car是继承Vehicle的字类,其多了一个doorCount成员变量 -->
            <case value="1" resultType="Car">
                  <result column="door_count" property="doorCount"/>
            </case>
            <case value="2" resultType="Suv">
                  <result column="all_wheel_drive"  property="allWheelDrive"/>
            </case>
      </discriminator>