MyBatis连接:高级结果映射,关联,集合,N + 1选择问题

本教程旨在向您展示如何使用简单的 Java 项目设置 iBatis (MyBatis),还将展示如何配置高级映射(结果映射)、关联(1 到 1)、集合(1 表示 n,n 到 n)、使用 XML 配置和注释选择 n+1 问题的示例。

先决条件

对于本教程,在 Eclipse 中,项目的结构将如下所示:

示例数据

在启动项目之前,运行 sql 文件夹中的脚本。此文件包含本教程中使用的示例数据。

第 1 名 - POJO - 豆类

我将用 UML 图表示 Bean,以便更好地可视化。完整的源代码可以在本教程结束时下载。

本文的目的是演示如何从数据库中检索所有博客信息,但如您所见,“博客”类包含一个关联(Author)和一个帖子集合(它包含一个标记集合)。我们将尝试一次检索所有这些信息。

因此,我们将使用iBatis/Mybatis演示一对一,一对多和多对多关系。

2 - 高级映射 - 结果映射

上述代码中的结果映射是高级结果映射。正如我在之前的文章中已经提到的,结果映射元素是MyBatis中最重要和最强大的元素。

MyBatis的创建有一个想法:数据库并不总是你想要的或需要的。虽然我们希望每个数据库都是完美的第三范式或BCNF,但事实并非如此。如果有可能将单个数据库完美地映射到所有使用它的应用程序,那就太好了,但事实并非如此。

resultMap 元素有许多子元素和一个值得讨论的结构。以下是结果映射元素的概念视图。

  • 构造函数 – 用于在实例化时将结果注入类的构造函数
  • id – ID 结果,将结果标记为 ID 将有助于提高整体性能
  • 结果 – 注入字段或 JavaBean 属性的正常结果
  • 关联 – 复杂类型关联;许多结果将汇总到此类型中
  • 集合 – 复杂类型的集合
  • 鉴别器 – 使用结果值来确定要使用的结果映射。

最佳实践:始终以增量方式生成结果映射。单元测试在这里确实有帮助。如果你试图同时构建一个巨大的结果地图,就像你上面一样,很可能你会弄错,而且很难使用。从简单的开始,一步一步地发展它。和单元测试!使用框架的缺点是它们有时有点像黑匣子(开源与否)。确保实现预期行为的最佳选择是编写单元测试。在提交错误时使用它们也会有所帮助

我们的目标是实现以下结果映射。

<resultMap id="resultBlog" type="Blog">
<id property="id" column="idBlog" />
<result property="name" column="blogname" />
<result property="url" column="blogurl" />
<association property="author" column="idBlog" javaType="Author"
select="selectAuthor" />
<collection property="posts" column="idBlog" javaType="ArrayList"
ofType="Post" select="selectPosts" resultMap="resultTag" />
</resultMap>

但是,让我们在当时迈出一步。我们将开始仅检索 Blog 数据,因此我们的初始结果映射和查询将如下所示:

<resultMap id="resultBlog" type="Blog">
<id property="id" column="idBlog" />
<result property="name" column="blogname" />
<result property="url" column="blogurl" />
</resultMap>

<select id="selectBlog" resultMap="resultBlog">
SELECT idBlog, name as blogname, url as blogurl FROM BLOG
</select>

到目前为止,一切都很好,我们在之前的帖子中看到了一个简单的选择。让我们继续下一步。

关联

现在,我们还尝试检索作者数据。

关联元素处理“有一”类型关系。例如,在我们的示例中,博客有一个作者。关联映射的工作方式与任何其他结果非常相似。您可以指定目标属性、要从中检索值的列、属性的 javaType(MyBatis 在大多数情况下可以计算出来)、jdbcType(如有必要)以及类型处理程序(如果要覆盖结果值的检索)。

关联的不同之处在于,您需要告诉 MyBatis 如何加载关联。我的巴蒂斯可以通过两种不同的方式做到这一点:

  • 嵌套选择:通过执行另一个返回所需复杂类型的映射 SQL 语句。
  • 嵌套结果:通过使用嵌套结果映射来处理联接结果的重复子集。

让我们先来看看嵌套选择。

这是我们的结果映射与作者关联。

