欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

学习MyBatis3这一篇就够了

程序员文章站 2022-06-20 12:24:33
...

目录


配套资料,免费下载
链接:https://pan.baidu.com/s/1cNmQt8BZCrHsVV1T4Dpo9Q
提取码:cnjh
复制这段内容后打开百度网盘手机App,操作更方便哦

第一章 MyBatis3概述

1.1、概述

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 对象)为数据库中的记录。

1.2、特点

  • 简单易学:本身就很小且简单,没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 灵活:MyBatis 不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化,通过sql语句可以满足操作数据库的所有需求。
  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的orm字段关系映射。
  • 提供对象关系映射标签,支持对象关系组建维护。
  • 提供xml标签,支持编写动态sql。

1.3、对比

  • JDBC
    • SQL夹在Java代码块里,耦合度高导致硬编码内伤。
    • 维护不易且实际开发需求中SQL是有变化,频繁修改的情况多见。
  • Hibernate和JPA
    • 长难复杂SQL,对于Hibernate而言处理也不容易。
    • 内部自动生产的SQL,不容易做特殊优化。
    • 基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难,导致数据库性能下降。
  • MyBatis
    • MyBatis是一个半自动化的持久化层框架,简单易学、使用灵活。
    • 解除SQL与程序代码的耦合,SQL和Java编码分开,功能边界清晰,一个专注业务、一个专注数据。

1.4、官网

文档地址:点击打开

源码地址:点击打开

1.5、下载

打开源码地址,在右侧导航栏找到“Release”,目前我们使用的是mybatis-3.5.5,因为会时常更新,建议和本教程采用版本一致。

学习MyBatis3这一篇就够了

打开新页面后,往下拉,找到这里。

学习MyBatis3这一篇就够了

第二章 MyBatis3的增删改查

2.1、环境准备

  • Eclipse:Oxygen.3a Release (4.7.3a)
  • Java:1.8.0_261
  • MySQL:5.5.61
  • Oracle:11gR2
  • 工程默认编码:UTF-8

学习MyBatis3这一篇就够了

  • 配置文件编码:UTF-8

学习MyBatis3这一篇就够了

  • 单元测试版本:Junit4

2.2、创建工程

学习MyBatis3这一篇就够了

学习MyBatis3这一篇就够了

学习MyBatis3这一篇就够了

学习MyBatis3这一篇就够了

学习MyBatis3这一篇就够了

学习MyBatis3这一篇就够了

学习MyBatis3这一篇就够了

学习MyBatis3这一篇就够了

学习MyBatis3这一篇就够了

学习MyBatis3这一篇就够了

学习MyBatis3这一篇就够了

学习MyBatis3这一篇就够了

学习MyBatis3这一篇就够了

2.3、导入依赖

  • mysql的数据库驱动包
    • mysql-connector-java-5.1.47-bin.jar
  • oracle的数据库驱动包
    • ojdbc6-11.2.0.4.0.jar
  • mybatis的开发工具包
    • mybatis-3.5.5.jar
  • mybatis的日志记录包
    • log4j-1.2.17.jar
    • log4j.xml

把以上所有依赖的JAR包,全部拷贝到lib文件夹中,然后右键Build Path一下!

学习MyBatis3这一篇就够了

学习MyBatis3这一篇就够了

学习MyBatis3这一篇就够了

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
	<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
		<param name="Encoding" value="UTF-8" />
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m  (%F:%L) \n" />
		</layout>
	</appender>
	<logger name="java.sql">
		<level value="debug" />
	</logger>
	<logger name="org.apache.ibatis">
		<level value="info" />
	</logger>
	<root>
		<level value="debug" />
		<appender-ref ref="STDOUT" />
	</root>
</log4j:configuration>

把以上xml配置代码,拷贝到 log4j.xml 文件中,然后保存,效果如下图:

学习MyBatis3这一篇就够了

到这里,依赖文件和配置文件都已经安排的明明白白了,接下来,我们准备数据库!

2.4、创建数据库

创建mysql数据库的测试数据库:

注意:如果你没有安装mysql,请自行参考《学习MySQL这一篇就够了》进行安装!

CREATE TABLE `employee` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `last_name` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `gender` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建oracle数据库的测试数据库:

注意:如果你没有安装oracle,请自行参考《学习Oracle这一篇就够了》进行安装!

CREATE TABLE employee (
  id number not null primary key,
  last_name varchar2(255) default null,
  email varchar2(255) default null,
  gender varchar2(255) default null
);

Oracle需要创建序列:

create sequence EMPLOYEES_SEQ
start with 1
increment by 1
nomaxvalue
minvalue 1
nocycle
cache 10000;

2.5、编写CRUD

第一步:创建mybatis-config.xml

学习MyBatis3这一篇就够了

文件的内容拷贝以下信息:

<?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="dbconfig.properties"></properties>

	<!-- 配置框架的全局配置信息 -->
	<settings>
		<setting name="mapUnderscoreToCamelCase" value="true" />
		<setting name="jdbcTypeForNull" value="NULL" />
		<setting name="lazyLoadingEnabled" value="true" />
		<setting name="aggressiveLazyLoading" value="false" />
	</settings>

	<!-- 配置框架的多数据源信息 -->
	<environments default="dev_mysql">
		<!-- 配置mysql开发环境,如果没有mysql可以去掉这个配置段 -->
		<environment id="dev_mysql">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${mysql.driver}" />
				<property name="url" value="${mysql.url}" />
				<property name="username" value="${mysql.username}" />
				<property name="password" value="${mysql.password}" />
			</dataSource>
		</environment>
		<!-- 配置oracle开发环境,如果没有oracle可以去掉这个配置段 -->
		<environment id="dev_oracle">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${oracle.driver}" />
				<property name="url" value="${oracle.url}" />
				<property name="username" value="${oracle.username}" />
				<property name="password" value="${oracle.password}" />
			</dataSource>
		</environment>
	</environments>

	<!-- 数据库厂商起别名 -->
	<databaseIdProvider type="DB_VENDOR">
		<property name="MySQL" value="mysql" />
		<property name="Oracle" value="oracle" />
		<property name="SQL Server" value="sqlserver" />
	</databaseIdProvider>

	<!-- 批量注册映射文件 -->
	<mappers>
		<package name="com.caochenlei.mybatis.mapper" />
	</mappers>
</configuration>

第二步:创建dbconfig.properties

学习MyBatis3这一篇就够了

文件的内容拷贝以下信息:

#mysql数据库配置,如果没有mysql可以去掉这个配置段
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis_crud
mysql.username=root
mysql.password=123456

#oracle数据库配置,如果没有oracle可以去掉这个配置段
oracle.driver=oracle.jdbc.OracleDriver
oracle.url=jdbc:oracle:thin:@localhost:1521:orcl
oracle.username=system
oracle.password=123456

第三步:编写实体类

Employee.java(全路径:/mybatis-crud/src/com/caochenlei/mybatis/crud/Employee.java)

package com.caochenlei.mybatis.crud;

public class Employee {
	private Integer id;
	private String lastName;
	private String email;
	private String gender;

	public Employee() {
		super();
	}

	public Employee(Integer id, String lastName, String email, String gender) {
		super();
		this.id = id;
		this.lastName = lastName;
		this.email = email;
		this.gender = gender;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getGender() {
		return gender;
	}

	public void setGender(String gender) {
		this.gender = gender;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ","
				+ " lastName=" + lastName + ","
				+ " email=" + email + ","
				+ " gender=" + gender + "]";
	}
}

第四步:编写接口类

EmployeeMapper.java(全路径:/mybatis-crud/src/com/caochenlei/mybatis/mapper/EmployeeMapper.java)

package com.caochenlei.mybatis.mapper;

import com.caochenlei.mybatis.crud.Employee;

public interface EmployeeMapper {
	public Long addEmp(Employee employee);

	public Employee getEmpById(Integer id);

	public Long updateEmp(Employee employee);

