MyBatis 简明教程

简介

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

入门案例

下面列举一个简单例子,来阐述 MyBatis 的常用操作。

例子:假设数据库school内部有一张表students,我们希望能使用 MyBatis 来实现对student表的增删改查操作。

具体步骤如下:

  • 首先,我们先创建数据库school和表students,并插入一些预备数据:
# 创建数据库 school
create database if not exists school;
# 使用数据库 school
use school;
# 创建表 student
create table if not exists students (
    -> id int primary key auto_increment,
    -> name varchar(20) not null,
    -> sex varchar(6) default "male",
    -> cls int not null);
# 插入一些数据
insert into students(id,name,sex,cls) values(1,"小明","male",1);
insert into students values(2,"小红","female",2);
insert into students(name,sex,cls) values("小芳","female",2);
insert into students(name,cls) values("小宁",4);

经过上面操作后,students表就存储了如下内容:

students
  • 创建一个使用 Maven 作为依赖管理的普通 Java 工程,如下图所示:
  • 在 pom.xml 中添加依赖:必须添加的是 MyBatis 依赖和 mysql-connector 驱动依赖,同时这里我们也添加一个 log4j 依赖,用以配置日志输出:
    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>

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

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

然后在主工程resources资源目录下,创建一个配置文件:log4j.properties,进行日志信息配置:

# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
  • 接下来,继续在主工程resources目录下创建一个全局的 MyBatis 配置文件,该文件可任意命名,我们这里将其命名为:MyBatisConfig.xml,具体内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://www.mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 配置环境:和spring整合后 environments配置将废除-->
    <environments default="development">
        <!-- 配置mysql环境 -->
        <environment id="development">
            <!-- 配置事务:使用jdbc事务管理-->
            <transactionManager type="JDBC"/>
            <!-- 数据库连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/school?serverTimezone=GMT"/>
                <!--注意:这里必须是username,一定要规范-->
                <property name="username" value="root"/>
                <property name="password" value="password"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

可以看到,上述全局配置表主要完成的是对开发环境和 MySql 连接信息的配置。

  • 要想使用 MyBatis 完成对具体某一张表的操作,我们还需要为这张表的相关操作创建相应接口和配置文件,并在全局配置表中将相关配置文件具体内容进行注入。具体操作如下:

1)创建表对应的 POJO 类:

package com.yn.bean;

import java.io.Serializable;

/**
 * @author Whyn
 * @date 7/16/2019 5:37 PM
 */
public class Student implements Serializable {
    private String name;
    private String sex;
    private int cls;

/**
 * 
 *
 * 必须创建一个默认构造函数,否则就需要构造一个映射器,比如
 *     <resultMap id="studentResultMap" type="com.yn.bean.Student">
 *        <constructor>
 *            <arg column="name" javaType="String" />
 *             <arg column="sex" javaType="String" />
 *             <arg column="cls" javaType="_int" />
 *         </constructor>
 *     </resultMap>
  */
    public Student(){

    }
    public Student(String name, String sex, int cls) {
        this.name = name;
        this.sex = sex;
        this.cls = cls;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getCls() {
        return cls;
    }

    public void setCls(int cls) {
        this.cls = cls;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", cls=" + cls +
                '}';
    }
}

2)创建对表操作的相应接口:

package com.yn.interfaces;

import com.yn.bean.Student;

import java.util.List;

public interface IStudentsDao {
    //    增:添加学生
    void add(Student student);

    //    删:删除学生
    void delete(String name);

    //    改:更改信息
    void update(String name);

    //    查:查询所有
    List<Student> selectAll();
}

3)在主工程资源目录resources下,创建对表操作的配置信息(对于每一个 Dao,都创建对应包名目录的配置文件,比如:com.yn.interfaces.IStudentsDao,则创建对应配置文件:com/yn/interfaces/IStudentsDao.xml):

<?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.yn.interfaces.IStudentsDao">

    <insert id="add" parameterType="com.yn.bean.Student">
        insert into students(name,sex,cls) values(#{name},#{sex},#{cls})
    </insert>

    <delete id="delete" parameterType="String">
        delete from students where name=#{name}
    </delete>

    <update id="update" parameterType="com.yn.bean.Student">
        update students set sex=#{sex},cls=#{cls} where name=#{name}
    </update>

    <select id="selectAll" resultType="com.yn.bean.Student">
        select * from students
    </select>
</mapper>

<mapper>标签中的namespace表示表对应接口类,各个数据库操作的属性id表示接口类对应的方法名。因此,由namespace + id就可以唯一确定对应的操作方法。

4)创建完对表的操作配置文件后,还需在全局配置文件中注入该配置文件,因此,全局配置表完整内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://www.mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 和spring整合后 environments配置将废除-->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事务管理-->
            <transactionManager type="JDBC"/>
            <!-- 数据库连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/school?serverTimezone=GMT"/>
                <!--注意:这里必须是username,一定要规范-->
                <property name="username" value="root"/>
                <property name="password" value="password"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 注入各个具体配置表 -->
    <mappers>
        <!-- 指明具体配置表路径 -->
        <mapper resource="com/yn/interfaces/IStudentsDao.xml"/>
    </mappers>
</configuration>

5)最后,就可以通过代码对数据库表进行操作,代码如下所示:

public class Main {

