在java项目中,某些实体类之间肯定有关键关系,比如一对一,一对多等。在hibernate 中用one to one和one to many,而mybatis 中就用association和collection。
【association】: 一对一关联(has one)
【collection】:一对多关联(has many)
注意,只有在做select查询时才会用到这两个标签,都有三种用法,且用法类似。
先看如下代码(省略set、get方法):
public class User { private Integer userId; private String userName; private Integer age; private Card card;//一个人一张身份证,1对1 }
public class Card { private Integer cardId; private String cardNum;//身份证号 private String address;//地址 }
public interface UserDao { /** * 通过userId查询user信息 * @param userId * @return */ User queryById(int userId); }
<select id="queryById" parameterType="int" resultMap="userMap"> SELECT u.user_name,u.age,c.card_id,c.card_num,c.address FROM tb_user u,tb_card c WHERE u.card_id=c.card_id AND u.user_id=#{userId} </select>
以上是实体类、dao层的设计以及在UserDao.xml中queryById方法的sql语句的编写,因为不论用association的哪种方式,sql语句都是一样的写,不同的只是userMap的写法,所以这里先给出这段代码。User询Card是一对一关系,在数据库中,tb_user表通过外键card_id关联tb_card表。下面分别用association的三种用法来实现queryById方法。
这种方法需要再定义CardDao.java,如下:
public interface CardDao { Card queryCardById(int cardId); }
在CardDao.xml中实现该方法:
<select id="queryCardById" parameterType="int" resultType="Card"> SELECT * FROM tb_card WHERE card_id=#{cardId} </select>
然后再看UserDao.xml中是如何引用这个方法的:
<resultMap type="User" id="userMap"> <result property="userName" column="user_name"/> <result property="age" column="age"/> <association property="card" column="card_id" select="com.zhu.ssm.dao. CardDao.queryCardById"> </association> </resultMap>
在这里直接通过select引用CardDao的queryById方法。个人感觉这种方法比较麻烦,因为还要在CardDao里定义queryCardById方法并且实现再引用才有用,不过这种方法思路清晰,易于理解。
<resultMap type="Card" id="cardMap"> <id property="cardId" column="card_id"/> <result property="cardNum" column="card_num"/> <result property="address" column="address"/> </resultMap>
<resultMap type="User" id="userMap"> <result property="userName" column="user_name"/> <result property="age" column="age"/> <association property="card" resultMap="cardMap"> </association> </resultMap>
第二种方法就是在UserDao.xml中先定义一个Card的resultMap,然后在User的resultMap的association标签中通过resultMap="cardMap"引用。这种方法相比于第一种方法较为简单。
<resultMap type="User" id="userMap"> <result property="userName" column="user_name"/> <result property="age" column="age"/> <association property="card" column="card_id" javaType="Card"> <id property="cardId" column="card_id"/> <result property="cardNum" column="card_num"/> <result property="address" column="address"/> </association> </resultMap>
这种方法就把Card的resultMap定义在了association 标签里面,通过javaType来指定是哪个类的resultMap,个人认为这种方法最简单,缺点就是cardMap不能复用。具体用哪种方法,视情况而定。
一个土豪有多个手机,看如下代码:
User实体类
public class User{ private Integer userId; private String userName; private Integer age; private List<MobilePhone> mobilePhone;//土豪,多个手机,1对多 }
手机类
public class MobilePhone { private Integer mobilePhoneId; private String brand;//品牌 private double price;//价格 private User user;//主人 }
dao层
public interface UserDao { /** * 通过userId查询user信息 * @param userId * @return */ User queryById(int userId); }
UserDao.xml中的select查询语句
<select id="queryById" parameterType="int" resultMap="userMap"> SELECT u.user_name,u.age, m.brand,m.price FROM tb_user u,tb_mobile_phone m WHERE m.user_id=u.user_id AND u.user_id=#{userId} </select>
数据库中,tb_mobile_phone中user_id作为外键。那么下面来看resultMap如何定义:
先定义 MobilePhoneDao.java
public interface MobilePhoneDao { List<MobilePhone> queryMbByUserId(int userId); }
然后实现该方法 MobilePhoneDao.xml
<resultMap type="MobilePhone" id="mobilePhoneMap"> <id property="mobilePhoneId" column="user_id"/> <result property="brand" column="brand"/> <result property="price" column="price"/> <association property="user" column="user_id" select= "com.zhu.ssm.dao.UserDao.queryById"> </association> </resultMap>
<select id="queryMbByUserId" parameterType="int" resultMap="mobilePhoneMap"> SELECT brand,price FROM tb_mobile_phone WHERE user_id=#{userId} </select>
做好以上准备工作,那就可以在UserDao.xml中引用了
<resultMap type="User" id="userMap"> <id property="userId" column="user_id"/> <result property="userName" column="user_name"/> <result property="age" column="age"/> <collection property="mobilePhone" column="user_id" select="com.zhu.ssm.dao .MobilePhoneDao.queryMbByUserId"> </collection> </resultMap>
这种方法和association的第一种用法几乎是一样的不同之处就是mobilePhMap中用到了association ,queryMbByUserId中要使用mobilePhoneMap,而不能直接使用resultType。
<resultMap type="MobilePhone" id="mobilephoneMap"> <id column="mobile_phone_id" property="mobilePhoneId"/> <result column="brand" property="brand" /> <result column="price" property="price" /></resultMap> <resultMap type="User" id="userMap"> <result property="userName" column="user_name"/> <result property="age" column="age"/> <collection property="mobilePhone" resultMap="mobilephoneMap" > </collection> </resultMap>
定义好这两个resultMap,再引用UserMap就行了。
<resultMap type="User" id="userMap"> <result property="userName" column="user_name"/> <result property="age" column="age"/> <collection property="mobilePhone" column="user_id" ofType="MobilePhone"> <id column="mobile_phone_id" property="mobilePhoneId" /> <result column="brand" property="brand" /> <result column="price" property="price" /> </collection> </resultMap>
这种方法需要注意,一定要有ofType,collection 装的元素类型是啥ofType的值就是啥,这个一定不能少。
注意:
所有resultMap中的type、select 标签中的resultType以及association中的javaType,collection中的ofType,这里只写了类名,是因为在mybatis-config.xml中配置了typeAliases,否则就要写该类的全类名。配置如下:
<typeAliases> <packagename="com.zhu.smm.entity"/> </typeAliases>
或者在 文件里配置:
mybatis.type-aliases-package=com.example.demo
如果我们希望在关联关系中只返回某个模型的部分字段,譬如用户在密码字段不希望返回,或者模型有上百个字段,不希望全部返回所有的字段给前端,那么怎么办呢?
这个时候,飘易建议大家可以使用 Map 类型,直接返回给前端。
新闻表 News (其中字段cat_id关联到新闻分类表的id)
新闻分类表 NewsCategories
News模型中添加:
/* 自定义属性 */ private NewsCategories cat; public NewsCategories getCat(){ return cat; } public void setCat(NewsCategories cat){ this.cat = cat; } /* 自定义属性 - 部分字段 */ private Map catMap; public Map getCatMap(){ return catMap; } public void setCatMap(Map catMap){ this.catMap = catMap; }
NewsMapper.xml中添加:
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.example.demo.model.News"> <result column="content" jdbcType="LONGVARCHAR" property="content" /> <!-- 一对一关联关系 --> <association property="cat" column="cat_id" select="cat"/> <association property="catMap" column="cat_id" select="catMap"/> </resultMap> <select id="cat" parameterType="java.lang.Integer" resultType="com.example.demo.model.NewsCategories" > select * from news_categories where id = #{id,jdbcType=INTEGER} </select> <select id="catMap" parameterType="java.lang.Integer" resultType="java.util.Map" > select id,title,keywords from news_categories where id = #{id,jdbcType=INTEGER} </select>
NewsCategories模型中添加:
// 自定义 private List<News> newsList; public List<News> getNewsList(){ return newsList; } public void setNewsList(List<News> newsList){ this.newsList = newsList; } // 自定义 - Map 部分字段 private List<Map> newsMapList; public List<Map> getNewsMapList(){ return newsMapList; } public void setNewsMapList(List<Map> newsMapList){ this.newsMapList = newsMapList; }
NewsCategoriesMapper.xml 中添加:
<resultMap id="BaseResultMap" type="com.example.demo.model.NewsCategories"> ... <!--一对多关联关系--> <collection property="newsList" column="id" select="queryNewsList"/> <collection property="newsMapList" column="id" select="queryNewsMapList"/> </resultMap> <select id="queryNewsList" parameterType="int" resultType="com.example.demo.model.News"> SELECT * FROM news WHERE cat_id=#{id} </select> <select id="queryNewsMapList" parameterType="int" resultType="java.util.Map"> SELECT id,title,keywords FROM news WHERE cat_id=#{id} </select>
这样配置后,我们就可以通过 java.util.Map 和 select 语句筛选出指定的一部分字段啦。
总结:
1、association表示的是has one的关系,一对一时使用。user has one card,所以在user的resultMap中接收card时应该用association;
2、collection表示的是has many的关系,一对多时使用。user has many mobilePhone,所以在user的resultMap中接收mobilePhone时应该用collection 。
3、都有三种用法,且非常类似,resultMap要复用建议第二种方法,不需要复用建议第三种方法。
4、特别注意表中主键字段要有所区分,不能都写成id,要写成user_id、card_id,反正要有所区分,不然查询的时候会查不到完整的数据。
【参考】
1、mybatis的association以及collection的用法