mybatis handles one-to-many, many-to-one, many-to-many relationships, annotations and xml file writing

Zero, Preface

This article is used to study one-to-many, many-to-one, and many-to-many relationships between tables and objects.

Two methods of xml file configuration and annotation configuration were studied

Super complete~

The entities used for research are students, teachers, courses

Note: The relationship between students and teachers should be many-to-many. This article uses many-to-one, that is, multiple students correspond to one teacher, and one teacher corresponds to multiple students. There is a many-to-many relationship between students and courses.

1. Preparation

1. Create a maven project and add dependencies

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>day10-2</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>

            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.23</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>

        <!-- log4j log -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>
</project>

2. Create the mybatis-config configuration file in the resources folder

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <properties resource="jdbc.properties"/>
    <settings>
        <!--Map underscore to camel case student_id -> studentId -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!--Enable lazy loading-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--Load on demand-->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
     
    <typeAliases>
        <package name="com.wtp.pojo"/>
    </typeAliases>


    <environments default="development">

        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>


    <mappers>
        <!--Note that the interface name of the mapper interface must be the same as the name of the mapper.xml file and must be in the same folder before you can directly import the package -->
        <package name="com.wtp.mapper"/>
    </mappers>
</configuration>

3. Create jdbc.properties in the resources folder and introduce the log4j.xml file

jdbc.properties:

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/xsgl3?serverTimezone=UTC
jdbc.username=root
jdbc.password=123456

log4j.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
        <param name="Encoding" value="UTF-8" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \\
" />
        </layout>
    </appender>
    <logger name="java.sql">
        <level value="debug" />
    </logger>
    <logger name="org.apache.ibatis">
        <level value="info" />
    </logger>
    <root>
        <level value="debug" />
        <appender-ref ref="STDOUT" />
    </root>
</log4j:configuration>

4. Create t_student table and t_teacher table

When establishing a data table, for many-to-one or one-to-many relationships, the primary key of the multi-party entity should be added to the primary key of the single-party entity, so the created t_student table must have the field teacher_id as a foreign key to connect the two tables.

5. Create t_lesson table and student_lesson table

When establishing a multi-pair relationship, the primary keys of the two multi-party entities should be added to the intermediate table to facilitate three-table queries.

6. Create entity classes Student, Teacher, Lesson

package com.wtp.pojo;

import lombok.*;

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {

    private int studentId;
    private String studentName;
    private int age;
    //Multiple students to one teacher
    private teacher teacher;

    //A student chooses multiple courses. There are multiple students in one course.
    private List<Lesson> lessons;


}

package com.wtp.pojo;

import lombok.*;

import java.util.List;


@Data
@NoArgsConstructor
@AllArgsConstructor
public class Teacher {

    private Integer teacherId;
    private String teacherName;
    private List<Student> students;


}

package com.wtp.pojo;


import lombok.*;

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Lesson {

    private Integer lessonId;
    private String lessonName;

    //A course is selected by multiple students
    private List<Student> students;
}

7. Create three interfaces StudentMapper, TeacherMapper, and StudentAndLessonMapper and corresponding mapping files

2. Attribute mapping

Whether it is one-to-many, many-to-one, or many-to-many, the queried entities need to be mapped when encapsulated into Java objects.

Look at a simple query:

 <!--Student getStudentById(@Param("studentId") Integer studentId);-->
    <select id="getStudentById" resultType="student">
        select * from t_student where student_id = #{studentId}
    </select>

operation result:

You can see that the teacher attribute among the queried students is null. Why is this attribute null?

In the process of learning jdbc, we should have learned about obtaining the number of columns in the result set through the result set, and then looping through the result set to obtain the field names of the found data, such as:

After obtaining the field name, you can create an object through reflection and assign values to the object’s attributes. If the queried field name is consistent with the object’s attribute name, then you can obtain the attribute and assign a value to it. If not, you cannot assign a value. The value is null

What is also used in mybatis is to obtain attribute assignments through reflection, so the mapping between entity attributes and fields in the table is required.

There is such a global configuration in the mybatis-config configuration file:

Able to map database naming rules (_ in the middle) to object attribute naming rules (camel case)

