Mybatis(三)

三 使用XML配置SQL映射器(映射文件)
关系型数据库和SQL是经受时间考验和验证的数据存储机制。和其他的ORM 框架如Hibernate不同,【MyBatis鼓励】开发者可以直接【使用数据库】,而不是将其对开发者隐藏,因为这样可以充分发挥数据库服务器所提供的SQL语句的巨大威力。
与此同时,MyBaits【消除】了书写大量【冗余代码】的痛苦,它让使用SQL更容易。在代码里直接嵌套
很差的编码实践,并且维护起来困难。MyBaits使用了映射文件或注解来配置SQL语句。

3.1 映射器文件和映射器接口
我们已经看见了一些在映射器配置文件中配置基本的映射语句,以及怎样使用SqlSession对象调用它们的例子。现在让我们看一下在com.briup.mappers包中的StudentMapper.xml 配置文件内,是如何配置id为”findStudentById”的SQL语句的,代码如下:

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">


我们可以通过下列代码调用findStudentById映射的SQL语句:
public Student findStudentById(Integer studId) {
SqlSession sqlSession = MyBatisSqlSessionFactory.openSession();
try{
Student student = sqlSession.selectOne("com.briup.mappers.StudentMapper.findStudentById", studId);
return student;
}
finally {
sql Session.close();
}
}

调用映射方法的方式一:
我们可以通过字符串(字符串形式为:映射器配置文件所在的包名的【namespace + sql语句id值】,如上,即包名com.briup.mappers.StudentMapper和语句id的值findStudentById组成)调用映射的SQL语句。
但是这种方式【容易出错】,我们【不推荐】使用。你需要检查映射器配置文件中的定义,以保证你的输入参数类型和结果返回类型是有效的。

调用映射方法的方式二:
也就是我们一直在使用的方式,通过session获取mapper对象,然后由mapper对象直接调用映射方法实现功能。
【重点部分:】
MyBatis通过使用映射器Mapper接口提供了更好的调用映射语句的方法。一旦我们通过映射器配置文件配置了映射语句,我们可以创建一个完全对应的一个映射器接口,xml映射文件中的【namespace属性值】和映射接口的【全限定名】需要保持【一致】。
映射器接口中的【方法名】也跟映射器配置文件中【id值】完全对应;
映射方法的【参数类型】和【parameterType属性值】对应;
映射方法【返回值类型】和【returnType属性值】一致。

上述的StudentMapper.xml文件,我们可以创建一个映射器接口StudentMapper.java如下:
     
    package com.briup.mappers; 
    public interface StudentMapper{ 
        Student findStudentById(Integer id); 
    } 
在Student Mapper.xml映射器配置文件中,其名空间namespace应该跟StudentMapper接口的全限定名保持一致。另外,StudentMapper.xml中语句id, parameterType,returnType 应该分别和StudentMapper接口中的方法名,参数类型,返回值相对应。

使用映射器接口我们可以以类型安全的形式调用调用映射语句。如下所示:
     
    public Student findStudentById(Integer studId){ 
        SqlSession sqlSession = MyBatisSqlSessionFactory.openSession();  
        try { 
            StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); 

            return student Mapper.findStudentById(studId); 
        } 
        finally { 
            sqlSession.close(); 
        } 
    } 

结论:
namespace关键字的用法
情形1: 任何值都可以
使用session对象执行sql语句;

情形2:  必须映射接口的全包名
    使用映射接口对象 执行sql语句。
    
推荐第2种。

3.2 映射语句
MyBatis提供了多种元素来配置不同类型的语句,如SELECT,INSERT,UPDATE,DELETE。让我们看看如何具体配置映射语句

3.2.1 INSERT 插入语句
一个INSERT语句可以在标签元素在映射器XML配置文件中配置,如下所示:

INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL, PHONE) VALUES(#{studId},#{name},#{email},#{phone})

这里我们设置一个ID属性为insertStudent,可以在命名空间 com.briup.mappers.StudentMapper.insertStudent中唯一标识该sql语句。
parameterType 属性是一个完全限定类名或者是一个类型别名(alias)。

我们可以如下调用这个语句:
    int count =  sqlSession.insert("com.briup.mappers.StudentMapper.insertStudent", student); 
    sqlSession.insert() 方法返回执行 INSERT 语句后所影响的行数。

如果不使用命名空间(namespace)和语句 id 来调用映射语句,你可以通过创建一个映射器Mapper 接口,并以类型安全的方式调用方法,如下所示:
    package com.briup.mappers; 
    public interface StudentMapper{ 
        int insertStudent(Student student); 
    } 
你可以如下调用insertStudent映射语句:
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); 
    int count = mapper.insertStudent(student); 

