1、简介

什么是MyBatis:

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。

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

maven仓库:

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>复制代码

2、基础配置

2.1、导入依赖

<dependencies>
	<dependency>
		<groupId>org.mybatis</groupId>
		<artifactId>mybatis</artifactId>
		<version>3.5.6</version>
	</dependency>
	<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.49</version>
	</dependency>
	<!-- https://mvnrepository.com/artifact/junit/junit -->
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.13.1</version>
		<scope>test</scope>
	</dependency>
</dependencies>
<build>
	<resources>
		<resource>
			<directory>src/main/java</directory>
			<includes>
				<include>**/*.properties</include>
				<include>**/*.xml</include>
			</includes>
			<filtering>true</filtering>
		</resource>
	</resources>
</build>复制代码

2.2、核心配置文件

<?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.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.1.101:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!--每一个Mapper.xml都需要在Mbatis核心配置文件中注册-->
    <mappers>
	<mapper resource="com/luke/dao/UserMapper.xml"/>
    </mappers>
</configuration>复制代码

3、增删改查实现

3.1、UserMapper接口

public interface UserMapper {
    List<User> getUser();//查询所有用户
    User getUserById(int id);//根据Id查询用户
    int addUser(User user);//增加用户
    int updateUser(User user);//修改用户
    int deleteUserById(int id);//删除用户
}复制代码

3.2、UserMappper.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.luke.dao.UserMapper">
    <select id="getUser" resultType="com.luke.pojo.User">
        select * from user;
    </select>

    <select id="getUserById" resultType="com.luke.pojo.User" parameterType="int">
        select * from user where id = #{id};
    </select>

    <insert id="addUser" parameterType="com.luke.pojo.User">
        insert into user (id,name,pwd) values (#{id},#{name},#{pwd});
    </insert>

    <update id="updateUser" parameterType="com.luke.pojo.User">
        update user set name = #{name},pwd = #{pwd} where id = #{id};
    </update>

    <delete id="deleteUserById" parameterType="int">
        delete from user where id = #{id};
    </delete>
</mapper>复制代码

3.3、Util工具类

public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    static {
            try {
                String resource = "mybatis-config.xml";
                InputStream inputStream;
                inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
    }
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}复制代码

3.4、测试类

public class MyTest {
    //查询所有用户
    @Test
    public void testGetUser(){
        SqlSession sqlSession = null;
        try {
            sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> users = mapper.getUser();
            for (User user : users) {
                System.out.println(user.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null)
                sqlSession.close();
        }

    }
    //根据Id查询用户
    @Test
    public void testGetUserById(){
        SqlSession sqlSession = null;
        try {
            sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            User user = mapper.getUserById(1);
            System.out.println(user);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null)
                sqlSession.close();
        }
    }
    //增加用户信息
    @Test
    public void testInsert(){
        SqlSession sqlSession = null;
        sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int i = mapper.addUser(new User(4,"帅帅","123456"));
        System.out.println(i);
        sqlSession.commit();//增删改需要提交事务
        sqlSession.close();
    }
    //修改用户信息
    @Test
    public void testUpdate(){
        SqlSession sqlSession = null;
        sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int i = mapper.updateUser(new User(4,"帅帅啊","1234567"));
        System.out.println(i);
        sqlSession.commit();//增删改需要提交事务
        sqlSession.close();
    }

    //删除用户
    @Test
    public void testDelete(){
        SqlSession sqlSession = null;
        sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int i = mapper.deleteUserById(4);
        System.out.println(i);
        sqlSession.commit();//增删改需要提交事务
        sqlSession.close();
    }
}复制代码

3.5、使用Map作为入参

Map传递参数,直接在sql中取出key即可。

4、配置解析

一般命名为mybatis-config.xml

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。

4.1、环境配置(environments)

MyBatis 可以配置成适应多种环境,由environment id 区分。

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

<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>复制代码

Mybatis默认的事务管理器就是:JDBC连接池是:POOLED

4.2、属性(properties)

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

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。

db.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.1.101:3306/mybatis?useSSL=false&characterEncoding=UTF-8
username=root
password=root复制代码

核心配置文件引入:

<properties resource="db.properties">
	<property name="username" value="root"/>
	<property name="password" value="123456"/>
</properties>复制代码
  • 可以直接引入外部文件,
  • 可以在其中添加一些属性
  • 在外部文件和属性添加了相同的字段时,优先使用外部文件的

4.3、类型别名(typeAliases)

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

<typeAliases>
	<typeAlias type="com.luke.pojo.User" alias="user"/>
</typeAliases>复制代码

当这样配置时,user可以用在任何使用 com.luke.pojo.User 的地方。

<typeAliases>
	<package name="com.luke.pojo"/>
</typeAliases>复制代码

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。

也可以使用注解添加别名

@Alias("user")
public class User {
    private int id;
    private String name;
    private String pwd;
}复制代码

4.4、设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。

  • cacheEnabled:全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。

  • lazyLoadingEnabled:延迟加载的全局开关。

  • multipleResultSetsEnabled:是否允许单个语句返回多结果集

  • useColumnLabel:使用列标签代替列名。实际表现依赖于数据库驱动。

  • useGeneratedKeys:允许 JDBC 支持自动生成主键,需要数据库驱动支持。

  • autoMappingBehavior:指定 MyBatis 应如何自动映射列到字段或属性。

  • NONE 表示关闭自动映射;

  • PARTIAL 只会自动映射没有定义嵌套结果映射的字段。

  • FULL 会自动映射任何复杂的结果集(无论是否嵌套)。

  • autoMappingUnknownColumnBehavior:指定发现自动映射目标未知列(或未知属性类型)的行为。

  • NONE: 不做任何反应

  • WARNING: 输出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARN)

  • FAILING: 映射失败 (抛出 SqlSessionException)

  • defaultExecutorType:配置默认的执行器。

  • SIMPLE 就是普通的执行器;

  • REUSE 执行器会重用预处理语句(PreparedStatement); 

  • BATCH 执行器不仅重用语句还会执行批量更新。

  • defaultStatementTimeout:设置超时时间,它决定数据库驱动等待数据库响应的秒数。

  • defaultFetchSize:为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。

  • safeRowBoundsEnabled:是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。

  • mapUnderscoreToCamelCase:是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。

  • localCacheScope:MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。

  • 默认值为 SESSION,会缓存一个会话中执行的所有查询。 

  • 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。

  • jdbcTypeForNull:当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。

  • lazyLoadTriggerMethods:指定对象的哪些方法触发一次延迟加载。

  • logImpl:指定 MyBatis 所用日志的具体实现,未指定时将自动查找。