    public static void main(String[] args) throws IOException {
//        加载全局配置文件
        InputStream config = Resources.getResourceAsStream("MyBatisConfig.xml");
//        创建工厂类
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config);
//        由工厂创建得到操作数据库表的 SqlSession 对象
        SqlSession session = factory.openSession();
//        得到表操作代理对象
        IStudentsDao studentsDaoProxy = session.getMapper(IStudentsDao.class);
//        增:添加学生
        Student student = new Student("Whyn", "male", 7);
        studentsDaoProxy.add(student);
        session.commit();

//        删:删除学生
        studentsDaoProxy.delete("Whyn");
        session.commit();

//        改:更改信息
        student = new Student("小明", "male", 8);
        studentsDaoProxy.update(student);
        session.commit();
//        查:查询所有
        List<Student> students = studentsDaoProxy.selectAll();
//        释放资源
        session.close();
        config.close();
    }
}

:此处我们使用的是 MyBatis 推荐的基于动态代理生成IUserDao代理实例进行数据库操作,当然也可以自己通过实现具体的IStudentsDao实例对数据库进行操作,其原理就是在具体实现类内部创建SqlSession实例进而操作数据库,具体做法此处就不进行展开了。

以上,就是使用 MyBatis 对数据库进行操作的基本操作过程了。

全局配置文件

下面对 MyBatis 的全局配置文件(即:MyBatisConfig.xml)中的常用配置选项进行讲解:

:以下所讲解的属性配置均位于大标签<configuration>内部。

  • properties:这些属性都是外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过properties元素的子元素来传递。例如:
<properties>
    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/school?serverTimezone=GMT"/>
    <property name="username" value="root"/>
    <property name="password" value="password"/>
</properties>

然后其中的属性就可以在整个配置文件中被用来替换需要动态配置的属性值。比如:

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

但是,properties更常用的方式是读取外部配置文件,通过属性resource/url指定外部配置文件即可,具体操作如下:
1)在resources目录下,创建一个配置文件:db.properties,内容如下:

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/school?serverTimezone=GMT
jdbc.username=root
jdbc.password=password

2)然后在全局配置文件(MyBatisConfig.xml)中进行导入并配置:

<configuration>
<!--    导入外部配置文件-->
    <properties resource="db.properties" />

    <environments default="development">
        <environment id="development">
            ...
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <!--注意:这里必须是username,一定要规范-->
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>

        </environment>
    </environments>
    ...
</configuration>

:如果同一个属性property在多个地方都定义了,那么其读取顺序为:标签properties内部指定的属性 > resource/url 属性中指定的配置文件 > 方法参数。因此,方法参数的优先级最高。

  • typeAliases:类型别名是为 Java 类型设置一个短的名字。 它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。例如:
<typeAliases>
<!-- type指定类全限定名,alias指定别名,且别名不区分大小写-->
    <typeAlias type="com.yn.bean.Student" alias="student" />
</typeAliases>

另一种更简便的配置别名的方法是使用标签packageMyBatis 会自动搜索package标签指定包名下面的所有 Java Bean,并将别名设置为其类名:

<typeAliases>
<!-- 类名即为别名,且不区分大小写-->
    <package name="com.yn.bean"/>
</typeAliases>

:使用package标签后,如果不想使用类名作为别名,则可以在使用@Alias注解自定义别名:

@Alias("student")
public class Student{
    ...
}

最后,就可以在任何使用com.yn.bean.Student的配置文件中,更改为使用别名:

<mapper namespace="com.yn.interfaces.IStudentsDao">

    <insert id="add" parameterType="student">
        insert into students(name,sex,cls) values(#{name},#{sex},#{cls})
    </insert>

    <update id="update" parameterType="Student">
        update students set sex=#{sex},cls=#{cls} where name=#{name}
    </update>

    <select id="selectAll" resultType="student">
        select * from students
    </select>

</mapper>

MyBatis 中已经为我们内置了许多 Java 类型的别名,具体如下表所示:

Alias Mapped Type
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator
  • mappers:映射器配置文件内部定义了 SQL 映射语句,因此我们需要将这些映射文件告诉 MyBatis,可以使用相对于类路径的资源引用, 或完全限定资源定位符(包括 file:/// 的 URL),或类名和包名等。例如:
<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

映射器配置文件

下面对 MyBatis 的配置 SQL 映射语句的映射文件(即:IStudentsDao.xml)中的常用配置选项进行讲解:

  • select: 映射查询语句,如下所示:
<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>

这个语句被称作selectPerson,接受一个int(或Integer)类型的参数,并返回一个HashMap类型的对象,其中的键是列名,值便是结果行中的对应值。

<select>标签的属性及其作用如下表所示:

属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置(unset)。
resultType 从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。可以使用 resultTyperesultMap,但不能同时使用。
resultMap 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。可以使用 resultMapresultType,但不能同时使用。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false
useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
fetchSize 这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)。
statementType STATEMENTPREPAREDCALLABLE 中的一个。这会让 MyBatis 分别使用StatementPreparedStatementCallableStatement,默认值:PREPARED
resultSetType FORWARD_ONLYSCROLL_SENSITIVE, SCROLL_INSENSITIVEDEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖驱动)。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。
resultOrdered 这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。 这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false
resultSets 这个设置仅对多结果集的情况适用。它将列出语句执行后返回的结果集并给每个结果集一个名称,名称是逗号分隔的。
<insert
  id="insertAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  keyProperty=""
  keyColumn=""
  useGeneratedKeys=""
  timeout="20">

<update
  id="updateAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

<delete
  id="deleteAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

<insert><update><delete>标签的属性及其作用如下表所示:

