2.1 ORM框架概述

面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系。ORM是“对象-关系映射(Object-Relational Mapping)”的缩写,一般以中间件的形式存在,主要实现程序对象到关系数据库数据的映射。

当我们实现一个不使用O/R 映射的应用程序时,我们可能会写特别多数据访问层的代码,从数据库保存、删除、读取对象信息,而这些代码都是重复的。而使用ORM则会大大减少重复性代码。

ORM具有如下的特点:

  1. 简单。ORM以最基本的形式建模数据,将数据标表映射成一个Java类(模型),表的字段就是这个类的成员变量 。
  2. 精确。ORM使所有的数据表都按照统一的标准精确地映射成Java类,在代码层面保持准确统一。
  3. 易懂。ORM使数据库结构文档化。比如MySQL数据库就被ORM转换为了Java程序员可以读懂的Java类,Java程序员可以只把注意力放在他擅长的Java层面(当然能够熟练掌握数据库更好)。
  4. 易用。ORM包含对持久类对象进行CRUD操作的API,例如 create()、update()、save()、load()、find()、find_all()、where()等,也就是说 SQL 查询全部封装成了编程语言中的函数,通过函数的链式组合生成最终的SQL语句。通过这种封装避免了不规范、冗余、风格不统一的SQL语句,可以避免很多人为Bug,方便编码风格的统一和后期维护。

此外ORM还具有提高开发效率、降低开发成本、 更加对象化、可移植性高、便于引入数据缓存等优点。

ORM的缺点是自动化进行关系数据库的映射需要消耗系统性能,处理多表联查、where 条件复杂之类的查询时,ORM的语法会变得复杂。

2.2 MyBatis简介

ORM 框架的本质是简化编程中操作数据库的编码,发展到现在基本上常见的有两个。一个是自称可以不用写一句 SQL 的 Hibernate,一个是可以灵活调试动态 SQL 的 MyBatis。两者各有特点,在企业级系统开发中可以根据需求灵活使用(通常传统企业大都喜欢使用 Hibernate,互联网行业则通常使用 MyBbatis)。

Hibernate 特点就是所有的 SQL 都用 Java 代码来生成,不用跳出程序去写(看) SQL ,有编程的完整性,发展到最顶端就是 Spring Data JPA 这种模式,基本上根据方法名就可以生成对应的 SQL。感兴趣的话可以看下面两个文档

  • Accessing Data with JPA https://spring.io/guides
  • Spring Boot Jpa 的使用 http://www.ityouknow.com/springboot/2016/08/20/spring-boot-jpa.html | image.png | MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。 | | —- | —- |

2.3 在项目中配置MyBatis

MyBatis 初期使用比较麻烦,需要各种配置文件、实体类、DAO 层映射关联以及一大推其它配置。也发现了这些弊端后,MyBatis开发了generator,可以根据表结果自动生产实体类、配置文件和 DAO 层代码,可以减轻一部分开发量。后来经过大量优化后支持注解的方式,能够自动管理 DAO 层和配置文件等。发展到最顶端就是本章后面要讨论的模式——mybatis-spring-boot-starter。根据官方说明,他的作用是

MyBatis Spring-Boot-Starter will help you use MyBatis with Spring Boot

其实就是 MyBatis 看 Spring Boot 这么火热也开发出一套解决方案来凑凑热闹,但这一凑确实解决了很多问题,使用起来确实顺畅了许多,即 Spring Boot 和 MyBatis 组合起来可以完全注解不用配置文件,也可以简单配置轻松上手(任何东西只要关联到 Spring Boot 都是化繁为简)。

因为我们在创建项目的时候选择了MyBatis,在pom.xml中已经定义了对mybatis-spring-boot-starter的依赖:

  1. <dependency>
  2. <groupId>org.mybatis.spring.boot</groupId>
  3. <artifactId>mybatis-spring-boot-starter</artifactId>
  4. <version>2.2.0</version>
  5. </dependency>

尽管可以使用在版本处使用 RELEASE 关键字来自动获得最新发布的依赖,但这里还是明确指定具体的版本号更合适一些。你可以在 Maven Repository 查询它的最新版本

修改了 pom.xml 需要重新加载它。可以在它的右键菜单中执行 Reload project
image.png
点击 Maven 窗口工具栏的图标
image.png
或者直接点击测个自动出现的浮动图标
image.png

