【学习笔记01】mybatis

mybatis

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

一、配置mybatis

  1. 使用maven新建项目,需要引入mybatis,mysql和junit(用于测试)三个依赖
    在这里插入图片描述

  2. 创建核心配置文件

    ​ 在resources文件下新建一个xml文件并写入以下内容,其中property标签的value属性需要修改为之前使用jdbc连接数据库时使用的数据

    注意点 :在xml中写url时用到的&符号需要转义,要写成&

在这里插入图片描述

<?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="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

</configuration>
  1. 编写数据库表中对应的实体类以及对应操作的Dao层接口(Dao层现在叫Mapper 另外不需要编写接口实现类)

  2. 编写静态mybatisUtils类用来获取SqlSession对象。

    package com.tuan.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 MybatisUtils {
        private static SqlSessionFactory sqlSessionFactory;
        static {
            //使用mybatis第一步获取SqlSessionFactory
            String resource = "org/mybatis/example/mybatis-config.xml";
            InputStream inputStream = null;
            try {
                inputStream = Resources.getResourceAsStream(resource);
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if(inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        }
    
        public static SqlSession getSession() {
            return sqlSessionFactory.openSession();
        }
    }
    
    
  3. 在mybatis中接口实现类使用一个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.tuan.map.UserMapper">
    
        <select id="getUserList" resultType="com.tuan.pojo.User">
            select * from mybatis.user
        </select>
    </mapper>
    
  4. 执行sql,这里使用junit测试执行

    package com.tuan.dao;
    
    import com.tuan.Dao.UserMapper;
    import com.tuan.pojo.User;
    import com.tuan.utils.MybatisUtils;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    import java.util.List;
    
    public class UserDaoTest {
        @Test
        public void test() {
            //获得SqlSession对象
            SqlSession sqlSession = MybatisUtils.getSession();
    
            //方式一:使用getMapper
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            List<User> user = userMapper.getUserList();
            for(User u : user) {
                System.out.println(u.toString());
            }
            sqlSession.close();
        }
    }
    
    
  5. 在第二步中建立的核心配置文件中写入mapper标签创建对应的接口实现xml的映射
    在这里插入图片描述

  6. 执行获得结果

在这里插入图片描述

  • 可能遇到的问题:

    1.xml资源被拦截不会生成在target文件中:

    ​ 解决方案:在项目pom.xml中加入Builder标签

        <build>
            <resources>
                <resource>
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>false</filtering>
                </resource>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>false</filtering>
                </resource>
            </resources>
        </build>
    

    2.【[ERROR] Some problems were encountered while processing the POMs: [ERROR] ‘packaging’ with value ‘jar’ is invalid. Aggregator projects require ‘pom’ as packaging. @ line 3, column 110】

    ​ 解决方案:报这个问题,主要是出现在父子工程多模块的时候,需要在父模块中指定打包方式为pom,最后一级加不加都行。

在这里插入图片描述

二、mybatis实现增删改查

首先需要确保接口实现类的xml文件中的命名空间namespace要和mapper接口对应

1.查询语句select
  • id:对应的namespace接口中的方法

  • resultType:sql语句中的返回值类型

  • parameterType:参数的类型

    <select id="getUserList" resultType="com.tuan.pojo.User" >
        select * from mybatis.user
    </select>
    
    <select id="getUserById" resultType="com.tuan.pojo.User" parameterType="int">
        select * from mybatis.user where  id = #{id}
    </select>
    
2.增删改语句
  • 插入数据insert
<insert id="addUser" parameterType="com.tuan.pojo.User">
    insert into mybatis.user (id,name,pwd) values(#{id},#{name},#{pwd});
</insert>
  • 修改用户update

    <update id="updateUser" parameterType="com.tuan.pojo.User">
        update mybatis.user set name=#{name},pwd=#{pwd} where id = #{id};
    </update>
    
  • 删除用户delete(与上面类似不再写实例)

注意点:所有的增删改语句在java代码中都需要使用SqlSession的commit方法提交事务

sqlSession.commit();

SqlSession sqlSession = MybatisUtils.getSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User(6,"dog刘博","147852");
userMapper.addUser(user);
sqlSession.commit();
sqlSession.close();

三、设置参数类型为Map

当我们数据库中的字段过多,使用实体类要传递很多参数时可以使用Map作为参数,将需要传递的值放入Map中并在xml中通过key值取出对应的value。

接口中定义:

User getUserByID(Map<String,Object> map);

xml中配置:

<select id="getUserByID" resultType="com.tuan.pojo.User" parameterType="map">
    select * from user where id = #{id}
</select>

测试代码:

public void getUserByID() {
    SqlSession sqlSession = MybatisUtils.getSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    Map<String,Object> map = new HashMap<String,Object>();
    map.put("id",2);
    User user = userMapper.getUserByID(map);
    System.out.println(user);
    sqlSession.close();
}

四、mybatis配置解析

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
在这里插入图片描述

在编写核心配置文件时标签的位置也要按照以下先后顺序编写,否则会报错

  • configuration(配置)
    • properties(属性)
    • settings(设置)
    • typeAliases(类型别名)
    • typeHandlers(类型处理器)
    • objectFactory(对象工厂)
    • plugins(插件)
    • environments(环境配置)
      • environment(环境变量)
        • transactionManager(事务管理器)
        • dataSource(数据源)
    • databaseIdProvider(数据库厂商标识)
    • mapper(映射器)
1.环境配置environments
<!-- default默认使用的环境 -->
<environments default="development">
    <!-- id环境名 -->
        <environment id="development">
            <!--transactionManager事务管理器,mybatis默认有两种事务管理器:JDBC和MANAGED,一般使用JDBC,因为JDBC使用了			事务提交和回滚 -->
            <transactionManager type="JDBC"/>
            <!-- dataSource数据源 一般都使用POOLED-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
2.属性properties

我们可以通过properties属性实现引用配置文件

方法一

① 先在resources目录下编写一个配置文件db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456

② 在核心配置文件中写入properties标签映射db.properties

在这里插入图片描述

db.properties在resource目录下不用再写路径

③ 在environment标签中的property中使用el表达式直接获取到db.properties中的值

<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>
方法二

另外也可以在核心配置文件的properties标签中使用property标签直接写入键值对

    <properties>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </properties>

如果在这里同时使用方法一和方法二写入了相同的key,那会优先使用外部配置文件里的value,即最后获取到的是db.properties中的值

3.配置类型别名typeAliases

类型别名可为 Java 类型设置一个缩写名字,用于降低冗余的全限定类名书写。

①使用typeAlias为指定的一个类起别名

    <typeAliases>
        <typeAlias type="com.tuan.pojo.User" alias="User"></typeAlias>
    </typeAliases>

②使用package扫描一个包,包下的所有类都可以直接使用类名作为参数/返回类型而不用全限定。

    <typeAliases>
        <package name="com.tuan.pojo"/>
    </typeAliases>

③使用注解在类中直接定义别名

@Alias("User")
public class User {...}
  • 实体类较少时使用第一种,实体类较多时使用第二种

  • 第一种可以自定义别名,第二种只能叫做类名

  • 存在注解的情况下只能使用注解作为别名(注解优先级最高)

  • 别名不区分大小写

4.映射器Mapper

映射器负责注册绑定我们的Mapper接口

①使用resource完全限定名绑定接口对应实现类的xml

    <mappers>
        <mapper resource="com/tuan/Dao/UserMapper.xml"/>
    </mappers>

②使用class完全限定名绑定对应的接口

    <mappers>
        <mapper class="com.tuan.Dao.UserMapper"></mapper>
    </mappers>

③使用package扫描一整个包

    <mappers>
        <package name="com.tuan.dao"/>
    </mappers>

使用方法二和方法三必须保证:

​ 接口必须和对应mapper的xml文件同名且在一个包下,即必须保证:

在这里插入图片描述

五、结果集映射ResultMap

ResultMap主要负责解决实体类属性名和数据库中的字段名不一致的情况

在这里插入图片描述

解决方法一:在sql语句中使用as别名解决
    <select id="getUserById" resultType="com.tuan.pojo.User" parameterType="int">
        select id,name,pwd as password from mybatis.user where  id = #{id}
    </select>
解决办法二:使用ResultMap结果集映射

在要使用Resultmap映射的地方就可以用resultMap属性替代resultType属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UuispPA0-1647414028899)(typora.assets/image-20211105141934432.png)]

    <resultMap id="user" type="com.tuan.pojo.User">
        <result column="id" property="id"></result>
        <result column="name" property="name"></result>
        <result column="pwd" property="password"></result>
    </resultMap>

    <select id="getUserById" resultMap="user" parameterType="int">
        select * from mybatis.user where  id = #{id}
    </select>

六、日志

日志是mybatis中的排错功能

使用日志需要在核心配置文件中设置settings标签的logImpl属性,它具有以下值:

SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

我们只需要使用其中的部分:

  • NO_LOGGING

    不使用日志

  • STDOUT_LOGGING

    官方自带日志,配置后可以直接使用无需导包,所有的信息都会在控制台输出

  • LOG4J (重点)

    1.先导入log4j的包

    <!-- https://mvnrepository.com/artifact/log4j/log4j -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    
    

    2.在核心配置文件中设置setting的logImpl属性为LOG4J

        <settings>
            <setting name="logImpl" value="LOG4J"/>
        </settings>
    

    3.资源目录下创建log4j.properties

    log4j.rootLogger = debug,stdout,D,E
    
    
    log4j.appender.stdout = org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.Target = System.out
    log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
    
    log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
    log4j.appender.D.File = D://logs/log.log
    log4j.appender.D.Append = true
    log4j.appender.D.Threshold = DEBUG 
    log4j.appender.D.layout = org.apache.log4j.PatternLayout
    log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n
    
    
    log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
    log4j.appender.E.File =D://logs/error.log 
    log4j.appender.E.Append = true
    log4j.appender.E.Threshold = ERROR 
    log4j.appender.E.layout = org.apache.log4j.PatternLayout
    log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n
    

    最后运行会生成log文件里面写有指定格式的日志

七、分页

1.使用limit分页

  • 接口定义

    List<User> selectUserLimit(int start,int num);
    
  • xml对应实现

    <select id="selectUserLimit" resultMap="UserMap" parameterType="int">
        select * from user limit #{param1},#{param2}
    </select>
  • 测试时传入参数即可

2.使用RowBounds类实现分页

3.使用分页工具类(如Mybatis PageHelper)

八、使用注解开发

使用注解可以避免配置接口对应的xml文件,但对于复杂的sql使用注解会难以实现

    //获取全部用户信息
    @Select("select * from user")
    List<User> getUserList();
    //通过id查询用户
    @Select("select * from user where id=#{id}")
    User getUserById(@Param("id") int id);

关于@param()注解:

​ 一般情况下建议基本类型使用该注解,引用类型不使用该注解。我们在sql中使用#{}引用的就是此注解中的属性名

九、联表查询

1. 多对一的处理

场景:每个学生对应一个老师,学生和老师表通过老师的id字段连接

方式一、按照查询嵌套处理

对结果集进行映射,映射的是另一个select语句

    <select id="selectStudent" resultMap="studentTeacher">
        select * from student;
    </select>

    <resultMap id="studentTeacher" type="Student">
        <result property="id" column="id"></result>
        <result property="name" column="name"></result>
        <association property="teacher" column="tid" javaType="Teacher" select="selectTeacher"/>
    </resultMap>

    <select id="selectTeacher" resultType="Teacher">
        select * from teacher where id=#{id};
    </select>
方式二、按照结果嵌套处理

对结果进行映射

    <select id="getStudent" resultMap="StudentTeacher">
        select s.id sid,s.name sname,t.name tname
        from student s,teacher t
        where s.tid=t.id
    </select>

    <resultMap id="StudentTeacher" type="Student">
        <result property="id" column="sid"></result>
        <result property="name" column="sname"></result>
        <association property="teacher" javaType="Teacher" >
            <result property="name" column="tname"></result>
        </association>
    </resultMap>
2.一对多的处理
方式一、按照结果嵌套处理
    <select id="selectTeacher" resultMap="TeacherStudent">
        select t.id tid,t.name tname,s.id sid,s.name sname
        from teacher t,student s
        where t.id=#{tid} and t.id=s.tid;
    </select>
    
    <resultMap id="TeacherStudent" type="Teacher">
        <result property="id" column="tid"></result>
        <result property="name" column="tname"></result>
        <collection property="students" ofType="Student">
            <result property="name" column="sname"></result>
            <result property="id" column="sid"></result>
            <result property="tid" column="tid"></result>
        </collection>t
    </resultMap>

十、动态 SQL

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

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

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:

<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符)。