属性 描述
id 命名空间中的唯一标识符,可被用来代表这条语句。
parameterType 将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器推断出具体传入语句的参数,默认值为未设置(unset)。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:true(对于 insert、update 和 delete 语句)。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
statementType STATEMENTPREPAREDCALLABLE 的一个。这会让 MyBatis 分别使用 StatementPreparedStatementCallableStatement,默认值:PREPARED
useGeneratedKeys (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。
keyProperty (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认值:未设置(unset)。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望使用多个生成的列,也可以设置为逗号分隔的属性名称列表。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。

动态 SQL

MyBatis 为我们提供了一些标签,方便我们进行 SQL 的动态拼接(动态 SQL)。

具体的标签及其功能如下:

  • if:动态 SQL 最常用处就是作为 where 子句的条件判断。比如:
// IStudentsDao.java
public interface IStudentsDao {
    ...
    List<Student> findStudentsByCondition(Student student);
}

// IStudentsDao.xml
...
<mapper namespace="com.yn.interfaces.IStudentsDao">
    ...
    <select id="findStudentsByCondition" parameterType="Student" resultMap="studentResultMap">
        select * from students where 1=1
        <if test="name != null">
            and name=#{name}
        </if>
        <if test="sex !=null">
            and sex=#{sex}
        </if>
        <if test="cls != 0">
            and class=#{cls}
        </if>
    </select>

</mapper>

// IStudentsDaoTest.java
    @Test
    public void testFindStudentsByCondition() {
        ...
        Student student = new Student();
        student.setSex("male");
        student.setCls(4);
        List<Student> students = this.studentsDaoProxy.findStudentsByCondition(student);
        for(Student stu: students){
            System.out.println(stu);
        }
    }
  • where:见名知意,该标签用于替换 where 子句。正如我们上面那个例子,如果直接使用 where 子句进行 if 判断,我们需要手动添加一个为”真“的条件,这样当 if 判断失败时,还能确保 SQL 语句的正确性,但使用 where 标签,就可以省略这冗余操作:
    <select id="findStudentsByCondition" parameterType="Student" resultMap="studentResultMap">
        select * from students
        <where>
            <if test="name != null">
                and name=#{name}
            </if>
            <if test="sex !=null">
                and sex=#{sex}
            </if>
            <if test="cls != 0">
                and class=#{cls}
            </if>
        </where>
    </select>
  • foreach:动态 SQL 的另一个常用操作就是对一个集合进行遍历,通常实在构建 IN 条件语句时使用。比如:
// StudentVo.java
public class StudentVo {
    private List<Integer> studentIds;

    public List<Integer> getStudentIds() {
        return studentIds;
    }

    public void setStudentIds(List<Integer> studentIds) {
        this.studentIds = studentIds;
    }
}

// IStudentDao.java
public interface IStudentsDao {
    ...
    List<Student> findStudentsInIds(StudentVo vo);
}

// IStudentDao.xml
<mapper namespace="com.yn.interfaces.IStudentsDao">
    ...
    <select id="findStudentsInIds" parameterType="StudentVo" resultMap="studentResultMap">
        select * from students
        <where>
            <if test="studentIds != null and studentIds.size() > 0">
                <foreach collection="studentIds" open="id in (" close=")" item="id" separator=",">
                    #{id}
                </foreach>
            </if>
        </where>
    </select>

</mapper>

// IStudentDaoTest.java
    @Test
    public void testFindStudentsInIds() {
        List<Integer> ids = Arrays.asList(1,2,3,4);
        StudentVo vo = new StudentVo();
        vo.setStudentIds(ids);
        List<Student> students = this.studentsDaoProxy.findStudentsInIds(vo);
        for(Student student : students){
            System.out.println(student);
        }
    }

foreach 标签中的collection属性用于指定集合对象,在我们上面的例子里就是StudentVo.studentIdsopen+item+separator+close组合而成我们的SQL 查询语句,上面完整的 SQL 语句如下:

select * from students where id in (1,2,3,4);

  • sql:该标签可以抽取出重复 SQL 语句,用于复用。如下所示:
<sql id="defaultSelect">
    select * from students
</sql>
<select id="selectAll" resultMap="studentResultMap">
    <include refid="defaultSelect"/>
</select>

更多动态 SQL 标签,请查看:dynamic-sql

数据库表间关联

在关系型数据中,表与表之间存在三种关系:一对一,一对多(多对一)和多对多。下面我们分别针对这三种关联关系给出具体配置。

前提:还是以上文入门案例作为示例,入门案例中已有一张表students,下面我们再创建一张表classes,并将字段stu_id作为指向表students.id的外键:

CREATE TABLE IF NOT EXISTS `classes` (
    -> `id` int(11) NOT NULL AUTO_INCREMENT,
    -> `name` VARCHAR(10) NOT NULL,
    -> `stu_id` int NOT NULL,
    -> PRIMARY KEY (`id`),
    -> KEY `stu_id` (`stu_id`),
    -> CONSTRAINT `stu_id` FOREIGN KEY (`stu_id`) REFERENCES `students` (`id`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建完表classes后,顺便插入几条数据:

insert into classes(name,stu_id) values('一班',1);
insert into classes(name,stu_id) values('二班',2);
insert into classes(name,stu_id) values('三班',3);
insert into classes(name,stu_id) values('四班',4);

然后,修改下students表:
1)先将表students的字段cls改为class_id

alter table students change column cls class_id int not null;

2)然后将其列class_id修改为指向表classes.id的外键:

# 禁用外键约束
set foreign_key_checks=0;
# 修改列
alter table students add constraint class_id foreign key(class_id) refererences classes(id);
# 启用外键约束
set foreign_key_checks=1;

至此,现数据库中总共有2张表:studentsclasses,且students.class_id指向表classes.id,表classes.stu_id指向表students.id

最后,在 MyBatis 中以代码方式实现对表classes的操作,完整代码如下:

// com.yn.bean.Classes.java
public class Classes implements Serializable {
    private int id;
    private String name;
    private int studentId;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getStudentId() {
        return studentId;
    }

    public void setStudentId(int studentId) {
        this.studentId = studentId;
    }

    @Override
    public String toString() {
        return "Classes{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", studentId=" + studentId +
                '}';
    }
}

// com.yn.interfaces.IClassesDao.java
public interface IClassesDao {
//    查:查询所有
    List<Classes> selectAll();
}

// 全局配置文件:MyBatis.xml
<configuration>
    ...
    <mappers>
    <!--直接使用 package 标签导入全部配置映射文件-->
        <package name="com.yn.interfaces"/>
    </mappers>
</configuration>

// resources/com/yn/interfaces/IClassesDao.xml
...
<mapper namespace="com.yn.interfaces.IClassesDao">
    <resultMap id="classesMap" type="com.yn.bean.Classes">
        <id property="id" column="id" />
        <result property="name" column="name" />
        <result property="studentId" column="stu_id" />
    </resultMap>

    <select id="selectAll" resultMap="classesMap">
        select * from classes;
    </select>
</mapper>

到此,我们的准备工作已算完成了,接下来就可以开始进入 MyBatis 建立表关联操作的实操。

  • 一对一关联:一个学生对应一个班级,因此,学生表students与班级表classes的关联关系即为一对一。具体到 MyBatis 中,这种关联体现的实现步骤如下:
  1. students对应的 POJO 类为Student,每个学生对应一个班级,因此,学生和班级的一对一关联关系在 Java 代码的体现只需为类Student内部添加一个Classes成员即可:
// com.yn.bean.Student.java
public class Student implements Serializable {
    private String name;
    private String sex;
    private int classId;

//    一对一关联:一个学生对应一个班级
    private Classes cls;
    ...
    public Classes getCls() {
        return cls;
    }

    public void setCls(Classes cls) {
        this.cls = cls;
    }
    ...
  1. 然后配置映射文件使用标签association表示一对一关联:
<mapper namespace="com.yn.interfaces.IStudentsDao">
    ...
    <resultMap id="studentClassMap" type="com.yn.bean.Student">
        <!--配置 Student 输出映射-->
        <result property="name" column="name"/>
        <result property="sex" column="sex"/>
        <result property="classId" column="class_id"/>
        <!--一对一关联:配置 Classes 输出映射-->
        <association property="cls" column="class_id" javaType="com.yn.bean.Classes">
            <id property="id" column="id" />
            <!--select classes.name as classname-->
            <result property="name" column="classname"/>
            <!--这里可以不配置,因为 sql 语句并没有输出该字段内容-->
            <result property="studentId" column="stu_id" />
        </association>

    </resultMap>

    <select id="selectAll" resultMap="studentClassMap">
        select s.*,c.name as classname from students s,classes c where s.class_id=c.id;
    </select>
    ...
</mapper>

到这里,我们就已经完成了一对一关联关系表的建立。

  • 一对多关联:一个班级可以有多个学生,因此班级表classes与学生表students的关系即为一对多关系。具体到 MyBatis 中,一对多的关联实现步骤如下所示:
  1. 由于一个班级有多个学生,此种一对多的关系在 Java 代码的体现就是:类Classes内部维护一个Students集合:
// com.yn.bean.Classes.java
public class Classes implements Serializable {
    private int id;
    private String name;
    private int studentId;

//    一对多关联:一个班级有多个学生
    private List<Student> students;

    public List<Student> getStudents() {
        return students;
    }

    public void setStudents(List<Student> students) {
        this.students = students;
    }
    ...
}
  1. 然后配置映射文件中使用标签collection表示一对多关联:
// resources/com/yn/interfaces/IClassesDao.xml
<mapper namespace="com.yn.interfaces.IClassesDao">
    <resultMap id="classStudentsMap" type="com.yn.bean.Classes">
        <!--配置 Classes 字段映射-->
        <id property="id" column="id" />
        <!--select classes.name as classname-->
        <result property="name" column="classname" />
        <result property="studentId" column="stu_id" />
        <!--一对多关联:配置 Student 映射-->
        <!--ofType表示集合内部元素-->
        <collection property="students" ofType="com.yn.bean.Student">
            <result property="name" column="name" />
            <result property="sex" column="sex" />
            <result property="classId" column="class_id" />
        </collection>
    </resultMap>

    <select id="selectAll" resultMap="classStudentsMap">
        select * from classes
    </select>

</mapper>

到这里,我们就已经完成了一对多关联关系的建立。

在讲解 多对多关联 前,我们先创建2张表:

  1. 老师表teachers,用于存储教师信息:
create table if not exists teachers (
    -> id int primary key auto_increment,
    -> name varchar(20) not null,
    -> sex varchar(10) default 'male'
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

# 插入一些数据
insert into teachers(name,sex) values('张三','male');
insert into teachers(name,sex) values('李四','female');
insert into teachers(name,sex) values('王五','male');
  1. 然后需要再创建一张中间表(因为多对多关联需要一张中间表进行连接),称为:stu_tea,其内部之包含两个字段:sidtid,分别作为指向表studnets.id和表teachers.id的外键:
create table if not exists `stu_tea` (
    -> `sid` int not null,
    ->  `tid` int not null,
    ->  primary key (`sid`,`tid`),
    -> foreign key (`sid`) references `students` (`id`),
    -> foreign key (`tid`) references `teachers` (`id`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

# 插入一些数据
insert into stu_tea (sid,tid) values (1,1),(1,3),(2,2),(4,1),(4,2);
  • 多对多关联: 一个学生可以有多个老师,一个老师也可以有多个学生。因此,老师与学生的关系是多对多关联。
  1. 首先从老师角度来说,其对应多个学生,在 Java 代码上的体现为:POJO 类Teacher内部维护一个Student集合,完整代码如下:
// com.yn.bean.Teacher.java
public class Teacher implements Serializable {
    private int id;
    private String name;
    private String sex;

//    多对多关联:一个老师对应多个学生
    private List<Student> students;
    
    public List<Student> getStudents() {
        return students;
    }

    public void setStudents(List<Student> students) {
        this.students = students;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    } 

    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
}

// com.yn.interfaces.ITeacherDao.java
public interface ITeacherDao {

    List<Teacher> selectAll();
}

// 全局配置文件:resources/MyBatisConfig.xml
<configuration>
    ...
    <mappers>
        <package name="com.yn.interfaces"/>
    </mappers>
</configuration>

// com.yn.interfaces.ITeacherDao.xml
<mapper namespace="com.yn.interfaces.ITeacherDao">
    <resultMap id="teacherStudentsMap" type="com.yn.bean.Teacher">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="sex" column="sex"/>
        <collection property="students" ofType="com.yn.bean.Student">
            <!--select students.name as sname-->
            <result property="name" column="sname"/>
            <!--select students.sex as ssex-->
            <result property="sex" column="ssex"/>
            <result property="classId" column="class_id"/>
        </collection>
    </resultMap>

    <select id="selectAll" resultMap="teacherStudentsMap">
        select t.*,s.name as sname,s.sex as ssex,s.class_id from teachers t
        left outer join stu_tea st on t.id=st.tid
        left outer join students s on s.id=st.sid
    </select>
</mapper>

// junit test
@Test
public void selectAll() {
    List<Teacher> teachers = this.teacherDaoProxy.selectAll();
    for(Teacher teacher: teachers){
        System.out.println(teacher);
        for(Student s : teacher.getStudents()){
            System.out.println(s);
        }
    }
}
  1. 从学生角度来讲,其对应多个老师,在 Java 代码上的体现为:POJO 类Student内部维护一个Teacher集合:
// com.yn.bean.Student.java
public class Student implements Serializable {
    private String name;
    private String sex;
    private int classId;
//    一对一关联:一个学生对应一个班级
    private Classes cls;

//    多对多关联:一个学生对应多个老师
    private List<Teacher> teachers;

    public List<Teacher> getTeachers() {
        return teachers;
    }

    public void setTeachers(List<Teacher> teachers) {
        this.teachers = teachers;
    }
    ...
}

// com.yn.interfaces.IStudentsDao.xml
<mapper namespace="com.yn.interfaces.IStudentsDao">
    ...
    <resultMap id="studentTeachersMap" type="com.yn.bean.Student">
        <result property="name" column="name" />
        <result property="sex" column="sex" />
        <result property="classId" column="class_id" />
        <collection property="teachers" ofType="com.yn.bean.Teacher">
            <!--select teachers.name as tname-->
            <result property="name" column="tname" />
            <!--select teachers.sex as tsex-->
            <result property="sex" column="tsex" />
        </collection>
    </resultMap>

    <select id="selectAll" resultMap="studentTeachersMap">
        select s.name,s.sex,s.class_id,t.name as tname,t.sex as tsex from students s
        left outer join stu_tea st on s.id=st.sid
        left outer join teachers t on t.id =st.tid
    </select>
    ...
</mapper>

// junit test
@Test
public void testSelectAll() throws IOException {
    List<Student> students = this.studentsDaoProxy.selectAll();
    for (Student student : students) {
        System.out.println(student);
        for(Teacher teacher : student.getTeachers()){
            System.out.println(teacher);
        }
    }
}

到此,MyBatis 中对于数据库表间的关联关系处理已基本完成。

关联表加载模式

首先了解下两个概念:

  • 立即加载:表间关联时,加载主表时,同时加载关联表。
  • 延迟加载:也即按需加载,关联表数据只有在使用到的时候,才进行加载。

正如我们上文提到的关系型数据库中,各表间可能存在一定的关联关系。比如一对多关联时,当我们查询主表数据时,此时对于关联表而言,就存在两种操作:
1)立即加载:查询主表数据时,同时也对关联表数据进行查询。即主副表查询是同步的。
2)延迟加载:查询主表数据时,不进行关联表数据查询。后续要使用到关联表数据时,才进行查询。即主副表查询是异步的。

通常而言,对于 N对一 的关联关系,建议使用 立即加载(可认为关联表数据量小)。
对于 N对多 的关联关系,建议使用 延迟加载(可认为关联表数据量大)。

MyBatis 中,开启延迟加载的步骤如下:

  1. 首先在全局配置文件中开启全局延迟加载模式:
// resources/MyBatisConfig.xml
<configuration>
<!--    导入外部配置文件-->
    <properties resource="db.properties" />
    
    <settings>
        <!--延迟加载:true-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--立即加载:false-->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    ...
</configuration>
  1. 通过提供select属性,配置关联表的数据获取(延迟)方法,因为是关系型数据库,关联表的数据获取通常都是通过主表的外键作为其参数,所提提供的方法的参数即为主表外键值,其由column属性指定。

比如,按上文学生表students和班级表classes的一对一关联来说,其配置如下:
1)因为要开启延迟加载,因此主表(即表students)操作(SQL 语句)不需要考虑关联表:

<!--com/yn/interfaces/IStudentsDao.xml-->
<select id="selectAll" resultMap="studentClassMap">
    select * from students
</select>

2)然后,关联表(即表classes)要提供关联查询方法,因主表(即表students)是通过外键(class_id)关联副表(即表classes),因此副表需要提供selectById(int cid)方法:

// com.yn.interfaces.IStudentsDao.java
public interface IClassesDao {

//    查:查询所有
    List<Classes> selectAll();

//    提供通过班级id 关联查询方法
    Classes selectById(Integer cid);
}

<!--com/yn/interfaces/IClassesDao.xml-->
<mapper namespace="com.yn.interfaces.IClassesDao">
    <resultMap id="classesMap" type="com.yn.bean.Classes">
        <id property="id" column="id" />
        <result property="name" column="name" />
        <result property="studentId" column="stu_id" />
    </resultMap>

    <select id="selectById" parameterType="int" resultMap="classesMap">
        select * from classes where id=#{cid}
    </select>
</mapper> 

3)最后,通过select属性和column属性为主表提供副表数据获取(延迟加载)方法:

<!--com/yn/interfaces/IStudentsDao.xml-->
<resultMap id="studentClassMap" type="com.yn.bean.Student">
    <!--配置 Student 输出映射-->
    <result property="name" column="name"/>
    <result property="sex" column="sex"/>
    <result property="classId" column="class_id"/>
    <!--一对一关联:配置 Classes 输出映射-->
    <association property="cls" column="class_id" javaType="com.yn.bean.Classes" select="com.yn.interfaces.IClassesDao.selectById">
    </association>
</resultMap>

到此,N对一 的配置就完成了。

而对于 N对多 的配置,其与 N对一 的配置是基本一致的。
比如还是按上文班级表classes和学生表students来说,他们是一对多的关系,即一个班级对应多个学生,那么其关联关系在 MyBatis 中的配置具体如下:
1)配置班级表classes数据获取 SQL 语句:

<!--com/yn/interfaces/IClassesDao.xml-->
<select id="selectAll" resultMap="classStudentsMap">
    select * from classes
</select>

2)配置表students依据班级id获取学生数据方法:

// com.yn.interfaces.IStudentsDao.java
public interface IStudentsDao {
    // 一个班级 cid 对应多个学生
    List<Student> selectById(Integer cid);
}

<!--com/yn/interfaces/IStudentsDao.xml-->
<mapper namespace="com.yn.interfaces.IStudentsDao">
    <resultMap id="studentResultMap" type="com.yn.bean.Student">
        <constructor>
            <arg column="name" javaType="String"/>
            <arg column="sex" javaType="String"/>
            <arg column="class_id" javaType="_int"/>
        </constructor>
        <result property="classId" column="class_id"/>
    </resultMap>

    <select id="selectById" parameterType="INT" resultMap="studentResultMap">
        select * from students where class_id=#{cid}
    </select>
    ...
</mapper>

3)最后,为主表配置副表数据加载方法:

<!--com/yn/interfaces/IClassesDao.xml-->
<resultMap id="classStudentsMap" type="com.yn.bean.Classes">
    <id property="id" column="id" />
    <result property="name" column="name" />
    <result property="studentId" column="stu_id" />
    <collection property="students" ofType="com.yn.bean.Student" column="id" select="com.yn.interfaces.IStudentsDao.selectById">
    </collection>
