mybatis是先处理sql,把sql执行完后把结果表映射到resultMap或者resultType上
如下面的:
一个project对应一个user
1 | public class Project { |
1 | public class User{ |
表为:
1 | create table project( |
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
1 | <mapper namespace="com.ljy.inspector.dao.ProjectDao"> |
UserMapper.xml
1 | <mapper namespace="com.ljy.inspector.dao.UserDao"> |
association中嵌套select语句,这样的执行顺序是:先执行上层select找出resultMap中除了复杂映射的其他部分,默认开启部分自动映射,因此其他部分会被自动映射,除了有的映射不到,需要使用类型处理器,如ItemState。接着执行association中的select语句,并且将user_id作为参数传入findById,user_id就会被当做findById中的 #{id} (即使名字不一样,但就是这么做的)
这样做的话,会得到正确的结果:
1 | { |
这样做相当于把查询分了两步:
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
1 | <mapper namespace="com.ljy.inspector.dao.ProjectDao"> |
结果为:
1 | { |
这种方法是执行一次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名称(虽然不起别名的话二者相同)。只要明确这一点,很多问题就能迎刃而解。