Note: There is no teacher attribute in the data table, so the teacher attribute in the student needs to define its own mapping and use the tag to customize it, where the tag writes the primary key in the data table. Write other attributes in the tag. The attributes column in the and tags correspond to fields in the data table, and property corresponds to attributes in the object. CamelCase mapping in global configuration fails when using custom mapping.

3. Research on one-to-many and many-to-one relationships

1. Many to one

If we want to query the information about students and their corresponding teachers based on student IDs, we need to map the teacher attribute. There are three methods:

(1) Cascade assignment

<!--Cascading assignment processing mapping-->
    <resultMap id="map1" type="student">
        <id column="student_id" property="studentId"/>
        <result column="student_name" property="studentName"/>
        <result column="age" property="age"/>
        <result column="teacher_id" property="teacher.teacherId"/>
        <result column="teacher_name" property="teacher.teacherName"/>
    </resultMap>
    <!--Student getStudentAndTeacher(@Param("studentId") Integer studentId);-->
    <select id="getStudentAndTeacher1" resultMap="map1">
        select * from t_students
        left join t_teacher t
        on s.teacher_id = t.teacher_id
        where student_id = #{studentId}
    </select>

Customize mapping map1 in the configuration file. The return type is student. Map the queried data to the properties of the object. The processing method for the teacher property is to map the corresponding fields to the teacher.teacherId and teacher.teacherName properties.

(2)association

<!--association processing mapping-->
    <resultMap id="map2" type="student">
        <id column="student_id" property="studentId"/>
        <result column="student_name" property="studentName"/>
        <result column="age" property="age"/>
        <association property="teacher" javaType="teacher" >
            <id column="teacher_id" property="teacherId" />
            <result column="teacher_name" property="teacherName" />
        </association>
    </resultMap>
    <!--Student getStudentAndTeacher2(@Param("studentId") Integer studentId);-->
    <select id="getStudentAndTeacher2" resultMap="map2">
        select * from t_students
        left join t_teacher t
        on s.teacher_id = t.teacher_id
        where student_id = #{studentId}
    </select>

When dealing with teachers, it is different from but similar to the cascade. The association tag maps the teacher attribute, and javaType is the type of the mapped attribute (here is the alias).

(3) Step-by-step query

Step-by-step query The first step of querying students based on student ID is written in the configuration file corresponding to the StudentMapper interface:

<!--Step-by-step query-->
    <resultMap id="map3" type="student">
        <id column="student_id" property="studentId"/>
        <result column="student_name" property="studentName"/>
        <result column="age" property="age"/>

        <association property="teacher" fetchType="lazy"
                     select="com.wtp.mapper.TeacherMapper.getStudentAndTeacherStep2"
                     column="teacher_id"
        />
    </resultMap>
    <!--Student getStudentAndTeacherStep1(@Param("studentId") Integer studentId);-->
    <select id="getStudentAndTeacherStep1" resultMap="map3">
        select * from t_student where student_id = #{studentId}
    </select>

The second step of step-by-step query is to query the teacher based on the teacher_id condition after the first step of query, and write it in the configuration file corresponding to the TeacherMapper interface:

<!--Teacher getStudentAndTeacherStep2(@Param("teacherId") Integer teacherId);-->
    <select id="getStudentAndTeacherStep2" resultType="teacher">
        select * from t_teacher where teacher_id = #{teacherId}
    </select>

Step-by-step query also uses the association tag. Use the select attribute in the tag to select the full class name and method name of the next sql to be executed. Column is the query condition for the next sql.

Step-by-step query splits complex sql into simple sql for step-by-step execution. This has the advantage of lazy loading.

Lazy loading

When executing the SQL of step-by-step query, if the data found in the second step is not used, then only the SQL of the first step will be executed, reducing memory consumption.

If only the student ID is used before opening:

After opening:

After turning on lazy loading, if the data queried in the second step is not used, only one sql will be executed.

How to enable lazy loading
<!--Enable lazy loading-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--Load on demand-->
<setting name="aggressiveLazyLoading" value="false"/>

1. Global configuration in the mybatis-config file:

