1、通用 Mapper 介绍
- 通用Mapper都可以极大的方便开发人员。可以随意的按照自己的需要选择通用方法,还可以很方便的开发自己的通用方法。
- 极其方便的使用MyBatis单表的增删改查。
- 支持单表操作,不支持通用的多表联合查询。
- 原生 Mapper 的难点:
- mapper.xml文件里有大量的sql,当数据库表字段变动,配置文件就要修改
- 需要自己实现sql分页,select * from table where . . . limit 1,3。自己手写分页,除了传参page、pageSize,还需要返回条目总数count。
- 数据库可移植性差:如果项目更换数据库,比如oracle–>mysql,mapper.xml中的sql要重新写,因为Oracle的PLSQL 和mysql 支持的函数是不同的。
- 生成的代码量过大。
- 批量操作,批量插入,批量更新,需要自写。
- 这个东西使用后,会不会降低我们的代码执行效率呢?
其实他的原理就是通过Mybatis的拦截器原理,利用反射机制拼出的 XML 形式的动态 SQL 然后去执行,并且内部对生成的 SQL 实现有缓存机制,所以你说代码时间消耗肯定也会有一些,但是这是很小很小的,再者在现在项目中,对于提供给前端的接口,通常都会利用些组件进行访问加速(毕竟直接从数据中检索不如在内存中来的快些),比如:redis、memcache、elastic search、solr 等,所以执行效率问题是可以避免或忽略的。 - 如果有些方法不想使用,例如:用户的接口服务不能使用删除方法也不想暴漏出来,该怎么办呢?
我们通常会定义增、删、改、查四个基础 Mapper 接口,之后可以按需要引入进行使用,其实即使你使用了公共的 CrudMapper(代表增删改查都在一起的类)也没有问题,只要你在 service 层不要放出删除方法也是可以的。
2、快速使用通用 Mapper
2.1、创建表、实体类和 Mapper 接口
表代码:
CREATE TABLE `table_emp` (
`emp_id` int NOT NULL AUTO_INCREMENT ,
`emp_name` varchar(500) NULL ,
`emp_salary` double(15,5) NULL ,
`emp_age` int NULL ,
PRIMARY KEY (`emp_id`)
);
INSERT INTO `tabple_emp` (`emp_name`, `emp_salary`, `emp_age`) VALUES ('tom', '1254.37', '27');
INSERT INTO `tabple_emp` (`emp_name`, `emp_salary`, `emp_age`) VALUES ('jerry', '6635.42', '38');
INSERT INTO `tabple_emp` (`emp_name`, `emp_salary`, `emp_age`) VALUES ('bob', '5560.11', '40');
INSERT INTO `tabple_emp` (`emp_name`, `emp_salary`, `emp_age`) VALUES ('kate', '2209.11', '22');
INSERT INTO `tabple_emp` (`emp_name`, `emp_salary`, `emp_age`) VALUES ('justin', '4203.15', '30');
实体类代码:
考虑到基本数据类型在 Java 类中都有默认值,会导致 MyBatis 在执行相关操作时很难判断当前字段是否为 null,所以如阿里开发手册所言,Java 实体类尽量不要使用基本数据类型,都使用对应的包装类型。
@Data // lombok 插件,自动生成 get、set 等方法
public class Employee {
private Integer empId;
private String empName;
private Double empSalary;
private Integer empAge;
}
操作数据库的 Mapper 接口代码:
public interface EmployeeMapper {
}
2.2、集成通用 Mapper
在 pom 文件中引入通用 Mapper 的启动器
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.3</version>
</dependency>
2.3、配置扫描包
在 SpringBoot 启动类上面添加扫描包注解。此时用的是通用 Mapper 的扫描注解(其与原生 Mybatis 扫描包注解同名)
原始扫描包注解全类名:
import org.mybatis.spring.mapper.MapperScannerConfigurer
通用 Mapper 扫描包注解全类名:
import tk.mybatis.spring.mapper.MapperScannerConfigurer
原因是:由通用 Mapper 扫描的 dao 层接口,会在进入容器前,生成通用方法。
2.4、Mapper 接口继承通用 Mapper 的核心接口 Mapper< T >
/**
* 具体操作数据库的 Mapper 接口,需要继承通用 Mapper 提供的核心接口 :Mapper<Employee>
* 泛型类型就是实体类的类型
*/
public interface EmployeeMapper extends Mapper<Employee> {
}
3、常用注解
3.1、@Table
作用: 建立实体类和数据库表之间的对应关系。(指定类对应的表名称)
默认规则为 实体类类名首字母小写作为表名。Employee 类→employee 表。
用法: 在@Table 注解的 name 属性中指定目标数据库表的表名
实体类代码:
@Data
@Table(name = "table_emp")
public class Employee {
private Integer empId;
private String empName;
private BigDecimal empSalary;
private Integer empAge;
}
3.2、@Column
作用: 建立实体类字段和数据库表字段之间的对应关系。
默认规则:
实体类字段:驼峰式命名
数据库表字段:使用“_”区分各个单词
用法: 在@Column 注解的 name 属性中指定目标字段的字段名
实体类代码:
@Data
@Table(name = "table_emp")
public class Employee {
@Column(name = "emp_id")
private Integer empId;
@Column(name = "emp_name")
private String empName;
@Column(name = "emp_salary")
private BigDecimal empSalary;
@Column(name = "emp_age")
private Integer empAge;
}
3.3、@Id
通用 Mapper 在执行 xxxByPrimaryKey(key)方法时,有两种情况:
情况 1:没有使用@Id 注解明确指定主键字段生成如下通用方法:
之所以会生成上面这样的 WHERE 子句是因为通用 Mapper 将实体类中的所有字段都拿来放在一起作为联合主键。
情况 2:使用 @Id 主键明确标记和数据库表中主键字段对应的实体类字段:
@Id
@Column(name = "emp_id")
private Integer empId;
3.4、@GeneratedValue
作用: 让通用 Mapper 在执行 insert 操作之后将数据库自动生成的主键值回写到实体类对象中。
自增主键用法(MySQL):
@Id
@Column(name = "emp_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer empId;
序列主键用法(Oracle):
@Id
@Column(name = "emp_id")
@GeneratedValue(
strategy = GenerationType.IDENTITY,
generator = "select SEQ_ID.nextval from dual")
private Integer empId;
应用场景:
- 增加商品销量…
- 减少商品库存…
- 生成订单数据→封装到 Order 对象中→保存 Order 对象→数据库自动生成主键值→回写到实体类对象 Order 中
- 生成一系列订单详情数据→List< OrderItem >→在每一个 OrderItem 中设置 Order 对象的主键值作为外键→批量保存 List< OrderItem >
3.5、@Transient
用于标记不与数据库表字段对应的实体类字段,即该属性不参与 SQL 语句。若属性没有加该注解,并且在数据库表中找不到匹配列,则会抛出列找不到异常
@Transient
private String otherThings; //非数据库表中字段
4、常用方法
4.1、selectOne 方法
public Employee getOne(Employee employee) {
return employeeMapper.selectOne(employee);
}
- 通用 Mapper 替我们自动生成的 SQL 语句情况如下:
- 实体类封装查询条件生成 WHERE 子句的规则
- 使用非空的值生成 WHERE 子句
- 在条件表达式中使用 “=” 进行比较(= 号局限性大,可以使用的是下文的 QBC 查询)
- 要求必须返回一个实体类结果,如果有多个,则会抛出异常
4.2、xxxByPrimaryKey 方法
public Employee getOne(Employee employee) {
return employeeMapper.selectByPrimaryKey(employee);
}
需要使用 @Id 主键明确标记和数据库表主键字段对应的实体类字段,否则通用 Mapper 会将所有实体类字段作为联合主键。
4.3、xxxSelective 方法
非主键字段如果为 null 值,则不加入到 SQL 语句中。适用于字段很多,但是插入少的情况。如果表字段很多,但是一开始可能只插入某几个字段,用此方法生成的 SQL 语句会很简介,并且便于调试。
5、QBC 查询
5.1、概念
- Query By Criteria
- Criteria 是 Criterion 的复数形式。意思是:规则、标准、准则。在 SQL 语句中相当于查询条件。
- QBC 查询是将查询条件通过 Java 对象进行模块化封装。
5.2、示例代码
//目标 SQL 查询语句:WHERE (emp_salary>? AND emp_age<?) OR (emp_salary<? AND emp_age>?)
//1.创建 Example 对象
Example example = new Example(Employee.class);
//***********************
//i.设置排序信息
example.orderBy("empSalary").asc().orderBy("empAge").desc();
//ii.设置“去重”
example.setDistinct(true);
//iii.设置 select 字段
example.selectProperties("empName","empSalary");
//***********************
//2.通过 Example 对象创建 Criteria 对象
Criteria criteria01 = example.createCriteria();
Criteria criteria02 = example.createCriteria();
//3.在两个 Criteria 对象中分别设置查询条件
//property 参数:实体类的属性名
//value 参数:实体类的属性值
criteria01.andGreaterThan("empSalary", 3000).andLessThan("empAge", 25);
criteria02.andLessThan("empSalary", 5000).andGreaterThan("empAge", 30);
//4.使用 OR 关键词组装两个 Criteria 对象
example.or(criteria02);
//5.执行查询
List<Employee> empList = employeeService.getEmpListByExample(example);
for (Employee employee : empList) {
System.out.println(employee);
}
6、逆向工程
6.1、原生 MyBatis 逆向工程和通用 Mapper 逆向工程对比
6.2、参考文档地址
6.3、在 pom 文件中添加参数
在 pom 文件中写入所有参数并不是硬性的要求,可以在要用的时候硬编码写死到代码中。先把参数放在 pom 文件中方便管理
<properties>
<!-- ${basedir} 引用工程根目录 -->
<!-- targetJavaProject 标签:声明存放源码的目录(接口、实体类、XxxMapper.xml)-->
<targetJavaProject>${basedir}/src/main/java</targetJavaProject>
<!-- targetMapperPackage 标签:声明 MBG 生成 XxxMapper 接口后存放的 package 位置 -->
<!-- 公司域名倒序(com.gongsi).项目名称.包名-->
<targetMapperPackage>per.cjh.example.mappers</targetMapperPackage>
<!-- targetModelPackage 标签:声明 MBG 生成实体类后存放的 package 位置 -->
<!-- 公司域名倒序(com.gongsi).项目名称.包名-->
<targetModelPackage>per.cjh.example.entities</targetModelPackage>
<!-- targetResourcesProject 标签:声明存放资源文件和 XML 配置文件的目录位置-->
<targetResourcesProject>${basedir}/src/main/resources</targetResourcesProject>
<!-- targetXMLPackage 标签:在 targetResourcesProject 标签所配置的目录基础上,套上这一级目录,将生成的 xml 文件放入其中-->
<targetXMLPackage>mappers</targetXMLPackage>
<!-- 通用 mapper 的版本号 -->
<mapper.version>4.0.0</mapper.version>
<!-- mysql 驱动的版本号-->
<mysql.version>8.0.19</mysql.version>
</properties>
6.4、在 pom 文件中添加插件
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.6</version>
<configuration>
<configurationFile>
${basedir}/src/main/resources/generator/generatorConfig.xml
</configurationFile>
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
<!-- MBG 插件的依赖信息 -->
<dependencies>
<!-- 通用 mapper 生成代码时,需要读取数据库。并且引用前面配置的版本号属性 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- 通用 mapper 的版本号 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>${mapper.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
6.5、创建 MBG 配置文件 generatorConfig.xml
注意:配置文件的路径要在上一步声明的路径下面。
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!-- 引入 config.properties 外部文件(内部配置属性,便于属性的管理,使用此方法之后,需要再下一步创建该 config 配置文件) -->
<properties resource="config.properties"/>
<!-- 常规配置 -->
<context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<!-- 配置通用 mapper 的 MBG 插件相关信息 -->
<!-- 引用 config.properties 配置文件里面的通用 mapper 插件类型 -->
<plugin type="${mapper.plugin}">
<property name="mappers" value="${mapper.Mapper}"/>
<property name="caseSensitive" value="true"/>
</plugin>
<!-- 引用数据库连接4大参数 -->
<jdbcConnection driverClass="${jdbc.driverClass}"
connectionURL="${jdbc.url}"
userId="${jdbc.user}"
password="${jdbc.password}">
</jdbcConnection>
<!-- 配置 Java 实体类存放位置,引用的是 pom 文件中的配置参数-->
<javaModelGenerator targetPackage="${targetModelPackage}"
targetProject="${targetJavaProject}"/>
<!-- 配置 XxxMapper.xml 存放位置,引用的是 pom 文件中的配置参数-->
<sqlMapGenerator targetPackage="${targetXMLPackage}"
targetProject="${targetResourcesProject}"/>
<!-- 配置 XxxMapper.java 接口的存放位置,引用的是 pom 文件中的配置参数-->
<javaClientGenerator targetPackage="${targetMapperPackage}"
targetProject="${targetJavaProject}"
type="XMLMAPPER"/>
<!-- 根据数据库表生成 Java 文件的相关规则 -->
<!-- 方法1:tableName='%' 表示数据库中所有表都参与逆向工程,此时使用默认规则 -->
<!-- 默认规则:table_dept(表名) -> TableDept(实体类名) 通常不这么使用,因为此时实体类名带了无意义的前缀 Table -->
<!--
<table tableName="%">
<generatedKey column="id" sqlStatement="Mysql" identity="true"/>
</table>
-->
<!-- 方法2:tableName 属性指定具体的表名,domainObjectName 属性指定对应生成的实体类名称(常用)需要把每个类都写下来,进行配置-->
<table tableName="account" domainObjectName="account">
<!-- 配置主键生成策略,column 为数据库表的主键名 -->
<generatedKey column="id" sqlStatement="Mysql" identity="true"/>
</table>
</context>
</generatorConfiguration>
6.6、运行通用 Mapper 逆向工程
6.6.1、Intellij 环境下
- 点击编译器左下角 Terminal,调出控制台
- 让路径跳转到当前工程,即 pom 文件所在目录。cd 命令 (change dir 改变目录)
- 运行 mvn mybatis-generator:generate 命令
6.6.2、eclipse 环境下
右键 pom 文件,Run As -> Maven build
在弹出窗口的 Goals:文本框内写入右边的命令 mvn mybatis-generator:generate
点击Run