4.5、其他配置

类型处理器(typeHandlers)

MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。

对象工厂(objectFactory)

每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。

插件(plugins)

  • mybatis-generator-core
  • mybatis-plus
  • 通用mapper

4.6、映射器(mappers)

就是告诉 MyBatis 到哪里去找到sql语句.

  • 使用相对于类路径的资源引用

  • 使用完全限定资源定位符(URL)

  • 使用映射器接口实现类的完全限定类名

注意点:接口和它的Mapper配置文件必须同名且在同一包下。

  • 将包内的映射器接口实现全部注册为映射器

注意点:接口和它的Mapper配置文件必须同名且在同一包下。

4.7、作用域(Scope)和生命周期

  • SqlSessionFactoryBuilder

SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

  • SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。

SqlSessionFactory 的最佳作用域是应用作用域。

有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

  • SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。

用完之后需要被关闭。

5、解决属性名和字段名不一致的问题:ResultMapper

resultMap 元素是 MyBatis 中最重要最强大的元素。

ResultMapper结果映射:

<resultMap id="UserMap" type="user">
	<result column="id" property="id"/>
	<result column="name" property="name"/>
	<result column="pwd" property="password"/>
</resultMap>

<select id="getUser" resultMap="UserMap">
	select * from user;
</select>复制代码

6、日志

6.1、日志工厂

在数据库出现异常的时候,需要打出日志帮助排错。

Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:

  • SLF4J、
  • LOG4J、【常用】
  • LOG4J2、
  • JDK_LOGGING、
  • COMMONS_LOGGING、
  • STDOUT_LOGGING、【常用】
  • NO_LOGGING

需要使用哪个在设置中设定

<settings>
    <!--标准的日志工厂实现-->
    <setting name="logImpl" value="STDOUT_LOGGING"/></settings>复制代码

6.2、LOG4J

Log4j简介:

  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等
  • 我们也可以控制每一条日志的输出格式
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
  • 这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码

导入依赖

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

配置文件:log4j.properties

#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/luke.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG复制代码

配置

<settings>
    <setting name="logImpl" value="LOG4J"/></settings>复制代码

