07 Mybatis的多表查询1----1对多和多对1---@Results注解用法总结

1.表与表之间的关系及其举例

表之间的关系有4种:一对多、多对一、一对一、多对多。
举例:
  (1)用户和订单就是一对多

    一个用户可以下多个订单
  (2)订单和用户就是多对一
    多个订单属于同一个用户

  (3)人和身份证号就是一对一
    一个人只能有一个身份证号
    一个身份证号只能属于一个人

  (4)老师和学生之间就是多对多
    一个学生可以被多个老师教过
    一个老师可以交多个学生

2.mybatis中的多表查询
示例:用户和账户
  一个用户可以有多个账户
  一个账户只能属于一个用户(多个账户也可以属于同一个用户)
步骤:
  1、建立两张表:用户表,账户表
    让用户表和账户表之间具备一对多的关系:需要使用外键在账户表中添加
  2、建立两个实体类:用户实体类和账户实体类
    让用户和账户的实体类能体现出来一对多的关系
  3、建立两个配置文件
    用户的配置文件
    账户的配置文件
  4、实现配置:
    当我们查询用户时,可以同时得到用户下所包含的账户信息
    当我们查询账户时,可以同时得到账户的所属用户信息

3.@Results注解用法总结:

MyBatis中使用@Results注解来映射查询结果集到实体类属性
(1)@Results的基本用法。当数据库字段名与实体类对应的属性名不一致时,可以使用@Results映射来将其对应起来。column为数据库字段名,porperty为实体类属性名,jdbcType为数据库字段数据类型,id为是否为主键。

@Select({"select id, name, class_id from my_student"})
@Results({
    @Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
    @Result(column="name", property="name", jdbcType=JdbcType.VARCHAR),
    @Result(column="class_id", property="classId", jdbcType=JdbcType.INTEGER)
})
List<Student> selectAll();

如上所示的数据库字段名class_id与实体类属性名classId,就通过这种方式建立了映射关系。

(2)@ResultMap的用法。当这段@Results代码需要在多个方法用到时,为了提高代码复用性,我们可以为这个@Results注解设置id,然后使用@ResultMap注解来复用这段代码。

复制代码
@Select({"select id, name, class_id from my_student"})
@Results(id="studentMap", value={
    @Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
    @Result(column="class_id", property="classId", jdbcType=JdbcType.INTEGER)
})
List<Student> selectAll();
 
@Select({"select id, name, class_id from my_student where id = #{id}"})
@ResultMap(value="studentMap")
Student selectById(integer id);
复制代码

(3)@One的用法。当我们需要通过查询到的一个字段值作为参数,去执行另外一个方法来查询关联的内容,而且两者是一对一关系时,可以使用@One注解来便捷的实现。比如当我们需要查询学生信息以及其所属班级信息时,需要以查询到的class_id为参数,来执行ClassesMapper中的selectById方法,从而获得学生所属的班级信息。可以使用如下代码。

@Select({"select id, name, class_id from my_student"})
@Results(id="studentMap", value={
    @Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
    @Result(column="class_id", property="myClass", javaType=MyClass.class,
        one=@One(select="com.example.demo.mapper.MyClassMapper.selectById"))
})
List<Student> selectAllAndClassMsg();

(4)@Many的用法。与@One类似,只不过如果使用@One查询到的结果是多行,会抛出TooManyResultException异常,这种时候应该使用的是@Many注解,实现一对多的查询。比如在需要查询学生信息和每次考试的成绩信息时。

@Select({"select id, name, class_id from my_student"})
@Results(id="studentMap", value={
    @Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
    @Result(column="class_id", property="classId", jdbcType=JdbcType.INTEGER),
    @Result(column="id", property="gradeList", javaType=List.class,
        many=@Many(select="com.example.demo.mapper.GradeMapper.selectByStudentId"))
})
List<Student> selectAllAndGrade();

参考文献:https://blog.csdn.net/cherlshall/article/details/80950150

4.操作案例

(1)案例1

查询所有查询所有账户,及其用户名和地址信息(逻辑为:账户表account-----》用户信息表user)

<1>对数据库表Account对应的实体类Account.java进行改造,加入User对象作为成员变量

复制代码
package domain;

import java.io.Serializable;

/**
 * 数据库的account表对应的实体类
 */
public class Account implements Serializable {
    private Integer id;
    private Integer uid;
    private Double money;
    //从表实体应该包含一个主表实体的对象引用
    private User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", uid=" + uid +
                ", money=" + money +
                '}';
    }
}
复制代码

