第一章:MyBatis的概念
MyBtais网址: https://mybatis.net.cn/
什么是MyBatis?
官方概念:MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。 补充: Mybatis也是一个半自动的ORM(Object Relation Mapping)框架
ORM(Object Relationship Mapping)对象关系映射的概念介绍:
- 对象: Java的实体类对象
- 关系:关系型数据库
- 映射:二者之间的对应关系
Java概念 |
数据库概念 |
类 |
表 |
属性 |
字段 |
对象 |
记录 |
与其它持久化层技术对比
- JDBC:
- SQL 夹杂在Java代码中耦合度高,导致硬编码内伤
- 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
- 代码冗长,开发效率低
- Hibernate 和 JPA
- 操作简便,开发效率高
- 程序中的长难复杂 SQL 需要绕过框架
- 内部自动生产的 SQL,不容易做特殊优化
- 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。
- 反射操作太多,导致数据库性能下降
- MyBatis
- 轻量级,性能出色
- SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
- 开发效率稍逊于HIbernate,但是完全能够接收
其他概念:
持久化的概念:对象的瞬时态和持久态相互转化。
瞬时转为持久的操作: 增加 insert;修改 update;删除 delete。
持久转瞬时的操作:查询 select。
序列化和反序列化的概念:
序列化把对象的状态转化为可存储的或可传输的状态(二进制字节流)
反序列化把可存储的或可传输的状态转化为对象的状态。
第二章: MyBtais的基本使用
2.1 环境的搭建
2.1.1 物理建模
CREATE TABLE book (
`bookid` INT (6),
`bookname` VARCHAR (300),
`price` FLOAT ,
`sortname` VARCHAR (150)
);
INSERT INTO `book` (`bookid`, `bookname`, `price`, `sortname`) VALUES('000001','Java实战',56,'技术开发');
INSERT INTO `book` (`bookid`, `bookname`, `price`, `sortname`) VALUES('000002','JavaWeb入门到提高',66,'开发');
INSERT INTO `book` (`bookid`, `bookname`, `price`, `sortname`) VALUES('000003','MySQL实战',64,'数据库');
INSERT INTO `book` (`bookid`, `bookname`, `price`, `sortname`) VALUES('000004','Mybatis实践开发',54,'框架');
2.1.2 逻辑建模
①:首先创建一个Maven module
②:创建Java实体类
注意:Java的实体类中,属性的类型不要使用基本数据类型,要使用包装类型。因为包装类型可以赋值为null,表示空,而基本数据类型不可以。
@Data // 这个是自动生成get、set等方法
@AllArgsConstructor // 创建有参构造器(所有参数)
@NoArgsConstructor // 创建无参构造器
public class Book {
private Integer bookId;
private String bookName;
private Float price;
private String sortName;
}
③:创建MyBatis的工具类
public class MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory;
// 声明Mybatis全局配置文件的路径
private final static String conf = "mybatis-conf.xml";
static {
try {
// 创建SqlSessionFactory对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 以输入流的形式加载Mybatis配置文件,并且创建创建SqlSessionFactory对象
InputStream is = Resources.getResourceAsStream(conf);
sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
} catch (IOException e) {
e.printStackTrace();
}
}
// 使用SqlSessionFactory对象开启一个会话
public static SqlSession getSqlSession(boolean flag){
return sqlSessionFactory.openSession();
}
// 自动开启事务
public static SqlSession getSqlSession(){
return getSqlSession(true);
}
// 关闭事务
public static void close(SqlSession session){
if(session != null){
// 关闭SqlSession
session.close();
}
}
}
2.1.3 搭建框架开发环境
要配置的环境位置如下图所示:
① :导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>demo02</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- MySql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<!-- MyBatis核心包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- 代码生成器-- 自动生成get和set方法 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<!-- junit框架 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<!-- 日志框架 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
</dependencies>
</project>
2.1.3.1 junit框架
导入依赖后就可以直接使用。
常用注解 |
作用 |
@Test |
此注解可以作为一个测试用例 |
@Before |
在调用每一个测试用例前,先执行此注解标记的方法 |
@After |
在调用完每一个测试用例后,都会执行此注解标记的方法 |
@BeforeClass |
所有测试用例调用前执行一次,在测试类没有实例化之前就已被加载,需用static修饰; |
@AfterClass |
所有测试用例调用后执行一次,在测试类没有实例化之前就已被加载,需用static修饰; |
@lgnore |
暂不执行该方法; |
2.1.3.2 log4j(日志)框架
首先要导入依赖,然后需要在MyBatis-conf.xml文件中配置。
配置日志的类型有:
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
配置格式如下。注意: setting的name是固定的,value只能取上面的几种类型。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
②:创建连接mysql的文件
url=jdbc:mysql://127.0.0.1:3306/数据库名?serverTimezone=GMT
driver=com.mysql.cj.jdbc.Driver
username=数据库账号名
password=数据库的密码
③: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>
<!-- 加载db.properties文件 -->
<properties resource="db.properties"/>
<settings>
<!-- 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- environments表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 -->
<environments default="development">
<!-- environment表示配置Mybatis的一个具体的环境 -->
<!-- 这样可以配置多个environment的环境,但environments的default值要与其中的一个environment的id值要相对应-->
<environment id="development">
<!-- Mybatis的内置的事务管理器 -->
<transactionManager type="JDBC"/>
<!-- 配置数据源 -->
<dataSource type="POOLED">
<!-- 建立数据库连接的具体信息 -->
<!-- 这里的value值与db.properties的键相对应-->
<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注册:指定Mybatis映射文件的具体位置 -->
<!-- mapper标签:配置一个具体的Mapper映射文件 -->
<!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 -->
<!-- 对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 -->
<mapper resource="com/wdzl/mapper/BookMapper.xml"/>
</mappers>
</configuration>
④:创建BookMapper.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属性:在Mybatis全局范围内找到一个具体的Mapper配置 -->
<!-- 引入接口后,为了方便通过接口全类名来找到Mapper配置文件,所以通常将namespace属性设置为接口全类名 -->
<mapper namespace="com.wdzl.dao.IBookDao">
<!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 -->
<!-- resultType属性:指定封装查询结果的Java实体类的全类名 -->
<select id="queryAll" resultType="com.wdzl.pojo.Book">
select bookid,bookname,price,sortname from book
</select>
</mapper>
注意:BookMapper.xml所在的目录要和mybatis-conf.xml中使用mapper标签配置的一致。
2.1.4 测试
①:编写dao接口
public interface IBookDao {
/**
* 查询所有图书
* @return
*/
public List<Book> queryAll();
/**
* 按条件查询图书
*/
public Book queryById(Integer bookId);
}
②:编写测试方法进行测试
// 查询所有图书
@Test
public void select(){
// 这个MyBatisUtil是上面写的的工具类
SqlSession session = MyBatisUtil.getSqlSession();
// 根据BookMapper接口的Class对象获取Mapper接口类型的对象
IBookDao mapper = session.getMapper(IBookDao.class);
// 调用BookMapper接口的方法完成对数据库的操作
List<Book> list = mapper.queryAll();
for (Book book : list) {
System.out.println(book);
}
MyBatisUtil.close(session);
}
// 按条件查询图书
@Test
public void selectById(){
SqlSession session = MyBatisUtil.getSqlSession();
IBookDao mapper = session.getMapper(IBookDao.class);
Book book = mapper.queryById(1);
System.out.println(book);
MyBatisUtil.close(session);
}
2.1.5 注意
MyBatis框架会在初始化时将XML文件读取进来,封装到对象中,再然后就都是Java代码的执行了,XML中的配置是没法执行的。
2.2 #{}与${}
2.2.1. #{}
Mybatis会在运行过程中,把配置文件中的SQL语句里面的#{}转换为“?”占位符,发送给数据库执行。防止了注入安全或特殊字符问题。
配置文件中的SQL:
<select id="queryById" resultType="com.wdzl.pojo.Book">
select bookid,bookname,price,sortname from book where bookid = #{bookid}
</select>
实际执行的Sql语句:
select bookid,bookname,price,sortname from book where bookid = ?
2.2.2 ${}
将来会根据${}拼字符串
使用场景:在SQL语句中,数据库表的表名不确定,需要外部动态传入,此时不能使用#{},因为数据库不允许表名位置使用问号占位符,此时只能使用${}。
配置文件中的SQL:
<select id="queryByName" resultType="com.wdzl.pojo.Book">
select bookid,bookname,price,sortname from book where bookname = '${bookName}'
</select>
实际执行的Sql语句:
select bookid,bookname,price,sortname from book where bookname = 'Java实战'
注意: 如果使用${}容易发生sql注入问题。因此只要能用#{}就不用 $ {}。
2.3 数据的输入
概念: 指在调用Dao层的接口时,数据传入的形式。
- 简单类型:只包含一个值的数据类型
①:基本数据类型:int、byte、short、double、……
②:基本数据类型的包装类型:Integer、Character、Double、……
③:字符串类型:String
- 复杂类型:包含多个值的数据类型
①:实体类类型:自定义类(Book)
②:集合类型:List、Set、Map、……
③:数组类型:int[]、String[]、……
2.3.1 单个简单类型参数
dao层中接口的方法:
public Book queryById(Integer bookId);
注意: 如果是传单个参数不需要指定parameterType,并且#{}中的名字可以任意,也不区分大小写。
SQL 语句:
<select id="queryByName" resultType="com.wdzl.pojo.Book">
select bookid,bookname,price,sortname from book where bookname = #{bookName}
</select>
2.3.2 实体类型参数
dao层中接口的方法:
public void insertBook(Book book);
注意: 如果是传实体类型参数需要指定parameterType,并且#{}中的名字需要与实体类中的属性保持一致,不然会报错。
SQL 语句:
<!-- 现在在这条SQL语句中,#{}中的表达式需要被用来从Book book实体类中获取bookid值、bookname、price、sortname的值 -->
<!-- 而我们从实体类中获取值通常都是调用getXxx()方法 -->
<!-- 而getXxx()方法、setXxx()方法定义了实体类的属性 -->
<!-- 定义属性的规则是:把get、set去掉,剩下部分的首字母小写,若后面还有单词则需要大写 -->
<!-- 所以我们在#{}中使用getXxx()方法、setXxx()方法定义的属性名即可 -->
<insert id="insertBook" parameterType="com.wdzl.pojo.Book" >
insert into book (bookid,bookname,price,sortname) values (#{bookId},#{bookName},#{price},#{sortName})
</insert>
结论:Mybatis会根据#{}中传入的数据,加工成getXxx()方法,通过反射在实体类对象中调用这个方法,从而获取到对应的数据。填充到#{}这个位置。
2.3.3 零散的简单类型数据
dao层中接口的方法:
public List<Book> queryBook(@Param("bookName") String bookName,@Param("price") Float price);
SQL 语句:
<select id="queryBook" resultType="com.wdzl.pojo.Book">
select bookid,bookname,price,sortname from book where bookname like #{bookName} and price > #{price}
</select>
2.3.4 Map 类型参数
使用场景: 有很多零散的参数需要传递,但是没有对应的实体类类型可以使用。使用@Param注解一个一个传入又太麻烦了。所以都封装到Map中。
dao层中接口的方法:
public void updatePrice(Map<String,Object> paramMap);
SQL 语句:
#{}中写Map的key名。
<update id="updatePrice">
update book set price = #{price} where bookid = #{bookId}
</update>
2.4 数据的输出
2.4.1 返回单个简单类型数据
dao层中接口的方法:
public Integer totalNum();
SQL语句:
<!-- 注意:必须要写resultType返回的类型 -->
<select id="totalNum" resultType="Integer">
select count(*) from book
</select>
2.4.2 返回实体类对象
dao层中接口的方法:
public Book queryByName(String bookName);
SQL语句:
<!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 -->
<!-- resultType属性:指定封装查询结果的Java实体类的全类名 -->
<select id="queryByName" resultType="com.wdzl.pojo.Book">
<!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符 -->
<!-- 给每一个字段设置一个别名,让别名和Java实体类中属性名一致 -->
select bookid,bookname,price,sortname from book where bookname = '${bookName}'
</select>
2.4.3 返回Map类型
适用于SQL查询返回的各个字段综合起来并不和任何一个现有的实体类对应,没法封装到实体类对象中。能够封装成实体类类型的,就不使用Map类型。
dao层中接口的方法:
public Map<String,Object> queryTopPrice();
SQL语句:
<!-- 注意:这里的resultType只能写map,否则会报错 -->
<select id="queryTopPrice" resultType="map">
select bookname,price from book where price = (select max(price) from book)
</select>
2.4.4 返回List类型
查询结果返回多个实体类对象,希望把多个实体类对象放在List集合中返回。此时不需要任何特殊处理,在resultType属性中还是设置实体类类型即可。
dao层中接口的方法:
public List<Book> queryAll();
SQL语句:
<select id="queryAll" resultType="com.wdzl.pojo.Book">
select bookid,bookname,price,sortname from book
</select>
2.4.5 返回自增主键
dao层中接口的方法:
public Integer insertBook(Book book);
SQL语句:
<!-- useGeneratedKeys属性字面意思就是“使用生成的主键” -->
<!-- keyProperty属性可以指定主键在实体类对象中对应的属性名,Mybatis会将拿到的主键值存入这个属性 -->
<!-- 这里parameterType写不写都不影响,但是这里的#{}里面的名字要与实体类的属性保持一致 -->
<insert keyProperty="bookId" useGeneratedKeys="true" id="insertBook" parameterType="com.wdzl.pojo.Book">
insert into book (bookid,bookname,price,sortname) values (#{bookId},#{bookName},#{price},#{sortName})
</insert>
注意: Mybatis是将自增主键的值设置到实体类对象中,而不是以Mapper接口方法返回值的形式返回。
不支持自增主键的数据库
例如Oracle不支持自增型主键,则就要使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用。
<insert id="insertEmployee"
parameterType="com.wdzl.pojo.Book"
databaseId="oracle">
<selectKey order="BEFORE" keyProperty="bookid"
resultType="Integer">
select book_seq.nextval from dual
</selectKey>
insert into orcl_employee(id,last_name,email,gender) values(#{id},#{lastName},#{email},#{gender})
</insert>
2.4.6 数据库表字段和实体类属性对应关系
①:设置别名的方式----将字段的别名设置成和实体类属性一致。
注意:这里并未传入实体类,所以这里数据库字段名和实体类的属性名不区分大小写,我这里只是作为一个演示,如果传入的是实体类,别名必须要与实体类的属性一样,也要注意大小写。
<select id="queryAll" resultType="com.wdzl.pojo.Book">
select bookid bookId,bookname bookName,price,sortname sortName from book
</select>
②:设置自动识别驼峰式命名规则
在MyBatis.xml中配置,下面一节我会给大家讲解settings中的配置
<!-- 在MyBatis-conf.xml内对Mybatis进行配置 -->
<settings>
<!-- 具体配置 -->
<!-- 将mapUnderscoreToCamelCase属性配置为true,表示开启自动映射驼峰式命名规则,开启后就不能使用别名的方式了 -->
<!-- 规则要求数据库表字段命名方式:单词_单词 -->
<!-- 规则要求Java实体类属性名命名方式:首字母小写的驼峰式命名 -->
<!-- 将xxx_xxx这样的列名自动映射到xxXxx这样驼峰式命名的属性名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
注意: 设置自动识别驼峰式命名规则后,就不能使用别名了。
<!-- public Book queryById(Integer bookId); -->
<select id="queryAll" resultType="com.wdzl.pojo.Book">
select book_id,book_name,price,sort_name from book where book_id = #{bookId}
</select>
③:使用resultMap标签
<!-- 专门声明一个resultMap设定column到property之间的对应关系 -->
<!-- type类型是针对哪个实体类做映射-->
<!--
autoMapping="true":自动映射
如果出现属性名和字段名一致的情况下,不用显式的使用<result>做映射配置的。
但是如果取值为false,则必须使用<result>做映射,否则为null
-->
<resultMap id="selectBookByResultMap" type="com.wdzl.pojo.Book" autoMapping="true">
<!-- 使用id标签设置主键列和主键属性之间的对应关系 -->
<!-- column属性用于指定字段名;property属性用于指定Java实体类属性名 -->
<id column="bookid" property="bookId"/>
<!-- 使用result标签设置普通字段和Java实体类属性之间的关系 -->
<result column="bookname" property="bookName"/>
<result column="price" property="price"/>
<result column="sortname " property="sortName"/>
</resultMap>
<!-- public Book queryByName(String bookName); -->
<select id="queryByName" resultMap="selectBookByResultMap">
select bookid,bookname,price,sortname from book where bookname = '${bookName}'
</select>
2.4.7 总结
- 接口中的方法名和SQL的id一致
- 方法返回值和resultType一致
- 方法的参数和SQL的参数一致
- 接口的全类名和映射配置文件的名称空间一致
2.5 MyBatis-conf.xml的常用全局配置
配置文档的顶层结构:
注意:配置时需要注意这些的顺序,要不然会报错。
2.5.1 properties(属性)
用来配置参数信息,例如数据库的连接信息。当然,我们可以将这些参数信息单独的放在properties 文件中,然后使用properties的resource属性将properties 文件引入进来,然后在xml 配置文件中用${}引用就可以了,引入的目的是为了放在在xml配置文件中写死参数。
<properties resource="db.properties"/>
2.5.2 settings(设置)
设置名 |
描述 |
有效值 |
默认值 |
cacheEnabled |
全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 |
true / false |
true |
lazyLoadingEnabled |
延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 |
true / false |
false |
useGeneratedKeys |
允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。 |
true / false |
false |
defaultStatementTimeout |
设置超时时间,它决定数据库驱动等待数据库响应的秒数 |
任意正整数 |
未设置 (null) |
mapUnderscoreToCamelCase |
是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 |
true / false |
false |
logImpl |
指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 |
SLF4J / LOG4J / LOG4J2 / JDK_LOGGING / COMMONS_LOGGING / STDOUT_LOGGING / NO_LOGGING |
未设置 (null) |
经常使用到的如上所示,如果还想使用其他的属性的话,你可以参考官方文档
2.5.3 typeAliases(类型别名)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
第一种配置方法:
<!-- 会扫描com.wdzl.pojo整个包下的需要的Java Bean-->
<typeAliases>
<package name="com.wdzl.pojo"/>
</typeAliases>
第二种配置方法
<typeAliases>
<typeAlias type="com.wdzl.pojo.Book" alias="book"/>
</typeAliases>
第三种配置方法(注解)
@Alias("book")
public class Book{
...
}
关于别名的使用:
配置好别名后,resultType、parameterType等可以直接写别名。
<select id="queryAll" resultType="Book">
select bookid,bookname,price,sortname from book
</select>
2.5.4 typeHandlers(类型处理器)
数据库中的字段类型与Java属性的类型不是对应的,因此我们要把Java对象的属性值转化为数据库中的字段值,数据库中的字段值转化为Java对象的属性值,这时候就需要到类型处理器
后面会给大家详细介绍并且使用类型处理器,这里只需要知道这个概念就行。
2.5.5 plugins(插件)
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可
<plugins>
<!-- 分页插件 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
2.5.6 environments(环境配置)
MyBatis 可以配置成适应多种环境,例如开发环境、测试环境、生产环境。可以在不同的环境中使用不同的数据库地址或者类型。
<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>
2.5.7 mappers(映射器)
< mappers >标签配置的是我们的映射器,也就是Mapper.xml 的路径。这里配置的目的是让MyBatis 在启动的时候去扫描这些映射器,创建映射关系。
经常使用的是第一种,因此对于剩下的三种也没有进行实践操作。
<!-- 我们有四种指定Mapper 文件的方式 -->
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="com/zhang/pojo/BookMapper.xml"/>
<mapper resource="com/zhang/pojo/AuthorMapper.xml"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<!-- 使用这种方式注意以下几点 -->
<!-- Dao接口和Mapper配置文件名称一致 -->
<!-- Mapper配置文件放在resources路径下 -->
<mappers>
<!-- 我这里的Dao层接口与Mapper配置文件名称不一致,所以这个用法是错误的
这里给大家提醒一下 -->
<package name="com/zhang/pojo"/>
</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>
第三章: 关联关系
关联关系在数量上的体现: 一对一,一对多,多对多。
关联关系的方向: 双向–>双方都可以访问到对方;单向–>双方中只有一方能够访问到对方。
创建模型:
①:创建数据库表并插入数据
create table author(
author_id int primary key auto_increment,
author_name varchar(11),
author_address varchar(20),
author_sex int default 1
)
insert into author (author_name,author_address,author_sex) values("老李","北京",1);
insert into author (author_name,author_address,author_sex) values("老张","上海",2);
insert into author (author_name,author_address,author_sex) values("老六","杭州",2);
create table book(
book_id int primary key auto_increment,
book_name varchar(11),
book_price float,
author_id int,
foreign key(author_id) references author(author_id)
)
insert into book(book_name,book_price,author_id) values("Java入门",45,2);
insert into book(book_name,book_price,author_id) values("Spring入门",55,1);
insert into book(book_name,book_price,author_id) values("Mybatis入门",75,3);
insert into book(book_name,book_price,author_id) values("C++入门",45,2);
insert into book(book_name,book_price,author_id) values("SpringMVC入门",45,2);
②: 创建实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Author {
private Integer authorId;
private String authorName;
private String authorAddress;
private Set<Book> authorBooks;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
private Integer bookId;
private String bookName;
private Float bookPrice;
private String bookSortName;
private Author author;
}
3.1 对一关系的映射
3.1.1 一对一
dao层中接口的方法
public Book queryOne(Integer bookId);
映射文件中的SQL语句
<!-- 创建resultMap实现“对一”关联关系映射 -->
<!-- id属性:通常设置为这个resultMap所服务的那条SQL语句的id加上“ResultMap” -->
<!-- type属性:要设置为这个resultMap所服务的那条SQL语句最终要返回的类型 -->
<resultMap id="bookMap" type="com.wdzl.pojo.Book" autoMapping="true">
<!-- 先设置Book自身属性和字段的对应关系 -->
<id column="book_id" property="bookId"/>
<result column="book_name" property="bookName"/>
<result column="book_price" property="bookPrice"/>
<!-- 使用association标签配置“对一”关联关系 -->
<!-- property属性:对应类中的属性 -->
<!-- column属性:数据库中的字段 -->
<!-- javaType属性:对应类的全类名 -->
<!-- 多对一关联关系中,autoMapping默认为false-->
<association property="author" autoMapping="true">
<!-- 配置Author类的属性和字段名之间的对应关系 -->
<id column="author_id" property="authorId"/>
<result column="author_name" property="authorName"/>
<result column="author_address" property="authorAddress"/>
<result column="author_sex" property="authorSex"/>
</association>
</resultMap>
<select id="queryOne" resultMap="bookMap">
select book_id,book_name,book_price,b.author_id,author_name,author_address,author_sex from book b,author a
where book_id = #{bookId} and a.author_id = b.author_id
</select>
3.1.2 多对一
dao层中接口的方法
public List<Book> queryAll();
映射文件中的SQL语句
<resultMap id="bookMap" type="com.wdzl.pojo.Book" autoMapping="true">
<id column="book_id" property="bookId"/>
<result column="book_name" property="bookName"/>
<result column="book_price" property="bookPrice"/>
<association property="author" autoMapping="true">
<id column="author_id" property="authorId"/>
<result column="author_name" property="authorName"/>
<result column="author_address" property="authorAddress"/>
<result column="author_sex" property="authorSex"/>
</association>
</resultMap>
<select id="queryAll" resultMap="bookMap">
select book_id,book_name,book_price,b.author_id,author_name,author_address,author_sex from book b,author a
where a.author_id = b.author_id
</select>
3.2 对多关系的映射
dao层中接口的方法
public List<Author> queryAll();
映射文件中的SQL语句
<resultMap id="authorMapper" type="Author" autoMapping="true">
<id column="author_id" property="authorId"/>
<result column="author_name" property="authorName"/>
<result column="author_address" property="authorAddress"/>
<result column="author_sex" property="authorSex"/>
<!-- collection标签:映射“对多”的关联关系 -->
<!-- property属性:对应类中的属性 -->
<!-- ofType属性:集合属性中元素的类型 -->
<collection property="books" ofType="Book" autoMapping="true">
<id column="book_id" property="bookId"/>
<result column="book_name" property="bookName"/>
<result column="book_price" property="bookPrice"/>
</collection>
</resultMap>
<select id="queryAll" resultMap="authorMapper">
select b.book_id,b.book_name,b.book_price,b.author_id,a.author_name,a.author_address,a.author_sex
from book b,author a
where a.author_id = b.author_id
</select>
3.3 分步查询
分开查询Book和Author。
例:
查询每本图书的作者
<resultMap id="bookMap" type="com.wdzl.pojo.Book" autoMapping="true">
<id column="book_id" property="bookId"/>
<result column="book_name" property="bookName"/>
<result column="book_price" property="bookPrice"/>
<association property="author" autoMapping="true" column="author_id" select="queryAuthorId"/>
</resultMap>
<select id="queryAll" resultMap="bookMap">
select book_id,book_name,book_price,author_id from book
</select>
<!-- resultMap="com.wdzl.dao.IAuthorDao.authorMap 这个resultMap引用了命名空间com.wdzl.dao.IAuthorDao的resultMap(authorMap) -->
<select id="queryAuthorId" resultMap="com.wdzl.dao.IAuthorDao.authorMap">
select author_id,author_name,author_sex ,author_address from author where author_id=#{authorId}
</select>
例:查询每个作者出版的书
<resultMap id="authorMap" type="Author" autoMapping="true">
<id column="author_id" property="authorId"/>
<result column="author_name" property="authorName"/>
<result column="author_address" property="authorAddress"/>
<result column="author_sex" property="authorSex"/>
<!--javaType 属性的类型
ofType : 集合中元素的类型
-->
<!-- books集合属性的映射关系,使用分步查询 -->
<!-- 在collection标签中使用select属性指定要引用的SQL语句 -->
<!-- select属性值的格式是:Mapper配置文件的名称空间.SQL语句id,如果在当前的名称空间,则可以直接引用 -->
<!-- column属性:指定Book和AAuthor之间建立关联关系时所依赖的字段 -->
<collection property="books" fetchType="lazy" autoMapping="true" column="author_id" select="queryBookId">
</collection>
</resultMap>
<!-- 作者下的所有图书-->
<select id="queryAll" resultMap="authorMap">
select author_id,author_name,author_address,author_sex from author
</select>
<!-- 为什么这里还需要配置别名,因为这里并没有与resultMap关联-->
<select id="queryBookId" resultType="Book">
select book_id bookId,book_name bookName,book_price bookPrice from book where author_id = #{author_id}
</select>
3.4 懒加载(两种办法)
懒加载的概念:对于实体类关联的属性到需要使用时才查询。也叫延迟加载。
在全局配置文件中设置懒加载
<!-- 使用settings对Mybatis全局进行设置 -->
<settings>
<!-- 开启延迟加载功能:需要配置两个配置项 -->
<!-- 1、将lazyLoadingEnabled设置为true,开启懒加载功能 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 2、将aggressiveLazyLoading设置为false,关闭“积极的懒加载” -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
在映射文件中配置懒加载(此时只有进行分步查询时才可以配置)
在collection 或association上 配置 fetchType=“lazy”
<collection property="books" fetchType="lazy" autoMapping="true" column="author_id" select="queryBookId">
<id column="book_id" property="bookId"/>
<result column="book_name" property="bookName"/>
<result column="book_price" property="bookPrice"/>
</collection>
第四章: 动态SQL
Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。
注意:这里的数据库模型和Java实体类与第三章的一样。
4.1 if和where标签
dao层中接口的方法:
// 按照书名模糊查询并且大于某个价格的书
public List<Book> queryByNameAndPrice(Book book);
使用where标签会自动去掉“标签体内前面、后面多余的and/or” 。
使用if标签,让我们可以有选择的加入SQL语句的片段。这个SQL语句片段是否要加入整个SQL语句,就看if标签判断的结果是否为true。
在if标签的test属性中,可以访问实体类的属性,不可以访问数据库表的字段。
<![CDATA[ ]]>在中括号里面可以写 > 、 < 等特殊符号,否则,写 > 、<等特殊符号需要使用 & gt ;& lt;等表示。
SQL语句:
<select id="queryByNameAndPrice" resultType="com.wdzl.pojo.Book">
select book_id bookId,book_name bookName,book_price bookPrice from book
<where>
<!-- 在if标签内部,需要访问接口的参数时还是正常写#{} -->
<if test="bookName != null and bookName != ''">
and book_name like #{bookName}
</if>
<if test="bookPrice != null">
<![CDATA[
and book_price > #{bookPrice}
]]>
</if>
<!--
第一种情况:所有条件都满足 WHERE book_name=? and book_price>?
第二种情况:部分条件满足 WHERE book_price>?
第三种情况:所有条件都不满足 没有where子句
-->
</where>
</select>
4.2 set标签
应用场景:更新一部分字段的信息。
使用set标签动态管理set子句,并且动态去掉两端多余的逗号。
dao层中接口的方法:
// 根据需要修改部分书籍信息
public void updateInfo(Book book);
SQL语句:
<update id="updateInfo">
update book
<set>
<if test="bookName != null and bookName != ''">
book_name = #{bookName},
</if>
<if test="bookPrice != null">
book_price = #{bookPrice}
</if>
</set>
where book_id = #{bookId}
<!--
第一种情况:所有条件都满足 SET book_name=?, book_price=?
第二种情况:部分条件满足 SET book_price =?
第三种情况:所有条件都不满足 update book where book_id=?
没有set子句的update语句会导致SQL语法错误
-->
</update>
4.3 trim标签
使用trim标签控制条件部分两端是否包含某些字符
- prefix属性:指定要动态添加的前缀
- suffix属性:指定要动态添加的后缀
- prefixOverrides属性:指定要动态去掉的前缀,使用“|”分隔有可能的多个值
- suffixOverrides属性:指定要动态去掉的后缀,使用“|”分隔有可能的多个值
dao层中接口的方法:
// 根据需要修改部分书籍信息
public List<Book> queryByNameAndPrice(Book book);
SQL语句:
<select id="queryByNameAndPrice" resultType="com.wdzl.pojo.Book">
select book_id bookId,book_name bookName,book_price bookPrice from book
<trim prefix="where" prefixOverrides="and|or">
<if test="bookName != null and bookName != ''">
and book_name like #{bookName}
</if>
<if test="bookPrice != null">
<![CDATA[
and book_price > #{bookPrice}
]]>
</if>
</trim>
</select>
4.4 choose/when/otherwise标签
在多个分支条件中,仅执行一个。
dao层中接口的方法:
// 根据需要修改部分书籍信息
public List<Book> queryByNameAndPrice(Book book);
SQL语句:
<select id="queryByNameAndPrice" resultType="com.wdzl.pojo.Book">
select book_id bookId,book_name bookName,book_price bookPrice from book
<choose>
<when test="bookName != null and bookName != ''">
where book_name like #{bookName}
</when>
<when test="bookPrice != null">
<![CDATA[
where book_price > #{bookPrice}
]]>
</when>
<otherwise>
where book_id = #{bookId}
</otherwise>
</choose>
<!--
第一种情况:第一个when满足条件 where book_name=?
第二种情况:第二个when满足条件 where book_price > ?
第三种情况:两个when都不满足,则执行book_id = ?
-->
</select>
4.5 foreach标签
在批量更新中,会报错。
dao层中接口的方法:
// 根据需要修改部分书籍信息
public void updateSomeInfo(List<Book> list);
SQL语句
<update id="updateSomeInfo">
<!--
collection属性:要遍历的集合
item属性:遍历集合的过程中能得到每一个具体对象,在item属性中设置一个名字,将来通过这个名字引用遍历出来的对象
separator属性:指定当foreach标签的标签体重复拼接字符串时,各个标签体字符串之间的分隔符
open属性:指定整个循环把字符串拼好后,字符串整体的前面要添加的字符串
close属性:指定整个循环把字符串拼好后,字符串整体的后面要添加的字符串
index属性:这里起一个名字,便于后面引用
遍历List集合,这里能够得到List集合的索引值
遍历Map集合,这里能够得到Map集合的key
-->
<foreach collection="list" item="book" separator=";">
<!-- 默认collection 取值可以是 list、arg0、collection;还可以使用@Param注解指定一个具体的名字,当做collection属性的值 -->
<!-- 在foreach标签内部如果需要引用遍历得到的具体的一个对象,需要使用item属性声明的名称 -->
update book set book_name = #{book.bookName} where book_id = #{book.bookId}
</foreach>
</update>
此时会出现语法错误,但并不是因为语法导致的,解决办法就是需要在数据库连接信息的 url地址 后跟allowMultiQueries=true
4.6 sql 标签
抽取重复的SQL片段
<sql id="mySelectSql">
select book_id,book_name,book_price from book
</sql>
引用已抽取的SQL片段
<!-- 使用include标签引用声明的SQL片段 -->
<include refid="mySelectSql"/>
第五章: 缓存
5.1 缓存
缓存机制:
一级缓存和二级缓存的使用顺序
查询的顺序是:
-
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- SqlSession关闭之前,一级缓存中的数据会写入二级缓存
效用范围:
一级缓存: SqlSession级别
二级缓存: SqlSessionFactory级别
一级缓存和二级缓存的范围大小:
5.2 一级缓存
MyBatis 默认一级缓存是开启的,一级缓存是在 SqlSession 级别的缓存。即,同一个 SqlSession ,多次调用同一个 Mapper 和同一个方法的同一个参数,只会进行一次数据库查询,然后把数据缓存到缓存中,以后直接先从缓存中取出数据,不会直接去查数据库。但是不同的 SqlSession 对象,是不同享缓存的。
上面查询中,可以发现查询相同的bookId,只需要查询第一次的Sql语句,第二次就不需要。
SqlSession session = MyBatisUtil.getSqlSession();
IBookDao mapper = session.getMapper(IBookDao.class);
List<Book> books = mapper.queryAll();
for (Book book : books) {
System.out.println(book);
}
System.out.println(mapper.queryOne(2));
MyBatisUtil.close(session);
注意:上面查询中,queryAll()查询出了所有的book信息,然后又进行了一次queryOne() 查询bookId=2的图书的记录,可以发现又进行了一次Sql语句的查询。因为 MyBatis判断是否相同查询方式是 Statement Id + Offset + Limmit + Sql + Params 来判断的。不同方法不同参数会判定为不同的查询。
一级缓存失效的情况
- sqlSession.clearCache(); ------ 清空缓存
- 在映射文件中的< select >上加flushCache=“true”
- 执行了更新操作:update insert delele
- .sqlSession.commit(); rollback(); ----- 引起清空一级缓存的
- 不同的映射或SQL 都不能相互缓存
- 查询条件发生了变化
5.3 二级缓存
一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示:
二级缓存的配置:
①:开启二级缓存功能: 注意:默认情况下二级缓存配置是开启的。
<mapper namespace="com.wdzl.dao.IBookDao">
<!-- 加入cache标签启用二级缓存功能 -->
<cache/>
</mapper>
注意:在上面配置的二级缓存,只是针对于此命名空间的。如果如果要单独针对某个查询配置二级缓存开启策略时,可以在映射文件中对查询单独配置。如下配置:
<select id="queryOne" resultType="book" useCache="true">
注意: 如果全局和局部缓存都配置了,则局部配置优先级高
使用< cache-ref> 引用其他命名空间中的缓存。
例:在AuthorMapper.xml中引用BookMapper.xml的缓存
<mapper namespace="com.wdzl.dao.IAuthorDao">
<!-- 引用其他命名空间的缓存 -->
<cache-ref namespace="com.wdzl.dao.IBookDao"/>
<!--上面直接引用下面空间中的缓存配置策略即可,注意下面如果没有配置cache,上面会异常 -->
②:让实体类序列化
public class Book implements Serializable
③:测试
@Test
public void test4(){
test3();
System.out.println("==================");
test3();
}
public void test3(){
SqlSession session = MyBatisUtil.getSqlSession();
IBookDao mapper = session.getMapper(IBookDao.class);
Book book = mapper.queryOne(1);
System.out.println(book);
MyBatisUtil.close(session);
}
打印效果:
Cache Hit Ratio [com.wdzl.dao.IBookDao]: 0.0
Opening JDBC Connection
Created connection 13803304.
==> Preparing: select book_id bookId,book_name bookName,book_price bookPrice from book where book_id = ?
==> Parameters: 1(Integer)
<== Columns: bookId, bookName, bookPrice
<== Row: 1, Java入门, 45.0
<== Total: 1
Book(bookId=1, bookName=Java入门, bookPrice=45.0, author=null)
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@d29f28]
Returned connection 13803304 to pool.
==================
As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
Cache Hit Ratio [com.wdzl.dao.IBookDao]: 0.5
Book(bookId=1, bookName=Java入门, bookPrice=45.0, author=null)
④:缓存命中率
日志中打印的Cache Hit Ratio叫做缓存命中率
Cache Hit Ratio [com.wdzl.dao.IBookDao]: 0.0
Cache Hit Ratio [com.wdzl.dao.IBookDao]: 0.5
缓存命中率=命中缓存的次数/查询的总次数
二级缓存注意点:
- 默认第一次查出的对象,放入一级缓存。默认不会自动存入二级缓存。
- 要存入二级缓存,执行sqlsession.close() 或 commit() 存入二级缓存。
- 如果直接清除缓存,你就会发现没有命中二级缓存。
自定义缓存的策略
在Mapper配置文件中添加的cache标签可以设置一些属性:
-
eviction属性:缓存回收策略
LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认的是 LRU。
-
flushInterval属性:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
-
size属性:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出
-
readOnly属性:只读,true/false
true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。
<mapper namespace="com.wdzl.dao.IBookDao">
<!--
加入cache标签启用二级缓存功能
size : 缓存存放多少个元素
size=2 时,当查询三个不同的对象时,第三个无法缓存,从而需要查询sql,前面两个不需要
-->
<cache size="2"/>
</mapper>
测试自定义缓存的策略
@Test
public void test5(){
SqlSession session0 = MyBatisUtil.getSqlSession();
IBookDao mapper = session0.getMapper(IBookDao.class);
mapper.queryOne(1);
mapper.queryOne(2);
mapper.queryOne(3);
session0.close();
SqlSession session1 = MyBatisUtil.getSqlSession();
IBookDao mapper1 = session1.getMapper(IBookDao.class);
mapper1.queryOne(1);
session0.close();
SqlSession session2 = MyBatisUtil.getSqlSession();
IBookDao mapper2 = session2.getMapper(IBookDao.class);
mapper2.queryOne(2); // 发现这里又执行SQL了,因为上面三个对象只能缓存其中两个
session0.close();
}
5.4 缓存组件:ehcache
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。
实现步骤
注意:使用组件不需要实现序列化接口了
①:在pom.xml中配置依赖
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
②:在映射文件中配置
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
第六章:其他
6.1 类型处理器
概念:MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。下表描述了一些常见的类型处理器。
类型处理器 |
Java 类型 |
JDBC 类型 |
BooleanTypeHandler |
java.lang.Boolean, boolean |
数据库兼容的 BOOLEAN |
ByteTypeHandler |
java.lang.Byte, byte |
数据库兼容的 NUMERIC 或 BYTE |
ShortTypeHandler |
java.lang.Short, short |
数据库兼容的 NUMERIC 或 SMALLINT |
IntegerTypeHandler |
java.lang.Integer, int |
数据库兼容的 NUMERIC 或 INTEGER |
LongTypeHandler |
java.lang.Long, long |
数据库兼容的 NUMERIC 或 BIGINT |
FloatTypeHandler |
java.lang.Float, float |
数据库兼容的 NUMERIC 或 FLOAT |
DoubleTypeHandler |
java.lang.Double, double |
数据库兼容的 NUMERIC 或 DOUBLE |
StringTypeHandler |
java.lang.String |
CHAR, VARCHAR |
DateOnlyTypeHandler |
java.util.Date |
DATE |
SqlDateTypeHandler |
SqlDateTypeHandler |
DATE |
LocalDateTypeHandler |
java.time.LocalDate |
DATE |
如果想要看其他的类型处理器,请参考官方文档
类型处理器分为内置类型转换器和自定义类型转换器
6.1.1 自定义类型转换器实现方式
两种实现方式:
- 实现TypeHandler
- 继承 BaseTypeHandler
6.1.1.1 编写自定义类型的转换器
// 此泛型指的是java中要转化对象的类型
// 实现性别类型的转换
// String 男 ----- JDBC int
public class SexTypeHandler implements TypeHandler<String> {
/**
*
* @param preparedStatement
* @param i 占位符位置
* @param s 给占位符赋值的参数
* @param jdbcType
* @throws SQLException
*/
@Override
public void setParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {
if("男".equals(s)){
preparedStatement.setInt(i,1);
}else if("女".equals(s)){
preparedStatement.setInt(i,2);
}
}
/**
*
* @param resultSet
* @param s 列名
* @return
* @throws SQLException
*/
@Override
public String getResult(ResultSet resultSet, String s) throws SQLException {
int sex = resultSet.getInt(s);
if(sex == 1){
return "男";
}
return "女";
}
/**
*
* @param resultSet
* @param i 处于第i个位置
* @return
* @throws SQLException
*/
@Override
public String getResult(ResultSet resultSet, int i) throws SQLException {
return null;
}
@Override
public String getResult(CallableStatement callableStatement, int i) throws SQLException {
return null;
}
}
6.1.1.2 使用两种配置方式
-
全局配置
在全局配置文件中配置,MyBatis-conf.xml中配置。
<!--全局类型转换器
通过 javaType和jdbcType两者来匹配的。
-->
<typeHandlers>
<typeHandler handler="com.wdzl.typehandler.SexTypeHandler" javaType="String" jdbcType="INTEGER"/>
</typeHandlers>
在映射文件中配置。
<resultMap id="sexChange" type="com.wdzl.pojo.Author">
<id column="author_id" property="authorId"/>
<result column="author_name" property="authorName"/>
<result column="author_address" property="authorAddress"/>
<result column="author_sex" property="authorSex" javaType="String" jdbcType="INTEGER"/>
</resultMap>
<!-- 查询时的转换语句 -->
<select id="queryAll" resultMap="sexChange">
select author_id,author_name,author_address,author_sex from author
</select>
<!-- 插入时的转换语句,更新也类似 -->
<insert id="insertAuthor">
insert into author(author_id,author_name,author_address,author_sex) values (#{authorId},#{authorName},#{authorAddress},#{authorSex,javaType=String,jdbcType=INTEGER})
</insert>
在映射文件中配置。
注意:resultMap标签在2.4.6节中讲过。
<resultMap id="sexChange" type="com.wdzl.pojo.Author">
<id column="author_id" property="authorId"/>
<result column="author_name" property="authorName"/>
<result column="author_address" property="authorAddress"/>
<result column="author_sex" property="authorSex" typeHandler="com.wdzl.typehandler.SexTypeHandler"/>
</resultMap>
<!-- 查询时的转换语句 -->
<select id="queryAll" resultMap="sexChange">
select author_id,author_name,author_address,author_sex from author
</select>
<!-- 插入时的转化语句,更新也类似-->
<insert id="insertAuthor">
insert into author(author_id,author_name,author_address,author_sex) values (#{authorId},#{authorName},#{authorAddress},#{authorSex,typeHandler=com.wdzl.typehandler.SexTypeHandler})
</insert>
6.2 分页插件
①:导入分页插件的依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
②:在全局配置文件中配置插件
<!--分页插件-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
③:使用
注意:在每次查询之前,都必须要设置起始页和每页记录数即可。
@Test
public void test(){
SqlSession session = MyBatisUtil.getSqlSession();
IBookDao mapper = session.getMapper(IBookDao.class);
// 第一个参数是起始页,第二个参数是每页记录数
Page<Book> page = PageHelper.startPage(2, 3);
mapper.queryAll().forEach(System.out::println);
System.out.println("当前页号: " + page.getPageNum()); //当前页号
System.out.println("总页数: " + page.getPages()); // 总页数
System.out.println("每页记录数: " + page.getPageSize()); //页的记录数
System.out.println("总记录数: " + page.getTotal()); // 总记录数
System.out.println("当前页记录列表: " + page.getResult()); //当前页记录列表
MyBatisUtil.close(session);
}
6.3 逆向工程
6.3.1 概念
正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的。
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
6.3.2 具体实现
准备sql语句
create table t_book(
book_id int primary key auto_increment,
book_name varchar(30),
book_price float,
boook_sortname varchar(30),
pubDate date,
pubcom varchar(30)
)
6.3.2.1 配置pom.xml
<dependencies>
<!-- 依赖Mybatis核心包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
</dependencies>
<!-- 控制Maven在构建过程中相关配置 -->
<build>
<!-- 构建过程中用到的插件 -->
<plugins>
<!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<!-- 插件的依赖 -->
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
6.3.2.2 配置逆向工程的xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD(清新简洁版)
MyBatis3: 生成带条件的CRUD(奢华尊享版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3">
<!-- 数据库的连接信息 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/testdb"
userId="root"
password="123456">
<!-- 解决table schema中有多个重名的表生成表结构不一致问题 -->
<property name="nullCatalogMeansCurrent" value="true"/>
</jdbcConnection>
<!-- javaBean的生成策略-->
<javaModelGenerator targetPackage="com.zhang.pojo" targetProject=".\src\main\java">
<!--enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="true" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="com.zhang.mapper" targetProject=".\src\main\resources">
<!--enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.zhang.mapper" targetProject=".\src\main\java">
<!--enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
<!-- domainObjectName属性指定生成出来的实体类的类名 -->
<table tableName="t_author" domainObjectName="Author"/>
<table tableName="t_book" domainObjectName="Book"/>
</context>
</generatorConfiguration>
6.3.2.3 点击生成逆向工程的文件
然后会帮你自动生成下述目录。
6.3.2.4 进行测试
编写db.properties文件
url=jdbc:mysql://127.0.0.1:3306/testdb?serverTimezone=GMT
driver=com.mysql.cj.jdbc.Driver
username=root
password=123456
编写mybatis-conf.xml的配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties"></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/zhang/mapper/BookMapper.xml"/>
</mappers>
</configuration>
编写测试文件
public static void main(String[] args) {
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
try {
InputStream is = Resources.getResourceAsStream("mybatis-conf.xml");
SqlSessionFactory sessionFactory = builder.build(is);
SqlSession session = sessionFactory.openSession(true);
BookMapper mapper = session.getMapper(BookMapper.class);
mapper.selectByExample(null).forEach(System.out::println);
is.close();
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
6.4 注解
6.4.1 基本使用
①: DAO接口中对应的方法上添加注解
@Select("select book_id bookId,book_name bookName,book_price bookPrice from book where book_id = #{bookId}")
public Book queryOne(Integer bookId);
@Insert("insert into book(book_id,book_name,book_price) values(#{bookId},#{bookName},#{bookPrice})")
public void insertBook(Book book);
②: 在配置文件中引入映射注解类
<mappers>
<mapper class="com.wdzl.dao.IBookDao"/>
</mappers>
6.4.2 主键获取
这里有两种方式。一种是使用**@Options注解;另一种是使用@SelectKey**注解。
@Insert("insert into book(book_id,book_name,book_price) values(#{bookId},#{bookName},#{bookPrice})")
/*@Options(useGeneratedKeys=true, keyProperty="bookId", keyColumn="book_id")*/
@SelectKey(statement = "select last_insert_id()", keyProperty = "bookId",
before = false, resultType = Integer.class)
public Integer insertBook(Book book);
测试:
@Test
public void test6(){
SqlSession session = MyBatisUtil.getSqlSession();
IBookDao mapper = session.getMapper(IBookDao.class);
Book book = new Book();
book.setBookName("Java从入门到入土");
book.setBookPrice(85f);
mapper.insertBook(book);
// 如果不使用@Options注解或@SelectKey注解,你会发现这里获取的id为空。
System.out.println(book.getBookId());
MyBatisUtil.close(session);
}
6.4.3 结果映射
@Select("select book_id,book_name,book_price from book")
@Results({
@Result(property = "bookId",id = true,column = "book_id"),
@Result(property = "bookName",column = "book_name"),
@Result(property = "bookPrice",column = "book_price")
})
public List<Book> queryAll();
如果多个查询需要结果映射时,重复编写 @Results 就过于繁琐,可以修改上面代码,给增加 id 属性,下面其他地方通过 id 引用即可重复使用。
两种方式:
方式一:
- @Results(id=“bookMap”)
- @ResultMap(“bookMap”)
@Select("select book_id,book_name,book_price from book")
@Results(id = "bookMap",value = {
@Result(property = "bookId",id = true,column = "book_id"),
@Result(property = "bookName",column = "book_name"),
@Result(property = "bookPrice",column = "book_price")
})
public List<Book> queryAll();
@Select("select book_id,book_name,book_price from book where book_id = #{bookId}")
@ResultMap("bookMap") // 引用上面的结果映射
public Book queryOne(Integer bookId);
方式二:
先在映射文件中配置,通过xml 方式配置结果映射
<resultMap id="bookMap" type="com.wdzl.pojo.Book">
<id property="bookId" column="book_id"/>
<result property="bookName" column="book_name"/>
<result property="bookPrice" column="book_price"/>
</resultMap>
然后在全局配置文件中,转化为xml资源进行引入
<mappers>
<mapper class="com.wdzl.dao.IBookDao"/>
<mapper resource="com/wdzl/mapper/BookMapper.xml"/>
</mappers>
最后在注解 Dao类中直接通过 @ResultMap() 引用即可
@Select("select book_id,book_name,book_price from book")
@ResultMap("bookMap") //引用上面的resultMap中resultMap的id。
public List<Book> queryAll();
6.4.4 动态SQL
在Dao层接口中添加此方法。
@Update("<script>" +
"update book " +
"<set>" +
" <if test=\"bookName!=null and bookName!=''\">" +
" book_name=#{bookName}," +
" </if>" +
" <if test=\"bookPrice!=null\">" +
" book_price=#{bookPrice}," +
" </if>" +
"</set>" +
"<where>" +
" <if test=\"bookId!=null\">" +
" book_id=#{bookId}" +
" </if>" +
"</where>" +
"</script>")
void update(Book book);
6.4.5 Provider方式实现动态SQL
上面的使用xml 标记方式实现动态SQL ,太繁琐,MyBatis 中提供了 Provider 的方式。
.①:编写动态生成 SQL 的provider 类,用来提供动态SQL字符串生成
/**
* 根据条件动态生成SQL
* @param book
* @return
*/
public String query(Book book) {
return new SQL(){
{
SELECT("book_id bookId","book_name bookName","book_price bookPrice");
FROM("book");
if(book.getBookName() != null){
WHERE("book_name = #{bookName}");
}
if(book.getBookPrice() != null){
WHERE("book_price > #{bookPrice}");
}
ORDER_BY("book_price desc");
}
}.toString();
}
②: 引用 provider类
- @SelectProvider
- @UpdateProvider
- @DeleteProvider
- @InsertProvider
@SelectProvider(type = BookProvider.class,method = "query")
public List<Book> search(Book book);