</resultMap>

至此,N对多 的延迟加载特定已配置完成。

:我们上面在全局配置表中开启了全局延迟加载模式,所以对于所有的关联表,默认都会开启延迟加载,但是可以通过为标签association/collection添加fetchType来显示覆盖全局配置,其取值有两个:eager表示立即加载,lazy表示延迟加载。

比如,我们前面也提到过,对于 N对一 关联的数据库表,通常使用的是立即加载模式,那么我们就可以按如下配置,启动立即加载:

<!--com/yn/interfaces/IStudentsDao.xml-->
<resultMap id="studentClassMap" type="com.yn.bean.Student">
    <result property="name" column="name"/>
    <result property="sex" column="sex"/>
    <result property="classId" column="class_id"/>

    <association fetchType="eager" property="cls" column="class_id" javaType="com.yn.bean.Classes" select="com.yn.interfaces.IClassesDao.selectById">
    </association>
</resultMap>

缓存

对访问频繁且不常变化的数据进行缓存,可以减少与数据库的交互,提高执行效率。

MyBatis 为我们提供了两种缓存功能:

  • 一级缓存:也成为 本地缓存,由SqlSession对象实例负责缓存。使用同一个SqlSession实例进行同一查询,后续的查询结果都是从一级缓存中获取。但该SqlSession实例销毁时,缓存随之销毁。