The lazy loading function is affected by two properties, lazyLoadingEnabled, aggressiveLazyLoading

lazyLoadingEnabled is set to true to enable lazy loading

The aggressiveLazyLoading attribute defaults to false. When set to true, two sql statements will be executed regardless of whether the data in the second query is used.

2. In the fetchType attribute in the step-by-step tag association and collection, set it to lazy to represent delayed loading, and set it to eager to represent immediate loading.

How to write comments

Annotations can also be divided into step-by-step and non-step-by-step

Non-step-by-step:

@Results(id = "studentMap",value = {
            @Result(column="student_id", property="studentId"),
            @Result(column="student_name", property="studentName"),
            @Result(column="age", property="age"),
            @Result(column="teacher_id", property="teacher.teacherId"),
            @Result(column="teacher_name", property="teacher.teacherName")

    })
    @Select("select * from t_student s\\
" +
            " left join t_teacher t\\
" +
            " on s.teacher_id = t.teacher_id\\
" +
            " where student_id = #{studentId}")
    Student getStudentAndTeacher3(@Param("studentId") Integer studentId);

Step by step:

@Results(id = "studentMap",value = {
            @Result(column="student_id", property="studentId"),
            @Result(column="student_name", property="studentName"),
            @Result(column="age", property="age"),
            @Result(property="teacher" , column = "teacher_id",
                    one = @One(select = "com.wtp.mapper.TeacherMapper.getStudentAndTeacherStep2"))
    })
    @Select("select * from t_student where student_id = #{studentId}")
    //Distributed query of students and teachers. The first step is to query students.
    Student getStudentAndTeacherStep1(@Param("studentId") Integer studentId);
//Distributed query of students and teachers. The second step is to query the teachers associated with the students.
    @Select("select * from t_teacher where teacher_id = #{teacherId}")
    Teacher getStudentAndTeacherStep2(@Param("teacherId") Integer teacherId);

Among them, one = @One can be replaced by many=@many. The underlying source code of these two annotations is the same, but they have the same function and different flags. They are equivalent to @Service identifying the Service layer and @Controller identifying the Controller layer. The functions are to create objects and add them to the IOC container. The functions of these two annotations point to the next step of sql

2. One-to-many

Query a piece of teacher information based on the teacher ID. The teacher information corresponds to a collection of students, and it is necessary to map the student collection.

(1)collection

<resultMap id="map1" type="Teacher">
        <id column="teacher_id" property="teacherId"/>
        <result column="teacher_name" property="teacherName"/>
        <collection property="students" ofType="student">
            <id property="studentId" column="student_id"/>
            <result property="studentName" column="student_name"/>
            <result property="age" column="age"/>
        </collection>
    </resultMap>
    <!--Teacher getTeacherAndStudentById1(@Param("teacherId") Integer teacherId);-->
    <select id="getTeacherAndStudentById1" resultMap="map1">
        select * from t_teacher t
        left join t_student s
        on t.teacher_id = s.teacher_id
        where t.teacher_id = #{teacherId}
    </select>

Collection is the parent interface of the collection type. This tag can handle the attributes of the collection type. In fact, association can also be replaced by the collection tag, which is equivalent to having only one element in the collection, but the collection tag cannot be replaced by the association tag.

(2) Step-by-step query

Written in the configuration file corresponding to the TeacherMapper interface, the first step is to query the teacher based on the teacher ID:

 <!--Step-by-step query-->
    <resultMap id="stepMap" type="teacher">
        <id column="teacher_id" property="teacherId"/>
        <result column="teacher_name" property="teacherName"/>
        <collection property="students"
                    select="com.wtp.mapper.StudentMapper.getTeacherAndStudentStep2"
                    column="teacher_id"
        />
    </resultMap>
    <!--Teacher getTeacherAndStudentStep1(@Param("teacherId") Integer teacherId);-->
    <select id="getTeacherAndStudentStep1" resultMap="stepMap" >
        select * from t_teacher where teacher_id = #{teacherId}
    </select>

The second step is written in the file corresponding to the StudentMapper interface to find the corresponding student based on the passed teacher ID.

