专注收集记录技术开发学习笔记、技术难点、解决方案
网站信息搜索 >> 请输入关键词:
您当前的位置: 首页 > XML/SOAP

mybatis学习小结:对象关系映射的xml配置实现

发布时间:2010-05-20 14:01:29 文章来源:www.iduyao.cn 采编人员:星星草
mybatis学习总结:对象关系映射的xml配置实现

简介

  在之前的文章里已经讨论过mybatis的基本配置和应用,但是在实际应用中,我们需要支持更加复杂的操作,比如对多个表之间数据的连接访问等。这里就牵涉到数据关系建模里的各种关系映射。比如一对一映射,一对多映射等。这里对这几种情况的实现做一个讨论。

 

数据库表结构定义

  在讨论具体的实现代码之前,我们先定义一系列的数据库表。它们有的是一对一的关系,有的是一对多的关系。这些表格的详细定义如下:

 

CREATE TABLE ADDRESSES 
(
  ADDR_ID INT(11) NOT NULL AUTO_INCREMENT,
  STREET VARCHAR(50) NOT NULL,
  CITY VARCHAR(50) NOT NULL,
  STATE VARCHAR(50) NOT NULL,
  ZIP VARCHAR(10) DEFAULT NULL,
  COUNTRY VARCHAR(50) NOT NULL,
  PRIMARY KEY (ADDR_ID)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=LATIN1;

CREATE TABLE STUDENTS 
(
  STUD_ID INT(11) NOT NULL AUTO_INCREMENT,
  NAME VARCHAR(50) NOT NULL,
  EMAIL VARCHAR(50) NOT NULL,
  PHONE VARCHAR(15) DEFAULT NULL,  
  DOB DATE DEFAULT NULL,
  GENDER VARCHAR(6) DEFAULT NULL, 
  BIO LONGTEXT DEFAULT NULL,
  PIC BLOB DEFAULT NULL,
  ADDR_ID INT(11) DEFAULT NULL,  
  PRIMARY KEY (STUD_ID),
  UNIQUE KEY UK_EMAIL (EMAIL),
  CONSTRAINT FK_STUDENTS_ADDR FOREIGN KEY (ADDR_ID) REFERENCES ADDRESSES (ADDR_ID)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=LATIN1;

CREATE TABLE TUTORS 
(
  TUTOR_ID INT(11) NOT NULL AUTO_INCREMENT,
  NAME VARCHAR(50) NOT NULL,
  EMAIL VARCHAR(50) NOT NULL,
  PHONE VARCHAR(15) DEFAULT NULL,  
  DOB DATE DEFAULT NULL,
  GENDER VARCHAR(6) DEFAULT NULL,
  BIO LONGTEXT DEFAULT NULL,
  PIC BLOB DEFAULT NULL,
  ADDR_ID INT(11) DEFAULT NULL,
  PRIMARY KEY (TUTOR_ID),
  UNIQUE KEY UK_EMAIL (EMAIL),
  CONSTRAINT FK_TUTORS_ADDR FOREIGN KEY (ADDR_ID) REFERENCES ADDRESSES (ADDR_ID)  
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=LATIN1;


CREATE TABLE COURSES 
(
  COURSE_ID INT(11) NOT NULL AUTO_INCREMENT,
  NAME VARCHAR(100) NOT NULL,
  DESCRIPTION VARCHAR(512) DEFAULT NULL,
  START_DATE DATE DEFAULT NULL,
  END_DATE DATE DEFAULT NULL,
  TUTOR_ID INT(11) NOT NULL,
  PRIMARY KEY (COURSE_ID),
  CONSTRAINT FK_COURSE_TUTOR FOREIGN KEY (TUTOR_ID) REFERENCES TUTORS (TUTOR_ID)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=LATIN1;


CREATE TABLE COURSE_ENROLLMENT
(
  COURSE_ID INT(11) NOT NULL,
  STUD_ID INT(11) NOT NULL,
  PRIMARY KEY (COURSE_ID,STUD_ID),
  CONSTRAINT FK_ENROLLMENT_STUD FOREIGN KEY (STUD_ID) REFERENCES STUDENTS (STUD_ID),
  CONSTRAINT FK_ENROLLMENT_COURSE FOREIGN KEY (COURSE_ID) REFERENCES COURSES (COURSE_ID)
) ENGINE=INNODB DEFAULT CHARSET=LATIN1;

  关于表的详细定义和数据部分可以参考附件里的内容。假定所有表格和数据都构建好之后,我们该怎么去访问它们呢?下面就针对常用的两种映射方式进行讨论。

 

一对一映射

  在前面数据库的定义中,我们看到有表格students, addresses。假设我们有定义两个实体对象Student和Address。它们的定义如下:

 

public class Student implements Serializable {
	private static final long serialVersionUID = 1L;
	private Integer studId;
	private String name;
	private String email;
	private PhoneNumber phone;
	private Address address;
        // get, set methods omitted
	
	@Override
	public String toString() {
		return "Student [studId=" + studId + ", name=" + name + ", email=" + email
				+ ", phone=" + (phone==null?null:phone.getAsString()) + ", address=" + address + "]";
	}
}

  

public class Address implements Serializable {
	
	private static final long serialVersionUID = 1L;
	
	private Integer addrId;
	private String street;
	private String city;
	private String state;
	private String zip;
	private String country;
        // get set methods omitted...	
	@Override
	public String toString() {
		return "Address [addrId=" + addrId + ", street=" + street + ", city=" + city
				+ ", state=" + state + ", zip=" + zip + ", country=" + country
				+ "]";
	}
}

   很显然,从代码里可以看到,Student和Address类是一对一的关系。从实现的角度来说,我们需要在对应的maper xml文件里定义它们的映射关系。常用的几种方式如下:

 

继承ResultMap

  这种方式就是定义一个继承自Student类型的ResultMap,然后将对应需要包含的address信息给映射进去。原有Student的映射定义如下:

<resultMap type="Student" id="StudentResult">
	<id 	property="studId" column="stud_id"/>
	<result property="name" column="name" />
	<result property="email" column="email"/>
	<result property="phone" column="phone"/>
</resultMap>

 

  继承Student的定义如下:

<resultMap type="Student" id="StudentWithAddressExtResult" extends="StudentResult">
	<result property="address.addrId" column="addr_id"/>
	<result property="address.street" column="street"/>
	<result property="address.city" column="city"/>
	<result property="address.state" column="state"/>
	<result property="address.zip" column="zip"/>
	<result property="address.country" column="country"/>
</resultMap>

   这个定义里相当于把address的对应属性和表里的字段映射给单独定义在一个地方映射起来。如果我们在后续定义如下的查找语句:

<select id="selectStudentWithAddress" parameterType="int" resultMap="StudentWithAddressExtResult">
	select stud_id, name, email,phone, a.addr_id, street, city, state, zip, country
  	FROM STUDENTS s left outer join ADDRESSES a on s.addr_id=a.addr_id
	where stud_id=#{studId}
</select>

 

对应的mapper接口定义如下:

public interface StudentMapper {
	Student selectStudentWithAddress(int id);
}

 

我们也在对应的StudentService里定义相关的方法如下:

public Student findStudentWithAddressById(int id) {
	SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
	try {
		StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
		return studentMapper.selectStudentWithAddress(id);
	} finally {
		sqlSession.close();
	}
}

  此时,如果我们执行如下的代码:

public static void main( String[] args ) {
    	StudentService studService = new StudentService();
    	Student student1 = studService.findStudentWithAddressById(1);
    	System.out.println(student1);
}

   将看到如下的输出结果:

Student [studId=1, name=Timothy, email=timothy@gmail.com, phone=123-123-1234, address=Address [addrId=3, street=710 N Cable Rd, city=Lima, state=OH, zip=45825, country=Allen]]

  很显然,这里将address里的信息都读出来了。

  针对这种方式,我们如果没有实现定义单独的Address的映射关系的话,还是一个比较合理的选择。但是在一些情况下,我们已经定义了Address的映射ResultMap,如果再在这里重新定义一遍,就显得比较冗余了。那么有没有别的办法呢?

 

association直接关联

  如果我们已经定义好了Address的映射,假设它的定义如下:

 

<mapper namespace="com.yunzero.map.mappers.AddressMapper">
	
  	<resultMap type="Address" id="AddressResult">
  		<id property="addrId" column="addr_id"/>
		<result property="street" column="street"/>
		<result property="city" column="city"/>
		<result property="state" column="state"/>
		<result property="zip" column="zip"/>
		<result property="country" column="country"/>
  	</resultMap>
  	
  	<select id="selectAddressById" parameterType="int" resultMap="AddressResult">
  		select * from ADDRESSES where addr_id=#{addrId}
  	</select>
  	
</mapper>

  那么,我们对应的Student ResultMap就需要修改如下:

<resultMap type="Student" id="StudentWithAddressResult">
		<id property="studId" column="stud_id"/>
		<result property="name" column="name"/>
		<result property="email" column="email"/>
		<result property="phone" column="phone"/>
		<association property="address" resultMap="com.yunzero.map.mappers.AddressMapper.AddressResult"/>
	</resultMap>

  在这个配置里,有一个比较值得注意的地方。一个是这里定义了一个association的属性,然后将对应的resultMap指向对应的AddressResult。这里定义的AddressResult是定义在另外一个文件以及命名空间里,所以需要引用它的全名。

 

内嵌select关联

  如果我们查看前面Address的映射文件,会发现里面有一些根据id查找address的方法。既然前面可以直接引用它的映射结果,那么这里也可以直接引用它的查找方法。我们只是需要在定义的结果里嵌入这个查找的定义方法就可以了。这种方式的定义如下:

 

<resultMap type="Student" id="StudentWithAddressNestedSelect">
	<id 	property="studId" column="stud_id"/>
	<result property="name" column="name"/>
	<result property="email" column="email"/>
	<result property="phone" column="phone"/>
	<association property="address" column="addr_id" select="com.yunzero.map.mappers.AddressMapper.selectAddressById"/>
</resultMap>

    采用这种方式同样需要注意的就是对目标方法的命名空间引用。这里还要设定的一个地方就是在association里指定column字段,它作为嵌入select语句里对应的参数。

 

ResultMap嵌套

  其实,除了上述的继承类型以外,我们也可以采用ResultMap嵌套的方式来实现这种一对一的映射关系。这种设置的定义如下:

<resultMap type="Student" id="StudentWithAddressNestedResultMap">
	<id 	property="studId" column="stud_id"/>
	<result property="name" column="name"/>
	<result property="email" column="email"/>
	<association property="address" javaType="Address">
		<id property="addrId" column="addr_id"/>
		<result property="street" column="street"/>
		<result property="city" column="city"/>
		<result property="state" column="state"/>
		<result property="zip" column="zip"/>
		<result property="country" column="country"/>
	</association>
</resultMap>

  采用这种方式和前面直接用association的方式很接近,只不过它将具体的address的映射细节放到这里了。 

 

一对多映射

  和一对一的关系比起来,一对多的映射要稍微复杂一点。我们先定义一个一对多的一组对象。

Tutor:

public class Tutor implements Serializable {
	private static final long serialVersionUID = 1L;
	
	private Integer tutorId;
	private String name;
	private String email;
	private Address address;
	private List<Course> courses;
        // get, set methods omitted...
	@Override
	public String toString() {
		return "Tutor [tutorId=" + tutorId + ", name=" + name + ", email=" + email
				+ ", address=" + address + ", courses=" + courses + "]";
	}
}

  在Tutor类的定义中,它包含了一个List<Course>的成员变量。这样相当于它和Course构成了一个一对多的关系。

 

嵌入ResultMap

  假设Course类的对应ResultMap定义如下:

<mapper namespace="com.yunzero.map.mappers.CourseMapper">
  	<resultMap type="Course" id="CourseResult">
  		<id 	column="course_id" property="courseId"/>
  		<result column="name" property="name"/>
  		<result column="description" property="description"/>
  		<result column="start_date" property="startDate"/>
  		<result column="end_date" property="endDate"/>
  	</resultMap>
  
  	<select id="selectCoursesByTutor" parameterType="int" resultMap="CourseResult">
  		select * from COURSES where tutor_id=#{tutorId}
  	</select>
  	
</mapper>

   那么对应的Tutor ResultMap的定义如下:

<resultMap type="Tutor" id="TutorWithCoursesNestedResult">
	<id 	column="tutor_id" property="tutorId"/>
	<result column="tutor_name" property="name"/>
	<result column="email" property="email"/>
	<association property="address" resultMap="com.yunzero.map.mappers.AddressMapper.AddressResult"/>
	<collection property="courses"  resultMap="com.yunzero.map.mappers.CourseMapper.CourseResult"  />
</resultMap>

      其实在Tutor的定义里,它关联了两个对象,一个是Address,一个是Course。只是它和Address是一对一的关系,而和Course是一对多的关系。于是在这里关联的方式就有点差别。对于多个course,它需要使用collection属性,并指定对应的CourseResult。 

  我们可以采用对应的select语句如下:

<select id="selectTutorById" parameterType="int" resultMap="TutorWithCoursesNestedResult">
	SELECT t.tutor_id, t.name as tutor_name, email, a.addr_id, street, city, state, zip, country,
	course_id, c.name, description, start_date, end_date
	FROM TUTORS t left outer join ADDRESSES a on t.addr_id=a.addr_id
	  left outer join COURSES c on t.tutor_id=c.tutor_id
	where t.tutor_id=#{tutorId}
</select>

   这里通过两个表连接来返回所有需要的字段。这种方式虽然没有直接在方法名里阐明要包含courses,但是结果里会包含这个结果。它对应的mapper接口和TutorService的实现如下:

TutorMapper:

public interface TutorMapper {
	
	Tutor selectTutorById(int tutorId);
}

 

TutorService:

public Tutor findTutorById(int tutorId) {
	SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
	try {
		TutorMapper mapper = sqlSession.getMapper(TutorMapper.class);
		return mapper.selectTutorById(tutorId);
	} 
		
	finally {
		sqlSession.close();
	}
}

 

嵌入select

  和前面描述的嵌入select方法很类似,这里需要将对应courses的选择嵌入进来。它的对应实现如下:

<resultMap type="Tutor" id="TutorWithCoursesNestedSelect">
	<id 	column="tutor_id" property="tutorId"/>
	<result column="tutor_name" property="name"/>
	<result column="email" property="email"/>
	<association property="address" resultMap="com.yunzero.map.mappers.AddressMapper.AddressResult"/>
	<collection property="courses"  column="tutor_id" select="com.yunzero.map.mappers.CourseMapper.selectCoursesByTutor"/>
  </resultMap>

  这里要注意的是选择的select的命名空间以及对应的column名。当然,因为有了对应的select之后,对应的select方法可以稍微简单一点:

<select id="selectTutorWithCourses" parameterType="int" resultMap="TutorWithCoursesNestedSelect">
	SELECT t.tutor_id, t.name as tutor_name, email, a.addr_id, street, city, state, zip, country
	FROM TUTORS t left outer join ADDRESSES a on t.addr_id=a.addr_id
	where t.tutor_id=#{tutorId}
</select>

  这种方式和前面那种方式的差别在于它将查找对应course的方法委派给对应course的映射定义里,所以这里值需要考虑它和address的映射就可以了。而前面通过两个连接操作相当于将所有结果都返回来了,就不需要再去利用别的查询。当然,因为是一对多的映射牵涉到返回多个结果,这种方式可能需要执行多次查询,有可能导致性能的问题。

 

总结

  在mybatis里针对各种映射关系的查询还是有很多小细节值得注意的。像一对一关系里,我们可以定义的类型继承,类型嵌套或者查询嵌套。我们可以根据实际的需要来进行调整。

 

参考材料

java persistence with mybatis 3 

 

友情提示:
信息收集于互联网,如果您发现错误或造成侵权,请及时通知本站更正或删除,具体联系方式见页面底部联系我们,谢谢。

其他相似内容:

热门推荐: