SpringBoot集成MyBatis通用Mapper4

Posted 一宿君

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot集成MyBatis通用Mapper4相关的知识,希望对你有一定的参考价值。

1、为什么使用通用Mapper

我们知道Mybatis是一个完全与数据库打交道的工具,最初我们对于数据库的操作,即使是使用了Mybatis之后,大部分也都是手动编辑xml配置文件,其实这也是很消耗时间的,后来就出现了MyBatis代码生成器(简称MBG),可以根据数据库链接自动逆向生成实体类Mapper接口XML配置文件 ,但是在后续使用MBG的过程中,如果数据库字段比较多,且变化比较频繁的话,就需要反复重新生成代码,并且由于MBG会覆盖生成代码和追加生成XML配置文件,导致每次重新生成的配置文件都要进行大量的比对修改,这就很烦人了,不是我们最初想要的结果。

其实在我们工作当中大多数情况还有一个问题,就是我们实际操作中对于单表的操作,仅仅只是增删改查,根本没有必要生成一大堆sql,这样的话会使xml文件产生大量冗余,内容多,看着不舒服,运行起来也消耗时间内存。

MBG中其实也定义了很多常用的单标操作方法,为了解决上述的问题,也为了兼容MBG的方法避免项目重构频繁,在MBG的基础上结合了部分JPA的部分注解就产生了通用Mapper,通用Mapper可以很简单的获取基础的单表操作方法,也可以很方便的扩展通用方法。

作用及优点:

  • 极大的提升开发效率,减少工作量。

  • 可以随意按照自己的需要选择通用方法。

  • 很方便的开发自己的通用方法。

  • 极其方便的使用MyBatis单表的正删改查。

  • 支持单表操作,不支持通用的多表联合查询。

  • 强调:

    实体类中不是数据库中的字段需要添加@Transient注解(排除映射)。

2、Spring Boot 集成

Spring Boot 在微服务领域中已经成为主流。

这里简单介绍通用 Mapper 如何同 Spring Boot 进行集成。

为了能适应各种情况的用法,这里也提供了多种集成方式,基本上分为两大类。

  • 基于 starter 的自动配置
  • 基于 @MapperScan 注解的手工配置

在 starter 的逻辑中,如果你没有使用 @MapperScan 注解,你就需要在你的接口上增加 @Mapper 注解,否则 MyBatis 无法判断扫描哪些接口。

通用Mapper需要MyBatis的依赖!!!

2.1、引入相关依赖

				<!--mybatis集成springboot-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </dependency>

        <!--通用mapper集成springboot-->
        <!-- https://mvnrepository.com/artifact/tk.mybatis/mapper-spring-boot-starter -->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.1.5</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>

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

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

mapper最新版本号如上所示,你也可以从下面地址查看:

http://mvnrepository.com/artifact/tk.mybatis/mapper-spring-boot-starter

注意:引入该 starter 时,和 MyBatis 官方的 starter 没有冲突,但是官方的自动配置不会生效!

如果你需要对特定的通用 Mapper 进行配置,你可以在 Spring Boot 的配置文件中配置 mapper. 前缀的配置。

例如在 yml 格式中配置:

mapper:
  mappers:
    - com.wbs.dao.Mapper1  #接口1
    - com.wbs.dao.Mapper2  #接口2 
  notEmpty: true

在 properties 配置中:

mapper.mappers=com.wbs.dao.Mapper1,com.wbs.dao.Mapper2
mapper.notEmpty=true

由于 Spring Boot 支持 Relax 方式的参数,因此你在配置 notEmpty 时更多的是用 not-empty,也只有在 Spring Boot 中使用的时候参数名不必和配置中的完全一致。

2.2、@MapperScan 注解配置

你可以给带有 @Configuration 的类配置该注解,或者直接配置到 Spring Boot 的启动类上,如下:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
@MapperScan("com.wbs.dao")
public class GeneralMapperApplication 
    public static void main(String[] args) 
        SpringApplication.run(GeneralMapperApplication.class, args);
    

注意:这里使用的 tk.mybatis.spring.annotation.MapperScan!

你可以直接在 Spring Boot 的配置文件中直接配置通用 Mapper,还可以使用注解中提供的两个属性进行配置:

/**
 * 通用 Mapper 的配置,一行一个配置
 *
 * @return
 */
String[] properties() default ;

/**
 * 还可以直接配置一个 MapperHelper bean
 *
 * @return
 */
String mapperHelperRef() default "";

注意:这两个属性配置方式的优先级更高,所以建议在 Spring Boot 中通过配置文件(或 Environment)配置。

3、对象关系映射

3.1、简单示例

示例针对 MySql 数据库(数据库对主键影响较大,和 insert 关系密切)。

数据库有如下表:

CREATE TABLE `user_info` (  
	`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id', 
  `name` varchar(255) DEFAULT NULL COMMENT '姓名', 
  `des` varchar(255) DEFAULT NULL COMMENT '描述', 
   PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4

对应的 Java 实体类型如下:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "user_info")
public class UserInfo implements Serializable 

    private static final long serialVersionUID = 1L;

    /**
     * 主键id
     * @mbg.generated Mon Dec 13 15:55:47 CST 2021
     */
    @Id
    @KeySql(useGeneratedKeys = true)
    private Long id;

    /**
     * 姓名
     * @mbg.generated Mon Dec 13 15:55:47 CST 2021
     */
    private String name;

    /**
     * 描述
     * @mbg.generated Mon Dec 13 15:55:47 CST 2021
     */
    private String des;

最简单的情况下,只需要一个 @Id 注解标记字段为主键即可。数据库中的字段名和实体类中的字段名是完全相同的,这中情况下实体和表可以直接映射,也就是不需要@Column注解了。

提醒:如果实体类中没有一个标记 @Id 的字段,当你使用带有 ByPrimaryKey(根据主键查询) 的方法时,所有的字段都会作为主键来联合使用,也就会出现类似 where id = ? and name = ? and des= ? 的情况,这种情况效率是极低的。

  • 后续会介绍代码生成器,可以自动生成上面的实体和下面的接口代码!

通用 Mapper 提供了大量的通用接口,这里以最常用的 Mapper 接口为例

该实体类对应的数据库操作接口如下:

import tk.mybatis.mapper.common.Mapper;

public interface UserInfoMapper extends Mapper<UserInfo> 


在SpringBoot中只需在启动类上配置注解@MapperScan("接口路径"), 能注册并且被扫描到,该接口提供的方法就都可以使用。

一旦继承了Mapper<T> 接口, 所继承的Mapper接口就拥有了以下通用的方法:

//根据实体类不为null的字段进行查询,条件全部使用=号and条件
List<T> select(T record);

//根据实体类不为null的字段查询总数,条件全部使用=号and条件
int selectCount(T record);

//根据主键进行查询,必须保证结果唯一
//单个字段做主键时,可以直接写主键的值
//联合主键时,key可以是实体类,也可以是Map
T selectByPrimaryKey(Object key);

//插入一条数据
//支持Oracle序列,UUID,类似Mysql的INDENTITY自动增长(自动回写)
//优先使用传入的参数值,参数值空时,才会使用序列、UUID,自动增长
int insert(T record);

//插入一条数据,只插入不为null的字段,不会影响有默认值的字段
//支持Oracle序列,UUID,类似Mysql的INDENTITY自动增长(自动回写)
//优先使用传入的参数值,参数值空时,才会使用序列、UUID,自动增长
int insertSelective(T record);

//根据实体类中字段不为null的条件进行删除,条件全部使用=号and条件
int delete(T key);

//通过主键进行删除,这里最多只会删除一条数据
//单个字段做主键时,可以直接写主键的值
//联合主键时,key可以是实体类,也可以是Map
int deleteByPrimaryKey(Object key);

//根据主键进行更新,这里最多只会更新一条数据
//参数为实体类
int updateByPrimaryKey(T record);

//根据主键进行更新
//只会更新不是null的数据
int updateByPrimaryKeySelective(T record);

以上是常见的增删改查单表操作,还有很多类似的方法就不一一列举了!

3.2、测试

从 Spring容器中获取到该接口的Bean之后就可以直接使用:

		@Autowired
    UserInfoMapper userInfoMapper;

    /**
     * 使用通用mapper中的selectAll方法
     */
    @Test
    void test1()
        List<UserInfo> userInfos = userInfoMapper.selectAll();
        for (UserInfo userInfo : userInfos) 
            System.out.println(userInfo.toString());
        
    

    /**
     * 根据实体类不为null的字段查询总数,条件全部使用=号and条件
     */
    @Test
    void test3()
        int count = userInfoMapper.selectCount(new UserInfo( 1L,"张三","法外狂徒"));
        System.out.println("人数为:" + count);
    

    /**
     * 根据主键进行查询,必须保证结果唯一
     * 单个字段做主键时,可以直接写主键的值
     * 联合主键时,key可以是实体类,也可以是Map
     */
    @Test
    void test4()
        UserInfo userInfo1 = new UserInfo();
        userInfo1.setId(1L);
        UserInfo userInfo = userInfoMapper.selectByPrimaryKey(userInfo1);
        System.out.println("主键为1的货是:" + userInfo);
    

    /**
     * 插入一条数据
     * 支持Oracle序列,UUID,类似Mysql的INDENTITY自动增长(自动回写)
     * 优先使用传入的参数值,参数值空时,才会使用序列、UUID,自动增长
     */
    @Test
    void test5()
        UserInfo userInfo = new UserInfo();
        userInfo.setName("马六");
        userInfo.setDes("吃瓜群众");
        int result = userInfoMapper.insert(userInfo);
        if (result > 0)
            System.out.println(userInfo + ":该条数据插入成功!");
        
    

    /**
     * 插入一条数据,只插入不为null的字段,不会影响有默认值的字段
     * 支持Oracle序列,UUID,类似Mysql的INDENTITY自动增长(自动回写)
     * 优先使用传入的参数值,参数值空时,才会使用序列、UUID,自动增长
     */
    @Test
    void test6()
        UserInfo userInfo = new UserInfo();
        userInfo.setName("啥七");
        //userInfo.setDes("啥也不是");
        int result = userInfoMapper.insertSelective(userInfo);
        if (result > 0)
            System.out.println(userInfo + ":该条数据插入成功!");
        
    

    /**
     * 根据实体类中字段不为null的条件进行删除,条件全部使用=号and条件
     */
    @Test
    void test7()
        UserInfo userInfo = new UserInfo();
        userInfo.setName("啥七");
        int result = userInfoMapper.delete(userInfo);
        if (result > 0)
            System.out.println(userInfo + ":该条数据删除成功!");
        
    

    /**
     * 通过主键进行删除,这里最多只会删除一条数据
     * 单个字段做主键时,可以直接写主键的值
     * 联合主键时,key可以是实体类,也可以是Map
     */
    @Test
    void test8()
        int result = userInfoMapper.deleteByPrimaryKey(4L);
        if (result > 0)
            System.out.println(4L + "主键:该条数据删除成功!");
        
    

    /**
     * 根据主键进行更新,这里最多只会更新一条数据
     * 参数为实体类;参数实体类中字段为null的字段也会更新
     */
    @Test
    void test9()
        UserInfo userInfo = new UserInfo();
        userInfo.setId(3L);
        userInfo.setName("王五五五五五");

        int result = userInfoMapper.updateByPrimaryKey(userInfo);
        if (result > 0)
            System.out.println(userInfo + ":该条数据更新成功!");
        
    

    /**
     * 根据主键进行更新
     * 只会更新参数实体类中不是null的数据
     */
    @Test
    void test10()
        UserInfo userInfo = new UserInfo();
        userInfo.setId(2L);
        userInfo.setName("李四四四四");

        int result = userInfoMapper.updateByPrimaryKeySelective(userInfo);
        if (result > 0)
            System.out.println(userInfo + ":该条数据更新成功!");
        
    

如果想要增加自己写的方法,可以直接在 UserInfoMapper 中增加。

方式1:

import tk.mybatis.mapper.common.Mapper;
import java.util.List;

public interface UserInfoMapper extends Mapper<UserInfo> 

    /**
     * 自定义查询所有方法
     * @return
     */
    List<UserInfo> selectAll2();

对应的UserInfoMapper.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.wbs.dao.UserInfoMapper">

    <!--查询所有用户信息-->
    <select id="selectAll2" resultType="com.wbs.pojo.UserInfo">
        select * from user_info
    </select>

</mapper>

方式2(基于JPA注解方式):

import tk.mybatis.mapper.common.Mapper;

public interface UserInfoMapper extends Mapper<UserInfo> 

    /**
     * 手动编写,根据名字查询用户
     * @param name
     * @return
     */
    @Select("select * from user_info where name = #name")
    UserInfo selectByName(String name);

上述两种方式均可以实现自定义扩展方法,并且互不影响,在接口中添加其他方法的时候和只用 MyBatis 效果是完全一样的,但是需要注意,在对应的 XML 中,不能出现和继承通用Mapper接口中同名的方法!

  • 多态!

    在接口中,只要不是通过注解来实现接口方法,接口是允许重名的,真正调用会使用通用 Mapper 提供的方法。

3.3、数据库映射(注解)

上述看到的是最简单的情况,其实实际使用过程中也不会更复杂,下面是更详细的映射配置。

通用 Mapper 中,默认情况下是将实体类字段按照驼峰转下划线形式的表名列名进行转换。

例如:

实体类的 userName 可以映射到表的 user_name 上。

如果想要修改默认的转换方式,可以在后续的配置中,修改 style 全局配置。

数据库映射主要涉及到一些注解和全局配置,这里会介绍所有注解,后面会有配置的介绍。

通用 Mapper 默认使用了几个简单的注解,其他 JPA 的注解默认并不支持,但是如果你开发自己的通用方法,你可以使用 JPA 注解或者引入自己的注解。

3.3.1、@NameStyle 注解(Mapper)

这个注解可以在类上进行配置,优先级高于对应的 style 全局配置。

注解支持以下几个选项:

normal,                     //原值
camelhump,                  //驼峰转下划线
uppercase,                  //转换为大写
lowercase,                  //转换为小写
camelhumpAndUppercase,      //驼峰转下划线大写形式
camelhumpAndLowercase,      //驼峰转下划线小写形式

使用时,直接在类上配置即可,例如:

@NameStyle(Style.camelhumpAndUppercase)
public class UserInfo

配置该注解后,对该类和其中的字段进行转换时,会将形如 userName 的字段转换为表中的 USER_NAME 字段。

3.3.2、@Table 注解(JPA)

@Table 注解可以配置 name,catalogschema 三个属性,配置 name 属性后,直接使用提供的表名,不再根据实体类名进行转换。其他两个属性中,同时配置时,catalog 优先级高于 schema,也就是只有 catalog 会生效。

配置示例如下:

//只要这里对应数据库表名即可,也就是将UserInfoTable实体类映射到user_info数据库表
@Table(name = "user_info") 
public class UserInfoTable

3.3.3、@Column 注解(JPA)

@Column 注解支持 name, insertableupdateable 三个属性。

  • name 配置映射的列名。
  • insertable 对提供的 insert 方法有效,设置为 false 就不会出现在 SQL 中。
  • updateable 对提供的 update 方法有效,设置为 false 就不会出现在 SQL 中。

配置示例如:

@Column(name = "user_name")
private String name;

除了直接映射 nameuser_name 这种用法外,在使用关键字的情况,还会有下面的用法:

//会用英文的``引起来
@Column(name = "`des`")
private String des;

对于关键字这种情况,通用 Mapper 支持自动转换,可以查看后续配置文档中的 wrapKeyword 配置。

3.3.4、@ColumnType 注解(Mapper)

这个注解提供了三个属性:

  • column属性和 @Column 中的 name 属性作用相同,但是 @Column 的优先级更高。
  • jdbcType 属性是指定字段的类型。
  • typeHandler 用于设置特殊类型处理器,常见的是枚举。

用法示例如下:

@ColumnType以上是关于SpringBoot集成MyBatis通用Mapper4的主要内容,如果未能解决你的问题,请参考以下文章

spring boot集成MyBatis 通用Mapper 使用总结

Springboot集成mybatis通用Mapper与分页插件PageHelper(推荐)

SpringBoot + MyBatis-Plus +SpringSecurity +Thymeleaf +LayUI 通用业务模块集成安全认证

SpringBoot + MyBatis-Plus +SpringSecurity +Thymeleaf +LayUI 通用业务模块集成安全认证

Springboot集成mybatis

个人项目实战3,springboot集成mybatis分页插件