`
frank-liu
  • 浏览: 1683992 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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 

 

分享到:
评论

相关推荐

    mybatis学习总结:annotation与xml结合示例

    本篇文章将聚焦于MyBatis中的注解(Annotation)与XML配置的结合使用,旨在帮助开发者更深入地理解这一关键特性。 首先,MyBatis允许我们使用注解来简化Mapper接口的定义,无需编写XML映射文件。例如,我们可以在...

    mybatis学习总结:基础示例

    【标题】:“mybatis学习总结:基础示例” 在IT领域,MyBatis是一个非常流行的持久层框架,它简化了Java开发中数据库操作的复杂性。这篇“mybatis学习总结:基础示例”旨在帮助初学者理解MyBatis的核心概念,并通过...

    MyBatis学习笔记(一):MyBatis configuration和mapper xml配置总结

    总结来说,MyBatis的configuration配置文件和Mapper XML配置是MyBatis工作的基础。通过合理的配置,我们可以灵活地处理SQL操作,实现与数据库的高效交互。理解并熟练运用这两个配置,对于提升MyBatis的使用效率和...

    mybatis-3.4.5

    4. **结果映射**:通过XML或注解,可以配置复杂的对象关系映射,包括一对一、一对多、多对多的关系映射。 5. **缓存机制**:MyBatis内置了本地缓存和二级缓存,可以有效地减少数据库的访问次数,提高系统性能。 6....

    Mybatis编程示例:基于XML的实现

    Mybatis编程:基于XML的实现 使用mysql数据库,配置下数据库就可以直接运行 Mybatis编程步骤分为: 步骤1、创建好MySQL数据库 步骤2、在pom.xml中添加mysql-connector-java和Mybatis依赖 步骤3、创建实体类User...

    【狂神说】mybatis学习总结笔记(全)PDF格式文档 MyBatis.pdf

    MyBatis可以通过简单的XML或注解来配置和映射原始类型、接口和Java POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。 MyBatis的作用是简化了JDBC的操作,帮助程序员将数据存入到数据库中。...

    mybatis+jdbc的jar包

    MyBatis可以使用简单的XML或注解进行配置和原始映射,将接口和Java的POJOs(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。而JDBC(Java Database Connectivity)是Java语言中用来规范客户端程序...

    mybatis系列一:开发环境搭建

    - `src/main/resources`:存放Mybatis配置文件`mybatis-config.xml`和映射文件`mapper/*.xml`。 - `src/main/java`:Java源代码,包括实体类、Mapper接口和Service层实现。 5. **运行与测试**: - 编写测试类,...

    mybatis 3.2.6

    MyBatis可以使用简单的XML或注解进行配置和原始映射,将接口和Java的POJOs(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。 标题中的"mybatis 3.2.6"指的是MyBatis框架的3.2.6版本。在这一版本中,...

    MyBatis+MySQL本地自动生成映射类和XML文件

    标题提到的"本地自动生成映射类和XML文件"就是这样的一个功能,它能够根据数据库表结构自动创建相应的Mapper接口和XML配置文件,使得开发者可以更专注于业务逻辑的实现。 以下是这个过程中的关键知识点: 1. **...

    mybatis-3.5.9.zip

    MyBatis可以使用简单的XML或注解进行配置和原始映射,将接口和Java的POJOs(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。 在"Mybatis-3.5.9.zip"这个压缩包中,我们主要关注以下几个核心知识点:...

    MyBatis执行SQL并将结果映射成Java对象.docx

    MyBatis使用这些信息将Java对象和数据库中的数据进行双向映射,实现了ORM(对象关系映射)。 为了更好地理解MyBatis的工作流程,我们可以创建一个简单的Maven项目。在Maven的`pom.xml`文件中,我们需要引入必要的...

    Mybatis关联映射Demo

    关联映射是Mybatis处理对象关系映射的核心功能之一。 首先,我们要理解Mybatis中的三种关联映射类型: 1. **一对一(OneToOne)映射**:这种映射通常用于表示两个实体之间一对一的关系,例如一个用户只有一个地址...

    MyBatis注解配置映射器:一对一关系的实现

    总结一下,MyBatis注解配置映射器使得一对一关系的实现更加简洁直观。通过在接口上定义SQL查询并使用@One注解,我们可以轻松地在实体类之间建立关联,避免了XML配置的繁琐。同时,@Select、@Insert等注解使得SQL语句...

    MyBatis-Plus:增强的ORM体验

    3. **配置灵活**:MyBatis允许开发者在XML文件中配置SQL语句,也可以使用Java注解或Java配置类来实现。 4. **动态SQL**:MyBatis支持动态SQL,可以根据条件动态地构建SQL语句。 5. **一级和二级缓存**:MyBatis内置...

    mybatis系列三:一对多双向关联

    而在MyBatis中,我们可以通过映射文件来定义这种关联,以便在Java对象之间建立对应的关系。 在MyBatis中,实现一对多关联有两种方式:集合映射和关联映射。集合映射通常用于一个实体包含一个列表或集合的子实体,而...

    Spring+SpringMVC+Mybatis整合所需jar包以及xml配置文件配置方式

    DI使得对象之间的依赖关系不再硬编码,而是通过配置文件或注解来管理。AOP则用于实现如日志、事务等横切关注点。整合时,你需要包含Spring的核心库,如`spring-context`、`spring-beans`等,并配置相关的bean定义XML...

    mybatis xml文件自动生成

    5. 配置映射关系:在MyBatis的配置文件中,需要添加新生成的Mapper文件路径,以使MyBatis知道如何查找和使用这些映射。 6. 自定义优化:虽然自动化工具能大大减轻工作量,但有时可能需要对生成的代码进行调整,比如...

Global site tag (gtag.js) - Google Analytics