文章目录
1、MyBatis 入门
1.1、介绍
MyBatis 作为一款优秀的持久层框架(ORM 框架),其支持自定义 SQL、存储过程以及高级映射。几乎免除了所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
主要特点
- 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个 Jar 文件 + 配置几个 SQL 映射文件。易于学习,易于使用。通过文档和源代码,可以比较完全的掌握他的设计思路和实现。
- 设计灵活:MyBatis 不会对应用程序或者数据库的现有设计强加任何影响。SQL 写在 XML 里,便于统一管理和优化。通过 SQL 语句可以满足操作数据库的所有需求。
- 降低耦合:通过提供 DAO 层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。SQL 和代码的分离,提高了可维护性。
- 关系映射:提供映射标签,支持对象与数据库的 ORM 字段关系映射。
- 动态 SQL:提供 XML 标签,支持编写动态 SQL。
相关网址
- 官方中文文档:https://mybatis.org/mybatis-3/zh/index.html。
- GitHub 下载:https://github.com/mybatis/mybatis-3/releases。
- mybatis-3.5.7.zip:包含 Mybatis 驱动、Log4j 日志等等 Jar 包,以及 MyBatis 官方英文文档。
- Source code (zip):源码包 Windows 版本。
- Source code (tar.gz):源码包 Linux 版本(相比 Windows 版本其保留有文件权限)。
如何使用 MyBatis
- Java 项目:首先导入 Jar 包,然后创建一个接口两个 XML(映射接口、配置文件、映射文件),最后使用相关 API 调用即可。
- Maven 项目:导入 Maven 依赖即可,其他的与上面相同。
Maven 依赖
- MyBatis 依赖:MyBatis、PageHelper。
- JDBC 依赖:MySQL Connector。
- 其他依赖:Junit、Lombok。
<!--MyBatis 依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<!--JDBC 依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<!--其他依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
数据库
CREATE DATABASE IF NOT EXISTS `mybatis` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE `mybatis`;
CREATE TABLE IF NOT EXISTS `book` (
`bid` INT COMMENT '主键',
`bname` VARCHAR(100) COMMENT '名称',
`bcount` INT COMMENT '数量',
PRIMARY KEY (`bid`)
);
INSERT INTO `book` (`bid`, `bname`, `bcount`) VALUES
('1', 'Java 基础', '1'),
('2', 'Java 中级', '2'),
('3', 'Java 高级', '3');
1.2、入门示例
步骤
- 一、创建 Maven 项目,导入 Maven 依赖。
- 二、创建数据库、实体类。
- 三、创建映射接口、映射文件。
- 四、创建配置文件(jdbc.properteis、mybatis-config.xml)。
- 五、测试。
入门示例
-
一、创建 Maven 项目,导入 Maven 依赖。
-
二、创建数据库、实体类。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
private Integer bid;
private String bname;
private Integer bcount;
}
- 三、创建映射接口、映射文件。
public interface BookMapper {
List<Book> selectAll();
}
<?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.feng.dao.BookMapper">
<select id="selectAll" resultType="com.feng.pojo.Book">
select * from book
</select>
</mapper>
- 四、创建配置文件(jdbc.properteis、mybatis-config.xml)。
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=test123!
<?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="jdbc.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/feng/dao/BookMapper.xml"/>
</mappers>
</configuration>
- 五、测试。
public class T01_Start {
@Test
public void test01() throws IOException {
// 1、读取 MyBatis 配置文件,获取 SqlSession 实例
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 2、获取映射接口实例,执行方法
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Book> bookList = mapper.selectAll();
bookList.forEach(System.out::println);
// 3、关闭 SqlSession
sqlSession.close();
}
}
关于无法找到 MyBatis 映射文件
MyBatis 映射文件放在源码包(例如 com.feng.dao 或者 com.feng.mapper),执行测试时发生异常,提示无法找到 MyBatis 映射文件。
原因分析:MyBatis 映射文件属于 XML 文件,如果放在源码包,执行测试时,其不会被编译到 target 目录。
解决方式:
- 方式一、将 MyBatis 映射文件放到 resource/com/feng/dao 目录。
- 方式二、发生异常时手动将将 MyBatis 映射文件复制到 target/com/feng/dao 目录,重新测试。
- 方式三、配置 Maven 静态资源过滤。
<!--Maven 静态资源过滤-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
1.3、核心 API
MyBatis 相比 JDBC,可以大大简化 Java 代码,容易理解,方便维护。
SqlSession 作为 MyBatis 的核心接口,用于获取 MyBatis 映射接口实例、执行 SQL 语句、提交与回滚事务等等。其由 SqlSessionFactory 实例创建,SqlSessionFactory 由 SqlSessionFactoryBuilder 实例创建。
如果搭配使用依赖注入框架(例如 Spring),SqlSession 会被创建并注入该容器,不需要再使用 SqlSessionFactory 或者 SqlSessionFactoryBuilder。具体可以参考 Mybatis-Spring。
SqlSessionFactoryBuilder
SqlSessionFactoryBuilder 提供多个重载的 build() 方法,用于从不同地方的资源中加载并创建 SqlSessionFactory 实例。
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader) {}
public SqlSessionFactory build(Reader reader, String environment) {}
public SqlSessionFactory build(Reader reader, Properties properties) {}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {}
public SqlSessionFactory build(InputStream inputStream) {}// 常用
public SqlSessionFactory build(InputStream inputStream, String environment) {}
public SqlSessionFactory build(InputStream inputStream, Properties properties) {}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {}
public SqlSessionFactory build(Configuration config) {}
}
最常用的 build(InputStream inputStream),用于从 MyBatis 配置文件中加载 SqlSessionFactory 实例。只需将 MyBatis 配置文件转换为 InputStream 传入即可。这里可以使用 MyBatis 提供的 org.apache.ibatis.io.Resources 工具类,其专门用于加载配置文件。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="{root}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
上面提到 SqlSessionFactoryBuilder 可以从不同地方的资源中加载并创建 SqlSessionFactory 实例。注意,如果这些信息同时存在多个地方,加载顺序如下:
- 首先,读取 properties 的子标签 property 中的信息。
- 其次,读取 properties 引入外部数据源文件中的信息(resource/url),并覆盖之前的信息。
- 然后,读取 environments 的子标签 property 中的信息,并覆盖之前的信息。
- 最后,读取方法参数中的信息,并覆盖之前的信息。
因此方法参数优先级最高,environments 优先级较高,resource/url 优先级中等,properties 优先级最低。
最后一个 build(Configuration config),用于直接从 Java 代码配置中加载并创建 SqlSessionFactory 实例,可以完全取代 MyBatis 配置文件(后面细说)。
SqlSessionFactory
SqlSessionFactory 提供多个重载的 openSession() 方法,用于创建 SqlSession 实例。并且需要考虑以下内容:
- 事务处理:自动提交还是手动提交。
- 数据库连接:如何获取数据库连接。
- 执行 SQL 语句:PreparedStatement 或者批量更新。
- 运行时检查 MyBatis 配置。
public interface SqlSessionFactory {
SqlSession openSession();// 手动提交事务
SqlSession openSession(boolean autoCommit);// true 自动提交
SqlSession openSession(Connection connection);// 使用自定义 Connection 获取 SqlSession
SqlSession openSession(TransactionIsolationLevel level);// 事务隔离级别
SqlSession openSession(ExecutorType execType);// ExecutorType 枚举:SIMPLE 为默认创建新的预处理语句,REUSE 为复用预处理语句,BATCH 为批量执行所有更新语句
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();// 运行时检查 MyBatis 配置
}
SqlSession
SqlSession 作为 MyBatis 的核心接口。方法分类如下:
- 获取 MyBatis 映射接口实例:getMapper()。
- 执行 SQL:select()、delete()、update()、delete()。
- 提交回滚事务:commit()、commit(boolean force)、void rollback()、rollback(boolean force)。
- 关闭连接:close()。
- 缓存管理。
- 映射注解。
1.4、生命周期
如果使用依赖注入容器(例如 Spring),作用域与生命周期直接交给容器管理。
SqlSessionFactoryBuilder
仅仅用于创建 SqlSessionFactory,创建后便不再需要。因此其最佳作用域是方法作用域(局部变量)。
SqlSessionFactory
用于创建 SqlSession,而且每次请求时都需要创建一个 SqlSession,程序运行期间应该一直存在,并且不需要重新创建。因此其最佳作用域是应用作用域。建议使用单例模式或者静态单例模式。
SqlSession
用于执行 SQL 语句,线程不安全,不能被共享。因此其最佳作用域是请求作用域。每次请求之后创建一个 SqlSession,返回响应之后关闭这个 SqlSession。关闭 SqlSession 非常重要,为了确保始终能够执行,可以将其放到 finally 块中。
1.5、MyBatis 工具类
如何获取 SqlSession
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
MybatisUtil 类(标准单例模式)
public class MybatisUtil {
private static final SqlSessionFactory sqlSessionFactory;
private MybatisUtil() {
}
static {
String resource = "mybatis-config.xml";
InputStream is = null;
try {
is = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
}
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
示例
- 测试
public class T02_MyBatisUtil {
@Test
public void test01() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Book> bookList = mapper.selectAll();
bookList.forEach(System.out::println);
sqlSession.close();
}
}
2、MyBatis 配置文件
2.1、介绍
MyBatis 配置文件(mybatis-config.xml)包含 MyBatis 的相关设置与一些属性信息。层次结构如下:
<?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> 属性
<settings> 设置
<typeAliases> 类型别名
<typeHandlers> 类型处理器
<objectFactory> 对象工厂
<plugins> 插件
<environments> 环境配置
<environment> 环境变量
<transactionManager> 事务管理器
<dataSource> 数据源
<databaseIdProvider> 数据库厂商标识
<mappers> 映射器
</configuration>
注意:这些顶级标签具有先后顺序,并且只能存在一个。
2.2、属性(properties)
属性用于配置数据源信息,或者引入外部数据源文件动态替换,以供环境配置使用。
标签说明
- resource/url 属性:引入外部 Properties 数据源文件。resource 为类路径,url 为网络路径。
- property 子标签:数据源属性键值对。name 为名称,value 为值并且可以使用 ${} 动态替换。
<properties resource="jdbc.properties"/>
<properties url="jdbc.properties"/>
<properties>
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</properties>
<properties>
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="test123!"/>
</properties>
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=test123!
2.3、设置(settings)
设置用于调整设置 MyBatis,可以改变 MyBatis 的运行时行为。(可选,详见官网中文文档)
标签说明
- setting 子标签:设置信息键值对。name 为名称,value 为值。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="cacheEnabled" value="true"/>
</settings>
2.4、类型别名(typeAliases)
类型别名可以为 Java 实体类型设置一个缩写名字,仅仅用于 MyBatis 映射文件中的参数类型与返回值类型,以便替换冗余的全限定类名。
标签说明
- typeAlias 子标签(单个设置):根据全限定类名设置类型别名。type 为全限定类名,Alias 为类型别名。
- package 子标签(批量设置):根据包名设置类型别名。name 为包名,默认别名为类名小写(如果同时存在 @Alias 注解,别名为注解值)。
<typeAliases>
<typeAlias type="com.feng.pojo.Book" alias="book"/>
<package name="com.feng.pojo"/><!--如果同时存在 @Alias 注解,别名为注解值-->
</typeAliases>
@Data
@AllArgsConstructor
@NoArgsConstructor
// @Alias("myBook")// 别名为 myBook
public class Book {
private int bid;
private String bname;
private int bcount;
}
内置 Java 类型别名
基本数据类型的类型别名为前面加“_”。
引用数据类型的类型别名为类名小写。主要包括:包装类、String 类、Date 类、Object 类及其数组、集合类(List、ArrayList、Map、HashMap、Collection、Iterator)、大数据类及其数组(BigDecimal、BigInteger、BigDecimal[]、BigInteger[])。
注意:这些内置 Java 类型别名不区分大小写,并且基本数据类型与包装类的类型别名可以替换使用。例如:映射接口的返回值类型为 int,对应映射文件的 resultType 存在多种写法。
public interface BookMapper {
int selectCount();
}
<select id="selectCount" resultType="int">
select count(*) from book
</select>
<select id="selectCount" resultType="_int">
select count(*) from book
</select>
<select id="selectCount" resultType="INT">
select count(*) from book
</select>
<select id="selectCount" resultType="Integer">
select count(*) from book
</select>
<select id="selectCount" resultType="_integer">
select count(*) from book
</select>
<select id="selectCount" resultType="integer">
select count(*) from book
</select>
<select id="selectCount" resultType="INTEGER">
select count(*) from book
</select>
2.5、类型处理器(typeHandlers)
类型处理器用于实现 Java 类型与 JDBC 类型按照合适的方式互相转换。详细说明:
- MyBatis 执行 SQL 语句前,设置预处理语句(PreparedStatement)的参数,会使用类型处理器将 Java 类型转换为合适的 JDBC 类型。底层调用的是 PreparedStatement 的 set() 方法。注意:如果在映射语句中使用 ${} 替换 #{},或者明确指定 statementType=“[STATEMENT|CALLABLE]”,则不支持。
- MyBatis 执行 SQL 语句后,处理结果集(ResultSet)的结果,会使用类型处理器将 JDBC 类型转换为合适的 Java 类型。底层调用的是 ResultSet 的 get() 方法。
MyBatis 内置很多类型处理器用于类型处理(详见官网中文文档,源码见 org.apache.ibatis.type 包)。如果需要处理非标准的、或者不支持的数据类型可以重写已有的类型处理器,或者自定义类型处理器。
重写已有的类型处理器步骤(不太建议)
- 首先继承已有的类型处理器,重写四个方法,泛型不变,只需更改方法实现。
- 其次配置这个类型处理器。
自定义类型处理器步骤
- 首先继承 org.apache.ibatis.type.BaseTypeHandler 抽象类,重写四个抽象方法,泛型为需要处理的 Java 类型。一个 setXXXX() 方法用于设置预处理语句(PreparedStatement)的参数类型,三个 getXXXX() 抽象方法用于处理结果集(ResultSet)的结果类型。
- 其次配置这个类型处理器。
标签说明
- typeHandler 子标签(单个设置):配置类型处理器。handler 为类型处理器的全限定类名,javaType 为 Java 类型,jdbcType 为 JDBC 类型。需要注意:这种写法如果同时使用 @MappedTypes 与 @MappedJdbcTypes 注解,javaType/jdbcType 会被覆盖。
- package 子标签(批量设置):配置类型处理器。name 为类型处理器所在包名。注意:这种写法必须配合 @MappedTypes 与 @MappedJdbcTypes 注解。
<typeHandlers>
<typeHandler handler="com.feng.typeHandler.DIYDateTypeHandler" javaType="date" jdbcType="VARCHAR"/>
<package name="com.feng.typeHandler"/><!--这种写法必须配合 @MappedTypes 与 @MappedJdbcTypes 注解-->
</typeHandlers>
注解说明
- @MappedTypes:注解在类型处理器类,表示处理的 Java 类型,参数同类型处理器的泛型。
- @MappedJdbcTypes:注解在类型处理器类,表示处理的 JDBC 类型。
自定义类型处理器步骤(要求 Java 类型为 Date,JDBC 类型为 VARCHAR)
- 步骤一、数据库添加字段(类型为 VARCHAR),实体类添加字段(类型为 Java.Util.Date)。
- 步骤二、映射接口添加方法,映射文件添加映射语句。注意:使用类型处理器,必须指定具体生效字段,否则 MyBatis 无法识别。设置参数时配置在 SQL 语句中,处理结果时配置在 resultMap 结果映射中。
- 步骤三、创建自定义类型处理器类,并在配置文件中进行配置。
- 步骤四、测试。
示例
- 数据库、实体类
ALTER TABLE `book` ADD `bdate` VARCHAR(100) COMMENT '添加时间';
private Date bdate;
- 映射接口、映射文件
// 类型处理器测试
int insert(Book book);
Book selectById(int id);
<!--类型处理器测试-->
<insert id="insert" parameterType="book">
insert into book (bid, bname, bcount, bdate) values (#{bid}, #{bname}, #{bcount}, #{bdate, typeHandler = com.feng.typeHandler.DIYDateTypeHandler})
</insert>
<resultMap id="selectById" type="book">
<id property="bid" column="bid"/>
<result property="bname" column="bname"/>
<result property="bcount" column="bcount"/>
<result property="bdate" column="bdate" typeHandler="com.feng.typeHandler.DIYDateTypeHandler"/>
</resultMap>
<select id="selectById" parameterType="int" resultMap="selectById">
select * from book where bid = #{bid};
</select>
- 自定义类型处理器类、MyBatis 配置文件
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(Date.class)
public class DIYDateTypeHandler extends BaseTypeHandler<Date> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(parameter));
}
@Override
public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
try {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(rs.getString(columnName));
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
@Override
public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
try {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(rs.getString(columnIndex));
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
@Override
public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
try {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(cs.getString(columnIndex));
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
<typeHandlers>
<package name="com.feng.typeHandler"/><!--这种写法必须配合 @MappedTypes 与 @MappedJdbcTypes 注解-->
</typeHandlers>
- 测试
public class T02_DIYDateTypeHandler {
// 插入
@Test
public void test01() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
int count = mapper.insert(new Book(4, "Java 高级", 4, new Date()));
System.out.println("count = " + count);
sqlSession.commit();
sqlSession.close();
}
// 查询
@Test
public void test02() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = mapper.selectById(4);
System.out.println(book);
sqlSession.close();
}
}
# 插入:对象属性为 Date,参数类型为 String
==> Preparing: insert into book (bid, bname, bcount, bdate) values (?, ?, ?, ?)
==> Parameters: 4(Integer), Java 高级(String), 4(Integer), 2022-11-11 21:25:13(String)
<== Updates: 1
count = 1
# 查询:结果集类型为 String,对象属性为 Date
==> Preparing: select * from book where bid = ?;
==> Parameters: 4(Integer)
<== Columns: bid, bname, bcount, bdate
<== Row: 4, Java 高级, 4, 2022-11-11 21:08:37
<== Total: 1
Book(bid=4, bname=Java 高级, bcount=4, bdate=Fri Nov 11 21:08:37 CST 2022)
2.6、对象工厂(objectFactory)
MyBatis 将结果集中的数据封装为对象之前,先会使用一个对象工厂实例创建一个对象。默认对象工厂仅仅用于实例化目标类,可以调用无参、或者有参构造方法。如果需要返回特定对象,可以自定义对象工厂。
自定义对象工厂实现步骤
- 步骤一、继承 DefaultObjectFactory 类,重写四个方法,这些方法全部来自 ObjectFactory 接口。
- 两个 create():创建实例(一个处理无参构造,一个处理有参构造)。
- isCollection():判断实例对象是否为集合。
- setProperties():初始化对象工厂实例时调用该方法。
- 步骤二、配置对象工厂。
- 步骤三、测试。
objectFactory 标签说明
- type 属性:自定义对象工厂的全限定类名。
- property 子标签(可选):配置自定义对象工厂的初始化属性。键值对,name 为名称 value 为值。
<objectFactory type="com.feng.objectFactory.DIYObjectFactory">
<property name="username" value="张三"/>
</objectFactory>
示例
- 步骤一、继承 DefaultObjectFactory 类,重写四个方法,这些方法全部来自 ObjectFactory 接口。
public class DIYObjectFactory extends DefaultObjectFactory {
private Properties properties;
@Override
public <T> T create(Class<T> type) {
System.out.println("调用了无参构造");
System.out.println("this.getProperties() = " + this.getProperties());
return super.create(type);
}
@Override
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
System.out.println("调用了有参构造");
System.out.println("this.getProperties() = " + this.getProperties());
return super.create(type, constructorArgTypes, constructorArgs);
}
@Override
public <T> boolean isCollection(Class<T> type) {
return super.isCollection(type);
}
@Override
public void setProperties(Properties properties) {
System.out.println(this.getProperties());
this.properties = properties;
}
public Properties getProperties() {
return properties;
}
}
- 步骤二、配置对象工厂。
<objectFactory type="com.feng.objectFactory.DIYObjectFactory">
<property name="username" value="张三"/>
</objectFactory>
- 步骤三、测试。
public class T03_DIYObjectFactory {
@Test
public void test01() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = mapper.selectById02(1);
System.out.println(book);
sqlSession.close();
}
}
Opening JDBC Connection
Created connection 1207608476.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47faa49c]
==> Preparing: select * from book where bid = ?;
==> Parameters: 1(Integer)
调用了无参构造
this.getProperties() = {username=张三}
调用了有参构造
this.getProperties() = {username=张三}
<== Columns: bid, bname, bcount, bdate
<== Row: 1, Java 基础, 1, 2022-11-11 08:08:08
调用了无参构造
this.getProperties() = {username=张三}
调用了有参构造
this.getProperties() = {username=张三}
<== Total: 1
Book(bid=1, bname=Java 基础, bcount=1, bdate=Fri Nov 11 08:08:08 CST 2022)
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47faa49c]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47faa49c]
Returned connection 1207608476 to pool.
2.7、插件拦截器(plugins)
MyBatis 允许在执行映射文件中的映射语句时,进行拦截调用。允许插件拦截调用以下方法:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
拦截器使用步骤
- 首先实现 Interceptor 接口,至少重写核心方法 intercept()。
- 然后配置插件拦截器。
标签说明
- plugin 子标签:配置插件拦截器,可以自定义或者使用第三方。interceptor 为拦截器类。
- property 子标签(可选):配置插件拦截器的初始化属性。键值对,name 为名称 value 为值。
<plugins>
<plugin interceptor="com.feng.interceptor.DIYInterceptor">
<property name="username" value="李四"/>
</plugin>
</plugins>
1、自定义插件拦截器
// 拦截方法签名
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class DIYInterceptor implements Interceptor {
private Properties properties = new Properties();
// 拦截器核心方法
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("执行目标方法之前");
System.out.println("当前用户是:" + properties.get("username"));
Object proceed = invocation.proceed();
System.out.println("执行目标方法之后");
return proceed;
}
// 获取 MyBatis 配置文件中的参数
@Override
public void setProperties(Properties properties) {
this.properties = properties;
}
}
<plugins>
<plugin interceptor="com.feng.interceptor.DIYInterceptor">
<property name="username" value="李四"/>
</plugin>
</plugins>
- 测试
public class T04_DIYInterceptor {
@Test
public void test01() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Book> bookList = mapper.selectAll();
bookList.forEach(System.out::println);
sqlSession.close();
}
}
2、使用第三方插件拦截器示例(PageHelper 分页)
导入 Maven 依赖,配置拦截器插件即可。
<!--PageHelper 分页助手插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>
- 测试
public class T05_PageHelper {
@Test
public void test01() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
// 设置分页参数,然后查询所有
Page<Object> page = PageHelper.startPage(1, 2);
List<Book> books = mapper.selectAll();
// 获取 PageInfo
PageInfo<Book> pageInfo = new PageInfo<>(books);
System.out.println("Total 总条数:" + pageInfo.getTotal());
System.out.println("Size 总页数:" + pageInfo.getSize());
System.out.println("PageNum 第几页:" + pageInfo.getPageNum());
System.out.println("PageSize 页大小 :" + pageInfo.getPageSize());
System.out.println("PrePage 上一页:" + pageInfo.getPrePage());
System.out.println("NextPage 下一页:" + pageInfo.getNextPage());
System.out.println("IsFirstPage 是否是第一页:" + pageInfo.isIsFirstPage());
System.out.println("IsLastPage 是否是最后一页:"+ pageInfo.isIsLastPage());
sqlSession.close();
}
}
2.8、环境配置(environments)
MyBatis 允许同时配置多套环境,可以将映射文件中的映射语句应用到多个、或者多种数据库中。例如:开发、测试、生产环境需要不同的数据库配置,等等类似场景。注意:虽然可以同时配置多套环境,但是一个 SqlSessionFactory 实例只能选择其中一个。如果需要连接多个数据库,则需要创建多个 SqlSessionFactory 实例,一个数据库对应一个 SqlSessionFactory 实例。
标签说明
- default 属性:默认使用的环境 ID。
- environment 子标签:环境。id 为当前环境 ID。
- transactionManager 子标签:事务管理器。type 为事务管理器类型。
- dataSource 子标签:数据源。type 为数据源类型。
- properties 属性:数据源属性键值对。name 为名称,value 为值并且可以使用 ${} 动态替换。
<environments default="development"><!--引用一个环境 ID-->
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="test123!"/>
</dataSource>
</environment>
</environments>
事务管理器类型(transactionManager)
MyBatis 提供两种事务管理器类型 type=“[JDBC|MANAGED]”(源码见 org.apache.ibatis.transaction 包):
- JDBC:使用 JDBC 提交与回滚事务。
- MANAGED:MyBatis 不负责提交与回滚事务,而是将其交给其他容器进行管理,例如 Web 服务器的应用上下文对象、Spring 容器等等。
数据源类型(dataSource)
MyBatis 提供三种内置数据源类型 type=“[UNPOOLED|POOLED|JNDI]”(源码见 org.apache.ibatis.dataSource 包):
- UNPOOLED:每次请求打开与关闭数据库连接。UNPOOLED 需要配置以下内容:
- 必选配置:driver、url、username、password。
- 可选配置:autoCommit、defaultTransactionIsolationLevel、defaultNetworkTimeout。
- POOLED:使用数据库连接池,每次请求结束会将数据库连接返还到”池“中。POOLED 需要配置以下内容:
- 包含以上 UNPOOLED 中的配置。
- 其他与“池”相关的配置,可选并提供默认值。
- JNDI:使用 JNDI。MyBatis 使用 JNDI 中配置好的数据源获取数据库连接。例如 EJB(企业级 JavaBean)或者应用服务器 Tomcat 等等(不做介绍)。
数据源类型源码
- UnpooledDataSource
public class UnpooledDataSource implements DataSource {
// 必选配置
private String driver;// JDBC 驱动的 Java 全限定类名
private String url;// JDBC 驱动连接数据库的 URL 地址
private String username;// 登录数据库的用户名
private String password;// 登录数据库的密码
// 可选配置
private Boolean autoCommit;// 是否自动提交事务
private Integer defaultTransactionIsolationLevel;// 默认事务隔离级别
private Integer defaultNetworkTimeout;// 默认网络连接超时时间(单位:毫秒)
...
}
- PooledDataSource
public class PooledDataSource implements DataSource {
// 包含 UNPOOLED 中的配置
private final UnpooledDataSource dataSource;
// 其他与“池”相关的配置,可选并提供默认值
protected int poolMaximumActiveConnections = 10;
protected int poolMaximumIdleConnections = 5;
protected int poolMaximumCheckoutTime = 20000;
protected int poolTimeToWait = 20000;
protected int poolMaximumLocalBadConnectionTolerance = 3;
protected String poolPingQuery = "NO PING QUERY SET";
protected boolean poolPingEnabled;
protected int poolPingConnectionsNotUsedFor;
...
}
UNPOOLED 与 POOLED 日志对比
- UNPOOLED:每次请求打开与关闭数据库连接。
- POOLED:每次请求结束会将数据库连接返还到”池“中。
Opening JDBC Connection
......
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@69e1dd28]
Opening JDBC Connection
Created connection 1926096844.
......
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@72cde7cc]
Returned connection 1926096844 to pool.
2.9、数据库厂商标识(databaseIdProvider)
数据库厂商标识允许 MyBatis 根据不同的数据库厂商执行不同的映射语句。使用步骤如下:
- 首先在 MyBatis 配置文件中配置数据库厂商。
- 然后在映射文件的映射语句中配置 databaseId 即可。
注意:如果映射文件的映射语句配置 databaseId,则 id 相同的映射语句可以存在多个。带有 databaseId 的为专用语句,不带 databaseId 的为通用语句。MyBatis 会同时加载所有语句,并且优先执行专用语句。
标签说明
- type:数据库厂商标识类型。提供一种 type=“DB_VENDOR”。用于处理数据库厂商标识,详见 org.apache.ibatis.mapping.VendorDatabaseIdProvider 类。
- property 子标签:配置数据库厂商别名键值对。name 为厂商名称,value 为别名。
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle"/>
</databaseIdProvider>
示例
- 配置文件、映射文件
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle"/>
</databaseIdProvider>
<!--数据库厂商标识测试-->
<select id="selectAll" resultType="book" databaseId="mysql">
-- 数据库厂商标识测试
select * from book
</select>
- 测试
public class T06_DataBaseId {
@Test
public void test01() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
List<Book> bookList = bookMapper.selectAll();
bookList.forEach(System.out::println);
sqlSession.close();
}
}
2.10、映射器(mappers)
MyBatis 配置完成之后,需要告诉 MyBatis 去哪里执行 SQL 语句,需要使用映射器关联 MyBatis 映射文件/接口。
标签说明
- mapper 子标签(单个设置):设置映射文件的位置。resource 为类路径,url 为网络路径,class 为全限定类名。
- package 子标签(批量设置):设置映射接口的位置。name 为映射文件所在包名。
注意:如果使用注解,映射器只能使用下面两种。
<mappers>
<mapper resource="com/feng/dao/BookMapper.xml"/>
<mapper url="file:///com/feng/dao/BookMapper.xml"/>
<mapper class="com.feng.dao.BookMapper"/>
<package name="com.feng.dao"/>
</mappers>
3、MyBatis 映射文件 - 单表
3.1、介绍
命名空间(namespace)
一个 MyBatis 映射文件对应一个的 MyBatis 映射接口。命名空间用于关联对应的映射接口。
<?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.feng.dao.BookMapper">
</mapper>
顶级标签
<select> 映射查询语句
<insert> 映射插入语句
<delete> 映射删除语句
<update> 映射更新语句
<resultMap> 结果映射,封装处理数据库结果集中的对象
<cache> 该命名空间的二级缓存配置
<cache-ref> 引用其他命名空间的二级缓存配置
<sql> SQL 片段抽取复用
select、insert、delete、update 标签说明
- select、insert、delete、update 通用属性:
- id:唯一标识符,可被其他地方引用(嵌套)。
- parameterType:参数类型,可以是全限定了类名或者别名。可选,MyBatis 可以通过类型处理器(TypeHandler)进行类型推断。
- flushCache:如果设置为 true,只要调用当前语句,本地缓存与二级缓存都会被清空。对 select 默认为 false。对 insert、update、delete 默认为 true。
- timeout:请求结果超时时间,单位为秒。默认为未设置(依赖数据库驱动)。
- statementType:使用哪种语句类型。可选 STATEMENT、PREPARED、CALLABLE。默认为 PREPARED。
- databaseId:如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
- select 特有属性(结果集相关):
- resultType:结果类型,可以是全限定类名或者别名。注意:如果返回集合,应该设置为集合包含的类型,而不是集合本身的类型。resultType 与 resultMap 只能二选一。
- resultMap:结果映射,引用一个 resultMap 标签。最强大最复杂。resultType 与 resultMap 只能二选一。
- useCache:如果设置为 true,只要调用当前语句,二级缓存就会将结果存储起来。默认为 true。
- fetchSize:请求结果返回行数。默认为未设置(依赖数据库驱动)。
- resultSetType:使用哪种结果集类型。可选 FORWARD_ONLY、SCROLL_SENSITIVE、SCROLL_INSENSITIVE、DEFAULT(未设置)。默认为未设置(依赖数据库驱动)。
- resultOrdered:仅仅用于嵌套查询。默认为 false。
- resultSets:仅仅用于多结果集。
- insert、update 特有属性(主键相关):
- useGeneratedKeys:使用主键自增。需要 MySQL、SQL Server 等数据库设置 ID 自增。默认为 false。
- keyProperty:生成键值对应的数据库列名。
- keyColumn:设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
3.2、基本 CRUD、分页、模糊
MyBatis 配置完成以后,仅需创建映射接口,映射文件,就可进行测试。注意:更新操作(insert、delete、update)需要提交回滚事务,查询操作(select)则不需要。
- 手动提交回滚事务:sqlSession.commit()、sqlSession.rollback()。
- 自动提交回滚事务:获取 SqlSession 实例时设置自动提交即可。例如 return sqlSessionFactory.openSession(true)。
基本 CURD
- selectAll、insert、delete、update、select。
分页查询三种方式
- 方式一、使用 PageHelper 分页助手插件(详见插件拦截器)。
- 方式二、使用 MySql 的 Limit 关键字分页。
- 方式三、使用 MyBatis 自带的 RowBounds 工具分页。
模糊查询四种方式
- 方式一、在 Java 代码中拼接通配符。
- 方式二、在 SQL 语句中拼接通配符,注意:需要使用 ${} 代替 #{}。
示例
- 映射接口、映射文件
public interface BookMapper {
// 基本 CURD
List<Book> selectAll();// 查询所有
void insert(Book book);// 插入
void delete(int id);// 删除
void update(Book book);// 更新
Book selectById(int id);// 根据 ID 查询
// 分页查询
List<Book> selectPageByLimit(Map<String, Integer> map);// Limit
List<Book> selectPageByRowBounds();// RowBounds
// 模糊查询
List<Book> selectLike01(String name);
List<Book> selectLike02(String name);
List<Book> selectLike03(String name);
List<Book> selectLike04(String name);
}
<?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.feng.dao.BookMapper">
<!--基本 CRUD-->
<select id="selectAll" resultType="book">
select * from book
</select>
<insert id="insert" parameterType="book">
insert into book (bid, bname, bcount) values (#{bid}, #{bname}, #{bcount})
</insert>
<delete id="delete" parameterType="int">
delete from book where bid = #{bid}
</delete>
<update id="update" parameterType="book">
update book set bname = #{bname}, bcount = #{bcount} where bid = #{bid}
</update>
<select id="selectById" parameterType="int" resultType="book">
select * from book where bid = #{bid}
</select>
<!--分页查询-->
<select id="selectPageByLimit" parameterType="map" resultType="book">
select * from book limit #{offset}, #{limit}
</select>
<select id="selectPageByRowBounds" resultType="book">
select * from book
</select>
<!--模糊查询-->
<select id="selectLike01" parameterType="string" resultType="book">
select * from book where bname like #{bname}
</select>
<select id="selectLike02" parameterType="string" resultType="book">
select * from book where bname like '%${bname}%'
</select>
<select id="selectLike03" parameterType="string" resultType="book">
select * from book where bname like concat('%', #{bname}, '%')
</select>
<select id="selectLike04" parameterType="string" resultType="book">
select * from book where bname like concat('%', '${bname}', '%')
</select>
</mapper>
- 测试
// 基本 CRUD
public class T01_BaseCRUD {
@Test
public void test01() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Book> bookList = mapper.selectAll();
bookList.forEach(System.out::println);
sqlSession.close();
}
@Test
public void test02() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
mapper.insert(new Book(4, "Java 高级", 4));
sqlSession.commit();
sqlSession.close();
}
@Test
public void test03() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
mapper.delete(4);
sqlSession.commit();
sqlSession.close();
}
@Test
public void test04() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = mapper.selectById(1);
book.setBcount(100);
mapper.update(book);
sqlSession.commit();
sqlSession.close();
}
@Test
public void test05() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = mapper.selectById(1);
System.out.println(book);
sqlSession.close();
}
}
// 分页查询
public class T02_SelectPage {
@Test
public void test01() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Map<String, Integer> map = new HashMap<>();
map.put("offset", 0);
map.put("limit", 2);
List<Book> bookList = mapper.selectPageByLimit(map);
bookList.forEach(System.out::println);
sqlSession.close();
}
@Test
public void test02() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
List<Book> bookList = sqlSession.selectList("com.feng.dao.BookMapper.selectPageByRowBounds", null, new RowBounds(0, 2));
bookList.forEach(System.out::println);
sqlSession.close();
}
}
// 模糊查询
public class T03_SelectLike {
@Test
public void test01() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Book> bookList = mapper.selectLike01("%级%");
bookList.forEach(System.out::println);
sqlSession.close();
}
@Test
public void test02() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Book> bookList = mapper.selectLike02("级");
bookList.forEach(System.out::println);
sqlSession.close();
}
@Test
public void test03() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Book> bookList = mapper.selectLike03("级");
bookList.forEach(System.out::println);
sqlSession.close();
}
@Test
public void test04() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Book> bookList = mapper.selectLike04("级");
bookList.forEach(System.out::println);
sqlSession.close();
}
}
3.3、使用注解
使用 MyBatis 注解配置 SQL,可以完全代替 MyBatis 映射文件,甚至不写。
注解说明
MyBatis 注解位于 org.apache.ibatis.annotations 包下。
@Select 源码:
- String[] value():SQL 语句。
- String databaseId() default “”:数据库 ID。
- @interface List {}:包含 Select 注解。
public @interface Select {
String[] value();// SQL 语句
String databaseId() default "";// 数据库 ID
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface List {// 包含 Select 注解。
Select[] value();
}
}
需要注意,如果使用注解,映射器只能使用注册映射接口的形式。
<mappers>
<!--注册映射文件-->
<mapper resource="com/feng/dao/BookMapper.xml"/>
<mapper url="file:///com/feng/mapper/BookMapper.xml"/>
<!--注册映射接口-->
<mapper class="com.feng.dao.BookMapper"/>
<package name="com.feng.dao"/>
</mappers>
示例
- 映射接口
public interface BookMapper {
// 基本 CURD
@Select("select * from book")
List<Book> selectAll();// 查询所有
@Insert("insert into book (bid, bname, bcount) values (#{bid}, #{bname}, #{bcount})")
void insert(Book book);// 插入
@Delete("delete from book where bid = #{bid}")
void delete(int id);// 删除
@Update("update book set bname = #{bname}, bcount = #{bcount} where bid = #{bid}")
void update(Book book);// 更新
@Select("select * from book where bid = #{bid}")
Book selectById(int id);// 根据 ID 查询
// 分页查询
@Select("select * from book limit #{offset}, #{limit}")
List<Book> selectPageByLimit(Map<String, Integer> map);// Limit
@Select("select * from book")
List<Book> selectPageByRowBounds();// RowBounds
// 模糊查询
@Select("select * from book where bname like #{bname}")
List<Book> selectLike01(String name);
@Select("select * from book where bname like '%${bname}%'")
List<Book> selectLike02(String name);
@Select("select * from book where bname like concat('%', #{bname}, '%')")
List<Book> selectLike03(String name);
@Select("select * from book where bname like concat('%', '${bname}', '%')")
List<Book> selectLike04(String name);
}
- 配置文件
<mappers>
<!--<mapper class="com.feng.dao.BookMapper"/>-->
<package name="com.feng.dao"/>
</mappers>
3.4、#{} 与 ${} 的区别
#{} 称为参数占位符,MyBatis 会先创建 PreparedStatement 再传入参数,相当于 ?。通常用于传入 SQL 语句的参数。使用较多,非常安全。
${} 称为字符串拼接,MyBatis 会先传入参数拼接为完整 SQL 语句,再执行。通常用于传入 SQL 语句的表名、列名等等(分表)。特殊场景使用,会导致 SQL 注入风险,除非对这些数据加以校验。
3.5、参数映射
对于简单类型或者原始类型(int、Integer、String),MyBatis 直接使用他们的值作为参数。因此如果只有一个参数,可以不用指定 parameterType,并且这个参数名称可以随意命名。例如:
<select id="selectById" parameterType="int" resultType="book">
select * from book where bid = #{bid}
</select>
<select id="selectById" resultType="book">
select * from book where bid = #{id}
</select>
如果参数类型为 Java 对象(例如 Book),必须指定 parameterType,参数名称对应属性名称。例如:
<insert id="insert" parameterType="book">
insert into book (bid, bname, bcount) values (#{bid}, #{bname}, #{bcount})
</insert>
其次,如果使用类型处理器,参数需要指定 javaType、jdbcType、typeHandler,具体参考类型处理器,很少使用,使用上面两种简写方式即可。
#{bdate, javaType = date, jdbcType = VARCHAR, typeHandler = com.feng.typeHandler.DIYDateTypeHandler}
3.6、结果映射(resultMap)
结果映射用于复杂结果(具体参考多表查询),或者使用类型处理器处理 MyBaits 不支持的数据类型(具体参考类型处理器)。
关于实体类属性名与数据库列名不一致问题。有三种解决方式:
- 使用结果映射
- 开启驼峰命名
- 使用 SQL 语句别名
示例
- 使用结果映射
<!--结果映射-->
<select id="selectResultMap" resultMap="resultMap">
select * from book
</select>
<resultMap id="resultMap" type="book">
<result property="id" column="bid"/>
<result property="name" column="bname"/>
<result property="count" column="bcount"/>
</resultMap>
- 开启驼峰命名
<setting name="mapUnderscoreToCamelCase" value="true"/>
- 使用 SQL 语句别名
select bid id, bname name, bcount count from book;
select bid as id, bname as name, bcount as count from book;
3.7、缓存(了解即可)
缓存是存储在内存中的临时数据。通常会将用户经常查询的数据存放到缓存(内存)中,减少用户直接查询数据库(磁盘),从而提高效率,解决高并发系统的性能问题。
经常查询且不经常改变的数据,适合使用缓存。反之,不经常查询且经常改变的数据,不适合使用缓存。
MyBatis 缓存特性:
- 映射文件中的所有 select 语句结果会被缓存。
- 映射文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 根据缓存清理策略,自动清理缓存。
- 调用 clearCache() 方法,手动清理缓存。
- 调用 commit()、roolback() 方法,会刷新缓存。
MyBatis 提供两种缓存,一级缓存与二级缓存。用户访问顺序如下:首先是二级缓存,其次是一级缓存,最后是数据库。
一级缓存
也叫本地会话缓存(SqlSession),默认开启因此无需配置(没什么用)。
二级缓存
属于事务性的缓存,默认关闭并且需要手动配置。可以是不同的 SqlSession,由于其针对的是同一个命名空间(命名空间),因此可能存在问题:一个命名空间更改了某些数据,另一个命名空间缓存没有更新的情况(不建议使用)。
二级缓存标签说明
- cache:该命名空间的二级缓存配置。属性:
- eviction:二级缓存清除策略。提供四种 eviction = “[LRU|FIFO|SOFT|WEAK]”,LRU 为按照最近最少使用移除,FIFO 为按照先进先出移除,SOFT 为软引用(基于垃圾回收器与软引用规则移除),WEAK 为弱引用(更积极地基于垃圾回收器与弱引用规则移除)。默认为 LRU。
- flushInterval:二级缓存刷新间隔时间。单位:毫秒。默认为不设置,也就是不刷新,只在调用该语句时刷新。
- size:二级缓存引用对象数量。需要注意运行环境或者服务器的内存大小。默认为 1024。
- readOnly:是否只读。提供两种 readOnly= “[true|false]”,true 为只读,返回用户相同的缓存对象实例,提升性能, false 为可读可写,返回用户不同的缓存对象实例(通过序列化拷贝),速度慢更安全。默认为 false。
- cache-ref:引用其他命名空间的二级缓存配置。
- namespace:引用命名空间。
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<cache-ref namespace=""/>
一级缓存示例
- 测试
public class T04_Cache {
// 一级缓存
@Test
public void test01() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Book> bookList = mapper.selectAll();
bookList.forEach(System.out::println);
System.out.println("========");
// 同一个 SqlSession
BookMapper mapper02 = sqlSession.getMapper(BookMapper.class);
sqlSession.getMapper(BookMapper.class);
List<Book> bookList02 = mapper02.selectAll();
bookList02.forEach(System.out::println);
sqlSession.close();
}
}
- 结果:第二次查询没有执行 SQL,数据来自缓存。
Opening JDBC Connection
Created connection 137460818.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8317c52]
==> Preparing: select * from book
==> Parameters:
<== Columns: bid, bname, bcount, bdate
<== Row: 1, Java 基础, 1, null
<== Row: 2, Java 中级, 2, null
<== Row: 3, Java 高级, 3, null
<== Total: 3
Book(bid=1, bname=Java 基础, bcount=1)
Book(bid=2, bname=Java 中级, bcount=2)
Book(bid=3, bname=Java 高级, bcount=3)
========
Book(bid=1, bname=Java 基础, bcount=1)
Book(bid=2, bname=Java 中级, bcount=2)
Book(bid=3, bname=Java 高级, bcount=3)
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8317c52]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8317c52]
Returned connection 137460818 to pool.
二级缓存示例
- 首先在配置文件中开启二级缓存。
- 其次在映射文件中配置二级缓存。
<setting name="cacheEnabled" value="true"/>
// 二级缓存测试方法
List<Book> select();
<!--二级缓存配置-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<select id="select" resultType="book" useCache="true">
select * from book
</select>
- 测试
// 二级缓存
@Test
public void test02() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Book> bookList = mapper.select();
bookList.forEach(System.out::println);
sqlSession.close();
System.out.println("========");
// 重新获取 SqlSession
SqlSession sqlSession02 = MybatisUtil.getSqlSession();
BookMapper mapper02 = sqlSession02.getMapper(BookMapper.class);
sqlSession02.getMapper(BookMapper.class);
List<Book> bookList02 = mapper02.select();
bookList02.forEach(System.out::println);
sqlSession02.close();
}
- 结果:第二次查询没有执行 SQL,因为不是同一个 SqlSession,所以数据来自二级缓存。(缓存命中率 0.5:Cache Hit Ratio [com.feng.dao.BookMapper]: 0.5)。
Cache Hit Ratio [com.feng.dao.BookMapper]: 0.0
Opening JDBC Connection
Created connection 285133380.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@10feca44]
==> Preparing: select * from book
==> Parameters:
<== Columns: bid, bname, bcount, bdate
<== Row: 1, Java 基础, 1, null
<== Row: 2, Java 中级, 2, null
<== Row: 3, Java 高级, 3, null
<== Total: 3
Book(bid=1, bname=Java 基础, bcount=1)
Book(bid=2, bname=Java 中级, bcount=2)
Book(bid=3, bname=Java 高级, bcount=3)
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@10feca44]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@10feca44]
Returned connection 285133380 to pool.
========
Cache Hit Ratio [com.feng.dao.BookMapper]: 0.5
Book(bid=1, bname=Java 基础, bcount=1)
Book(bid=2, bname=Java 中级, bcount=2)
Book(bid=3, bname=Java 高级, bcount=3)
4、MyBatis 映射文件 - 多表
4.1、多表模型介绍
多表模型
- 一对一(例如:一个 Person 对应一个 Address):
- 数据库:选择其中一张为主表,另一张为从表(外键)。例如:PERSON 为主表,ADDRESS 为从表(外键)。
- 实体类:两个实体类中分别包含一个对象。例如:Person 包含一个 Address 对象,Address 包含一个 Person 对象。
- 一对多/多对一(例如:一个 Customer 对应多个 Order):
- 数据库:选择“一的”表为主表,“多的”表为从表(外键)。例如:CUSTOMER 为主表,ORDER 为从表(外键)。
- 实体类:“一的”实体类中包含一个集合,“多的”实体类中包含一个对象。例如:Customer 包含一个 Order 集合,Order 包含一个 Customer 对象。
- 多对多(例如:多个 User 对应多个 Role):
- 数据库:使用中间表作为从表,至少包含两列外键,关联另外两张主表。例如 USERROLE 为从表(两列外键)。
- 实体类:两个实体类中分别包含一个集合。
使用外键注意事项
传统情况使用数据库级别的外键确定表与表之间的关系。优点是可以确保数据的完成性,缺点是级联操作非常麻烦。
实际开发中要求不能使用数据库级别的外键,表与表之间使用 ID 关联即可但不设置为外键,所有情况使用代码解决。
级联操作
- 级联添加:先添加主表,再添加从表(外键)。
- 级联删除:先删除从表(外键),再删除主表。
- 级联修改:因为主键通常不会修改,外键可以随意修改。因此级联修改不受影响。
多表查询
- 可以使用关联查询、嵌套查询。
4.2、一对一
一对一(例如:一个 Person 对应一个 Address)
- 数据库:选择其中一张为主表,另一张为从表(外键)。例如:PERSON 为主表,ADDRESS 为从表(外键)。
- 实体类:两个实体类中分别包含一个对象。例如:Person 包含一个 Address 对象,Address 包含一个 Person 对象。
示例
- 数据库、实体类
USE `mybatis`;
-- 主表
CREATE TABLE `person` (
`person_id` INT,
`person_name` VARCHAR(50),
PRIMARY KEY (`person_id`)
);
INSERT INTO `person` (`person_id`, `person_name`) VALUES
(1, '张三'),
(2, '李四'),
(3, '王五');
-- 从表
CREATE TABLE `address` (
`address_id` INT,
`address_name` VARCHAR(50),
`person_id` INT,
PRIMARY KEY (`address_id`),
CONSTRAINT `address_person_id` FOREIGN KEY (`person_id`) REFERENCES `person` (`person_id`)
);
INSERT INTO `address` (`address_id`, `address_name`, `person_id`) VALUES
(1, '陕西西安', 1),
(2, '河南郑州', 2),
(3, '四川成都', 3);
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private Integer personId;
private String presonName;
private Address address;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address {
private Integer addressId;
private String addressName;
private Person person;
}
- 映射接口、映射文件
public interface AddressDao {
List<Address> selectAll();
List<Address> selectAll02();
}
<?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.feng.dao.AddressDao">
<!--标准查询-->
<select id="selectAll" resultMap="selectAll" >
select * from address, person where address.person_id = person.person_id
</select>
<resultMap id="selectAll" type="address">
<result property="addressId" column="address_id"/>
<result property="addressName" column="address_name"/>
<association property="person" javaType="person">
<result property="personId" column="person_id"/>
<result property="personName" column="person_name"/>
</association>
</resultMap>
<!--嵌套查询-->
<select id="selectAll02" resultMap="selectAll02" >
select * from address
</select>
<resultMap id="selectAll02" type="address">
<result property="addressId" column="address_id"/>
<result property="addressName" column="address_name"/>
<association property="person" javaType="person" column="person_id" select="selectPerson"/>
</resultMap>
<select id="selectPerson" parameterType="int" resultType="person">
select * from person where person_id = #{personId}
</select>
</mapper>
4.3、一对多/多对一
一对多/多对一(例如:一个 Customer 对应多个 Order):
- 数据库:选择“一的”表为主表,“多的”表为从表(外键)。例如:CUSTOMER 为主表,ORDER 为从表(外键)。
- 实体类:“一的”实体类中包含一个集合,“多的”实体类中包含一个对象。例如:Customer 包含一个 Order 集合,Order 包含一个 Customer 对象。
示例
- 数据库、实体类
USE `mybatis`;
-- 主表
CREATE TABLE `customer` (
`customer_id` INT,
`customer_name` VARCHAR(50),
PRIMARY KEY (`customer_id`)
);
INSERT INTO `customer` (`customer_id`, `customer_name`) VALUES
(1, '刘德华'),
(2, '张学友'),
(3, '周润发');
-- 从表
CREATE TABLE `order` (
`order_id` INT,
`order_name` VARCHAR(50),
`customer_id` INT,
PRIMARY KEY (`order_id`),
CONSTRAINT `order_customer_id` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`customer_id`)
);
INSERT INTO `order` (`order_id`, `order_name`, `customer_id`) VALUES
(1, '小米14', 1),
(2, '小米15', 2),
(3, '小米16', 3),
(4, '苹果11', 1),
(5, '苹果12', 2),
(6, '苹果13', 3);
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Customer {
private Integer customerId;
private String customerName;
List<Order> orderList;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;
}
- 映射接口、映射文件
public interface CustomerDao {
List<Customer> selectAll();
List<Customer> selectAll02();
}
<?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.feng.dao.CustomerDao">
<select id="selectAll" resultMap="selectAll">
select * from customer, mybatis.order where customer.customer_id = order.customer_id
</select>
<resultMap id="selectAll" type="customer">
<result property="customerId" column="customer_id"/>
<result property="customerName" column="customer_name"/>
<collection property="orderList" javaType="list" ofType="order">
<result property="orderId" column="order_id"/>
<result property="orderName" column="order_name"/>
</collection>
</resultMap>
<select id="selectAll02" resultMap="selectAll02">
select * from customer
</select>
<resultMap id="selectAll02" type="customer">
<result property="customerId" column="customer_id"/>
<result property="customerName" column="customer_name"/>
<collection property="orderList" javaType="list" ofType="order" column="customer_id" select="selectOrder"/>
</resultMap>
<select id="selectOrder" parameterType="int" resultType="order">
select * from mybatis.order where customer_id = #{customerId}
</select>
</mapper>
4.4、多对多
多对多(例如:多个 User 对应多个 Role):
- 数据库:使用中间表作为从表,至少包含两列外键,关联另外两张主表。例如 USERROLE 为从表(两列外键)。
- 实体类:两个实体类中分别包含一个集合。
示例
- 数据库、实体类
USE `mybatis`;
-- 主表
CREATE TABLE `user` (
`user_id` INT,
`user_name` VARCHAR(50),
`user_password` VARCHAR(50),
PRIMARY KEY (`user_id`)
);
INSERT INTO `user` (`user_id`, `user_name`, `user_password`) VALUE
(1, '张三', '111'),
(2, '李四', '222'),
(3, '王五', '333' );
-- 主表
CREATE TABLE `role` (
`role_id` INT,
`role_name` VARCHAR(50),
PRIMARY KEY (`role_id`)
);
INSERT INTO `role` (`role_id`, `role_name`) VALUE
(1, '超级管理员'),
(2, '管理员'),
(3, '普通用户');
-- 中间表
CREATE TABLE `userrole` (
`user_id` INT,
`role_id` INT,
CONSTRAINT `userrole_id_01` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`),
CONSTRAINT `userrole_id_02` FOREIGN KEY (`role_id`) REFERENCES `role` (`role_id`)
);
INSERT INTO `userrole` (`user_id`, `role_id`) VALUES
(1, 1),
(1, 2),
(1, 3),
(2, 2),
(2, 3),
(3, 3);
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {
private Integer roleId;
private String roleName;
private List<User> userList;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer userId;
private String userName;
private String userPassword;
private List<Role> roleList;
}
- 映射接口、映射文件
public interface UserDao {
List<User> selectAll();
List<User> selectAll02();
}
<?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.feng.dao.UserDao">
<!--多对多标准查询-->
<select id="selectAll" resultMap="selectAll">
select * from user, role, userrole where user.user_id = userrole.user_id and role.role_id = userrole.role_id
</select>
<resultMap id="selectAll" type="user">
<result property="userId" column="user_id"/>
<result property="userName" column="user_name"/>
<result property="userPassword" column="user_password"/>
<collection property="roleList" javaType="list" ofType="role">
<result property="roleId" column="role_id"/>
<result property="roleName" column="role_name"/>
</collection>
</resultMap>
<!--多对多嵌套查询-->
<select id="selectAll02" resultMap="selectAll02">
select * from user
</select>
<resultMap id="selectAll02" type="user">
<result property="userId" column="user_id"/>
<result property="userName" column="user_name"/>
<result property="userPassword" column="user_password"/>
<collection property="roleList" javaType="list" ofType="role" column="user_id" select="selectUserRole"/>
</resultMap>
<select id="selectUserRole" parameterType="int" resultType="role">
select * from role, userrole where role.role_id = userrole.role_id and user_id = #{userId}
</select>
</mapper>
4.5、注解一对一
public interface PersonDao {
@Select("select * from person")
@Results({
@Result(property = "personId", column = "person_id"),
@Result(property = "personName", column = "person_name"),
@Result(
property = "address",
javaType = Address.class,
column = "person_id",
one = @One(select = "com.feng.dao.AddressDao.select")
)
})
List<Person> selectAll();
}
public interface AddressDao {
@Select("select * from address where person_id = #{personId}")
Address select(int id);
}
4.6、注解一对多/多对一
public interface CustomerDao {
@Select("select * from customer")
@Results({
@Result(property = "customerId", column = "customer_id"),
@Result(property = "customerName", column = "customer_name"),
@Result(
property = "orderList",
javaType = List.class,
column = "customer_id",
many = @Many(select = "com.feng.dao.OrderDao.selectByCustomerId")
)
})
List<Customer> selectAll();
}
public interface OrderDao {
@Select("select * from mybatis.order where customer_id = #{customerId}")
List<Order> selectByCustomerId(int id);
}
4.7、注解多对多
public interface UserDao {
@Select("select * from user")
@Results({
@Result(property = "userId", column = "user_id"),
@Result(property = "userName", column = "user_name"),
@Result(property = "userPassword", column = "user_password"),
@Result(
property = "roleList",
javaType = List.class,
column = "user_id",
many = @Many(select = "com.feng.dao.RoleDao.selectByUserId")
)
})
List<User> selectAll();
}
public interface RoleDao {
@Select("select * from role, userrole where role.role_id = userrole.role_id and user_id = #{userId}")
List<Role> selectByUserId(int id);
}
5、MyBatis 映射文件 - 动态 SQL
5.1、介绍
MyBatis 可以根据不同的条件,动态生成不同的 SQL 语句。通常用在两个地方:
- 多条件查询:如果某些字段为 null,希望 SQL 语句中不出现该字段,而不是将 null 值作为查询条件。
- 多字段更新:如果某些字段为 null,希望 SQL 语句中不出现该字段,而不是将 null 值作为数据插入。
-- 多条件查询
select * from book where bid = ? bname = ? and bcount = ?;
-- 多字段更新
update book set bname = ?, bcount = ? where bid = ?
动态 SQL 标签说明
- 条件语句 if:如果条件成立则拼接该语句。
- 条件语句 choose when otherwise:如果条件成立则拼接该语句,所有条件都不成立拼接 otherwise 中的语句。类似 Java 中的 switch case default 语句。
- 关键字 where:至少一个条件成立,则会生成 where 关键字,并且会去除多余的 and/or 关键字。通常搭配 if、choose when otherwise 使用。
- 关键字 set:至少一个条件成立,则会生成 set 关键字,并且会去除多余的逗号。通常搭配 if、choose when otherwise 使用。
- 通用语句 trim(代替 where、set):prefix 为前缀、suffix 为后缀、prefixOverrides 为前缀重写(自动去除不需要的符号)、suffixOverrides 为后缀重写(自动去除不需要的符号)。
- 循环语句 foreach:用来简化 SQL 语句中多次重复的内容。通常用于批量插入、删除、修改。例如 where in/or id = (?, ?, ?)、value (?, ?, ?), (?, ?, ?), (?, ?, ?) 等等。
- SQL 片段抽取复用
- sql:定义 SQL 片段。id 为唯一标识符。
- include:引用 SQL 片段。refid 为引用 SQL 片段的 ID。
这些标签可以自由搭配,非常灵活。
5.2、条件语句 if
// if
List<Book> selectIf(Book book);
<!--if 标签-->
<!--select * from book where 1 = 1 and bid = ? and bname = ? and bcount = ?-->
<select id="" parameterType="book" resultType="book">
select * from book where 1 = 1
<if test="bid != null">
and bid = #{bid}
</if>
<if test="bname != null">
and bname = #{bname}
</if>
<if test="bcount != null">
and bcount = #{bcount}
</if>
</select>
public class T01_If {
@Test
public void test01() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = new Book();
book.setBid(2);
List<Book> bookList = mapper.selectIf(book);
bookList.forEach(System.out::println);
sqlSession.close();
}
}
5.3、条件语句 choose when otherwise
// choose
List<Book> selectChoose(Book book);
<!--choose when other 标签-->
<!--select * from book where bid = ?-->
<select id="selectChoose" parameterType="book" resultType="book">
select * from book
<where>
<choose>
<when test="bid != null">
and bid = #{bid}
</when>
<when test="bname != null">
and bname = #{bname}
</when>
<otherwise>
and bcount = #{bcount}
</otherwise>
</choose>
</where>
</select>
public class T02_Choose {
@Test
public void test01() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = new Book();
book.setBid(2);
List<Book> bookList = mapper.selectChoose(book);
bookList.forEach(System.out::println);
sqlSession.close();
}
}
5.4、关键字 where
// where
List<Book> selectByCondition(Book book);
<!--where 标签-->
<!--select * from book where bid = ? and bname = ? and bcount = ?-->
<select id="selectByCondition" parameterType="book" resultType="book">
select * from book
<where>
<if test="bid != null">
and bid = #{bid}
</if>
<if test="bname != null">
and bname = #{bname}
</if>
<if test="bcount != null">
and bcount = #{bcount}
</if>
</where>
</select>
public class T03_Where {
@Test
public void test01() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = new Book();
book.setBid(2);
List<Book> bookList = mapper.selectByCondition(book);
bookList.forEach(System.out::println);
sqlSession.close();
}
}
5.5、关键字 set
// set
void update(Book book);
<!--set 标签-->
<!--update book set bname = ?, bcount = ? where bid = ?-->
<update id="update" parameterType="book">
update book
<set>
<if test="bname != null">
bname = #{bname},
</if>
<if test="bcount != null">
bcount = #{bcount},
</if>
</set>
where bid = #{bid}
</update>
public class T04_Set {
@Test
public void test01() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = new Book(3, "Java", 3);
mapper.update(book);
sqlSession.commit();
sqlSession.close();
}
}
5.6、通用语句 trim
// trim
List<Book> selectByCondition02(Book book);
void update02(Book book);
<!--trim 标签-->
<!--select * from book where bid = ? and bname = ? and bcount = ?-->
<select id="selectByCondition02" parameterType="book" resultType="book">
select * from book
<trim prefix="where" prefixOverrides="and">
<if test="bname != null">
and bname = #{bname}
</if>
<if test="bcount != null">
and bcount = #{bcount}
</if>
</trim>
</select>
<!--update book set bname = ?, bcount = ? where bid = ?-->
<update id="update02" parameterType="book">
update book
<trim prefix="set" suffixOverrides="," suffix="where">
<if test="bname != null">
bname = #{bname},
</if>
<if test="bcount != null">
bcount = #{bcount},
</if>
</trim>
bid = #{bid}
</update>
public class T05_Trim {
@Test
public void test01() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = new Book(null, null, 3);
List<Book> bookList = mapper.selectByCondition02(book);
bookList.forEach(System.out::println);
sqlSession.close();
}
@Test
public void test02() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = new Book(3, "Java", 3);
mapper.update02(book);
sqlSession.commit();
sqlSession.close();
}
}
5.7、循环语句 foreach
遍历 Array、List、Map
- 遍历 List 对象, collection 属性的值为 list。
- 遍历 Map 对象,collection 属性的值为 Map 集合中元素的键。
foreach 标签
- collection 属性:需要遍历的对象,Array 为 array,List 为 list,Map 没有。
- item 属性:集合迭代时元素的别名。
- open 属性:开始符号。
- separator 属性:分割符号。
- close 属性:结束符号。
- index 属性(可选):迭代 Array、List 时 index 为元素序号,迭代 Map 时 index 为元素的键。
批量操作
- 批量添加。
- 按照 ID 批量删除(注意:建议直接写明 where 关键字,不要使用 where 标签,因为 List 为空时则不会生成 where 关键字,导致删除所有数据)。
- 按照 ID 批量查询。
// foreach
void insertBatch(List<Book> bookList);
void deleteByIds(List<Integer> ids);
void deleteByIds02(List<Integer> ids);
List<Book> selectByIds(List<Integer> ids);
<!--foreach 标签-->
<!--insert into book (bid, bname, bcount) value (?, ?, ?), (?, ?, ?), (?, ?, ?)-->
<insert id="insertBatch" parameterType="list">
insert into book (bid, bname, bcount) value
<foreach collection="list" item="book" open="(" separator="), (" close=")">
#{book.bid}, #{book.bname}, #{book.bcount}
</foreach>
</insert>
<!--delete from book where bid in (? , ? , ?)-->
<delete id="deleteByIds" parameterType="list">
delete from book where
<foreach collection="list" item="id" open="bid in (" separator="," close=")">
#{id}
</foreach>
</delete>
<!--delete from book where bid in (? , ? , ?)-->
<delete id="deleteByIds02" parameterType="list">
delete from book
<where>
<foreach collection="list" item="id" open="bid in (" separator="," close=")">
#{id}
</foreach>
</where>
</delete>
<!--select * from book where bid in (? , ? , ?)-->
<select id="selectByIds" parameterType="list" resultType="book">
select * from book
<where>
<foreach collection="list" item="id" open="bid in (" separator="," close=")">
#{id}
</foreach>
</where>
</select>
public class T06_Foreach {
@Test
public void insertBatch() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Book> bookList = new ArrayList<>();
bookList.add(new Book(4, "设计模式 I", 99));
bookList.add(new Book(5, "设计模式 II", 99));
bookList.add(new Book(6, "设计模式 III", 99));
mapper.insertBatch(bookList);
sqlSession.commit();
sqlSession.close();
}
@Test
public void deleteByIds() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Integer> ids = Arrays.asList(4, 5, 6);
mapper.deleteByIds(ids);
sqlSession.commit();
sqlSession.close();
}
@Test
public void deleteByIds02() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Integer> ids = Arrays.asList(4, 5, 6);
mapper.deleteByIds(ids);
sqlSession.commit();
sqlSession.close();
}
@Test
public void SelectByIds() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Integer> ids = Arrays.asList(1, 2, 3);
List<Book> bookList = mapper.selectByIds(ids);
bookList.forEach(System.out::println);
sqlSession.close();
}
}
5.8、SQL 片段抽取复用
// sql include
List<Book> selectAll();
<!--sql include 标签-->
<sql id="selectAllSQL">
select *
</sql>
<select id="selectAll" resultType="book">
<include refid="selectAllSQL"/> from book
</select>
public class T07_SQLInclude {
@Test
public void test() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mpper = sqlSession.getMapper(BookMapper.class);
List<Book> bookList = mpper.selectAll();
bookList.forEach(System.out::println);
sqlSession.close();
}
}
6、MyBatis SQL 语句构建器(了解)
虽然 MyBatis 映射文件支持映射动态的 SQL 语句,但是这种写法非常麻烦。因此 MyBatis 也提供了使用 Java 代码生成 SQL 语句的特性。
public class BookSqlBuilder {
// Builder 写法
public String selectById() {
return new SQL()
.SELECT("*")
.FROM("book")
.WHERE("bid = #{bid}")
.toString();
}
// 匿名内部类写法
public String deleteById() {
return new SQL() {{
DELETE_FROM("book");
WHERE("bid = #{bid}");
}}.toString();
}
// 条件查询
public String selectByCondition(Book book) {
return new SQL() {{
SELECT("*");
FROM("book");
if (book.getBname() != null) {
WHERE("bname = #{bname}");
}
if (book.getBcount() != null) {
WHERE("bcount = #{bcount}");
}
}}.toString();
}
}
如何使用 SQL 语句构建器
- 首先,使用 SQL 类生成 SQL 语句(SQL 类继承 AbstractSQL 抽象类,位于 org.apache.ibatis.jdbc 包下)。
- 然后,定义 SqlBuilder 类,用于返回 SQL 语句。
- 最后,在映射接口中使用 @SelectProvider 注解即可。
注意:虽然 MyBatis 支持生成所有 SQL 语句,但是使用 LIMIT、OFFSET 等等方法生成 SQL 语句时,需要数据库本身的支持,否则会报错。
示例
- SqlBuilder
public class BookSqlBuilder {
// Builder 写法
public String selectById() {
return new SQL()
.SELECT("*")
.FROM("book")
.WHERE("bid = #{bid}")
.toString();
}
// 匿名内部类写法
public String deleteById() {
return new SQL() {{
DELETE_FROM("book");
WHERE("bid = #{bid}");
}}.toString();
}
// 条件查询
public String selectByCondition(Book book) {
return new SQL() {{
SELECT("*");
FROM("book");
if (book.getBname() != null) {
WHERE("bname = #{bname}");
}
if (book.getBcount() != null) {
WHERE("bcount = #{bcount}");
}
}}.toString();
}
}
- 映射接口
public interface BookMapper {
@SelectProvider(type = BookSqlBuilder.class, method = "selectById")
Book selectById(int id);
}
- 测试
public class T01_SqlBuilder {
@Test
public void test01() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = mapper.selectById(1);
System.out.println(book);
sqlSession.close();
}
@Test
public void test02() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Book> bookList = mapper.selectByCondition(new Book(null, null, null));
bookList.forEach(System.out::println);
sqlSession.close();
}
}
7、MyBatis 原理(了解)
8、MyBatis 总结
8.1、项目说明
mybatis(全部基于 Maven)
- mybatis01_start
- mybatis02_config
- mybatis03_mapper01
- mybatis04_mapper02
- mybatis05_dynamicsql
- mybatis06_sqlbuilder
8.2、项目资料
数据库
CREATE DATABASE IF NOT EXISTS `mybatis` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE `mybatis`;
CREATE TABLE IF NOT EXISTS `book` (
`bid` INT COMMENT '主键',
`bname` VARCHAR(100) COMMENT '书名',
`bcount` INT COMMENT '数量',
PRIMARY KEY (`bid`)
);
INSERT INTO `book` (`bid`, `bname`, `bcount`) VALUES
('1', 'Java 基础', '1'),
('2', 'Java 中级', '2'),
('3', 'Java 高级', '3');
-- 添加字段
ALTER TABLE `book` ADD `bdate` VARCHAR(100) COMMENT '添加时间';
Maven 依赖
- MyBatis 依赖:MyBatis、PageHelper。
- JDBC 依赖:MySQL Connector。
- 其他依赖:Junit、Lombok。
<!--MyBatis 依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<!--JDBC 依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<!--其他依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
8.3、常见问题
MySQL6 与 MySQL5 的区别
- MySQL6 驱动名称为 com.mysql.cj.jdbc.Driver,URL 需要添加时区。
- MySQL5 驱动名称为 com.mysql.jdbc.Driver,URL 不需要添加时区。
# MySQL6
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=test123!
# MySQL5
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=test123!