:对于一级缓存而言,其有如下特性:

  1. 一级缓存默认开启
  2. 一级缓存其实内部是一个Map结构,其缓存的是对象实例(相应 POJO 类实例)
  3. 但调用SqlSession的增加,修改,删除,commit(),close()等方法时,其一级缓存会被清空
  • 二级缓存:由SqlSessionFactory对象实例负责缓存。由同一个SqlSessionFactory实例生产的SqlSession对象实例共享该二级缓存。

:对于二级缓存而言,其有如下特性:

  1. 二级缓存默认不开启。手动开启方法如下:
    1)全局配置文件中开启二级缓存(可选操作,因为默认已开启):
<!--resources/MyBatisConfig.xml-->
<configuration>
    ...
    <settings>
        <!-- 开启二级缓存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    ...
</configuration>

2)在当前配置映射文件中开启二级缓存:

<!--com/yn/interfaces/IClassesDao.xml-->
<mapper namespace="com.yn.interfaces.IClassesDao">
    <!--配置映射文件开启二级缓存-->
    <cache />
    ...
</mapper>

3)在当前数据库操作中开启二级缓存(注:POJO 类需要实现序列化接口):

<!--com/yn/interfaces/IClassesDao.xml-->
<mapper namespace="com.yn.interfaces.IClassesDao">
    ...
    <!--操作开启二级缓存-->
    <select id="selectAll" resultMap="classStudentsMap" useCache="true">
        select * from classes
    </select>
    ...
