本文修订日期:2019年11月25日

SQL脚本

在实际项目开发中,经常存在一对一关系,比如一个人只能有一个身份证,一个身份证只能给一个人使用,这就是一对一的关系。一对一关系推荐使用唯一主外键关联,即两张表使用外键关联,由于是一对一关联,因此还需要给外键列增加unique唯一约束。下面我们就用一个简单示例来看看MyBatis怎么处理一对一关系。

首先,在数据库创建两个表tb_card和tb_person,并插入测试数据。SQL 脚本如下:


drop table if exists tb_card;

create table tb_card (
id int primary key auto_increment,
code varchar(18)
);

insert into tb_card(code) values ('432801198009191038');

drop table if exists tb_person;

create table tb_person (
id int primary key auto_increment,
name varchar(18),
sex varchar(18),
age int,
card_id int unique,
foreign key(card_id) peferences tb_card(id)
);

insert into tb_person(name, sex, age, card_id) values ('jack','男',23,1) ;

提示:
tb_person表的card_id作为外键,其参照tb_card表的主键id,因为是一对一关系,即一个card只能让一个person使用,所以card_id做成了唯一键约束。如此一来,当一个person使用了一个card之后,其他的person就不能使用该card了。

实体类

接下来,创建一个Card对象和一个Person对象分别映射tb_card和tb_person表。

package cn.mybatis.mydemo.domain;

import java.io.Serializable;

public class Card implements Serializable
{
    private static final long serialVersionUID = 1L;
    private Integer id; // 主键id
    private String code; // 身份证编号

    public Card()
    {
        super();
    }

    public Integer getId()
    {
        return id;
    }

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

    public String getCode()
    {
        return code;
    }

    public void setCode(String code)
    {
        this.code = code;
    }

    @Override
    public String toString()
    {
        return "Card [id=" + id + ", code=" + code + "]";
    }

}


package cn.mybatis.mydemo.domain;

import java.io.Serializable;

public class Person implements Serializable
{
    private static final long serialVersionUID = 1L;
    private Integer id; // 主键id
    private String name; // 姓名
    private String sex; // 性别
    private Integer age; // 年龄

    // 人和身份证是一对一的关系,即一个人只有一个身份证
    private Card card;

    public Person()
    {
        super();
    }

    public Integer getId()
    {
        return id;
    }

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

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public String getSex()
    {
        return sex;
    }

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

    public Integer getAge()
    {
        return age;
    }

    public void setAge(Integer age)
    {
        this.age = age;
    }

    public Card getCard()
    {
        return card;
    }

    public void setCard(Card card)
    {
        this.card = card;
    }

    @Override
    public String toString()
    {
        return "Person [id=" + id + ", name=" + name + ", sex=" + sex + ", age=" + age + "]";
    }

}

映射文件

人和身份证是一对一的关系,即一个人只有一个身份证。在Person类中定义了一个card属性,该属性是一个Card类型,用来映射一对一的关联关系,表示这个人的身份证。接下来是XML映射文件。

<mapper namespace="cn.mybatis.mydemo.mapper.CardMapper">

  <!-- 根据id查询Card,返回Card对象 -->
  <select id="selectCardById" parameterType="int" resultType="cn.mybatis.mydemo.domain.Card">
      select * from tb_card where id = #{id} 
  </select>
  
</mapper>



<mapper namespace="cn.mybatis.mydemo.mapper.PersonMapper">

    <!-- 根据id查询Person,返回resultMap -->
    <select id="selectPersonById" parameterType="int" resultMap="personMapper">
        select * from tb_person where id = #{id}
    </select>

    <!-- 映射Peson对象的resultMap -->
    <resultMap type="cn.mybatis.mydemo.domain.Person" id="personMapper">
        <id property="id" column="id" />
        <result property="name" column="name" />
        <result property="sex" column="sex" />
        <result property="age" column="age" />
        
        <!-- 一对一关联映射:association -->
        <association property="card" column="card_id"
            select="cn.mybatis.mydemo.mapper.CardMapper.selectCardById"
            javaType="cn.mybatis.mydemo.domain.Card" />
    </resultMap>

</mapper>

在PersonMapper.xml中定义了一个<select.../>,其根据id查询Person信息,由于Person类除了简单的属性id、name、sex和age之外,还有一个关联对象card,所以返回的是一个名为personMapper的resultMap。personMapper中使用了<association .../>元素映射一对一的关联关系。其中,select属性表示会使用column属性的card_id值作为参数执行CardMapper中定义的selectCardById语句,查询对应的Card数据,查询出的数据将被封装到property表示的card对象当中。

映射接口

我们通常都是使用SqlSession对象调用insert,update,delete和select方法进行测试。实际上,Mybatis官方手册建议通过mapper接口的代理对象访问mybatis,该对象关联了SqlSession对象,开发者可以通过该对象直接调用方法操作数据库。

下面定义一个mapper接口对象,需要注意的是,mapper接口对象的类名必须和之前的XML文件中的mapper的namespace一致,而方法名和参数也必须和XML文件中的<select.../>元素的id属性和parameterType属性一致。

package cn.mybatis.mydemo.mapper;

import cn.mybatis.mydemo.domain.Person;

public interface PersonMapper
{

    /**
     * 根据id查询Person,方法名和参数必须和XML文件中的<select.../>元素的id属性和parameterType属性一致
     * */
    Person selectPersonById(Integer id);

}

测试类

public class App
{
    public static void main(String[] args) throws Exception
    {
        // 读取mybatis-config.xml文件
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

        // 初始化mybatis,创建SqlSessionFactory类的实例
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 创建Session实例
        SqlSession session = sqlSessionFactory.openSession();

        Person p = session.selectOne("cn.mybatis.mydemo.mapper.PersonMapper.selectPersonById", 1);
        System.out.println(p);
        System.out.println(p.getCard().getCode());

        // 获得mapper接口的代理对象
        PersonMapper pm = session.getMapper(PersonMapper.class);
        // 直接调用接口的方法,查询id为1的Peson数据
        Person p2 = pm.selectPersonById(1);
        // 打印Peson对象
        System.out.println(p2);
        // 打印Person对象关联的Card对象
        System.out.println(p2.getCard());

        // 提交事务
        session.commit();
        // 关闭Session
        session.close();
    }
}

运行APP类的main方法,通过SqlSession的getMapper(Class<?> type)方法获得mapper接口的代理对象PersonMapper,调用selectPersonByld方法时会执行PersonMapper.xml中<select.../>元素中定义的sql语句。

标签: none

已有 5 条评论

  1. 互联网微尘 互联网微尘

    感谢分享,同时希望你们可以好好维护这个网站,发表的文章需要进行一些勘误。以上SQL语句出错,另外:网站UI可以优化一下。加油!

    1. 好的。谢谢提醒。

  2. 互联网微尘 互联网微尘

    祝你们的网站越来越好,发表的文章质量逐步提高。作最好的mybatis中国分站。

    1. 谢谢,我们会努力的

  3. 王聪 王聪

    如果我在selectCardById接口上面添加@Cacheable注解对当前数据使用redis缓存,多对一的情况下为了让一能够更高效的查询,此时无效!我如果使用redisCache替换MyBatis默认的二级缓存是可以实现复用的的,但是涉及到两个问题,一个是序列化问题,另一个是生成的key太恶心了,毫无规则,不方便维护,请教一下站长有啥解决方案嘛

添加新评论