Mybatis 中对于多表查询提供了非常强大的实现方式,主要是通过resultMap的结果映射对于多表查询后的返回值进行封装,让我们来看一下官网上对于resultMap的解释:resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的长达数千行的代码。ResultMap 的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了。通过描述对象之间的关系将查询后的结果映射到我们定义的实体类中。
首先介绍一下本例中的实体类以及其映射关系,Demo中存在User类以及Account类,其关系为一个用户对应零个、一个或者多个账户,账户中为了简单单单保存用户的账户余额以及所属用户的ID。我们实现的查询的目标为:每次查询一个账户的时候同时将其所属的用户信息也展示出来。为了更好的帮助理解,我们将展示一种非mybatis方式以及两种mybatis方式的实现来实现。User类以及Accoun类t的内容如下:
import java.io.Serializable; public class Account implements Serializable{
private Integer id;
private Integer uid;
private Double money;
private User user; public User getUser() {
return user;
} public void setUser(User user) {
this.user = user;
} public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public Integer getUid() {
return uid;
} public void setUid(Integer uid) {
this.uid = uid;
} public Double getMoney() {
return money;
} public void setMoney(Double money) {
this.money = money;
} @Override
public String toString() {
return "Account{" +
"id=" + id +
", uid=" + uid +
", money=" + money +
'}';
}
}
import java.io.Serializable;
import java.util.Date; public class User implements Serializable{
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address; public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} public Date getBirthday() {
return birthday;
} public void setBirthday(Date birthday) {
this.birthday = birthday;
} public String getSex() {
return sex;
} public void setSex(String sex) {
this.sex = sex;
} public String getAddress() {
return address;
} public void setAddress(String address) {
this.address = address;
} @Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
数据库的建表语句如下:
DROP TABLE IF EXISTS user; CREATE TABLE user (
id INT(11) NOT NULL auto_increment,
username VARCHAR(32) NOT NULL COMMENT '用户名称',
birthday datetime default NULL COMMENT '生日',
sex char(1) default NULL COMMENT '性别',
address varchar(256) default NULL COMMENT '地址',
PRIMARY KEY (id)
)ENGINE=InnoDB default CHARSET=utf8
INSERT INTO `user` VALUES ('', '老王', '2018-02-27 17:47:08', '男', '石家庄');
INSERT INTO `user` VALUES ('', '老李', '2018-02-27 17:47:08', '男', '石家庄');
INSERT INTO `user` VALUES ('', '老郭', '2018-02-27 17:47:08', '男', '石家庄'); DROP TABLE IF EXISTS account;
CREATE TABLE account(
ID int(11) NOT NULL COMMENT '编号',
UID INT(11) DEFAULT NULL COMMENT '用户编号',
MONEY DOUBLE DEFAULT NULL COMMENT '金额',
PRIMARY KEY (ID),
KEY FK_Reference_8 (UID),
CONSTRAINT FK_Reference_8 FOREIGN KEY (UID) REFERENCES user (id)
)ENGINE=INNODB DEFAULT CHARSET=utf8 INSERT INTO accountc (ID,UID,MONEY) VALUES (1,46,1000),(2,45,1000),(3,46,2000);
搭建项目的过程就不展示了,主要的核心实体类和对应的数据库表如上,接下来我们展示我们所要展示的三种方式实现一对一的联表查询。
1.非mybatis的高级结果映射方式实现联表查询。
这种方式的原理为通过创建一个新的类AccountUser类继承Account类并在AccountUser类中添加我们想要查询的User的信息,并且在账户查询的Dao.xml文件中配置相应的sql语句即可实现。假如我们查询Account的信息的时候同时想要查询用户的名称以及地址,那就在AccountUser的类中声明用户的名称以及地址。这种实现方式只是作为一种拓展的实现方式,在实际使用过程中并不推荐使用。
(1)声明AccountUser类
public class AccountUser extends Account {
private String username;
private String address; public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} public String getAddress() {
return address;
} public void setAddress(String address) {
this.address = address;
} @Override
public String toString() {
return super.toString() + " "+"AccountUser{" +
"username='" + username + '\'' +
", address='" + address + '\'' +
'}';
}
}
需要注意的是该类继承了Account类,声明了我们需要的User类中的用户名称以及地址,对AccountUser类的toString()方法进行了改造,添加了super.toString(),方便我们打印的时候可以打印出从父类中继承的属性的属性值。
(2)在AccountDao类中声明查询账户信息的方法
/**
* 查找所有账户同时包含用户的姓名和地址
* @return
*/
List<AccountUser> findAllAccountUser();
(3)在AccountDao.xml中配置findAllAccountUser方法的实现
<select id="findAllAccountUser" resultType="com.example.domain.AccountUser">
SELECT a.*,u.username,u.address FROM USER u,account a WHERE a.UID= u.id;
</select>
(4)测试该方法
@Test
public void findAllAccounUsertTest(){
List<AccountUser> accountList = accountDao.findAllAccountUser();
for (AccountUser account:accountList){
System.out.println(account);
}
}
2.通过Mybatis中的高级结果映射的resultMap的关联属性(association)来实现多表的一对一查询。
关联属性主要用来处理“有一个”类型的关系,关联的关键之处是我们需要告诉 MyBatis 如何加载关联。在MyBatis 中有两种不同的方式加载关联:一是嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。二是嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。通过这两种不同的方式衍生出两种不同的方式去实现多表的一对一查询。
1.关联的嵌套SELECT查询
(1)因为我们要实现的是在查询账户的时候期望可以得到账户所属用户的某些信息,所以我们需要在Account类中声明User对象,用来将查询到的结果进行封装。如下
private User user; public User getUser() {
return user;
} public void setUser(User user) {
this.user = user;
}
(2)AccountDao类中添加查询的方法的声明。
/**
* 查找所有账户同时包含用户的所有信息
* @return
*/
List<Account> findAll();
(3)在AccountDao.xml中配置findAll方法的的结果映射。首先声明结果映射关系resultMap,resultMap的id为该结果映射的唯一标识,type为结果类的完全限定名,resultMap中的属性说明:id 和 result 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。这两者之间的唯一不同是,id 元素表示的结果将是对象的标识属性,这会在比较对象实例时用到。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。id和result中的属性说明:property
映射到列结果的字段或属性,其实就是实体类中属性的名称。column是指数据库中的列名,对应实体类的属性。在下面的<id property=”id” column=”aid”/>中的column属性的值aid没有完全匹配上数据中的id,是因为在查询语句中对account中的id字段设置了别名aid。association的属性的 property为user对应实体类中声明的user对象,其类型使用javaType属性指定为User类,column为数据表的列名,并作为参数传递给此 select 语句。select属性用于加载复杂类型属性的映射语句的 ID,它会从 column 属性中指定的列检索数据。
<resultMap id="accountUserMap" type="com.example.domain.Account">
<id property="id" column="aid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!--关联的嵌套的select查询-->
<association property="user" javaType="com.example.domain.User" column="uid" select="selectUser"/>
</resultMap>
(4)在AccountDao.xml中配置结果映射中的<association property=”user” javaType=”com.example.domain.User” column=”uid” select=”selectUser”/>的select=”selectUser”的实现以及findAll方法的实现,
<select id="selectUser" resultType="user">
SELECT * FROM USER WHERE ID = #{id};
</select>
<select id="findAll" resultMap="accountUserMap">
SELECT u.*,a.id AS aid,a.uid,a.money FROM USER u,account a WHERE a.UID= u.id;
</select>
这里我们有两个 select 查询语句:一个用来加载账户信息(Account),另外一个用来加载用户信息(User),而且accountUserMap的结果映射描述了应该使用 selectUser 语句加载它的 user属性,其它的列名和属性名相匹配的属性将会被自动加载。
(5)查询测试
@Test
public void findAllTest(){
List<User> userList = userDao.findAll();
for (User user: userList){
System.out.println(user);
}
}
2.关联的嵌套结果映射实现1。
(1)(2)步骤是上一方法是相同的。
(3)主要是修改了上一种方式中第三步中的resultMap中的association关联属性,将其替换为:<association property=”user” javaType=”com.example.domain.User” column=”uid” resultMap=”userMap”/>,在association 中添加了resultMap=”userMap”属性,userMap为结果映射的 ID,可以将此关联的嵌套结果集映射到一个合适的对象中,也就是将关联属性user的结果映射到映射ID为userMap的resultMap中。userMap的声明如下:
<resultMap id="userMap" type="com.example.domain.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday" jdbcType="DATE"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
</resultMap>
(4)AccountDao.xml的findAll方法的映射则只需要findAll方法,不再需要上一个方式中的selectUser映射的方法
<select id="findAll" resultMap="accountUserMap">
SELECT u.*,a.id AS aid,a.uid,a.money FROM USER u,account a WHERE a.UID= u.id;
</select>
(5)(6)查询代码以及测试结果不再贴出
3.关联的嵌套结果映射实现2。
第二种实现方式中使用了外部的结果映射元素来映射关联。这使得 User的结果映射可以被重用。 然而,如果我们不需要重用它(在上个例子中他是userMap),或者你更喜欢将你所有的结果映射放在一个具有描述性的结果映射元素中。 你可以直接将结果映射作为子元素嵌套在内。
(1)(2)步骤是上一方法是相同的。
(3)仍然是修改了上一种方式中第三步中的resultMap结果映射中的association关联属性,将其替换如下:
<!--关联的嵌套的结果映射2-->
<association property="user" javaType="com.example.domain.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday" jdbcType="DATE"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
</association>
这样实现与第二种实现大同小异,只是将关联对象的属性配置直接在association中进行了配置。
(4)AccountDao.xml的findAll方法的映射的findAll方法
<select id="findAll" resultMap="accountUserMap">
SELECT u.*,a.id AS aid,a.uid,a.money FROM USER u,account a WHERE a.UID= u.id;
</select>
(5)(6)查询代码以及测试结果不再贴出
总结:通过上述例子可以初步窥探了Mybatis中多表联查(一对一)的使用方式,主要是通过resultMap的高级结果映射来实现的,在本例中最关键的属性是resultMap的关联属性association,association也是我们告诉Mybatis对象之间的关系的桥梁,同时也介绍了resultMap的属性的说明,通过解释其属性再加上Demo可以更好的理解结果映射的含义以及使用,这只是最简单的一种使用方式,以后会详细介绍一对多、多对多、多对一等复杂情况在Mybatis中的如何查询映射。
参考网址:mybatis中文官网 http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html