【自动生成主键】
情形1:
数据库默认支持自动生成主键列的值,例如mysql。

在上述的INSERT语句中,我们为可以自动生成(auto-generated)主键的列 STUD_ID 插入值。我们可以使用useGeneratedKeys和keyProperty属性让数据库生成auto_increment列的值,并将生成的值设置到其中一个输入对象属性内,如下所示:
 
     
        INSERT INTO STUDENTS(NAME, EMAIL, PHONE) VALUES(#{name},#{email},#{phone}) 
     
这里STUD_ID列值将会被数据库自动生成(如mysql),并且生成的值会被设置到student对象的studId属性上。

情形2:
数据库不支持自动生成主键列的值,例如oracle。
但是有些数据库如Oracle并不支持AUTO_INCREMENT列,其使用序列【SEQUENCE】来生成主键值。假设我们有一个名为my_seq的序列来生成SUTD_ID主键值。使用如下代码来生成主键:
drop sequence my_seq;
create sequence my_seq;

     
         
            SELECT my_seq.nextval FROM DUAL 
         
        INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL, PHONE) 
            VALUES(#{studId},#{name},#{email},#{phone}) 
     
这里我们使用了子元素来生成主键值,并将值保存到Student对象的studId 属性上。属性order=“before”表示MyBatis将取得序列的下一个值作为主键值,并且在执行INSERT语句之前将值设置到studId属性上。

【注意】
    SelectKey需要注意order属性,像MySQL、SQLServer等一类支持自动增长类型的数据库中,order需要设置为after才会取到正确的值。
    像Oracle这样取序列的情况,需要设置为before,否则会报错。

3.2.2 UPDATE 更新语句
一个UPDATE SQL语句可以在元素在映射器XML配置文件中配置,如下所示:

UPDATE STUDENTS SET NAME=#{name}, EMAIL=#{email}, PHONE=#{phone}
WHERE STUD_ID=#{studId}

我们可以如下调用此语句:
    int noOfRowsUpdated = sqlSession.update("com.briup.mappers.StudentMapper.updateStudent", student); 
    sqlSession.update()方法返回执行UPDATE语句之后影响的行数。

如果不使用名空间(namespace)和语句id来调用映射语句,你可以通过创建一个映射器Mapper接口,并以类型安全的方式调用方法,如下所示:
     
    package com.briup.mappers; 
    public interface StudentMapper{ 
        int updateStudent(Student student); 
    } 

你可以使用映射器Mapper接口来调用updateStudent语句,如下所示:
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); 
    int noOfRowsUpdated = mapper.updateStudent(student); 

3.2.3 DELETE 删除语句
一个UPDATE SQL语句可以在元素在映射器XML配置文件中配置,如下所示

DELETE FROM STUDENTS WHERE STUD_ID=#{id}

我们可以如下调用此语句:
    int studId = 1; 
    //          delete("命名空间名.方法名",参数);
    int noOfRowsDeleted = sqlSession.delete("com.briup.mappers.StudentMapper.deleteStudent", studId); 
    sqlSession.delete()方法返回 delete 语句执行后影响的行数。

如果不使用名空间(namespace)和语句 id 来调用映射语句,你可以通过创建一个映射器 Mapper 接口,并以类型安全的方式调用方法,如下所示:
    package com.briup.mappers; 
    public interface StudentMapper{ 
      int deleteStudent(int studId); 
    } 

你可以使用映射器Mapper接口来调用deleteStudent语句,如下所示:
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); 
    int noOfRowsDeleted = mapper.deleteStudent(studId);

3.2.4 SELECT 查询语句
MyBatis真正【强大】的功能,在于【映射SELECT】查询结果到java的【各种类型】。
让我们看看一个简单的select查询是如何(在MyBatis中)配置的,如下所示:

我们可以如下调用此语句:
int studId = 1;
Student student = sqlSession.selectOne("com.briup.mappers. StudentMapper.findStudentById", studId);