</mapper>
  1. 二级缓存工作机制:在会话中,进行查询只会将结果保存到一级缓存中。只有当会话关闭时,才会将一级缓存的内容保存到二级缓存中(序列化)。
  2. 二级缓存缓存的是数据(即序列化),也即 SQL 操作从二级缓存获取数据后,再重新组装成对象实例(序列化)。而一级缓存直接缓存的就是对象实例。

MyBatis Generator

当数据库中存在很多张表时,如果我们还是手动创建表对应的实体类(Bean)、数据访问操作类(Dao)和映射文件(Mapper.xml)...这样操作就会非常繁琐,因此,此时可以借助 MyBatis Generator 这类插件自动生成上述文件,具体操作如下:

:以下是在 Spring Boot 项目进行的配置。

  1. 首先,在pom.xml中导入 Mybatis Generator 插件:

    <build>
        <plugins>
            <!--引入generator插件-->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.4.0</version>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.26</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <!--允许移动生成的文件-->
                    <verbose>true</verbose>
                    <!--允许自动覆盖文件,第一次可覆盖,以后都不可覆盖-->
                    <overwrite>true</overwrite>
                    <!-- 自定义配置文件的名称  默认是generatorConfig.xml -->
                    <configurationFile>
                        src/main/resources/mybatis-generator-config.xml
                    </configurationFile>
                </configuration>
            </plugin>
        </plugins>
    </build>
    

    :上述插件配置中,我们把 MyBatis Generator 的配置文件指定为:src/main/resources/mybatis-generator-config.xml

  2. application.yml文件中配置 MyBatis 和数据库连接相关信息:

    #配置数据源
    spring:
      datasource:
        # 数据库名为 whyn
        url: jdbc:mysql://localhost:3306/whyn?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: 123456
    
    # MyBatis 配置
    mybatis:
      mapper-locations: classpath:mapper/*.xml
    
  3. 创建 MyBatis Generator 配置文件:src/main/resources/mybatis-generator-config.xml,具体内容如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE generatorConfiguration
            PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
            "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    <!--配置生成器-->
    <generatorConfiguration>
    
        <!-- 引入配置文件,方便后续使用相关配置 -->
        <!-- 注意:无法使用 yml 配置文件 -->
        <properties resource="application.properties"/>
        <!-- context 是逆向工程的主要配置信息,一个数据库对应一个 context -->
        <!-- id: 名字而已  作为唯一标识 -->
        <!-- targetRuntime: 设置生成的文件使用于哪个 MyBatis 版本  -->
        <context id="default" targetRuntime="MyBatis3">
            <!-- 生成的Java文件的编码 -->
            <property name="javaFileEncoding" value="UTF-8"/>
            <!-- 格式化 Java 代码 -->
            <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
            <!-- 格式化 XML -->
            <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>
            <!-- 实现序列化接口 -->
            <plugin type="org.mybatis.generator.plugins.SerializablePlugin"/>
            <!-- 为生成的实体类添加equals,hashCode方法 -->
            <plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin"/>
            <!-- 该插件可以为 bean 生成 toString() 方法 -->
            <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>
    
            <commentGenerator>
                <!-- 生成的注释中不带时间戳 -->
                <property name="suppressDate" value="true"/>
                <!-- 是否去除自动生成的注释 true:是 : false:否 -->
                <property name="suppressAllComments" value="true"/>
                <!-- 添加表中字段注释 -->
                <property name="addRemarkComments" value="true"/>
            </commentGenerator>
    
            <!--数据库链接地址账号密码-->
            <jdbcConnection driverClass="${spring.datasource.driver-class-name}"
                            connectionURL="${spring.datasource.url}"
                            userId="${spring.datasource.username}"
                            password="${spring.datasource.password}">
                <!-- 高版本的 mysql-connector-java 需要设置 nullCatalogMeansCurrent=true -->
                <property name="nullCatalogMeansCurrent" value="true"/>
            </jdbcConnection>
    
            <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和
               NUMERIC 类型解析为java.math.BigDecimal -->
            <javaTypeResolver>
                <property name="forceBigDecimals" value="true"/>
                <!-- This property is used to specify whether MyBatis Generator should force the use of JSR-310 data types for DATE, TIME,
                and TIMESTAMP fields, rather than using java.util.Date -->
                <property name="useJSR310Types" value="true"/>
            </javaTypeResolver>
    
    
            <!--生成pojo存放位置-->
            <!-- targetPackage: 生成实体类所在的包 -->
            <!-- targetProject: 生成实体类所在的硬盘位置 -->
            <javaModelGenerator targetPackage="com.yn.entity" targetProject="src/main/java">
    
                <!-- 是否让数据库名(即 schema) 作为包的后缀,默认为 false -->
                <property name="enableSubPackages" value="false"/>
                <!-- 是否对modal添加构造函数 -->
                <property name="constructorBased" value="true"/>
                <!-- 是否清理从数据库中查询出的字符串左右两边的空白字符 -->
                <property name="trimStrings" value="true"/>
                <!-- 建立modal对象是否不可改变 即生成的modal对象不会有setter方法,只有构造方法 -->
                <property name="immutable" value="false"/>
    
            </javaModelGenerator>
    
            <!--生成映射文件 mapper.xml 存放位置-->
            <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
                <!-- 针对数据库的一个配置,是否把 schema 作为包名 -->
                <property name="enableSubPackages" value="false"/>
            </sqlMapGenerator>
    
            <!--生成Dao类存放位置  也就是生成接口所在的位置 -->
            <!-- 客户端代码,生成易于使用的针对Model对象和XML配置文件 的代码
                    type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象
                    type="MIXEDMAPPER",生成基于注解的Java Model 和相应的Mapper对象
                    type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口
            -->
            <javaClientGenerator type="XMLMAPPER" targetPackage="com.yn.dao" targetProject="src/main/java">
                <!-- 针对 Oracle 数据库的一个配置,是否把 schema 作为包名 -->
                <property name="enableSubPackages" value="false"/>
            </javaClientGenerator>
    
            <!--生成对应表及类名,这里每一个表的五项属性是为了删除自动编写的复杂查询-->
            <!-- tableName: 对应的是数据库表名,domainObjectName: 是实体类的名字,可以自定义-->
            <!-- escapeWildcards设置为true可以帮助抵御SQL注入 -->
            <table tableName="tb_article" domainObjectName="Article" escapeWildcards="true"
                   enableCountByExample="false" enableUpdateByExample="false" 
                   enableDeleteByExample="false" enableSelectByExample="false" 
                   selectByExampleQueryId="false" >
                <columnOverride column="summary" javaType="java.lang.String" jdbcType="VARCHAR" />
            </table>
    
            <!-- 生成下一张表 -->
            <table tableName="tb_settings" domainObjectName="Settings" enableCountByExample="false"
                   enableUpdateByExample="false" enableDeleteByExample="false"
                   enableSelectByExample="false" selectByExampleQueryId="false" />
    
        </context>
    </generatorConfiguration>
    

    :当表中字段为Text等类型时,MyBatis Generator 会生成XxxWithBLOBs.java文件,要取消这个特性只需配置将Text类型视为VARCHAR类型即可:

    <table tableName="tb_article" domainObjectName="Article" > 
        <!-- create table tb_article( summary text) -->
        <columnOverride column="summary" javaType="java.lang.String" jdbcType="VARCHAR" />
    </table>
    

    各配置选项具体作用,可参考文章:MyBatis Generator 超详细配置

  4. 配置完成后,只需在 IDEA maven 面板找到mybatis-generator:generate,双击执行即可:

    mybatis-generator

其他

下面列举一些常用配置具体实现:

  • 主键自增回填:对于插入(insert)语句,如果数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server):比如我们上文入门案例中的使用自增字段id作为表students的主键。那么此时,可以设置 useGeneratedKeys=”true”,然后再把keyProperty设置到目标属性上,这样,当插入完成后,数据库生成的主键就会回填到该对象的主键属性上:
<insert id="add" parameterType="student" useGeneratedKeys="true" keyProperty="id">
    insert into students(name,sex,cls) values(#{name},#{sex},#{cls})
</insert>

如果数据库还支持多行插入,那么我们可以传入一个Student数组或者集合,并返回自动生成的主键:

public interface IStudentsDao {
    ...
    void addAll(List<Student> students);
    ...
}


<!-- IStudentsDao.xml -->
<insert id="addAll" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id">
    insert into students(name,sex,cls) values
    <foreach collection="list" item="stu" separator=",">
        (#{stu.name},#{stu.sex},#{stu.cls})
    </foreach>
</insert>
  • 结果映射:通常来说,我们自定义的 Java Bean 的成员应该与数据库表的字段名称一致,这样 MyBatis 就能自动地帮我们进行数据转换。但在实际开发中,往往会出现 Java Bean 的成员名称与数据库表字段名称不一致的情况:比如,我们将上文入门案例的数据库表studentscls字段更改为class
alter table students change column cls class int not null;

现在,Student的成员cls就无法匹配数据库表studentsclass字段了,我们先查看下表students的内容:

然后我们在运行一下获取操作:selectAll(),结果如下所示:

可以看到,数据库的class字段获取不到了。因此,这里我们需要配置Studentcls成员到数据库表studentsclass字段的映射:比如,我们可以通过更改 Sql 语句来让数据库表students.class字段映射到Student.cls

<select id="selectAll" resultType="student">
    select id,name,sex,class as cls from students
</select>

但是,Sql 语句的配置会显得繁琐些,尤其当需要配置字段较多的时候,因此,MyBatis 已经为我们提供了相关方法(resultMap)解决这个问题,其具体配置如下所示:

<mapper namespace="com.yn.interfaces.IStudentsDao">
    <!--配置mysql表列名与实体类属性的映射关系-->
    <resultMap id="studentResultMap" type="com.yn.bean.Student">
        <!--主键使用标签 id -->
        <!--<id property="id" column="id"/>-->
        <!--property指Java Bean成员,column指对应数据库字段名-->
        <result property="cls" column="class"/>
    </resultMap>

    <select id="selectAll" resultMap="studentResultMap">
        select * from students;
    </select>
</mapper>

其实就是配置一个resultMap,并将该resultMap放置到所需方法中即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容