然后在application.yml新增以下配置

  1. mybatis:
  2. config-location: classpath:config/mybatis-config.xml

src/main/resources/config目录下建立一个新文件mybatis-config.xml

<?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>

</configuration>

上面mybatis-config.xml其实是空的,后面随着教程的展开,我们会在期中陆续增加配置。

mybatis-spring-boot-starter主要有两种解决方案,一种是使用注解解决一切问题,一种是简化后的老传统。下面分别讨论两种开发模式

2.4 公共基础工作

2.4.1 创建数据表

在你准备好的数据库中用如下的命令创建数据表和初始数据

CREATE TABLE user (
  id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  user_name varchar(128) NOT NULL COMMENT '用户登录名',
  password varchar(100) default '' NOT NULL COMMENT '用户加密后的密码' 
  nick_name varchar(32) DEFAULT NULL COMMENT '用户的昵称',
  mobile char(11) default '' NOT NULL COMMENT '用户登录用的手机号码',
  gender tinyint default 2 not null comment '性别',
  degree tinyint default 0 null comment '最高学位'
  PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8;

INSERT INTO user (id,user_name, nick_name, mobile, gender)
VALUES
    (1, 'David', '', '18801681588', 1);

2.4.2 数据源配置

首先我们把应用配置中的数据源配置完整

spring:
  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: jdbc:mysql://【数据库服务器ip地址】:3306/?
    username: 【数据库用户名】
    password: 【数据库密码】

Spring Boot 会自动加载这里datasource的相关配置,然后把数据源就自动注入到 sqlSessionFactory 中。之后sqlSessionFactory 会自动注入到 Mapper 中,然后一切都不用管了,直接拿起来使用就好了。

2.4.3 定义用户数据对象

创建包 com.longser.union.cloud.data.model,编写数据对象UserEntry.java

package com.longser.union.cloud.data.model;

public class UserEntry {

    private static final long serialVersionUID = 1L;
    private Long id;
    private String userName;
    private String nickName;
    private String mobile;
    private String password;

    public UserEntry() {
        super();
    }

    public UserEntry(String userName, String mobile) {
        super();
        this.userName = userName;
        this.mobile = mobile;
    }

    public Long getId() {
        return id;
    }

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

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "userName " + this.userName + ", nickName " + this.nickName + ", mobile " + this.mobile;
    }

}

注意,这里我们特意数据库字段名和实体类属性名定义的命名是不一样的,数据库字段名用下划线分隔不同的单词,而类属性的命名遵循驼峰规则

2.5 用注解应用MyBatis

用注解应用MyBatis的时候,不需要任何配置文件,所有的定义都通过注解完成。

2.5.1 基础注解

MyBatis 主要提供了以下CRUD注解:

下面这段代码无需XML即可完成数据查询:

public interface UserMapper {
    @Select("SELECT * FROM user")
    List<UserEntry> getAll();
}

这里为什么没有配置映射关系也能完成属性注入?因为在配置文件中开启了全局驼峰映射,SpringBoot 能够做到简单快捷地自动映射。

2.5.2 映射注解

虽然开启了全局驼峰映射,但你可能还会质疑,如果不符合下划线转驼峰规则的字段,拿查询回来的实体对象属性将获取为null,为了防止(或解决)对象属性和字段驼峰不一致的问题,我们可以使用映射注解@Results来指定映射关系。

Mybatis主要提供这些映射注解:

  • @Results 用于填写结果集的多个字段的映射关系
  • @Result 用于填写结果集的单个字段的映射关系
  • @ResultMap 根据ID关联XML里面

例如上面的 list 方法,可以在查询SQL的基础上,指定返回的结果集的映射关系,其中property表示实体对象的属性名,column表示对应的数据库字段名。

public interface UserMapper {
    @Select("SELECT * FROM user")
    @Results({
            @Result(property = "mobile",  column = "mobile"),
            @Result(property = "userName", column = "user_name"),
            @Result(property = "nickName", column = "nick_name"),
    })
    List<UserEntry> getAll();
}

2.5.3 定义映射类

在com.longser.union.cloud.data.mapper下编写UserMapper.java

package com.longser.union.cloud.data.mapper;

import java.util.List;

import org.apache.ibatis.annotations.*;
import com.longser.union.cloud.data.model.UserEntry;
import org.springframework.stereotype.Repository;

@Repository
public interface UserMapper {