如果不使用名空间(namespace)和语句 id 来调用映射语句,你可以通过创建一个映射器 Mapper 接口,并以类型安全的方式调用方法,如下所示:
    package com.briup.mappers; 
    public interface StudentMapper{ 
        Student findStudentById(Integer studId); 
    }
你可以使用映射器Mapper接口来调用 findStudentById 语句,如下所示:
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); 
    Student student = mapper.findStudentById(studId); 

如果你检查Student对象的属性值,你会发现studId属性值并没有被stud_id列值填充。这是因为MyBatis自动对java对象中和列名匹配的属性进行填充。这就是为什么name,email和 phone属性被填充而studId属性没有被填充。
解决这一问题,我们可以为列名起一个可以与JavaBean中属性名匹配的别名,如下所示:
     

    
MyBatis执行返回多条结果的SELECT语句查询,如下所示:
    

    List students =  
    sqlSession.selectList("com.briup.mappers.StudentMapper.findAllStudents"); 

映射器 Mapper 接口 StudentMapper 可以如下定义:
    package com.briup.mappers; 
    public interface StudentMapper{ 
        List findAllStudents(); 
    } 

使用上述代码,我们可以如下调用
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); 
    List students = mapper.findAllStudents(); 

如果你注意到上述的SELECT映射定义,你可以看到,我们为所有的映射语句中的stud_id 起了别名。我们可以使用ResultMaps,来避免上述的到处重复别名。我们稍后会继续讨论。

除了java.util.List,你也可以使用其他类型的集合类,如Set,Map,以及(SortedSet)。MyBatis 根据集合的类型,会采用适当的集合实现,如下所示:
    对于List,Collection,Iterable类型,MyBatis将返回java.util.ArrayList 
    对于Map类型,MyBatis 将返回java.util.HashMap  
    对于Set类型,MyBatis 将返回java.util.HashSet 
    对于SortedSet类型,MyBatis将返回java.util.TreeSet 

3.3 结果集映射 ResultMaps
ResultMaps被用来将SELECT语句的结果集映射到java对象的属性中。我们可以定义结果集映射ResultMaps并且在一些SELECT语句上引用resultMap。MyBatis的结果集映射 ResultMaps特性非常强大,你可以使用它将简单的SELECT语句映射到复杂的一对一、一对多关系的SELECT语句上。

3.3.1 简单ResultMap
一个映射了查询结果为Student类型的resultMap定义如下:





     
     

resultMap的id值应该在此名空间内是唯一的,并且type属性是完全限定类名或者是返回类型的别名。
子元素被用来将一个resultset列映射到对象的一个属性中。
元素和元素功能相同,不过它被用来映射到唯一标识属性,用来区分和比较对象(一般和主键列相对应)。
在语句中配置了resutlMap属性,MyBatis会使用表中的列名与对象属性 【映射关系】 来填充对象中的属性值。
    
【注意】:
    【resultType和resultMap】二者只能用其一,】不能同时使用】。

【问题升级1】:

SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}

在上述的映射语句中如何将查询【多条】数据填充到Map中?

由于resultType=”map”和语句返回多行,则最终返回的数据类型应该是List>,如下所示:

    List> studentMapList = sqlSession.selectList("com.briup.mappers.StudentMapper.findAllStudents"); 
    for(Map studentMap : studentMapList) { 
    System.out.println("studId :" + studentMap.get("stud_id")); 
        System.out.println("name :" + studentMap.get("name")); 
        System.out.println("email :" + studentMap.get("email")); 
        System.out.println("phone :" + studentMap.get("phone")); 
    } 


其他实例1:【结果为对象】
    
对应的接口中的方法,你写什么类型的集合,Mybatis就给你返回什么类型的集合,但是要注意使用SortedSet的时候,Student类需要实现Comparable接口,否则是不能进行排序的。
例如:
    public List findAllStudents_List();
    或者
    public Set findAllStudents_Set();
    或者
    public SortedSet findAllStudents_SortedSet();
    SortedSet是Set的子接口,TreeSet是其实现子类【需要实现比较接口才可以成功】。

其他实例2:【查询指定列】
    
    对应的接口中的方法: 把查询到所有名字都放到List集合中并返回
    public List findAllName_list();

    
其他实例3:【查询组函数】
    
    对应的接口中的方法: 把查询到的这个值直接返回
    public int findCount_int();