如果希望通过 “title” 和 “author” 两个参数进行可选搜索该怎么办呢?首先,我想先将语句名称修改成更名副其实的名称;接下来,只需要加入另一个条件即可。

<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>
choose、when、otherwise

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员精选的 Blog)。

<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>
trim、where、set

前面几个例子已经方便地解决了一个臭名昭著的动态 SQL 问题。现在回到之前的 “if” 示例,这次我们将 “state = ‘ACTIVE’” 设置成动态条件,看看会发生什么。

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

如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:

SELECT * FROM BLOG
WHERE

这会导致查询失败。如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:

SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’

这个查询也会失败。这个问题不能简单地用条件元素来解决。这个问题是如此的难以解决,以至于解决过的人不会再想碰到这种问题。

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>

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

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

prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。

用于动态更新语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:

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

这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

来看看与 set 元素等价的自定义 trim 元素吧:

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

注意,我们覆盖了后缀值设置,并且自定义了前缀值。

foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:

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

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

至此,我们已经完成了与 XML 配置及映射文件相关的讨论。下一章将详细探讨 Java API,以便你能充分利用已经创建的映射配置。

script

要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素。比如:

    @Update({"<script>",
      "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}",
      "</script>"})
    void updateAuthorValues(Author author);
bind

bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。比如:

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>
多数据库支持

