MyBatis 教程

文章列表
简介 准备工作 回顾 JDBC 数据准备 查找id为1的用户信息 自定义连接池 不用MyBatis配置文件 查询密码为123的所有用户 如果Bean中成员变量和表中字段命名不一致 更多查询用户的方式 对查询结果排序 日志 添加、删除、修改数据 事务 动态SQL 一对一和一对多的实现 一对一和一对多的延迟加载 多对多的实现 分页查询 把SQL写在注解中 自动生成Mapper代码和映射XML mybatis generator 生成 select for update mybatis generator 支持数据版本号

MyBatis: 一对一和一对多的实现


什么是一对一?一个人只有一个身份证号,一篇博客是一个人写的,这就是一对一。

什么是一对多?一个老师有多个学生,一个人写了很多博客,这就是一对多。

什么是多对多?一个老师有多个学生,一个学生有多个老师,学生和老师之间就是多对多。

这里先讲一对一、一对多,如何用 mybatis 实现。

本节示例代码在 mybatis-demo-012

数据准备

数据准备

user表和blog表的默认内容如下:

mysql> select * from user;
+----+--------+----------------+----------+
| id | name   | email          | password |
+----+--------+----------------+----------+
|  1 | letian | letian@111.com | 123      |
|  2 | xiaosi | xiaosi@111.com | 123      |
+----+--------+----------------+----------+

mysql> select * from blog;
+----+----------+---------------+--------------+
| id | owner_id | title         | content      |
+----+----------+---------------+--------------+
|  1 |        1 | 标题1         | 文本1        |
|  2 |        1 | 标题2         | 文本2        |
|  3 |        1 | 标题3         | 文本3        |
|  4 |        1 | 标题4         | 文本4        |
|  5 |        1 | 标题5         | 文本5        |
|  6 |        2 | 标题21        | 文本21       |
|  7 |        1 | 你好, World   | 你好, 😆       |
+----+----------+---------------+--------------+

项目结构

使用 IDEA 创建 gradle 项目,最终结构如下:

新的 User、Blog 类

在 User 类中新增字段

package bean;

import lombok.Data;

import java.util.List;

@Data
public class User {

    private Long id;
    private String name;
    private String email;
    private String password;
    private List<Blog> blogs; // 属于该用户的博客

}

用户和博客是一对多的关心,所以增加了List<Blog> blogs变量。

在本节示例中会新引入有引入blog 表,对应的 bean 定义:

package bean;

import lombok.Data;

@Data
public class Blog {

    private Long id;
    private Long ownerId;  // 博客作者的用户id
    private String title;
    private String content;
    private User user;  // 博客作者信息

}

博客和用户是一对一的关系,所以增加了User user变量。

让mybatis输出执行流程

配置 log4j.properties 内容为:

log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

# mapper 包下的所有接口的方法都会打日志,会打印不低于 DEBUG 级别的日志
log4j.logger.org.mybatis.mapper=TRACE

如此,mapper 包下的所有接口的方法都会打日志,会打印不低于 DEBUG 级别的日志。而DEBUG 是日志的最低级别,所以这种配置我们能看到mybatis的运行流程。

根据 id 获取用户及其博客信息

在 UserMapper 中增加方法:

User findById(Long id);

在 UserMapper.xml 增加以下内容:

<resultMap id="userResult" type="bean.User">

    <result property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <result property="email" column="user_email"/>
    <result property="password" column="user_password"/>

    <collection property="blogs" ofType="bean.Blog">
        <id property="id" column="blog_id"/>
        <result property="ownerId" column="user_id"/>
        <result property="title" column="blog_title"/>
        <result property="content" column="blog_content"/>
    </collection>

</resultMap>

<select id="findById" parameterType="Long" resultMap="userResult" resultType="bean.User">
    SELECT
        user.id AS user_id,
        user.name AS user_name,
        user.email AS user_email,
        user.password AS user_password,
        blog.id AS blog_id,
        blog.title AS blog_title,
        blog.content AS blog_content
    FROM user, blog 
    WHERE 
        user.id = #{id} 
        AND user.id=blog.owner_id;
</select>

<select id="findById"></select>中的sql语句的作用是根据给出的用户id得到用户的信息以及该用户所有的blog,所以SQL 执行结果中,若有多条记录,那么 user_iduser_nameuser_emailuser_password的值是一样的。而Blog信息会 不一样。例如执行:

SELECT
    user.id AS user_id,
    user.name AS user_name,
    user.email AS user_email,
    user.password AS user_password,
    blog.id AS blog_id,
    blog.title AS blog_title,
    blog.content AS blog_content
FROM user, blog 
WHERE 
    user.id = 1 
    AND user.id=blog.owner_id;

结果是:

<select id="findById"></select>的resultMap属性值为userResult,所以会找到<resultMap id="userResult"></resultMap>来处理查询到的数据。上面的示例中我们查到了6条数据,mybatis根据<resultMap id="userResult"></resultMap>的配置,将相同 user_iduser_nameuser_emailuser_password的数据收拢在一个User类中,而blog_idblog_titleblog_content分别构造Blog对象,然后放入User对象的List类型的 blogs 变量中。<collection>用于1对多。

在 Main 类中编写测试代码:

@Test
public void test_01() throws IOException {
    SqlSession sqlSession = getSqlSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.findById(1L);
    log.info("{}", user);
}

运行结果如下(忽略myabatis的执行信息):

 INFO [main] - User(id=1, name=letian, email=letian@111.com, password=123, blogs=[Blog(id=1, ownerId=1, title=标题1, content=文本1, user=null), Blog(id=2, ownerId=1, title=标题2, content=文本2, user=null), Blog(id=3, ownerId=1, title=标题3, content=文本3, user=null), Blog(id=4, ownerId=1, title=标题4, content=文本4, user=null), Blog(id=5, ownerId=1, title=标题5, content=文本5, user=null), Blog(id=7, ownerId=1, title=你好, World, content=你好, 😆, user=null)])

根据密码查询用户及其博客信息

实际案例中,几乎不会有根据密码查询用户的业务。这里仅作为示例,展示如何使用mybatis。

在 UserMapper 接口中增加函数:

List<User> findByPassword(String password);

在 UserMapper.xml 增加映射:

<select id="findByPassword" parameterType="String" resultMap="userResult" resultType="bean.User">
    SELECT
        user.id AS user_id,
        user.name AS user_name,
        user.email AS user_email,
        user.password AS user_password,
        blog.id AS blog_id,
        blog.title AS blog_title,
        blog.content AS blog_content
    FROM
        user, blog
    WHERE
        user.password = #{password}
        AND user.id=blog.owner_id;
</select>

与根据id查询用户及其博客信息相同,这里的 resultMap 也使用了userResult。

在 Main 类中增加示例代码:

@Test
public void test_02() throws IOException {
    SqlSession sqlSession = getSqlSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = userMapper.findByPassword("123");

    userList.forEach(user -> {
        log.info("{}", user);
    });
}

执行结果(忽略mybatis执行信息):

 INFO [main] - User(id=1, name=letian, email=letian@111.com, password=123, blogs=[Blog(id=1, ownerId=1, title=标题1, content=文本1, user=null), Blog(id=2, ownerId=1, title=标题2, content=文本2, user=null), Blog(id=3, ownerId=1, title=标题3, content=文本3, user=null), Blog(id=4, ownerId=1, title=标题4, content=文本4, user=null), Blog(id=5, ownerId=1, title=标题5, content=文本5, user=null), Blog(id=7, ownerId=1, title=你好, World, content=你好, 😆, user=null)])

 INFO [main] - User(id=2, name=xiaosi, email=xiaosi@111.com, password=123, blogs=[Blog(id=6, ownerId=2, title=标题21, content=文本21, user=null)])

得到两个user结果,第1个user有6个博客,第2个user有1个博客。

根据id查询博客及其所属用户信息

在 BlogMapper 接口中新增方法:

Blog findById(Long id);

在 BlogMapper.xml 中增加映射:

<resultMap id="blogResult" type="bean.Blog">
    <result property="id" column="blog_id"/>
    <result property="ownerId" column="user_id"/>
    <result property="title" column="blog_title"/>
    <result property="content" column="blog_content"/>

    <association property="user" javaType="bean.User">
        <id property="id" column="user_id"/>
        <result property="name" column="user_name"/>
        <result property="email" column="user_email"/>
        <result property="password" column="user_password"/>
    </association>

</resultMap>

<select id="findById" parameterType="Long" resultMap="blogResult" resultType="bean.Blog">
    SELECT
        blog.id AS blog_id,
        blog.title AS blog_title,
        blog.content AS blog_content,
        user.id AS user_id,
        user.name AS user_name,
        user.email AS user_email,
        user.password AS user_password
    FROM blog, user 
    WHERE 
        blog.id = #{id} 
        AND user.id=blog.owner_id;
</select>

这段xml中的逻辑和上面获取用户以及博客信息的思路是一样的。因为博客和用户是一对一的关系,所以<resultMap> 中用的是<association>,而非<collection>

在 Main 类中编写示例代码:

@Test
public void test_03() throws IOException {
    SqlSession sqlSession = getSqlSession();
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
    Blog blog = blogMapper.findById(1L);
    log.info("{}", blog);
}

执行结果(忽略mybatis执行信息):

 INFO [main] - Blog(id=1, ownerId=1, title=标题1, content=文本1, user=User(id=1, name=letian, email=letian@111.com, password=123, blogs=null))

符合预期 😊


( 本文完 )

文章目录