Administrator
发布于 2024-10-06 / 59 阅读
0

3、SpringBoot2:集成 MyBatis

ORM 框架是什么  

对象关系映射(Object Relational Mapping,简称 ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象技术。简单的说,ORM 是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。

为什么需要 ORM

当开发一个应用程序的时候(不使用 O/

ORM 框架是什么

对象关系映射(Object Relational Mapping,简称 ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象技术。简单的说,ORM 是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。

为什么需要 ORM

当开发一个应用程序的时候(不使用 O/R Mapping),你可能会写不少数据访问层的代码,用来从数据库保存、删除、读取对象信息等。在 DAL 中写了很多的方法来读取对象数据、改变状态对象等任务,而这些代码写起来总是重复的。 针对这些问题 ORM 提供了解决方案,简化了将程序中的对象持久化到关系数据库中的操作。

ORM 框架的本质是简化编程中操作数据库的编码,在 Java 江湖中发展到现在基本上就剩两家,一个是宣称可以不用写一句 SQL 的 Hibernate,一个是以动态 SQL 见长的 MyBatis,两者各有特点。在企业级系统开发中可以根据需求灵活使用,发现一个有趣的现象:传统企业大都喜欢使用 Hibernate,而互联网行业通常使用 MyBatis。

MyBatis 介绍

MyBatis 就是一款标准的 ORM 框架,被广泛的应用于各企业开发中。MyBatis 本是 Apache 的一个开源项目 IBatis,2010 年这个项目由 Apache Software Foundation 迁移到了 Google Code,并且改名为 MyBatis,2013 年 11 月又迁移到 Github 上。从 MyBatis 的迁移史,也可以看出源码托管平台的发展史,GitHub 目前已经成为世界上最大的开源软件托管平台,建议大家多多关注这个最大的社交网站。

MyBatis 支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的 JDBC 代码和参数的手工设置以及对结果集的检索封装。MaBatis 可以使用简单的 XML 或注解用于配置和原始映射。将接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。

作为一款使用广泛的开源软件,它的特点有哪些?

优点:

  • SQL 被统一提取出来,便于统一管理和优化

  • SQL 和代码解耦,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试

  • 提供映射标签,支持对象与数据库的 ORM 字段关系映射

  • 提供对象关系映射标签,支持对象关系组件维护

  • 灵活书写动态 SQL,支持各种条件来动态生成不同的 SQL

缺点:

  • 编写 SQL 语句时工作量很大,尤其是字段多、关联表多时,更是如此

  • SQL 语句依赖于数据库,导致数据库移植性差

MyBatis 几个重要的概念

Mapper 配置: Mapper 配置可以使用基于 XML 的 Mapper 配置文件来实现,也可以使用基于 Java 注解的 MyBatis 注解来实现,甚至可以直接使用 MyBatis 提供的 API 来实现。

Mapper 接口: Mapper 接口是指自行定义的一个数据操做接口,类似于通常所说的 DAO 接口。早期的 Mapper 接口需要自定义去实现,现在 MyBatis 会自动为 Mapper 接口创建动态代理对象。Mapper 接口的方法通常与 Mapper 配置文件中的 select、insert、update、delete 等 XML 结点存在一一对应关系。

Executor: MyBatis 中所有的 Mapper 语句的执行都是通过 Executor 进行的,Executor 是 MyBatis 的一个核心接口。

SqlSession: SqlSession 是 MyBatis 的关键对象,是执行持久化操作的独享,类似于 JDBC 中的 Connection,SqlSession 对象完全包含以数据库为背景的所有执行 SQL 操作的方法,它的底层封装了 JDBC 连接,可以用 SqlSession 实例来直接执行被映射的 SQL 语句。

SqlSessionFactory: SqlSessionFactory 是 MyBatis 的关键对象,它是单个数据库映射关系经过编译后的内存镜像。SqlSessionFactory 对象的实例可以通过 SqlSessionFactoryBuilder 对象类获得,而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出。

MyBatis 的工作流程如下:

  • 首先加载 Mapper 配置的 SQL 映射文件,或者是注解的相关 SQL 内容。

  • 创建会话工厂,MyBatis 通过读取配置文件的信息来构造出会话工厂(SqlSessionFactory)。

  • 创建会话。根据会话工厂,MyBatis 就可以通过它来创建会话对象(SqlSession)。会话对象是一个接口,该接口中包含了对数据库操作的增删改查方法。

  • 创建执行器。因为会话对象本身不能直接操作数据库,所以它使用了一个叫做数据库执行器(Executor)的接口来帮它执行操作。

  • 封装 SQL 对象。在这一步,执行器将待处理的 SQL 信息封装到一个对象中(MappedStatement),该对象包括 SQL 语句、输入参数映射信息(Java 简单类型、HashMap 或 POJO)和输出结果映射信息(Java 简单类型、HashMap 或 POJO)。

  • 操作数据库。拥有了执行器和 SQL 信息封装对象就使用它们访问数据库了,最后再返回操作结果,结束流程。

在具体的使用过程中,按照上述的流程来执行。

什么是 MyBatis-Spring-Boot-Starter

MyBatis-Spring-Boot-Starter 是 MyBatis 帮助我们快速集成 Spring Boot 提供的一个组件包。

使用这个组件可以做到以下几点:

  • 构建独立的应用

  • 几乎可以零配置

  • 需要很少的 XML 配置

注意:MyBatis-Spring-Boot-Starter 依赖于 MyBatis-Spring 和 Spring Boot,最新版 1.3.1 需要 MyBatis-Spring 1.3 以上,Spring Boot 版本 1.5 以上。

其实就是 MyBatis 看 Spring Boot 这么火热也开发出一套解决方案主动来集成, 但这一集成确实解决了很多问题,使用起来比以前简单顺畅了许多。mybatis-spring-boot-starter主要提供了两种解决方案,一种是简化后的 XML 配置版,一种是使用注解解决一切问题。

MyBatis 以前只有 XML 配置这种使用的形式,到了后来注解使用特别广泛,MyBatis 也顺应潮流提供了注解的支持,从这里可以看出 MyBatis 一直都跟随着主流技术的变化来完善自己。接下来介绍一下如何使用 XML 版本。

XML 版本

XML 版本保持映射文件的老传统,优化主要体现在不需要实现 Dao 的实现层,系统会自动根据方法名在映射文件中找到对应的 SQL。

相关配置

关键依赖包

当然,先引入的是MySql的依赖包:

<dependency>

    <groupId>mysql</groupId>

    <artifactId>mysql-connector-java</artifactId>

    <scope>runtime</scope>

</dependency>

然后是任何模式都需要首先引入mybatis-spring-boot-starter的 pom 文件

<dependency>

    <groupId>org.mybatis.spring.boot</groupId>

    <artifactId>mybatis-spring-boot-starter</artifactId>

    <version>2.0.0</version>

</dependency>

本次教程我们还会引入另外一个组件,Apache 的commons-lang 3,此包有非常多的工具类可以使用,比如常用的判断字符串是否为空,快速复写实体类的 toString() 方法等。

<dependency>

    <groupId>org.apache.commons</groupId>

    <artifactId>commons-lang3</artifactId>

    <version>3.6</version>

</dependency>

application 配置

application.properties 添加相关配置:

mybatis.config-locations=classpath:mybatis/mybatis-config.xml

mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

mybatis.type-aliases-package=com.mybatis.demo.model

spring.datasource.driverClassName = com.mysql.cj.jdbc.Driver

spring.datasource.url = jdbc:mysql://localhost:3306/localtest?useUnicode=true&characterEncoding=utf-8

spring.datasource.username = root

spring.datasource.password root

  • mybatis.config-locations:配置 mybatis-config.xml 路径,mybatis-config.xml 中配置 MyBatis 基础属性。

  • mybatis.mapper-locations:配置 Mapper 对应的 XML 文件路径

  • mybatis.type-aliases-package:配置项目中实体类包路径

  • spring.datasource.*:数据源配置

  • 这些config和mapper的xml路径配置在项目的 src —> main —>resource —> mybatis (config文件) —>mapper(所有项目模块的相关Sql操作的xml文件)



Spring Boot 启动时数据源会自动注入到 SqlSessionFactory 中,使用 SqlSessionFactory 构建 SqlSessionFactory,再自动注入到 Mapper 中,最后我们直接使用 Mapper 即可。

启动类

在启动类中添加对 Mapper 包扫描@MapperScan,Spring Boot 启动的时候会自动加载包路径下的 Mapper类

@SpringBootApplication

@MapperScan("com.example.mybatisdemo.mapper")

public class MybatisdemoApplication {

   public static void main(String[] args) {

      SpringApplication.run(MybatisdemoApplication.class, args);

   }

}

或者直接在 Mapper 类上面添加注解@Mapper,建议使用上面那种,不然每个 Mapper 加个注解会很麻烦。


Model类的编写:

package com.example.mybatisdemo.model;

import java.io.Serializable;

public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private long userId;

    private String name;

    private int age;

    public  User(String name, int age){

        this.name = name;

        this.age = age;

    }

    public User(long userId, String name, int age) {

        this.userId = userId;

        this.name = name;

        this.age = age;

    }

    @Override

    public String toString() {

        return "User{" +

                "userId=" + userId +

                ", name='" + name + '\'' +

                ", age=" + age +

                '}';

    }

    public long getUserId() {

        return userId;

    }

    public void setUserId(long userId) {

        this.userId = userId;

    }

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

    public int getAge() {

        return age;

    }

    public void setAge(int age) {

        this.age = age;

    }

}

参数类的编写:

package com.example.mybatisdemo.model;

public class UserParam extends PageParam{

    private String name;

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

}

package com.example.mybatisdemo.model;

import java.io.Serializable;

import java.util.ArrayList;

import java.util.List;

public class Page<E> implements Serializable {

    private static final long serialVersionUID = -2020350783443768083L;

    private int currentPage = 1; //当前页数

    private long totalPage; //总页数

    private long totalNumber; //总记录数

    private List<E> list; //数据集

    public static Page NULL = new Page(0, 0, 15, new ArrayList());

    public Page() {

        super();

    }

    /**

     * @param beginLine 当前页数

     * @param totalNumber 总记录数

     * @param pageSize 页大小

     * @param list 页数据

     */

    public Page(int beginLine, long totalNumber, int pageSize, List<E> list) {

        super();

        this.currentPage = beginLine / pageSize + 1;

        this.totalNumber = totalNumber;

        this.list = list;

        this.totalPage = totalNumber % pageSize == 0 ? totalNumber

                / pageSize : totalNumber / pageSize + 1;

    }

    public Page(PageParam pageParam, long totalNumber, List<E> list){

        super();

        this.currentPage = pageParam.getCurrentPage();

        this.totalNumber = totalNumber;

        this.list = list;

        this.totalPage = totalNumber % pageParam.getPageSize() == 0 ? totalNumber

                / pageParam.getPageSize() : totalNumber / pageParam.getPageSize() + 1;

    }

    public static long getSerialVersionUID() {

        return serialVersionUID;

    }

    public int getCurrentPage() {

        return currentPage;

    }

    public void setCurrentPage(int currentPage) {

        this.currentPage = currentPage;

    }

    public long getTotalPage() {

        return totalPage;

    }

    public void setTotalPage(long totalPage) {

        this.totalPage = totalPage;

    }

    public long getTotalNumber() {

        return totalNumber;

    }

    public void setTotalNumber(long totalNumber) {

        this.totalNumber = totalNumber;

    }

    public List<E> getList() {

        return list;

    }

    public void setList(List<E> list) {

        this.list = list;

    }

    @Override

    public String toString() {

        return "Page{" +

                "currentPage=" + currentPage +

                ", totalPage=" + totalPage +

                ", totalNumber=" + totalNumber +

                ", list=" + list +

                '}';

    }

}

分页类的编写

package com.example.mybatisdemo.model;

import java.io.Serializable;

import java.util.ArrayList;

import java.util.List;

public class Page<E> implements Serializable {

    private static final long serialVersionUID = -2020350783443768083L;

    private int currentPage = 1; //当前页数

    private long totalPage; //总页数

    private long totalNumber; //总记录数

    private List<E> list; //数据集

    public static Page NULL = new Page(0, 0, 15, new ArrayList());

    public Page() {

        super();

    }

    /**

     * @param beginLine 当前页数

     * @param totalNumber 总记录数

     * @param pageSize 页大小

     * @param list 页数据

     */

    public Page(int beginLine, long totalNumber, int pageSize, List<E> list) {

        super();

        this.currentPage = beginLine / pageSize + 1;

        this.totalNumber = totalNumber;

        this.list = list;

        this.totalPage = totalNumber % pageSize == 0 ? totalNumber

                / pageSize : totalNumber / pageSize + 1;

    }

    public Page(PageParam pageParam, long totalNumber, List<E> list){

        super();

        this.currentPage = pageParam.getCurrentPage();

        this.totalNumber = totalNumber;

        this.list = list;

        this.totalPage = totalNumber % pageParam.getPageSize() == 0 ? totalNumber

                / pageParam.getPageSize() : totalNumber / pageParam.getPageSize() + 1;

    }

    public static long getSerialVersionUID() {

        return serialVersionUID;

    }

    public int getCurrentPage() {

        return currentPage;

    }

    public void setCurrentPage(int currentPage) {

        this.currentPage = currentPage;

    }

    public long getTotalPage() {

        return totalPage;

    }

    public void setTotalPage(long totalPage) {

        this.totalPage = totalPage;

    }

    public long getTotalNumber() {

        return totalNumber;

    }

    public void setTotalNumber(long totalNumber) {

        this.totalNumber = totalNumber;

    }

    public List<E> getList() {

        return list;

    }

    public void setList(List<E> list) {

        this.list = list;

    }

    @Override

    public String toString() {

        return "Page{" +

                "currentPage=" + currentPage +

                ", totalPage=" + totalPage +

                ", totalNumber=" + totalNumber +

                ", list=" + list +

                '}';

    }

}

开发 SQL

MyBatis 公共属性

mybatis-config.xml,主要配置常用的 typeAliases,设置类型别名为 Java 类型设置一个短的名字。它只和 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>

    <typeAliases>

        <typeAlias alias="Integer" type="java.lang.Integer" />

        <typeAlias alias="Long" type="java.lang.Long" />

        <typeAlias alias="HashMap" type="java.util.HashMap" />

        <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />

        <typeAlias alias="ArrayList" type="java.util.ArrayList" />

        <typeAlias alias="LinkedList" type="java.util.LinkedList" />

    </typeAliases>

</configuration>


这样在使用 Mapper.xml 的时候,需要引入可以直接这样写:

resultType="Integer"

//或者

parameterType="Long"

添加 User 的映射文件,UserMapper.xml

(1)指明对应文件的 Mapper 类地址:

<mapper namespace="com.example.mybatisdemo.mapper.UserMapper" >

(2)配置表结构和类的对应关系:

<mapper namespace="com.example.mybatisdemo.mapper.UserMapper" >

    <resultMap id="BaseResultMap" type="com.example.mybatisdemo.model.User" >

        <id column="userId" property="userId" jdbcType="BIGINT" />

        <result column="name" property="name" jdbcType="VARCHAR" />

        <result column="age" property="age" jdbcType="INTEGER" />

    </resultMap>


(3)写具体的 SQL 语句:

MyBatisXML 有一个特点是可以复用 XML,比如公用的一些 XML 片段可以提取出来,在其他 SQL 中去引用,如:

<sql id="Base_Column_List" >

    userId, name, age

</sql>

<sql id="Base_Where_List">

    <if test="name != null and name != ''">

        and name = #{userName}

    </if>

</sql>

这个例子就是,上面定义了需要查询的表字段,下面 SQL 使用 include 引入,避免了写太多重复的配置内容。


完整的UserMapper.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.example.mybatisdemo.mapper.UserMapper" >

    <resultMap id="BaseResultMap" type="com.example.mybatisdemo.model.User" >

        <id column="userId" property="userId" jdbcType="BIGINT" />

        <result column="name" property="name" jdbcType="VARCHAR" />

        <result column="age" property="age" jdbcType="INTEGER" />

    </resultMap>

    <sql id="Base_Column_List" >

        userId, name, age

    </sql>

    <sql id="Base_Where_List">

        <if test="name != null and name != ''">

            and name = #{userName}

        </if>

    </sql>

    <select id="getAll" resultMap="BaseResultMap" >

        SELECT

        <include refid="Base_Column_List" />

        FROM user_info

    </select>

    <select id="getList" resultMap="BaseResultMap" parameterType="com.example.mybatisdemo.model.PageParam">

        select

        <include refid="Base_Column_List" />

        from user_info

        where 1=1

        <include refid="Base_Where_List" />

        order by userId desc

        limit #{beginLine} , #{pageSize}

    </select>

    <select id="getCount" resultType="Integer" parameterType="com.example.mybatisdemo.model.UserParam">

        select

        count(1)

        from user_info

        where 1=1

        <include refid="Base_Where_List" />

    </select>

    <select id="getOne" parameterType="Long" resultMap="BaseResultMap" >

        SELECT

        <include refid="Base_Column_List" />

        FROM user_info

        WHERE userId = #{id}

    </select>

    <insert id="insert" keyProperty="userId" useGeneratedKeys="true" parameterType="com.example.mybatisdemo.model.User" >

        INSERT INTO

        user_info

        (name,age)

        VALUES

        (#{name}, #{age})

    </insert>

    <update id="update" parameterType="com.example.mybatisdemo.model.User" >

        UPDATE

        user_info

        SET

        <if test="name != null">name = #{name},</if>

        name = #{name}

        WHERE

        userId = #{userId}

    </update>

    <delete id="delete" parameterType="Long" >

        DELETE FROM

        user_info

        WHERE

        userId =#{userId}

    </delete>

</mapper>


上面的 SQL 使用了if标签,可以根据不同的条件动态的生产 SQL,这就是 MyBatis 最大的特点。




编写 Dao 层的代码,我们放倒java包的mapper文件夹下(com.example.mybatisdemo.mapper)

package com.example.mybatisdemo.mapper;

import com.example.mybatisdemo.model.User;

import com.example.mybatisdemo.model.UserParam;

import java.util.List;

public interface UserMapper {

    List<User> getAll();

    List<User> getList(UserParam userParam);

    int getCount(UserParam userParam);

    User getOne(Long id);

    void insert(User user);

    int update(User user);

    int delete(Long id);

}


注意:这里的方法名需要和 XML 配置中的 id 属性一致,不然会找不到方法去对应执行的 SQL。


Controller的编写

package com.example.mybatisdemo.controller;

import com.example.mybatisdemo.mapper.UserMapper;

import com.example.mybatisdemo.model.Page;

import com.example.mybatisdemo.model.User;

import com.example.mybatisdemo.model.UserParam;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

import java.util.List;

@RestController

public class UserController {

    @Resource

    private UserMapper userMapper;

    @RequestMapping("/getUsers")

    public List<User> getUsers() {

        List<User> users=userMapper.getAll();

        return users;

    }

    @RequestMapping("/getList")

    public Page<User> getList(UserParam userParam) {

        List<User> users=userMapper.getList(userParam);

        long count=userMapper.getCount(userParam);

        Page page = new Page(userParam,count,users);

        return page;

    }

    @RequestMapping("/getUser")

    public User getUser(Long id) {

        User user=userMapper.getOne(id);

        return user;

    }

    @RequestMapping("/add")

    public void save(User user) {

        userMapper.insert(user);

    }

    @RequestMapping(value="update")

    public void update(User user) {

        userMapper.update(user);

    }

    @RequestMapping(value="/delete/{id}")

    public void delete(@PathVariable("id") Long id) {

        userMapper.delete(id);

    }

}



测试使用

接下来直接使用 userMapper 进行数据库操作即可。

package com.example.mybatisdemo;

import com.example.mybatisdemo.mapper.UserMapper;

import com.example.mybatisdemo.model.Page;

import com.example.mybatisdemo.model.User;

import com.example.mybatisdemo.model.UserParam;

import org.junit.Assert;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.context.junit4.SpringRunner;