zoukankan      html  css  js  c++  java
  • Mybatis_day3

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

    3.1 映射器文件和映射器接口
    我们已经看见了一些在映射器配置文件中配置基本的映射语句,以及怎样使用SqlSession对象调用它们的例子。现在让我们看一下在com.briup.mappers包中的StudentMapper.xml 配置文件内,是如何配置id为”findStudentById”的SQL语句的,代码如下:
    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.briup.mappers.StudentMapper">
    <select id="findStudentById" parameterType="int" resultType="Student">
    select stud_id as studId, name, email, dob
    from students where stud_id=#{studId}
    </select>
    </mapper>

    我们可以通过下列代码调用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语句可以在<insert>标签元素在映射器XML配置文件中配置,如下所示:
    <insert id="insertStudent" parameterType="Student">
    INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL, PHONE) VALUES(#{studId},#{name},#{email},#{phone})
    </insert>
    这里我们设置一个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 id="insertStudent" parameterType="Student" useGeneratedKeys="true" keyProperty="studId">
    INSERT INTO STUDENTS(NAME, EMAIL, PHONE) VALUES(#{name},#{email},#{phone})
    </insert>
    这里STUD_ID列值将会被数据库自动生成(如mysql),并且生成的值会被设置到student对象的studId属性上。

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

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

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


    3.2.2 UPDATE 更新语句
    一个UPDATE SQL语句可以在<update>元素在映射器XML配置文件中配置,如下所示:
    <update id="updateStudent" parameterType="Student">
    UPDATE STUDENTS SET NAME=#{name}, EMAIL=#{email}, PHONE=#{phone}
    WHERE STUD_ID=#{studId}
    </update>

    我们可以如下调用此语句:
    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语句可以在<update>元素在映射器XML配置文件中配置,如下所示
    <delete id="deleteStudent" parameterType="int">
    DELETE FROM STUDENTS WHERE STUD_ID=#{id}
    </delete>

    我们可以如下调用此语句:
    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中)配置的,如下所示:
    <select id="findStudentById" parameterType="int"
    resultType="Student">
    SELECT STUD_ID, NAME, EMAIL, PHONE
    FROM STUDENTS
    WHERE STUD_ID=#{studId}
    </select>
    我们可以如下调用此语句:
    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中属性名匹配的别名,如下所示:
    <select id="findStudentById" parameterType="int"
    resultType="Student">
    SELECT STUD_ID AS studId, NAME,EMAIL, PHONE
    FROM STUDENTS
    WHERE STUD_ID=#{studId}
    </select>


    MyBatis执行返回多条结果的SELECT语句查询,如下所示:
    <select id="findAllStudents" resultType="Student">
    SELECT STUD_ID AS studId, NAME, EMAIL, PHONE
    FROM STUDENTS
    </select>

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

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

    使用上述代码,我们可以如下调用
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> 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="StudentResult" type="com.briup.pojo.Student">
    <id property="studId" column="stud_id" />
    <result property="name" column="name" />
    <result property="email" column="email" />
    <result property="phone" column="phone" />
    </resultMap>

    <select id="findAllStudents" resultMap="StudentResult">
    SELECT * FROM STUDENTS
    </select>
    <select id="findStudentById" parameterType="int" resultMap="StudentResult">
    SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}
    </select>

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

    【注意】:
    【resultType和resultMap】二者只能用其一,】不能同时使用】。

    【问题升级1】:
    <select>映射语句中如何将查询【一条】数据填充到Map中?
    <select id="findStudentById" parameterType="int" resultType="map">
    SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}
    </select>
    在上述的<select>语句中,我们将resultType配置成【map,即java.util.HashMap】的别名。在这种情况下,结果集的列名将会作为Map中的key值,而列值将作为Map的value值。

    HashMap<String,Object> studentMap = sqlSession.selectOne("com.briup.mappers.StudentMapper.findStudentById", studId);
    System.out.println("stud_id :"+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"));


    【问题升级2】:
    <select>映射语句中如何将查询【多条】数据填充到Map中?
    <select id="findAllStudents" resultType="map">
    SELECT STUD_ID, NAME, EMAIL, PHONE FROM STUDENTS
    </select>

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

    List<Map<String, Object>> studentMapList = sqlSession.selectList("com.briup.mappers.StudentMapper.findAllStudents");
    for(Map<String, Object> 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:【结果为对象】
    <select id="findAllStudents_student" resultType="Student">
    SELECT STUD_ID AS STUDID,NAME,EMAIL,DOB
    FROM STUDENTS
    </select>
    对应的接口中的方法,你写什么类型的集合,Mybatis就给你返回什么类型的集合,但是要注意使用SortedSet的时候,Student类需要实现Comparable接口,否则是不能进行排序的。
    例如:
    public List<Student> findAllStudents_List();
    或者
    public Set<Student> findAllStudents_Set();
    或者
    public SortedSet<Student> findAllStudents_SortedSet();
    SortedSet是Set的子接口,TreeSet是其实现子类【需要实现比较接口才可以成功】。

    其他实例2:【查询指定列】
    <select id="findAllName_list" resultType="String">
    SELECT NAME
    FROM STUDENTS
    </select>
    对应的接口中的方法: 把查询到所有名字都放到List集合中并返回
    public List<String> findAllName_list();


    其他实例3:【查询组函数】
    <select id="findCount_int" resultType="int">
    SELECT count(*)
    FROM STUDENTS
    </select>
    对应的接口中的方法: 把查询到的这个值直接返回
    public int findCount_int();


    3.3.2 拓展/继承 ResultMap 【后面知识点,暂不介绍】
    (注:这个例子在下面的一对一映射的知识点中进行测试,因为这里需要建立一对一关系的表结构)
    我们可以从从另外一个<resultMap>,【拓展】出一个新的<resultMap>,这样,原先的属性映射可以【继承】过来,以实现:
    <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类中又新增加了一个属性,该属性的类型是Address -->
    <!-- 自定义类Address,类中也有多个属性,同时数据库中ADDRESSES表与其对应 -->
    <resultMap type="Student" id="StudentWithAddressResult" 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>

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

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

    <select id="findStudentById" parameterType="int"
    resultMap="StudentResult">
    SELECT * FROM STUDENTS WHERE STUD_ID=#{stud Id}
    </select>

    如果你想将映射Student数据和Address数据,你可以使用id为StudentWithAddressResult的 resultMap:
    <select id="selectStudentWithAddress" parameterType="int"
    resultMap="StudentWithAddressResult">
    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>
    注:该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 john@gmail.com 123-456-7890 1
    2 Paul paul@gmail.com 111-222-3333 2

    insert into students(stud_id,name,email,phone,addr_id) values(1,'John','john@gmail.com','123-456-7890',1);
    insert into students(stud_id,name,email,phone,addr_id) values(2,'Paul','paul@gmail.com','111-222-3333',2);


    【执行select操作】
    mapper XML:

    <resultMap type="Student" id="StudentWithAddressResult">
    <id property="studId" column="stud_id" />
    <result property="name" column="name" />
    <result property="email" column="email" />
    <result property="dob" column="dob" />
    <result property="phone" column="phone" />
    <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>

    //【多表查询】实现功能
    <select id="findStudentByIdWithAddress" parameterType="int" resultMap="StudentWithAddressResult">
    select stud_id, name, email, dob, 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=#{id}
    </select>
    //注意,有歧义的一定要有【别名】,两个表中都有【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.映射文件:
    <insert id="insertAddress" parameterType="Address">
    <selectKey keyProperty="addrId" resultType="int" order="BEFORE">
    select my_seq.nextval from dual
    </selectKey>
    insert into addresses(addr_id,street,city,state,zip,country)
    values(#{addrId},#{street},#{city},#{state},#{zip},#{country})
    </insert>

    <insert id="insertStudent" parameterType="Student">
    <selectKey keyProperty="studId" resultType="int" order="BEFORE">
    select my_seq.nextval from dual
    </selectKey>
    insert into students(stud_id,name,email,phone,dob,addr_id)
    values(#{studId},#{name},#{email},#{phone},#{dob},#{address.addrId})
    </insert>

    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信息。

    映射文件 【映射接口和测试代码 使用之前的即可】
    <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>

    <resultMap type="Student" id="StudentWithAddressResult">
    <id property="studId" column="stud_id" />
    <result property="name" column="name" />
    <result property="email" column="email" />
    <result property="dob" column="dob" />
    <result property="phone" column="phone" />
    <!-- 【关联】的意思 -->
    <association property="address" resultMap="AddressResult" />
    </resultMap>

    <select id="findStudentByIdWithAddress" parameterType="int" resultMap="StudentWithAddressResult">
    select stud_id, name, email, dob, 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>

    【等值连接】
    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是关联】的意思
    元素<association>被用来导入“有一个”(has-one)类型的关联。在上述的例子中,我们使用了<association>元素引用了另外的在同一个XML文件中定义的<resultMap>。


    同时我们也可以使用<association> 定义【内联的resultMap】,代码如下所示:
    <resultMap type="Student" id="StudentWithAddressResult">
    <id property="studId" column="stud_id" />
    <result property="name" column="name" />
    <result property="email" column="email" />
    <result property="dob" column="dob" />
    <result property="phone" column="phone" />
    <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>


    3.4.2 使用【嵌套查询select】实现一对一关系映射
    嵌套查询本质:
    一个select语句 转化成 多条select语句去实现功能。
    我们可以通过使用嵌套select查询来获取Student及其Address信息,代码如下:
    1.【先根据addr_id去查找 地址对象】
    <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="findAddressById" parameterType="int" resultMap="AddressResult">
    select * from addresses where addr_id=#{id}
    </select>

    2.【根据学号 查询 学生对象】
    <resultMap type="Student" id="StudentWithAddress">
    <id property="studId" column="stud_id" />
    <result property="name" column="name" />
    <result property="email" column="email" />
    <result property="dob" column="dob" />
    <result property="phone" column="phone" />
    <!-- 【嵌套查询实现】 -->
    <association property="address" column="addr_id" select="findAddressById" />
    </resultMap>

    <select id="findStudentByIdWithAddress" parameterType="int" resultMap="StudentWithAddress">
    select * from students where stud_id=#{id}
    </select>

    在此方式中,<association>元素的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 zs@briup.com 123-456-7890 1
    2 ls ls@briup.com 111-222-3333 2

    插入数据:
    insert into tutors(tutor_id,name,email,phone,addr_id)
    values(1,'zs','zs@briup.com','123-456-7890',1);
    insert into tutors(tutor_id,name,email,phone,addr_id)
    values(2,'ls','ls@briup.com','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<Course> courses;

    get/set
    }

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

    get/set
    }

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

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

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

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

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

    代码如下:
    <!-- 只要遇到Address对象,就按照AddressResult封装 -->
    <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>

    <!-- 只要遇到Course对象,就按照CourseResult封装 -->
    <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>

    <!-- 只要遇到Tutor类型对象,就按照TutorResult封装 -->
    <resultMap type="Tutor" id="TutorResult">
    <id column="tutor_id" property="tutorId" />
    <result column="name" property="name" />
    <result column="email" property="email" />
    <result column="phone" property="phone" />
    <!-- association表一对一关系,address属性用AddressResult处理 -->
    <association property="address" resultMap="AddressResult" />
    <!-- collection表一对多关系,遇到courses集合成员 -->
    <collection property="courses" resultMap="CourseResult" />
    </resultMap>

    <!-- 以下为【select语句】通过【左外连接】实现 -->
    <select id="findTutorById" parameterType="int" resultMap="TutorResult">
    select t.tutor_id, t.name, t.email,t.phone, c.course_id, c.name, description, start_date, end_date , a.addr_id, a.street, a.state, a.zip, a.country
    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>

    select语句也可以通过【多表查询】 【等值连接】实现
    <select id="findTutorById" parameterType="int" resultMap="TutorResult">
    select t.tutor_id,t.name,t.email,t.phone,
    a.addr_id,a.street,a.city,a.state,a.zip,a.country,
    c.course_id,c.name,c.description,c.start_date,c.end_date
    from tutors t , addresses a, courses c
    where t.addr_id=a.addr_id
    and c.tutor_id=t.tutor_id
    and t.tutor_id=#{id}
    </select>

    【注意】
    此时查询出来的结果中,课程name值和讲师name值相同,原因是select中有多个name,产生了歧义,可以使用别名解决,即c.name as 【cname】。

    这里我们使用了一个简单的使用了JOINS连接的Select语句获取讲师及其所教课程信息。<collection>元素的resultMap属性设置成了CourseResult,CourseResult包含了Course对象属性与表列名之间的映射。
    要查询到Address相关信息,按照上面一对一的方式,在配置中加入<association>。

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

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

    第四步:在映射文件One2MoreMapper.xml文件中添加如下代码
    <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>
    <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>


    <resultMap type="Tutor" id="TutorResult">
    <id column="tutor_id" property="tutorId" />
    <result column="tutor_name" property="name" />
    <result column="email" property="email" />
    <result column="phone" property="phone" />
    <!--根据表中addr_id去findAddressById,获得address成员信息 -->
    <association property="address" column="addr_id" select="findAddressById"></association>
    <!-- 【第3执行子句】,把当前tutor_id表中列的值当做参数传递给findCoursesByTutor去查询,得到的结果封装到Tutor类中的courses属性中 -->
    <collection property="courses" column="tutor_id" select="findCoursesByTutor" />
    </resultMap>

    <!-- 此处是select主句,【第1执行】 -->
    <select id="findTutorById" parameterType="int" resultMap="TutorResult">
    select *
    from tutors
    where tutor_id=#{tutor_id}
    </select>
    <!-- 此处是select子句,【第2执行】-->
    <select id="findAddressById" parameterType="int" resultMap="AddressResult">
    select *
    from addresses
    where addr_id = #{addr_id}
    </select>
    <!-- 此处是select子句,【第3执行】-->
    <select id="findCoursesByTutor" parameterType="int" resultMap="CourseResult">
    select *
    from courses
    where tutor_id=#{tutor_id}
    </select>


    注意:
    在这种方式中,<aossication>元素的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<Course> 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<Student> students;// 选课学生
    get/set
    }
    public class Student {
    private Integer id;
    private String name; // 姓名
    private String gender; // 性别
    private String major; // 专业
    private String grade; // 年级
    private List<Course> 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<Student> getStudentByIdOnCondition(Integer id);
    //查询student级联查询出所选的course并且组装成完整的对象
    public Student getStudentByIdWithCourses(Integer id);
    }

    第四步:在映射文件Many2ManyMapper.xml中进行配置
    a.插入学生、课程信息【基本操作】
    <insert id="insertStudent" parameterType="Student">
    <selectKey keyProperty="id" resultType="int" order="BEFORE">
    select my_seq.nextval from dual
    </selectKey>
    insert into student(id,name,gender,major,grade)
    values(#{id},#{name},#{gender},#{major},#{grade})
    </insert>

    <insert id="insertCourse" parameterType="Course">
    <selectKey keyProperty="id" resultType="int" order="BEFORE">
    select my_seq.nextval from dual
    </selectKey>
    insert into course(id,course_code,course_name)
    values(#{id},#{courseCode},#{courseName})
    </insert>

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

    //插入课程
    mapper.insertCourse(new Course("002","Oracle"));
    session.commit();

    *****************************************

    c.在Many2ManyMapper.xml中配置查询操作
    <select id="getStudentById" parameterType="int" resultType="Student">
    select id,name,gender,major,grade
    from student
    where id=#{id}
    </select>

    <select id="getCourseById" parameterType="int" resultType="Course">
    select id,course_code as courseCode,course_name as courseName
    from course
    where id=#{id}
    </select>

    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配置文件
    <!-- param1代表方法中第一个参数 以此类推 -->
    <insert id="studentSelectCourse">
    insert into student_course(id,student_id,course_id)
    values(my_seq.nextval,#{param1.id},#{param2.id})
    </insert>

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

    ************************************

    c. 根据条件查找学生信息
    <!-- 【注意】如果有特殊符号的话 需要用 <![CDATA[ 特殊符号 ]]> 例如 < & 等等 -->
    <select id="getStudentByIdOnCondition" parameterType="int" resultType="Student">
    select *
    from student
    where id <![CDATA[ < ]]> #{id}
    </select>
    d. 添加测试代码
    Many2ManyMapper mapper = session.getMapper(Many2ManyMapper.class);
    List<Student> list = mapper.getStudentByIdOnCondition(30);
    for (Student student : list) {
    System.out.println(student);
    }

    *************************
    第六步:核心功能,查询student级联查询出所选的course并且组装成完整的对象
    a.封装基本Student查询结果,不包含Course
    <!--
    这里使用了嵌套结果ResultMap的方式进行级联查询
    当然也可以使用嵌套查询select
    -->
    <!-- 映射一个基本的Student查询结果 -->
    <resultMap id="StudentResult" type="Student">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="gender" column="gender"/>
    <result property="major" column="major"/>
    <result property="grade" column="grade"/>
    </resultMap>
    b.在以上ResultMap基础上,封装完整Student查询结果
    <!-- 继承上面那个基本的映射,再扩展出级联查询 -->
    <resultMap id="StudentResultWithCourses" type="Student" extends="StudentResult">
    <collection property="courses" resultMap="CourseResult"></collection>
    </resultMap>
    c.单独再封装遇到的Course对象为CourseResult
    <!-- 这里特别要是的是column="cid" 这是和select语句中的 c.id as cid对应的 一定一定一定要对应起来 -->
    <resultMap id="CourseResult" type="Course">
    <id property="id" column="cid"/>
    <result property="courseCode" column="course_code"/>
    <result property="courseName" column="course_name"/>
    </resultMap>
    d.书写select查询语句
    <!--
    注意:查询语句的中的c.id as cid这个地方,避免名字相同出现查询结果不正确的情况
    同时在id="CourseResult"的resultMap中也有与这里对应的设置要特别特别注意
    -->
    <select id="getStudentByIdWithCourses" parameterType="int" resultMap="StudentResultWithCourses">
    select s.id,s.name,s.gender,s.major,s.grade,c.id as cid,c.course_code,c.course_name,sc.id,sc.student_id,sc.course_id
    from student s,course c,student_course sc
    where s.id=#{id}
    and s.id=sc.student_id
    and sc.course_id=c.id
    </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<Student> 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语句控制每一步操作

  • 相关阅读:
    Catch That Cow POJ 3278(BFS)
    python的各种推导式(列表推导式、字典推导式、集合推导式)
    贝叶斯神经网络
    浅谈贝叶斯
    置换检验
    Python的基本用法
    字符串和编码
    开启新篇章
    无偏博弈类问题
    PAT1103
  • 原文地址:https://www.cnblogs.com/Diyo/p/11453258.html
Copyright © 2011-2022 走看看