MyBatis
结果映射
[1]单表映射
MyBatis是一个ORM框架。
在MyBatis的映射文件中有一个标签resultMap
专门用来配置映射关系。
基本的准备:
实体类:
/**
* @author 戴着假发的程序员
* @company 起点编程
* 2022/10/9 9:22
*/
public class Category {
private Integer categoryId;
private String categoryName;
}
/**
* @author 戴着假发的程序员
* @company 起点编程
* 2022/10/9 9:23
*/
public class Book {
private String isbn;
private String title;
private Float cost;
private Float price;
private Integer pid;
private Integer categoryId;
}
需求:根据id查询Category
数据表和实体类的映射情况:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DvgdJWmK-1671874540716)(笔记.assets/1665278916778.png)]
在查询的时候,列名和属性名无法自动映射,需要配置resultMap
<!-- 配置一个结果映射 -->
<resultMap id="baseMap" type="com.qidian.pojo.Category">
<!-- 主键 -->
<id column="cid" property="categoryId" javaType="java.lang.Integer"/>
<!-- 其他列 -->
<result column="cname" property="caregoryName"/>
</resultMap>
查询时设置select语句的resultMap为指定的map
<select id="queryById" resultMap="baseMap">
select * from category where cid = #{cid}
</select>
同理映射book。
<!-- 配置一个结果映射 -->
<resultMap id="baseMap" type="com.qidian.pojo.Book">
<!-- 主键 -->
<id column="isbn" property="isbn" javaType="java.lang.String"/>
<!-- 其他列 -->
<result column="title" property="title"/>
<result column="cost" property="cost"/>
<result column="price" property="price"/>
<result column="pid" property="pid"/>
<result column="category_id" property="categoryId"/>
</resultMap>
<select id="queryByIsbn" resultMap="baseMap">
select * from g_book where isbn = #{isbn}
</select>
[2]关联映射
①关联关系
数据表之间的关系使用外键来表示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SYNfJeQO-1671874540717)(笔记.assets/1665279378836.png)]
对象与对象之间的关系如何表达?
对象与对象之间是相互持有引用,不是依赖外键。
就上面的两个实体类之间应该表达为:
图书直接持有类别的引用。 类别也可以持有一组图书的引用。
对象与对象之间关系情况:
- 一对多: 班级对学生。图书类别对图书。
- 多对一:上面关系反过来。
- 一对一:身份证对一个公民。
- 多对多:学生对课程。程序员对项目。图书对作者。
我们使用A和B表示双方关系:
如果A持有B的一个引用:(持有对方的一个引用)
多对一,一对一
如果A持有B的一组引用:(持有对方的一组引用)
一对多,多对多
在上面的例子中:
图书持有类别的一个引用:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OKMVGIbC-1671874540717)(笔记.assets/1665279933747.png)]
类别持有图书的一组引用:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N7vG71MR-1671874540718)(笔记.assets/1665279990355.png)]
②一对一的关联映射
需求:根据编号查询图书信息,同时查询图书的类别信息。
方式1:连接查询
<select id="queryByIsbn" resultMap="baseMap">
select * from g_book b join g_category c on b.category_id = c.cid where isbn = #{isbn}
</select>
resultMap的配置方式:
<!-- 配置一个结果映射 -->
<resultMap id="baseMap" type="com.qidian.pojo.Book">
<!-- 图书的属性的映射 -->
<!-- 主键 -->
<id column="isbn" property="isbn" javaType="java.lang.String"/>
<!-- 其他列 -->
<result column="title" property="title"/>
<result column="cost" property="cost"/>
<result column="price" property="price"/>
<result column="pid" property="pid"/>
<result column="category_id" property="categoryId"/><!-- 可有可无 -->
<!-- 映射一对一的关联关系 -->
<!-- property:当前对象中的引用其他对象的属性名称
column: 外键
javaType:引用的其他对象的全限定类名
-->
<association property="category" column="category_id" javaType="com.qidian.pojo.Category">
<!-- 映射category属性 -->
<id column="cid" property="categoryId"/>
<result column="cname" property="categoryName"/>
</association>
</resultMap>
测试程序:
@Test
public void testOne2OneByJoin(){
SqlSession sqlSession = sqlSessionFactory.openSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = mapper.queryByIsbn("9787111370048");
System.out.println(book);
}
方式二:子查询实现
CategoryMapper中本身就有通过id查询Category的方法:
<select id="queryById" resultMap="baseMap">
select * from g_category where cid = #{cid}
</select>
我们可以在BookMapper中通过子查询配置一对一映射关系:
<!-- 子查询映射一对一 -->
<resultMap id="bookMap" type="com.qidian.pojo.Book">
<!-- 图书的属性的映射 -->
<!-- 主键 -->
<id column="isbn" property="isbn" javaType="java.lang.String"/>
<!-- 其他列 -->
<result column="title" property="title"/>
<result column="cost" property="cost"/>
<result column="price" property="price"/>
<result column="pid" property="pid"/>
<!-- 子查询映射一对一 -->
<association property="category"
javaType="com.qidian.pojo.Book"
column="category_id"
select="com.qidian.mapper.CategoryMapper.queryById"
/>
</resultMap>
<!-- 不适用链接查询 -->
<select id="queryByIsbn1" resultMap="bookMap">
select * from g_book where isbn = #{isbn}
</select>
测试:
@Test
public void testOne2OneBySubSelect(){
SqlSession sqlSession = sqlSessionFactory.openSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = mapper.queryByIsbn1("9787111370048");
System.out.println(book);
}
③一对多关联映射
查询类别的同时查询这个类别下的所有图书信息
方式1:连接查询
SQL:
<!-- 关联映射 -->
<!-- 连接查询配置一对多关联映射 -->
<resultMap id="catAndBooksMap" type="com.qidian.pojo.Category">
<!-- 主键 -->
<id column="cid" property="categoryId" javaType="java.lang.Integer"/>
<!-- 其他列 -->
<result column="cname" property="categoryName"/>
<!-- 集合属性映射 -->
<!-- property : 映射的属性名 ofType: 配置映射的属性的集合的泛型 -->
<collection property="books" ofType="com.qidian.pojo.Book">
<!-- 映射图书的属性 -->
<!-- 主键 -->
<id column="isbn" property="isbn" javaType="java.lang.String"/>
<!-- 其他列 -->
<result column="title" property="title"/>
<result column="cost" property="cost"/>
<result column="price" property="price"/>
<result column="pid" property="pid"/>
</collection>
</resultMap>
<!-- 连接查询 -->
<select id="queryCatAndBooksById" resultMap="catAndBooksMap">
select * from g_book b join g_category c on b.category_id = c.cid where cid = #{cid}
</select>
测试程序:
@Test
public void testOne2ManyByJoin(){
SqlSession sqlSession = sqlSessionFactory.openSession();
CategoryMapper mapper = sqlSession.getMapper(CategoryMapper.class);
Category category = mapper.queryCatAndBooksById(1);
System.out.println(category);
}
方式二:子查询
在BookMapper中添加通过类别编号查询图书的SQL
<!-- 通过类别编号查询图书列表 -->
<select id="queryByCatId" resultType="com.qidian.pojo.Book">
select isbn,title,cost,price,category_id categoryId pid
from g_book where category_id = #{cid}
</select>
在CategoryMapper中添加SQL配置:
<!-- 子查询配置一对多关联映射 -->
<resultMap id="catAndBooksMap1" type="com.qidian.pojo.Category">
<!-- 主键 -->
<id column="cid" property="categoryId" javaType="java.lang.Integer"/>
<!-- 其他列 -->
<result column="cname" property="categoryName"/>
<!-- 集合属性映射 -->
<collection property="books" javaType="list" ofType="com.qidian.pojo.Book"
column="cid" select="com.qidian.mapper.BookMapper.queryByCatId"/>
</resultMap>
<!-- 连接查询 -->
<select id="queryCatAndBooksById1" resultMap="catAndBooksMap1">
select * from g_category where cid = #{cid}
</select>
测试程序:
@Test
public void testOne2ManyBySubSelect(){
SqlSession sqlSession = sqlSessionFactory.openSession();
CategoryMapper mapper = sqlSession.getMapper(CategoryMapper.class);
Category category = mapper.queryCatAndBooksById1(1);
System.out.println(category);
}
MyBatis的缓存
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。
MyBatis的缓存分为一级缓存和二级缓存。一级缓存是自动开启的,而二级缓存需要手动开启。
一级缓存称之为session级别的缓存:在一个事务中,同样的查询只会执行一次,第二次执行的时候是从缓存查询的。
二级缓存称之为sessionFactory级别的缓存。缓存的数据跨域了事务。
提前的准备:添加配置让MyBatis显示SQL
添加依赖:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.6</version>
</dependency>
在MyBatis的配置文件中添加配置:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AqzKdi6e-1671874540718)(笔记.assets/1665283016626.png)]
测试一级缓存:
@Test
public void testCach1(){
SqlSession sqlSession = sqlSessionFactory.openSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = mapper.queryByIsbn("9787111370048");
System.out.println(book);
book = mapper.queryByIsbn("9787111370048");
System.out.println(book);
}
执行这个测试程序,会发现两次查询只执行了一次实际的物理查询。
MyBatis默认会将查询的结果放在缓存中,当在一次事务中重复查询相同的数据的时候,会直接从缓存获取,而不查询数据库。
缓存被刷新的情况:
- 在当前事务中执行了增删改就会刷新缓存。
@Test
public void testCach1(){
SqlSession sqlSession = sqlSessionFactory.openSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = mapper.queryByIsbn("9787111370048");
System.out.println(book);
// 修改
mapper.update(book);
// 在查询
book = mapper.queryByIsbn("9787111370048");
System.out.println(book);
}
- 强制清空缓存
@Test
public void testCach2(){
SqlSession sqlSession = sqlSessionFactory.openSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = mapper.queryByIsbn("9787111370048");
System.out.println(book);
// 强制清空缓存
sqlSession.clearCache();
// 在查询
book = mapper.queryByIsbn("9787111370048");
System.out.println(book);
}
- 不同的事务,不能使用同一个缓存
@Test
public void testCach3(){
SqlSession sqlSession = sqlSessionFactory.openSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = mapper.queryByIsbn("9787111370048");
System.out.println(book);
// 提交事务
// sqlSession.commit();
// 重新获取会话也是不同的事务
sqlSession = sqlSessionFactory.openSession();
mapper = sqlSession.getMapper(BookMapper.class);
// 在查询
book = mapper.queryByIsbn("9787111370048");
System.out.println(book);
}
二级缓存(慎用)
二级缓存又称之为SessionFactory级别的缓存,就是缓存的数据是夸事务的。
二级缓存需要手动开启。在映射文件中进行配置。
<!-- 开启二级缓存 -->
<cache
size="1204"
flushInterval="60000"
readOnly="true"
eviction="FIFO"
/>
size: 缓存的引用数量。
flushInterval: 刷新缓存的时间
readOnly: 缓存是否只读
eviction: 缓存的清楚策略 当缓存满了之后,再来新的数据的时候如何处理。
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
@Test
public void testCach3(){
SqlSession sqlSession = sqlSessionFactory.openSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = mapper.queryByIsbn("9787111370048");
System.out.println(book);
// 提交事务
sqlSession.commit();
// 重新获取会话也是不同的事务
sqlSession = sqlSessionFactory.openSession();
mapper = sqlSession.getMapper(BookMapper.class);
// 在查询
book = mapper.queryByIsbn("9787111370048");
System.out.println(book);
}
延迟加载
MyBatis也支持延迟加载。
延迟加载:查询的时候不做实际的查询,当使用数据的的时候才做实际的查询。
在MyBatis配置文件中添加配置:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z6hJURVJ-1671874540719)(笔记.assets/1665286265558.png)]
关联查询的子查询会产生延迟加载。
@Test
public void testOne2OneBySubSelect(){
SqlSession sqlSession = sqlSessionFactory.openSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = mapper.queryByIsbn1("9787111370048");
System.out.println(book.getTitle());
//System.out.println(book.getCategory().getCategoryName());
}
会发现只执行对图书的查询,不执行对Category的查询。
我们可以通过设置覆盖全局的延迟加载的配置:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cz1ibuhW-1671874540719)(笔记.assets/1665286588152.png)]
MyBatis配置文件
属性(properties)
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。例如:
我们可以在resources下中一个db.properties文件
jdbc.username=root
jdbc.password=root123
jdbc.url=jdbc:mysql://localhost:3306/books?serverTimezone=GMT%2B8&characterEncoding=utf8
jdbc.driverClassName=com.mysql.jdbc.Driver
在MyBatis的配置文件中使用:
<properties resource="db.properties">
<!-- 这里的属性会被外部的配置文件的属性覆盖掉 -->
<property name="username" value="root"/>
<property name="password" value="root587"/>
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!-- 配置数据库连接 相关属性 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。
参考的地址:https://mybatis.net.cn/configuration.html#settings
- cacheEnabled 缓存开启,默认是true
- lazyLoadingEnabled 延迟加载 默认 false
- aggressiveLazyLoading 急加载 false
- mapUnderscoreToCamelCase 按照驼峰规则映射 列名 : user_tel 属性名 userTel
- logImpl 日志的配置
配置的案例:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
<typeAliases>
<!-- 给Book实体类配置一个别名为book -->
<typeAlias type="com.qidian.pojo.Book" alias="book"/>
<!-- 可以直接配置一个包 -->
<package name="com.qidian.pojo"/>
</typeAliases>
经过上面的配置,我们在映射文件中可以使用 book 直接代替com.qidian.pojo.Book
如果配置的一个包,这个包下的所有的类都可以使用简称:
book
、Book
、Category
、category
动态sql
我们之前的开发使用这样的语句
select * from tableName where 1 = 1
if(){
xx
}
if(){
xxxx
}
JDBC可以使用java逻辑进行控制,使用MyBatis之后就无法使用java逻辑控制。
MyBatis提供了这样标签可以在映射文件中进行sql语句动态拼接。
[1]if
需求:根据图书的信息查询图书列表
接口:
List<Book> queryByPojo(Book book);
sql写法:
<select id="queryByPojo" parameterType="book" resultMap="BaseBookMap">
select * from g_book where 1 = 1
<if test="isbn != null and isbn != ''">
and isbn = #{isbn}
</if>
<if test="title != null and title !=''">
and title like '%${title}%'
</if>
</select>
测试:
@Test
public void testIf(){
Book book = new Book();
book.setTitle("一");
SqlSession sqlSession = sqlSessionFactory.openSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Book> books = mapper.queryByPojo(book);
System.out.println(books);
}
tisp:
在if的test中不需要写 #{xxxx}
在if的test中不能使用 && > 和 < 要使用 and
<
和>
当参数是一个实体对象的时候,在test中直接写属性名即可。
[2]choose、when、otherwise
这个逻辑上就是一个switch
修改刚才的SQL写法:
<select id="queryByPojo" parameterType="book" resultMap="BaseBookMap">
select * from g_book where 1 = 1
<choose>
<when test="isbn != null and isbn != ''">
and isbn = #{isbn}
</when>
<when test="title != null and title !=''">
and title like '%${title}%'
</when>
<otherwise>
and category_id = #{categoryId}
</otherwise>
</choose>
</select>
程序的逻辑意义和之前会有所不同,因为这个例子中有且仅有一个条件会生效。
[3]trim、where、set
- where : 可以按需添加where关键字,删除多余的and或者or运算符。
案例:
<!-- where的用法 -->
<select id="queryByPojo" parameterType="book" resultMap="BaseBookMap">
select * from g_book
<where>
<if test="isbn != null and isbn != ''">
and isbn = #{isbn}
</if>
<if test="title != null and title !=''">
and title like '%${title}%'
</if>
</where>
</select>
- set : 添加set关键字。删除多余的“,”
<!-- set的使用 -->
<update id="updateBook" parameterType="book">
update g_book
<set>
<if test="title != null and title != ''">
title = #{title},
</if>
<if test="cost != null">
cost = #{cost},
</if>
<if test="price != null">
price = #{price},
</if>
</set>
where isbn = #{isbn}
</update>
- trim : 添加前缀和忽略分割符号
使用trim代替where: 设置前缀和忽略运算符
<select id="queryByPojo" parameterType="book" resultMap="BaseBookMap">
select * from g_book
<trim prefix="where" prefixOverrides="and | or">
<if test="isbn != null and isbn != ''">
and isbn = #{isbn}
</if>
<if test="title != null and title !=''">
and title like '%${title}%'
</if>
</trim>
</select>
使用trim代替set:
<update id="updateBook" parameterType="book">
update g_book
<trim prefix="set" suffixOverrides=",">
<if test="title != null and title != ''">
title = #{title},
</if>
<if test="cost != null">
cost = #{cost},
</if>
<if test="price != null">
price = #{price},
</if>
</trim>
where isbn = #{isbn}
</update>
[4]foreach
案例:
接口:
List<Book> queryByIsbns(List<String> isbns);
SQL:
<!-- 通过isbn列表查询 -->
<select id="queryByIsbns" parameterType="list" resultMap="BaseBookMap">
select * from g_book where isbn in
<foreach collection="list" index="index" item="isbn" open="(" close=")" separator=",">
#{isbn}
</foreach>
</select>
注解配置映射sql
MyBatis可以不写映射文件,直接使用接口,并且在接口中使用注解直接完成sql映射
/**
* @author 戴着假发的程序员
* @company 起点编程
* 2022/10/9 14:09
*/
public interface PublisherMapper {
@Insert("insert into g_pubsher(pid,pname) values(#{pid},#{pname})")
// @SelectKey("sel")
int save(Publisher publisher);
@Select("select * from g_pubsher where pid = #{pid}")
Publisher queryById(String pid);
@Update("update g_pubsher set pname = #{pname} where pid = #{pid}")
int update(Publisher publisher);
@Delete("delete from g_pubsher where pid = #{pid}")
int delete(String pid);
@Select("select * from g_pubsher")
List<Publisher> queryAll();
}
将这个接口注册到MyBatis的核心配置文件中:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yf96bUQy-1671874540720)(笔记.assets/1665296112705.png)]
测试:
@Test
public void testAnnotation(){
SqlSession sqlSession = sqlSessionFactory.openSession();
PublisherMapper mapper = sqlSession.getMapper(PublisherMapper.class);
List<Publisher> publishers = mapper.queryAll();
System.out.println(publishers);
}
e})")
// @SelectKey(“sel”)
int save(Publisher publisher);
@Select(“select * from g_pubsher where pid = #{pid}”)
Publisher queryById(String pid);
@Update(“update g_pubsher set pname = #{pname} where pid = #{pid}”)
int update(Publisher publisher);
@Delete(“delete from g_pubsher where pid = #{pid}”)
int delete(String pid);
@Select(“select * from g_pubsher”)
List queryAll();
}
将这个接口注册到MyBatis的核心配置文件中:
[外链图片转存中...(img-Yf96bUQy-1671874540720)]
测试:
```java
@Test
public void testAnnotation(){
SqlSession sqlSession = sqlSessionFactory.openSession();
PublisherMapper mapper = sqlSession.getMapper(PublisherMapper.class);
List<Publisher> publishers = mapper.queryAll();
System.out.println(publishers);
}
博文转自@戴着假发的程序员