    @Select("SELECT * FROM user")
    @Results({
            @Result(property = "mobile",  column = "mobile"),
            @Result(property = "userName", column = "user_name"),
            @Result(property = "nickName", column = "nick_name"),
    })
    List<UserEntry> getAll();

    @Select("SELECT * FROM user WHERE id = #{id}")
    @Results({
            @Result(property = "mobile",  column = "mobile"),
            @Result(property = "userName", column = "user_name"),
            @Result(property = "nickName", column = "nick_name"),
    })
    UserEntry getOne(Long id);

    @Insert("INSERT INTO user(user_name, mobile) VALUES(#{userName}, #{mobile})")
    void insert(UserEntry userEntry);

    @Update("UPDATE user SET user_name=#{userName},nick_name=#{nickName} WHERE id =#{id}")
    void update(UserEntry userEntry);

    @Delete("DELETE FROM user WHERE id =#{id}")
    void deleteById(Long id);
}

下面是MyBatis注解的说明

  • @Select 是查询类的注解,所有的查询均使用这个
  • @Result 修饰返回的结果集,关联实体类属性和数据库字段一一对应,如果实体类属性和数据库属性名保持一致,就不需要这个属性来修饰。
  • @Insert 插入数据库使用,直接传入实体类会自动解析属性到对应的值
  • @Update 负责修改,也可以直接传入对象
  • @Delete 负责删除

更丰富的MyBatis注解请阅读官方文档

现在我们已经完成了相关 Mapper 层的开发,使用的时候当作普通的类注入进入就可以了。

2.5.4 返回自增主键

我们在第2.4.1节创建数据表时,定义了一个“自增主键”

id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',

这个主键会由数据库管理系统给新插入的数据分配一个始终增加的数字用来做数据主键。根据实际业务场景的不同,有时我们需要在插入之后获得这个主键值,一遍用于下一步的操作。MyBatis提供了专门实现这一功能的方法。下面的代码可以把自增主键值回填入数据实体对象

    @Insert("INSERT INTO user(user_name, mobile) VALUES(#{userName}, #{mobile})")
    @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
    Integer insert2(UserEntry userEntry);