3.3.2 拓展/继承 ResultMap 【后面知识点,暂不介绍】
(注:这个例子在下面的一对一映射的知识点中进行测试,因为这里需要建立一对一关系的表结构)
我们可以从从另外一个,【拓展】出一个新的,这样,原先的属性映射可以【继承】过来,以实现:





    
    
     
         
         
         
         
         
         
     

    其中id为StudentWithAddressResult的resultMap拓展了id为StudentResult的resultMap

    如果你只想映射Student数据,你可以使用id为StudentResult的resultMap,如下所示:
     
     

    如果你想将映射Student数据和Address数据,你可以使用id为StudentWithAddressResult的 resultMap:
     
    注:该sql语句使用了连接查询中的左外连接,也可以使用等值连接

3.4 一对一映射
Student和Address是一个【一对一】关系
建表语言:
drop table students;
drop table addresses;
如果需要可以使用 cascade constraints;

    create table addresses(
        addr_id number primary key,
        street varchar2(50) not null,
        city varchar2(50) not null,
        state varchar2(50) not null,
        zip varchar2(10),
        country varchar2(50)
    );

    create table students(
        stud_id number primary key,
        name varchar2(50) not null,
        email varchar2(50),
        phone varchar2(15),  
        dob date ,
        addr_id number references addresses(addr_id)
    );

java类:
    public class PhoneNumber {
        private String countryCode;
        private String stateCode;
        private String number;
        get/set
    }
    public class Address{
        private Integer addrId;
        private String street;
        private String city;
        private String state;
        private String zip;
        private String country;
        get/set
    }
    public class Student {
        private Integer studId; 
        private String name; 
        private String email; 
        private Date dob;
        private PhoneNumber phone;
        private Address address;
        get/set
    }


    addresses 表的样例输入如下所示:
    addr_id  street     city     state  zip   country 
        1    redSt      kunshan   W     12345  china 
        2    blueST     kunshan   W     12345  china 

    insert into addresses(addr_id,street,city,state,zip,country) values(1,'redSt','kunshan','W','12345','china');
    insert into addresses(addr_id,street,city,state,zip,country) values(2,'blueST','kunshan','W','12345','china');


    students 表的样例数据如下所示:
    stud_id  name    email          phone       addr_id 
       1    John  [email protected]  123-456-7890   1 
       2    Paul  [email protected]  111-222-3333   2 
    
    insert into students(stud_id,name,email,phone,addr_id) values(1,'John','[email protected]','123-456-7890',1);
    insert into students(stud_id,name,email,phone,addr_id) values(2,'Paul','[email protected]','111-222-3333',2);

【执行select操作】
mapper XML:

     
         
         
         
         
         
         
         
         
         
         
         
    
    
    //【多表查询】实现功能
      
    //注意,有歧义的一定要有【别名】,两个表中都有【addr_id】

我们可以使用(【对象.属性名】)的方式为【内嵌的对象】的属性赋值。在上述的resultMap中,Student的address属性使用该方式被赋上了 address 对应列的值。同样地,我们可以访问【任意深度】的内嵌对象的属性。

//接口定义 
    public interface StudentMapper{ 
        Student selectStudentWithAddress(int studId); 
    } 

//方法调用
    int studId = 1; 
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); 
    Student student = studentMapper.selectStudentWithAddress(studId); 
    System.out.println("Student :" + student); 
    System.out.println("Address :" + student.getAddress()); 

【执行insert操作】
1.映射文件:


select my_seq.nextval from dual

