Mybatis中对于association的理解

mybatis是先处理sql,把sql执行完后把结果表映射到resultMap或者resultType上

如下面的:

一个project对应一个user

public class Project {
    int id;
    String name;
    User user;
    ItemState state;
    Set<User> signer;
}
public class User{
    public int id;
    public String username;
    public String password;
}

表为:

create table project(
    id int primary key auto_increment,
    name varchar(50),
    user_id int,
    state enum('created', 'planned', 'started', 'finished'),
)engine=innoDB, charset=utf8;

user表:
±---------±------------±-----±----±--------±---------------+
| Field | Type | Null | Key | Default | Extra |
±---------±------------±-----±----±--------±---------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(20) | NO | MUL | NULL | |
| password | varchar(18) | NO | | NULL | |
±---------±------------±-----±----±--------±---------------+

当给定一个userid,要查询其对应的project及其对应的user。
首先如果结果集不是简单的POJO(POJO其属性不含其它bean的类型),就不能简单使用resultType,因为复杂映射映射不出来。要使用resultMap,且使用association和collection

下面称association和collection为“复杂映射”

所有的复杂映射都是根据结果表进行映射

如要使用userid去查询project及其对应的user,有以下办法:

用法一:

ProjectMapper.xml

<mapper namespace="com.ljy.inspector.dao.ProjectDao">

    <resultMap id="projectMap" type="com.ljy.inspector.entity.Project">
        <id column="id" property="id"/>
        <result column="state" property="state" typeHandler="com.ljy.inspector.type_handler.ItemStateHandler"/>
        <association column="user_id" property="user" select="com.ljy.inspector.dao.UserDao.findById">
        </association>
    </resultMap>

    <select id="findByUserId" resultMap="projectMap">
        select
        from project as p
        <where>
            p.user_id = #{userId}
        </where>
    </select>
</mapper>

UserMapper.xml

扫描二维码关注公众号,回复: 9967202 查看本文章
<mapper namespace="com.ljy.inspector.dao.UserDao">

    <resultMap id="UserMap" type="com.ljy.inspector.entity.User">
        <id column="id" property="id" jdbcType="INTEGER"/>
        <result column="username" property="username" jdbcType="VARCHAR"/>
        <result column="password" property="password" jdbcType="VARCHAR"/>
    </resultMap>

    <select id="findById" resultType="com.ljy.inspector.entity.User">
        SELECT * FROM user AS u
        <where>
            u.id = #{id}
        </where>
    </select>

</mapper>

association中嵌套select语句,这样的执行顺序是:先执行上层select找出resultMap中除了复杂映射的其他部分,默认开启部分自动映射,因此其他部分会被自动映射,除了有的映射不到,需要使用类型处理器,如ItemState。接着执行association中的select语句,并且将user_id作为参数传入findById,user_id就会被当做findById中的 #{id} (即使名字不一样,但就是这么做的)
这样做的话,会得到正确的结果:

{
            "id": 10,
            "name": "czxczc",
            "state": "CREATED",
            "user": {
                "id": 2,
                "username": "123",
                "password": "11111111"
            },
        }
      

这样做相当于把查询分了两步:
1.在project中先select查询user_id为给定值的所有元组(行),把能自动映射或者指定了类型处理器的属性先映射到Project对象上

2.再根据上述元组中的user_id属性去select查询user表中id为userid中的行,并把查询结果集映射到User对象上,并且把这个User对象赋给上面的Project对象的user属性。

整体就是两步select,根据第一步查询出的结果的某个字段去进行第二步查询。
先构建出第一步查询的结果对象obj1,然后把第二步查询的结果对象obj2赋给obj1对应的属性。

(先得到结果表1,对其进行映射,再使用其中的某字段得到结果表2,再对结果表2进行映射,将映射结果2嵌入映射结果1的属性中。整个过程产生了两张结果表,因为有两个select)

用法二:

ProjectMapper.xml

<mapper namespace="com.ljy.inspector.dao.ProjectDao">

    <resultMap id="projectMap" type="com.ljy.inspector.entity.Project">
        <id column="id" property="id"/>
        <result column="state" property="state" typeHandler="com.ljy.inspector.type_handler.ItemStateHandler"/>
        <association property="user" javaType="com.ljy.inspector.entity.User">
            <id column="u_id" property="id"/>
            <result column="username" property="username"/>
        </association>
    </resultMap>

    <sql id="base_user">
        p.*, u.id as u_id, u.username, u.password
    </sql>

    <select id="findByUserId" resultMap="projectMap">
        select
        <include refid="base_user"/>
        from project as p,user as u
        <where>
            p.user_id = #{userId}
            and
            u.id = #{userId}
        </where>
    </select>
</mapper>

结果为:

{
            "id": 10,
            "name": null,
            "state": "CREATED",
            "user": {
                "id": 2,
                "username": "123",
                "password": null
            },
        }

这种方法是执行一次select查询,一次查询两个表,然后再对这个联合结果表进行映射,这种方式完全是按照sql的思想理念去做的,只有最后才是对联合结果表进行一次映射。
这种方式的resultMap会自行关闭自动映射,原因在于结果表只有一张,如果两张表中有名称相同的字段,会产生歧义(如project的叫id,user的也叫id)。在sql查询中,这种情况不会有问题,结果表可以有两个列都叫id,但是对于映射来说列名是唯一标签,所以会产生歧义。所以这种情况下,要对有歧义的列名起别名,如上面的sql标签,将user中的id别名为u_id,那么最终产生的结果表中,只有一列叫id,即project.id,而user.id的这一列叫u_id,所以在association中列名叫做u_id。
这样得出一张结果表后,先整体映射为指定的对象类型,再把其中要映射为属性或者需要处理的列去映射成相应的属性。而把需要映射为association的列摘出来,根据提供的对象类型把它们映射成相应的对象,赋给上层的对象的属性

方法二还有一种做法是指定association的resultMap属性,两者其实是一样的。本质上都是从一张结果表中取结果列取映射。

所以对于association需要注意的点:

1.如果使用了select属性,则本质上要进行两次查询,得到两张结果表,因为是从不同的表中查询(相同也无所谓)且每次只查询一个表,所以二者互不干涉,所以不存在字段歧义的问题,自动映射会开启。
2.如果使用resultMap属性,或者association本身当做一个resultMap,则本质是对多张表的一次联合查询,只产生一张结果表。然后对这张结果表一次进行映射。可能存在字段歧义问题,自动映射会关闭(如果没有指明映射关系的字段会得到空值)。此时要把产生歧义的字段起别名,并且把所有需要映射的字段都显式写出来。

所有的mybatis的resultMap,不管其结构有多复杂,本质都是执行完sql后对结果表再进行映射(归根结底要很清晰执行完sql语句后结果表是什么样子),其中的column的名称是结果表的column名称而不是原表的colum名称(虽然不起别名的话二者相同)。只要明确这一点,很多问题就能迎刃而解。

发布了21 篇原创文章 · 获赞 19 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43752854/article/details/103306957
今日推荐