<2>IAccountDao中添加findAll()方法

复制代码
package dao;

import domain.Account;
import domain.AccountUser;

import java.util.List;

public interface IAccountDao {

    /**
     * 查询所有查询所有账户,及其用户名和地址信息
     * @return
     */
    List<Account> findAll();

  
}
复制代码

<3>映射关系配置

(1)IAccountDao.xml配置

ResultMap标签基本作用:建立SQL查询结果字段与实体属性的映射关系信息

标签属性id和type的含义:

  id:该resultMap的标志
  type:返回值的类名

子标签含义:

  id:用于设置主键字段与领域模型属性的映射关系,此处主键为ID,对应id。
  result:用于设置普通字段与领域模型属性的映射关系
  association 为关联关系,是实现一对一的关键
    1. property 为javabean中容器对应字段名     
    2. javaType 指定关联的类型,当使用select属性时,无需指定关联的类型
    3. select 使用另一个select查询封装的结果
    4. column 为数据库中的列名,与select配合使用 

复制代码
<?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="dao.IAccountDao">

    <resultMap id="accountUserMap" type="domain.Account">
        <id property="id" column="aid"></id>
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>
        <!--配置所外键所关联表user的内容-->
        <association property="user" column="uid" javaType="domain.User">
            <id property="id" column="id"></id>
            <result property="username" column="username"></result>
            <result property="birthday" column="birthday"></result>
            <result property="sex" column="sex"></result>
            <result property="address" column="address"></result>
        </association>
    </resultMap>
    
    <!-- 查询所有查询所有账户,及其用户名和地址信息:方法1(更通用) -->
    <!--account a 给account表起一个别名-->
    <select id="findAll" resultMap="accountUserMap">
        select u.*,a.id as aid,a.uid,a.money from account a , user u where u.id = a.uid;
    </select>


</mapper>
复制代码

注解配置:IAccountDao.java文件中进行如下配置

复制代码
 /**
     * 查询所有查询所有账户,及其用户名和地址信息
     * 注解中的第四个result的column为uid(外键),连接到user表,select调用dao.IUserDao.findById()方法
     * @return
     */
    @Select("select * from account")
    @Results(id="accountMap",value = {
            @Result(id=true,column = "id",property = "id"),
            @Result(column = "uid",property = "uid"),
            @Result(column = "money",property = "money"),
            @Result(column = "uid",property = "user",one=@One(select = "dao.IUserDao.findById",fetchType = FetchType.EAGER))
    })
    List<Account> findAll();
复制代码

<4>测试代码

复制代码
package test;

import dao.IAccountDao;
import dao.IUserDao;
import domain.Account;
import domain.AccountUser;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.InputStream;
import java.util.List;

public class MybatisTest01 {

    private InputStream in;
    private SqlSession sqlSession;
    private IAccountDao accountDao;

    /**
     * 初始化MyBatis
     * @throws Exception
     */
    public void initMyBatis() throws Exception{
        //1.读取配置文件
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建SqlSessionFactory
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder(); //创建SqlSessionFactory的构建者builder
        SqlSessionFactory factory=builder.build(in);  //利用构建者builder创建SqlSessionFactory
        //3.使用工厂生产SqlSession对象
        sqlSession = factory.openSession();
        //4.使用SqlSessions对象创建Dao接口的代理对象
        accountDao = sqlSession.getMapper(IAccountDao.class);
    }

    /**
     * 释放资源
     * @throws Exception
     */
    public void destroy() throws Exception{
        sqlSession.commit();//提交事务
        sqlSession.close();
        in.close();
    }

    /**
     * 查询所有
     * @throws Exception
     */
    @Test
    public void testFindAll()throws Exception{
        initMyBatis();
        List<Account> accounts = accountDao.findAll();
        for (Account account : accounts) {
            System.out.println(account);
            System.out.println(account.getUser());
        }
        destroy();
    }

  
}
复制代码

效果图:

 

(2)案例2

查询所有查询用户信息,及其拥有的账户信息(逻辑为:用户信息表user-----》账户表account)

<1>对数据库表user对应的实体类User.java进行改造,加入List<Account>作为成员变量

复制代码
package domain;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

/**
 * 数据库表对应的实体类
 */
public class User implements Serializable {
    //实体类的成员变量名称应该与数据库中的列名一致
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    private List<Account> accounts;

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }



    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
复制代码

<2>IUserDao中添加findAll()方法

复制代码
package dao;