<!--Student getTeacherAndStudentStep2(@Param("teacherId") Integer teacherId);-->
    <select id="getTeacherAndStudentStep2" resultType="student">
        select * from t_student where teacher_id = #{teacherId}
    </select>

Annotations

Step by step: TeacherMapper:

@Results(id = "teacherMap",value = {
            @Result(column="teacher_id", property="teacherId"),
            @Result(column="teacher_name", property="teacherName"),
            @Result(property="students" ,column = "teacher_id",
                    many = @Many(select = "com.wtp.mapper.StudentMapper.getTeacherAndStudentStep2"))
    })
    @Select("select * from t_teacher where teacher_id = #{teacherId}")
    //Query teachers and students step by step. The first step is to query teachers.
    Teacher getTeacherAndStudentStep1(@Param("teacherId") Integer teacherId);

StudentMapper:

@Select("select * from t_student where teacher_id = #{teacherId}")
    //Query teachers and students step by step. The second step is to query the students associated with the teacher.
    Student getTeacherAndStudentStep2(@Param("teacherId") Integer teacherId);

3. Many-to-many

From the perspective of one-party query, it is all one-to-many. One-to-many query has two methods: collection and step-by-step query. Only the sql statement becomes a three-table joint query

xml file configuration:

<?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.wtp.mapper.StudentAndLessonMapper">

    <resultMap id="studentMap" type="student">
        <id column="student_id" property="studentId"/>
        <result column="student_name" property="studentName"/>
        <result column="age" property="age"/>
        <collection property="lessons" ofType="lesson">
            <id property="lessonId" column="lesson_id"/>
            <result property="lessonName" column="lesson_name"/>
        </collection>
    </resultMap>
    <!--Query the corresponding course based on studentId-->
    <!--Student getStudentById(@Param("studentId") Integer studentId);-->
    <select id="getStudentById" resultMap="studentMap">
        select * from t_student
        left join student_lesson
        on t_student.student_id = student_lesson.student_id
        left join t_lesson
        on student_lesson.lesson_id = t_lesson.lesson_id
        where t_student.student_id = #{studentId}
    </select>


    <resultMap id="lessonMap" type="lesson">
        <id column="lesson_id" property="lessonId"/>
        <result column="lesson_name" property="lessonName"/>
        <collection property="students" ofType="student">
            <id property="studentId" column="student_id"/>
            <result property="studentName" column="student_name"/>
            <result property="age" column="age"/>
        </collection>
    </resultMap>
    <!--Query corresponding students based on lessonId-->
    <!--Student getLessonById(@Param("lessonId") Integer lessonId);-->
    <select id="getLessonById" resultMap="lessonMap">
        select * from t_lesson
        left join student_lesson
        on t_lesson.lesson_id = student_lesson.lesson_id
        left join t_student
        on student_lesson.student_id = t_student.student_id
        where t_lesson.lesson_id = #{lessonId}
    </select>
</mapper>

Methods in the interface and annotation queries in the interface:

package com.wtp.mapper;

import com.wtp.pojo.Lesson;
import com.wtp.pojo.Student;
import org.apache.ibatis.annotations.*;

import java.util.Collection;
import java.util.List;
import java.util.Set;

public interface StudentAndLessonMapper {

    //Query students and courses based on studentId
    Student getStudentById(@Param("studentId") Integer studentId);

    //Query courses and selected students based on lessonId
    Lesson getLessonById(@Param("lessonId") Integer lessonId);

    //Step-by-step query in annotation mode
    @Results(id = "studentMap2",value = {
            @Result(column="student_id", property="studentId"),
            @Result(column="student_name", property="studentName"),
            @Result(column="age", property="age"),
            @Result(property="lessons" ,column = "student_id",
                    many = @Many(select = "getStudentByIdStep2"))
    })
    @Select("select * from t_student where student_id = #{studentId}")
    Student getStudentByIdStep1(@Param("studentId") Integer studentId);

    //Check the courses in the intermediate table based on the student ID
    @Select("select * from student_lesson sl left join t_lesson l " +
            "on sl.lesson_id = l.lesson_id " +
            " where sl.student_id = #{studentId}")
    Lesson getStudentByIdStep2(@Param("studentId") Integer studentId);

}