简介
在之前的文章里已经讨论过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允许我们使用注解来简化Mapper接口的定义,无需编写XML映射文件。例如,我们可以在...
【标题】:“mybatis学习总结:基础示例” 在IT领域,MyBatis是一个非常流行的持久层框架,它简化了Java开发中数据库操作的复杂性。这篇“mybatis学习总结:基础示例”旨在帮助初学者理解MyBatis的核心概念,并通过...
总结来说,MyBatis的configuration配置文件和Mapper XML配置是MyBatis工作的基础。通过合理的配置,我们可以灵活地处理SQL操作,实现与数据库的高效交互。理解并熟练运用这两个配置,对于提升MyBatis的使用效率和...
4. **结果映射**:通过XML或注解,可以配置复杂的对象关系映射,包括一对一、一对多、多对多的关系映射。 5. **缓存机制**:MyBatis内置了本地缓存和二级缓存,可以有效地减少数据库的访问次数,提高系统性能。 6....
Mybatis编程:基于XML的实现 使用mysql数据库,配置下数据库就可以直接运行 Mybatis编程步骤分为: 步骤1、创建好MySQL数据库 步骤2、在pom.xml中添加mysql-connector-java和Mybatis依赖 步骤3、创建实体类User...
MyBatis可以通过简单的XML或注解来配置和映射原始类型、接口和Java POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。 MyBatis的作用是简化了JDBC的操作,帮助程序员将数据存入到数据库中。...
MyBatis可以使用简单的XML或注解进行配置和原始映射,将接口和Java的POJOs(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。而JDBC(Java Database Connectivity)是Java语言中用来规范客户端程序...
- `src/main/resources`:存放Mybatis配置文件`mybatis-config.xml`和映射文件`mapper/*.xml`。 - `src/main/java`:Java源代码,包括实体类、Mapper接口和Service层实现。 5. **运行与测试**: - 编写测试类,...
MyBatis可以使用简单的XML或注解进行配置和原始映射,将接口和Java的POJOs(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。 标题中的"mybatis 3.2.6"指的是MyBatis框架的3.2.6版本。在这一版本中,...
标题提到的"本地自动生成映射类和XML文件"就是这样的一个功能,它能够根据数据库表结构自动创建相应的Mapper接口和XML配置文件,使得开发者可以更专注于业务逻辑的实现。 以下是这个过程中的关键知识点: 1. **...
MyBatis可以使用简单的XML或注解进行配置和原始映射,将接口和Java的POJOs(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。 在"Mybatis-3.5.9.zip"这个压缩包中,我们主要关注以下几个核心知识点:...
MyBatis使用这些信息将Java对象和数据库中的数据进行双向映射,实现了ORM(对象关系映射)。 为了更好地理解MyBatis的工作流程,我们可以创建一个简单的Maven项目。在Maven的`pom.xml`文件中,我们需要引入必要的...
关联映射是Mybatis处理对象关系映射的核心功能之一。 首先,我们要理解Mybatis中的三种关联映射类型: 1. **一对一(OneToOne)映射**:这种映射通常用于表示两个实体之间一对一的关系,例如一个用户只有一个地址...
总结一下,MyBatis注解配置映射器使得一对一关系的实现更加简洁直观。通过在接口上定义SQL查询并使用@One注解,我们可以轻松地在实体类之间建立关联,避免了XML配置的繁琐。同时,@Select、@Insert等注解使得SQL语句...
3. **配置灵活**:MyBatis允许开发者在XML文件中配置SQL语句,也可以使用Java注解或Java配置类来实现。 4. **动态SQL**:MyBatis支持动态SQL,可以根据条件动态地构建SQL语句。 5. **一级和二级缓存**:MyBatis内置...
而在MyBatis中,我们可以通过映射文件来定义这种关联,以便在Java对象之间建立对应的关系。 在MyBatis中,实现一对多关联有两种方式:集合映射和关联映射。集合映射通常用于一个实体包含一个列表或集合的子实体,而...
DI使得对象之间的依赖关系不再硬编码,而是通过配置文件或注解来管理。AOP则用于实现如日志、事务等横切关注点。整合时,你需要包含Spring的核心库,如`spring-context`、`spring-beans`等,并配置相关的bean定义XML...
5. 配置映射关系:在MyBatis的配置文件中,需要添加新生成的Mapper文件路径,以使MyBatis知道如何查找和使用这些映射。 6. 自定义优化:虽然自动化工具能大大减轻工作量,但有时可能需要对生成的代码进行调整,比如...