import domain.User;

import java.util.List;

/**
 *
 */
public interface IUserDao {
    /**
     * 查询所有
     * @return
     */
    List<User> findAll();

}
复制代码

<3>IUserDao.xml

ResultMap标签基本作用:建立SQL查询结果字段与实体属性的映射关系信息

标签属性id和type的含义:

  id:该resultMap的标志
  type:返回值的类名

子标签含义:

  id:用于设置主键字段与领域模型属性的映射关系,此处主键为ID,对应id。
  result:用于设置普通字段与领域模型属性的映射关系
  association 为关联关系,是实现一对一的关键 
    1. property 为javabean中容器对应字段名     
    2. javaType 指定关联的类型,当使用select属性时,无需指定关联的类型
    3. select 使用另一个select查询封装的结果
    4. column 为数据库中的列名

  collection 为关联关系,是实现一对多的关键
    1. property 为javabean中容器对应字段名
    2. ofType 指定集合中元素的对象类型
    3. select 使用另一个查询封装的结果
    4. column 为数据库中的列名,与select配合使用

复制代码
<?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="dao.IUserDao">

    <resultMap id="userAccountMap" type="domain.User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
        <!--配置user对象中accounts集合的映射-->
        <collection property="accounts" ofType="domain.Account">
            <id property="id" column="aid"></id>
            <result property="uid" column="uid"></result>
            <result property="money" column="money"></result>
        </collection>
    </resultMap>
    <!-- 查询所有 -->
    <select id="findAll" resultMap="userAccountMap">
        select * from user u left outer join account a on u.id = a.uid
    </select>
    
</mapper>
复制代码

注解配置:IUserDao.java文件中进行如下配置

复制代码
/**
     * 查询所有
     * @return
     */
    @Select("select * from user")
    @Results(id="userMap",value = {
            @Result(id=true,column = "id",property = "id"),
            @Result(column = "username",property = "username"),
            @Result(column = "address",property = "address"),
            @Result(column = "sex",property = "sex"),
            @Result(column = "birthday",property = "birthday"),
            @Result(column = "id",property ="accounts",many =@Many(select = "dao.IAccountDao.findAccountById",fetchType = FetchType.EAGER))
    })
    List<User> findAll();
复制代码

<4>测试代码

复制代码
package test;

import dao.IAccountDao;
import dao.IUserDao;
import domain.Account;
import domain.AccountUser;
import domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.InputStream;
import java.util.List;

public class MybatisTest02_User {

    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    /**
     * 初始化MyBatis
     * @throws Exception
     */
    public void initMyBatis() throws Exception{
        //1.读取配置文件
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建SqlSessionFactory
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder(); //创建SqlSessionFactory的构建者builder
        SqlSessionFactory factory=builder.build(in);  //利用构建者builder创建SqlSessionFactory
        //3.使用工厂生产SqlSession对象
        sqlSession = factory.openSession();
        //4.使用SqlSessions对象创建Dao接口的代理对象
        userDao = sqlSession.getMapper(IUserDao.class);
    }

    /**
     * 释放资源
     * @throws Exception
     */
    public void destroy() throws Exception{
        sqlSession.commit();//提交事务
        sqlSession.close();
        in.close();
    }

    /**
     * 查询所有
     * @throws Exception
     */
    @Test
    public void testFindAll()throws Exception{
        initMyBatis();
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
            System.out.println(user.getAccounts());
        }
        destroy();
    }

}
复制代码

效果图:

4.Mybatis中的缓存
(1)什么是缓存
  存在于内存中的临时数据。
(2)为什么使用缓存
  减少和数据库的交互次数,提高执行效率。
(3)什么样的数据能使用缓存,什么样的数据不能使用
  <1>适用于缓存:
    经常查询并且不经常改变的。
    数据的正确与否对最终结果影响不大的。
  <2>不适用于缓存:
    经常改变的数据
    数据的正确与否对最终结果影响很大的。
    例如:商品的库存,银行的汇率,股市的牌价。
5.Mybatis中的一级缓存和二级缓存
(1)一级缓存:
  它指的是Mybatis中SqlSession对象的缓存。
  当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。
  该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中
  查询是否有,有的话直接拿出来用。
  当SqlSession对象消失时,mybatis的一级缓存也就消失了。

(2)二级缓存:
  它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
  二级缓存的使用步骤:
    第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
    第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
    第三步:让当前的操作支持二级缓存(在select标签中配置)

posted @   雨后观山色  阅读(2589)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示