insert into addresses(addr_id,street,city,state,zip,country)
values(#{addrId},#{street},#{city},#{state},#{zip},#{country})

    
        
            select my_seq.nextval from dual
        
        insert into students(stud_id,name,email,phone,dob,addr_id) 
        values(#{studId},#{name},#{email},#{phone},#{dob},#{address.addrId})
    
    
2.映射接口:
    public void insertAddress(Address addr);

    public void insertStudent(Student stu);

3.测试代码:
    One2OneMapper mapper = session.getMapper(One2OneMapper.class);
    Address addr = new Address("xyl", "ks", "sz", "js", "china");
    mapper.insertAddress(addr);
    
    Student stu = new Student("zs", "email", new Date(), new PhoneNumber("110-120-119"), addr);
    mapper.insertStudent(stu);
    
    session.commit();
        
上面展示了一对一关联映射的一种方法。然而,使用这种方式映射,如果address结果需要在其他的SELECT映射语句中映射成Address对象,我们需要为每一个语句重复这种映射关系。
MyBatis提供了更好地实现一对一关联映射的方法:【嵌套结果】ResultMap和【嵌套查询】select语句。接下来,我们将讨论这两种方式。

3.4.1 使用【嵌套结果ResultMap】实现一对一关系映射
我们可以使用一个嵌套结果ResultMap方式来获取Student及其Address信息。

映射文件 【映射接口和测试代码 使用之前的即可】
     
         
         
         
         
         
         
     
    
     
         
         
         
         
         
        
         
    
    
    
    
    【等值连接】
        select stud_id, name, email, dob, phone, a.addr_id, street, city, state, zip, country 
        from students s, addresses a 
        where s.addr_id=a.addr_id 
        and stud_id=#{studid}
    
注:【association是关联】的意思
    元素被用来导入“有一个”(has-one)类型的关联。在上述的例子中,我们使用了元素引用了另外的在同一个XML文件中定义的。     
    
    
同时我们也可以使用 定义【内联的resultMap】,代码如下所示:
     
         
         
         
         
         
         
             
             
             
             
             
             
         
     

3.4.2 使用【嵌套查询select】实现一对一关系映射
嵌套查询本质:
一个select语句 转化成 多条select语句去实现功能。
我们可以通过使用嵌套select查询来获取Student及其Address信息,代码如下:
1.【先根据addr_id去查找 地址对象】







     

2.【根据学号 查询 学生对象】
     
         
         
         
         
        
        
         
    
    
     

在此方式中,元素的select属性被设置成了id为findAddressById的语句。这里,两个分开的SQL语句将会在数据库中分别执行,第一个调用findStudentById加载student信息,而第二个调用findAddressById来加载address信息。

addr_id列的值将会被作为输入参数传递给selectAddressById语句。

3.【映射接口】
    public Address findAddressById(int id);
    另外一个映射方法 之前已经实现,不需要再次定义。
    
4.【测试代码】
    测试代码同上。

我们可以如下调用findStudentWithAddress映射语句:
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); 
    Student student = mapper.selectStudentWithAddress(studId); 
    System.out.println(student); 
    System.out.println(student.getAddress());

总结:
嵌套结果映射 本质上是一条sql语句查询多张表;
嵌套查询 本质上是每条sql语句查询一张表,组合在一起查询多张表。
效率上,嵌套结果更快。

结论:
如果是 嵌套结果,通过 association标签中的 resultMap属性 实现;
如果是 嵌套查询,通过 association标签中的 select属性 实现。


3.5 一对多映射
【一个讲师】tutors可以教授一个或者【多个课程】course。这意味着讲师和课程之间存在一对多的映射关系。

第一步:建立表结构,并插入数据
注意:在一对多关系中,数据库建表的时候外键一定是在多的那一方建立.
建表语句:
drop table tutors;
drop table courses;
如果需要可以使用 cascade constraints;

    create table tutors(
        tutor_id number primary key,
        name varchar2(50) not null,
        email varchar2(50) ,
        phone varchar2(15) ,  
        addr_id number(11) references addresses (addr_id)
    );

    create table courses(
        course_id number primary key,
        name varchar2(100) not null,
        description varchar2(512),
        start_date date ,
        end_date date ,
        tutor_id number references tutors (tutor_id)
    );

tutors 表的样例数据如下:
    tutor_id   name     email         phone     addr_id 
        1       zs  [email protected]   123-456-7890    1 
        2       ls  [email protected]   111-222-3333    2 

插入数据:
    insert into tutors(tutor_id,name,email,phone,addr_id)
    values(1,'zs','[email protected]','123-456-7890',1);
    insert into tutors(tutor_id,name,email,phone,addr_id)
    values(2,'ls','[email protected]','111-222-3333',2);

course 表的样例数据如下:
    course_id  name  description  start_date   end_date  tutor_id 
        1    JavaSE    JavaSE      2015-09-10  2016-02-10   1 
        2    JavaEE    JavaEE      2015-09-10  2016-03-10   2 
        3    MyBatis   MyBatis     2015-09-10  2016-02-20   2 

插入数据:
    insert into courses(course_id,name,description,start_date,end_date,tutor_id) 
    values(1,'JavaSE','JavaSE',to_date('2015-09-10','yyyy-mm-dd'),to_date('2016-02-10','yyyy-mm-dd'),1);

    insert into     courses(course_id,name,description,start_date,end_date,tutor_id)
    values(2,'JavaEE','JavaEE',to_date('2015-09-10','yyyy-mm-dd'),to_date('2016-03-10','yyyy-mm-dd'),2);

    insert into         courses(course_id,name,description,start_date,end_date,tutor_id)
    values(3,'MyBatis','MyBatis',to_date('2015-09-10','yyyy-mm-dd'),to_date('2016-02-20','yyyy-mm-dd'),1);

在上述的表数据中,zs 讲师教授一个课程,而 ls 讲师教授两个课程

第二步:建立对应Java类
java代码:
public class Tutor{
private Integer tutorId;
private String name;
private String email;
private PhoneNumber phone;
private Address address;
//【此处注意,一个老师可以教多门课,所以用集合】
private List courses;

    get/set
}

public class Course{
    private Integer courseId; 
    private String name; 
    private String description; 
    private Date startDate; 
    private Date endDate; 

    get/set
}

【元素】被用来将多行课程结果映射成一个课程Course对象的一个集合。和一对一映射一样,我们可以使用【嵌套结果ResultMap】和【嵌套查询Select】语句两种方式映射实现一对多映射。

第三步
建立One2MoreMapper.xml映射文件
建立One2MoreMapper.java映射接口

在mybatis-config.xml文件中配置mapper标签,使映射文件生效。

3.5.1 使用嵌套结果 ResultMap 实现一对多映射

第四步:在One2MoreMapper.xml文件中添加以下配置
使用嵌套结果resultMap方式获得讲师及其课程信息,
注意,一个讲师类Tutor 中包含一个地址address 和多门课程 courses。
所以表示讲师的ResultMap里面可以包含一个address和多个course;

代码如下:
    
     
         
         
         
         
         
         
    
    
    
     
         
         
         
         
         
     
    
    
     
         
         
         
         
        
        
        
         
     


     
    
select语句也可以通过【多表查询】 【等值连接】实现
    
    
【注意】
    此时查询出来的结果中,课程name值和讲师name值相同,原因是select中有多个name,产生了歧义,可以使用别名解决,即c.name as 【cname】。
    
    这里我们使用了一个简单的使用了JOINS连接的Select语句获取讲师及其所教课程信息。元素的resultMap属性设置成了CourseResult,CourseResult包含了Course对象属性与表列名之间的映射。
    要查询到Address相关信息,按照上面一对一的方式,在配置中加入。

第五步:添加测试文件,获得session对象,再有session获得mapper对象,然后调用findTutorById(1)方法去获得1号讲师,然后输出。

3.5.2 使用嵌套Select语句实现一对多映射
也可以使用【嵌套Select语句】方式获得讲师及其课程信息
嵌套查询可以理解成子查询,但稍微不同,子查询是先查子句然后再查主句;但此处是先查主句讲师 tutor表信息,得到讲师id后,再据此去查找课程course表信息,同时得到讲师地址id后,再据此查找讲师地址address信息。

第四步:在映射文件One2MoreMapper.xml文件中添加如下代码
     
         
         
         
         
         
         
    
     
         
         
         
         
         
    

    
     
         
         
         
        
        
        
        
         
     
    
    
    
    
    
    
     


注意:     
    在这种方式中,元素的select属性被设置为id为findCourseByTutor的语句,用来触发单独的SQL查询加载课程信息。tutor_id这一列值将会作为输入参数传递给 findCouresByTutor语句。

第五步:去One2MoreMapper.java接口中添加成员方法
    public interface TutorMapper{ 
        Tutor findTutorById(int tutorId); 
    } 

第六步:在测试文件中调用方法
    TutorMapper mapper = sqlSession.getMapper(TutorMapper.class); 
    Tutor tutor = mapper.findTutorById(tutor Id); 
    System.out.println(tutor); 
    List courses = tutor.getCourses(); 
    for (Course course : courses){ 
        System.out.println(course); 
    } 
【注意】嵌套查询Select语句查询会导致1+N选择问题。首先,主查询将会执行(1 次),对于主查询返回的每一行,另外一个查询将会被执行(主查询 N 行,则此查询 N 次)。对于大量数据而言,这会导致很差的性能问题。

总之,我们还是推荐【嵌套结果】方式。

3.5 多对多映射
对于在mybatis中的多对多的处理,其实我们可以参照一对多来解决
【注意】在这个例子中有三个字段都是一样的:id,这种情况一定要小心,要给列起别名的(上面的一对一和一对多中如果出现这种情况也是一样的处理方式)

多对多的关系,需要建立第三张桥表 来帮助实现功能。

第一步:建立表结构【此处不用插入数据,通过mybatis编码实现】
    建表语句:
    drop table student_course;
    drop table course;
    drop table student;
    如果需要可以使用 cascade constraints;

    create table course (
        id number primary key,
        course_code varchar2(30) not null,
        course_name varchar2(30) not null 
    );
    create table student (
        id number primary key,
        name varchar2(10) not null,
        gender varchar2(10) ,
        major varchar2(10) ,
        grade varchar2(10) 
    );
    //学生课程表  就是 桥表
    create table student_course (
        id number primary key,
        student_id number references student(id),
        course_id number references course(id)
    );

第二步:建立对应Java类 com.briup.many2many
    java代码:
    public class Course {
        private Integer id;
        private String courseCode; // 课程编号
        private String courseName;// 课程名称
        private List students;// 选课学生
        get/set
    }
    public class Student {
        private Integer id;
        private String name; // 姓名
        private String gender; // 性别
        private String major; // 专业
        private String grade; // 年级
        private List courses;// 所选的课程
        get/set
    }
    
第三步:建立Many2ManyMapper.java接口
    public interface Many2ManyMapper {
        //插入student数据
        public void insertStudent(Student student);
        //插入course数据
        public void insertCourse(Course course);
        //通过id查询学生
        public Student getStudentById(Integer id);
        //通过id查询课程
        public Course getCourseById(Integer id);
        
        //学生x选课y
        public void studentSelectCourse(Student student, Course course);
        //查询比指定id值小的学生信息
        public List getStudentByIdOnCondition(Integer id);
        //查询student级联查询出所选的course并且组装成完整的对象
        public Student getStudentByIdWithCourses(Integer id);
    }

第四步:在映射文件Many2ManyMapper.xml中进行配置
a.插入学生、课程信息【基本操作】
    
        
            select my_seq.nextval from dual
        
        insert into student(id,name,gender,major,grade)
        values(#{id},#{name},#{gender},#{major},#{grade})
    
    
    
        
            select my_seq.nextval from dual
        
        insert into course(id,course_code,course_name)
        values(#{id},#{courseCode},#{courseName})
    

b.Many2ManyMapperTest.java文件中添加测试代码
【插入基本信息到数据库,注意 两个表先不相互包含】
    //插入学生 
    Student s = new Student("lisi", "男", "计算机", "大四");
    mapper.insertStudent(s);
    session.commit();
    
    //插入课程
    mapper.insertCourse(new Course("002","Oracle"));
    session.commit();

c.在Many2ManyMapper.xml中配置查询操作
    
    
    

d.Many2ManyMapperTest.java文件中添加测试代码
    //查学生
    Many2ManyMapper mapper = session.getMapper(Many2ManyMapper.class);
    Student stu = mapper.getStudentById(22);
    System.out.println(stu);

    //查课程
    Many2ManyMapper mapper = session.getMapper(Many2ManyMapper.class);
    Course course = mapper.getCourseById(24);
    System.out.println(course);

在前面所有功能实现完成以后,再做下面的工作

【核心功能】,实现学生选课功能
    
第五步:映射文件中配置【学生选课】 多对多映射
a.添加xml配置文件
    
    
        insert into student_course(id,student_id,course_id)
        values(my_seq.nextval,#{param1.id},#{param2.id})
    

b.添加测试代码
    Student student = mapper.getStudentById(22);
    Course course = mapper.getCourseById(24);
    mapper.studentSelectCourse(student,course);
    session.commit();

c. 根据条件查找学生信息
    
    
d. 添加测试代码
    Many2ManyMapper mapper = session.getMapper(Many2ManyMapper.class);
    List list = mapper.getStudentByIdOnCondition(30);
    for (Student student : list) {
        System.out.println(student);
    }

第六步:核心功能,查询student级联查询出所选的course并且组装成完整的对象
a.封装基本Student查询结果,不包含Course
    
    
    
        
        
        
        
        
    
b.在以上ResultMap基础上,封装完整Student查询结果
    
    
        
    
c.单独再封装遇到的Course对象为CourseResult
    
    
        
        
        
    
d.书写select查询语句
    
    
e.添加测试代码
    Many2ManyMapper mapper = session.getMapper(Many2ManyMapper.class);
    Student stu = mapper.getStudentByIdWithCourses(22);
    System.out.println(stu);

Many2ManyMapperTest.java中完整测试代码如下:
@Test
public void test_insertStudent(){

    SqlSession session = null;
    try {
        session = MyBatisSqlSessionFactory.openSession();
        
        Many2ManyMapper mapper = session.getMapper(Many2ManyMapper.class);
            
        mapper.insertStudent(new Student("张三","男","计算机","大四"));
        
        session.commit();
        
    } catch (Exception e) {
        e.printStackTrace();
        session.rollback();
    }finally {
        if(session!=null)session.close();
    }
    
}

@Test
public void test_insertCourse(){
    
    SqlSession session = null;
    try {
        session = MyBatisSqlSessionFactory.openSession();
        
        Many2ManyMapper mapper = session.getMapper(Many2ManyMapper.class);
            
        mapper.insertCourse(new Course("001","corejava"));
        mapper.insertCourse(new Course("002","oracle"));
        
        session.commit();
        
    } catch (Exception e) {
        e.printStackTrace();
        session.rollback();
    }finally {
        if(session!=null)session.close();
    }
    
}

@Test
public void test_getStudentById() {
    SqlSession session = null;
    try {
        session = MyBatisSqlSessionFactory.openSession();
        
        Many2ManyMapper mapper = session.getMapper(Many2ManyMapper.class);
        
        Student stu = mapper.getStudentById(61);
        System.out.println(stu);
        
        session.commit();
        
    } catch (Exception e) {
        e.printStackTrace();
        session.rollback();
    }finally {
        if(session!=null)session.close();
    }
}

@Test
public void test_getCourseById() {
    SqlSession session = null;
    try {
        session = MyBatisSqlSessionFactory.openSession();
        
        Many2ManyMapper mapper = session.getMapper(Many2ManyMapper.class);
        
        Course c = mapper.getCourseById(62);
        System.out.println(c);
        
        session.commit();
        
    } catch (Exception e) {
        e.printStackTrace();
        session.rollback();
    }finally {
        if(session!=null)session.close();
    }
    
}

@Test
public void test_studentSelectCourse(){
    
    SqlSession session = null;
    try {
        session = MyBatisSqlSessionFactory.openSession();
        
        Many2ManyMapper mapper = session.getMapper(Many2ManyMapper.class);
        
        //【注意】一定是 获取已经插入表中的学生 和 课程,然后进行选课
        Student student = mapper.getStudentById(58);
        Course course = mapper.getCourseById(59);
        
        mapper.studentSelectCourse(student, course);
        
        session.commit();
        
    } catch (Exception e) {
        e.printStackTrace();
        session.rollback();
    }finally {
        if(session!=null)session.close();
    }
    
}

@Test
public void test_getStudentByIdOnCondition(){
    
    SqlSession session = null;
    try {
        session = MyBatisSqlSessionFactory.openSession();
        
        Many2ManyMapper mapper = session.getMapper(Many2ManyMapper.class);
        
        List list = mapper.getStudentByIdOnCondition(100);
        
        for(Student s:list){
            System.out.println(s);
        }
        
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        if(session!=null)session.close();
    }
    
}

@Test
public void test_getStudentByIdWithCourses(){
    
    SqlSession session = null;
    try {
        session = MyBatisSqlSessionFactory.openSession();
        
        Many2ManyMapper mapper = session.getMapper(Many2ManyMapper.class);
        
        Student student = mapper.getStudentByIdWithCourses(58);
        
        System.out.println(student);
        
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        if(session!=null)session.close();
    }
    
}


注:这是从student这边出发所做的一些操作,从course一边开始操作是一样的,因为俩者的关系是多对多(对称的).
同时不论是一对一还是一对多还是多对多,都不能在mybatis中进行级联保存、更新、删除,我们需要使用sql语句控制每一步操作

你可能感兴趣的:(Mybatis(三))