赞
踩
上一章中实现的MyBatis对象映射较为简单,对象中的属性和数据库中的表字段是一一对应的(无论数量和名称都完全一样),如果对象中的属性名和表中的字段名不一致怎么办?又或者Java对象中存在复杂类型属性(即类似Hibernate中多对一、一对多关系对象时)怎么完成数据库表和对象的映射?本章来解决这样的问题。
1 MyBatis的数据映射规则
MyBatis可以自动把查询到的表数据填充到对象中,这一过程是通过Java反射技术实现的,默认情况下,MyBatis会按照查询出来的结果集字段名去填充对象的属性,因此数据库表中的字段名应该与对象的属性名相符合。但这个要求并不总是能保证。
1.1 使用查询别名映射对象属性
(1)对象属性名和表字段名不一致时
如果仅仅是数据库表中的字段名和Java对象的属性名不一致时,可以在select语句中指定查询字段的别名,别名与对象的属性名相同,MyBatis就可以对属性正确赋值了。
例如有以下Java对象和数据库表Street:
对象中的外键属性名为“districtId”而数据表中的外键字段名为“district_id”这时,可以编写以下SQL完成映射:
- <mapper namespace="mycinema.dao.StreetDao">
- <select id="getAll" parameterType="int" resultType="demo.entity.Street">
- select s.*, s.district_id as districtId from Street s
- </select>
- </mapper>
(2)需要跨表查询并填充对象时
比如我们希望在查询Movie的对象的同时,获取到Movie对应的外键表Category的Name值,因此我们在Movie对象中添加CategoryName属性。为了获取该值,我们可以使用表连接Join语句,并把Category的Name字段在查询中起别名为CategoryName以符合对象填充要求。
Movie表中只有CategoryId外键
Category表中有Name字段
对象中需要跨表获取数据
针对上述需求,我们可以把MyBatis中的Movie查询按如下方式实现。
- <mapper namespace="mycinema.dao.MovieMapper">
- <select id="fetchById" parameterType="int" resultType="Movie">
- select m.*,
- c.Name as CategoryName
- from Movie m left join Category c on m.CategoryId=c.Id where m.Id=#{id}
- </select>
- </mapper>
1.2 使用hashmap作为查询结果的返回类型
如果从多个数据表中查询一些字段,无法填充到某一个实体中,我们还可以把resultType(返回结果类型)声明为hashmap,这时,查询到的每一行数据都会封装到一个HashMap<String, Object>集合中,键就是字段名,值就是字段值。
- <select id="getMoviesMap" resultType="hashmap">
- select m.title, c.name as CategoryName from Movie m
- inner join Category c on m.CategoryId=c.Id
- </select>
上述查询的执行代码如下:
- public static void main(String[] args) throws IOException {
- SqlSession sess = MyBatisUtil.openSession();
- MovieMapper dao = sess.getMapper(MovieMapper.class);
- for(Map<String,Object> map : dao.getMoviesMap()){
- for(String key : map.keySet()){
- System.out.print(key+":"+map.get(key)+"\t");
- }
- System.out.println();
- }
- sess.close();
- }
执行结果如下所示:
2 SQL的重用
映射配置文件中还有一个<sql>元素,用于声明可以被重用的sql语句块。例如上述Movie信息的连接查询语句,可能需要在多个<select>中被重用,就可以通过<sql>元素声明,然后使用<include>元素引用。
- <sql id="movieJoinCategory">
- select m.*, c.name CategoryName
- from Movie m inner join Category c on m.categoryid=c.id
- </sql>
- <select id="fetchById" parameterType="int" resultMap="movieResultMap">
- <include refid="movieJoinCategory"/> where m.id=#{id}
- </select>
在<sql>元素中用id声明该SQL语句块的名称,然后在<include>元素中通过refid属性来应用它,这样就可以大大提高SQL语句的可维护性。
3在SQL语句中传入多个参数
实际应用中,SQL语句所需的参数往往不止一个。这时,我们可以把<select>元素的parameterType设置为“hashmap”,即通过键值对集合(HashMap)的方式为SQL语句传入多个命名参数。
例如下面这个分页查询,其中包含三个命名参数categoryId(分类ID)、skips(跳过的行数)和takes(取出的最大行数)。
- <select id="getMoviesPaging" parameterType="hashmap" resultMap="movieResultMap">
- <include refid="movieJoinCategory"/>
- where m.CategoryId=#{categoryId}
- limit #{skips},#{takes}
- </select>
(1)使用命名查询方式时的参数传递
在执行的时侯,通过定义一个Map集合作为SQL参数,即可完成参数传递。
- public List<Movie> getMoviesPaging(int cid, int pageNum, int pageSize) {
- SqlSession session = MyBatisUtil.openSessionn();
- Map<String,Object> parameters = new HashMap<String,Object>();
- parameters.put("categoryId", cid);
- parameters.put("skips", (pageNum-1)*pageSize);
- parameters.put("takes", pageSize);
- try {
- return session.selectList(
- "mycinema.dao.MovieDao.getMoviesPaging", parameters);
- } finally {
- session.close();
- }
- }
(2)使用Mapper方式时的参数传递
使用Mapper方式时,只需声明接口就可以去调用<select>了,因此没有办法通过代码传入HashMap参数,这是,需要通过MyBatis提供的@Param注解在接口参数中声明SQL参数的名称。
- public interface MovieMapper {
- public List<Movie> getMoviesPaging(
- @Param("categoryId") int cid,
- @Param("skips") int skips,
- @Param("takes") int takes
- );
- …
- }
4 使用resultMap描述复杂映射
如果对象和表之间有更复杂的差异,比如Java对象中内嵌其它对象属性(多对一或一对多),就需要在MyBatis的实体配置文件中使用resultMap元素描述映射细节。
例如在MyCinema中,电影(Movie)对象中如果内嵌一个电影分类(Category)对象作为属性描述数据库外键,结构如下图所示:
实体对象Movie中包含另一个实体对象Category
具体代码参考如下:
- public class Movie {
- private int id;
- private String title;
- private String movieCode;
- private Category category;
- private String director;
- private Date dateReleased;
- ……
- }
- public class Category {
- private int id;
- private String name;
- ……
- }
对于上述情况,若仅仅配置返回resultType为Movie类型的查询,MyBatis就无法完成外键属性Category 的数据填充了,因此Category属性的值为null。
- <?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="mycinema.dao.MovieDao">
- <select id="fetchById" parameterType="int" resultType="Movie">
- select * from Movie where id=#{id}
- </select>
- </mapper>
使用resultType填充实体对象
Category属性为null
4.1 resultMap的使用
如果希望一口气填充对象及其子对象,可以使用<select>元素中resultMap属性替代resultType属性,配置对象的映射。<select>中resultMap属性指定的是一个名为<resultMap>的元素定义,<resultMap>是MyBatis中非常重要的元素,它完成了类似JDBC中从ResultSet往Java对象填充数据的过程。通过配置resultMap,可以实现任意复杂的Java对象的数据映射问题。
4.1.1 外键对象映射
(1)通过join关联
下面的示例中:select语句使用了join把外键表相关数据一并查询了出来;通过resultMap元素,定义了查询结果字段与Java对象之间的映射填充关系。注意的是,resultMap中的<association>子元素,声明了外键对象Category的填充细节。
- <?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="mycinema.dao.MovieMapper">
-
- <!-- 定义填充数据用的resultMap -->
- <resultMap type="Movie" id="movieResultMap">
- <id column="movie_id" property="id"/>
- <result column="movie_title" property="title"/>
- <result column="movie_director" property="director"/>
- <result column="movie_movieCode" property="movieCode"/>
- <result column="movie_dateReleased" property="dateReleased"/>
- <association property="category" javaType="Category">
- <id column="category_id" property="id"/>
- <result column="category_name" property="name" />
- </association>
- </resultMap>
- <!-- 定义查询fetchById 使用resultMap填充返回的数据对象 -->
- <select id="fetchById" parameterType="int" resultMap="movieResultMap">
- select m.id as movie_id,
- m.title as movie_title,
- m.moviecode as movie_moviecode,
- m.director as movie_director,
- m.dateReleased as movie_dateReleased,
- c.id as category_id,
- c.name as category_name
- from Movie m inner join Category c on m.categoryid=c.id
- where m.id=#{id}
- </select>
- </mapper>
以下是resultMap配置的重要子元素的解析:
子元素 | 作用 |
id | 一个 ID 结果;标记结果作为 ID 可以帮助提高整体效能。 |
result | 注入到字段或 JavaBean 普通属性的普通结果 |
association | 一个复杂的类型关联;许多结果将包成这种类型嵌入结果映射 |
collection | 复杂类型的集嵌入结果映射 |
上述示例中,association元素实现了Movie和Category对象的多对一关系。
4.1.2 外键集合(一对多)映射
(1)通过join关联
与上述类似,如果现在的情况是Category对象中包含一个Movie对象的集合(如下代码所示),就需要使用<resultMap>中的<collection>子元素来描述集合属性映射。
Category对象中包含Movie对象的集合
- public class Category {
- ……
- public List<Movie> movies;
- public List<Movie> getMovies() { return movies; }
- public void setMovies(List<Movie> movies) { this.movies = movies; }
- }
在<collection>元素中,property是对象中集合属性的属性名,ofType是集合元素类型(也就是一对多中多一方对象的类型)。
- <?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="mycinema.dao.CategoryDao">
-
- <resultMap type="Category" id="categoryResultMap">
- <id property="id" column="category_id" />
- <result property="name" column="category_name"/>
- <collection property="movies" ofType="Movie">
- <id column="movie_id" property="id"/>
- <result column="movie_title" property="title"/>
- <result column="movie_director" property="director"/>
- <result column="movie_movieCode" property="movieCode"/>
- <result column="movie_dateReleased" property="dateReleased"/>
- <association property="category" javaType="Category">
- <id column="category_id" property="id"/>
- <result column="category_name" property="name" />
- </association>
- </collection>
- </resultMap>
-
- <select id="fetchById" parameterType="int" resultMap="categoryResultMap">
- select c.id as category_id,
- c.name as category_name,
- m.id as movie_id,
- m.title as movie_title,
- m.moviecode as movie_moviecode,
- m.director as movie_director,
- m.dateReleased as movie_dateReleased
- from Category c inner join Movie m on c.id=m.categoryid
- where c.id=#{id}
- </select> ……
- </mapper>
4.2 另一种外键映射方式:通过二次查询实现外键加载
(1)外键对象的二次查询映射
除了通过join的方式关联外键对象,还可通过二次查询的方式关联。也就是说,把主对象和外键对象的查询,分成两个独立查询来执行,通过主对象,找到外键ID,再根据外键ID查询外键对象,其具体配置如下所示。
- <?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="mycinema.dao.MovieDao">
-
- <resultMap type="Movie" id="movieResultMap">
- <association property="category" javaType="Category"
- column="CategoryId" select="mycinema.dao.CategoryDao.fetchById" />
- </resultMap>
-
- <select id="fetchById" parameterType="int" resultMap="movieResultMap">
- select * from Movie where id=#{id}
- </select>
- </mapper>
注意上述的配置,对于Movie的查询,只是单表查询,而resultMap的association元素,多提供了column(外键字段名)和select(使用外键值做关联二次查询)两个属性。其中,select的值“mycinema.dao.CategoryDao.fetchById”指的是CategoryMapper.xml配置文件中,根据id查询Category对象的select元素,如下所示:
- <mapper namespace="mycinema.dao.CategoryDao">
- <select id="fetchById" parameterType="int" resultType="Category">
- select * from Category where id=#{id}
- </select>
- ……
- </mapper>
上述这种方式,看起来配置方便一些,但是会造成1+N次查询的问题,实际使用应慎重考虑。下图是该查询的执行日志(log4j)输出的执行过程,从中可以看出,一个Movie对象的查询,使用了两条SQL语句。
(2)外键集合的二次查询映射
外键集合映射同样可以使用二次加载的方式。
- <resultMap type="Category" id="categoryResultMap">
- <id property="id" column="category_id" />
- <result property="name" column="category_name"/>
- <collection property="movies" ofType="Movie"
- column="id" select="mycinema.dao.MovieDao.getMoviesByCate" />
- </resultMap>
-
- <select id="fetchById" parameterType="int" resultMap="categoryResultMap">
- select * from Category where id=#{id}
- </select>
在上述的<collection>元素中,column属性是一对多关系中一方被外键引用的字段名(通常是主键字段名),select属性则是根据外键获取多方集合的查询名称,在上述列子中,这个查询应预先配置在MovieMapper.xml中,例如:
- <select id="getMoviesByCate" parameterType="int" resultType="Movie">
- select * from Movie where CategoryId=#{categoryId}
- </select>
使用上述这种方式获取外键集合,同样会造成N+1次查询的问题,该查询的执行日志(log4j)如下:
这种做法查询语句简单了,但可能换来了性能损耗。实践中往往难以两全其美,需要根据情况选择不同的方案。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。