和上一节中的代码相比,我们在注解中设置了 useGeneratedKeyskeyPropertykeyColumn属性(当字段名与实体类属性名相同或可以按照下划线转驼峰规则进行转换时,可以省略keyColumn属性。

MyBatis会使用JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键。获得主键值后将其复制给keyProperty配置的id属性。当需要设置多个属性时,使用逗号隔开,这种情况下通常还需要设置keyColumn属性,按顺序指定数据库的列,这里列的值会和keyProperty配置的属性意义对应。

注意,新方法有一个返回值,这个值是插入数据的数量,不是新生成的ID!新ID会直接赋值给**user.id**(参见见4.6.2单元测试部分的代码)。

2.5.5 定义扫描映射的路径

定义好映射类后,需要在CloudApplication.java中 增加如下的注解声明它的包路径

@SpringBootApplication
+@MapperScan({"com.longser.union.cloud.data.mapper"})
public class CloudApplication {

    public static void main(String[] args) {
        SpringApplication.run(CloudApplication.class, args);
    }

}

上面在启动类中用 @MapperScan注 解定义了Spring自动扫描映射的包路径(有多个路径的时候可以在字符串内用逗号分开)。当然我们也可以直接在 Mapper 类上面添加注解 @Mapper 来达到相同的目标,但建议使用相同的目标,否则为每个映射累逐一添加加注解过于繁琐也不易维护。

2.5.5 #符号和$符号的区别

上一节的SQL语句模板中使用#{}符号定义字段变量值,如果使用${}符号也是可以的,但这并不值得提倡。具体的原因我们先看一下这两种定义的区别:

// This example creates a prepared statement, something like select from teacher where name = ?;
@Select(“Select
from teacher where name = #{name}”)

// This example creates n inlined statement, something like select from teacher where name = ‘someName’;
@Select(“Select
from teacher where name = ‘${name}’”)

显然用$符号的时候SQL语句会使用用类似字符串拼接放的方式,这容易导致“SQL注入”漏洞,因此不要在实际的项目中使用。

2.6 设计单元测试程序

2.6.1 关于JUnit 5

我们使用 JUnit 5 编写单元测试代码。这里仅给出代码的内容,关于 JUnit 的更详细内容参见本教程有关章节及后端知识库中专门的 JUnit 教程。

在项目创建初始化的时候,在pom.xml中已经添加了如下的依赖,应该不需要额外处理

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

2.6.2 设计单元测试代码

在test/java下创建新的包com.longser.union.cloud.data.mapper,然后创建新类UserMapperTest.java

package com.longser.union.cloud.data.mapper;

import java.util.List;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.longser.utils.enums.Gender;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import com.longser.union.cloud.data.model.UserEntry;
import org.springframework.transaction.annotation.Transactional;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
public class UserMapperTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    @Transactional
    public void testInsert() {
        userMapper.insert(new UserEntry("aa1", "13701111234"));
        userMapper.insert(new UserEntry("bb1", "18601234567"));
        userMapper.insert(new UserEntry("cc1", "188018856781234"));

        assertEquals(4, userMapper.getAll().size());

        List<UserEntry> users = userMapper.getAll();
        for (UserEntry current : users) {
            String s = current.toString();
            System.out.println(current.getId() + ": " + s);

            if (current.getId() > 1) {
                userMapper.deleteById(current.getId());
            }
        }
        assertEquals(1, userMapper.getAll().size());
    }

    @Test
    @Rollback
    @Transactional
    public void testInsertRollback() {
        userMapper.insert(new UserEntry("aa1", "13701111234"));
        userMapper.insert(new UserEntry("bb1", "18601234567"));
        userMapper.insert(new UserEntry("cc1", "18801885678"));

        assertEquals(4, userMapper.getAll().size());
    }

    @Test
    public void testInsert2() {
        UserEntry user = new UserEntry("ddd1", "13701111234");
        userMapper.insert2(user);
        assertEquals(2, userMapper.getAll().size());

        Long newId = user.getId();
        System.out.println("the new id is  " + newId);

        System.out.println(userMapper.getOne(newId).toString());

        userMapper.deleteById(newId);
        assertEquals(1, userMapper.getAll().size());
    }

    @Test
    public void testQuery() {
        List<UserEntry> users = userMapper.getAll();
        System.out.println(users.toString());
    }

    @Test
    public void testUpdate() {
        UserEntry user = userMapper.getOne(1L);
        System.out.println(user.toString());
        user.setNickName("Grace Runner");
        userMapper.update(user);
        assertEquals("Grace Runner", userMapper.getOne(1L).getNickName());
    }

    @Test
    @Rollback
    @Transactional
    public void testPage() {
        //在原来一条数据的基础上再增加6条数据
        userMapper.insert(new UserEntry("aa2", "13701111234"));
        userMapper.insert(new UserEntry("bb3", "18601234567"));
        userMapper.insert(new UserEntry("cc4", "18801885678"));
        userMapper.insert(new UserEntry("dd5", "18601234567"));
        userMapper.insert(new UserEntry("ee6", "18801885678"));
        userMapper.insert(new UserEntry("ff7", "18801885678"));

        //获取第1页,3条内容,默认查询总数count
        PageHelper.startPage(3, 3);
        //紧跟着的第一个select方法会被分页
        List<UserEntry> list = userMapper.getAll();

        //这里我们可以看到实际返回的结果list类型是Page<E>
        System.out.println("The resule is:");
        System.out.println(list.toString());

        //如果想取出分页信息,需要强制转换为Page<E> 或者 用PageInfo包装结果
        //PageInfo中关于分页的信息非常丰富,
        PageInfo<UserEntry> pageInfo = new PageInfo<>(list);
        System.out.println("\nThe pageInfo is:");
        System.out.println("PageNum = " + pageInfo.getPageNum());
        System.out.println("PageSize = " + pageInfo.getPageSize());
        System.out.println("StartRow = " + pageInfo.getStartRow());
        System.out.println("EndRow = " + pageInfo.getEndRow());
        System.out.println("Total = " + pageInfo.getTotal());
        System.out.println("Pages = " + pageInfo.getPages());
        System.out.println("IsFirstPage = " + pageInfo.isIsFirstPage());
        System.out.println("IsLastPage = " + pageInfo.isIsLastPage());
        System.out.println("HasPreviousPage = " + pageInfo.isHasPreviousPage());
        System.out.println("HasNextPage = " + pageInfo.isHasNextPage());

        //遍历当前页的全部数据
        System.out.println("\nThe userList is:");
        for (UserEntry userEntry : list) {
            System.out.println(userEntry);
        }

        //PageHelper.startPage的作用是一次性的,
        //后面的查询不会被分页,除非再次调用PageHelper.startPage
        list = userMapper.getAll();
        System.out.println("\nThe size is:" + list.size());
    }

}

2.6.3 运行单元测试的方法

我们有很多办法运行单元测试:

  • 在左侧目录树上选中目标类后在鼠标右键上下文相关菜单中运行

image.png

  • 打开目标类后点击运行图标

image.png

  • 打开目标类后在编辑窗口鼠标右键上下文相关菜单中运行

image.png

  • 打开目标类后在编辑窗口左边的运行图标出点击运行全部测试方法或指定的方法

image.png

  • 选中或打开目标类,按Ctrl+Shift+R快捷键

    2.6.4 查看测试运行结果

    如果三个测试都成功完成,我们可以看到类似如下的结果
    image.png
    点击左侧不同的方法名,可以在右侧窗口看到它运行的结果
    image.png

image.png
如果测试没有成功(有错误或警告)也可以在右侧窗口查看具体的信息。

2.6.5 关于@Param注解

在本例子中SQL语句注解中的参数名和Mapper类映射方法方法的参数名称是一致的,例如

    @Delete("DELETE FROM user WHERE id =#{id}")
    void deleteById(Long id);

如果映射方法方法的变量名与参数名不一致,可以使用@Param注解

    @Delete("DELETE FROM user WHERE id =#{id}")
    void deleteById(@Param("id") Long userId);

此外,如果你的映射方法接受多个参数,就可以使用这个注解自定义每个参数的名字。否则在默认情况下,除 RowBounds 以外的参数会以 “param” 加参数位置被命名。例如 #{param1}, #{param2}。如果使用了 @Param("person"),参数就会被命名为 #{person}

2.7 使用映射文件应用MyBatis

映射文件是一种极为简单的XML文件,它保持了映射文件的老传统,接口层只需要定义空方法,系统会自动根据方法名在映射文件中找对应的 SQL语句。

2.7.1 应用文件参数配置

application.yml新增以下配置

mybatis:
  config-location: classpath:config/mybatis-config.xml
+ mapper-locations: classpath:mybatis/mapper/*.xml

上面增加的设置指定了 Mybatis 基础配置文件和实体类映射文件的地址。

2.7.2 编写 Mapper 层的代码

在com.longser.union.cloud.data.mapper创建新类UserMapperXml

package com.longser.union.cloud.data.mapper;

import java.util.List;
import com.longser.union.cloud.data.model.UserEntry;
import org.springframework.stereotype.Repository;

@Repository
public interface UserMapperXml {

    List<UserEntry> getAll();

    UserEntry getOne(Long id);

    void insert(UserEntry userEntry);

    void update(UserEntry userEntry);

    void deleteById(Long id);

}

对比上一步,这里只需要定义接口方法

2.7.3 添加 UserEntry 的映射文件

创建一个新目录src/main/resources/mybatis/mapper,在这个目录下创建一个新文件User.xml(其实就是把上个版本中 Mapper 的(部分) SQL 搬到了这里的 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.longser.union.cloud.data.mapper.UserMapperXml" >
    <resultMap id="BaseResultMap" type="com.longser.union.cloud.data.model.UserEntry" >
        <id column="id" property="id" jdbcType="BIGINT" />
        <result column="user_name" property="userName" javaType="string" />
        <result column="nick_name" property="nickName" javaType="string" />
        <result column="mobile" property="mobile" javaType="string" />
    </resultMap>

    <sql id="Base_Column_List" >
        id, user_name, nick_name, mobile
    </sql>

    <select id="getAll" resultMap="BaseResultMap"  >
        SELECT
        <include refid="Base_Column_List" />
        FROM user
    </select>

    <select id="getOne" parameterType="java.lang.Long" resultMap="BaseResultMap" >
        SELECT
        <include refid="Base_Column_List" />
        FROM user
        WHERE id = #{id}
    </select>

    <insert id="insert" parameterType="com.longser.union.cloud.data.model.UserEntry" >
        INSERT INTO
        user
        (user_name,mobile)
        VALUES
        (#{userName}, #{mobile})
    </insert>

    <update id="update" parameterType="com.longser.union.cloud.data.model.UserEntry" >
        UPDATE
        user
        SET
        <if test="userName != null">user_name = #{userName},</if>
        <if test="mobile != null">mobile = #{mobile},</if>
        nick_name = #{nickName}
        WHERE
        id = #{id}
    </update>

    <delete id="deleteById" parameterType="java.lang.Long" >
        DELETE FROM
        user
        WHERE
        id =#{id}
    </delete>
</mapper>

在打开上面这个映射文件的时候,IDEA编辑器可能会有大量的出错信息。这个不用管他,并不影响程序运行。

另外,对于提示 SQL dialect is not configured,可以按照下图的方法设置消除。
image.png

2.7.4 返回自增主键

和注解定义类似,在用XML映射文件的时候也需要定义useGeneratedKeys和keyProperty属性

    <insert id="insert2" useGeneratedKeys="true" keyProperty="id"
            parameterType="com.longser.union.cloud.data.model.UserEntry" >
        INSERT INTO
        user
        (user_name,mobile)
        VALUES
        (#{userName}, #{mobile})
    </insert>

Java的代码同样不需要额外的设计

Integer insert2(UserEntry user);

2.7.5 测试使用Mapper中的方法

创建一个新的测试用类UserMapperXmlTest,下面是它的代码(这里测试使用使用Mapper中的方法本质上和上个版本没有任何区别只是改了文件名和变量名)

package com.longser.union.cloud.data.mapper;

import java.util.List;

import com.longser.utils.enums.Gender;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.longser.union.cloud.data.model.UserEntry;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
public class UserMapperXmlTest {

    @Autowired
    private UserMapperXml userMapperXml;

    @Test
    public void testInsert() {
        userMapperXml.insert(new UserEntry("aa1", "13701111234"));
        userMapperXml.insert(new UserEntry("bb1", "18601234567"));
        userMapperXml.insert(new UserEntry("cc1", "18801885678"));

        assertEquals(4, userMapperXml.getAll().size());

        List<UserEntry> users = userMapperXml.getAll();
        for (UserEntry current : users) {
            String s = current.toString();
            System.out.println(current.getId() + ": " + s);

            if (current.getId() > 1) {
                userMapperXml.deleteById(current.getId());
            }
        }
        assertEquals(1, userMapperXml.getAll().size());
    }

    @Test
    public void testInsert2() {
        UserEntry user = new UserEntry("ddd1", "13701111234");
        userMapperXml.insert2(user);
        assertEquals(2, userMapperXml.getAll().size());

        Long newId = user.getId();
        System.out.println("the new id is  " + newId);

        System.out.println(userMapperXml.getOne(newId).toString());

        userMapperXml.deleteById(newId);
        assertEquals(1, userMapperXml.getAll().size());
    }

    @Test
    public void testQuery()  {
        List<UserEntry> users = userMapperXml.getAll();
        System.out.println(users.toString());
    }

    @Test
    public void testUpdate() {
        UserEntry user = userMapperXml.getOne(1L);
        System.out.println(user.toString());
        user.setNickName("Grace Runner");
        userMapperXml.update(user);
        assertEquals("Grace Runner", userMapperXml.getOne(1L).getNickName());
    }

}

2.8 关于如何选择的讨论

看了上面的介绍,做MyBatis开发,你用注解还是 XML 映射文件?

这两种模式各有特点,其中基于注解开发是趋势,给我们简化了非常多东西,它特别适合简单快速的模式。尤其像现在流行的微服务模式,一个微服务就会对应一个自已的数据库,多表连接查询的需求会大大的降低,会越来越适合这种模式。

可以说,基于注解开发大体上一件很优雅,很值得推广的事情。为什么说大体上呢?因为有一种观点认为“对于 MyBatis 框架来说,没有 XML 映射文件的 MyBatis 是没有灵魂的”

当然,如果你全是简单的 SQL 语句就能实现的功能,你全部用注解开发是毫无问题的。但很多实际去的项目需求并不是这样。虽然我们可以用注解的形式实现所有用XML定义能实现的功能,但这种做法/写法实在是太不优雅。

一个比较折中的方式是简单 SQL 用注解开发,那些有诸如条件判断类需求的复杂SQL还是要写在XML文件中

总之,不要为了拥抱注解而完全摒弃了 XML 的形式

MyBatis官方中文文档 https://mybatis.org/mybatis-3/zh/

版权说明:本文由北京朗思云网科技股份有限公司原创,向互联网开放全部内容但保留所有权力。