Mybatis(2)

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

如果配置的一个包,这个包下的所有的类都可以使用简称:

bookBookCategorycategory

动态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 &lt;&gt;

当参数是一个实体对象的时候,在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);
}

博文转自@戴着假发的程序员

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值