<resultMap id="resultBlog" type="Blog">
<id property="id" column="idBlog" />
<result property="name" column="blogname" />
<result property="url" column="blogurl" />
<association property="author" column="idBlog" javaType="Author"
select="selectAuthor" />
</resultMap>

<select id="selectBlog" resultMap="resultBlog">
SELECT idBlog, name as blogname, url as blogurl FROM BLOG
</select>

<select id="selectAuthor" parameterType="int" resultType="Author">
SELECT idAuthor as id, name, email FROM AUTHOR WHERE idBlog = #{idBlog}
</select>

查看“选择”=“选择作者”属性。这意味着 MyBatis 将运行查询以获取写入博客的作者。要建立博客和作者之间的关系,我们指定属性列=“idBlog”,以过滤作者。

请注意,我们还指定了 java 类型 =“作者”。我们使用的是别名(还记得吗?如果您没有别名,则必须将类的全名(与包名称放在一起:com.loiane.model.author)。

就是这样。我们有2个带有选择语句的查询:一个用于从博客上传数据,另一个用于加载作者,博客结果Map描述MyBatis应使用“选择作者”语句来获取作者信息。

此方法非常简单,但对于大型数据集或列表效果不佳。此问题称为“选择 N+1 问题”。此问题是由以下原因引起的:

  • 运行返回记录列表 (“+1”) 的 SQL。
  • 对于返回的每条记录,运行加载详细信息的 SELECT(“N”)。

此问题可能导致数百或数千个 SELECT 运行。这有时不是你想要的。好的部分是iBatis可以执行这样的查询,但您应该意识到这在性能方面是昂贵的,即性能不佳。

我将在文章中向您展示如何避免此问题,但首先我们将了解如何处理列表或集合。

收集

我们已经在数据库中查找来自博客和作者的信息。博客包含帖子列表,而这些帖子又包含标签列表。我们现在正在处理2个关系:1-N(博客文章)和N-N(帖子标签)。让我们看看如何使用我的巴蒂斯来做到这一点。

让我们仍然使用嵌套选择来执行此操作。

看看带有 Post 集合的结果映射:

<resultMap id="resultBlog" type="Blog">
<id property="id" column="idBlog" />
<result property="name" column="blogname" />
<result property="url" column="blogurl" />
<association property="author" column="idBlog" javaType="Author"
select="selectAuthor" />
<collection property="posts" column="idBlog" javaType="ArrayList"
ofType="Post" select="selectPosts" resultMap="resultTag" />
</resultMap>

集合元素的工作方式与关联的工作方式几乎相同。让我们关注差异。

首先,我们使用集合元素。然后你会注意到有“的类型”属性。需要此特性来注意 JavaBean 属性类型(特性)与集合所包含的类型之间的差异。

为了处理 Post 和 Tag 之间的 N-N 关系,我们还将使用集合元素,但我们不会为此使用嵌套结果。

<resultMap id="resultPosts" type="Post">
<id property="id" column="idPost" />
<result property="title" column="title" />
<collection property="tags" column="idPost" javaType="ArrayList"
ofType="Tag" resultMap="resultTag" />
</resultMap>

<resultMap id="resultTag" type="Tag">
<id property="id" column="idTag" />
<result property="value" column="value" />
</resultMap>

<select id="selectPosts" parameterType="int" resultType="Post"
resultMap="resultPosts">
SELECT
P.idPost as idPost, P.title as title,
T.idTag as idTag, T.value as value
FROM Post P
left outer join Post_Tag PT on P.idPost = PT.idPost
left outer join Tag T on PT.idTag = T.idTag
WHERE P.idBlog = #{idBlog}
</select>

准备!让我们看看Blog.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="Blog">

    <resultMap id="resultBlog" type="Blog">
        <id property="id" column="idBlog"/>
        <result property="name" column="blogname"/>
        <result property="url" column="blogurl"/>
        <association property="author" column="idBlog" javaType="Author" select="selectAuthor"/>
        <collection property="posts" column="idBlog" javaType="ArrayList" ofType="Post"
                        select="selectPosts" resultMap="resultTag"/>
    </resultMap>

    <resultMap id="resultPosts" type="Post">
        <id property="id" column="idPost"/>
        <result property="title" column="title"/>
        <collection property="tags" column="idPost" javaType="ArrayList" ofType="Tag"
                        resultMap="resultTag"/>
    </resultMap>

    <resultMap id="resultTag" type="Tag">
        <id property="id" column="idTag"/>
        <result property="value" column="value"/>
    </resultMap>

    <select id="selectBlog" resultMap="resultBlog">
        SELECT idBlog, name as blogname, url as blogurl FROM BLOG
    </select>

    <select id="selectAuthor" parameterType="int" resultType="Author">
        SELECT idAuthor as id, name, email FROM AUTHOR WHERE idBlog = #{idBlog}
    </select>

    <select id="selectPosts" parameterType="int" resultType="Post" resultMap="resultPosts">
        SELECT
        P.idPost as idPost, P.title as title,
        T.idTag as idTag, T.value as value
        FROM Post P
        left outer join Post_Tag PT on P.idPost = PT.idPost
        left outer join Tag T on PT.idTag = T.idTag
        WHERE P.idBlog = #{idBlog}
    </select>

</mapper>

N+1 选择问题的解决方案

如何解决这个问题?

使用嵌套结果: - 使用嵌套映射来处理连接结果中的重复子集。

我们需要做的是编写一个查询来一次获取所有信息,ResultMap 处理结果。

非常重要:ID 元素在嵌套结果映射中起着非常重要的作用。 始终指定一个或多个可用于将结果标识为唯一的属性。事实是,如果您不指定,MyBatis仍然可以工作,但它会降低性能。在这种情况下,主键是一个明显的选择(即使它是复合键)。
博客文件.xml因此使用嵌套结果(BlogBestPractice.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="BlogBestPractice">

    <resultMap id="resultBlog" type="Blog">
        <id property="id" column="idBlog"/>
        <result property="name" column="blogName"/>
        <result property="url" column="url"/>
        <association property="author" column="idBlog" javaType="Author">
            <id property="id" column="idAuthor"/>
            <result property="name" column="authorName"/>
            <result property="email" column="email"/>
        </association>
        <collection property="posts" column="idBlog" javaType="ArrayList" ofType="Post">
            <id property="id" column="idPost"/>
            <result property="title" column="title"/>
            <collection property="tags" column="idBlog" javaType="ArrayList" ofType="Tag">
                <id property="id" column="idTag"/>
                <result property="value" column="value"/>
            </collection>
        </collection>
    </resultMap>


    <select id="selectBlogBestPractice" resultMap="resultBlog">
        SELECT
        B.idBlog as idBlog, B.name as blogName, B.url as url,
        A.idAuthor as idAuthor, A.name as authorName, A.email as email ,
        P.idPost as idPost, P.title as title,
        T.idTag as idTag, T.value as value
        FROM BLOG as B
        left outer join Author A on B.idBlog = A.idBlog
        left outer join Post P on P.idBlog = B.idBlog
        left outer join Post_Tag PT on P.idPost = PT.idPost
        left outer join Tag T on PT.idTag = T.idTag
    </select>

</mapper>
请注意,这是很好的做法。最好避免N + 1问题,除非这是选择的解决方案,但请继续关注性能。

3 - 博客道

现在我们已经设置好了所有内容,让我们编写DAO:
package com.loiane.dao;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

import com.loiane.model.Blog;

/**
 * Blog DAO - xml configuration
 *
 * @author Loiane Groner http://loianegroner.com (English) http://loiane.com
 * (Portuguese)
 */
public class BlogDAO {

    /**
     * Returns the list of all Contact instances from the database.
     *
     * @return the list of all Contact instances from the database.
     */
    @SuppressWarnings("unchecked")
    public List<Blog> select() {

        SqlSessionFactory sqlSessionFactory = MyBatisConnectionFactory.getSqlSessionFactory();
        SqlSession session = sqlSessionFactory.openSession();

        try {
            List<Blog> list = session.selectList("Blog.selectBlog");
            return list;
        } finally {
            session.close();
        }
    }

    /**
     * Returns the list of all Contact instances from the database avoiding the
     * N + 1 problem
     *
     * @return the list of all Contact instances from the database.
     */
    @SuppressWarnings("unchecked")
    public List<Blog> selectN1ProblemSolution() {

        SqlSessionFactory sqlSessionFactory = MyBatisConnectionFactory.getSqlSessionFactory();
        SqlSession session = sqlSessionFactory.openSession();

        try {
            List<Blog> list = session.selectList("BlogBestPractice.selectBlogBestPractice");
            return list;
        } finally {
            session.close();
        }
    }
}
此 DAO 中有 2 种方法:第一种方法使用第一种方法(具有选择 n+1 问题)进行选择,第二种方法使用避免 n+1 问题的查询搜索数据。

4 - 注释

由于我已经发表了一篇解释MyBatis中基本注释的文章,我只想列出差异,好吗?
让我们写2个选择(一个专业博客,一个专业作者和一个帖子和标签,就像我们在博客上所做的那样.xml):
package com.loiane.data;

import java.util.List;

import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;

import com.loiane.model.Author;
import com.loiane.model.Blog;
import com.loiane.model.Post;

/**
 * Blog Mapper contains all the myBatis/iBatis annotations
 *
 * @author Loiane Groner http://loianegroner.com (English) http://loiane.com
 * (Portuguese)
 */
public interface BlogMapper {

    final String SELECT_POSTS = "SELECT  P.idPost as idPost, P.title as title, T.idTag as idTag, T.value as value "
            + "FROM Post P left outer join Post_Tag PT on P.idPost = PT.idPost "
            + "left outer join Tag T on PT.idTag = T.idTag WHERE P.idBlog = #{idBlog}";

    /**
     * Returns the list of all Blog instances from the database.
     *
     * @return the list of all Blog instances from the database.
     */
    @Select("SELECT idBlog, name as blogname, url as blogurl FROM BLOG")
    @Results(value = {
        @Result(property = "id", column = "idBlog"),
        @Result(property = "name", column = "blogname"),
        @Result(property = "url", column = "blogurl"),
        @Result(property = "author", column = "idBlog", javaType = Author.class, one = @One(select = "selectAuthor")),
        @Result(property = "posts", column = "idBlog", javaType = List.class, many = @Many(select = "selectBlogPosts"))
    })
    List<Blog> selectAllBlogs();

    /**
     * Returns the list of all Author instances from the database of a Blog
     *
     * @param idBlog
     * @return the list of all Author instances from the database of a Blog
     */
    @Select("SELECT idAuthor as id, name, email FROM AUTHOR WHERE idBlog = #{idBlog}")
    Author selectAuthor(String idBlog);

    /**
     * Returns the list of all Post instances from the database of a Blog
     *
     * @param idBlog
     * @return the list of all Post instances from the database of a Blog
     */
    @Select(SELECT_POSTS)
    @Results(value = {
        @Result(property = "id", column = "idPost"),
        @Result(property = "title", column = "title"),
        @Result(property = "tags", column = "idPost", javaType = List.class, many = @Many)
    })
    List<Post> selectBlogPosts(String idBlog);

}
让我们使用注释处理关系 1-1 和 1-N @One@Many
让我们回顾一下@Result注释:

@结果

列和属性之间的单个映射。
特性:标识、列、属性、javaType、jdbcType、类型处理程序、 一个多个
id 属性是一个布尔值,指示是否应将该属性用于比较。(类似于 XML 配置中的<id> 标记)。
一个属性用于关联,类似于<关联> 标记, 许多属性用于收集,类似于<集合>。他们以这种方式命名以避免冲突。

@One

复杂类型的单个属性之间的映射。
属性:选择为获取结果而必须执行的查询的名称(映射器方法名称)。

@Many

作为复杂类型集合的属性之间的映射。
属性:选择为获取结果而必须执行的查询的名称(映射器方法名称)。

5 - SqlMapConfig.xml

文件.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>
    <settings>

        <setting name="logImpl" value="LOG4J"/>
    
    </settings>


    <typeAliases>
        <typeAlias alias="Blog" type="com.loiane.model.Blog"/>
        <typeAlias alias="Author" type="com.loiane.model.Author"/>
        <typeAlias alias="Post" type="com.loiane.model.Post"/>
        <typeAlias alias="Tag" type="com.loiane.model.Tag"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/blog_ibatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="com/loiane/data/Blog.xml"/>
        <mapper resource="com/loiane/data/BlogBestPractice.xml"/>
    </mappers>

</configuration>

6 - 我的巴蒂斯连接工厂

在 sqlMap 配置.xml定义别名和 XML 中的 2 个映射器。但现在我们也在同一项目中使用注释。
我们需要在MyBatis连接工厂中设置新的映射器。
因此,我们可以在项目中同时使用两者:XML和注释。
个人意见:只使用一个(XML或注释),使用两者有点混乱。
package com.loiane.dao;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.loiane.data.BlogMapper;

/**
 * MyBatis Connection Factory, which reads the configuration data from a XML
 * file.
 *
 * @author Loiane Groner http://loianegroner.com (English) http://loiane.com
 * (Portuguese)
 */
public class MyBatisConnectionFactory {

    private static SqlSessionFactory sqlSessionFactory;

    static {

        try {

            String resource = "SqlMapConfig.xml";
            Reader reader = Resources.getResourceAsReader(resource);

            if (sqlSessionFactory == null) {
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

                sqlSessionFactory.getConfiguration().addMapper(BlogMapper.class);
            }
        } catch (FileNotFoundException fileNotFoundException) {
            fileNotFoundException.printStackTrace();
        } catch (IOException iOException) {
            iOException.printStackTrace();
        }
    }

    public static SqlSessionFactory getSqlSessionFactory() {

        return sqlSessionFactory;
    }

}

Main

package com.loiane.data;

import com.loiane.model.Blog;
import java.io.IOException;
import java.io.Reader;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class MyBatisDemo {

    private static SqlSessionFactory factory = null;

    public static void main(String[] args) throws IOException {

        String resource = "SqlMapConfig.xml";
        Reader reader = null;
        SqlSession session = null;

        reader = Resources.getResourceAsReader(resource);

        factory = new SqlSessionFactoryBuilder().build(reader);
        //factory.getConfiguration().addMapper(MyMapper.class);
//        factory.getConfiguration().addMapper(BlogMapper.class);

        reader.close();

        try {
            session = factory.openSession();
//            String version = session.selectOne("getMySQLVersion");
//            System.out.println(version);

            List<Blog> list;
            list = session.selectList("Blog.selectBlog");

            for (Blog blog : list) {
                System.out.println(blog.toString());
            }

            list = session.selectList("BlogBestPractice.selectBlogBestPractice");

            for (Blog blog : list) {
                System.out.println(blog.toString());
            }

        } finally {

            if (session != null) {
                session.close();
            }
        }
    }
}

下载

https://github.com/allwaysoft/MyBatisMySQLDemoOne2OneAndOne2Many

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis ,一对多关联查询结果映射通常会采用两种方式: 1. 嵌套结果映射:使用嵌套结果映射可以将一对多关联查询的结果集映射成对象的嵌套结构。例如,假设我们有一个 User 对象和一个 Order 对象,一个用户可以拥有多个订单,我们可以通过如下方式进行嵌套结果映射: ``` <resultMap id="userResultMap" type="User"> <id column="id" property="id"/> <result column="name" property="name"/> <collection property="orders" ofType="Order"> <id column="order_id" property="id"/> <result column="order_name" property="name"/> <result column="order_amount" property="amount"/> </collection> </resultMap> ``` 上述代码,我们定义了一个名为 userResultMap 的 resultMap,包含了 User 对象的 id 和 name,以及一个 Order 对象的集合 orders,其 Order 对象包含 id、name 和 amount 属性。在 SQL 查询,我们需要使用 LEFT JOIN 等方式将 User 和 Order 表进行关联查询,并通过 DISTINCT 或 GROUP BY 等方式去重。 2. 延迟加载:使用延迟加载可以避免在一对多关联查询时一次性加载所有的关联对象,从而提高查询性能。MyBatis 提供了两种延迟加载方式,分别是按需加载和分步加载。按需加载是指在需要使用关联对象时再进行加载,而分步加载是指在多次查询逐步加载关联对象。两种方式都需要在配置文件进行相应的配置。 总之,MyBatis一对多关联查询结果映射采用嵌套结果映射或延迟加载的方式,具体采用哪种方式取决于业务需求和性能要求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值