如果配置了 databaseIdProvider,你就可以在动态代码中使用名为 “_databaseId” 的变量来为不同的数据库构建特定的语句。比如下面的例子:

<insert id="insert">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    <if test="_databaseId == 'oracle'">
      select seq_users.nextval from dual
    </if>
    <if test="_databaseId == 'db2'">
      select nextval for seq_users from sysibm.sysdummy1"
    </if>
  </selectKey>
  insert into users values (#{id}, #{name})
</insert>
动态 SQL 中的插入脚本语言

MyBatis 从 3.2 版本开始支持插入脚本语言,这允许你插入一种语言驱动,并基于这种语言来编写动态 SQL 查询语句。

可以通过实现以下接口来插入一种语言:

public interface LanguageDriver {
  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
  SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
  SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}

实现自定义语言驱动后,你就可以在 mybatis-config.xml 文件中将它设置为默认语言:

<typeAliases>
  <typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings>
  <setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>

或者,你也可以使用 lang 属性为特定的语句指定语言:

<select id="selectBlog" lang="myLanguage">
  SELECT * FROM BLOG
</select>

或者,在你的 mapper 接口上添加 @Lang 注解:

public interface Mapper {
  @Lang(MyLanguageDriver.class)
  @Select("SELECT * FROM BLOG")
  List<Blog> selectBlog();
}

十一、缓存

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

<cache/>

并在核心配置文件中的settings标签中加上:

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

开启全局缓存

基本上就是这样。这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

这些属性可以通过 cache 元素的属性来修改。比如:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

使用自定义缓存

除了上述自定义缓存的方式,你也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。

<cache type="com.domain.something.MyCustomCache"/>

这个示例展示了如何使用一个自定义的缓存实现。type 属性指定的类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器。 这个接口是 MyBatis 框架中许多复杂的接口之一,但是行为却非常简单。

public interface Cache {
  String getId();
  int getSize();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  boolean hasKey(Object key);
  Object removeObject(Object key);
  void clear();
}

为了对你的缓存进行配置,只需要简单地在你的缓存实现中添加公有的 JavaBean 属性,然后通过 cache 元素传递属性值,例如,下面的例子将在你的缓存实现上调用一个名为 setCacheFile(String file) 的方法:

<cache type="com.domain.something.MyCustomCache">
  <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>

你可以使用所有简单类型作为 JavaBean 属性的类型,MyBatis 会进行转换。 你也可以使用占位符(如 ${cache.file}),以便替换成在配置文件属性中定义的值。

从版本 3.4.2 开始,MyBatis 已经支持在所有属性设置完毕之后,调用一个初始化方法。 如果想要使用这个特性,请在你的自定义缓存类里实现 org.apache.ibatis.builder.InitializingObject 接口。

public interface InitializingObject {
  void initialize() throws Exception;
}

提示 上一节中对缓存的配置(如清除策略、可读或可读写等),不能应用于自定义缓存。

请注意,缓存的配置和缓存实例会被绑定到 SQL 映射文件的命名空间中。 因此,同一命名空间中的所有语句和缓存将通过命名空间绑定在一起。 每条语句可以自定义与缓存交互的方式,或将它们完全排除于缓存之外,这可以通过在每条语句上使用两个简单属性来达成。 默认情况下,语句会这样来配置:

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

鉴于这是默认行为,显然你永远不应该以这样的方式显式配置一条语句。但如果你想改变默认的行为,只需要设置 flushCache 和 useCache 属性。比如,某些情况下你可能希望特定 select 语句的结果排除于缓存之外,或希望一条 select 语句清空缓存。类似地,你可能希望某些 update 语句执行时不要刷新缓存。

cache-ref

回想一下上一节的内容,对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新。 但你可能会想要在多个命名空间中共享相同的缓存配置和实例。要实现这种需求,你可以使用 cache-ref 元素来引用另一个缓存。

<cache-ref namespace="com.someone.application.data.SomeMapper"/>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值