Java中使用时:

static Logger logger = Logger.getLogger(MyTest.class);

@Test
public void testLog4j(){
	logger.info("info:进入了testLog4j");
	logger.debug("debug:进入了testLog4j");
	logger.error("error:进入了testLog4j");
}复制代码

7、分页

作用:

  • 减少数据的处理量

7.1、使用Limit分页

select * from user limit startIndex,pageSize;复制代码

使用Mybatis实现

接口:

List<User> getUserByLimit(Map<String,Object> map);复制代码

sql:

<select id="getUserByLimit" resultMap="UserMap" parameterType="map">
	select * from user limit #{startIndex},#{pageSize}
</select>复制代码

测试类:

@Test
public void getUserByLimit(){
	SqlSession sqlSession = MybatisUtils.getSqlSession();
	UserMapper mapper = sqlSession.getMapper(UserMapper.class);
	HashMap<String, Object> map = new HashMap<String, Object>();
	map.put("startIndex",1);
	map.put("pageSize",2);
	List<User> userList = mapper.getUserByLimit(map);
	for (User user : userList) {
		System.out.println(user);
	}

}复制代码

7.2、RowBounds

接口

List<User> getUserByRowBounds();复制代码

sql:

<select id="getUserByRowBounds" resultMap="UserMap" >
	select * from user;
</select>复制代码

测试类

@Test
public void getUserByRowBounds(){
	SqlSession sqlSession = MybatisUtils.getSqlSession();
	RowBounds rowBounds = new RowBounds(2,2);

	List<User> userList = sqlSession.selectList("com.luke.dao.UserMapper.getUserByRowBounds", null, rowBounds);
	for (User user : userList) {
		System.out.println(user);
	}
	sqlSession.close();
}复制代码

8、使用注解

8.1、面向接口编程

  • 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程

  • 根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 使得开发变得容易 , 规范性更好

  • 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;

  • 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

关于接口的理解

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。

  • 接口的本身反映了系统设计人员对系统的抽象理解。

  • 接口应有两类:

  • 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);

  • 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);

  • 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。

三个面向区别

  • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法 .

  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现 .

  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题.更多的体现就是对系统整体的架构

8.2、使用注解开发

本质是由反射机制实现

底层是动态代理

1.注解在接口上实现

@Select("select * from user")
List<User> getUser();复制代码

2.在核心配置文件中绑定接口

<mappers>
	<mapper class="com.luke.dao.UserMapper"/>
</mappers>复制代码

3.测试类