	public Long deleteEmpById(Integer id);
}

第五步:编写映射文件

EmployeeMapper.xml(全路径:/mybatis-crud/src/com/caochenlei/mybatis/mapper/EmployeeMapper.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.caochenlei.mybatis.mapper.EmployeeMapper">

	<!-- mysql数据库语句映射,如果没有mysql可以去掉这个配置段 -->

	<!-- public Long addEmp(Employee employee); -->
	<insert id="addEmp" parameterType="com.caochenlei.mybatis.crud.Employee" useGeneratedKeys="true" keyProperty="id" databaseId="mysql">
		INSERT INTO `employee`(`last_name`,`email`,`gender`)
		VALUES(#{lastName},#{email},#{gender})
	</insert>

	<!-- public Employee getEmpById(Integer id); -->
	<select id="getEmpById" resultType="com.caochenlei.mybatis.crud.Employee" databaseId="mysql">
		SELECT * FROM `employee` WHERE `id` = #{id}
	</select>

	<!-- public boolean updateEmp(Employee employee); -->
	<update id="updateEmp" databaseId="mysql">
		UPDATE `employee`
		SET `last_name`=#{lastName},`email`=#{email},`gender`=#{gender}
		WHERE `id`=#{id}
	</update>

	<!-- public Long deleteEmpById(Integer id); -->
	<delete id="deleteEmpById" databaseId="mysql">
		DELETE FROM `employee` WHERE `id`=#{id}
	</delete>

	<!-- oracle数据库语句映射,如果没有oracle可以去掉这个配置段 -->

	<!-- public Long addEmp(Employee employee); -->
	<insert id="addEmp" databaseId="oracle">
		<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
			SELECT EMPLOYEES_SEQ.nextval FROM DUAL
		</selectKey>

		INSERT INTO employee(id,last_name,email,gender)
		VALUES(#{id},#{lastName},#{email},#{gender})
	</insert>

	<!-- public Employee getEmpById(Integer id); -->
	<select id="getEmpById" resultType="com.caochenlei.mybatis.crud.Employee" databaseId="oracle">
		SELECT * FROM employee WHERE id = #{id}
	</select>

	<!-- public boolean updateEmp(Employee employee); -->
	<update id="updateEmp" databaseId="oracle">
		UPDATE employee
		SET last_name=#{lastName},email=#{email},gender=#{gender}
		WHERE id=#{id}
	</update>

	<!-- public Long deleteEmpById(Integer id); -->
	<delete id="deleteEmpById" databaseId="oracle">
		DELETE FROM employee WHERE id=#{id}
	</delete>

</mapper>

第六步:测试MySQL的增查改删

注意:当前的数据源切换到了mysql,请检查mybatis-config.xml

<environments default=“dev_mysql”>,请依次运行EmployeeTest.java中的测试方法

package com.caochenlei.mybatis.crud;

import java.io.IOException;
import java.io.InputStream;

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 org.junit.Test;

import com.caochenlei.mybatis.mapper.EmployeeMapper;

public class EmployeeTest {

	@Test
	public void addEmpTest() {
		try {
			String resource = "mybatis-config.xml";
			InputStream inputStream = Resources.getResourceAsStream(resource);
			SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
			SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
			SqlSession openSession = sqlSessionFactory.openSession(true);
			EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);

			Employee employee = new Employee();
			employee.setLastName("zhangsan");
			employee.setGender("男");
			employee.setEmail("aaa@qq.com");
			Long rowCounts = employeeMapper.addEmp(employee);
			System.out.println("影响行数:" + rowCounts);
			System.out.println("自动增长:" + employee.getId());

			openSession.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Test
	public void getEmpByIdTest() {
		try {
			String resource = "mybatis-config.xml";
			InputStream inputStream = Resources.getResourceAsStream(resource);
			SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
			SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
			SqlSession openSession = sqlSessionFactory.openSession(true);
			EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);

			Employee employee = employeeMapper.getEmpById(1);
			System.out.println(employee);

			openSession.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Test
	public void updateEmpTest() {
		try {
			String resource = "mybatis-config.xml";
			InputStream inputStream = Resources.getResourceAsStream(resource);
			SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
			SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
			SqlSession openSession = sqlSessionFactory.openSession(true);
			EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);

			Employee employee = employeeMapper.getEmpById(1);
			employee.setGender("女");
			Long rowCounts = employeeMapper.updateEmp(employee);
			System.out.println("影响行数:" + rowCounts);

			openSession.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Test
	public void deleteEmpByIdTest() {
		try {
			String resource = "mybatis-config.xml";
			InputStream inputStream = Resources.getResourceAsStream(resource);
			SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
			SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
			SqlSession openSession = sqlSessionFactory.openSession(true);
			EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);

			Long rowCounts = employeeMapper.deleteEmpById(1);
			System.out.println("影响行数:" + rowCounts);

			openSession.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

第七步:测试Oracle的增查改删

注意:当前的数据源切换到了oracle,请检查mybatis-config.xml

<environments default=“dev_oracle”>,请依次运行EmployeeTest.java中的测试方法

package com.caochenlei.mybatis.crud;

import java.io.IOException;
import java.io.InputStream;

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 org.junit.Test;

import com.caochenlei.mybatis.mapper.EmployeeMapper;

public class EmployeeTest {

	@Test
	public void addEmpTest() {
		try {
			String resource = "mybatis-config.xml";
			InputStream inputStream = Resources.getResourceAsStream(resource);
			SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
			SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
			SqlSession openSession = sqlSessionFactory.openSession(true);
            
			EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
			Employee employee = new Employee();
			employee.setLastName("zhangsan");
			employee.setGender("男");
			employee.setEmail("aaa@qq.com");
			Long rowCounts = employeeMapper.addEmp(employee);
			System.out.println("影响行数:" + rowCounts);
			System.out.println("自动增长:" + employee.getId());

			openSession.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Test
	public void getEmpByIdTest() {
		try {
			String resource = "mybatis-config.xml";
			InputStream inputStream = Resources.getResourceAsStream(resource);
			SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
			SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
			SqlSession openSession = sqlSessionFactory.openSession(true);
            
			EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
			Employee employee = employeeMapper.getEmpById(1);
			System.out.println(employee);

			openSession.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Test
	public void updateEmpTest() {
		try {
			String resource = "mybatis-config.xml";
			InputStream inputStream = Resources.getResourceAsStream(resource);
			SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
			SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
			SqlSession openSession = sqlSessionFactory.openSession(true);
            
			EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
			Employee employee = employeeMapper.getEmpById(1);
			employee.setGender("女");
			Long rowCounts = employeeMapper.updateEmp(employee);
			System.out.println("影响行数:" + rowCounts);

			openSession.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Test
	public void deleteEmpByIdTest() {
		try {
			String resource = "mybatis-config.xml";
			InputStream inputStream = Resources.getResourceAsStream(resource);
			SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
			SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
			SqlSession openSession = sqlSessionFactory.openSession(true);
            
			EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
			Long rowCounts = employeeMapper.deleteEmpById(1);
			System.out.println("影响行数:" + rowCounts);

			openSession.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

其实我们会发现,我们只是切换了数据源,而代码逻辑没有一点改变,改变的只是sql语句,这就是MyBatis的魅力了!

那么接下来,我们所有的操作都是基于mysql数据库进行的操作,请大家务必注意一点,其它章节的代码有可能会在本章节的基础上进行,也有可能会另起工程,具体请往后看!

第三章 MyBatis3的全局配置

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息, 配置文档的顶层结构如下:

  • configuration(配置)
    • properties(属性)
    • settings(设置)
    • typeAliases(类型别名)
    • typeHandlers(类型处理器)
    • objectFactory(对象工厂)
    • plugins(插件)
    • environments(环境配置)
      • environment(环境变量)
        • transactionManager(事务管理器)
        • dataSource(数据源)
    • databaseIdProvider(数据库厂商标识)
    • mappers(映射器)

3.1、properties

第一种:内置属性

<properties>
	<property name="mysql.driver" value="com.mysql.jdbc.Driver" />
	<property name="mysql.url" value="jdbc:mysql://localhost:3306/mybatis_crud" />
	<property name="mysql.username" value="root" />
	<property name="mysql.password" value="123456" />
</properties>

第二种:引入文件

<properties resource="dbconfig.properties"></properties>

第三种:混合使用

<properties resource="dbconfig.properties">
	<property name="mysql.driver" value="com.mysql.jdbc.Driver" />
	<property name="mysql.url" value="jdbc:mysql://localhost:3306/mybatis_crud" />
	<property name="mysql.username" value="root" />
	<property name="mysql.password" value="123456" />
</properties>

3.2、settings

这些都是非常重要的调整,可以修改MyBatis在运行时的行为方式。这个下表说明了这些设置、它们的含义和默认值。

设置 描述 取值 默认值
cacheEnabled 该配置是影响所有映射器中配置缓存的全局开关。 true | false true
lazyLoadingEnabled 该配置是延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。在特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true | false false
aggressiveLazyLoading 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载,反之,每种属性将会按需加载。 true | false false
(true in ≤3.4.1)
multipleResultSetsEnabled 是否允许单一语句返回多结果集,需要兼容驱动。 true | false true
useColumnLabel 使用列标签代替列名。不同的驱动会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 true | false true
useGeneratedKeys 允许JDBC 支持自动生成主键,需要驱动兼容。如果设置为 true,则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 true | false false
autoMappingBehavior 指定 MyBatis 应如何自动映射列到字段或属性。
NONE:表示取消自动映射。
PARTIAL:表示只会自动映射,没有定义嵌套结果集和映射结果集。
FULL:表示会自动映射任意复杂的结果集,无论是否嵌套。
NONE
PARTIAL
FULL
PARTIAL
autoMappingUnknownColumnBehavior 指定自动映射当中未知列(或未知属性类型)时的行为。 默认是不处理,只有当日志级别达到 WARN 级别或者以下,才会显示相关日志,如果处理失败会抛出 SqlSessionException异常。 NONE
WARNING
FAILING
NONE
defaultExecutorType 配置默认的执行器。
SIMPLE:是普通的执行器。
REUSE:会重用预处理语句。
BATCH:执行器将重用语句并执行批量更新。
SIMPLE
REUSE
BATCH
SIMPLE
defaultStatementTimeout 设置超时时间,它决定驱动等待数据库响应的秒数。 任意正整数值 Not Set (null)
defaultFetchSize 设置数据库驱动程序默认返回的条数限制,此参数可以重新设置。 任意正整数值 Not Set (null)
defaultResultSetType 指定按语句设置忽略它的滚动策略。(Since: 3.5.2) FORWARD_ONLY
SCROLL_SENSITIVE
SCROLL_INSENSITIVE
DEFAULT
(same behavior with ‘Not Set’)
Not Set (null)
safeRowBoundsEnabled 允许在嵌套语句中使用分页(RowBounds)。
如果允许,设置 false。
true | false false
safeResultHandlerEnabled 允许在嵌套语句中使用分页(ResultHandler)。
如果允许,设置false
true | false true
mapUnderscoreToCamelCaseEnables 是否开启自动驼峰命名规则映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 true | false false
localCacheScope MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速关联复嵌套査询。
SESSION:这种情况下会缓存一个会话中执行的所有查询。
STATEMENT:代表本地会话仅用在语句执行上,对相同 SqlScssion 的不同调用将不会共享数据。
SESSION | STATEMENT SESSION
jdbcTypeForNull 当没有为参数提供特定的 JDBC 类型时,为空值时指定 JDBC 类型。某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 JdbcType 枚举值 OTHER
lazyLoadTriggerMethods 指定哪个对象的方法触发一次延迟加载。 一个逗号分隔的方法名称列表 equals,clone,
hashCode,toString
defaultScriptingLanguage 指定动态 SQL 生成的默认语言。 类型别名或指定类的全名称 org.apache.
ibatis.scripting.
xmltags.XMLLang
defaultEnumTypeHandler 指定默认情况下用于枚举的TypeHandler。(Since: 3.4.5) 类型别名或指定类的全名称 org.apache.
ibatis.type.
EnumTypeHandler
callSettersOnNulls 指定当结果集中值为 null 时,是否调用映射对象的 setter(map 对象时为 put)方法,这对于 Map.keySet() 依赖或 null 值初始化时是有用的。注意,基本类型(int、boolean 等)不能设置成 null。 true | false false
returnInstanceForEmptyRowMyBatis 默认情况下,MyBatis在返回行的所有列都为null时返回null。启用此设置后,MyBatis将返回空实例。注意,它也适用于嵌套结果(即collectioin和association)。(Since: 3.4.2) true | false false
logPrefix 指定 MyBatis 增加到日志名称的前缀。 任何字符串 Not set
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动査找。 SLF4J
LOG4J
LOG4J2
JDK_LOGGING
COMMONS_LOGGING
STDOUT_LOGGING
NO_LOGGING
Not set
proxyFactory 指定 MyBatis 创建具有延迟加载能力的对象所用到的代理工具。 CGLIB | JAVASSIST JAVASSIST
(MyBatis 3.3 or above)
vfsImpl 指定 VFS 的实现类。 自定义VFS实现的类的全名称,用逗号分隔。 Not set
useActualParamName 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(Since: 3.4.1) true | false true
configurationFactory 指定提供配置实例的类。返回的配置实例用于加载反序列化对象的惰性属性。此类必须具有签名静态配置getConfiguration() 的方法。(Since: 3.2.3) 类型别名或指定类的全名称 Not set
shrinkWhitespacesInSql 从SQL中删除多余的空白字符。注意,这也会影响SQL中的文字字符串。(Since 3.5.5) true | false false

一个较为完整的settings配置的示例如下:

<settings>
	<setting name="cacheEnabled" value="true" />
	<setting name="lazyLoadingEnabled" value="true" />
	<setting name="multipleResultSetsEnabled" value="true" />
	<setting name="useColumnLabel" value="true" />
	<setting name="useGeneratedKeys" value="false" />
	<setting name="autoMappingBehavior" value="PARTIAL" />
	<setting name="autoMappingUnknownColumnBehavior" value="WARNING" />
	<setting name="defaultExecutorType" value="SIMPLE" />
	<setting name="defaultStatementTimeout" value="25" />
	<setting name="defaultFetchSize" value="100" />
	<setting name="safeRowBoundsEnabled" value="false" />
	<setting name="mapUnderscoreToCamelCase" value="false" />
	<setting name="localCacheScope" value="SESSION" />
	<setting name="jdbcTypeForNull" value="NULL" />
	<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString" />
</settings>

3.3、typeAliases

类型别名可为 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>

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<typeAliases>
	<package name="domain.blog" />
</typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

@Alias("author")
public class Author {
    ...
}

下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

3.4、typeHandlers

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

注意:从 3.4.5 开始,MyBatis 默认支持 JSR-310(日期和时间 API) 。

类型处理器 Java 类型 JDBC 类型
BooleanTypeHandler java.lang.Boolean, boolean 数据库兼容的 BOOLEAN
ByteTypeHandler java.lang.Byte, byte 数据库兼容的 NUMERICBYTE
ShortTypeHandler java.lang.Short, short 数据库兼容的 NUMERICSMALLINT
IntegerTypeHandler java.lang.Integer, int 数据库兼容的 NUMERICINTEGER
LongTypeHandler java.lang.Long, long 数据库兼容的 NUMERICBIGINT
FloatTypeHandler java.lang.Float, float 数据库兼容的 NUMERICFLOAT
DoubleTypeHandler java.lang.Double, double 数据库兼容的 NUMERICDOUBLE
BigDecimalTypeHandler java.math.BigDecimal 数据库兼容的 NUMERICDECIMAL
StringTypeHandler java.lang.String CHAR, VARCHAR
ClobReaderTypeHandler java.io.Reader -
ClobTypeHandler java.lang.String CLOB, LONGVARCHAR
NStringTypeHandler java.lang.String NVARCHAR, NCHAR
NClobTypeHandler java.lang.String NCLOB
BlobInputStreamTypeHandler java.io.InputStream -
ByteArrayTypeHandler byte[] 数据库兼容的字节流类型
BlobTypeHandler byte[] BLOB, LONGVARBINARY
DateTypeHandler java.util.Date TIMESTAMP
DateOnlyTypeHandler java.util.Date DATE
TimeOnlyTypeHandler java.util.Date TIME
SqlTimestampTypeHandler java.sql.Timestamp TIMESTAMP
SqlDateTypeHandler java.sql.Date DATE
SqlTimeTypeHandler java.sql.Time TIME
ObjectTypeHandler Any OTHER 或未指定类型
EnumTypeHandler Enumeration Type VARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值)。
EnumOrdinalTypeHandler Enumeration Type 任何兼容的 NUMERICDOUBLE 类型,用来存储枚举的序数值(而不是名称)。
SqlxmlTypeHandler java.lang.String SQLXML
InstantTypeHandler java.time.Instant TIMESTAMP
LocalDateTimeTypeHandler java.time.LocalDateTime TIMESTAMP
LocalDateTypeHandler java.time.LocalDate DATE
LocalTimeTypeHandler java.time.LocalTime TIME
OffsetDateTimeTypeHandler java.time.OffsetDateTime TIMESTAMP
OffsetTimeTypeHandler java.time.OffsetTime TIME
ZonedDateTimeTypeHandler java.time.ZonedDateTime TIMESTAMP
YearTypeHandler java.time.Year INTEGER
MonthTypeHandler java.time.Month INTEGER
YearMonthTypeHandler java.time.YearMonth VARCHARLONGVARCHAR
JapaneseDateTypeHandler java.time.chrono.JapaneseDate DATE

你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。

具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个 JDBC 类型。比如:

// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {
  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) 
      throws SQLException {
    ps.setString(i, parameter);
  }

  @Override
  public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
    return rs.getString(columnName);
  }

  @Override
  public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    return rs.getString(columnIndex);
  }

  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    return cs.getString(columnIndex);
  }
}
<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>

使用上述的类型处理器将会覆盖已有的处理 Java String 类型的属性以及 VARCHAR 类型的参数和结果的类型处理器。 要注意 MyBatis 不会通过检测数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明字段是 VARCHAR 类型, 以使其能够绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才清楚数据类型。

通过类型处理器的泛型,MyBatis 可以得知该类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变:

  • 在类型处理器的配置元素(typeHandler 元素)上增加一个 javaType 属性(比如:javaType="String");
  • 在类型处理器的类上增加一个 @MappedTypes 注解指定与其关联的 Java 类型列表。 如果在 javaType 属性中也同时指定,则注解上的配置将被忽略。

可以通过两种方式来指定关联的 JDBC 类型:

  • 在类型处理器的配置元素上增加一个 jdbcType 属性(比如:jdbcType="VARCHAR");
  • 在类型处理器的类上增加一个 @MappedJdbcTypes 注解指定与其关联的 JDBC 类型列表。 如果在 jdbcType 属性中也同时指定,则注解上的配置将被忽略。

当在 ResultMap 中决定使用哪种类型处理器时,此时 Java 类型是已知的(从结果类型中获得),但是 JDBC 类型是未知的。 因此 Mybatis 使用 javaType=[Java 类型], jdbcType=null 的组合来选择一个类型处理器。 这意味着使用 @MappedJdbcTypes 注解可以限制类型处理器的作用范围,并且可以确保,除非显式地设置,否则类型处理器在 ResultMap 中将不会生效。 如果希望能在 ResultMap 中隐式地使用类型处理器,那么设置 @MappedJdbcTypes 注解的 includeNullJdbcType=true 即可。 然而从 Mybatis 3.4.0 开始,如果某个 Java 类型只有一个注册的类型处理器,即使没有设置 includeNullJdbcType=true,那么这个类型处理器也会是 ResultMap 使用 Java 类型时的默认处理器。

最后,可以让 MyBatis 帮你批量注册类型处理器:

<!-- mybatis-config.xml -->
<typeHandlers>
  <package name="org.mybatis.example"/>
</typeHandlers>

注意在使用批量注册功能的时候,只能通过注解方式来指定 JDBC 的类型。

你可以创建能够处理多个类的泛型类型处理器。为了使用泛型类型处理器, 需要增加一个接受该类的 class 作为参数的构造器,这样 MyBatis 会在构造一个类型处理器实例的时候传入一个具体的类。

//GenericTypeHandler.java
public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> {

  private Class<E> type;

  public GenericTypeHandler(Class<E> type) {
    if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
    this.type = type;
  }
  ...

3.5、objectFactory

每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。比如:

// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
  public Object create(Class type) {
    return super.create(type);
  }
  public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
    return super.create(type, constructorArgTypes, constructorArgs);
  }
  public void setProperties(Properties properties) {
    super.setProperties(properties);
  }
  public <T> boolean isCollection(Class<T> type) {
    return Collection.class.isAssignableFrom(type);
  }}
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>

ObjectFactory 接口很简单,它包含两个创建实例用的方法,一个是处理默认无参构造方法的,另外一个是处理带参数的构造方法的。 另外,setProperties 方法可以被用来配置 ObjectFactory,在初始化你的 ObjectFactory 实例后, objectFactory 元素体中定义的属性会被传递给 setProperties 方法。

3.6、plugins

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。

默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

// ExamplePlugin.java
@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  private Properties properties = new Properties();
  public Object intercept(Invocation invocation) throws Throwable {
    // implement pre processing if need
    Object returnObject = invocation.proceed();
    // implement post processing if need
    return returnObject;
  }
  public void setProperties(Properties properties) {
    this.properties = properties;
  }
}
<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行底层映射语句的内部对象。

注意:覆盖配置类

除了用插件来修改 MyBatis 核心行为以外,还可以通过完全覆盖配置类来达到目的。只需继承配置类后覆盖其中的某个方法,再把它传递到 SqlSessionFactoryBuilder.build(myConfig) 方法即可。再次重申,这可能会极大影响 MyBatis 的行为,务请慎之又慎。

3.7、environments

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置,或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。

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

所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单:

每个数据库对应一个 SqlSessionFactory 实例

为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);

如果忽略了环境参数,那么将会加载默认环境,如下所示:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);

environments 元素定义了如何配置环境:

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <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>

注意一些关键点:

  • 默认使用的环境 ID(比如:default=“development”)。
  • 每个 environment 元素定义的环境 ID(比如:id=“development”)。
  • 事务管理器的配置(比如:type=“JDBC”)。
  • 数据源的配置(比如:type=“POOLED”)。

默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。

那如何创建多数据源呢,可以参考以下配置:

<environments default="dev_mysql">
	<!-- 配置mysql开发环境,如果没有mysql可以去掉这个配置段 -->
	<environment id="dev_mysql">
		<transactionManager type="JDBC" />
		<dataSource type="POOLED">
			<property name="driver" value="${mysql.driver}" />
			<property name="url" value="${mysql.url}" />
			<property name="username" value="${mysql.username}" />
			<property name="password" value="${mysql.password}" />
		</dataSource>
	</environment>
	<!-- 配置oracle开发环境,如果没有oracle可以去掉这个配置段 -->
	<environment id="dev_oracle">
		<transactionManager type="JDBC" />
		<dataSource type="POOLED">
			<property name="driver" value="${oracle.driver}" />
			<property name="url" value="${oracle.url}" />
			<property name="username" value="${oracle.username}" />
			<property name="password" value="${oracle.password}" />
		</dataSource>
	</environment>
</environments>

事务管理器(transactionManager)

在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):

  • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
  • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。

注意:如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

这两种事务管理器类型都不需要设置任何属性。它们其实是类型别名,换句话说,你可以用 TransactionFactory 接口实现类的全限定名或类型别名代替它们。

public interface TransactionFactory {
  default void setProperties(Properties props) { // 从 3.5.2 开始,该方法为默认方法
    // 空实现
  }
  Transaction newTransaction(Connection conn);
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}

在事务管理器实例化后,所有在 XML 中配置的属性将会被传递给 setProperties() 方法。你的实现还需要创建一个 Transaction 接口的实现类,这个接口也很简单:

public interface Transaction {
  Connection getConnection() throws SQLException;
  void commit() throws SQLException;
  void rollback() throws SQLException;
  void close() throws SQLException;
  Integer getTimeout() throws SQLException;
}

使用这两个接口,你可以完全自定义 MyBatis 对事务的处理。

数据源(dataSource)

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。

有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):

  • UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:

    • driver – 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。

    • url – 这是数据库的 JDBC URL 地址。

    • username – 登录数据库的用户名。

    • password – 登录数据库的密码。

    • defaultTransactionIsolationLevel – 默认的连接事务隔离级别。

    • defaultNetworkTimeout – 等待数据库操作完成的默认网络超时时间(单位:毫秒)。

      注意:作为可选项,你也可以传递属性给数据库驱动,只需在属性名加上“driver.”前缀即可,如:driver.encoding=UTF8

  • POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:

    • poolMaximumActiveConnections – 在任意时间可存在的活动(正在使用)连接数量,默认值:10
    • poolMaximumIdleConnections – 任意时间可能存在的空闲连接数。
    • poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
    • poolTimeToWait – 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。
    • poolMaximumLocalBadConnectionTolerance – 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过 poolMaximumIdleConnectionspoolMaximumLocalBadConnectionTolerance 之和。 默认值:3(新增于 3.4.5)
    • poolPingQuery – 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动出错时返回恰当的错误消息。
    • poolPingEnabled – 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。
    • poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。
  • JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要2个属性:

    • initial_context – 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。

    • data_source – 这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。

      注意:和其他数据源配置类似,可以通过添加前缀“env.”直接把属性传递给 InitialContext。如:env.encoding=UTF8

3.8、databaseIdProvider

MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性,只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可,databaseIdProvider 对应的 DB_VENDOR 实现会将 databaseId 设置为 DatabaseMetaData#getDatabaseProductName() 返回的字符串。 由于通常情况下这些字符串都非常长,而且相同产品的不同版本会返回不同的值,你可能想通过设置属性别名来使其变短:

<databaseIdProvider type="DB_VENDOR">
	<property name="MySQL" value="mysql" />
	<property name="Oracle" value="oracle" />
	<property name="SQL Server" value="sqlserver" />
</databaseIdProvider>

在提供了属性别名时,databaseIdProvider 的 DB_VENDOR 实现会将 databaseId 设置为数据库产品名与属性中的名称第一个相匹配的值,如果没有匹配的属性,将会设置为 “null”。 在这个例子中,如果 getDatabaseProductName() 返回“Oracle (DataDirect)”,databaseId 将被设置为“oracle”。

你可以通过实现接口 org.apache.ibatis.mapping.DatabaseIdProvider 并在 mybatis-config.xml 中注册来构建自己的 DatabaseIdProvider:

public interface DatabaseIdProvider {
  default void setProperties(Properties p) { // 从 3.5.2 开始,该方法为默认方法
    // 空实现
  }
  String getDatabaseId(DataSource dataSource) throws SQLException;
}

3.9、mappers

既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。例如:

第一种形式:

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

第二种形式:

<!-- 使用完全限定资源定位符(URL) -->
<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>

注意:这种形式必须将映射器接口实现类和映射文件放在同一目录中,否则会找不到相对应的xxx.xml。

第四种形式:

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

这些配置会告诉 MyBatis 去哪里找映射文件,剩下的细节就应该是每个 SQL 映射文件了,也就是接下来我们要讨论的。

第四章 MyBatis3的映射配置

MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。

SQL 映射文件只有很少的几个*元素(按照应被定义的顺序列出):

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • sql – 可被其它语句引用的可重用语句块。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句。

4.1、select

select 查询语句是 MyBatis 中最常用的元素之一,光能把数据存到数据库中价值并不大,还要能重新取出来才有用,多数应用也都是查询比修改要频繁。 MyBatis 的基本原则之一是:在每个插入、更新或删除操作之间,通常会执行多个查询操作。因此,MyBatis 在查询和结果映射做了相当多的改进。一个简单查询的 select 元素是非常简单的。比如:

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>

这个语句名为 selectPerson,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值。

注意:参数符号(#{id})

这就告诉 MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样:

// 近似的 JDBC 代码,非 MyBatis 代码...
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);

当然,使用 JDBC 就意味着使用更多的代码,以便提取结果并将它们映射到对象实例中,而这就是 MyBatis 的拿手好戏,参数和结果映射的详细细节会分别在后面单独的小节中说明。

select 元素允许你配置很多属性来配置每条语句的行为细节,例如:

<select
  id="selectPerson"
  parameterType="int"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">
    ...
</select>

那Select 元素的属性都是什么含义呢,请看下表:

属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
resultType 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
resultMap 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
fetchSize 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。
statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultSetType FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
resultOrdered 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false
resultSets 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。

4.1.1、select返回一个对象

**需求信息:**根据员工ID查询员工信息然后将查询结果封装到一个对象中

接口方法:

public Employee getEmpById(Integer id);

映射配置:

<!-- public Employee getEmpById(Integer id); -->
<select id="getEmpById" resultType="com.caochenlei.mybatis.crud.Employee" databaseId="mysql">
	SELECT * FROM `employee` WHERE `id` = #{id}
</select>

测试方法:

EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.getEmpById(1);
System.out.println(employee);

4.1.2、select返回一个List

**需求信息:**查询所有员工信息然后将查询结果封装到一个集合中

接口方法:

public List<Employee> getEmpToList();

映射配置:

<!-- public List<Employee> getEmpToList(); -->
<select id="getEmpToList" resultType="com.caochenlei.mybatis.crud.Employee" databaseId="mysql">
	SELECT * FROM `employee`
</select>

测试方法:

EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
List<Employee> list = employeeMapper.getEmpToList();
for (Employee employee : list) {
	System.out.println(employee);
}

4.1.3、select返回一个Map

**需求信息:**根据员工ID查询员工信息然后将查询结果封装到一个Map中,键是列名,值是所对应的值

接口方法:

public Map<String, Object> getEmpToMapById(Integer id);

映射配置:

<!-- public Map<String, Object> getEmpToMapById(Integer id); -->
<select id="getEmpToMapById" resultType="map" databaseId="mysql">
	SELECT * FROM `employee` WHERE `id` = #{id}
</select>

测试方法:

EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
Map<String, Object> map = employeeMapper.getEmpToMapById(2);
Set<String> keySet = map.keySet();
for (String key : keySet) {
	Object value = map.get(key);
	System.out.println(key + ":" + value);
}

**需求信息:**根据员工名称模糊查询员工信息然后将查询结果封装到一个Map中,键是每个员工的名字,值是所查询出的对象

接口方法:

@MapKey("lastName")
public Map<String, Employee> getEmpToMapLikeLastName(String lastName);

映射配置:

<!-- public Map<String, Employee> getEmpToMapLikeLastName(String lastName); -->
<select id="getEmpToMapLikeLastName" resultType="com.caochenlei.mybatis.crud.Employee" databaseId="mysql">
	SELECT * FROM `employee` WHERE `last_name` like #{lastName}
</select>

测试方法:

EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
Map<String, Employee> map = employeeMapper.getEmpToMapLikeLastName("%zhang%");
Set<String> keySet = map.keySet();
for (String key : keySet) {
	Employee employee = map.get(key);
	System.out.println(key + ":" + employee);
}

4.1.4、select的自定义结果的映射

**需求信息:**根据员工ID查询员工信息然后将查询结果封装到一个对象中,要求使用自己定义的封装结构resultMap

接口方法:

public Employee getEmpByMapping(Integer id);

映射配置:

<!-- public Employee getEmpByMapping(Integer id); -->
<resultMap type="com.caochenlei.mybatis.crud.Employee" id="empMap">
	<!-- 设置主键映射 -->
	<id column="id" property="id" />
	<!-- 普通字段映射 -->
	<result column="last_name" property="lastName" />
	<result column="email" property="email" />
	<result column="gender" property="gender" />
</resultMap>
<select id="getEmpByMapping" resultMap="empMap" databaseId="mysql">
	SELECT * FROM `employee` WHERE `id` = #{id}
</select>

测试方法:

EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.getEmpByMapping(2);
System.out.println(employee);

4.1.5、select的级联封装关联对象

**需求信息:**根据员工ID查询员工信息级联查询员工所在部门的信息

准备工作:

第一步:修改employee表结构,添加一列 dep_id,然后打开表,为这几个员工随便给一个编号(1、2、3)

ALTER TABLE `employee` ADD COLUMN `dep_id` INT;

第二步:创建department表,属性有dep_id、dep_name

CREATE TABLE `department` (
  `dep_id` INT NOT NULL AUTO_INCREMENT,
  `dep_name` VARCHAR (255),
  PRIMARY KEY (`dep_id`)
) ;

INSERT INTO `department` (`dep_id`, `dep_name`) VALUES ('1', '开发部'); 
INSERT INTO `department` (`dep_id`, `dep_name`) VALUES ('2', '公关部'); 
INSERT INTO `department` (`dep_id`, `dep_name`) VALUES ('3', '测试部'); 

第三步:新增Department.java,添加以下代码

package com.caochenlei.mybatis.crud;

public class Department {
	private Integer depId;
	private String depName;

	public Department() {
		super();
	}

	public Department(Integer depId, String depName) {
		super();
		this.depId = depId;
		this.depName = depName;
	}

	public Integer getDepId() {
		return depId;
	}

	public void setDepId(Integer depId) {
		this.depId = depId;
	}

	public String getDepName() {
		return depName;
	}

	public void setDepName(String depName) {
		this.depName = depName;
	}

	@Override
	public String toString() {
		return "Department [depId=" + depId + ", depName=" + depName + "]";
	}
}

第四步:修改Employee.java,添加以下代码

private Department dep;

public Department getDep() {
	return dep;
}

public void setDep(Department dep) {
	this.dep = dep;
}

接口方法:

public Employee getEmpByIdWithDep1(Integer id);

映射配置:

<!-- public Employee getEmpByIdWithDep1(Integer id); -->
<resultMap type="com.caochenlei.mybatis.crud.Employee" id="empCascadeDep1">
	<!-- 设置主键映射 -->
	<id column="id" property="id" />
	<!-- 普通字段映射 -->
	<result column="last_name" property="lastName" />
	<result column="email" property="email" />
	<result column="gender" property="gender" />
	<!-- 级联查询部门 -->
	<result column="dep_id" property="dep.depId" />
	<result column="dep_name" property="dep.depName" />
</resultMap>
<select id="getEmpByIdWithDep1" resultMap="empCascadeDep1" databaseId="mysql">
	SELECT *
	FROM employee e,department d
	WHERE e.dep_id = d.dep_id
	AND e.id = #{id};
</select>

测试方法:

EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.getEmpByIdWithDep1(2);
System.out.println(employee);
System.out.println(employee.getDep());

4.1.6、select的association级联封装

**需求信息:**根据员工ID查询员工信息级联查询员工所在部门的信息

接口方法:

public Employee getEmpByIdWithDep2(Integer id);

映射配置:

<!-- public Employee getEmpByIdWithDep2(Integer id); -->
<resultMap type="com.caochenlei.mybatis.crud.Employee" id="empCascadeDep2">
	<!-- 设置主键映射 -->
	<id column="id" property="id" />
	<!-- 普通字段映射 -->
	<result column="last_name" property="lastName" />
	<result column="email" property="email" />
	<result column="gender" property="gender" />
	<!-- 级联查询部门 -->
	<association property="dep">
		<!-- 设置主键映射 -->
		<id column="dep_id" property="depId" />
		<!-- 普通字段映射 -->
		<result column="dep_name" property="depName" />
	</association>
</resultMap>
<select id="getEmpByIdWithDep2" resultMap="empCascadeDep2" databaseId="mysql">
	SELECT *
	FROM employee e,department d
	WHERE e.dep_id = d.dep_id
	AND e.id = #{id};
</select>

测试方法:

EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.getEmpByIdWithDep2(2);
System.out.println(employee);
System.out.println(employee.getDep());

4.1.7、select的association分步查询

**需求信息:**根据员工ID查询员工信息级联查询员工所在部门的信息

准备工作:

第一步:创建DepartmentMapper.java,拷贝以下代码

package com.caochenlei.mybatis.mapper;

import com.caochenlei.mybatis.crud.Department;

public interface DepartmentMapper {

	public Department getDepById(Integer depId);

}

第二步:创建DepartmentMapper.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.caochenlei.mybatis.mapper.DepartmentMapper">

	<!-- public Employee getDepById(Integer id); -->
	<select id="getDepById" resultType="com.caochenlei.mybatis.crud.Department" databaseId="mysql">
		SELECT * FROM `department` WHERE `dep_id` = #{depId}
	</select>

</mapper>

接口方法:

public Employee getEmpByIdWithDep3(Integer id);

映射配置:

<!-- public Employee getEmpByIdWithDep3(Integer id); -->
<resultMap type="com.caochenlei.mybatis.crud.Employee" id="empCascadeDep3">
	<!-- 设置主键映射 -->
	<id column="id" property="id" />
	<!-- 普通字段映射 -->
	<result column="last_name" property="lastName" />
	<result column="email" property="email" />
	<result column="gender" property="gender" />
	<!-- 级联查询部门 -->
	<association property="dep"
				 select="com.caochenlei.mybatis.mapper.DepartmentMapper.getDepById"
				 column="dep_id">
	</association>
</resultMap>
<select id="getEmpByIdWithDep3" resultMap="empCascadeDep3" databaseId="mysql">
	SELECT * FROM `employee` WHERE `id` = #{id} 
</select>

测试方法:

EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.getEmpByIdWithDep3(2);
System.out.println(employee);
System.out.println(employee.getDep());

4.1.8、select的association延迟加载

**需求信息:**使用以上三种关联查询的任意一种,开启延迟加载,根据员工ID查询员工名称,观察是否不会关联查询部门信息,答案:不会发送语句,这就实现了延迟加载的效果,然后方便的话,再把部门信息输出出来,再次运行,观察是否会发送语句,答案:会发送。

准备工作:

第一步:修改mybatis-config.xml中的settings配置,在里边加入以下代码

<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true" />
<setting name="aggressiveLazyLoading" value="false" />

测试方法:

EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.getEmpByIdWithDep3(2);
System.out.println(employee.getLastName());

4.1.9、select的collection级联封装

准备工作:

第一步:创建DepartmentTest.java,在里边进行代码测试,例如

package com.caochenlei.mybatis.crud;

import java.io.IOException;
import java.io.InputStream;

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 org.junit.Test;

public class DepartmentTest {

	@Test
	public void test1() {
		try {
			String resource = "mybatis-config.xml";
			InputStream inputStream = Resources.getResourceAsStream(resource);
			SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
			SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
			SqlSession openSession = sqlSessionFactory.openSession(true);

			openSession.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

第二步:修改Department.java,新增以下代码

private List<Employee> emps;

public List<Employee> getEmps() {
	return emps;
}

public void setEmps(List<Employee> emps) {
	this.emps = emps;
}

接口方法:

public Department getDepByIdWithEmps1(Integer depId);

映射配置:

<!-- public Department getDepByIdWithEmps1(Integer depId); -->
<resultMap type="com.caochenlei.mybatis.crud.Department" id="depCascadeEmps1">
	<!-- 设置主键映射 -->
	<id column="dep_id" property="depId" />
	<!-- 普通字段映射 -->
	<result column="dep_name" property="depName" />
	<!-- 级联查询员工 -->
	<collection property="emps" ofType="com.caochenlei.mybatis.crud.Employee">
		<!-- 设置主键映射 -->
		<id column="id" property="id" />
		<!-- 普通字段映射 -->
		<result column="last_name" property="lastName" />
		<result column="email" property="email" />
		<result column="gender" property="gender" />
	</collection>
</resultMap>
<select id="getDepByIdWithEmps1" resultMap="depCascadeEmps1">
	SELECT *
	FROM employee e,department d
	WHERE e.dep_id = d.dep_id
	AND d.dep_id = #{depId};
</select>

测试方法:

DepartmentMapper departmentMapper = openSession.getMapper(DepartmentMapper.class);
Department department = departmentMapper.getDepByIdWithEmps1(1);
System.out.println(department);
System.out.println(department.getEmps());

4.1.10、select的collection分步查询

**需求信息:**根据部门dep_id查询部门信息级联查询该部门下所有员工的信息

接口方法:

// EmployeeMapper.java
public Employee getEmpByDepId(Integer depId);

// DepartmentMapper.java
public Department getDepByIdWithEmps2(Integer depId);

映射配置:

// EmployeeMapper.xml
<!-- public Employee getEmpByDepId(Integer depId); -->
<select id="getEmpByDepId" resultType="com.caochenlei.mybatis.crud.Employee" databaseId="mysql">
	SELECT * FROM `employee` WHERE `dep_id` = #{depId}
</select>

// DepartmentMapper.xml
<!-- public Department getDepByIdWithEmps2(Integer depId); -->
<resultMap type="com.caochenlei.mybatis.crud.Department" id="depCascadeEmps2">
	<!-- 设置主键映射 -->
	<id column="dep_id" property="depId" />
	<!-- 普通字段映射 -->
	<result column="dep_name" property="depName" />
	<!-- 级联查询员工 -->
	<collection property="emps"
				select="com.caochenlei.mybatis.mapper.EmployeeMapper.getEmpByDepId"
				column="dep_id">
	</collection>
</resultMap>
<select id="getDepByIdWithEmps2" resultMap="depCascadeEmps2">
	SELECT * FROM `department` WHERE `dep_id` = #{depId}
</select>

测试方法:

DepartmentMapper departmentMapper = openSession.getMapper(DepartmentMapper.class);
Department department = departmentMapper.getDepByIdWithEmps2(1);
System.out.println(department);
System.out.println(department.getEmps());

4.1.11、select的collection延迟加载

**需求信息:**使用以上两种关联查询的任意一种,开启延迟加载,根据部门dep_id查询部门信息,观察是否不会关联查询员工信息,答案:不会发送语句,这就实现了延迟加载的效果,然后方便的话,再把该部门下所有员工信息输出出来,再次运行,观察是否会发送语句,答案:会发送。

**准备工作:**如果有这两个配置,请自行忽略此步骤

第一步:修改mybatis-config.xml中的settings配置,在里边加入以下代码

<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true" />
<setting name="aggressiveLazyLoading" value="false" />

测试方法:

DepartmentMapper departmentMapper = openSession.getMapper(DepartmentMapper.class);
Department department = departmentMapper.getDepByIdWithEmps2(1);
System.out.println(department.getDepName());

4.1.12、select的discriminator鉴别器

**准备工作:**修改employee表中部分数据的性别为女

**需求信息:**如果员工是女生就级联查询它所在的部门,如果是男生就只查询男生自己的信息,不级联查询他所在的部门。

接口方法:

public Employee getDepWithGirl(Integer id);

映射配置:

<!-- public Employee getDepWithGirl(Integer id); -->
<resultMap type="com.caochenlei.mybatis.crud.Employee" id="depWithGirl">
	<!-- 设置主键映射 -->
	<id column="id" property="id" />
	<!-- 普通字段映射 -->
	<result column="last_name" property="lastName" />
	<result column="email" property="email" />
	<result column="gender" property="gender" />
	<!-- 级联查询员工 -->
	<discriminator javaType="string" column="gender">
		<case value="" resultType="com.caochenlei.mybatis.crud.Employee">
			<association property="dep" 
						 select="com.caochenlei.mybatis.mapper.DepartmentMapper.getDepById" 
						 column="dep_id">
			</association>
		</case>
	</discriminator>
</resultMap>
<select id="getDepWithGirl" resultMap="depWithGirl">
	SELECT * FROM `employee` WHERE `id` = #{id}
</select>

测试方法:

EmployeeMapper employeeMapper = openSession.getMapper(EmployeeMapper.class);
Employee employee1 = employeeMapper.getDepWithGirl(2);//2号员工为女生
System.out.println(employee1);
System.out.println(employee1.getDep());

Employee employee2 = employeeMapper.getDepWithGirl(4);//4号员工为男生
System.out.println(employee2);
System.out.println(employee2.getDep());

控制台输出:

学习MyBatis3这一篇就够了

4.1.13、本章做完整个工程结构说明

注意:如果想要查看源码,请下载配套资料,内含源码!

学习MyBatis3这一篇就够了

4.1.14、本章做完整个数据库的说明

employee表:

学习MyBatis3这一篇就够了

department表:

学习MyBatis3这一篇就够了

4.2、insert, update 和 delete

数据变更语句 insert,update 和 delete 的实现非常接近。

下面是 insert,update 和 delete 语句的结构:

<insert
  id="insertAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  keyProperty=""
  keyColumn=""
  useGeneratedKeys=""
  timeout="20">
    ...
</insert>    

<update
  id="updateAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">
    ...
</update>    

<delete
  id="deleteAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">
    ...
</delete>    

下面是 insert,update 和 delete 语句的属性:

属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys (仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
keyProperty (仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
keyColumn (仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。

下面是 insert,update 和 delete 语句的示例:

<insert id="insertAuthor">
  insert into Author (id,username,password,email,bio)
  values (#{id},#{username},#{password},#{email},#{bio})
</insert>

<update id="updateAuthor">
  update Author set
    username = #{username},
    password = #{password},
    email = #{email},
    bio = #{bio}
  where id = #{id}
</update>

<delete id="deleteAuthor">
  delete from Author where id = #{id}
</delete>

如前所述,插入语句的配置规则更加丰富,在插入语句里面有一些额外的属性和子元素用来处理主键的生成,并且提供了多种生成方式。

首先,如果你的数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),那么你可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置为目标属性就 OK 了。例如,如果上面的 Author 表已经在 id 列上使用了自动生成,那么语句可以修改为:

<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id">
  insert into Author (username,password,email,bio)
  values (#{username},#{password},#{email},#{bio})
</insert>

如果你的数据库还支持多行插入, 你也可以传入一个 Author 数组或集合,并返回自动生成的主键。

<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id">
  insert into Author (username, password, email, bio) values
  <foreach item="item" collection="list" separator=",">
    (#{item.username}, #{item.password}, #{item.email}, #{item.bio})
  </foreach>
</insert>

对于不支持自动生成主键列的数据库和可能不支持自动生成主键的 JDBC 驱动,MyBatis 有另外一种方法来生成主键。

这里有一个简单(也很傻)的示例,它可以生成一个随机 ID(不建议实际使用,这里只是为了展示 MyBatis 处理问题的灵活性和宽容度):

<insert id="insertAuthor">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
  </selectKey>
  insert into Author
    (id, username, password, email,bio, favourite_section)
  values
    (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>

实际上,对于Oracle数据库,我们可以采用序列的方式进行主键自增长,例如:

<insert id="insertAuthor">
  <selectKey keyProperty="id" resultType="Integer" order="BEFORE">
    select AUTHOR_SEQ.nextval from dual
  </selectKey>
  insert into Author
    (id, username, password, email,bio, favourite_section)
  values
    (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>

在上面的示例中,首先会运行 selectKey 元素中的语句,并设置 Author 的 id,然后才会调用插入语句。这样就实现了数据库自动生成主键类似的行为,同时保持了 Java 代码的简洁。

selectKey 元素描述如下:

<selectKey
  keyProperty="id"
  resultType="int"
  order="BEFORE"
  statementType="PREPARED">
    ...
</selectKey>    

selectKey 属性描述如下:

属性 描述
keyProperty selectKey 语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称。
keyColumn 返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。
resultType 结果的类型。通常 MyBatis 可以推断出来,但是为了更加准确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果生成列不止一个,则可以使用包含期望属性的 Object 或 Map。
order 可以设置为 BEFOREAFTER。如果设置为 BEFORE,那么它首先会生成主键,设置 keyProperty 再执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。
statementType 和前面一样,MyBatis 支持 STATEMENTPREPAREDCALLABLE 类型的映射语句,分别代表 Statement, PreparedStatementCallableStatement 类型。

4.3、参数获取

单个参数:

mybatis不会做特殊处理,#{参数名/任意名},取出参数值。

多个参数:

mybatis会做特殊处理,多个参数会被封装成一个map,#{key}就是从map中获取指定key的值。
	key:当全局配置参数useActualParamName为true,使用#{arg0},...,#{argN}或者#{param1},...,#{paramN}来获取。
	     当全局配置参数useActualParamName为false,使用#{0},...,#{N}或者#{param1},...,#{paramN}来获取。
	     注意:自从3.4.1版本以后,useActualParamName默认就为true。
	value:传入的参数值。

命名参数:

使用@Param参数注解明确指定封装参数时map的key;@Param("id"),多个参数会被封装成一个map。
	key:使用@Param注解指定的值。
	value:传入的参数值。

POJO:

如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo,#{属性名}就是取出传入的pojo的属性值。

MAP:

如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map,#{key}就是取出map中对应的值。

注意:如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象。

Collection或数组:

如果是Collection(List、Set)类型或者是数组,也会特殊处理,也是把传入的Collection(List、Set)或者数组封装在map中。
	key:对于单一参数Collection使用(collection、list)、数组使用(array)来获取,例如:Collection使用#{collection[0]},...#{collection[N]},List集合也可以使用#{list[0]},...#{list[N]},数组可以使用#{array[0]},...,#{array[N]}这种形式来获取键所对应的值。
	value:集合或数组索引所对应的值。

注意:启用后useActualParamName,可以使用其实际参数名称引用单个ListCollection类型参数。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(Since: 3.4.1)

#{} 和 ${} 的区别:

相同点:

#{}:可以获取map中的值或者pojo对象属性的值。
${}:可以获取map中的值或者pojo对象属性的值。

不同点:

#{}:是以预编译的形式,将参数设置到sql语句中,防止sql注入。
${}:取出的值直接拼装在sql语句中,会有安全问题。
因此大多情况下,我们取参数的值都应该去使用#{},除了一些特定场景,需要在预编译前拼接sql语句的情况,比如按照年份分表查询。同时#{}它支持更丰富的用法:规定参数的一些规则:javaType、jdbcType、mode(存储过程)、numericScale、resultMap、typeHandler、jdbcTypeName,例如:#{email,jdbcType=OTHER}。

4.4、sql片段

这个元素可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。 参数可以静态地(在加载的时候)确定下来,并且可以在不同的 include 元素中定义不同的参数值。比如:

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

这个 SQL 片段可以在其它语句中使用,例如:

<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1 cross join some_table t2
</select>

也可以在 include 元素的 refid 属性或内部语句中使用属性值,例如:

<sql id="sometable">
  ${prefix}Table
</sql>

<sql id="someinclude">
  from
    <include refid="${include_target}"/>
</sql>

<select id="select" resultType="map">
  select
    field1, field2, field3
  <include refid="someinclude">
    <property name="prefix" value="Some"/>
    <property name="include_target" value="sometable"/>
  </include>
</select>

第五章 MyBatis3的动态SQL

5.1、概述

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

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

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

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

5.2、select (if, where, set)

**需求信息:**查询员工,要求携带了哪个字段查询条件就带上这个字段的值。

查询方式一:

我们可以使用if标签来对每个属性进行判断,看它是不是为空,如果为空说明不需要查询这个字段,不为空则需要查询。

<!-- public List<Employee> getEmpsByConditionIf1(); -->
<select id="getEmpsByConditionIf1" resultType="com.caochenlei.mybatis.crud.Employee">
	select * from employee where
	<if test="id!=null">
		id = #{id}
	</if>
	<if test="lastName!=null">
		and last_name like #{lastName}
	</if>
	<if test="email!=null">
		and email like #{email}
	</if>
	<if test="gender!=null">
		and gender like #{gender}
	</if>
	<if test="dep!=null">
		and dep_id = #{dep.depId}
	</if>
</select>

查询方式二:

但是,上述的方法它有一个很大的弊端,一旦id属性没有设置,而其它属性设置了,这个查询就会报错了,因为在where后边直接连接了一个and语句,这显然是不合法的,所以,大多数公司采用以下这种方式:

<!-- public List<Employee> getEmpsByConditionIf2(); -->
<select id="getEmpsByConditionIf2" resultType="com.caochenlei.mybatis.crud.Employee">
	select * from employee where 1 = 1
	<if test="id!=null">
		and id = #{id}
	</if>
	<if test="lastName!=null">
		and last_name like #{lastName}
	</if>
	<if test="email!=null">
		and email like #{email}
	</if>
	<if test="gender!=null">
		and gender like #{gender}
	</if>
	<if test="dep!=null">
		and dep_id = #{dep.depId}
	</if>
</select>

查询方式三:

但是,以上的写法也是有问题的,在sql查询的时候,无缘无故的多加了一个 1 = 1,显然这跟我们的业务并没有什么关系,那有没有一种既能解决where后有and报错问题,又不用写多余的代码呢?答案是肯定的,我们只要使用where标签来代替where语句即可,例如:

<!-- public List<Employee> getEmpsByConditionIf3(); -->
<select id="getEmpsByConditionIf3" resultType="com.caochenlei.mybatis.crud.Employee">
	select * from employee
	<where>
		<if test="id!=null">
			and id = #{id}
		</if>
		<if test="lastName!=null">
			and last_name like #{lastName}
		</if>
		<if test="email!=null">
			and email like #{email}
		</if>
		<if test="gender!=null">
			and gender like #{gender}
		</if>
		<if test="dep!=null">
			and dep_id = #{dep.depId}
		</if>
	</where>
</select>

**需求信息:**更新员工,要求携带了哪个字段就更新哪个字段的值。

更新方式:

通过以上三个例子,我们是不是可以类比实现update更新,那我们来看看一个最佳的update更新代码片段是怎样写的?

<!-- public boolean updateEmpByConditionIf(Employee employee); -->
<update id="updateEmpByConditionIf">
	update employee
	<set>
		<if test="lastName!=null">
			last_name = #{lastName},
		</if>
		<if test="email!=null">
			email = #{email},
		</if>
		<if test="gender!=null">
			gender = #{gender},
		</if>
		<if test="dep!=null">
			dep_id = #{dep.depId},
		</if>
	</set>
	where id = #{id}
</update>

5.3、trim (where, set)

通过以上的案例,我们学会了if标签的使用以及where标签的使用,但是,我们想一个问题,现在的and是在每一个条件的前边,那我要是想要把and放到每一个标签的后边,那是不是也可以呢(where标签会不会自动给我处理了多余的and)?显然这是不现实的,where标签只能处理and在条件前边的情况,那我非要实现这种情况又该怎么办呢?这时候,我们就要使用trim标签了,它的功能更加强大,例如:

<!-- public List<Employee> getEmpsByConditionTrim(Employee employee); -->
<select id="getEmpsByConditionTrim" resultType="com.caochenlei.mybatis.crud.Employee">
	select * from employee
	<!-- 
		trim:
			prefix:给拼串后的整个字符串加一个前缀
			prefixOverrides:去掉整个字符串前面多余的字符,支持或(|)
			suffix:给拼串后的整个字符串加一个后缀
			suffixOverrides:去掉整个字符串后面多余的字符,支持或(|)
	 -->
	<trim prefix="where" suffixOverrides="AND | OR">
		<if test="id!=null">
			id = #{id} and
		</if>
		<if test="lastName!=null">
			last_name like #{lastName} and
		</if>
		<if test="email!=null">
			email like #{email} and
		</if>
		<if test="gender!=null">
			gender like #{gender} and
		</if>
		<if test="dep!=null">
			dep_id = #{dep.depId} and
		</if>
	</trim>
</select>

以上的代码正确的演示了一个查询语句where的使用,那么,如果我想要通过trim更新数据库中的记录,那又该怎么做呢?没错,我想你心里可能已经有想法了,来看看是不是:

<!-- public boolean updateEmpByConditionTrim(Employee employee); -->
<update id="updateEmpByConditionTrim">
	update employee
	<!-- 
		trim:
			prefix:给拼串后的整个字符串加一个前缀
			prefixOverrides:去掉整个字符串前面多余的字符,支持或(|)
			suffix:给拼串后的整个字符串加一个后缀
			suffixOverrides:去掉整个字符串后面多余的字符,支持或(|)
	 -->
	<trim prefix="set" suffixOverrides=",">
		<if test="lastName!=null">
			last_name = #{lastName},
		</if>
		<if test="email!=null">
			email = #{email},
		</if>
		<if test="gender!=null">
			gender = #{gender},
		</if>
		<if test="dep!=null">
			dep_id = #{dep.depId},
		</if>
	</trim>
	where id = #{id}
</update>

5.4、choose (when, otherwise)

**需求信息:**如果带了id就用id查,如果带了lastName就用lastName查,只会进入其中一个,如果一个都没有,默认查询所有女生信息,这样又该如何实现呢?

实现方式:

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

<!-- public List<Employee> getEmpsByConditionChoose(Employee employee); -->
<select id="getEmpsByConditionChoose" resultType="com.caochenlei.mybatis.crud.Employee">
	select * from employee
	<where>
		<choose>
			<when test="id!=null">
				id = #{id}
			</when>
			<when test="lastName!=null">
				last_name like #{lastName}
			</when>
			<when test="email!=null">
				email = #{email}
			</when>
			<otherwise>
				gender = '女'
			</otherwise>
		</choose>
	</where>
</select>

其实以上的情况为什么不使用if进行判断呢?因为单纯的使用if判断,不适宜默认情况的执行,这时候使用choose (when, otherwise)就非常合适了。

5.5、foreach

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

<!-- public List<Employee> getEmpsByConditionForeach(@Param("ids") List<Integer> ids); -->
<select id="getEmpsByConditionForeach" resultType="com.caochenlei.mybatis.crud.Employee">
	select * from employee
	<!-- 
		foreach:
			collection:指定要遍历的集合(list类型的参数会特殊处理封装在map中,map的key就叫list)
			item:将当前遍历出的元素赋值给指定的变量
			separator:每个元素之间的分隔符
			open:遍历出所有结果拼接一个开始的字符串
			close:遍历出所有结果拼接一个结束的字符串
			index:遍历list的时候,index就是索引,item就是当前值
				      遍历map的时候,index就是map的key,item就是map[key]的值
				      
		#{变量名}就能取出变量的值也就是当前遍历出的元素					      
	 -->
	<foreach collection="ids" item="item_id" separator="," open="where id in(" close=")">
		#{item_id}
	</foreach>
</select>

除了可以用于构建IN查询,还可以对数据进行批量插入。例如:

<!-- public void addEmps(@Param("emps")List<Employee> emps); -->
<insert id="addEmps">
	insert into employee (id,last_name,email,gender,dep_id) 
	values
	<foreach collection="emps" item="emp" separator=",">
		(#{emp.id},#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dep.depId})
	</foreach>
</insert>

但是,此时又会出现问题,因为mysql支持values(),而Oracle不支持values(),那么如果要是向oracle数据库中进行批量插入,我们又该如何实现呢?

方式一:

<!-- public void addEmpsForOracle1(@Param("emps") List<Employee> emps); -->
<insert id="addEmpsForOracle1" databaseId="oracle">
	<foreach collection="emps" item="emp" open="begin" close="end;">
		insert into employee (id,last_name,email,gender)
		values(employees_seq.nextval,#{emp.lastName},#{emp.email},#{emp.gender});
	</foreach>
</insert>

方式二:

<!-- public void addEmpsForOracle2(@Param("emps") List<Employee> emps); -->
<insert id="addEmpsForOracle2" databaseId="oracle">
	insert into employee (id,last_name,email,gender)
	<foreach collection="emps" item="emp" separator="union" 
		open="select employees_seq.nextval,lastName,email,gender from(" 
		close=")">
		select #{emp.lastName} lastName,#{emp.email} email,#{emp.gender} gender from dual
	</foreach>
</insert>

5.6、两个内置参数

mybatis默认还有两个内置参数:

  • _parameter:代表整个参数
    • 单个参数:_parameter就是这个参数
    • 多个参数:参数会被封装为一个map,_parameter就是代表这个map
  • _databaseId:如果配置了databaseIdProvider标签,那么databaseId就是代表当前数据库的别名,比如:oracle

5.7、bind

bind:可以将OGNL表达式的值绑定到一个变量中,方便后来引用这个变量的值。

**需求信息:**我们现在要针对不同的数据库使用不同的模糊查询语句,而且一个使用bind绑定变量后引用,而另一个则是直接使用内置对象进行引用,如下:

<!-- public List<Employee> getEmpsTestInnerParameter(Employee employee); -->
<select id="getEmpsTestInnerParameter" resultType="com.caochenlei.mybatis.crud.Employee">
	<bind name="_lastName" value="'%'+lastName+'%'" />
	<if test="_databaseId=='mysql'">
		select * from employee
		<if test="_parameter!=null">
			<!-- 使用bind绑定的变量_lastName -->
			where last_name like #{_lastName}
		</if>
	</if>
	<if test="_databaseId=='oracle'">
		select * from employee
		<if test="_parameter!=null">
			<!-- 使用内置对象_parameter -->
			where last_name like #{_parameter.lastName}
		</if>
	</if>
</select>

第六章 MyBatis3的缓存机制

6.1、概述

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

MyBatis系统中默认定义了两级缓存,它们分别是:一级缓存和二级缓存。

  1. 默认情况下,只有一级缓存(sqlSession级别的缓存,也称为本地缓存)开启。
  2. 二级缓存需要手动开启和配置,它是基于namespace级别的缓存。
  3. 为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存。

6.2、一级缓存

一级缓存(local cache),即本地缓存,作用域默认为 sqlSession。当 Session flush 或 close 后,该 Session 中的所有 Cache 将被清空。

本地缓存不能被关闭,但可以调用 clearCache() 来清空本地缓存或者改变缓存的作用域。

在 mybatis3.1 之后,可以配置本地缓存的作用域.,在settings 中配置如下:

设置 描述 取值 默认值
localCacheScope MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速关联复嵌套査询。
SESSION:这种情况下会缓存一个会话中执行的所有查询。
STATEMENT:代表本地会话仅用在语句执行上,对相同 SqlScssion 的不同调用将不会共享数据。
SESSION | STATEMENT SESSION

一级缓存失效的几种情况:

  • sqlSession不同
  • sqlSession相同,查询条件不同(当前一级缓存中还没有这个数据)
  • sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
  • sqlSession相同,手动清除了一级缓存(缓存清空)

6.3、二级缓存

二级缓存(second level cache),它是基于namespace级别的全局作用域缓存,二级缓存默认不开启,需要手动配置,要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

<cache />

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

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

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

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

<!-- 
	cache:
		eviction:缓存的回收策略。
			• LRU – 最近最少使用:移除最长时间不被使用的对象。(默认值)
			• FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
			• SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
			• WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
		flushInterval:刷新间隔,该属性可以被设置为任意的正整数,单位为毫秒, 默认情况是不设置。
		size:引用数目,该属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源,默认值是 1024。
		readOnly:只读,该属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 
		type:指定自定义缓存的全类名,实现Cache接口即可。
 -->
<cache eviction="LRU"
		flushInterval="60000"
		size="1024"
		readOnly="true"
		type="" />

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

你可能会想要在多个命名空间*享相同的缓存配置和实例,要实现这种需求,你可以使用 cache-ref 元素来引用另一个缓存。

<cache-ref namespace="com.someone.application.data.SomeMapper"/>

那么我们简单梳理一下,二级缓存的使用步骤:

  1. 开启全局二级缓存配置:<setting name=“cacheEnabled” value=“true”/>
  2. 去mapper.xml中配置使用二级缓存:<cache />
  3. 我们的POJO需要实现序列化接口

6.4、和缓存有关的设置

mybatis-config.xml的settings标签中的配置:

  • <setting name=“localCacheScope” value=“SESSION”/>,本地缓存(一级缓存)作用域,当前会话的所有数据保存在会话缓存中,如果值为STATEMENT可以禁用一级缓存
  • <setting name=“cacheEnabled” value=“true”/>,二级缓存开启和关闭的开关,如果为true则开启二级缓存

xxxxxMapper.xml的每个select标签中的配置:

  • useCache=“true”:默认值为true,如果值为false代表select标签不使用缓存(一级缓存可使用,二级缓存不使用)
  • flushCache=“false”:默认值为false,如果值为false代表select标签不清除缓存

xxxxxMapper.xml的每个insert、update、delete标签中的配置:

  • flushCache=“true”:默认值为true,如果值为true代表清除缓存(一级二级都会清除)

sqlSession的调用代码中:

  • sqlSession.clearCache():只是清除当前session的一级缓存,而二级缓存不会清除

6.5、自定义缓存的方法

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

<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 语句执行时不要刷新缓存。

6.6、与第三方缓存整合

前导部分:

接下来可能需要下载一些软件包,如果你没兴趣,或者,你怕版本有冲突,你可以直接使用我提供好的软件包,而且我也建议你使用我提供的,下载流程了解即可。

整合方法:

  1. 导入第三方缓存包

  2. 导入与第三方缓存整合的适配包

  3. mapper.xml中使用自定义缓存,例如:

    • <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
      
  4. 导入第三方缓存包所依赖的配置文件

**案例演示:**整合ehcache

第一步:去ehcache官网,下载第三方缓存包,点击打开官网,然后往下拉,找到整合包

学习MyBatis3这一篇就够了

点击下载:

学习MyBatis3这一篇就够了

右键解压:

学习MyBatis3这一篇就够了

拷贝jar包到工程的lib文件夹中,然后Build Path:

学习MyBatis3这一篇就够了

第二步:去mybatis官网,下载第三方整合包,点击打开官网,然后往下拉,找到整合包

学习MyBatis3这一篇就够了

点击进去后,找到最近发布的新版本,如下图所示:

学习MyBatis3这一篇就够了

点击下载:

学习MyBatis3这一篇就够了

右键解压:

学习MyBatis3这一篇就够了

进入目录:进行安装,如果你不会maven,你就直接使用我的配套资料中提供的已经编译好的版本

学习MyBatis3这一篇就够了

编译完成,在target文件夹中会出现mybatis-ehcache-1.2.1.jar就是mybatis和ehcache的整合包

学习MyBatis3这一篇就够了

找到安装后的jar包,然后拷贝到工程的lib文件夹中,然后Build Path一下:

学习MyBatis3这一篇就够了

第三步:开启自定义缓存,我们现在EmployeeMapper.xml中添加

<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

第四步:编辑ehcache配置文件,在src目录下,新建一个ehcache.xml文件,把以下代码拷贝进去:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
 <!-- 磁盘保存路径 -->
 <diskStore path="d:\ehcache" />
 
 <!-- 缓存配置选项 -->
 <defaultCache 
   maxElementsInMemory="1" 
   maxElementsOnDisk="10000000"
   eternal="false" 
   overflowToDisk="true" 
   timeToIdleSeconds="120"
   timeToLiveSeconds="120" 
   diskPersistent="false"
   diskExpiryThreadIntervalSeconds="120"
   memoryStoreEvictionPolicy="LRU">
 </defaultCache>
</ehcache>
<!-- 
属性说明:
	diskStore:指定数据在磁盘中的存储位置。
	defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略
 
以下属性是必须的:
	maxElementsInMemory - 在内存中缓存的element的最大数目 
	maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
	eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
	overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
 
以下属性是可选的:
	timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
	timeToLiveSeconds - 缓存element的有效生命期,默认是0,也就是element存活时间无穷大
	diskSpoolBufferSizeMB - 这个参数设置DiskStore(磁盘缓存)的缓存区大小,默认是30MB,每个Cache都应该有自己的一个缓冲区
	diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
	diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
	memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
 -->

第五步:找到EmployeeTest.java的getEmpByIdTest方法,测试一下查询语句

学习MyBatis3这一篇就够了

我们到我们之前指定的文件夹中去看看有没有缓存文件

学习MyBatis3这一篇就够了


学习MyBatis3这一篇就够了

我们会发现它确实已经进行了缓存,而且文件名是namespace的命名空间,这也证实了一点,二级缓存(second level cache),它是基于namespace级别的全局作用域缓存,如果你测试了,发现文件夹没有任何东西,可能你的<setting name="cacheEnabled" value="true"/>的值是false或者没有配置,默认它是true,如果你的版本和我的版本不一致,它就可能默认是有变化,所以,我们建议,用到哪些属性,就显示的声明一下,这样无论版本有没有变化默认值,都不影响使用。

第七章 MyBatis3的****

7.1、概述

MyBatis生成器(MBG)是MyBatis的代码生成器。它将为MyBatis的所有版本生成代码。它将内省一个数据库表(或多个表),并将生成可用于访问表的工件。这减轻了设置对象和配置文件以与数据库表进行交互的麻烦。MBG试图对简单CRUD(创建,查询,更新,删除)的大部分数据库操作产生重大影响。您仍将需要手工编写SQL和对象代码以进行联接查询或存储过程。

MBG会根据其配置方式以不同的样式和不同的语言生成代码。例如,MBG可以生成Java或Kotlin代码。MBG可以生成MyBatis3兼容的XML,尽管现在认为MBG是旧版使用,生成的代码的较新样式不需要XML。

当作为Eclipse功能运行时,生成器还可以合并Java文件并将用户修改保存到生成的Java文件中。生成器使用Eclipse Java分析器和AST walker来完成此任务。Eclipse功能还具有一些用户界面增强功能,这些功能使生成器的运行更加容易。最后,Eclipse功能为Eclipse帮助系统提供了完整的生成器用户手册。

Eclipse功能可在Eclipse市场上找到:https://marketplace.eclipse.org/content/mybatis-generator

MBG除了JRE之外没有其他依赖项,需要Java 8或更高版本,另外,还需要一个实现DatabaseMetaData接口的JDBC驱动程序。

7.2、下载

****:点击打开

打开****的网页地址,在右侧导航栏找到“Release”,目前我们使用的是MyBatis Generator Release 1.4.0,因为会时常更新,建议和本教程采用版本一致。

学习MyBatis3这一篇就够了

打开新页面后,往下拉,找到这里。

学习MyBatis3这一篇就够了

7.3、配置详解

7.3.1、generatorConfiguration

<generatorConfiguration>元素是MyBatis Generator配置文件的根元素。该文件应包含以下DOCTYPE:

<!DOCTYPE generatorConfiguration PUBLIC
  “-// mybatis.org//DTD MyBatis Generator配置1.0 // EN”
  “ http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd”>

**必填属性:**无

**可选属性:**无

子元素:

  • <properties> (0 or 1)
  • <classPathEntry> (0…N)
  • <context> (1…N)
7.3.1.1、properties

<properties>元素用于指定用于配置解析的外部属性文件。配置中的任何属性都将接受$ {property}形式的属性。将在指定的属性文件中搜索匹配值,并将替换该匹配值。该属性文件是Java属性文件的常规格式。

如果此处指定的属性与系统属性之间发生名称冲突,则系统属性将获胜。

<properties>元素是<generatorConfiguration> 元素的子元素。

**必填属性:**以下属性之一是唯一的。

属性 描述
resource 属性文件的限定名称。指定资源时,将在类路径中搜索属性文件。因此,在com.myproject程序包中必须存在指定为com/myproject/generatorConfig.properties的文件 。
url 用于属性文件的URL值。当以file:///C:/myfolder/generatorConfig.properties之类的形式使用时,可用于在文件系统上的特定位置指定属性文件 。

**可选属性:**无

**子元素:**无

7.3.1.2、classPathEntry

<classPathEntry>元素用于将类路径位置添加到MyBatis Generator(MBG)运行的类路径中。在<classPathEntry>元素是一个选项的子元素<generatorConfiguration>元素。MBG在以下情况下从这些位置加载类:

  • 加载用于数据库自省的JDBC驱动程序时
  • 在JavaModelGenerator中加载根类以检查重写的方法时

如果您在MBG外部设置类路径(例如,使用java命令的-cp参数),则此元素是可选的,但不是必需的

**重要说明:**加载扩展MBG类之一或实现MBG接口之一的类时,不会使用这些位置。在这些情况下,您必须以将MBG添加到类路径的相同方式(例如,使用java命令的-cp参数 )将外部类添加到运行时类路径。

必填属性:

属性 描述
location 要添加到类路径的JAR / ZIP文件的完整路径名,或要添加到类路径的目录的全路径名。

**可选属性:**无

**子元素:**无

7.3.1.3、context

<context>元素用于指定生成一组对象的环境。子元素用于指定要连接的数据库,要生成的对象的类型以及要进行内省的表。可以在<generatorConfiguration> 元素内列出多个<context>元素, 以允许在MyBatis Generator(MBG)的同一运行中从不同的数据库或具有不同的生成参数生成对象。

必填属性:

属性 描述
id 此上下文的唯一标识符。该值将在某些错误消息中使用。

可选属性:

属性 描述
defaultModelType 如果目标运行时为“ MyBatis3Simple”,“ MyBatis3DynamicSql”或“ MyBatis3Kotlin”,则将忽略此属性。此属性用于为生成的模型类型设置默认值。模型类型定义MBG如何生成域类。对于某些模型类型,MBG将为每个表生成单个域类,而对于其他模型类型,MBG可能会根据表的结构生成不同的类。该属性支持以下值:有条件的这是默认值。此模型与分层模型相似,不同之处在于,如果该单独的类仅包含一个字段,则不会生成单独的类。因此,如果一个表只有一个主键字段,则该字段将合并到基本记录类中。平面该模型为任何表仅生成一个域类。该类将保留表中的所有字段。如果表具有主键,则该模型将生成一个主键类,另一个类将保存表中的任何BLOB列,另一个类将保留其余字段。类之间存在适当的继承关系。
targetRuntime 此属性用于为生成的代码指定运行时目标。该属性支持以下特殊值:
MyBatis3DynamicSql这是默认值。 使用该值,MBG将生成与MyBatis版本3.4.2和更高版本以及Java 8和更高版本兼容的对象(例如,Java模型和映射器接口将使用通用类型和其他Java 8功能,例如默认方法)。**重要提示:**在此目标运行时中生成的Java代码取决于“ MyBatis Dynamic SQL”支持库版本1.1.3或更高版本。其他重要信息:无论为“defaultModelType”指定什么内容,都将以“ FLAT”样式生成模型对象。这也意味着不存在“具有BLOB”和“不具有BLOB”的方法。无论为<javaClientGenerator>的“类型”指定了什么,映射器都将作为带注释的映射器生成。不会生成XML。<sqlMapGenerator>不是必需的,如果指定,将被忽略。MyBatis Dynamic SQL以“每个查询”的方式而不是其他运行时的“全有或全无”的方式支持表别名。因此,已配置的表别名将被忽略。
MyBatis3Kotlin使用该值,MBG将生成与MyBatis版本3.4.2及更高版本兼容的Kotlin对象。**重要说明:**此目标运行时生成的Kotlin代码取决于“ MyBatis Dynamic SQL”支持库版本1.1.4或更高版本。其他重要信息:无论为“ defaultModelType”指定什么内容,都将以“ FLAT”样式生成模型对象。这也意味着不存在“具有BLOB”和“不具有BLOB”的方法。无论为<javaClientGenerator>的“类型”指定了什么,映射器都将作为带注释的映射器生成。不会生成XML。<sqlMapGenerator>不是必需的,如果指定,将被忽略。MyBatis Dynamic SQL以“每个查询”的方式而不是其他运行时的“全有或全无”的方式支持表别名。因此,已配置的表别名将被忽略。
MyBatis3使用该值,MBG将生成与MyBatis 3.0版及更高版本以及JSE 5.0版及更高版本兼容的对象(例如,Java模型和映射器接口将使用通用类型)。这些生成的对象中的“示例”方法实际上支持无限的动态where子句。此外,使用这些生成器生成的Java对象支持许多JSE 5.0功能,包括参数化类型和注释。
MyBatis3Simple使用该值,MBG将生成与MyBatis 3.0版及更高版本以及JSE 5.0版及更高版本兼容的对象(例如,Java模型和映射器接口将使用通用类型)。用此目标运行时生成的映射器是非常基本的CRUD操作,仅没有“示例”方法且动态SQL很少。用这些生成器生成的Java对象支持许多JSE 5.0功能,包括参数化类型和注释。
introspectedColumnImpl 使用此值来指定扩展org.mybatis.generator.api.IntrospectedColumn的类的标准名称。如果要在计算列信息时更改代码生成器的行为,则使用此方法。

子元素:

  • <property> (0…N)
  • <plugin> (0…N)
  • <commentGenerator> (0 or 1)
  • <connectionFactory> (connectionFactory 或 jdbcConnection 是必需的)
  • <jdbcConnection> (connectionFactory 或 jdbcConnection 是必需的)
  • <javaTypeResolver> (0 or 1)
  • <javaModelGenerator> (1 是必需的)
  • <sqlMapGenerator> (0 or 1)
  • <javaClientGenerator> (0 or 1)
  • <table> (1…N)
7.3.1.3.1、property

下表列出了可以通过<property>子元素指定的属性:

属性名 属性值
autoDelimitKeywords 如果为true,则MBG如果将SQL关键字用作表中的列名,则将分隔SQL关键字。MBG维护许多不同数据库的SQL关键字列表。但是,该列表可能并不完整。如果某个特定关键字不在MBG的列表中,则可以强制使用<columnOverride>分隔该列 。请参阅org.mybatis.generator.internal.db.SqlReservedWords类的源代码,以获取MBG可以识别的关键字列表。默认值为false。
beginningDelimiter 用作需要定界符的SQL标识符的开始标识符定界符的值。如果标识符包含空格,MBG将自动分隔SQL标识符。如果在<table>或<columnOverride>配置中特别要求,MBG还将分隔SQL标识符。默认值为双引号(“)。
endingDelimiter 用作需要定界符的SQL标识符的结束标识符定界符的值。如果标识符包含空格,MBG将自动分隔SQL标识符。如果在<table>或<columnOverride>配置中特别要求,MBG还将分隔SQL标识符。默认值为双引号(“)。
javaFileEncoding 使用此属性可以指定在处理Java文件时使用的编码。新生成的Java文件将以这种编码方式写入文件系统,而现有Java文件将在执行合并时以这种编码方式读取。如果未指定,则将使用平台默认编码。有关有效编码的信息,请参见java.nio.charset.Charset
javaFormatter 使用此属性可以为生成的Java文件指定用户提供的格式化程序的完整类名。该类必须实现org.mybatis.generator.api.JavaFormatter, 并且必须具有默认的(无参数)构造函数。每个上下文都包含Java格式化程序的单个实例。默认的Java格式化程序是 org.mybatis.generator.api.dom.DefaultJavaFormatter
targetJava8 使用此属性可以指定生成的代码可以使用Java 8+功能。例如,用于集合实例化的菱形运算符。有效值为truefalse。默认值为true
kotlinFileEncoding 使用此属性可以指定使用Kotlin文件时要使用的编码。新生成的Kotlin文件将以这种编码方式写入文件系统。如果未指定,则将使用平台默认编码。有关有效编码的信息,请参见java.nio.charset.Charset
kotlinFormatter 使用此属性可以为生成的Kotlin文件指定用户提供的格式化程序的完整类名。该类必须实现org.mybatis.generator.api.KotlinFormatter, 并且必须具有默认的(无参数)构造函数。每个上下文都包含Kotlin格式化程序的单个实例。默认的Kotlin格式化程序是 org.mybatis.generator.api.dom.DefaultKotlinFormatter
xmlFormatter 使用此属性可以为生成的XML文件指定用户提供的格式化程序的完整类名。该类必须实现org.mybatis.generator.api.XmlFormatter, 并且必须具有默认的(无参数)构造函数。每个上下文都包含XML格式化程序的单个实例。默认的XML格式化程序是 org.mybatis.generator.api.dom.DefaultXmlFormatter。默认格式器使用XML DOM类中内置的格式。
7.3.1.3.2、plugin

<plugin>元素用于定义插件。插件可用于扩展或修改MyBatis Generator(MBG)生成的代码。此元素是<context>元素的子元素。可以在上下文中指定任意数量的插件。插件将按照配置中列出的顺序进行调用。

必填属性:

属性 描述
type 实现插件的类的完全限定名称。该类必须实现org.mybatis.generator.api.Plugin接口 ,并且必须具有公共的默认构造函数。请注意,扩展适配器类org.mybatis.generator.api.PluginAdapter 比实现整个接口要容易得多。

**可选属性:**无

子元素:

  • <property> (0 or 1)
7.3.1.3.3、commentGenerator

<commentGenerator>元素用于定义注释生成器的属性。Comment Generator用于为MyBatis Generator(MBG)生成的各种元素(Java字段,Java方法,XML元素等)生成注释。默认的Comment Generator将JavaDoc注释添加到所有生成的Java元素中,以在Eclipse插件中启用Java合并功能。而且,注释会添加到每个生成的XML元素中。注释的目的还在于通知用户元素已生成且需要重新生成。此元素是<context>元素的可选子元素。

默认实现是org.mybatis.generator.internal.DefaultCommentGenerator。如果您只想修改某些行为,则默认实现是为可扩展性而设计的。

**必填属性:**无

可选属性:

属性 描述
type 这可以用于指定用户提供的注释生成器的类型。该类必须实现org.mybatis.generator.api.CommentGenerator接口, 并且必须具有公共的默认构造函数。该属性还接受特殊值DEFAULT,在这种情况下将使用默认实现(与未指定类型具有相同的效果)。

子元素:

  • <property> (0…N)

支持属性:

下表列出了可以通过<property>子元素指定的默认注释生成器的属性:

属性名 属性值
suppressAllComments 此属性用于指定MBG是否在生成的代码中包含任何注释。该属性支持以下值:false,这是默认值。如果属性为false或未指定,则所有生成的元素将包括注释,指示该元素是生成的元素。如果该属性为true,则不会将注释添加到任何生成的元素。
**警告:**如果将此值设置为true,则将禁用所有代码合并。如果禁用所有注释,则可能会发现UnmergeableXmlMappersPlugin有用。这将使生成器遵守XML文件的覆盖标志。
suppressDate 此属性用于指定MBG是否在生成的注释中包括生成时间戳。该属性支持以下值:假这是默认值。 如果属性为false或未指定,则所有生成的注释将包括生成元素时的时间戳。真正如果该属性为true,则不会将时间戳添加到生成的注释中。
addRemarkComments 此属性用于指定MBG是否在生成的注释中包括来自数据库的表和列注释。该属性支持以下值:false,这是默认值。如果属性为false或未指定,则在生成元素时,所有生成的注释将包含数据库的表和列注释。真正当该属性为true时,数据库的表和列注释将添加到生成的注释中。
**警告:**如果 suppressAllComments 选项为true,则将忽略此选项。
dateFormat 将日期写入生成的注释时要使用的日期格式字符串。此字符串将用于构造java.text.SimpleDateFormat对象。可以在此处指定该对象的任何有效格式字符串。默认情况下,日期字符串将来自java.util.DatetoString()方法。从1.3.4开始。
**警告:**如果 suppressAllComments 选项为true,则将忽略此选项。
**警告:**如果 suppressDate 选项为true,则将忽略此选项。

简单示例:

<commentGenerator>
  <property name="suppressDate" value="true" />
</commentGenerator>
7.3.1.3.4、connectionFactory

<connectionFactory>元素用于指定连接工厂,以获取内省表所需的数据库连接。MyBatis Generator使用JDBC的DatabaseMetaData类来发现您在配置中指定的表的属性。每个<context>元素都需要一个<connectionFactory>或<jdbcConnection >元素。

**必填属性:**无

可选属性:

属性 描述
type 这可以用于指定用户提供的连接工厂的类型。该类必须实现org.mybatis.generator.api.ConnectionFactory接口, 并且必须具有公共的默认构造函数。该属性还接受特殊值DEFAULT,在这种情况下将使用默认实现(与未指定类型具有相同的效果)。

子元素:

  • <property>(0…N)

支持属性:

注意:对于默认连接工厂,超出下面详细说明的任何指定属性都将添加到JDBC驱动程序的属性中。

下表列出了可以使用<property>子元素指定的默认连接工厂的属性:

属性名 属性值
driverClass 此属性用于指定JDBC驱动程序的标准类名。默认连接工厂需要此属性。
connectionURL 此属性用于指定数据库的JDBC连接URL。默认连接工厂需要此属性。
userId 此属性用于指定连接的用户ID(用户名)
password 此属性用于指定连接的密码。

简单示例:

<connectionFactory>
  <property name="driverClass" value="com.mysql.jdbc.Driver"/>
  <property name="connectionURL" value="jdbc:mysql://localhost:3306/mybatis_crud"/>
  <property name="userId" value="root"/>
  <property name="password" value="123456"/>
</connectionFactory>
7.3.1.3.5、jdbcConnection

<jdbcConnection>元素用于指定内省表所需的数据库连接的属性。MyBatis Generator使用JDBC的DatabaseMetaData类来发现您在配置中指定的表的属性。每个<context>元素都需要一个<connectionFactory>或<jdbcConnection >元素。

必填属性:

属性 描述
driverClass 用于访问数据库的JDBC驱动程序的标准类名。
connectionURL 用于访问数据库的JDBC连接URL。

可选属性:

属性 描述
userId 用于连接数据库的用户标识。
password 用于连接数据库的密码。

子元素:

  • <property> (0…N)

注意:此处指定的任何属性都将添加到JDBC驱动程序的属性中。

简单示例:

<jdbcConnection 
	driverClass="com.mysql.jdbc.Driver" 
	connectionURL="jdbc:mysql://localhost:3306/mybatis_crud" 
	userId="root" 
	password="123456" />
7.3.1.3.6、javaTypeResolver

<javaTypeResolver>元素用于定义Java Type Resolver的属性。Java类型解析器用于根据数据库列信息计算Java类型。缺省的Java Type Resolver尝试通过尽可能替换Integral类型(Long,Integer,Short等)来使JDBC DECIMAL和NUMERIC类型更易于使用。如果这种行为是不希望的,请将属性“ forceBigDecimals”设置为“ true”。如果您想要不同于默认行为的行为,也可以替换自己的实现。此元素是<context>元素的可选子元素。

注意:如果使用MyBatis3Kotlin运行时,则生成器将在适用时自动将Java类型转换为它们相应的Kotlin等效项。

**必填属性:**无

可选属性:

属性 描述
type 这可以用来指定用户提供的Java类型解析器。该类必须实现org.mybatis.generator.api.JavaTypeResolver接口 ,并且必须具有公共的默认构造函数。该属性还接受特殊值DEFAULT,在这种情况下将使用默认实现(与未指定类型具有相同的效果)。

子元素:

  • <property> (0…N)

支持属性:

下表列出了可以通过<property>子元素指定的默认Java类型解析器的属性:

学习MyBatis3这一篇就够了

简单示例:

此元素指定我们始终要对DECIMAL和NUMERIC列使用java.math.BigDecimal类型:

<javaTypeResolver>
  <property name="forceBigDecimals" value="true" />
</javaTypeResolver>
7.3.1.3.7、javaModelGenerator

<javaModelGenerator>元素用于定义Java模型生成器的属性。Java模型生成器将构建与自省表匹配的主键类,记录类和“按示例查询”类。此元素是<context>元素的必需子元素。

必填元素:

属性 描述
targetPackage 这是将放置生成的类的包。在默认生成器中,属性“ enableSubPackages”控制如何计算实际包。如果为true,则计算出的包将是targetPackage加上表的目录和架构的子包(如果存在)。如果为false(默认值),则计算出的包将与targetPackage属性中指定的包完全相同。MyBatis Generator将根据所生成软件包的需要创建文件夹。
targetProject 这用于为生成的对象指定目标项目。在Eclipse环境中运行时,它指定将在其中保存对象的项目和源文件夹。在其他环境中,该值应该是本地文件系统上的现有目录。如果该目录不存在,MyBatis Generator将不会创建该目录。

**可选元素:**无

子元素:

  • <property> (0…N)

支持属性:

下表列出了可以通过<property>子元素指定的默认Java模型生成器的属性:

学习MyBatis3这一篇就够了

简单示例:

该元素指定我们始终希望将生成的类放置在“'test.model”包中,并且希望使用基于表模式和目录的子包。我们还希望修剪字符串列。

<javaModelGenerator targetPackage="test.model"
     targetProject="\MyProject\src">
  <property name="enableSubPackages" value="true" />
  <property name="trimStrings" value="true" />
</javaModelGenerator>
7.3.1.3.8、sqlMapGenerator

<sqlMapGenerator>元素用于定义SQL映射生成器的属性。SQL Map Generator为每个自省表构建MyBatis格式的SQL map XML文件。

仅当您选择的javaClientGenerator需要XML时,此元素才是<context>元素的必需子元素。

基于MyBatis Dynamic SQL的运行时不会生成XML,并且如果指定了该元素,则会忽略该元素。

如果未指定javaClientGenerator,则适用以下规则:

  • 如果指定sqlMapGenerator,则MBG将仅生成SQL映射XML文件和模型类。
  • 如果未指定sqlMapGenerator,则MBG仅生成模型类。

必填属性:

属性 描述
targetPackage 这是将放置生成的SQL Map的程序包。在默认生成器中,属性“ enableSubPackages”控制如何计算实际包。如果为true,则计算出的包将是targetPackage加上表的目录和架构的子包(如果存在)。如果为false(默认值),则计算出的包将与targetPackage属性中指定的包完全相同。MyBatis Generator(MBG)将根据生成的软件包的需要创建文件夹。
targetProject 这用于为生成的SQL映射指定目标项目。在Eclipse环境中运行时,它指定将在其中保存对象的项目和源文件夹。在其他环境中,该值应该是本地文件系统上的现有目录。如果该目录不存在,MBG将不会创建该目录。

**可选属性:**无

子元素:

  • <property> (0…N)

支持属性:

下表列出了可以通过<property>子元素指定的默认SQL Map生成器的属性:

物业名称 物业价值
enableSubPackages 此属性用于选择MBG是否将根据自省表的目录和架构为对象生成不同的Java包。例如,假设在模式MYSCHMA中有一个表MYTABLE。还要假设targetPackage属性设置为“ com.mycompany”。如果此属性为true,则将为表生成的SQL Map放置在包“ com.mycompany.myschema”中。如果该属性为false,则将生成的SQL Map放置在“ com.mycompany”架构中。默认值为false。

简单示例:

该元素指定我们始终希望将生成的SQL Map放置在“'test.model”包中,并且希望基于表模式和目录使用子包。

<sqlMapGenerator targetPackage="test.model"
     targetProject="\MyProject\src">
  <property name="enableSubPackages" value="true" />
</sqlMapGenerator>
7.3.1.3.9、javaClientGenerator

<javaClientGenerator>元素用于定义Java客户端生成器的属性。Java客户端生成器构建Java接口和类,以方便使用所生成的Java模型和XML映射文件。对于MyBatis,生成的对象采用mapper接口的形式。此元素是<context>元素的可选子元素。如果未指定此元素,则MyBatis Generator(MBG)将不会生成Java客户端接口和类。

必填属性:

学习MyBatis3这一篇就够了

**可选属性:**无

子元素:

  • <property> (0…N)

支持属性:

学习MyBatis3这一篇就够了

简单示例:

该元素指定我们始终希望将生成的接口和对象放置在“'test.model”包中,并且希望基于表模式和目录使用子包。它还指定我们要生成映射程序接口,该接口引用MyBatis3的XML配置文件。

<javaClientGenerator targetPackage="test.model"
     targetProject="\MyProject\src" type="XMLMAPPER">
  <property name="enableSubPackages" value="true" />
</javaClientGenerator>
7.3.1.3.10、table

<table>元素用于选择数据库中的表以进行自省。选定的表将导致为每个表生成以下对象:

  • MyBatis格式化的SQL Map文件
  • 构成表的“模型”的一组类,包括:
    • 一个与表的主键匹配的类(如果表具有主键)。
    • 一个类,用于匹配表中不在主键中的字段和非BLOB字段。如果有一个,此类将扩展主键。
    • 一个用于保存表中任何BLOB字段(如果有)的类。该类将扩展前两个类之一,具体取决于表的配置。
    • 用于在不同的“按示例”方法(selectByExample,deleteByExample)中生成动态where子句的类。
  • (可选)MyBatis映射器界面

必须至少指定一个<table>元素作为<context>元素的必需子元素。您可以指定无限的表元素。

MyBatis Generator(MBG)尝试自动处理数据库标识符的区分大小写。在大多数情况下,无论您为catalog,schema和tableName 属性指定了什么,MBG都能找到表。MBG的过程遵循以下步骤:

  1. 如果catalogschematableName属性中的任何一个都包含空格,则MBG将根据指定的确切大小写查找表。在这种情况下,MBG将自动在生成的SQL中分隔表标识符。
  2. 否则,如果数据库报告标识符存储为大写,则MBG会自动将任何表标识符转换为大写。
  3. 否则,如果数据库报告标识符存储为小写,则MBG会自动将任何表标识符转换为小写。
  4. 其他MBG将根据指定的确切大小写查找表。

在大多数情况下,此过程可以完美运行。但是,在某些情况下它将失败。例如,假设您创建一个像这样的表:

create table "myTable" (
     ...some columns
)

由于表名是有界的,因此即使数据库通常以大写形式存储标识符,大多数数据库也会使用指定的精确大小写来创建表。在这种情况下,您应该在表配置中指定属性 delimitIdentifiers =“ true”

必填属性:

属性 描述
tableName 数据库表的名称(不包括架构或目录)。如果需要,指定的值可以包含SQL通配符。

可选属性:

学习MyBatis3这一篇就够了

子元素:

  • <property> (0…N)
  • <generatedKey> (0 or 1)
  • <domainObjectRenamingRule> (0 or 1)
  • <columnRenamingRule> (0 or 1)
  • <columnOverride> (0…N)
  • <ignoreColumn> (0…N)

支持属性:

下表列出了可以通过<property>子元素指定的默认SQL Map生成器的属性:

学习MyBatis3这一篇就够了

简单示例:

该元素指定我们始终希望为模式MYSCHEMA中的表MYTABLE生成代码。我们还希望忽略表中名为“ fred”的列,并且希望覆盖列“ BEG_DATE”,以便生成的属性名称为“ startDate”。

<table tableName="MYTABLE" schema="MYSCHEMA">
  <ignoreColumn column="fred"/>
  <columnOverride column="BEG_DATE" property="startDate"/>
</table>

7.4、快速使用

第一步:创建一个全新的工程mybatis-mbg

第二步:创建一个lib文件夹,把以下依赖添加进去,然后全部选中,右键Build Path一下

  • mybatis-3.5.5.jar
  • mybatis-generator-core-1.4.0.jar
  • log4j-1.2.17.jar
  • mysql-connector-java-5.1.47-bin.jar

第三步:拷贝之前工程的log4j.xml日志配置到src目录下

第四步:在工程根目录下创建generatorConfig.xml,用来配置生成代码的具体配置信息

<!DOCTYPE generatorConfiguration PUBLIC
 "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
 "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
	<context id="mybatis_crud" targetRuntime="MyBatis3">
		<!-- 数据库连接信息配置 -->
		<jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatis_crud" userId="root" password="123456" />
		<!-- JavaBean的生成策略 -->
		<javaModelGenerator targetPackage="com.caochenlei.example.dao" targetProject="./src">
			<property name="enableSubPackages" value="true" />
			<property name="trimStrings" value="true" />
		</javaModelGenerator>
		<!-- SqlMapper的生成策略 -->
		<sqlMapGenerator targetPackage="com.caochenlei.example.mapper" targetProject="./src">
			<property name="enableSubPackages" value="true" />
		</sqlMapGenerator>
		<!-- JavaDao的生成策略 -->
		<javaClientGenerator type="XMLMAPPER" targetPackage="com.caochenlei.example.mapper" targetProject="./src">
			<property name="enableSubPackages" value="true" />
		</javaClientGenerator>
		<!-- 数据表与JavaBean的映射 -->
		<table tableName="department" domainObjectName="Department" />
		<table tableName="employee" domainObjectName="Employee" />
	</context>
</generatorConfiguration>

第五步:在src目录下创建MBGRunner.java,用于启动代码生成器,代码如下:

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;

public class MBGRunner {
	public static void main(String[] args) throws Exception {
		List<String> warnings = new ArrayList<String>();
		boolean overwrite = true;
		File configFile = new File("generatorConfig.xml");
		ConfigurationParser cp = new ConfigurationParser(warnings);
		Configuration config = cp.parseConfiguration(configFile);
		DefaultShellCallback callback = new DefaultShellCallback(overwrite);
		MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
		myBatisGenerator.generate(null);
	}
}

第六步:运行main方法,然后刷新工程,你会发现多了一点东西,如下,这就是生成的代码:

学习MyBatis3这一篇就够了

第八章 MyBatis3的工作原理

2020-09-30日前更新

第九章 MyBatis3的插件开发

2020-09-30日前更新

相关标签: ORM