@Test
public void testGetUser(){
	SqlSession sqlSession = null;
	try {
		sqlSession = MybatisUtils.getSqlSession();
		UserMapper mapper = sqlSession.getMapper(UserMapper.class);
		List<User> users = mapper.getUser();
		for (User user : users) {
			System.out.println(user.toString());
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		if (sqlSession != null)
			sqlSession.close();
	}

}复制代码

8.3、CRUD

自动提交事务:

public static SqlSession getSqlSession(){
	return sqlSessionFactory.openSession(true);
}复制代码

查询:

//根据id查询用户
@Select("select * from user where id = #{id}")
User selectUserById(@Param("id") int id);复制代码

新增:

//添加一个用户
@Insert("insert into user (id,name,pwd) values (#{id},#{name},#{pwd})")
int addUser(User user);复制代码

修改:

//修改一个用户
@Update("update user set name=#{name},pwd=#{pwd} where id = #{id}")
int updateUser(User user);复制代码

删除:

//根据id删除用户
@Delete("delete from user where id = #{id}")
int deleteUser(@Param("id")int id);复制代码

@Param注解用于给方法参数起一个名字。以下是总结的使用原则:

  • 在方法只接受一个参数的情况下,可以不使用@Param。

  • 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。

  • 如果参数是 JavaBean , 则不能使用@Param。

  • 不使用@Param注解时,参数只能有一个,并且是Javabean。

9、复杂查询(多对一)

9.1、按照查询嵌套处理

查询语句:

<?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.luke.dao.StudentMapper">

    <resultMap id="studentTeacher" type="student">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
    </resultMap>
    
    <select id="getAllStudent" resultMap="studentTeacher">
        select * from student;
    </select>
    
    <select id="getTeacher" resultType="teacher">
        select * from teacher where id = #{tid};
    </select>
</mapper>复制代码

9.2、按照结果嵌套处理

<resultMap id="studentTeacher2" type="student">
	<result property="id" column="sid"/>
	<result property="name" column="sname"/>
	<association property="teacher" javaType="teacher">
		<result property="name" column="tname"/>
	</association>

</resultMap>

<select id="getAllStudent2" resultMap="studentTeacher2">
	select s.id sid, s.name sname, t.name tname from student s,teacher t where s.tid = t.id;
</select>复制代码

10、复杂查询(一对多)

10.1、按照结果嵌套

<?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.luke.dao.TeacherMapper">

    <resultMap id="teacherStudent" type="teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <collection property="students" ofType="student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
        
    </resultMap>
    
    <select id="getTeacherById" resultMap="teacherStudent">
        select s.id sid, s.name sname, t.name tname, t.id tid from student s,teacher t where s.tid = t.id and tid = #{id};
    </select>

</mapper>复制代码

10.2、按照查询嵌套

<resultMap id="teacherStudent2" type="teacher">
	<result property="id" column="id"/>
	<collection property="students" javaType="ArrayList" ofType="student" column="id" select="getStudengByTeacherId"/>
</resultMap>

<select id="getTeacherById2" resultMap="teacherStudent2">
	select * from teacher where id = #{tid}
</select>

<select id="getStudengByTeacherId" resultType="student">
	select * from student where tid = #{tid}
</select>复制代码

11、动态SQL

动态 SQL 是 MyBatis 的强大特性之一。

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

11.1、if

<select id="queryBlogIf" parameterType="map" resultType="blog">
	select * from blog
	<where>
		<if test="title != null">
			and title = #{title}
		</if>
		<if test="author != null">
			and author = #{author}
		</if>
	</where>
</select>复制代码

11.2、choose (when, otherwise)

<select id="queryBlogChoose" parameterType="map" resultType="blog">
  select * from blog
   <where>
       <choose>
           <when test="title != null">
                title = #{title}
           </when>
           <when test="author != null">
              and author = #{author}
           </when>
           <otherwise>
              and views = #{views}
           </otherwise>
       </choose>
   </where>
</select>复制代码

11.3、set

<!--注意set是用的逗号隔开-->
<update id="updateBlog" parameterType="map">
  update blog
     <set>
         <if test="title != null">
            title = #{title},
         </if>
         <if test="author != null">
            author = #{author}
         </if>
     </set>
  where id = #{id};
</update>复制代码

11.4、Foreach

<select id="queryBlogForeach" parameterType="map" resultType="blog">
  select * from blog
   <where>
       <!--
       collection:指定输入对象中的集合属性
       item:每次遍历生成的对象
       open:开始遍历时的拼接字符串
       close:结束时拼接的字符串
       separator:遍历对象之间需要拼接的字符串
       select * from blog where 1=1 and (id=1 or id=2 or id=3)
     -->
       <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
          id=#{id}
       </foreach>
   </where>
</select>复制代码

11.5、SQL片段

提取:

<sql id="if-title-author">
   <if test="title != null">
      title = #{title}
   </if>
   <if test="author != null">
      and author = #{author}
   </if>
</sql>复制代码

引入:

<select id="queryBlogIf" parameterType="map" resultType="blog">
  select * from blog
   <where>
       <include refid="if-title-author"></include>
   </where>
</select>复制代码

12、缓存

  • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。

  • MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存

  • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)

  • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。

  • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

12.1、一级缓存

一级缓存也叫本地缓存:

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中。

  • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;

也就是同一个session中查询同样的查询语句,第二次及之后的将读取缓存中的数据

SqlSession session = MybatisUtils.getSession();复制代码

缓存失效的四种情况:

  1. sqlSession不同

  2. sqlSession相同,查询条件不同

  3. sqlSession相同,两次查询之间执行了增删改操作!

  4. sqlSession相同,手动清除一级缓存

12.2、二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存

  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;

  • 工作机制

  • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;

  • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;

  • 新的会话查询信息,就可以从二级缓存中获取内容;

  • 不同的mapper查出的数据会放在自己对应的缓存(map)中;

开启全局缓存:

<setting name="cacheEnabled" value="true"/>复制代码

配置使用:

<cache
 eviction="FIFO"
 flushInterval="60000"
 size="512"
 readOnly="true"/>复制代码

这个更配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的

只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据。查出的数据都会被默认先放在一级缓存中。只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中。